#region File and License Information
/* Based on code from http://www.c-sharpcorner.com/uploadfile/rmcochran/ai_oop_neuralnet06192006090112am/ai_oop_neuralnet.aspx */
/*
<File>
<Copyright>Copyright � 2007, Daniel Vaughan. All rights reserved.</Copyright>
<License see="prj:///Documentation/License.txt"/>
<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
<CreationDate>2008-12-27 18:11:45Z</CreationDate>
<LastSubmissionDate>$Date: $</LastSubmissionDate>
<Version>$Revision: $</Version>
</File>
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
namespace DanielVaughan.AI.NeuralNetworking
{
/// <summary>
/// An artificial neural network.
/// </summary>
[Serializable]
public partial class NeuralNetwork
{
double trueLevel = .99;
double falseLevel = .01;
double middleLevel = .5;
NeuralLayer InputLayer { get; set; }
NeuralLayer HiddenLayer { get; set; }
NeuralLayer OutputLayer { get; set; }
//NeuralLayer ContextLayer { get; set; }
readonly object networkLock = new object();
TrainingSet totalTrainingSet;
/// <summary>
/// Sets the input value at the specified index to be the value of either
/// the true level or false level.
/// </summary>
/// <param name="neuronIndex">Index of the neuron.</param>
/// <param name="inputValue">if set to <c>true</c> the input layer
/// at the specified index will be set to the value of the trueLevel value
/// (a high double value less than 1),
/// otherwise it will be set to the falseLevel value
/// (a low double value greater than 0).</param>
public void SetInputValue(int neuronIndex, bool inputValue)
{
lock (networkLock)
{
InputLayer[neuronIndex].Output = inputValue ? trueLevel : falseLevel;
}
}
/// <summary>
/// Gets the output value at the specified index.
/// </summary>
/// <param name="neuronIndex">Index of the neuron.</param>
/// <returns>The value of the output.</returns>
public double GetOutputValue(int neuronIndex)
{
lock (networkLock)
{
return OutputLayer[neuronIndex].Output;
}
}
/// <summary>
/// Gets the output layer or sets the input layer at the specified index with a value
/// representing either <code>true</code> or <code>false</code>.
/// </summary>
/// <value></value>
public bool this[int neuronIndex]
{
get
{
lock (networkLock)
{
return OutputLayer[neuronIndex].Output > middleLevel;
}
}
set
{
lock (networkLock)
{
InputLayer[neuronIndex].Output = value ? trueLevel : falseLevel;
}
}
}
/// <summary>
/// Sets the input to the neural network as booleans.
/// </summary>
/// <value>The input as booleans.</value>
public IEnumerable<bool> InputAsBooleans
{
set
{
ArgumentValidator.AssertNotNull(value, "value");
int count = 0;
foreach (bool neuronValue in value)
{
InputLayer[count].Output = neuronValue ? trueLevel : falseLevel;
count++;
}
}
}
/// <summary>
/// Sets the raw input doubles prior to pulsing.
/// </summary>
/// <value>The input.</value>
public IEnumerable<double> Input
{
set
{
lock (networkLock)
{
ArgumentValidator.AssertNotNull(value, "value");
int count = 0;
foreach (var neuronValue in value)
{
InputLayer[count].Output = neuronValue;
count++;
}
}
}
}
/// <summary>
/// Gets the output as booleans.
/// </summary>
/// <value>The output as booleans.</value>
public IEnumerable<bool> OutputAsBooleans
{
get
{
foreach (var neuron in OutputLayer)
{
yield return neuron.Output > middleLevel;
}
}
}
/// <summary>
/// Gets the outputs of the output layer.
/// </summary>
/// <value>The output.</value>
public IEnumerable<double> Output
{
get
{
var result = from neuron in OutputLayer
select neuron.Output;
return result.ToList();
}
}
/// <summary>
/// Gets the output error level.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>A value representing the error level of the output layer
/// at the specified index.</returns>
public double GetOutputError(int index)
{
var result = OutputLayer[index].Error;
return result;
}
/// <summary>
/// Gets or sets how rapidly the network learns. That is, how much affect
/// new input has on the network.
/// </summary>
/// <value>The learning rate.</value>
public double LearningRate { get; set; }
public NeuralNetwork()
{
LearningRate = 0.3;
}
/// <summary>
/// Initializes a new instance of the <see cref="NeuralNetwork"/> class.
/// </summary>
/// <param name="inputNeuronCount">The number of neuron to have in the input layer.</param>
/// <param name="hiddenNeuronCount">The number of neuron to have in the hidden layer.</param>
/// <param name="outputNeuronCount">The number of neuron to have in the output layer.</param>
public NeuralNetwork(int inputNeuronCount, int hiddenNeuronCount, int outputNeuronCount) : this()
{
Initialize(inputNeuronCount, hiddenNeuronCount, outputNeuronCount);
}
/// <summary>
/// Applies the input.
/// </summary>
public void Pulse()
{
lock (networkLock)
{
HiddenLayer.Pulse();
OutputLayer.Pulse();
}
}
void ApplyLearning()
{
lock (networkLock)
{
HiddenLayer.ApplyLearning(this);
OutputLayer.ApplyLearning(this);
}
}
void InitializeLearning()
{
lock (networkLock)
{
HiddenLayer.InitializeLearning(this);
OutputLayer.InitializeLearning(this);
}
}
/// <summary>
/// Trains the network using the specified training set.
/// </summary>
/// <param name="trainingSet">The training set.</param>
/// <param name="iterations">The number of training iterations to perform.</param>
public void Train(TrainingSet trainingSet, int iterations)
{
Train(trainingSet, TrainingType.BackPropagation, iterations);
}
/// <summary>
/// Trains the network using the specified input and output values.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="expectedOutput">The expected output.</param>
/// <param name="iterations">The iterations.</param>
public void Train(bool[][] input, bool[][] expectedOutput, int iterations)
{
ArgumentValidator.AssertNotNull(input, "input");
ArgumentValidator.AssertNotNull(expectedOutput, "expectedOutput");
ArgumentValidator.AssertGreaterThan(iterations, 0, "iterations");
double[][] inputDoubles = ConvertToDoubleArray(input);
double[][] expectedDoubles = ConvertToDoubleArray(expectedOutput);
var mappings = new List<KeyValuePair<LayerStimulus, LayerStimulus>>();
for (int i = 0; i < inputDoubles.Length; i++)
{
mappings.Add(new KeyValuePair<LayerStimulus, LayerStimulus>(
new LayerStimulus(inputDoubles[i]),
new LayerStimulus(expectedDoubles[i])));
}
var trainingSet = new TrainingSet(mappings);
Train(trainingSet, TrainingType.BackPropagation, iterations);
}
double[][] ConvertToDoubleArray(bool[][] input)
{
var inputDoubles = new double[input.Length][];
for (int i = 0; i < input.Length; i++)
{
bool[] bools = input[i];
inputDoubles[i] = new double[bools.Length];
for (int j = 0; j < bools.Length; j++)
{
inputDoubles[i][j] = bools[j] ? trueLevel : falseLevel;
}
}
return inputDoubles;
}
void Initialize(int inputNeuronCount, int hiddenNeuronCount, int outputNeuronCount)
{
var random = new Random();
InputLayer = new NeuralLayer();
HiddenLayer = new NeuralLayer();
OutputLayer = new NeuralLayer();
for (int i = 0; i < inputNeuronCount; i++)
{
InputLayer.Add(new Neuron());
}
for (int i = 0; i < hiddenNeuronCount; i++)
{
HiddenLayer.Add(new Neuron());
}
for (int i = 0; i < outputNeuronCount; i++)
{
OutputLayer.Add(new Neuron());
}
/* Connect input layer to hidden layer. */
for (int i = 0; i < HiddenLayer.Count; i++)
{
for (int j = 0; j < InputLayer.Count; j++)
{
double nextRandom = random.NextDouble();
HiddenLayer[i].Inputs.Add(InputLayer[j], new NeuralBias(nextRandom));
}
}
/* Connect output layer to hidden layer. */
for (int i = 0; i < OutputLayer.Count; i++)
{
for (int j = 0; j < HiddenLayer.Count; j++)
{
OutputLayer[i].Inputs.Add(HiddenLayer[j], new NeuralBias(random.NextDouble()));
}
}
// /* Connect hidden layer to context layer. */
// for (int i = 0; i < network.HiddenLayer.Count; i++)
// {
// for (int j = 0; j < network.ContextLayer.Count; j++)
// {
// network.HiddenLayer[i].Inputs.Add(network.ContextLayer[j], new NeuralBias(random.NextDouble()));
// }
// }
}
void CalculateErrors(double[] desiredResults)
{
/* Calcualte output error values. */
for (int i = 0; i < OutputLayer.Count; i++)
{
var outputNeuron = OutputLayer[i];
double output = outputNeuron.Output;
double derivative = CalculateSigmoidDerivative(output);
outputNeuron.Error = (desiredResults[i] - output) * derivative;
}
/* Calculate hidden layer error values. */
for (int i = 0; i < HiddenLayer.Count; i++)
{
var hiddenNeuron = HiddenLayer[i];
double output = hiddenNeuron.Output;
double error = 0;
for (int j = 0; j < OutputLayer.Count; j++)
{
var outputNeuron = OutputLayer[j];
error += outputNeuron.Error * outputNeuron.Inputs[hiddenNeuron].Weight * CalculateSigmoidDerivative(output);
}
hiddenNeuron.Error = error;
}
}
double CalculateSigmoidDerivative(double value)
{
return value * (1.0F - value);
}
void PrepareInputLayerForPulse(double[] input)
{
if (input.Length != InputLayer.Count)
{
/* TODO: Make localizable resource. */
throw new ArgumentException(string.Format("Expecting {0} inputs for this network",
InputLayer.Count));
}
/* Initialize data. */
for (int i = 0; i < InputLayer.Count; i++)
{
InputLayer[i].Output = input[i];
}
}
void CalculateAndAppendTransformation()
{
/* Adjust output layer weight change. */
for (int j = 0; j < OutputLayer.Count; j++)
{
var outputNeuron = OutputLayer[j];
for (int i = 0; i < HiddenLayer.Count; i++)
{
var hiddenNeuron = HiddenLayer[i];
outputNeuron.Inputs[hiddenNeuron].WeightDelta += outputNeuron.Error * hiddenNeuron.Output;
}
outputNeuron.Bias.WeightDelta += outputNeuron.Error * outputNeuron.Bias.Weight;
}
/* Adjust hidden layer weight change. */
for (int j = 0; j < HiddenLayer.Count; j++)
{
var hiddenNeuron = HiddenLayer[j];
for (int i = 0; i < InputLayer.Count; i++)
{
var inputNode = InputLayer[i];
hiddenNeuron.Inputs[inputNode].WeightDelta += hiddenNeuron.Error * inputNode.Output;
}
hiddenNeuron.Bias.WeightDelta += hiddenNeuron.Error * hiddenNeuron.Bias.Weight;
}
}
void TrainUsingBackPropogation(double[] input, double[] desiredResult)
{
PrepareInputLayerForPulse(input);
Pulse();
CalculateErrors(desiredResult);
CalculateAndAppendTransformation();
}
}
}