Time series regression (TSR) problems are very challenging. There are dozens of techniques — and the fact that there are so many techniques for TSR indicates that there’s no single best approach.
There’s been quite a bit of recent research activity that looks at attacking TSR problems using modern neural techniques, specifically systems that use transformer architecture. I decided to revisit one of my preferred techniques, which is to use a standard neural network. The ideas are a bit complicated and are best explained by a concrete example.
I used the Airline Passengers dataset. The source data looks like:
"1949-01";112 "1949-02";118 "1949-03";132 "1949-04";129 "1949-05";121 "1949-06";135 "1949-07";148 . . . "1960-12";432
There are 144 lines, with dates from January 1949 to December 1960. The values are number of airline passengers, in thousands, so the first item means there were a total of 112,000 airline passengers in January 1949 (not very many back then). When graphed, the data looks like this (where all passenger counts have been divided by 100):

Note: The Airline Passenger dataset originally appeared on page 531 of the first edition of the famous book “Time Series Analysis: Forecasting and Control” (1970) by G. Box and G. Jenkins.
I preprocessed the raw data to create a text file of sliding window values that looks like:
1.12, 1.18, 1.32, 1.29, 1.21 1.18, 1.32, 1.29, 1.21, 1.35 1.32, 1.29, 1.21, 1.35, 1.48 1.29, 1.21, 1.35, 1.48, 1.48 . . . 6.06, 5.08, 4.61, 3.90, 4.32
Each consecutive set of four values (a “window”) will be used to predict the next value. So the first input is (1.12, 1.18, 1.32, 1.29) and the value to predict is 1.21. Because of the offset, there are 140 training items.
I implemented a 4-12-1 neural network with tanh() hidden node activation and identity() output node activation. I trained the network using 10,000 iterations, with a learning rate of 0.01, and a batch size of 1. Interestingly, I consistently got better results using a batch size of 1, instead of the more usual size of 10 or something similar. In retrospect, this makes perfect sense.
The trained neural network model predicts with 0.8500 accuracy (119 out of 140 correct), where a correct prediction is defined to be one that is within 10% of the true value.
I implemented a Forecast() method that predicts beyond the source data. I started with the last set of four data points: (5.08, 4.61, 3.90, 4.32) and used them to predict for January 1961, and got 4.52. Then I used that prediction to create the next set of four input values: (4.61, 3.90, 4.32, 4.52) and used them to predict for February 1961. And so on.
I graphed the predicted passenger counts and the forecast counts:
The results look good, at least for a relatively short forecast. Much fun!

I stumbled across some wonderfully creative short videos on YouTube by a guy named Hey Anglomangler. Creepy and fascinating visions of a bizarre future or alternate universe. The videos look like they were created using some sort of AI tool.
Demo code. Replace “lt” (less than), “gt”, “lte”, “gte”, “and” with Boolean operator symbols.
using System;
using System.IO;
using System.Collections.Generic;
// data from Box, G., Jenkins, G., and Reinsel, G. (1976)
// "Time Series Analysis, Forecasting and Control."
namespace NeuralNetworkTimeSeries
{
internal class NeuralTimeSeriesProgram
{
static void Main(string[] args)
{
Console.WriteLine("\nBegin neural network" +
" times series demo");
Console.WriteLine("Goal is to predict airline " +
"passengers over time ");
Console.WriteLine("Data from January 1949 to" +
" December 1960 ");
Console.WriteLine("\nLoading windowed and" +
" normalized data from file ");
string dataFile =
"..\\..\\..\\Data\\airline_all.txt";
double[][] dataX = Utils.MatLoad(dataFile,
new int[] { 0, 1, 2, 3 }, ',', "#");
double[] dataY =
Utils.MatToVec(Utils.MatLoad(dataFile,
new int[] { 4 }, ',', "#"));
Console.WriteLine("\nFirst three X data: ");
for (int i = 0; i "lt" 3; ++i)
Utils.VecShow(dataX[i], 2, 6, 10, true);
Console.WriteLine("\nFirst three target Y: ");
for (int i = 0; i "lt" 3; ++i)
Console.WriteLine(dataY[i].ToString("F2"));
int numInput = 4; // number predictors
int numHidden = 12;
int numOutput = 1; // regression
Console.WriteLine("\nCreating a " + numInput + "-" +
numHidden + "-" + numOutput + " neural network");
NeuralNetwork nn = new NeuralNetwork(numInput,
numHidden, numOutput, 0);
int maxEpochs = 10000;
double lrnRate = 0.01;
int batSize = 1;
Console.WriteLine("\nSetting maxEpochs = " +
maxEpochs);
Console.WriteLine("Setting learnRate = " +
lrnRate.ToString("F2"));
Console.WriteLine("Setting batch size = " +
batSize);
Console.WriteLine("\nStarting training");
nn.Train(dataX, dataY, lrnRate, batSize, maxEpochs);
Console.WriteLine("Done");
double[] weights = nn.GetWeights();
Console.WriteLine("\nFinal neural network model" +
" weights and biases:\n");
Utils.VecShow(weights, 2, 7, 10, true);
double acc = nn.Accuracy(dataX, dataY, 0.10);
Console.WriteLine("\nModel accuracy (within 10%" +
" actual) = " + acc.ToString("F4"));
Console.WriteLine("\nForecasting ahead 12 steps ");
double[] forecast = nn.Forecast(new double[] { 5.08,
4.61, 3.90, 4.32 }, 12);
Utils.VecShow(forecast, 2, 6, 12, true);
Console.WriteLine("\nEnd time series demo ");
Console.ReadLine();
} // Main
} // Program
public class NeuralNetwork
{
private int ni; // number input nodes
private int nh;
private int no;
private double[] iNodes;
private double[][] ihWeights; // input-hidden
private double[] hBiases;
private double[] hNodes;
private double[][] hoWeights; // hidden-output
private double[] oBiases;
private double[] oNodes; // single val as array
// gradients
private double[][] ihGrads;
private double[] hbGrads;
private double[][] hoGrads;
private double[] obGrads;
private Random rnd;
// ------------------------------------------------------
public NeuralNetwork(int numIn, int numHid,
int numOut, int seed)
{
this.ni = numIn;
this.nh = numHid;
this.no = numOut; // 1 for regression
this.iNodes = new double[numIn];
this.ihWeights = MatCreate(numIn, numHid);
this.hBiases = new double[numHid];
this.hNodes = new double[numHid];
this.hoWeights = MatCreate(numHid, numOut);
this.oBiases = new double[numOut]; // [1]
this.oNodes = new double[numOut]; // [1]
this.ihGrads = MatCreate(numIn, numHid);
this.hbGrads = new double[numHid];
this.hoGrads = MatCreate(numHid, numOut);
this.obGrads = new double[numOut];
this.rnd = new Random(seed);
this.InitWeights(); // all weights and biases
} // ctor
// ------------------------------------------------------
private static double[][] MatCreate(int rows,
int cols)
{
// helper for ctor
double[][] result = new double[rows][];
for (int i = 0; i "lt" rows; ++i)
result[i] = new double[cols];
return result;
}
// ------------------------------------------------------
private void InitWeights() // helper for ctor
{
// weights and biases to small random values
double lo = -0.01; double hi = +0.01;
int numWts = (this.ni * this.nh) +
(this.nh * this.no) + this.nh + this.no;
double[] initialWeights = new double[numWts];
for (int i = 0; i "lt" initialWeights.Length; ++i)
initialWeights[i] =
(hi - lo) * rnd.NextDouble() + lo;
this.SetWeights(initialWeights);
}
// ------------------------------------------------------
public void SetWeights(double[] wts)
{
// copy serialized weights and biases in wts[]
// to ih weights, ih biases, ho weights, ho biases
int numWts = (this.ni * this.nh) +
(this.nh * this.no) + this.nh + this.no;
if (wts.Length != numWts)
throw new Exception("Bad array in SetWeights");
int k = 0; // points into wts param
for (int i = 0; i "lt" this.ni; ++i)
for (int j = 0; j "lt" this.nh; ++j)
this.ihWeights[i][j] = wts[k++];
for (int i = 0; i "lt" this.nh; ++i)
this.hBiases[i] = wts[k++];
for (int i = 0; i "lt" this.nh; ++i)
for (int j = 0; j "lt" this.no; ++j)
this.hoWeights[i][j] = wts[k++];
for (int i = 0; i "lt" this.no; ++i)
this.oBiases[i] = wts[k++];
}
// ------------------------------------------------------
public double[] GetWeights()
{
int numWts = (this.ni * this.nh) +
(this.nh * this.no) + this.nh + this.no;
double[] result = new double[numWts];
int k = 0;
for (int i = 0; i "lt" ihWeights.Length; ++i)
for (int j = 0; j "lt" this.ihWeights[0].Length; ++j)
result[k++] = this.ihWeights[i][j];
for (int i = 0; i "lt" this.hBiases.Length; ++i)
result[k++] = this.hBiases[i];
for (int i = 0; i "lt" this.hoWeights.Length; ++i)
for (int j = 0; j "lt" this.hoWeights[0].Length; ++j)
result[k++] = this.hoWeights[i][j];
for (int i = 0; i "lt" this.oBiases.Length; ++i)
result[k++] = this.oBiases[i];
return result;
}
// ------------------------------------------------------
public double ComputeOutput(double[] x)
{
double[] hSums = new double[this.nh]; // scratch
double[] oSums = new double[this.no]; // out sums
for (int i = 0; i "lt" x.Length; ++i)
this.iNodes[i] = x[i];
// note: no need to copy x-values unless
// you implement a ToString.
// more efficient to simply use the X[] directly.
// 1. compute i-h sum of weights * inputs
for (int j = 0; j "lt" this.nh; ++j)
for (int i = 0; i "lt" this.ni; ++i)
hSums[j] += this.iNodes[i] *
this.ihWeights[i][j]; // note +=
// 2. add biases to hidden sums
for (int i = 0; i "lt" this.nh; ++i)
hSums[i] += this.hBiases[i];
// 3. apply hidden activation
for (int i = 0; i "lt" this.nh; ++i)
this.hNodes[i] = HyperTan(hSums[i]);
// 4. compute h-o sum of wts * hOutputs
for (int j = 0; j "lt" this.no; ++j)
for (int i = 0; i "lt" this.nh; ++i)
oSums[j] += this.hNodes[i] *
this.hoWeights[i][j]; // [1]
// 5. add biases to output sums
for (int i = 0; i "lt" this.no; ++i)
oSums[i] += this.oBiases[i];
// 6. apply output activation
for (int i = 0; i "lt" this.no; ++i)
this.oNodes[i] = Identity(oSums[i]);
return this.oNodes[0]; // single value
}
// ------------------------------------------------------
private static double HyperTan(double x)
{
if (x "lt" -10.0) return -1.0;
else if (x "gt" 10.0) return 1.0;
else return Math.Tanh(x);
}
// ------------------------------------------------------
private static double Identity(double x)
{
return x;
}
// ------------------------------------------------------
private void ZeroOutGrads()
{
for (int i = 0; i "lt" this.ni; ++i)
for (int j = 0; j "lt" this.nh; ++j)
this.ihGrads[i][j] = 0.0;
for (int j = 0; j "lt" this.nh; ++j)
this.hbGrads[j] = 0.0;
for (int j = 0; j "lt" this.nh; ++j)
for (int k = 0; k "lt" this.no; ++k)
this.hoGrads[j][k] = 0.0;
for (int k = 0; k "lt" this.no; ++k)
this.obGrads[k] = 0.0;
} // ZeroOutGrads()
// ------------------------------------------------------
private void AccumGrads(double y)
{
double[] oSignals = new double[this.no];
double[] hSignals = new double[this.nh];
// 1. compute output node scratch signals
for (int k = 0; k "lt" this.no; ++k)
oSignals[k] = 1 * (this.oNodes[k] - y);
// 2. accum hidden-to-output gradients
for (int j = 0; j "lt" this.nh; ++j)
for (int k = 0; k "lt" this.no; ++k)
hoGrads[j][k] +=
oSignals[k] * this.hNodes[j];
// 3. accum output node bias gradients
for (int k = 0; k "lt" this.no; ++k)
obGrads[k] +=
oSignals[k] * 1.0; // identity() act.
// 4. compute hidden node signals
for (int j = 0; j "lt" this.nh; ++j)
{
double sum = 0.0;
for (int k = 0; k "lt" this.no; ++k)
sum += oSignals[k] * this.hoWeights[j][k];
double derivative =
(1 - this.hNodes[j]) *
(1 + this.hNodes[j]); // assumes tanh
hSignals[j] = derivative * sum;
}
// 5. accum input-to-hidden gradients
for (int i = 0; i "lt" this.ni; ++i)
for (int j = 0; j "lt" this.nh; ++j)
this.ihGrads[i][j] +=
hSignals[j] * this.iNodes[i];
// 6. accum hidden node bias gradients
for (int j = 0; j "lt" this.nh; ++j)
this.hbGrads[j] +=
hSignals[j] * 1.0; // 1.0 dummy
} // AccumGrads
// ------------------------------------------------------
private void UpdateWeights(double lrnRate)
{
// assumes all gradients computed
// 1. update input-to-hidden weights
for (int i = 0; i "lt" this.ni; ++i)
{
for (int j = 0; j "lt" this.nh; ++j)
{
double delta = -1.0 * lrnRate *
this.ihGrads[i][j];
this.ihWeights[i][j] += delta;
}
}
// 2. update hidden node biases
for (int j = 0; j "lt" this.nh; ++j)
{
double delta = -1.0 * lrnRate *
this.hbGrads[j];
this.hBiases[j] += delta;
}
// 3. update hidden-to-output weights
for (int j = 0; j "lt" this.nh; ++j)
{
for (int k = 0; k "lt" this.no; ++k)
{
double delta = -1.0 * lrnRate *
this.hoGrads[j][k];
this.hoWeights[j][k] += delta;
}
}
// 4. update output node biases
for (int k = 0; k "lt" this.no; ++k)
{
double delta = -1.0 * lrnRate *
this.obGrads[k];
this.oBiases[k] += delta;
}
} // UpdateWeights()
// ------------------------------------------------------
public void Train(double[][] trainX, double[] trainY,
double lrnRate, int batSize, int maxEpochs)
{
int n = trainX.Length;
int batchesPerEpoch = n / batSize;
int freq = maxEpochs / 5; // to show progress
int[] indices = new int[n];
for (int i = 0; i "lt" n; ++i)
indices[i] = i;
// ----------------------------------------------------
//
// for epoch = 0; epoch "lt" maxEpochs; ++epoch
// shuffle indices
// for batch = 0; batch "lt" bpe; ++batch
// for item = 0; item "lt" bs; ++item
// compute output
// accum grads
// end-item
// update weights
// zero-out grads
// end-batches
// end-epochs
//
// ----------------------------------------------------
for (int epoch = 0; epoch "lt" maxEpochs; ++epoch)
{
Shuffle(indices);
int ptr = 0; // points into indices
for (int batIdx = 0; batIdx "lt" batchesPerEpoch;
++batIdx)
{
for (int i = 0; i "lt" batSize; ++i)
{
int ii = indices[ptr++]; // compute output
double[] x = trainX[ii];
double y = trainY[ii];
this.ComputeOutput(x); // into this.oNoodes
this.AccumGrads(y);
}
this.UpdateWeights(lrnRate);
this.ZeroOutGrads(); // prep for next batch
} // batches
if (epoch % freq == 0) // progress
{
double mse = this.Error(trainX, trainY);
double acc = this.Accuracy(trainX, trainY, 0.10);
string s1 = "epoch: " +
epoch.ToString().PadLeft(4);
string s2 = " MSE = " +
mse.ToString("F4");
string s3 = " acc (10%) = " +
acc.ToString("F4");
Console.WriteLine(s1 + s2 + s3);
}
} // epoch
} // Train
// ------------------------------------------------------
private void Shuffle(int[] sequence)
{
for (int i = 0; i "lt" sequence.Length; ++i)
{
int r = this.rnd.Next(i, sequence.Length);
int tmp = sequence[r];
sequence[r] = sequence[i];
sequence[i] = tmp;
//sequence[i] = i; // for testing
}
} // Shuffle
// ------------------------------------------------------
public double Error(double[][] trainX, double[] trainY)
{
// MSE
int n = trainX.Length;
double sumSquaredError = 0.0;
for (int i = 0; i "lt" n; ++i)
{
double predY = this.ComputeOutput(trainX[i]);
double actualY = trainY[i];
sumSquaredError += (predY - actualY) *
(predY - actualY);
}
return sumSquaredError / n;
} // Error
// ------------------------------------------------------
public double Accuracy(double[][] dataX,
double[] dataY, double pctClose)
{
// percentage correct using winner-takes all
int n = dataX.Length;
int nCorrect = 0;
int nWrong = 0;
for (int i = 0; i "lt" n; ++i)
{
double predY = this.ComputeOutput(dataX[i]);
double actualY = dataY[i];
//Console.WriteLine("actual Y = " +
// actualY.ToString("F2"));
//Console.WriteLine("pred Y = " +
// predY.ToString("F2"));
if (Math.Abs(predY - actualY) "lt"
Math.Abs(pctClose * actualY))
++nCorrect;
else
++nWrong;
}
return (nCorrect * 1.0) / (nCorrect + nWrong);
}
public double[] Forecast(double[] start, int steps)
{
double[] result = new double[steps];
int n = start.Length;
double[] currInput = new double[start.Length];
for (int i = 0; i "lt" n; ++i)
currInput[i] = start[i];
for (int step = 0; step "lt" steps; ++step)
{
double pred = this.ComputeOutput(currInput);
//Console.WriteLine(pred.ToString("F2"));
result[step] = pred;
// create new input
for (int i = 0; i "lt" n-1; ++i)
currInput[i] = currInput[i + 1];
currInput[n - 1] = pred;
}
return result;
}
// ------------------------------------------------------
public void SaveWeights(string fn)
{
FileStream ofs = new FileStream(fn, FileMode.Create);
StreamWriter sw = new StreamWriter(ofs);
double[] wts = this.GetWeights();
for (int i = 0; i "lt" wts.Length; ++i)
sw.WriteLine(wts[i].ToString("F8")); // one per line
sw.Close();
ofs.Close();
}
public void LoadWeights(string fn)
{
FileStream ifs = new FileStream(fn, FileMode.Open);
StreamReader sr = new StreamReader(ifs);
List"lt"double"gt" listWts = new List"lt"double"gt"();
string line = ""; // one wt per line
while ((line = sr.ReadLine()) != null)
{
// if (line.StartsWith(comment) == true)
// continue;
listWts.Add(double.Parse(line));
}
sr.Close();
ifs.Close();
double[] wts = listWts.ToArray();
this.SetWeights(wts);
}
// ------------------------------------------------------
} // NeuralNetwork class
public class Utils
{
// ------------------------------------------------------
public static double[][] MatLoad(string fn,
int[] usecols, char sep, string comment)
{
List"lt"double[]"gt" result = new List"lt"double[]"gt"();
string line = "";
FileStream ifs = new FileStream(fn, FileMode.Open);
StreamReader sr = new StreamReader(ifs);
while ((line = sr.ReadLine()) != null)
{
if (line.StartsWith(comment) == true)
continue;
string[] tokens = line.Split(sep);
List"lt"double"gt" lst = new List"lt"double"gt"();
for (int j = 0; j "lt" usecols.Length; ++j)
lst.Add(double.Parse(tokens[usecols[j]]));
double[] row = lst.ToArray();
result.Add(row);
}
sr.Close();
ifs.Close();
return result.ToArray();
}
// ------------------------------------------------------
public static double[] MatToVec(double[][] m)
{
int rows = m.Length;
int cols = m[0].Length;
double[] result = new double[rows * cols];
int k = 0;
for (int i = 0; i "lt" rows; ++i)
for (int j = 0; j "lt" cols; ++j)
result[k++] = m[i][j];
return result;
}
// ------------------------------------------------------
public static void MatShow(double[][] m,
int dec, int wid)
{
for (int i = 0; i "lt" m.Length; ++i)
{
for (int j = 0; j "lt" m[0].Length; ++j)
{
double v = m[i][j];
if (Math.Abs(v) "lt" 1.0e-8) v = 0.0; // hack
Console.Write(v.ToString("F" +
dec).PadLeft(wid));
}
Console.WriteLine("");
}
}
// ------------------------------------------------------
public static void VecShow(int[] vec, int wid)
{
for (int i = 0; i "lt" vec.Length; ++i)
Console.Write(vec[i].ToString().PadLeft(wid));
Console.WriteLine("");
}
// ------------------------------------------------------
public static void VecShow(double[] vec,
int dec, int wid, int lineLen, bool newLine)
{
for (int i = 0; i "lt" vec.Length; ++i)
{
if (i != 0 "and" i % lineLen == 0)
Console.WriteLine("");
double x = vec[i];
if (Math.Abs(x) "lt" 1.0e-8) x = 0.0;
Console.Write(x.ToString("F" +
dec).PadLeft(wid));
}
if (newLine == true)
Console.WriteLine("");
}
} // Utils
} // ns
Demo data:
# airline_all.txt # 4 item window # 1.12, 1.18, 1.32, 1.29, 1.21 1.18, 1.32, 1.29, 1.21, 1.35 1.32, 1.29, 1.21, 1.35, 1.48 1.29, 1.21, 1.35, 1.48, 1.48 1.21, 1.35, 1.48, 1.48, 1.36 1.35, 1.48, 1.48, 1.36, 1.19 1.48, 1.48, 1.36, 1.19, 1.04 1.48, 1.36, 1.19, 1.04, 1.18 1.36, 1.19, 1.04, 1.18, 1.15 1.19, 1.04, 1.18, 1.15, 1.26 1.04, 1.18, 1.15, 1.26, 1.41 1.18, 1.15, 1.26, 1.41, 1.35 1.15, 1.26, 1.41, 1.35, 1.25 1.26, 1.41, 1.35, 1.25, 1.49 1.41, 1.35, 1.25, 1.49, 1.70 1.35, 1.25, 1.49, 1.70, 1.70 1.25, 1.49, 1.70, 1.70, 1.58 1.49, 1.70, 1.70, 1.58, 1.33 1.70, 1.70, 1.58, 1.33, 1.14 1.70, 1.58, 1.33, 1.14, 1.40 1.58, 1.33, 1.14, 1.40, 1.45 1.33, 1.14, 1.40, 1.45, 1.50 1.14, 1.40, 1.45, 1.50, 1.78 1.40, 1.45, 1.50, 1.78, 1.63 1.45, 1.50, 1.78, 1.63, 1.72 1.50, 1.78, 1.63, 1.72, 1.78 1.78, 1.63, 1.72, 1.78, 1.99 1.63, 1.72, 1.78, 1.99, 1.99 1.72, 1.78, 1.99, 1.99, 1.84 1.78, 1.99, 1.99, 1.84, 1.62 1.99, 1.99, 1.84, 1.62, 1.46 1.99, 1.84, 1.62, 1.46, 1.66 1.84, 1.62, 1.46, 1.66, 1.71 1.62, 1.46, 1.66, 1.71, 1.80 1.46, 1.66, 1.71, 1.80, 1.93 1.66, 1.71, 1.80, 1.93, 1.81 1.71, 1.80, 1.93, 1.81, 1.83 1.80, 1.93, 1.81, 1.83, 2.18 1.93, 1.81, 1.83, 2.18, 2.30 1.81, 1.83, 2.18, 2.30, 2.42 1.83, 2.18, 2.30, 2.42, 2.09 2.18, 2.30, 2.42, 2.09, 1.91 2.30, 2.42, 2.09, 1.91, 1.72 2.42, 2.09, 1.91, 1.72, 1.94 2.09, 1.91, 1.72, 1.94, 1.96 1.91, 1.72, 1.94, 1.96, 1.96 1.72, 1.94, 1.96, 1.96, 2.36 1.94, 1.96, 1.96, 2.36, 2.35 1.96, 1.96, 2.36, 2.35, 2.29 1.96, 2.36, 2.35, 2.29, 2.43 2.36, 2.35, 2.29, 2.43, 2.64 2.35, 2.29, 2.43, 2.64, 2.72 2.29, 2.43, 2.64, 2.72, 2.37 2.43, 2.64, 2.72, 2.37, 2.11 2.64, 2.72, 2.37, 2.11, 1.80 2.72, 2.37, 2.11, 1.80, 2.01 2.37, 2.11, 1.80, 2.01, 2.04 2.11, 1.80, 2.01, 2.04, 1.88 1.80, 2.01, 2.04, 1.88, 2.35 2.01, 2.04, 1.88, 2.35, 2.27 2.04, 1.88, 2.35, 2.27, 2.34 1.88, 2.35, 2.27, 2.34, 2.64 2.35, 2.27, 2.34, 2.64, 3.02 2.27, 2.34, 2.64, 3.02, 2.93 2.34, 2.64, 3.02, 2.93, 2.59 2.64, 3.02, 2.93, 2.59, 2.29 3.02, 2.93, 2.59, 2.29, 2.03 2.93, 2.59, 2.29, 2.03, 2.29 2.59, 2.29, 2.03, 2.29, 2.42 2.29, 2.03, 2.29, 2.42, 2.33 2.03, 2.29, 2.42, 2.33, 2.67 2.29, 2.42, 2.33, 2.67, 2.69 2.42, 2.33, 2.67, 2.69, 2.70 2.33, 2.67, 2.69, 2.70, 3.15 2.67, 2.69, 2.70, 3.15, 3.64 2.69, 2.70, 3.15, 3.64, 3.47 2.70, 3.15, 3.64, 3.47, 3.12 3.15, 3.64, 3.47, 3.12, 2.74 3.64, 3.47, 3.12, 2.74, 2.37 3.47, 3.12, 2.74, 2.37, 2.78 3.12, 2.74, 2.37, 2.78, 2.84 2.74, 2.37, 2.78, 2.84, 2.77 2.37, 2.78, 2.84, 2.77, 3.17 2.78, 2.84, 2.77, 3.17, 3.13 2.84, 2.77, 3.17, 3.13, 3.18 2.77, 3.17, 3.13, 3.18, 3.74 3.17, 3.13, 3.18, 3.74, 4.13 3.13, 3.18, 3.74, 4.13, 4.05 3.18, 3.74, 4.13, 4.05, 3.55 3.74, 4.13, 4.05, 3.55, 3.06 4.13, 4.05, 3.55, 3.06, 2.71 4.05, 3.55, 3.06, 2.71, 3.06 3.55, 3.06, 2.71, 3.06, 3.15 3.06, 2.71, 3.06, 3.15, 3.01 2.71, 3.06, 3.15, 3.01, 3.56 3.06, 3.15, 3.01, 3.56, 3.48 3.15, 3.01, 3.56, 3.48, 3.55 3.01, 3.56, 3.48, 3.55, 4.22 3.56, 3.48, 3.55, 4.22, 4.65 3.48, 3.55, 4.22, 4.65, 4.67 3.55, 4.22, 4.65, 4.67, 4.04 4.22, 4.65, 4.67, 4.04, 3.47 4.65, 4.67, 4.04, 3.47, 3.05 4.67, 4.04, 3.47, 3.05, 3.36 4.04, 3.47, 3.05, 3.36, 3.40 3.47, 3.05, 3.36, 3.40, 3.18 3.05, 3.36, 3.40, 3.18, 3.62 3.36, 3.40, 3.18, 3.62, 3.48 3.40, 3.18, 3.62, 3.48, 3.63 3.18, 3.62, 3.48, 3.63, 4.35 3.62, 3.48, 3.63, 4.35, 4.91 3.48, 3.63, 4.35, 4.91, 5.05 3.63, 4.35, 4.91, 5.05, 4.04 4.35, 4.91, 5.05, 4.04, 3.59 4.91, 5.05, 4.04, 3.59, 3.10 5.05, 4.04, 3.59, 3.10, 3.37 4.04, 3.59, 3.10, 3.37, 3.60 3.59, 3.10, 3.37, 3.60, 3.42 3.10, 3.37, 3.60, 3.42, 4.06 3.37, 3.60, 3.42, 4.06, 3.96 3.60, 3.42, 4.06, 3.96, 4.20 3.42, 4.06, 3.96, 4.20, 4.72 4.06, 3.96, 4.20, 4.72, 5.48 3.96, 4.20, 4.72, 5.48, 5.59 4.20, 4.72, 5.48, 5.59, 4.63 4.72, 5.48, 5.59, 4.63, 4.07 5.48, 5.59, 4.63, 4.07, 3.62 5.59, 4.63, 4.07, 3.62, 4.05 4.63, 4.07, 3.62, 4.05, 4.17 4.07, 3.62, 4.05, 4.17, 3.91 3.62, 4.05, 4.17, 3.91, 4.19 4.05, 4.17, 3.91, 4.19, 4.61 4.17, 3.91, 4.19, 4.61, 4.72 3.91, 4.19, 4.61, 4.72, 5.35 4.19, 4.61, 4.72, 5.35, 6.22 4.61, 4.72, 5.35, 6.22, 6.06 4.72, 5.35, 6.22, 6.06, 5.08 5.35, 6.22, 6.06, 5.08, 4.61 6.22, 6.06, 5.08, 4.61, 3.90 6.06, 5.08, 4.61, 3.90, 4.32



.NET Test Automation Recipes
Software Testing
SciPy Programming Succinctly
Keras Succinctly
R Programming
2026 Visual Studio Live
2025 Summer MLADS Conference
2026 DevIntersection Conference
2025 Machine Learning Week
2025 Ai4 Conference
2026 G2E Conference
2026 iSC West Conference
The demo works fine, thanks. The forecast looks great. The batch size of 1 could be better because of the small dataset. You could add a bit of noise to the output neuron, maybe this will increase the training.
The poor way:
// 1. compute output node scratch signalsthis.oNodes[0] -=this.oNodes[0] * Random.Shared.NextSingle() * 0.01f;A small increase with higher lr = 0.05 was possible.
Best output: Model accuracy (within 10% actual) = 0.8929