UNPKG

@modelx/model

Version:

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

440 lines (439 loc) 19 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.pivotVector = exports.LSTMMultivariateTimeSeries = void 0; const lstm_time_series_1 = require("./lstm_time_series"); const lodash_range_1 = __importDefault(require("lodash.range")); /** * Long Short Term Memory Multivariate Time Series with Tensorflow * @class LSTMMultivariateTimeSeries * @extends {LSTMTimeSeries} */ //@ts-ignore class LSTMMultivariateTimeSeries extends lstm_time_series_1.LSTMTimeSeries { /** * @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: [], 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; } /** * 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(dataset = [], look_back = 1) { 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 = 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); //@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, columns) { return data.reduce((cols, row, i) => { cols[i] = []; row.forEach((col, idx) => { 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, n_in = 1, n_out = 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 = []; // let names = []; // input sequence (t-n, ... t-1) for (let x in data) { //@ts-ignore x = Number(parseInt(x)); //@ts-ignore let maxIndex = x + n_in + n_out; if (maxIndex > data.length) break; //@ts-ignore cols[x] = []; // console.log({ x,maxIndex }); for (let i in lodash_range_1.default(n_in)) { //@ts-ignore i = parseInt(i); //@ts-ignore cols[x].push(...data[x + i]); // console.log({ i, cols, }); } for (let j in lodash_range_1.default(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, n_in, n_out) { 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 lodash_range_1.default(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(x_timeseries) { 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(timeseries, look_back) { 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, }; } /** * 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 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(x_timeseries, y_timeseries, layers, x_test, y_test) { 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); 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; } } exports.LSTMMultivariateTimeSeries = LSTMMultivariateTimeSeries; /** * 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 */ function pivotVector(vectors = []) { /* istanbul ignore next */ return vectors.reduce((result, val, index /*, arr*/) => { val.forEach((vecVal, i) => { (index === 0) ? (result.push([vecVal,])) : (result[i].push(vecVal)); }); return result; }, []); } exports.pivotVector = pivotVector;