lib/core.mjs
import tf from '@tensorflow/tfjs';
/**
* Base class for tensorscript models
* @interface TensorScriptModelInterface
* @property {Object} settings - tensorflow model hyperparameters
* @property {Object} model - tensorflow model
* @property {Object} tf - tensorflow / tensorflow-node / tensorflow-node-gpu
* @property {Function} reshape - static reshape array function
* @property {Function} getInputShape - static TensorScriptModelInterface
*/
export class TensorScriptModelInterface {
/**
* @param {Object} options - tensorflow model hyperparameters
* @param {Object} customTF - custom, overridale tensorflow / tensorflow-node / tensorflow-node-gpu
* @param {{model:Object,tf:Object,}} properties - extra instance properties
*/
constructor(options = {}, properties = {}) {
/** @type {Object} */
this.settings = options;
/** @type {Object} */
this.model = properties.model;
/** @type {Object} */
this.tf = properties.tf || tf;
/** @type {Function} */
this.reshape = TensorScriptModelInterface.reshape;
/** @type {Function} */
this.getInputShape = TensorScriptModelInterface.getInputShape;
return this;
}
/**
* Reshapes an array
* @function
* @example
* const array = [ 0, 1, 1, 0, ];
* const shape = [2,2];
* TensorScriptModelInterface.reshape(array,shape) // =>
* [
* [ 0, 1, ],
* [ 1, 0, ],
* ];
* @param {Array<number>} array - input array
* @param {Array<number>} shape - shape array
* @return {Array<Array<number>>} returns a matrix with the defined shape
*/
static reshape(array, shape) {
const rows = shape[ 0 ];
const cols = shape[ 1 ];
const shapedArray = array.reduce((result, val, i) => {
const index = Math.floor(i / cols);
if (Array.isArray(result[ index ])) {
result[ index ].push(val);
} else {
result[ index ] = [val,];
}
return result;
}, []);
if (shapedArray.length !== rows) throw new SyntaxError(`specified shape (${shape}) is compatible with input array or length (${array.length})`);
return shapedArray;
}
/**
* Returns the shape of an input matrix
* @function
* @example
* const input = [
* [ 0, 1, ],
* [ 1, 0, ],
* ];
* TensorScriptModelInterface.getInputShape(input) // => [2,2]
* @param {Array<Array<number>>} matrix - input matrix
* @return {Array<number>} returns the shape of a matrix (e.g. [2,2])
*/
static getInputShape(matrix=[]) {
if (Array.isArray(matrix) === false || !matrix[ 0 ] || !matrix[ 0 ].length || Array.isArray(matrix[ 0 ]) === false) throw new TypeError('input must be a matrix');
const x_dimensions = matrix[ 0 ].length;
matrix.forEach(vector => {
if (vector.length !== x_dimensions) throw new SyntaxError('input must have the same length in each row');
});
return [
matrix.length, x_dimensions,
];
}
/**
* Asynchronously trains tensorflow model, must be implemented by tensorscript class
* @abstract
* @param {Array<Array<number>>} x_matrix - independent variables
* @param {Array<Array<number>>} y_matrix - dependent variables
* @return {Object} returns trained tensorflow model
*/
train(x_matrix, y_matrix) {
throw new ReferenceError('train method is not implemented');
}
/**
* Predicts new dependent variables
* @abstract
* @param {Array<Array<number>>|Array<number>} matrix - new test independent variables
* @return {{data: Promise}} returns tensorflow prediction
*/
calculate(matrix) {
throw new ReferenceError('calculate method is not implemented');
}
/**
* Loads a saved tensoflow / keras model
* @param {Object} options - tensorflow load model options
* @return {Object} tensorflow model
*/
async loadModel(options) {
this.model = await this.tf.loadModel(options);
return this.model;
}
/**
* Returns prediction values from tensorflow model
* @param {Array<Array<number>>|Array<number>} input_matrix - new test independent variables
* @param {Boolean} [options.json=true] - return object instead of typed array
* @param {Boolean} [options.probability=true] - return real values instead of integers
* @return {Array<number>|Array<Array<number>>} predicted model values
*/
async predict(input_matrix, options = {}) {
if (!input_matrix || Array.isArray(input_matrix)===false) throw new Error('invalid input matrix');
const x_matrix = (Array.isArray(input_matrix[ 0 ]))
? input_matrix
: [
input_matrix,
];
const config = Object.assign({
json: true,
probability: true,
}, options);
return this.calculate(x_matrix)
.data()
.then(predictions => {
if (config.json === false) {
return predictions;
} else {
const shape = [x_matrix.length, this.yShape[ 1 ],];
const predictionValues = (options.probability === false) ? Array.from(predictions).map(Math.round) : Array.from(predictions);
return this.reshape(predictionValues, shape);
}
})
.catch(e => {
throw e;
});
}
}