Permalink
Browse files

fix "SVM wrapper doesn't work" (#48)

  • Loading branch information...
erelsgl committed Mar 2, 2017
1 parent fafa9b2 commit be9a6593504b8aa5e806a7fd3d85ab95b3e2ad0d
Showing with 64 additions and 75 deletions.
  1. +37 −42 classifiers/svm/SvmLinear.js
  2. +26 −29 classifiers/svm/SvmPerf.js
  3. +1 −1 package.json
  4. +0 −3 test/classifiersTest/bayesian/README.md
@@ -1,14 +1,14 @@
/**
* A wrapper for the LibLinear package, by Fan, Chang, Hsieh, Wang and Lin.
*
* To use this wrapper, the LibLinear executable (liblinear_train) should be in your path.
*
*
* To use this wrapper, the LibLinear executable (liblinear_train) should be in your path.
*
* You can download LibLinear here: http://www.csie.ntu.edu.tw/~cjlin/liblinear/
* subject to the copyright license.
*
* @author Erel Segal-haLevi
* @since 2013-09-09
*
*
* @param opts options: <ul>
* <li>learn_args - a string with arguments for liblinear_train
* <li>model_file_prefix - prefix to path to model file (optional; the default is to create a temporary file in the system temp folder).
@@ -20,7 +20,7 @@ function SvmLinear(opts) {
if (!SvmLinear.isInstalled()) {
var msg = "Cannot find the executable 'liblinear_train'. Please download it from the LibLinear website, and put a link to it in your path.";
console.error(msg)
throw new Error(msg);
throw new Error(msg);
}
this.learn_args = opts.learn_args || "";
this.model_file_prefix = opts.model_file_prefix || null;
@@ -39,14 +39,11 @@ function SvmLinear(opts) {
}
SvmLinear.isInstalled = function() {
/* try {
var result = execSync(this.train_command);
return true;
} catch (err) {
if (!('stderr' in err))
return true
return err["stderr"].length == 0
}*/
try {
var result = execSync(this.train_command);
} catch (err) {
return false
}
return true
};
@@ -68,7 +65,7 @@ SvmLinear.prototype = {
/**
* Send the given dataset to liblinear_train.
*
* @param dataset an array of samples of the form {input: [value1, value2, ...] , output: 0/1}
* @param dataset an array of samples of the form {input: [value1, value2, ...] , output: 0/1}
*/
trainBatch: function(dataset) {
this.timestamp = new Date().getTime()+"_"+process.pid
@@ -85,15 +82,15 @@ SvmLinear.prototype = {
}, this)
// convert all arraay-like outputs to just values
dataset = _.map(dataset, function(datum){
dataset = _.map(dataset, function(datum){
if (_.isArray(datum.output))
datum.output = datum.output[0]
return datum });
return datum });
this.allLabels = _(dataset).map(function(datum){return datum.output});
this.allLabels = _.uniq(_.flatten(this.allLabels))
// dataset = _.map(dataset, function(datum){
// dataset = _.map(dataset, function(datum){
// datum.output = this.allLabels.indexOf(datum.output)
// return datum });
@@ -119,45 +116,45 @@ SvmLinear.prototype = {
if (this.debug) console.log("trainBatch end");
},
setModel: function(modelFileString) {
// this.modelFileString = modelFileString;
this.modelString = fs.readFileSync(modelFileString, "utf-8")
this.mapLabelToMapFeatureToWeight = modelStringToModelMap(this.modelString);
this.allLabels = Object.keys(this.mapLabelToMapFeatureToWeight);
if (this.debug) console.dir(this.mapLabelToMapFeatureToWeight);
},
getModelWeights: function() {
if (!this.mapLabelToMapFeatureToWeight)
this.setModel(this.modelFileString)
return (this.multiclass? this.mapLabelToMapFeatureToWeight: this.mapLabelToMapFeatureToWeight[1]);
},
/**
* @param features - a feature-value hash.
* @param explain - int - if positive, an "explanation" field, with the given length, will be added to the result.
* @param explain - int - if positive, an "explanation" field, with the given length, will be added to the result.
* @param continuous_output if true, return the net classification score. If false [default], return 0 or 1.
* @return the binary classification - 0 or 1.
*/
classifyBatch: function(trainset) {
// console.log(JSON.stringify(this.modelFileString, null, 4))
_.each(trainset, function(value, key, list){
trainset[key].output = 0
}, this)
var testFile = svmcommon.writeDatasetToFile(
trainset, this.bias, /*binarize=*/false, "/tmp/test_"+this.timestamp, "SvmLinear", FIRST_FEATURE_NUMBER);
var command = this.test_command+" "+testFile + " " + this.modelFileString + " /tmp/out_" + this.timestamp;
var output = child_process.execSync(command)
var output = child_process.execSync(command)
console.log(command)
var result = fs.readFileSync("/tmp/out_" + this.timestamp, "utf-8").split("\n")
return result
},
@@ -171,7 +168,7 @@ SvmLinear.prototype = {
!continuous_output? this.allLabels[0]:
!this.multiclass? 1.0:
[[this.allLabels[0], 1.0]]);
return (explain>0?
return (explain>0?
{
classes: result,
explanation: ["Single label ("+result+") - no classification needed"],
@@ -185,19 +182,19 @@ SvmLinear.prototype = {
var scoreWithExplain = svmcommon.classifyWithModelMap(
mapFeatureToWeight, this.bias, features, explain, /*continuous_output=*/true, this.featureLookupTable);
var score = (explain>0? scoreWithExplain.classification: scoreWithExplain);
var labelAndScore = [parseInt(label), score];
if (scoreWithExplain.explanation && explain>0) {
labelAndScore.push(this.multiclass?
labelAndScore.push(this.multiclass?
scoreWithExplain.explanation.join(" "):
scoreWithExplain.explanation)
}
labels.push(labelAndScore);
}
labels.sort(function(a,b) {return b[1]-a[1]}); // sort by decreasing score
if (explain>0) {
@@ -211,12 +208,12 @@ SvmLinear.prototype = {
var explanations = (labels[0][0]>0? labels[0][2]: labels[1][2])
}
}
var result = (
!continuous_output? labels[0][0]:
!this.multiclass? (labels[0][0]>0? labels[0][1]: labels[1][1]):
labels);
return (explain>0?
return (explain>0?
{
classes: result,
classification: result,
@@ -226,18 +223,18 @@ SvmLinear.prototype = {
},
/**
* Link to a FeatureLookupTable from a higher level in the hierarchy (typically from an EnhancedClassifier), used ONLY for generating meaningful explanations.
* Link to a FeatureLookupTable from a higher level in the hierarchy (typically from an EnhancedClassifier), used ONLY for generating meaningful explanations.
*/
setFeatureLookupTable: function(featureLookupTable) {
this.featureLookupTable = featureLookupTable;
},
toJSON: function() {
return this.mapFeatureToWeight;
return this.mapFeatureToWeight;
},
fromJSON: function(json) {
this.mapFeatureToWeight = json;
this.mapFeatureToWeight = json;
},
};
@@ -279,7 +276,7 @@ function modelStringToModelMap(modelString) {
var weightsMatrix = matches[3];
// each line represents a feature; each column represents a label:
var weightsLines = weightsMatrix.split(NEWLINE);
for (var feature in weightsLines) {
var weights = weightsLines[feature].split(/\s+/);
@@ -306,5 +303,3 @@ function modelStringToModelMap(modelString) {
module.exports = SvmLinear;
View
@@ -1,14 +1,14 @@
/**
* A wrapper for Thorsten Joachims' SVM-perf package.
*
* To use this wrapper, the SVM-perf executable (svm_perf_learn) should be in your path.
*
*
* To use this wrapper, the SVM-perf executable (svm_perf_learn) should be in your path.
*
* You can download SVM-perf here: http://www.cs.cornell.edu/people/tj/svm_light/svm_perf.html
* subject to the copyright license.
*
* @author Erel Segal-haLevi
* @since 2013-09-02
*
*
* @param opts options: <ul>
* <li>learn_args - a string with arguments for svm_perf_learn (see http://www.cs.cornell.edu/people/tj/svm_light/svm_perf.html )
* <li>model_file_prefix - prefix to path to model file (optional; the default is to create a temporary file in the system temp folder).
@@ -18,15 +18,15 @@
var fs = require('fs')
, util = require('util')
, execSync = require('child_process').execSync
, svmcommon = require('./svmcommon')
, svmcommon = require('./svmcommon')
, _ = require("underscore")._;
function SvmPerf(opts) {
if (!SvmPerf.isInstalled()) {
var msg = "Cannot find the executable 'svm_perf_learn'. Please download it from the SvmPerf website, and put a link to it in your path.";
console.error(msg)
throw new Error(msg);
throw new Error(msg);
}
this.learn_args = opts.learn_args || "";
this.learn_args += " --b 0 "; // we add the bias here, so we don't need SvmPerf to add it
@@ -37,14 +37,12 @@ function SvmPerf(opts) {
}
SvmPerf.isInstalled = function() {
/* try {
var result = execSync("svm_perf_learn -c 1 a");
return true;
} catch (err) {
return false;
}
*/
return true
try {
var result = execSync("svm_perf_learn");
} catch (err) {
return false;
}
return true
}
var FIRST_FEATURE_NUMBER=1; // in svm perf, feature numbers start with 1, not 0!
@@ -58,18 +56,18 @@ SvmPerf.prototype = {
/**
* Send the given dataset to svm_perf_learn.
*
* @param dataset an array of samples of the form {input: [value1, value2, ...] , output: 0/1}
* @param dataset an array of samples of the form {input: [value1, value2, ...] , output: 0/1}
*/
trainBatch: function(dataset) {
if (this.debug) console.log("trainBatch start");
var timestamp = new Date().getTime()+"_"+process.pid
var learnFile = svmcommon.writeDatasetToFile(dataset, this.bias, /*binarize=*/true, this.model_file_prefix+"_"+timestamp, "SvmPerf", FIRST_FEATURE_NUMBER);
var modelFile = learnFile.replace(/[.]learn/,".model");
var command = "svm_perf_learn "+this.learn_args+" "+learnFile + " "+modelFile;
if (this.debug) console.log("running "+command);
console.log(command)
var result = execSync(command);
if (result.code>0) {
console.dir(result);
@@ -80,7 +78,7 @@ SvmPerf.prototype = {
this.setModel(fs.readFileSync(modelFile, "utf-8"));
if (this.debug) console.log("trainBatch end");
},
setModel: function(modelString) {
this.modelString = modelString;
this.mapFeatureToWeight = modelStringToModelMap(modelString); // weights in modelMap start from 0 (- the bias).
@@ -100,14 +98,14 @@ SvmPerf.prototype = {
featlist = _.sortBy(featlist, function(num){return num[1]})
return featlist
},
getModelWeights: function() {
return this.mapFeatureToWeight;
},
/**
* @param features - a feature-value hash.
* @param explain - int - if positive, an "explanation" field, with the given length, will be added to the result.
* @param explain - int - if positive, an "explanation" field, with the given length, will be added to the result.
* @param continuous_output if true, return the net classification score. If false [default], return 0 or 1.
* @return the binary classification - 0 or 1.
*/
@@ -117,19 +115,19 @@ SvmPerf.prototype = {
},
/**
* Link to a FeatureLookupTable from a higher level in the hierarchy (typically from an EnhancedClassifier), used ONLY for generating meaningful explanations.
* Link to a FeatureLookupTable from a higher level in the hierarchy (typically from an EnhancedClassifier), used ONLY for generating meaningful explanations.
*/
setFeatureLookupTable: function(featureLookupTable) {
//console.log("SVMPERF setFeatureLookupTable "+featureLookupTable);
this.featureLookupTable = featureLookupTable;
},
toJSON: function() {
return this.mapFeatureToWeight;
return this.mapFeatureToWeight;
},
fromJSON: function(json) {
this.mapFeatureToWeight = json;
this.mapFeatureToWeight = json;
},
};
@@ -139,7 +137,7 @@ SvmPerf.prototype = {
*/
var SVM_PERF_MODEL_PATTERN = new RegExp(
"[\\S\\s]*"+
"[\\S\\s]*"+
"^([\\S\\s]*) # threshold b[\\S\\s]*"+ // parse the threshold line
"^([\\S\\s]*) #[\\S\\s]*" + // parse the weights line
"", "m");
@@ -161,7 +159,7 @@ function modelStringToModelMap(modelString) {
var featuresAndWeights = matches[2].split(" ");
var mapFeatureToWeight = {};
//mapFeatureToWeight.threshold = threshold; // not needed - we use our own bias
//String alphaTimesY = featuresAndWeights[0]; // always 1 in svmperf
for (var i=1; i<featuresAndWeights.length; ++i) {
var featureAndWeight = featuresAndWeights[i];
@@ -182,4 +180,3 @@ function modelStringToModelMap(modelString) {
module.exports = SvmPerf;
View
@@ -1,7 +1,7 @@
{
"name": "limdu",
"description": "A machine learning framework for Node.js. Supports multi-level classification and online learning.",
"version": "0.8.0",
"version": "0.9.0",
"author": "Erel Segal-haLevi <erelsgl@gmail.com>",
"repository": {
"type": "git",

This file was deleted.

Oops, something went wrong.

0 comments on commit be9a659

Please sign in to comment.