This JavaScript program demonstrates how to use backpropagation in a neural network to learn a set of data points. The tables display the weights of the training layers and the training data.
<!DOCTYPE html>
<html>
<head>
<title>XoaX.net's Javascript</title>
<script type="text/javascript" src="BackpropagationWithTables.js"></script>
<style type="text/css">
th {
background-color: white;
}
td {
background-color: #E8E8E8;
}
</style>
</head>
<body onload="Initialize()">
</body>
</html>function Initialize() {
const kiInputs = 3;
const kiOutputs = 2;
const kiSamples = 6;
// Make a random set of inputs and outputs. Keep it simple and don't require them to be scaled. Leave them in the range [0, 1]
let daaTrainingInputs = [];
let daaTrainingOutputs = [];
for(let i = 0; i < kiSamples; ++i) {
daaTrainingInputs[i] = [];
daaTrainingOutputs[i] = [];
for(let j = 0; j < kiInputs; ++j) {
daaTrainingInputs[i][j] = Math.random();
}
for(let j = 0; j < kiOutputs; ++j) {
daaTrainingOutputs[i][j] = Math.random();
}
}
// Create a training data object
let qTrainingData = new CTrainingData(daaTrainingInputs, daaTrainingOutputs);
// Create the hidden neural network layer sizes definitions
let iaHiddenLayers = [7, 4];
// Create the neural network object
let qNN = new CNeuralNetwork(qTrainingData, iaHiddenLayers);
let qNNElement = qNN.CreateAndReturnNetworkVisualization();
let qBody = document.getElementsByTagName("body")[0];
qBody.appendChild(qNNElement);
// Run through the layer array
const kiEpochs = 10000;
const kdLearningRate = .0001
for (let i = 0; i < kiEpochs; ++i) {
let dTotalLoss = qNN.Train(kdLearningRate);
console.log("Total Loss = "+dTotalLoss);
}
qNN.FillInElements();
let qDataTable = qTrainingData.CreateAndReturnDataTable();
qBody.appendChild(qDataTable);
}
class CNeuralNetwork {
constructor(qTrainingData, iaHiddenLayers) {
this.mqTrainingData = qTrainingData;
this.miaSizesIO = [];
this.miaSizesIO[0] = qTrainingData.InputSize();
for (let i = 0; i < iaHiddenLayers.length; ++i) {
this.miaSizesIO[i+1] = iaHiddenLayers[i];
}
this.miaSizesIO[this.miaSizesIO.length] = qTrainingData.OutputSize();
// Construct the layers
this.mqaLayers = [];
for (let i = 0; i < this.miaSizesIO.length - 1; ++i) {
if (i < this.miaSizesIO.length - 2) {
this.mqaLayers[i] = new CLayer(this.miaSizesIO[i], this.miaSizesIO[i+1], CActivation.GetActivationReLU());
} else { // The last layer has the identity as its activation function
this.mqaLayers[i] = new CLayer(this.miaSizesIO[i], this.miaSizesIO[i+1], CActivation.GetActivationIdentity());
}
}
}
Train(kdLearningRate) {
let dTotalLoss = 0.0;
const kiSampleCount = this.mqTrainingData.SampleCount();
const kiInputSize = this.mqTrainingData.InputSize();
const kiOutputSize = this.mqTrainingData.OutputSize();
for (let s = 0; s < kiSampleCount; ++s) {
let daInput = this.mqTrainingData.Input(s);
let daOutput = this.mqTrainingData.Output(s);
// Feedforward the inputs to get the outputs
let daLayerOutput = daInput;
for (let l = 0; l < this.mqaLayers.length; ++l) {
daLayerOutput = this.mqaLayers[l].FeedForward(daLayerOutput);
}
// Calculate the output errors
let daOutputErrors = [];
for (let i = 0; i < kiOutputSize; ++i) {
let dError = daLayerOutput[i] - daOutput[i];
daOutputErrors[i] = dError;
dTotalLoss += (dError*dError);
}
// Propagate the errors backward
for (let l = this.mqaLayers.length - 1; l >= 0; --l) {
daOutputErrors = this.mqaLayers[l].PropagateErrors(daOutputErrors);
}
// Use backprogatation to adjust the weights. The errors are held internally
for (let l = this.mqaLayers.length - 1; l >= 0; --l) {
this.mqaLayers[l].BackPropagationCorrection(.0001);
}
}
return dTotalLoss/kiSampleCount;
}
CreateAndReturnNetworkVisualization() {
let qContainer = document.createElement("div");
for (let l = 0; l < this.mqaLayers.length; ++l) {
let qTable = this.mqaLayers[l].CreateAndReturnLayerTable(l);
qContainer.appendChild(qTable);
}
return qContainer;
}
FillInElements() {
for (let l = 0; l < this.mqaLayers.length; ++l) {
this.mqaLayers[l].FillInElements();
}
}
}
class CLayer {
// Each layer can hold its last input, output, z-output (without activation), last errors
// Add an activation
constructor(kiIn, kiOut, qActivation) {
// These values will be kept internally
this.mdaErrorsIn = [];
this.mdaInput = [];
this.mdaZ = [];
this.mdaOutput = [];
this.mdaErrorsOut = [];
this.mqaErrorsInElements = [];
this.mqaInputElements = [];
this.mqActivationElement = "";
this.mqaaWBElements = [];
this.mqaZElements = [];
this.mqaOutputElements = [];
this.mqaErrorsOutElements = [];
this.mdaaWB = [];
// The output size tells how many neurons to use
for (let i = 0; i < kiOut; ++i) {
this.mdaaWB[i] = [];
this.mqaaWBElements[i] = [];
// Add an extra value to the inputs (weights) for the bias term
for (let j = 0; j <= kiIn; ++j) {
this.mdaaWB[i][j] = Math.random() - .5;
}
}
this.mqActivation = qActivation;
}
FeedForward(daInput) {
// Copy the input
for (let i = 0; i < daInput.length; ++i) {
this.mdaInput[i] = daInput[i];
}
for (let j = 0; j < this.mdaaWB.length; ++j) {
// Start with the bias in the last position
this.mdaZ[j] = this.mdaaWB[j][daInput.length];
for (let i = 0; i < daInput.length; ++i) {
this.mdaZ[j] += this.mdaaWB[j][i]*this.mdaInput[i];
}
this.mdaOutput[j] = this.mqActivation.F(this.mdaZ[j]);
}
// Copy the output and return it
let daOutput = [];
for (let j = 0; j < this.mdaaWB.length; ++j) {
daOutput[j] = this.mdaOutput[j];
}
return daOutput;
}
PropagateErrors(daErrorsOut) {
// Copy the output errors multiplied by the devivative of the activation function
for (let j = 0; j < daErrorsOut.length; ++j) {
this.mdaErrorsOut[j] = daErrorsOut[j]*this.mqActivation.DF(this.mdaZ[j]);
}
// Propagate the errors backward to get the input errors
let daErrorsIn = []
for (let i = 0; i < this.mdaInput.length; ++i) {
daErrorsIn[i] = 0.0;
for (let j = 0; j < daErrorsOut.length; ++j) {
daErrorsIn[i] += this.mdaaWB[j][i]*this.mdaErrorsOut[j];
}
}
// Copy the input errors before returning them
for (let i = 0; i < this.mdaaWB[0].length; ++i) {
this.mdaErrorsIn[i] = daErrorsIn[i];
}
return daErrorsIn;
}
BackPropagationCorrection(dLearningRate) {
// Appy these errors to adjust the weights
for (let j = 0; j < this.mdaaWB.length; ++j) {
for (let i = 0; i < this.mdaaWB[j].length - 1; ++i) {
this.mdaaWB[j][i] -= dLearningRate*this.mdaErrorsOut[j]*this.mdaInput[i] ;
}
this.mdaaWB[j][this.mdaaWB[j].length - 1] -= dLearningRate*this.mdaErrorsOut[j];
}
}
InputSize() {
// Subtract 1 because the last value is the constant bias value.
return this.mdaaWB[0].length - 1;
}
OutputSize() {
return this.mdaaWB.length;
}
CreateAndReturnLayerTable(iLayerIndex) {
const kiInputSize = this.InputSize();
const kiOutputSize = this.OutputSize();
// Create the table element
let qTable = document.createElement("table");
qTable.cellPadding = 5;
qTable.cellSpacing = 0;
qTable.border = 1;
let qCaption = document.createElement("caption");
qCaption.innerHTML = "Neural Network Layer "+iLayerIndex;
qTable.appendChild(qCaption);
// Add all of the rows to the table
let qErrorsInRow = document.createElement("tr");
qTable.appendChild(qErrorsInRow);
let qInputHeadersRow = document.createElement("tr");
qTable.appendChild(qInputHeadersRow);
let qInputRow = document.createElement("tr");
qTable.appendChild(qInputRow);
let qNeuronHeadersRow = document.createElement("tr");
qTable.appendChild(qNeuronHeadersRow);
let qaNeuronsRows = [];
for (let i = 0; i < kiOutputSize; ++i) {
qaNeuronsRows[i] = document.createElement("tr");
qTable.appendChild(qaNeuronsRows[i]);
}
// Add the errors in elements and the input values
let qErrorsInHeader = document.createElement("th");
qErrorsInRow.appendChild(qErrorsInHeader);
qErrorsInHeader.innerHTML = "Errors In";
qInputHeadersRow.appendChild(document.createElement("td"));
let qInputHeader = document.createElement("th");
qInputRow.appendChild(qInputHeader);
qInputHeader.innerHTML = "Input";
for (let i = 0; i < kiInputSize; ++i) {
this.mqaErrorsInElements[i] = document.createElement("td");
qErrorsInRow.appendChild(this.mqaErrorsInElements[i]);
let qInputVariableHeader = document.createElement("th");
qInputVariableHeader.innerHTML = "X"+i;
qInputHeadersRow.appendChild(qInputVariableHeader);
this.mqaInputElements[i] = document.createElement("td");
qInputRow.appendChild(this.mqaInputElements[i]);
}
// Add spacing elements to the errors in row
let qSpaceElement = document.createElement("td");
qSpaceElement.rowSpan = 3;
qErrorsInRow.appendChild(qSpaceElement);
qSpaceElement = document.createElement("td");
qSpaceElement.colSpan = 2;
qErrorsInRow.appendChild(qSpaceElement);
qSpaceElement = document.createElement("td");
qSpaceElement.rowSpan = 3;
qErrorsInRow.appendChild(qSpaceElement);
// Activation elements
let qActivationHeader = document.createElement("th");
qActivationHeader.innerHTML = "Activation Function";
qActivationHeader.colSpan = 2;
qInputHeadersRow.appendChild(qActivationHeader);
this.mqActivationElement = document.createElement("td");
this.mqActivationElement.colSpan = 2;
qInputRow.appendChild(this.mqActivationElement);
// Add the headers for the weights, bias, outputs, and errors out
qNeuronHeadersRow.appendChild(document.createElement("td"));
for (let i = 0; i < kiInputSize; ++i) {
let qWeightHeader = document.createElement("th");
qWeightHeader.innerHTML = "W"+i;
qNeuronHeadersRow.appendChild(qWeightHeader);
}
let qBiasHeader = document.createElement("th");
qBiasHeader.innerHTML = "Bias";
qNeuronHeadersRow.appendChild(qBiasHeader);
let qZHeader = document.createElement("th");
qZHeader.innerHTML = "Z";
qNeuronHeadersRow.appendChild(qZHeader);
let qOutputHeader = document.createElement("th");
qOutputHeader.innerHTML = "Output";
qNeuronHeadersRow.appendChild(qOutputHeader);
let qErrorsOutHeader = document.createElement("th");
qErrorsOutHeader.innerHTML = "Errors Out";
qNeuronHeadersRow.appendChild(qErrorsOutHeader);
// Add the entries for the neuron weights and biases, z, output, and errors out.
for (let i = 0; i < kiOutputSize; ++i) {
let qCurrRow = qaNeuronsRows[i];
let qNeuron = document.createElement("th");
qNeuron.innerHTML = "Neuron "+i;
qCurrRow.appendChild(qNeuron);
// Use <= to include the bias term
for (let j = 0; j <= kiInputSize; ++j) {
this.mqaaWBElements[i][j] = document.createElement("td");
qCurrRow.appendChild(this.mqaaWBElements[i][j]);
}
// Add in the z, output and error out terms
this.mqaZElements[i] = document.createElement("td");
this.mqaOutputElements[i] = document.createElement("td");
this.mqaErrorsOutElements[i] = document.createElement("td");
qCurrRow.appendChild(this.mqaZElements[i]);
qCurrRow.appendChild(this.mqaOutputElements[i]);
qCurrRow.appendChild(this.mqaErrorsOutElements[i]);
}
return qTable;
}
FillInElements() {
const kiInputSize = this.InputSize();
const kiOutputSize = this.OutputSize();
for (let i = 0; i < kiInputSize; ++i) {
this.mqaErrorsInElements[i].innerHTML = SetFixedDecimal(this.mdaErrorsIn[i]);
this.mqaInputElements[i].innerHTML = SetFixedDecimal(this.mdaInput[i]);
}
for (let i = 0; i < kiOutputSize; ++i) {
for (let j = 0; j <= kiInputSize; ++j) {
this.mqaaWBElements[i][j].innerHTML = SetFixedDecimal(this.mdaaWB[i][j]);
}
this.mqaZElements[i].innerHTML = SetFixedDecimal(this.mdaZ[i]);
this.mqaOutputElements[i].innerHTML = SetFixedDecimal(this.mdaOutput[i]);
this.mqaErrorsOutElements[i].innerHTML = SetFixedDecimal(this.mdaErrorsOut[i]);
}
this.mqActivationElement.innerHTML = this.mqActivation.Name();
}
}
function SetFixedDecimal(dX) {
return Number.parseFloat(dX).toFixed(6);
}
class CActivation {
constructor(fnF, fnDF, sName) {
this.mfnF = fnF;
this.mfnDF = fnDF;
this.msName = sName;
}
F(x) {
return this.mfnF(x);
}
DF(x) {
return this.mfnDF(x);
}
Name() {
return this.msName;
}
static GetActivationReLU() {
return new CActivation(CActivation.ReLU, CActivation.DerivativeReLU, "ReLU");
}
static GetActivationIdentity() {
return new CActivation(CActivation.Identity, CActivation.DerivativeIdentity, "Identity");
}
static ReLU(x) {
return (x > 0.0) ? x : 0.0;
}
static DerivativeReLU(x) {
return (x > 0.0) ? 1.0 : 0.0;
}
static Identity(x) {
return x;
}
static DerivativeIdentity(x) {
return 1;
}
}
class CTrainingData {
constructor(daaInputs, daaOutputs) {
// Create internal copies of the data
this.mdaaInputs = new Array();
for (let i = 0; i < daaInputs.length; ++i) {
this.mdaaInputs[i] = new Array();
for (let j = 0; j < daaInputs[i].length; ++j) {
this.mdaaInputs[i][j] = daaInputs[i][j];
}
}
this.mdaaOutputs = new Array();
for (let i = 0; i < daaOutputs.length; ++i) {
this.mdaaOutputs[i] = new Array();
for (let j = 0; j < daaOutputs[i].length; ++j) {
this.mdaaOutputs[i][j] = daaOutputs[i][j];
}
}
}
CreateAndReturnDataTable() {
const kiInputSize = this.InputSize();
const kiOutputSize = this.OutputSize();
const kiSampleCount = this.SampleCount();
// Create a table element
let qTable = document.createElement("table");
qTable.cellPadding = 5;
qTable.cellSpacing = 0;
qTable.border = 1;
let qCaption = document.createElement("caption");
qCaption.innerHTML = "Training Data";
qTable.appendChild(qCaption);
// Add two rows for the headers
let qMainRow = document.createElement("tr");
qTable.appendChild(qMainRow);
let qHeadersRow = document.createElement("tr");
qTable.appendChild(qHeadersRow);
// Add the first row of headers
qMainRow.appendChild(document.createElement("td"));
let qInputHeader = document.createElement("th");
qInputHeader.colSpan = kiInputSize;
qInputHeader.innerHTML = "Input";
qMainRow.appendChild(qInputHeader);
let qOutputHeader = document.createElement("th");
qOutputHeader.colSpan = kiOutputSize;
qOutputHeader.innerHTML = "Output";
qMainRow.appendChild(qOutputHeader);
// Add in the second row of headers
qHeadersRow.appendChild(document.createElement("td"));
for (let i = 0; i < kiInputSize; ++i) {
let qInputVarHeader = document.createElement("th");
qInputVarHeader.innerHTML = "X"+i;
qHeadersRow.appendChild(qInputVarHeader);
}
for (let i = 0; i < kiOutputSize; ++i) {
let qOutputVarHeader = document.createElement("th");
qOutputVarHeader.innerHTML = "Z"+i;
qHeadersRow.appendChild(qOutputVarHeader);
}
// Add rows for each input and output data element
for (let i = 0; i < kiSampleCount; ++i) {
let qCurrRowElement = document.createElement("tr");
let qRowHeader = document.createElement("th");
qRowHeader.innerHTML = "Data "+i;
qCurrRowElement.appendChild(qRowHeader);
qTable.appendChild(qCurrRowElement);
for (let j = 0; j < kiInputSize; ++j) {
let qDataElement = document.createElement("td");
qDataElement.innerHTML = SetFixedDecimal(this.mdaaInputs[i][j]);
qCurrRowElement.appendChild(qDataElement);
}
for (let j = 0; j < kiOutputSize; ++j) {
let qDataElement = document.createElement("td");
qDataElement.innerHTML = SetFixedDecimal(this.mdaaOutputs[i][j]);
qCurrRowElement.appendChild(qDataElement);
}
}
return qTable;
}
Input(i) {
return this.mdaaInputs[i];
}
Output(i) {
return this.mdaaOutputs[i];
}
InputSize() {
return this.mdaaInputs[0].length;
}
SampleCount() {
return this.mdaaInputs.length;
}
OutputSize() {
return this.mdaaOutputs[0].length;
}
}
© 20072026 XoaX.net LLC. All rights reserved.