gullible
Version:
A naive Bayes text classifier.
175 lines (149 loc) • 4.33 kB
JavaScript
/**
* Constructs a naive Bayes classifier.
*/
function Gullible(opts) {
var options = opts || {};
this.wcWordCls = {}; // word count by word and class
this.wcCls = {}; // word count by class
this.dwcCls = {}; // distinct word count by class
this.scCls = {}; // sample count by class
this.sc = 0; // sample count
this.tokenize = options.tokenizer || Gullible.defaultTokenize;
}
/**
* Learns a text-class pair.
*/
Gullible.prototype.learn = function(text, cls) {
var tokens = this.tokenize(text);
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
this.wcWordCls[token] = this.wcWordCls[token] || {};
this.wcWordCls[token][cls] = this.wcWordCls[token][cls] || 0;
this.wcWordCls[token][cls] += 1;
this.wcCls[cls] = this.wcCls[cls] || 0;
this.wcCls[cls] += 1;
if (this.wcWordCls[token][cls] === 1) {
this.dwcCls[cls] = this.dwcCls[cls] || 0;
this.dwcCls[cls] += 1;
}
}
this.scCls[cls] = this.scCls[cls] || 0;
this.scCls[cls] += 1;
this.sc += 1;
};
/**
* Unlearns a previously learned sample.
* WARNING: This method assumes that the sample was previously learned.
* Unlearning a non-existent sample will cause problems. Use at your own risk.
*/
Gullible.prototype.unlearn = function(text, cls) {
var tokens = this.tokenize(text);
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
this.wcWordCls[token][cls] -= 1;
if (this.wcWordCls[token][cls] === 0) {
this.dwcCls[cls] -= 1;
}
this.wcCls[cls] -= 1;
}
this.scCls[cls] -= 1;
if (this.scCls[cls] === 0) {
delete this.scCls[cls];
}
this.sc -= 1;
};
/**
* Estimates a score hinting the relation
* between a list of words and a class.
*/
Gullible.prototype.estimateWithTokens = function(tokens, cls) {
// p(cls | text) ~ p(cls) * [ p(word1 | cls) * ... * p(wordn | cls) ]
// p(word | cls) ~ #{word in cls} / #{all words in cls}
// work with log probabilities to prevent any underflow
var score = Math.log(this.scCls[cls] / this.sc);
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
// apply Laplace smoothing
var smoothedWordCount =
this.wcWordCls[token] && this.wcWordCls[token][cls]
? this.wcWordCls[token][cls] + 1 : 1;
var smoothedAllCount = this.wcCls[cls] + this.dwcCls[cls];
score += Math.log(smoothedWordCount / smoothedAllCount);
}
return score;
};
/**
* Estimates a score hinting the relation
* between a text and class.
*/
Gullible.prototype.estimate = function(text, cls) {
var tokens = this.tokenize(text);
return this.estimateWithTokens(tokens, cls) / tokens.length;
};
/**
* Classifies given text.
*/
Gullible.prototype.classify = function(text) {
var maxCls;
var maxScore = Number.NEGATIVE_INFINITY;
var tokens = this.tokenize(text);
var classes = Object.keys(this.scCls);
for (var i = 0; i < classes.length; i++) {
var score = this.estimateWithTokens(tokens, classes[i]);
if (score > maxScore) {
maxCls = classes[i];
maxScore = score;
}
}
return maxCls;
};
/**
* Getter for classes
*/
Gullible.prototype.getClasses = function() {
return Object.keys(this.scCls);
};
/**
* Getter for words
*/
Gullible.prototype.getWords = function() {
return Object.keys(this.wcWordCls);
};
/**
* Getter for sample count
*/
Gullible.prototype.getSampleCount = function() {
return this.sc;
};
/**
* Default tokenization method.
* Removes any non-word characters. Converts to lowercase.
* Splits at white-space.
*/
Gullible.defaultTokenize = function(text) {
text = text.replace(/\W+/g, ' ');
text = text.toLowerCase();
return text.split(/\s+/g).filter(function(s) {return s !== '';});
};
/**
* Serializes given classifier as a JSON string.
*/
Gullible.toJSON = function(classifier) {
return JSON.stringify(classifier);
};
/**
* Restores the classifier from JSON string.
*/
Gullible.fromJSON = function(str, opts) {
var classifier = new Gullible(opts);
var data = JSON.parse(str);
for (var key in data) {
if (data.hasOwnProperty(key)) {
classifier[key] = data[key];
}
}
return classifier;
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Gullible;
}