lib/lstm_time_series.mjs
import { BaseNeuralNetwork, } from './deep_learning';
import range from 'lodash.range';
/**
* Long Short Term Memory Time Series with Tensorflow
* @class LSTMTimeSeries
* @implements {BaseNeuralNetwork}
*/
export class LSTMTimeSeries extends BaseNeuralNetwork {
/**
* Creates dataset data
* @example
* LSTMTimeSeries.createDataset([ [ 1, ], [ 2, ], [ 3, ], [ 4, ], [ 5, ], [ 6, ], [ 7, ], [ 8, ], [ 9, ], [ 10, ], ], 3) // =>
// [
// [
// [ [ 1 ], [ 2 ], [ 3 ] ],
// [ [ 2 ], [ 3 ], [ 4 ] ],
// [ [ 3 ], [ 4 ], [ 5 ] ],
// [ [ 4 ], [ 5 ], [ 6 ] ],
// [ [ 5 ], [ 6 ], [ 7 ] ],
// [ [ 6 ], [ 7 ], [ 8 ] ],
// ], //x_matrix
// [ [ 4 ], [ 5 ], [ 6 ], [ 7 ], [ 8 ], [ 9 ] ] //y_matrix
// ]
* @param {Array<Array<number>} dataset - array of values
* @param {Number} look_back - number of values in each feature
* @return {[Array<Array<number>>,Array<number>]} returns x matrix and y matrix for model trainning
*/
/* istanbul ignore next */
static createDataset(dataset=[], look_back = 1) {
const dataX = [];
const dataY = [];
for (let index in range(dataset.length - look_back - 1)) {
let i = parseInt(index);
let a = dataset.slice(i, i + look_back);
dataX.push(a);
dataY.push(dataset[ i + look_back ]);
}
return [dataX, dataY, ];
}
/**
* Reshape input to be [samples, time steps, features]
* @example
* LSTMTimeSeries.getTimeseriesShape([
[ [ 1 ], [ 2 ], [ 3 ] ],
[ [ 2 ], [ 3 ], [ 4 ] ],
[ [ 3 ], [ 4 ], [ 5 ] ],
[ [ 4 ], [ 5 ], [ 6 ] ],
[ [ 5 ], [ 6 ], [ 7 ] ],
[ [ 6 ], [ 7 ], [ 8 ] ],
]) //=> [6, 1, 3,]
* @param {Array<Array<number>} x_timeseries - dataset array of values
* @return {Array<Array<number>>} returns proper timeseries forecasting shape
*/
static getTimeseriesShape(x_timeseries) {
const time_steps = this.settings.timeSteps;
const xShape = this.getInputShape(x_timeseries);
const _samples = xShape[ 0 ];
const _timeSteps = time_steps;
const _features = xShape[ 1 ];
const newShape = (this.settings.mulitpleTimeSteps || this.settings.stateful)
? [_samples, _features, _timeSteps, ]
: [ _samples, _timeSteps, _features, ];
// console.log({newShape})
return newShape;
}
/**
* Returns data for predicting values
* @param timeseries
* @param look_back
*/
static getTimeseriesDataSet(timeseries, look_back) {
const lookBack = look_back || this.settings.lookBack;
const matrices = LSTMTimeSeries.createDataset.call(this, timeseries, lookBack);
const x_matrix = matrices[ 0 ];
const y_matrix = matrices[ 1 ];
// const timeseriesShape = LSTMTimeSeries.getTimeseriesShape.call(this,x_matrix);
const x_matrix_timeseries = BaseNeuralNetwork.reshape(x_matrix, [x_matrix.length, lookBack, ]);
const xShape = BaseNeuralNetwork.getInputShape(x_matrix_timeseries);
const yShape = BaseNeuralNetwork.getInputShape(y_matrix);
return {
yShape,
xShape,
y_matrix,
x_matrix:x_matrix_timeseries,
};
}
/**
* @param {{layers:Array<Object>,compile:Object,fit:Object}} options - neural network configuration and tensorflow model hyperparameters
* @param {{model:Object,tf:Object,}} properties - extra instance properties
*/
constructor(options = {}, properties) {
const config = Object.assign({
layers: [],
type: 'simple',
stateful:false,
stacked: false,
mulitpleTimeSteps:false,
lookBack:1,
batchSize:1,
timeSteps:1,
learningRate:0.1,
compile: {
loss: 'meanSquaredError',
optimizer: 'adam',
},
fit: {
epochs: 100,
batchSize: 1,
},
}, options);
super(config, properties);
this.createDataset = LSTMTimeSeries.createDataset;
this.getTimeseriesDataSet = LSTMTimeSeries.getTimeseriesDataSet;
this.getTimeseriesShape = LSTMTimeSeries.getTimeseriesShape;
return this;
}
/**
* Adds dense layers to tensorflow classification model
* @override
* @param {Array<Array<number>>} x_matrix - independent variables
* @param {Array<Array<number>>} y_matrix - dependent variables
* @param {Array<Object>} layers - model dense layer parameters
* @param {Array<Array<number>>} x_test - validation data independent variables
* @param {Array<Array<number>>} y_test - validation data dependent variables
*/
generateLayers(x_matrix, y_matrix, layers) {
const xShape = this.getInputShape(x_matrix);
const yShape = this.getInputShape(y_matrix);
this.yShape = yShape;
this.xShape = xShape;
// const sgdoptimizer = this.tf.train.sgd(this.settings.learningRate);
const lstmLayers = [];
const rnnLayers = [];
const denseLayers = [];
/* istanbul ignore next */
if (layers) {
if(layers.lstmLayers && layers.lstmLayers.length) lstmLayers.push(...layers.lstmLayers);
if(layers.rnnLayers && layers.rnnLayers.length) rnnLayers.push(...layers.rnnLayers);
if(layers.denseLayers && layers.denseLayers.length) denseLayers.push(...layers.denseLayers);
} else if (this.settings.stateful) {
const batchInputShape = [this.settings.fit.batchSize, this.settings.lookBack, 1, ];
rnnLayers.push({ units: 4, batchInputShape:batchInputShape, returnSequences:true, });
rnnLayers.push({ units: 4, batchInputShape:batchInputShape, });
denseLayers.push({ units: yShape[1], });
// } else if(this.settings.stacked) {
// lstmLayers.push({ units: 4, inputShape: [ 1, this.settings.lookBack ], });
// // model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True, return_sequences=True))
// // model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True))
// denseLayers.push({ units: yShape[1], });
} else {
const inputShape= [1, this.settings.lookBack, ];
// console.log('default timeseries', { inputShape, xShape, yShape , });
lstmLayers.push({ units:4, inputShape, });
denseLayers.push({ units: yShape[1], });
}
// console.log('lstmLayers',lstmLayers)
// console.log('denseLayers',denseLayers)
if (lstmLayers.length) {
lstmLayers.forEach(layer => {
this.model.add(this.tf.layers.lstm(layer));
});
}
if (rnnLayers.length) {
/* istanbul ignore next */
rnnLayers.forEach(layer => {
this.model.add(this.tf.layers.simpleRNN(layer));
});
}
if (denseLayers.length) {
denseLayers.forEach(layer => {
this.model.add(this.tf.layers.dense(layer));
});
}
this.layers = {
lstmLayers,
rnnLayers,
denseLayers,
};
// this.settings.compile.optimizer = sgdoptimizer;
}
async train(x_timeseries, y_timeseries, layers, x_test, y_test) {
let yShape;
let x_matrix;
let y_matrix;
const look_back = this.settings.lookBack;
if (y_timeseries) {
x_matrix = x_timeseries;
y_matrix = y_timeseries;
} else {
const matrices = this.createDataset(x_timeseries, look_back);
x_matrix = matrices[ 0 ];
y_matrix = matrices[ 1 ];
yShape = this.getInputShape(y_matrix);
}
//_samples, _timeSteps, _features
const timeseriesShape = this.getTimeseriesShape(x_matrix);
const x_matrix_timeseries = BaseNeuralNetwork.reshape(x_matrix, timeseriesShape);
const xs = this.tf.tensor(x_matrix_timeseries, timeseriesShape);
const ys = this.tf.tensor(y_matrix, yShape);
this.xShape = timeseriesShape;
this.yShape = yShape;
this.model = this.tf.sequential();
this.generateLayers.call(this, x_matrix_timeseries, y_matrix, layers || this.layers, x_test, y_test);
this.model.compile(this.settings.compile);
if (this.settings.stateful) {
this.settings.fit.shuffle = false;
}
await this.model.fit(xs, ys, this.settings.fit);
// this.model.summary();
xs.dispose();
ys.dispose();
return this.model;
}
calculate(x_matrix) {
const timeseriesShape = this.getTimeseriesShape(x_matrix);
const input_matrix = BaseNeuralNetwork.reshape(x_matrix, timeseriesShape);
return super.calculate(input_matrix);
}
async predict(input_matrix, options) {
if (this.settings.stateful && input_matrix.length > 1) {
return Promise.all(input_matrix.map(input=>super.predict([input, ], options))) ;
} else {
return super.predict(input_matrix, options);
}
}
}