//
// rapidLibPost.js
// RapidLib
//
// Created by mzed on 05/09/2016.
// Copyright © 2016 Goldsmiths. All rights reserved.
//
/* globals Module */
"use strict";
console.log("RapidLib 26.9.2017 18:04");
/**
* Utility function to convert js objects into C++ trainingSets
* @param {Object} trainingSet - JS Object representing a training set
* @property {function} Module.TrainingSet - constructor for emscripten version of this struct
* @property {function} Module.VectorDouble - constructor for the emscripten version of std::vector<double>
* @returns {Module.TrainingSet}
*/
Module.prepTrainingSet = function (trainingSet) {
let rmTrainingSet = new Module.TrainingSet();
for (let i = 0; i < trainingSet.length; ++i) {
let tempInput = new Module.VectorDouble();
let tempOutput = new Module.VectorDouble();
for (let j = 0; j < trainingSet[i].input.length; ++j) {
tempInput.push_back(parseFloat(trainingSet[i].input[j]));
}
for (let j = 0; j < trainingSet[i].output.length; ++j) {
tempOutput.push_back(parseFloat(trainingSet[i].output[j]));
}
let tempObj = {'input': tempInput, 'output': tempOutput};
rmTrainingSet.push_back(tempObj);
}
return rmTrainingSet;
};
Module.prepTrainingSeriesSet = function (trainingSeriesSet) {
let rmTrainingSeriesSet = new Module.TrainingSeriesSet();
for (let i = 0; i < trainingSeriesSet.length; ++i) {
let input = new Module.VectorVectorDouble();
for (let j = 0; j < trainingSeriesSet[i].input.length; ++j) {
let tempVector = new Module.VectorDouble();
for (let k = 0; k < trainingSeriesSet[i].input[j].length; ++k) {
tempVector.push_back(parseFloat(trainingSeriesSet[i].input[j][k]));
}
input.push_back(tempVector);
}
let tempObj = {'input': input, 'label': trainingSeriesSet[i].label};
rmTrainingSeriesSet.push_back(tempObj);
}
return rmTrainingSeriesSet;
};
/**
* Utility function to add an empty output to a "training set" if it is undefined
* @param jsInput
* @returns {*}
*/
Module.checkOutput = function (jsInput) {
for (let i = 0; i < jsInput.length; ++i) {
if (typeof jsInput[i].output === "undefined") {
jsInput[i].output = [];
}
}
return jsInput;
};
//////////////////////////////////////////////// Regression
/**
* Creates a set of regression objects using the constructor from emscripten
* @constructor
* @property {function} Module.RegressionCpp - constructor from emscripten
*/
Module.Regression = function () {
this.modelSet = new Module.RegressionCpp(); //TODO implement optional arguments
};
Module.Regression.prototype = {
/**
* Trains the models using the input. Starts training from a randomized state.
* @param {Object} trainingSet - An array of training examples
* @returns {Boolean} true indicates successful training
*/
train: function (trainingSet) {
this.modelSet.reset();
//change to vectorDoubles and send in
return this.modelSet.train(Module.prepTrainingSet(trainingSet));
},
/**
* Returns the number of hidden layers in a MLP.
* @returns {Number} k values
*/
getNumHiddenLayers: function () {
let outputVector = this.modelSet.getNumHiddenLayers();
//change back to javascript array
let output = [];
for (let i = 0; i < outputVector.size(); ++i) {
output.push(outputVector.get(i));
}
return output[0];
},
/**
* Sets the number of hidden layers for an MLP.
* @param {Number} numHiddenLayers
*/
setNumHiddenLayers: function (numHiddenLayers) {
this.modelSet.setNumHiddenLayers(numHiddenLayers);
},
/**
* Sets the number of epochs for MLP training.
* @param {Number} numEpochs
*/
setNumEpochs: function (numEpochs) {
this.modelSet.setNumEpochs(numEpochs);
},
/**
* Returns the model set to its initial configuration.
* @returns {Boolean} true indicates successful initialization
*/
reset: function () {
return this.modelSet.reset();
},
/**
* Runs feed-forward regression on input
* @param {Array} input - An array of features to be processed. Non-arrays are converted.
* @returns {Array} output - One number for each model in the set
*/
run: function (input) {
//I'll assume that the args should have been an array
if (arguments.length > 1) {
input = Array.from(arguments);
}
//change input to vectors of doubles
let inputVector = new Module.VectorDouble();
for (let i = 0; i < input.length; ++i) {
inputVector.push_back(input[i]);
}
//get the output
let outputVector = this.modelSet.run(inputVector);
//change back to javascript array
let output = [];
for (let i = 0; i < outputVector.size(); ++i) {
output.push(outputVector.get(i));
}
return output;
},
/**
* Deprecated! Use run() instead
* @param input
* @returns {Array}
*/
process: function (input) {
//return this.run(input); //Why doesn't this work? MZ
//I'll assume that the args should have been an array
if (arguments.length > 1) {
input = Array.from(arguments);
}
//change input to vectors of doubles
let inputVector = new Module.VectorDouble();
for (let i = 0; i < input.length; ++i) {
inputVector.push_back(input[i]);
}
//get the output
let outputVector = new Module.VectorDouble();
outputVector = this.modelSet.run(inputVector);
//change back to javascript array
let output = [];
for (let i = 0; i < outputVector.size(); ++i) {
output.push(outputVector.get(i));
}
return output;
}
};
///////////////////////////////////////////////// Classification
/**
* Creates a set of classification objects using the constructor from emscripten
* @constructor
* @property {function} Module.ClassificationCpp - constructor from emscripten
* @param {string} [type] - which classification algorithm to use
*/
Module.Classification = function (type) {
if (type) {
this.modelSet = new Module.ClassificationCpp(type);
} else {
this.modelSet = new Module.ClassificationCpp();
}
};
Module.Classification.prototype = {
/**
* Trains the models using the input. Clears previous training set.
* @param {Object} trainingSet - An array of training examples.
* @returns {Boolean} true indicates successful training
*/
train: function (trainingSet) {
this.modelSet.reset();
return this.modelSet.train(Module.prepTrainingSet(trainingSet));
},
/**
* Returns a vector of current k values for each model.
* @returns {Array} k values
*/
getK: function () {
let outputVector = this.modelSet.getK();
let output = [];
for (let i = 0; i < outputVector.size(); ++i) {
output.push(outputVector.get(i));
}
return output;
},
/**
* Sets the k values for a particular model model.
* @param {Number} whichModel - which model
* @param {Number} newK - set K to this value
*/
setK: function (whichModel, newK) {
this.modelSet.setK(whichModel, newK);
},
/**
* Returns the model set to its initial configuration.
* @returns {Boolean} true indicates successful initialization
*/
reset: function () {
return this.modelSet.reset();
},
/**
* Does classifications on an input vector.
* @param {Array} input - An array of features to be processed. Non-arrays are converted.
* @returns {Array} output - One number for each model in the set
*/
run: function (input) {
//I'll assume that the args should have been an array
if (arguments.length > 1) {
input = Array.from(arguments);
}
//change input to vectors of doubles
let inputVector = new Module.VectorDouble();
for (let i = 0; i < input.length; ++i) {
inputVector.push_back(input[i]);
}
//get the output
let outputVector = new Module.VectorDouble();
outputVector = this.modelSet.run(inputVector);
//change back to javascript array
let output = [];
for (let i = 0; i < outputVector.size(); ++i) {
output.push(outputVector.get(i));
}
return output;
},
/**
* Deprecated! USe run() instead
* @param input
*/
process: function (input) {
//return this.run(input); //why doesn't this work?
//I'll assume that the args should have been an array
if (arguments.length > 1) {
input = Array.from(arguments);
}
//change input to vectors of doubles
let inputVector = new Module.VectorDouble();
for (let i = 0; i < input.length; ++i) {
inputVector.push_back(input[i]);
}
//get the output
let outputVector = this.modelSet.run(inputVector);
//change back to javascript array
let output = [];
for (let i = 0; i < outputVector.size(); ++i) {
output.push(outputVector.get(i));
}
return output;
}
};
////////////////////////////////////////////////// ModelSet
/**
* Creates a set of machine learning objects using constructors from emscripten. Could be any mix of regression and classification.
* This is only useful when importing JSON from Wekinator.
* @constructor
*/
Module.ModelSet = function () {
this.myModelSet = [];
this.modelSet = new Module.ModelSetCpp();
};
/**
* Creates a model set populated with models described in a JSON document.
* This only works in documents that are part of a CodeCircle document.
* @param {string} url - JSON loaded from a model set description document.
* @returns {Boolean} true indicates successful training
*/
Module.ModelSet.prototype = {
loadJSON: function (url) {
let that = this;
console.log('url ', url);
let request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "json";
request.onload = function () {
let modelSet = this.response;
console.log("loaded: ", modelSet);
let allInputs = modelSet.metadata.inputNames;
modelSet.modelSet.forEach(function (value) {
let numInputs = value.numInputs;
let whichInputs = new Module.VectorInt();
switch (value.modelType) {
case 'kNN classification':
let neighbours = new Module.TrainingSet();
let k = value.k;
for (let i = 0; i < allInputs.length; ++i) {
if (value.inputNames.includes(allInputs[i])) {
whichInputs.push_back(i);
}
}
let myKnn = new Module.KnnClassification(numInputs, whichInputs, neighbours, k);
value.examples.forEach(function (value) {
let features = new Module.VectorDouble();
for (let i = 0; i < numInputs; ++i) {
features.push_back(parseFloat(value.features[i]));
}
myKnn.addNeighbour(parseInt(value.class), features);
});
that.addkNNModel(myKnn);
break;
case 'Neural Network':
let numLayers = value.numHiddenLayers;
let numNodes = value.numHiddenNodes;
let weights = new Module.VectorDouble();
let wHiddenOutput = new Module.VectorDouble();
let inRanges = new Module.VectorDouble();
let inBases = new Module.VectorDouble();
let localWhichInputs = [];
for (let i = 0; i < allInputs.length; ++i) {
if (value.inputNames.includes(allInputs[i])) {
whichInputs.push_back(i);
localWhichInputs.push(i);
}
}
let currentLayer = 0;
value.nodes.forEach(function (value, i) {
if (value.name === 'Linear Node 0') { //Output Node
for (let j = 1; j <= numNodes; ++j) {
let whichNode = 'Node ' + (j + (numNodes * (numLayers - 1)));
wHiddenOutput.push_back(parseFloat(value[whichNode]));
}
wHiddenOutput.push_back(parseFloat(value.Threshold));
} else {
currentLayer = Math.floor((i - 1) / numNodes); //FIXME: This will break if node is out or order.
if (currentLayer < 1) { //Nodes connected to input
for (let j = 0; j < numInputs; ++j) {
weights.push_back(parseFloat(value['Attrib ' + allInputs[localWhichInputs[j]]]));
}
} else { //Hidden Layers
for (let j = 1; j <= numNodes; ++j) {
weights.push_back(parseFloat(value['Node ' + (j + (numNodes * (currentLayer - 1)))]));
}
}
weights.push_back(parseFloat(value.Threshold));
}
});
for (let i = 0; i < numInputs; ++i) {
inRanges.push_back(value.inRanges[i]);
inBases.push_back(value.Bases[i]);
}
let outRange = value.outRange;
let outBase = value.outBase;
let myNN = new Module.NeuralNetwork(numInputs, whichInputs, numLayers, numNodes, weights, wHiddenOutput, inRanges, inBases, outRange, outBase);
that.addNNModel(myNN);
break;
default:
console.warn('unknown model type ', value.modelType);
break;
}
});
};
request.send(null);
return true; //TODO: make sure this is true;
},
/**
* Add a NN model to a modelSet. //TODO: this doesn't need it's own function
* @param model
*/
addNNModel: function (model) {
console.log('Adding NN model');
this.myModelSet.push(model);
},
/**
* Add a kNN model to a modelSet. //TODO: this doesn't need it's own function
* @param model
*/
addkNNModel: function (model) {
console.log('Adding kNN model');
this.myModelSet.push(model);
},
/**
* Applies regression and classification algorithms to an input vector.
* @param {Array} input - An array of features to be processed.
* @returns {Array} output - One number for each model in the set
*/
run: function (input) {
let modelSetInput = new Module.VectorDouble();
for (let i = 0; i < input.length; ++i) {
modelSetInput.push_back(input[i]);
}
let output = [];
for (let i = 0; i < this.myModelSet.length; ++i) {
output.push(this.myModelSet[i].run(modelSetInput));
}
return output;
},
/**
* Deprecated! Use run() instead.
* @param {Array} input - An array of features to be processed
* @returns {Array} output - One number for each model in the set
*/
process: function (input) {
return this.run(input);
}
};
////////////////////////////////////////////////
/**
* Creates a series classification object using the constructor from emscripten
* @constructor
* @property {function} Module.SeriesClassificationCpp - constructor from emscripten
*/
Module.SeriesClassification = function () {
this.seriesClassification = new Module.SeriesClassificationCpp(); //TODO implement optional arguments
};
Module.SeriesClassification.prototype = {
/**
* Resets the model, and adds a set of series to be evaluated
* @param {Object} newSeriesSet - an array of objects, each with input: <array of arrays> and label: <string>
* @return {Boolean} True indicates successful training.
*/
train: function (newSeriesSet) {
this.reset();
this.seriesClassification.train(Module.prepTrainingSeriesSet(newSeriesSet));
return true;
},
/**
* Returns the model set to its initial configuration.
* @returns {Boolean} true indicates successful initialization
*/
reset: function () {
return this.seriesClassification.reset();
},
/**
* Evaluates an input series and returns the index of the closet example
* @param {Object} inputSeries - an array of arrays
* @returns {Number} The index of the closest matching series
*/
run: function (inputSeries, label) {
let vecInputSeries = new Module.VectorVectorDouble();
for (let i = 0; i < inputSeries.length; ++i) {
let tempVector = new Module.VectorDouble();
for (let j = 0; j < inputSeries[i].length; ++j) {
tempVector.push_back(inputSeries[i][j]);
}
vecInputSeries.push_back(tempVector);
}
if (arguments.length > 1) {
return this.seriesClassification.runLabel(vecInputSeries, label);
} else {
return this.seriesClassification.run(vecInputSeries);
}
},
/**
* Deprecated! Use run()
* @param inputSeries
* @returns {Number}
*/
process: function (inputSeries) {
return this.run(inputSeries);
},
/**
* Returns an array of costs to match the input series to each example series. A lower cost is a closer match
* @returns {Array}
*/
getCosts: function () {
let returnArray = [];
let VecDouble = this.seriesClassification.getCosts();
for (let i = 0; i < VecDouble.size(); ++i) {
returnArray[i] = VecDouble.get(i);
}
return returnArray;
}
};
/////////////////////////////////////////////////
/**
* Creates a circular buffer that can return various statistics
* @constructor
* @param {number} [windowSize=3] - specify the size of the buffer
* @property {function} Module.rapidStreamCpp - constructor from emscripten
*/
Module.StreamBuffer = function (windowSize) {
if (windowSize) {
this.rapidStream = new Module.RapidStreamCpp(windowSize);
} else {
this.rapidStream = new Module.RapidStreamCpp();
}
};
Module.StreamBuffer.prototype = {
/**
* Add a value to a circular buffer whose size is defined at creation.
* @param {number} input - value to be pushed into circular buffer.
*/
push: function (input) {
this.rapidStream.pushToWindow(parseFloat(input));
},
/**
* Resets all the values in the buffer to zero.
*/
reset: function () {
this.rapidStream.clear();
},
/**
* Calculate the first-order difference (aka velocity) between the last two inputs.
* @return {number} difference between last two inputs.
*/
velocity: function () {
return this.rapidStream.velocity();
},
/**
* Calculate the second-order difference (aka acceleration) over the last three inputs.
* @return {number} acceleration over the last three inputs.
*/
acceleration: function () {
return this.rapidStream.acceleration();
},
/**
* Find the minimum value in the buffer.
* @return {number} minimum.
*/
minimum: function () {
return this.rapidStream.minimum();
},
/**
* Find the maximum value in the buffer.
* @return {number} maximum.
*/
maximum: function () {
return this.rapidStream.maximum();
},
/**
* Calculate the sum of all values in the buffer.
* @return {number} sum.
*/
sum: function () {
return this.rapidStream.sum();
},
/**
* Calculate the mean of all values in the buffer.
* @return {number} mean.
*/
mean: function () {
return this.rapidStream.mean();
},
/**
* Calculate the standard deviation of all values in the buffer.
* @return {number} standard deviation.
*/
standardDeviation: function () {
return this.rapidStream.standardDeviation();
},
/**
* Calculate the root mean square of the values in the buffer
* @return {number} rms
*/
rms: function () {
return this.rapidStream.rms();
},
/**
* Calculate the minimum first-order difference over consecutive inputs in the buffer.
* @return {number} minimum velocity.
*/
minVelocity: function () {
return this.rapidStream.minVelocity();
},
/**
* Calculate the maximum first-order difference over consecutive inputs in the buffer.
* @return {number} maximum velocity.
*/
maxVelocity: function () {
return this.rapidStream.maxVelocity();
},
/**
* Calculate the minimum second-order difference over consecutive inputs in the buffer.
* @return {number} minimum acceleration.
*/
minAcceleration: function () {
return this.rapidStream.minAcceleration();
},
/**
* Calculate the maximum second-order difference over consecutive inputs in the buffer.
* @return {number} maximum acceleration.
*/
maxAcceleration: function () {
return this.rapidStream.maxAcceleration();
}
};