UNPKG

@modelx/model

Version:

Deep Learning Classification, LSTM Time Series, Regression and Multi-Layered Perceptrons with Tensorflow

446 lines (436 loc) 18.7 kB
import { TensorScriptOptions, TensorScriptProperties, Matrix, Vector, TensorScriptLayers, TensorScriptSavedLayers, NestedArray, TensorScriptModelContext,TensorScriptLSTMModelContext, } from './model_interface'; import { LSTMTimeSeries, } from './lstm_time_series'; import range from 'lodash.range'; /** * Long Short Term Memory Multivariate Time Series with Tensorflow * @class LSTMMultivariateTimeSeries * @extends {LSTMTimeSeries} */ //@ts-ignore export class LSTMMultivariateTimeSeries extends LSTMTimeSeries { /** * Creates dataset data * @example * const ds = [ [10, 20, 30, 40, 50, 60, 70, 80, 90,], [11, 21, 31, 41, 51, 61, 71, 81, 91,], [12, 22, 32, 42, 52, 62, 72, 82, 92,], [13, 23, 33, 43, 53, 63, 73, 83, 93,], [14, 24, 34, 44, 54, 64, 74, 84, 94,], [15, 25, 35, 45, 55, 65, 75, 85, 95,], [16, 26, 36, 46, 56, 66, 76, 86, 96,], [17, 27, 37, 47, 57, 67, 77, 87, 97,], [18, 28, 38, 48, 58, 68, 78, 88, 98,], [19, 29, 39, 49, 59, 69, 79, 89, 99,], ]; * LSTMMultivariateTimeSeries.createDataset(ds,1) // => // [ // [ // [ 20, 30, 40, 50, 60, 70, 80, 90 ], // [ 21, 31, 41, 51, 61, 71, 81, 91 ], // [ 22, 32, 42, 52, 62, 72, 82, 92 ], // [ 23, 33, 43, 53, 63, 73, 83, 93 ], // [ 24, 34, 44, 54, 64, 74, 84, 94 ], // [ 25, 35, 45, 55, 65, 75, 85, 95 ], // [ 26, 36, 46, 56, 66, 76, 86, 96 ], // [ 27, 37, 47, 57, 67, 77, 87, 97 ], // [ 28, 38, 48, 58, 68, 78, 88, 98 ] // ], //x_matrix // [ 11, 12, 13, 14, 15, 16, 17, 18, 19 ], //y_matrix // 8 //features // ] * @param {Array<Array<number>} dataset - array of values * @param {Number} look_back - number of values in each feature * @override * @return {[Array<Array<number>>,Array<number>]} returns x matrix and y matrix for model trainning */ /* istanbul ignore next */ static createDataset(this:TensorScriptModelContext, dataset:NestedArray<Array<number>> = [], look_back:number = 1):NestedArray<number> { const features = (this.settings && this.settings.features) ? this.settings.features : dataset[ 0 ].length - 1; const n_in = look_back || this.settings.lookback ||1; //lookbacks const n_out = (this.settings && this.settings.outputs) ? this.settings.outputs : 1; //1; //@ts-ignore const series = LSTMMultivariateTimeSeries.seriesToSupervised(dataset, n_in, n_out); const dropped = LSTMMultivariateTimeSeries.getDropableColumns(features, n_in, n_out); //@ts-ignore const droppedColumns:any = LSTMMultivariateTimeSeries.drop(series, dropped); const dropped_c_columns = [ 0, ]; const original_dropped_c_columns = [ 0, droppedColumns[ 0 ].length - 1, ]; for (let i=0; i < n_in; i++){ dropped_c_columns.push((i + 1) * features+1); } // console.log({ series, dropped_c_columns,original_dropped_c_columns, dropped, }); const y = pivotVector(droppedColumns)[ droppedColumns[0].length - 1 ]; const x = LSTMMultivariateTimeSeries.drop( droppedColumns, original_dropped_c_columns, // [ 0, droppedColumns[ 0 ].length - 1, ] ); //@ts-ignore return [ x, y, features, ]; } /** * Drops columns by array index * @example const data = [ [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 11, 21, 31, 41, 51, 61, 71, 81, 91 ], [ 11, 21, 31, 41, 51, 61, 71, 81, 91, 12, 22, 32, 42, 52, 62, 72, 82, 92 ], [ 12, 22, 32, 42, 52, 62, 72, 82, 92, 13, 23, 33, 43, 53, 63, 73, 83, 93 ], [ 13, 23, 33, 43, 53, 63, 73, 83, 93, 14, 24, 34, 44, 54, 64, 74, 84, 94 ], [ 14, 24, 34, 44, 54, 64, 74, 84, 94, 15, 25, 35, 45, 55, 65, 75, 85, 95 ], [ 15, 25, 35, 45, 55, 65, 75, 85, 95, 16, 26, 36, 46, 56, 66, 76, 86, 96 ], [ 16, 26, 36, 46, 56, 66, 76, 86, 96, 17, 27, 37, 47, 57, 67, 77, 87, 97 ], [ 17, 27, 37, 47, 57, 67, 77, 87, 97, 18, 28, 38, 48, 58, 68, 78, 88, 98 ], [ 18, 28, 38, 48, 58, 68, 78, 88, 98, 19, 29, 39, 49, 59, 69, 79, 89, 99 ] ]; const n_in = 1; //lookbacks const n_out = 1; const dropColumns = getDropableColumns(8, n_in, n_out); // =>[ 10, 11, 12, 13, 14, 15, 16, 17 ] const newdata = drop(data,dropColumns); //=> [ // [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 11 ], // [ 11, 21, 31, 41, 51, 61, 71, 81, 91, 12 ], // [ 12, 22, 32, 42, 52, 62, 72, 82, 92, 13 ], // [ 13, 23, 33, 43, 53, 63, 73, 83, 93, 14 ], // [ 14, 24, 34, 44, 54, 64, 74, 84, 94, 15 ], // [ 15, 25, 35, 45, 55, 65, 75, 85, 95, 16 ], // [ 16, 26, 36, 46, 56, 66, 76, 86, 96, 17 ], // [ 17, 27, 37, 47, 57, 67, 77, 87, 97, 18 ], // [ 18, 28, 38, 48, 58, 68, 78, 88, 98, 19 ] //] * @param {Array<Array<number>>} data - data set to drop columns * @param {Array<number>} columns - array of column indexes * @returns {Array<Array<number>>} matrix with dropped columns */ static drop(data:NestedArray<Array<number>>, columns:Array<number>):NestedArray<Array<number>>|number[] { return data.reduce((cols:NestedArray<Array<number>>, row, i) => { cols[ i ] = []; row.forEach((col: any, idx:number) => { if (columns.indexOf(idx)===-1) { cols[ i ].push(col); } }); return cols; }, []); } /** * Converts data set to supervised labels for forecasting, the first column must be the dependent variable * @example const ds = [ [10, 20, 30, 40, 50, 60, 70, 80, 90,], [11, 21, 31, 41, 51, 61, 71, 81, 91,], [12, 22, 32, 42, 52, 62, 72, 82, 92,], [13, 23, 33, 43, 53, 63, 73, 83, 93,], [14, 24, 34, 44, 54, 64, 74, 84, 94,], [15, 25, 35, 45, 55, 65, 75, 85, 95,], [16, 26, 36, 46, 56, 66, 76, 86, 96,], [17, 27, 37, 47, 57, 67, 77, 87, 97,], [18, 28, 38, 48, 58, 68, 78, 88, 98,], [19, 29, 39, 49, 59, 69, 79, 89, 99,], ]; const n_in = 1; //lookbacks const n_out = 1; const series = seriesToSupervised(ds, n_in, n_out); //=> [ // [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 11, 21, 31, 41, 51, 61, 71, 81, 91 ], // [ 11, 21, 31, 41, 51, 61, 71, 81, 91, 12, 22, 32, 42, 52, 62, 72, 82, 92 ], // [ 12, 22, 32, 42, 52, 62, 72, 82, 92, 13, 23, 33, 43, 53, 63, 73, 83, 93 ], // [ 13, 23, 33, 43, 53, 63, 73, 83, 93, 14, 24, 34, 44, 54, 64, 74, 84, 94 ], // [ 14, 24, 34, 44, 54, 64, 74, 84, 94, 15, 25, 35, 45, 55, 65, 75, 85, 95 ], // [ 15, 25, 35, 45, 55, 65, 75, 85, 95, 16, 26, 36, 46, 56, 66, 76, 86, 96 ], // [ 16, 26, 36, 46, 56, 66, 76, 86, 96, 17, 27, 37, 47, 57, 67, 77, 87, 97 ], // [ 17, 27, 37, 47, 57, 67, 77, 87, 97, 18, 28, 38, 48, 58, 68, 78, 88, 98 ], // [ 18, 28, 38, 48, 58, 68, 78, 88, 98, 19, 29, 39, 49, 59, 69, 79, 89, 99 ] //]; * * @param {Array<Array<number>>} data - data set * @param {number} n_in - look backs * @param {number} n_out - future iterations (only 1 supported) * @todo support multiple future iterations * @returns {Array<Array<number>>} multivariate dataset for time series */ static seriesToSupervised(data: number[], n_in:number = 1, n_out:number = 1) { if (n_out !== 1) throw new RangeError('Only 1 future iteration supported'); if (data && Array.isArray(data) && Array.isArray(data[0]) && data[ 0 ].length < 2) throw new RangeError('Must include at least two columns, the first column must be the dependent variable, followed by independent variables'); // let n_vars = data[ 0 ].length; let cols: number[] = []; // let names = []; // input sequence (t-n, ... t-1) for (let x in data) { //@ts-ignore x = Number(parseInt(x)); //@ts-ignore let maxIndex:number = x + n_in + n_out; if (maxIndex > data.length) break; //@ts-ignore cols[ x ] = []; // console.log({ x,maxIndex }); for (let i in range(n_in)) { //@ts-ignore i = parseInt(i); //@ts-ignore cols[ x ].push(...data[x+i]); // console.log({ i, cols, }); } for (let j in range(n_out)){ //@ts-ignore j = parseInt(j); //@ts-ignore cols[ x ].push(...data[ x+j+n_in ]); // console.log({ j, cols, }); } } return cols; } /** * Calculates which columns to drop by index * @todo support multiple iterations in the future, also only one output variable supported in column features * lookbacks -1 * @example const ds = [ [10, 20, 30, 40, 50, 60, 70, 80, 90,], [11, 21, 31, 41, 51, 61, 71, 81, 91,], [12, 22, 32, 42, 52, 62, 72, 82, 92,], [13, 23, 33, 43, 53, 63, 73, 83, 93,], [14, 24, 34, 44, 54, 64, 74, 84, 94,], [15, 25, 35, 45, 55, 65, 75, 85, 95,], [16, 26, 36, 46, 56, 66, 76, 86, 96,], [17, 27, 37, 47, 57, 67, 77, 87, 97,], [18, 28, 38, 48, 58, 68, 78, 88, 98,], [19, 29, 39, 49, 59, 69, 79, 89, 99,], ]; const n_in = 1; //lookbacks const n_out = 1; const dropped = getDropableColumns(8, n_in, n_out); //=> [ 10, 11, 12, 13, 14, 15, 16, 17 ] * @param {number} features - number of independent variables * @param {number} n_in - look backs * @param {number} n_out - future iterations (currently only 1 supported) * @returns {Array<number>} array indexes to drop */ static getDropableColumns(features: number, n_in: number, n_out: number) { if (n_out !== 1) throw new RangeError('Only 1 future iteration supported'); const cols = features + 1; const total_cols = cols * n_in + cols * n_out; // console.log({ cols, total_cols }); return range(total_cols - cols +1, total_cols); } /** * Reshape input to be [samples, time steps, features] * @example * @override * 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(this:TensorScriptModelContext, x_timeseries: NestedArray<any> | undefined) { const time_steps = (this.settings && this.settings.lookback) ? this.settings.lookback : 1; const xShape = this.getInputShape(x_timeseries); const _samples = xShape[ 0 ]; const _timeSteps = time_steps; const _features = (this.settings && this.settings.features) ? this.settings.features : xShape[ 1 ]; // reshape input to be 3D [samples, timesteps, features] // train_X = train_X.reshape((train_X.shape[0], n_hours, n_features)) const newShape = [ _samples, _timeSteps, _features, ]; // console.log({newShape}) return newShape; } /** * Returns data for predicting values * @param timeseries * @param look_back * @override */ static getTimeseriesDataSet(this:TensorScriptModelContext, timeseries: NestedArray<number[]> | undefined, look_back: any) { const lookBack = look_back || this.settings.lookback; const matrices = LSTMMultivariateTimeSeries.createDataset.call(this, timeseries, lookBack); const x_matrix = matrices[0]; //@ts-ignore const y_matrix_m = LSTMMultivariateTimeSeries.reshape(matrices[ 1 ], [ matrices[ 1 ].length, 1 ]); //@ts-ignore const timeseriesShape = LSTMMultivariateTimeSeries.getTimeseriesShape.call(this,x_matrix); // const timeseriesShape = LSTMMultivariateTimeSeries.getTimeseriesShape(x_matrix); //@ts-ignore const x_matrix_timeseries = LSTMMultivariateTimeSeries.reshape(x_matrix, timeseriesShape); // const x_matrix_timeseries = LSTMMultivariateTimeSeries.reshape(x_matrix, [x_matrix.length, lookBack, ]); const xShape = LSTMMultivariateTimeSeries.getInputShape(x_matrix_timeseries); //@ts-ignore const y_matrix = pivotVector(y_matrix_m)[ y_matrix_m[0].length - 1 ]; const yShape = LSTMMultivariateTimeSeries.getInputShape(y_matrix_m); return { yShape, xShape, y_matrix, x_matrix: x_matrix_timeseries, }; } createDataset: (...args: any[])=>NestedArray<number>; seriesToSupervised: (...args: any[])=>Array<number>; getDropableColumns: (...args: any[])=>any; drop: (...args: any[])=>any; getTimeseriesShape: (...args: any[])=>any; getTimeseriesDataSet: (...args: any[])=>any; /** * @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:TensorScriptOptions = {}, properties?:TensorScriptProperties) { const config = Object.assign({ layers: [], stateful: true, mulitpleTimeSteps:false, lookback: 1, features: undefined, outputs:1, learningRate: 0.1, compile: { loss: 'meanSquaredError', optimizer: 'adam', }, fit: { epochs: 100, batchSize: 1, shuffle: false, }, }, options); super(config, properties); this.type = 'LSTMMultivariateTimeSeries'; this.createDataset = LSTMMultivariateTimeSeries.createDataset; this.seriesToSupervised = LSTMMultivariateTimeSeries.seriesToSupervised; this.getDropableColumns = LSTMMultivariateTimeSeries.getDropableColumns; this.drop = LSTMMultivariateTimeSeries.drop; this.getTimeseriesShape = LSTMMultivariateTimeSeries.getTimeseriesShape; this.getTimeseriesDataSet = LSTMMultivariateTimeSeries.getTimeseriesDataSet; 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:Matrix, y_matrix:Matrix, layers: TensorScriptSavedLayers) { const xShape = this.getInputShape(x_matrix); const yShape = this.getInputShape(y_matrix); this.yShape = yShape; this.xShape = xShape; const lstmLayers = []; const denseLayers = []; if (layers) { if(layers.lstmLayers && layers.lstmLayers.length) lstmLayers.push(...layers.lstmLayers); if(layers.denseLayers && layers.denseLayers.length) denseLayers.push(...layers.denseLayers); } else { const inputShape = [ xShape[ 1 ], xShape[ 2 ], ]; // console.log('default timeseries', { inputShape, xShape, yShape, }); // model.add(LSTM(50, input_shape=(train_X.shape[1], train_X.shape[2]))) // model.add(Dense(1)) lstmLayers.push({ units: 10, 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 (denseLayers.length) { denseLayers.forEach(layer => { this.model.add(this.tf.layers.dense(layer)); }); } this.layers = { lstmLayers, denseLayers, }; // this.settings.compile.optimizer = sgdoptimizer; } /** * @override * @param x_timeseries * @param y_timeseries * @param layers * @param x_test * @param y_test */ async train(this:TensorScriptLSTMModelContext,x_timeseries: any, y_timeseries: any, layers:TensorScriptLayers, x_test:Matrix, y_test:Matrix) { let xShape; 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; xShape = this.getInputShape(x_matrix); yShape = this.getInputShape(y_matrix); } else { const matrices = this.createDataset(x_timeseries, look_back); // console.log({matrices}) x_matrix = matrices[0]; //@ts-ignore y_matrix = this.reshape(matrices[ 1 ], [ matrices[ 1 ].length, 1 ]); xShape = this.getInputShape(x_matrix); yShape = this.getInputShape(y_matrix); } //_samples, _timeSteps, _features const timeseriesShape = this.getTimeseriesShape(x_matrix); // console.log({ // timeseriesShape, yShape, xShape, // // y_matrix, x_matrix, // }); const x_matrix_timeseries = LSTMMultivariateTimeSeries.reshape(x_matrix, timeseriesShape); // console.log('x_matrix_timeseries',JSON.stringify(x_matrix_timeseries)); // console.log('x_matrix',JSON.stringify(x_matrix)); // console.log('y_matrix',JSON.stringify(y_matrix)); const xs = this.tf.tensor(x_matrix_timeseries, timeseriesShape); const ys = this.tf.tensor(y_matrix, yShape); this.xShape = timeseriesShape; this.yShape = yShape; if (this.compiled===false) { this.model = this.tf.sequential(); //@ts-ignore this.generateLayers.call(this, x_matrix_timeseries, y_matrix, layers || this.layers, // x_test, y_test ); this.model.compile(this.settings.compile); this.compiled = true; } if (x_test && y_test && this.settings && this.settings.fit) { this.settings.fit.validation_data = [ x_test, y_test, ]; } await this.model.fit(xs, ys, this.settings.fit); this.trained = true; // this.model.summary(); xs.dispose(); ys.dispose(); return this.model; } } /** * Returns an array of vectors as an array of arrays (from modelscript) * @example const vectors = [ [1,2,3], [1,2,3], [3,3,4], [3,3,3] ]; const arrays = pivotVector(vectors); // => [ [1,2,3,3], [2,2,3,3], [3,3,4,3] ]; * @memberOf util * @param {Array[]} vectors * @returns {Array[]} * @ignore * @see {https://github.com/repetere/modelscript/blob/master/src/util.js} */ /* istanbul ignore next */ export function pivotVector(vectors:Matrix=[]):Matrix { /* istanbul ignore next */ return vectors.reduce((result:Matrix, val, index/*, arr*/) => { val.forEach((vecVal, i) => { (index === 0) ? (result.push([vecVal,])) : (result[ i ].push(vecVal)); }); return result; }, []); }