DEV Community

Cover image for Tensorflow.js basics: LSTM prediction per timestep
Practicing Datscy
Practicing Datscy

Posted on

Tensorflow.js basics: LSTM prediction per timestep

The Long Short Term Model (LSTM) is a sequential neural network that uses past datapoints (within a window called timesteps) to predict future datapoints. It is a powerful model used for forecasting with a repetitive model structure that allows for past value relationships to be remembered, and thus used for future prediction, by passing two matrices called hidden cell layer matrices (a, c) from one timestep to the next. The hidden cell layer matrices have size [n_a, batch_size, timesteps], where n_a is a parameter the modeler can choose with respect to the data. The larger n_a is set, one is able to "describe the data" more; however "describing the data" too much can also prevent patterns to be found in the data. So, setting n_a not too small and not too big often gives the best results.

Key parameters to tune in an LSTM

  1. Tuning the models returnState=true allows for these hidden cell layer matrices (a, c) to be passed from one timestep to the next. returnState=false does not pass the (a, c) matricies, but a reliable model can still be obtained without the accumulated hidden cell layer information.
  2. returnSequences=true allows for the output to be 3 dimensional [batch_size, n_a, timesteps], where as returnSequences=false allows for the output to be 2 dimensional [batch_size, n_a]. Notice in the stacked layer model definition below, we set returnSequences=true for the first 2 layers to pass all the timestep information to the next layer, but the 3rd lstm layer has returnSequences=false to only pass out the accumlated data in n_a data points.
  3. stateful is the final important parameter, it allows accumulated data points to be passed from batch to batch. In this example, we do not use it for simplicity.

Main webapp

Below is a simple webapp to demonstrate how to set the parameters for the Tensorflow.js LSTM model, to predict one future datapoint per timesteps.

<!DOCTYPE html>
<html>
<head></head>
<body>

<h1>Timeseries webapp: improved for testing several cases</h1>
<ol type="A">
<li>Select a button.</li>
</ol>

<!-- ---------------------------------------- -->
<!-- View two split window -->
<div align="left">
<table style='text-align: left; width: 500px; display:block'>
<tr>

<th id="table_leftside_input">

<h3>[Step 0] Input epochs and select a button.</h3>

<input id="epochs" type="text" value="" placeholder="epochs" rows="10" cols="100" style="display:block; text-align: left; width: 600px;">

<br><br>

<button id="run_per_timestep" onclick="run_per_timestep()">run_per_timestep</button>

<br><br>

<progress id="progress_bar" max="100" value="0" style="display:none">0%</progress>

<!-- ---------------------------------------- -->

<th id="table_rightside_output">
<h3>[Step 1] View Results.</h3>
<div id="data_display" style="display:block; text-align: left; width: 600px; height: 600px">
<br>
<div id="notification"></div>
<br>
<div id="error"></div>
</th>

</tr>
</table>
</div>  
<!-- ---------------------------------------- -->



<!-- ---------------------------------------- -->
<!-- CSS -->
<style>
div { padding: 10px; display:block; font-family:courier; font-size:15px; }
div#notification { position: relative; color: #3236a8; }
div#error { position: relative; color: #bd1f17; }

table {vertical-align: top; border-collapse: collapse; position: relative; z-index: 0; border: 0px solid black;}

tr {vertical-align: top; border: 0px solid black; padding: 30px 30px; }

th, td {vertical-align: top; border: 0px solid black; padding: 10px; }
th#char_prediction_input {width: 100%; }
th#char_prediction_output {width: 100%; }

div#data_display {position: absolute; vertical-align: top; top: 200; z-index: 200; }
</style>

<!-- ---------------------------------------- -->

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>

<script src="https://cdn.plot.ly/plotly-2.30.0.min.js" charset="utf-8"></script>

<!-- ---------------------------------------- -->

<script>
var past_temperature_data = [23,  22.5, 22.3, 21.5, 21.3, 21.3, 21.2, 22.1, 24.3, 26.5, 29.1, 31.1, 32.8, 34.1, 34.5, 34.6, 34.2, 33.3, 31.4, 29, 27.1, 25.9, 23.9, 23.1, 22.3, 21.4, 20.9, 19.9, 19.4, 19.2, 19.8, 21.7, 23.8, 25.3, 27.6, 30, 29.1, 29.7, 30.7, 30.1, 29.9, 29.3, 27.1, 22.9, 20, 20.7, 20.5, 20.5, 20.4, 20.4, 20.2, 20.1, 19.7, 19.5, 19.4, 20.3, 21.9, 23.7, 25.4, 26.8, 28.2, 29, 29.4, 29.6, 29.6, 28.9, 27.8, 26.1, 24.8, 24.4, 23.9, 23, 22.2, 21.1, 20.5, 19.7, 19.1, 18.7, 19.5, 20.4, 22.2, 24.4, 26.5, 28.1, 30.3, 31, 31.3, 26.3, 24.9, 24.3, 24, 23.1, 22.6, 22.4, 22, 21.2, 21.3, 20.3, 20, 20.3, 20.2, 20.2, 20.2, 20.8, 21.5, 21.3, 21.6, 21.6, 22.3, 22.5, 22, 22.1, 22.4, 21.4, 21.1, 20.4, 20.1, 19.8, 19.6, 19.4, 17.7, 18.1, 17.9, 17.5, 17, 16.9, 16.7, 16.8, 17, 17.1, 17.4, 18.1, 18.9, 18.9, 20.3, 20.7, 19.8, 19.9, 19.2, 18.3, 17.7, 16.6, 16, 15.1, 14.5, 13.9, 13.7, 14, 13.7, 13.7, 14.2, 14.6, 15.8, 16.7, 18.1, 20.8, 22, 22.7, 24, 23.7, 24.4, 24.1, 23.3, 21.5, 20.3, 19.6, 19.2, 19, 18.9, 18.4, 18, 17.6, 17.3, 17.1, 17.2, 18.2, 20.2, 23.2, 25.1, 26.1, 28.1, 29.8, 30.1, 29.4, 27.8, 20.5, 20, 19.1, 19.2, 19, 18.9, 18.8, 18.1, 18, 18, 17.9, 17.7, 17.4, 16.5, 17.2, 18.4, 19.2, 20.3, 20.5, 21.9, 21.8, 21.5, 20.4, 18.9, 18.6, 18.5, 18, 17.4, 17.3, 17.2, 17.1, 17, 16.4, 16.1, 15, 15.2, 15.5, 15.5, 15.8, 16.3, 17.7, 18.8, 19.1, 20.1, 19.8, 19.7, 19.5, 19.8, 19.8, 19.4, 18.9, 18.1, 17.7, 17.3, 17];

var timesteps = 0;
var num_of_features = 0;
var batch_size = 0;
var batch_size_test = 0;
var num_of_outputs = 1;
var lstmModel = [];
var epochs = 500;

async function run_per_timestep() {

var obj = await create_xs_ys_predict_1_datapoint();

lstmModel = await modelDefinition_prediction_per_timestep();

var xs_train_tf = obj.xs_train;
var ys_train_tf = obj.ys_train;
var xs_test_js = obj.xs_test;
var ys_test_js = obj.ys_test;

await compile_train_predict_lstm(xs_train_tf, ys_train_tf, xs_test_js, ys_test_js, lstmModel);
}
</script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Create the dataset

Below the xs_train, ys_train, xs_test, and ys_test matrices are created from the data array above. The xs data is [timesteps] long and ys is the datapoint after each timestep.

// -----------------------------------------------
// [0] xs and ys creation
// -----------------------------------------------
async function create_xs_ys_predict_1_datapoint() {

// Prepare the X matrix: [num_of_features=1, num_of_datapoints=240]
var xs_2d_arr = [past_temperature_data];
console.log('xs_2d_arr: ', xs_2d_arr);

var shap = await shape(xs_2d_arr);
console.log('Shape xs_2d_arr: ', shap);

// -------------------------------------------------

// Calculate [batch_size, timestep, num_of_features] to separate X and Y
timesteps = 12; // Try a longer window such that the LSTM has more past data to train on, for a more accurate result
console.log('timesteps: ', timesteps);

num_of_features = xs_2d_arr.length;  // num_of_features_OR_colnum
console.log('num_of_features: ', num_of_features);

batch_size = Math.floor(xs_2d_arr[0].length / timesteps); // 20
console.log('batch_size: ', batch_size);

// -------------------------------------------------

var xs_3d_train = [];
var ys_2d_train = [];
var xs_3d_test = [];
var ys_2d_test = [];

// The number of batch_size to keep for testing the model
batch_size_test = Math.floor(batch_size*0.1);
console.log("batch_size_test: ", batch_size_test);

for (var feats=0; feats<num_of_features; feats++) {
// for the organization, features are rows
var temp_datappoints_arr = xs_2d_arr.at(feats);
console.log("temp_datappoints_arr: ", temp_datappoints_arr);

// cut temp_datappoints_arr into X and Y, every timesteps
var x_temp = [];
var y_temp = [];
var x_temp_test = [];
var y_temp_test = [];

// loop over the data by batch_size, to cut the data by [batch_size] number of [timesteps]
for (var num_of_desired_predictions=0; num_of_desired_predictions<batch_size; num_of_desired_predictions++) {
var st = num_of_desired_predictions * timesteps;
var stop = st + timesteps;

// Separate train and test data
if (num_of_desired_predictions > batch_size-batch_size_test-1) {
// Test data set
x_temp_test.push(temp_datappoints_arr.slice(st, stop-1));
y_temp_test.push(temp_datappoints_arr.at(stop-1));
} else {
// Train data set
x_temp.push(temp_datappoints_arr.slice(st, stop-1)); // from [0-(timesteps-2)th] value
y_temp.push(temp_datappoints_arr.at(stop-1));  // returns the (timesteps-1)th value

if (num_of_desired_predictions == 0) {
console.log("x_temp: ", x_temp);
console.log("y_temp: ", y_temp);
}
if (num_of_desired_predictions == batch_size-batch_size_test) {
console.log("x_temp_test: ", x_temp_test);
console.log("y_temp_test: ", y_temp_test);
}
}
}
xs_3d_train.push(x_temp);
ys_2d_train.push(y_temp);
xs_3d_test.push(x_temp_test);
ys_2d_test.push(y_temp_test);
}

// Train shape1
shap = await shape(xs_3d_train);  
console.log('Shape xs_3d_train: ', shap);  // [num_of_features, batch_size, timesteps] = [ 1, 129, 1 ]

shap = await shape(ys_2d_train);
console.log('Shape ys_2d_train: ', shap); // [num_of_features, batch_size] = [ 1, 129 ]

// -------------------------------------------------

// Test shape1
shap = await shape(xs_3d_test);  
console.log('Shape xs_3d_test: ', shap);  // [num_of_features, batch_size, timesteps] = [ 1, 1, 1 ]

shap = await shape(ys_2d_test);  
console.log('Shape ys_2d_test: ', shap);  // [num_of_features, batch_size]

// -------------------------------------------------

// Update timesteps, timesteps should be 1 less due to assigning the last datapoint to Y.
timesteps = timesteps - 1;
console.log('timesteps: ', timesteps); // 1

// Recalculate batch_size train, taking into account batch_size_test that was not included in xs_train
batch_size = batch_size - batch_size_test;
console.log('batch_size: ', batch_size); // 129

// -------------------------------------------------

// Put X_3d_test_shape1 [num_of_features, batch_size, timesteps]=[0,1,2] into [batch_size, timesteps, num_of_features]=[1,2,0]

var xs_3d_arr = await exchange_3d_dimensions(xs_3d_train, [1,2,0]);

// Train shape
shap = await shape(xs_3d_arr);  
console.log('Shape xs_3d_arr: ', shap); // [batch_size, timesteps, num_of_features] = [ 129, 1, 1 ]

var xs_train_tf = await tf.tensor(xs_3d_arr);
console.log('Tensorflow tensor (X train) - xs_train_tf: ', xs_train_tf);

// -------------------------------------------------

var xs_test_3d_jsArr = await exchange_3d_dimensions(xs_3d_test, [1,2,0]);

// Test shape
shap = await shape(xs_test_3d_jsArr);  
console.log('Shape xs_test_3d_jsArr: ', shap); // [batch_size_test, timesteps, num_of_features] = [ 1, 1, 1 ]

// -------------------------------------------------

// [Train] Assign Y to a 1D tensor
console.log('ys_2d_train: ', ys_2d_train); // [num_of_features, batch_size] = [ 1, 129 ]

// So, need to stack [batch_size] per [feature]
var ys_2d_arr = [];
for (var i=0; i<num_of_features; i++) {
ys_2d_arr = ys_2d_arr.concat(ys_2d_train[i]);
}

// Test shape
shap = await shape(ys_2d_arr);  
console.log('Shape ys_2d_arr: ', shap);

var ys_train_tf = await tf.tensor(ys_2d_arr);
console.log('Tensorflow tensor (Y train) - ys_train_tf: ', ys_train_tf);

// -------------------------------------------------

  // [Test] Assign Y to a 1D tensor
console.log('ys_2d_test: ', ys_2d_test); // [num_of_features, batch_size] = [ 1, 129 ]

// So, need to stack [batch_size] per [feature]
var ys_test_1d_jsArr = [];
for (var i=0; i<num_of_features; i++) {
ys_test_1d_jsArr = ys_test_1d_jsArr.concat(ys_2d_test[i]);
}

  // -------------------------------------------------

return {xs_train: xs_train_tf, ys_train: ys_train_tf, xs_test: xs_test_3d_jsArr, ys_test: ys_test_1d_jsArr};
}
Enter fullscreen mode Exit fullscreen mode

Model definition for predicting one datapoint per timestep

async function modelDefinition_prediction_per_timestep() {

  // -----------------------

var n_a = 2*timesteps;  // timesteps;
var num_of_outputs = 1; // a value from 1 to timesteps, or the number of data points to predict into the future

  // -----------------------

// Input layer
const input = tf.input({batchShape: [batch_size, timesteps, num_of_features]});
console.log("input: ", input.name);

console.log("Input: ", JSON.stringify(input.shape));
// [129=batch_size, 1=timesteps, 1=num_of_features]

// -----------------------

// LSTM layer 0
const lstm0 = tf.layers.lstm({
recurrentActivation: 'hardSigmoid',
units: n_a,

// receive a prediction per datapoint
returnSequences: true,

// do not return the a and c matricies
returnState: false,
kernelInitializer: 'heNormal',

// batchSize = batch_size gives Input - LSTM:  [129,129]
batchSize: batch_size,
inputShape: [timesteps, num_of_features],
stateful: false,
// If stateful=true it gives Error: Input 0 is incompatible with layer lstm_LSTM1: expected shape=168,,1, found shape=32,3,1.
activation: 'tanh',
trainable: true,
dropout: 0.2
});

// Apply lstm layer to the model
const input_lstm0 = lstm0.apply(input);
console.log("input_lstm0: ", input_lstm0.name);

console.log("Input - LSTM0: ", JSON.stringify(input_lstm0.shape));
// Input - LSTM0: [m=batch_size=129, n_y=units=num_of_lstm_outpus=1]

// This size is the shape of y_total_forward_pass per timestep

// y_hat<t>_(n_y, batch_size)

// -----------------------

// LSTM layer 1
const lstm1 = tf.layers.lstm({
recurrentActivation: 'hardSigmoid',
units: n_a,

// receive a prediction per timestep
returnSequences: true,

// do not return the a and c matricies
returnState: false,
kernelInitializer: 'heNormal',

// batchSize = batch_size gives Input - LSTM:  [129,129]
batchSize: batch_size,
inputShape: [timesteps, num_of_features],
stateful: false,
// If stateful=true it gives Error: Input 0 is incompatible with layer lstm_LSTM1: expected shape=168,,1, found shape=32,3,1.
activation: 'tanh',
trainable: true,
dropout: 0.2
});

// Apply lstm layer to the model
const input_lstm0_lstm1 = lstm1.apply(input_lstm0);
console.log("input_lstm0_lstm1: ", input_lstm0_lstm1.name);

console.log("Input - LSTM0 - LSTM1: ", JSON.stringify(input_lstm0_lstm1.shape));
// Input - LSTM: [m=batch_size=129, n_y=units=num_of_lstm_outpus=1]

// This size is the shape of y_total_forward_pass per timestep

// y_hat<t>_(n_y, batch_size)

// -----------------------

// LSTM layer 2
const lstm2 = tf.layers.lstm({
recurrentActivation: 'hardSigmoid',
units: n_a,

// receive a prediction per timestep
returnSequences: false,

// do not return the a and c matricies
returnState: false,
kernelInitializer: 'heNormal',

// batchSize = batch_size gives Input - LSTM:  [129,129]
batchSize: batch_size,
inputShape: [timesteps, num_of_features],
stateful: false,
// If stateful=true it gives Error: Input 0 is incompatible with layer lstm_LSTM1: expected shape=168,,1, found shape=32,3,1.
activation: 'tanh',
trainable: true,
dropout: 0.2
});

// Apply lstm layer to the model
const input_lstm0_lstm1_lstm2 = lstm2.apply(input_lstm0_lstm1);
console.log("input_lstm0_lstm1_lstm2: ", input_lstm0_lstm1_lstm2.name);

console.log("Input - LSTM0 - LSTM1 - LSTM2: ", JSON.stringify(input_lstm0_lstm1_lstm2.shape));
// Input - LSTM: [m=batch_size=129, n_y=units=num_of_lstm_outpus=1]

// This size is the shape of y_total_forward_pass per timestep

// y_hat<t>_(n_y, batch_size)

// -----------------------

// Dense layer
// const denseLayer1 = tf.layers.dense({units: num_of_outputs, inputShape: [timesteps, num_of_features]});
const denseLayer1 = tf.layers.dense({units: num_of_outputs});

// -----------

const input_lstm0_lstm1_lstm2_dense = denseLayer1.apply(input_lstm0_lstm1_lstm2);
console.log("Input - LSTM0 - LSTM1 - LSTM2 - Dense: ", JSON.stringify(input_lstm0_lstm1_lstm2_dense.shape));

// -----------------------

lstmModel = await tf.model({inputs: input, outputs: input_lstm0_lstm1_lstm2_dense});
console.log('lstmModel: ', lstmModel);

return lstmModel;
}
Enter fullscreen mode Exit fullscreen mode

Compile and train the model

Below is the function that compiles and trains the LSTM model.

// -----------------------------------------------
// [2] compile_train_predict model
// -----------------------------------------------
async function compile_train_predict_lstm(xs_train_tf, ys_train_tf, xs_test_js, ys_test_js, lstmModel) {

await lstmModel.compile({optimizer: tf.train.adam(), loss: tf.losses.meanSquaredError, metrics: ['mse'],});

epochs = document.getElementById('epochs').value;

const history = await lstmModel.fit(xs_train_tf, ys_train_tf, {
batchSize: batch_size,
epochs: epochs,
callbacks: { onEpochEnd: async (epoch, logs) => {
if (epoch % 50 == 0) {
            document.getElementById("progress_bar").style.display = "block";
document.getElementById("progress_bar").value = epoch/epochs*100;
console.log('epoch: ', epoch);
// console.log('logs: ', logs);
}
}}
});
console.log('history: ', history);

document.getElementById("progress_bar").style.display = "none";

// -----------------------------------------------
// Plot mse
// -----------------------------------------------
var mse = history.history.mse;
var loss = history.history.loss;
var epochs_plot = await Array.from({length: mse.length}, (val, ind) => { return ind; });

// https://plotly.com/javascript/line-charts/
var title_text = "Model results: loss, mse";
var x_text_trace1and2 = "Epochs";
var y_text_trace1and2 = "Mean squared error or loss";
var trace1 = {x: epochs_plot, y: mse, mode: 'lines+markers', type: 'line', name: 'MSE'};
var trace2 = {x: epochs_plot, y: loss, mode: 'lines+markers', type: 'line', name: 'Loss'};
var data = [trace1, trace2];
var layout = {grid: {rows: 1, columns: 1, pattern: 'independent'}, title: title_text, xaxis: {title: x_text_trace1and2}, yaxis: {title: y_text_trace1and2}};
Plotly.newPlot('data_display', data, layout);


for (var i=0; i<xs_test_js.length; i++) {

console.log('---------------------TEST MODEL: ', i, ' ---------------------: ');

// ------------------------

// Repeat entries to obtain a 3d array that is the same size as xs_train
// input: xs_test_js is a JS array [ batch_size, timesteps, num_of_features=1 ]
var xs_test_js_batch = await create_correct_batchSize_for_xsTest(xs_test_js[i], batch_size);
// console.log('xs_test_js_batch: ', xs_test_js_batch);
// output: xs_test_js is a JS array [ 337, 1, 1 ]

shap = await shape(xs_test_js_batch);  
console.log('Shape xs_test_js_batch: ', shap);

// ------------------------

// Evaluation of prediction with respect to the model
var result = lstmModel.predict( tf.tensor(xs_test_js_batch) );

// ------------------------

// Obtain prediction output from model
var model_predictions = result.dataSync()[0];  
console.log('model_predictions : ', model_predictions);

// ------------------------

// Correct response
console.log("ys_test_js: ", ys_test_js.at(i));

  // ------------------------
}
}
Enter fullscreen mode Exit fullscreen mode

The following figure shows how the mean squared error reduces over each iteration, thus showing that the model is able to reduce the loss and learn the timeseries.
mse

Model output shape

Below shows the model output shape for each layer, showing how returnSequences set to true outputs a 3 dimensional layer. Whereas returnSequences=false outputs a 2 dimensional layer, thus allowing for the dense layer to be applied to reduce n_a=hidden lstm layer=22 to a single output value. The final output size is [18,1], thus one output per each of the 18 batches.
Model output shape

Test prediction output

In the figure below, we can see that with this LSTM model definition we can pretty accurately predict the next data point in the timeseries; forecasting for one data point. The first test prediction is 17.26, for a target value of 19.1.

Test prediction output

Subfunctions for this post are found below:

// -----------------------------------------------
// SUBFUNCTIONS
// -----------------------------------------------
async function recur_func(arr) {
if (arr != undefined) {
return [arr[0], arr.length];
} else {
return arr;
}
}

async function shape(arr) {
var out = await recur_func(arr);
var shap = [out[1]];
var c = 0; // typically work with 4D arrays or less
while (out != undefined && c < 4) {
out = await recur_func(out[0]);
if (out != undefined) {
shap.push(out[1]);
}
c = c + 1;
}

if (shap.length > 1) {
shap = shap.slice(0, shap.length-1);
}

return shap;
}

// -----------------------------------------------

async function zeros(dims) {

// dims: [desired_0th_dim, desired_1st_dim, desired_2nd_dim]

var out = [];

if (dims.length == 1) {
var desired_0th_dim = dims.at(0);

out = Array.from({length: desired_0th_dim}, (val, ind) => { return 0; });

} else if (dims.length == 2) {
var desired_0th_dim = dims.at(0);
var desired_1st_dim = dims.at(1);

for (var i=0; i<desired_0th_dim; i++) {
var arr = Array.from({length: desired_1st_dim}, (val, ind) => { return 0; });
out.push(arr);
}

} else if (dims.length == 3) {
var desired_0th_dim = dims.at(0);
var desired_1st_dim = dims.at(1);
var desired_2nd_dim = dims.at(2);

for (var i=0; i<desired_0th_dim; i++) {
temp = [];
for (var j=0; j<desired_1st_dim; j++) {
var arr = Array.from({length: desired_2nd_dim}, (val, ind) => { return 0; });
temp.push(arr);
}
out.push(temp);
}

} else {
console.log('Enter an array of length 1, 2, or 3. (ie: [desired_0th_dim, desired_1st_dim, desired_2nd_dim])')
}

return out;
}

// -----------------------------------------------

async function exchange_3d_dimensions(arr_3d, output_dims_index) {

// dims: [2,1,2] implying the output dimension size
// OR
// output_dims_index: [0,2,1] implying how to switch the dimension index of arr_3d, this is technically more precise because two dimension indexes could have the same size. So one is specifying exactly which dimension index to use for a particular dimension.

// Way 0
// dims notation [2,2,1] => [2,1,2]

// Way 1
// need to say which dimension should switch with, which dimension
// output_dims_index specifies the [dimension index] of the desired output array
// [2,2,1] = [org_0th_dim, org_1st_dim, org_2nd_dim] => [2,1,2] = [org_0th_dim, org_2nd_dim, org_1st_dim] = [0,2,1]


// [0] List the original array location_values and values
var org_0th_dim = [];
var org_1st_dim = [];
var org_2nd_dim = [];
var val = [];

for (var i=0; i<arr_3d.length; i++) {
for (var j=0; j<arr_3d[0].length; j++) {
for (var k=0; k<arr_3d[0][0].length; k++) {
      org_0th_dim.push(i);
      org_1st_dim.push(j);
      org_2nd_dim.push(k);
      val.push(arr_3d[i][j][k])
}
}
}

// Need to transform output_dims_index, from index to dimensions
var dims_of_org_arr = [arr_3d.length, arr_3d[0].length, arr_3d[0][0].length];
var dims = output_dims_index.map((val, i) => { return dims_of_org_arr.at(val); });


// [1] Make the new shape array, filled with zeros
var desired_3d = await zeros(dims);

// Exchange notation for desired output index dimensions
var all = [org_0th_dim, org_1st_dim, org_2nd_dim];
var all_switched = output_dims_index.map((val, i) => { return all.at(val); });
org_0th_dim = all_switched.at(0);
org_1st_dim = all_switched.at(1);
org_2nd_dim = all_switched.at(2);


// [2] Fill in output shaped 3d array
var desired_0th_dim = dims.at(0);
var desired_1st_dim = dims.at(1);
var desired_2nd_dim = dims.at(2);

for (var i=0; i<desired_0th_dim; i++) {
for (var j=0; j<desired_1st_dim; j++) {
for (var k=0; k<desired_2nd_dim; k++) {

// cycle over the index of a for every i,j,k value
      for (var ind=0; ind<org_0th_dim.length; ind++) {
      if (org_0th_dim.at(ind) == i && org_1st_dim.at(ind) == j && org_2nd_dim.at(ind) == k) {
desired_3d[i][j][k] = val.at(ind);
      }
      }
}
}
}

return desired_3d;      
}

// -----------------------------------------------

async function transpose_2d_array(arr) {

var transpose_colNum = arr.length;
var transpose_rowNum = arr.at(0).length;
var transpose_arr = [];
for (var i=0; i<transpose_rowNum ; i++) {
const col = Array.from({ length: transpose_colNum }, (_, i) => 0);
transpose_arr.push(col);
}

for (var i=0; i<arr.length; i++) {
for (var j=0; j<arr.at(i).length; j++) {
transpose_arr[j][i] = arr[i][j];
}
}
return transpose_arr;
}

// -----------------------------------------------


async function flat_arr(arr) {

var str = JSON.stringify(arr);

const regex = /\b\d+\.\d+\b|\b\d+\b/g;
var matches = str.match(regex);
console.log('matches: ', matches);

var floatArray = matches.map((val, ind) => {
return Number(val);
});

return floatArray;
}

// -----------------------------------------------

async function reshape_arr(arr, desired_dims) {

// Flatten array
var xs_test_flat = await flat_arr(arr);   // [timesteps=11]
console.log('xs_test_flat: ', xs_test_flat);

// -----------------------------

var arr = await zeros(desired_dims);

const shap = await shape(arr);  
console.log('Shape arr: ', shap);

if (shap.length == 2) {
// Reshape 1D_flat to 2d
var c = 0;
for (var i=0; i<desired_dims[0]; i++) {
for (var j=0; j<desired_dims[1]; j++) {
arr[i][j][k] = xs_test_flat.at(c);
c = c + 1;
}
}
} else if (shap.length == 3) {
// Reshape 1D_flat to 3d
var c = 0;
for (var i=0; i<desired_dims[0]; i++) {
for (var j=0; j<desired_dims[1]; j++) {
for (var k=0; k<desired_dims[2]; k++) {
      arr[i][j][k] = xs_test_flat.at(c);
c = c + 1;
}
}
}
} else if (shap.length == 4) {
// Reshape 1D_flat to 4d
var c = 0;
for (var i=0; i<desired_dims[0]; i++) {
for (var j=0; j<desired_dims[1]; j++) {
for (var k=0; k<desired_dims[2]; k++) {
for (var l=0; l<desired_dims[3]; l++) {
      arr[i][j][k][l] = xs_test_flat.at(c);
c = c + 1;
}
}
}
}
} else {
arr = xs_test_flat;
}
// -----------------------------

console.log('arr: ', arr);

// -----------------------------

return arr;
}

// -----------------------------------------------

async function create_correct_batchSize_for_xsTest(xs_test_3d_jsArr, batch_size) {

// xs_test_3d_jsArr is a JS array [ 1, 11, 1 ]

var xs_test_flat = await flat_arr(xs_test_3d_jsArr);
console.log('xs_test_flat: ', xs_test_flat);

// ------------------------

var xs_test_1d_arr = xs_test_flat;

var xs_test_2d_arr = xs_test_1d_arr.map((val, ind) => { return [val]; });
console.log('xs_test_2d_arr: ', xs_test_2d_arr); // [timesteps=11, num_of_features=1]

shap = await shape(xs_test_2d_arr);  
console.log('Shape xs_test_2d_arr: ', shap);

// -----------------------------

// Repeat xs_test batch_size times
xs_test_3d_arr = [];
for (var i=0; i<batch_size; i++) {
xs_test_3d_arr.push(xs_test_2d_arr)
}
// console.log('xs_test_3d_arr: ', xs_test_3d_arr);  // [batch_size=19, timesteps=11, num_of_features=1]

shap = await shape(xs_test_3d_arr);  
console.log('Shape xs_test_3d_arr: ', shap);

return xs_test_3d_arr;
}

// -----------------------------------------------
Enter fullscreen mode Exit fullscreen mode

Happy Practicing! πŸ‘‹

πŸ’» GitHub | 🌷 Practicing Datscy @ Dev.to | 🌳 Practicing Datscy @ Medium

References

  1. Time series forecasting analysis with LSTM and Regression with Tensorflow.js: https://medium.com/towardsdev/time-series-forecasting-analysis-with-lstm-with-tensorflow-js-cef6e517bcd3
  2. Tensorflow.js LSTM: https://js.tensorflow.org/api/latest/#layers.lstm

Top comments (0)