|
public class NeuralNetwork |
|
{ |
|
public delegate void PrintEventHandler(object message); |
|
public event PrintEventHandler Cout; |
|
private int numInput; |
|
private int numHidden; |
|
private int numOutput; |
|
|
|
private double[] inputs; |
|
private double[][] ihWeights; // input-to-hidden |
|
private double[] ihBiases; |
|
private double[][] hoWeights; // hidden-to-output |
|
private double[] hoBiases; |
|
private double[] outputs; |
|
|
|
|
|
public void SelfDemo() |
|
{ |
|
try |
|
{ |
|
Console.WriteLine("\nBegin neural network feed-forward demo\n"); |
|
|
|
Console.WriteLine("Creating a 3-input, 4-hidden, 2-output neural network"); |
|
Console.WriteLine("Using log-sigmoid function for input-to-hidden and hidden-to-output activation"); |
|
|
|
const int numInput = 3; |
|
const int numHidden = 4; |
|
const int numOutput = 2; |
|
|
|
NeuralNetwork nn = new NeuralNetwork(numInput, numHidden, numOutput); |
|
nn.Cout += message => Console.Write("{0}", message); |
|
|
|
const int numWeights = (numInput * numHidden) + (numHidden * numOutput) + numHidden + numOutput; // (3*4)+(4*2)+4+2 = 26 |
|
|
|
double[] weights = new double[numWeights] { |
|
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, |
|
-2.0, -6.0, -1.0, -7.0, |
|
1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, |
|
-2.5, -5.0 }; |
|
|
|
Console.WriteLine("\nWeights and biases are:"); |
|
nn.ShowVector(weights, 2); |
|
|
|
Console.WriteLine("Loading neural network weights and biases"); |
|
nn.SetWeights(weights); |
|
|
|
Console.WriteLine("\nSetting neural network inputs:"); |
|
double[] xValues = new double[] { 2.0, 3.0, 4.0 }; |
|
nn.ShowVector(xValues, 2); |
|
|
|
Console.WriteLine("Loading inputs and computing outputs\n"); |
|
double[] yValues = nn.ComputeOutputs(xValues); |
|
|
|
Console.WriteLine("\nNeural network outputs are:"); |
|
nn.ShowVector(yValues, 4); |
|
|
|
Console.WriteLine("\nEnd neural network demo\n"); |
|
Console.ReadLine(); |
|
} |
|
catch (Exception ex) |
|
{ |
|
Console.WriteLine(ex.Message); |
|
Console.ReadLine(); |
|
} |
|
|
|
Console.ReadLine(); |
|
} |
|
|
|
public void Write(string format, object[] args) |
|
{ |
|
string message = string.Format(format, args); |
|
if (this.Cout != null) |
|
{ |
|
this.Cout(message); |
|
} |
|
} |
|
|
|
public void Write(string message) |
|
{ |
|
if (this.Cout != null) |
|
{ |
|
this.Cout(message); |
|
} |
|
} |
|
|
|
public NeuralNetwork(int numInput, int numHidden, int numOutput) |
|
{ |
|
this.numInput = numInput; |
|
this.numHidden = numHidden; |
|
this.numOutput = numOutput; |
|
|
|
inputs = new double[numInput]; |
|
ihWeights = MakeMatrix(numInput, numHidden); |
|
ihBiases = new double[numHidden]; |
|
hoWeights = MakeMatrix(numHidden, numOutput); |
|
hoBiases = new double[numOutput]; |
|
outputs = new double[numOutput]; // aka hoOutputs |
|
} |
|
|
|
/*public int numWeights() |
|
{ |
|
int numWeights = (numInput * numHidden) + (numHidden * numOutput) + numHidden + numOutput; // (3*4)+(4*2)+4+2 = 26 |
|
return numWeights; |
|
}*/ |
|
|
|
private static double[][] MakeMatrix(int rows, int cols) |
|
{ |
|
double[][] result = new double[rows][]; |
|
for (int i = 0; i < rows; ++i) |
|
result[i] = new double[cols]; |
|
return result; |
|
} |
|
|
|
public void SetWeights(double[] weights) |
|
{ |
|
// order: ihWeights (row-col order), ihBiases, hoWeights, hoBiases |
|
int numWeights = (numInput * numHidden) + (numHidden * numOutput) + numHidden + numOutput; |
|
if (weights.Length != numWeights) |
|
throw new Exception("The weights array length: " + weights.Length + " does not match the total number of weights and biases: " + numWeights); |
|
|
|
int k = 0; // points into weights param |
|
|
|
for (int i = 0; i < numInput; ++i) |
|
for (int j = 0; j < numHidden; ++j) |
|
ihWeights[i][j] = weights[k++]; |
|
|
|
for (int i = 0; i < numHidden; ++i) |
|
ihBiases[i] = weights[k++]; |
|
|
|
for (int i = 0; i < numHidden; ++i) |
|
for (int j = 0; j < numOutput; ++j) |
|
hoWeights[i][j] = weights[k++]; |
|
|
|
for (int i = 0; i < numOutput; ++i) |
|
hoBiases[i] = weights[k++]; |
|
} |
|
|
|
public void ShowMatrix(double[][] matrix, int numRows) |
|
{ |
|
Write("\n"); |
|
int ct = 0; |
|
if (numRows == -1) numRows = int.MaxValue; // if numRows == -1, show all rows |
|
for (int i = 0; i < matrix.Length && ct < numRows; ++i) |
|
{ |
|
for (int j = 0; j < matrix[0].Length; ++j) |
|
{ |
|
if (matrix[i][j] >= 0.0) Write(" "); // blank space instead of '+' sign |
|
Write(matrix[i][j].ToString("F2") + " "); |
|
} |
|
Write("\n"); |
|
++ct; |
|
} |
|
Write("\n"); |
|
} |
|
|
|
public void ShowVector(double[] vector, int decimals) |
|
{ |
|
Write("\n"); |
|
for (int i = 0; i < vector.Length; ++i) |
|
{ |
|
if (i > 0 && i % 12 == 0) // max of 12 values per row |
|
Write("\n"); |
|
if (vector[i] >= 0.0) Console.Write(" "); |
|
Write(vector[i].ToString("F" + decimals) + " "); // 2 decimals |
|
} |
|
Console.WriteLine("\n"); |
|
} |
|
|
|
public double[] ComputeOutputs(double[] xValues) |
|
{ |
|
if (xValues.Length != numInput) |
|
throw new Exception("Inputs array length " + inputs.Length + " does not match NN numInput value " + numInput); |
|
|
|
double[] ihSums = new double[this.numHidden]; // these scratch arrays could be class members |
|
double[] ihOutputs = new double[this.numHidden]; |
|
double[] hoSums = new double[this.numOutput]; |
|
|
|
for (int i = 0; i < xValues.Length; ++i) // copy x-values to inputs |
|
this.inputs[i] = xValues[i]; |
|
|
|
//Console.WriteLine("Inputs:"); |
|
//FeedForwardProgram.ShowVector(this.inputs, 2); |
|
|
|
Write("input-to-hidden weights:"); |
|
ShowMatrix(this.ihWeights, -1); |
|
|
|
for (int j = 0; j < numHidden; ++j) // compute input-to-hidden weighted sums |
|
for (int i = 0; i < numInput; ++i) |
|
ihSums[j] += this.inputs[i] * ihWeights[i][j]; |
|
|
|
Write("input-to-hidden sums before adding i-h biases:"); |
|
ShowVector(ihSums, 2); |
|
|
|
Write("input-to-hidden biases:"); |
|
ShowVector(this.ihBiases, 2); |
|
|
|
for (int i = 0; i < numHidden; ++i) // add biases to input-to-hidden sums |
|
ihSums[i] += ihBiases[i]; |
|
|
|
Write("input-to-hidden sums after adding i-h biases:"); |
|
ShowVector(ihSums, 2); |
|
|
|
for (int i = 0; i < numHidden; ++i) // determine input-to-hidden output |
|
ihOutputs[i] = LogSigmoid(ihSums[i]); |
|
|
|
Write("input-to-hidden outputs after log-sigmoid activation:"); |
|
ShowVector(ihOutputs, 2); |
|
|
|
Console.WriteLine("hidden-to-output weights:"); |
|
ShowMatrix(hoWeights, -1); |
|
|
|
for (int j = 0; j < numOutput; ++j) // compute hidden-to-output weighted sums |
|
for (int i = 0; i < numHidden; ++i) |
|
hoSums[j] += ihOutputs[i] * hoWeights[i][j]; |
|
|
|
Write("hidden-to-output sums before adding h-o biases:"); |
|
ShowVector(hoSums, 2); |
|
|
|
Write("hidden-to-output biases:"); |
|
ShowVector(this.hoBiases, 2); |
|
|
|
for (int i = 0; i < numOutput; ++i) // add biases to input-to-hidden sums |
|
hoSums[i] += hoBiases[i]; |
|
|
|
Write("hidden-to-output sums after adding h-o biases:"); |
|
ShowVector(hoSums, 2); |
|
|
|
for (int i = 0; i < numOutput; ++i) // determine hidden-to-output result |
|
this.outputs[i] = LogSigmoid(hoSums[i]); |
|
|
|
double[] result = new double[numOutput]; // copy hidden-to-output to this.outputs |
|
this.outputs.CopyTo(result, 0); |
|
|
|
return result; |
|
} // ComputeOutputs |
|
|
|
private static double LogSigmoid(double z) |
|
{ |
|
if (z < -20.0) return 0.0; |
|
else if (z > 20.0) return 1.0; |
|
else return 1.0 / (1.0 + Math.Exp(-z)); |
|
} |
|
|
|
//extend |
|
private static int CalculateWeight(int inputNodeCount, int hiddenNodeCount, int outputNodeCount) |
|
{ |
|
int numWeights = (inputNodeCount * hiddenNodeCount) + (hiddenNodeCount * outputNodeCount) + hiddenNodeCount + outputNodeCount; //count the total weights |
|
return numWeights; |
|
} |
|
|
|
} // class |