Skip to content
Browse files

Prepare for release

  • Loading branch information...
1 parent 8619590 commit f4650356bb5c4b0aeff414aeb9084819078adeed @thisandagain committed
Showing with 185 additions and 44 deletions.
  1. +18 −10 README.md
  2. +117 −25 lib/index.js
  3. +2 −1 package.json
  4. +48 −8 test/index.js
View
28 README.md
@@ -1,9 +1,10 @@
## troll
#### Language sentiment analysis and neural networks... for trolls.
-Troll is a tool for performing sentiment analysis (ie: "is this naughty or nice") on arbitrary blocks of text and associating it with a unique user. Using this data, combined with a rather naivé neural network and some simple training, users can be reliably classified as "trolls".
+Troll is a tool for performing sentiment analysis (ie: "is this naughty or nice") on arbitrary blocks of text and associating it with a unique user. Using this data, combined with a rather naivé neural network and some training, users can be classified as "trolls".
### Installation
+Troll uses [Redis](http://redis.io/) for data storage. Once Redis is up and running, you can install Troll using NPM:
```bash
npm install troll
```
@@ -16,30 +17,37 @@ troll.analyze('This is great!', 'user123', function (err, result) {
console.log(result); // 6
});
-troll.analyze('I hate this... totally stupid.', 'user456', function (err, result) {
+troll.analyze('I hate this stupid thing.', 'user456', function (err, result) {
console.log(result); // -10
});
```
-### User Classification
+### Training
+Before attempting to classify a user, you'll need to train Troll. You can specify your own training data or use a basic set that is included. To load the included training set:
```javascript
-troll.classify('user123', function (err, result) {
- console.dir(result); // { user: 'user123', total: 24, sum: 10, troll: false }
+troll.train(function (err, result) {
+ console.dir(result); // { error: 0.005, iterations: 72 }
});
```
-### Training
+### User Classification
+Once trained, now you can classify:
```javascript
-troll.train('user456', true, function (err) {
- console.log('The neural network has been told that user456 is a "troll"');
+troll.classify('user123', function (err, result) {
+ console.dir(result); // { total: 9, sum: 36, troll: 0.010294962292857838 }
});
```
+The value returned for the `troll` key represents the probability of that user being a troll. A value close to zero means that they are most likely not a troll, while a number closer to one means that they are.
+
---
### Redis Connection Options
-```javascript
-
+Troll uses your environment by looking at `process.env` for connection settings. If none are found, default [Redis](http://redis.io/) connection settings are used:
+```
+TROLL_HOST: null
+TROLL_PORT: null
+TROLL_PASS: null
```
---
View
142 lib/index.js
@@ -10,32 +10,94 @@
*/
var _ = require('underscore'),
async = require('async'),
- sentiment = require('sentiment');
+ brain = require('brain'),
+ sentimental = require('sentimental'),
+ redis = require('redis');
+
+var Adapter = require('./adapter'),
+ Trainer = require('./trainer.json');
+
+/**
+ * Simple numeric range mapping function.
+ *
+ * @param {Number} Input value
+ * @param {Array} Source range (e.g. [-5,5])
+ * @param {Array} Destination range (e.g. [0,1])
+ *
+ * @return {Number}
+ */
+function convertToRange(value, srcRange, dstRange){
+ // value is outside source range return
+ if (value < srcRange[0] || value > srcRange[1]){
+ return NaN;
+ }
+
+ var srcMax = srcRange[1] - srcRange[0],
+ dstMax = dstRange[1] - dstRange[0],
+ adjValue = value - srcRange[0];
+
+ return (adjValue * dstMax / srcMax) + dstRange[0];
+}
+
+/**
+ * Converts redis list into an array of numbers between 0 and 1.
+ *
+ * @param {Array} Input array
+ *
+ * @return {Array}
+ */
+function convertList (list, callback) {
+ // Trim list to the latest 12 values
+ var set = list.slice(0, 10);
+ for (var i = set.length; i < 10; i++) {
+ set.push(0);
+ }
+
+ // Map AFINN data
+ async.map(set, function (item, callback) {
+ callback(null, convertToRange(Number(item), [-10,10], [0,1]));
+ }, callback);
+}
/**
* Constructor
*/
-function Troll (redis) {
+function Troll () {
var self = this;
- _.defaults(redis, {
- host: null,
- port: null
+ _.defaults(process.env, {
+ TROLL_HOST: 'localhost',
+ TROLL_PORT: 6379
});
- self.analyze = sentiment.analyze;
- self.redis = redis.createClient(redis);
+ // Redis connection
+ var client = redis.createClient(
+ Number(process.env.TROLL_PORT),
+ process.env.TROLL_HOST
+ );
+ if (typeof process.env.TROLL_PASS !== 'undefined') {
+ client.auth(process.env.TROLL_PASS, function (err) {
+ if (err) process.stderr.write(err);
+ });
+ }
+
+ // Setup
+ self.adapter = new Adapter(client);
+ self.sentiment = sentimental.analyze;
+ self.net = new brain.NeuralNetwork();
};
/**
* Performs sentiment analysis on a given input.
*
* @param {String} Input text
- * @param {String} Unique user identifier (optional) for tracking
+ * @param {String, Optional} Unique user identifier for tracking
*
* @return {Number}
*/
Troll.prototype.analyze = function (input, user, callback) {
+ var self = this;
+
// Process args
if (typeof callback === 'undefined') {
callback = user;
@@ -43,10 +105,14 @@ Troll.prototype.analyze = function (input, user, callback) {
}
// Analyze input
- var result = self.analyze(input);
+ var result = self.sentiment(input).score;
// Associate
-
+ if (user === null) {
+ callback(null, result);
+ } else {
+ self.adapter.push(user, result, callback);
+ }
};
/**
@@ -55,32 +121,58 @@ Troll.prototype.analyze = function (input, user, callback) {
* @param {String} Unique user identifier
*
* @return {Object}
- * - user {String} User identity
* - total {Number} Total number of analyses performed
- * - average {Number} Average rating
- * - troll {Boolean} Is this user a troll?
+ * - sum {Number} Sum of all ratings
+ * - troll {Number} Probability of this user being a troll
*/
-Troll.prototype.lookup = function (user, callback) {
- // Fetch list from datastore
-
- // Parse
+Troll.prototype.classify = function (user, callback) {
+ var self = this;
- // Calc
+ // Fetch from datastore
+ async.auto({
+ total: function (callback) {
+ self.adapter.length(user, callback);
+ },
+ sum: function (callback) {
+ self.adapter.sum(user, callback);
+ },
+ troll: ['total', 'sum', function (callback, obj) {
+ self.adapter.all(user, function (err, all) {
+ if (err) {
+ callback(err);
+ } else {
+ convertList(all, function (err, result) {
+ callback(null, self.net.run(result)[0]);
+ });
+ }
+ });
+ }]
+ }, callback);
};
/**
- * Trains the neural troll detection network.
+ * Trains the troll detection network. :-)
*
- * @param {String} Unique user identifier
- * @param {Boolean} Is troll?
+ * @param {String} Training data (optional)
*
- * @return {Err}
+ * @return {Object}
+ * - error {Number} Training error
+ * - iterations {Number} Training iterations
*/
-Troll.prototype.train = function (user, troll, callback) {
- // body...
+Troll.prototype.train = function (data, callback) {
+ var self = this;
+
+ // Process arguments
+ if (typeof callback === 'undefined') {
+ callback = data;
+ data = Trainer;
+ }
+
+ // Train
+ callback(null, self.net.train(data));
};
/**
* Export
*/
-module.exports = Troll;
+module.exports = new Troll();
View
3 package.json
@@ -16,7 +16,8 @@
"async": "~0.1.22",
"underscore": "~1.4.1",
"Sentimental": "0.0.2",
- "redis": "~0.8.1"
+ "redis": "~0.8.1",
+ "brain": "~0.6.0"
},
"devDependencies": {
"tap": "~0.3.1",
View
56 test/index.js
@@ -11,26 +11,66 @@
var async = require('async'),
test = require('tap').test,
- target = require(__dirname + '/../lib/index.js');
+ troll = require(__dirname + '/../lib/index.js');
/**
* Suite
*/
async.auto({
- troll: function (callback) {
- target.troll(callback);
+ train: function (callback) {
+ troll.train(callback);
},
- test: ['troll', function (callback, obj) {
+ analyze: function (callback) {
+ troll.analyze('This is lame!', callback);
+ },
+
+ positive: function (callback) {
+ troll.analyze('This is totally awesome!', '0', callback);
+ },
+
+ negative: function (callback) {
+ troll.analyze('This is totally stupid and lame!', '1', callback);
+ },
+
+ classifyh: ['train', 'analyze', 'positive', 'negative', function (callback) {
+ troll.classify('1', callback);
+ }],
+
+ classifyl: ['train', 'analyze', 'positive', 'negative', function (callback) {
+ troll.classify('0', callback);
+ }],
+
+ test: ['classifyh', 'classifyl', function (callback, obj) {
test('Component definition', function (t) {
- t.type(target, 'object', 'Component should be an object');
- t.type(target.troll, 'function', 'Method should be a function');
+ t.type(troll, 'object', 'Component should be an object');
+ t.type(troll.analyze, 'function', 'Method should be a function');
+ t.type(troll.classify, 'function', 'Method should be a function');
+ t.type(troll.train, 'function', 'Method should be a function');
+ t.end();
+ });
+
+ test('Train method', function (t) {
+ t.type(obj.train, 'object', 'Results should be an object');
+ t.end();
+ });
+
+ test('Analyze method', function (t) {
+ t.type(obj.analyze, 'number', 'Results should be a number');
+ t.equal(obj.analyze, -2, 'Expected result');
+ t.type(obj.positive, 'number', 'Results should be a number');
+ t.equal(obj.positive, 4, 'Expected result');
+ t.type(obj.negative, 'number', 'Results should be a number');
+ t.equal(obj.negative, -4, 'Expected result');
t.end();
});
- test('troll method', function (t) {
- t.type(obj.all, 'object', 'Results should be an object');
+ test('Classify method', function (t) {
+ console.dir(obj.classifyh);
+ console.dir(obj.classifyl);
+ t.type(obj.classifyh, 'object', 'Results should be an object');
+ t.type(obj.classifyl, 'object', 'Results should be an object');
t.end();
});

0 comments on commit f465035

Please sign in to comment.
Something went wrong with that request. Please try again.