AI – C# – Neural Net

email me

This is a simple neural net written in C#. I’ve documented it for ease of understanding. But, it basically shows you the before training output and after training output (using an internal dataset). The point of this example is to demonstrate how neural nets fundamentally work, which is by neurons forming connections during training. These connections influence output. Something to note, normally these datasets have to be huge for the output to be accurate. When I say huge, I mean 100s of gigabytes or even terabytes of data.

In large-scale neural nets, you have inputs (the question, or what you’re looking for), many hidden layers (the trained dataset), and then the output (the answer).

In the below example, the question is, Which shoe is best? The dataset is based upon quality, price, availability, and customer ratings, and the output is the answer.


An example of a simple neural network

now, on to the C# example…

Screenshot

You can see that the weighted values in the output change after training. This is because new connections have formed, and the deep learning aspect of neural nets has altered the output. 

C# Code

using System;
using System.Collections.Generic; // list
using System.Data;                // table data
using System.Linq;
using System.Diagnostics;         // stopwatch
using ConsoleTableExt;            // console table

/// <notes>
/// run this package import https://www.nuget.org/packages/ConsoleTableExt/2.0.1
/// </notes>

namespace NeuralNet1
{
class NeuralNetSample1

{
static void Main()
{
Stopwatch stopwatch = new Stopwatch();

NetworkModel model = new NetworkModel();
model.Layers.Add(new NeuralLayer(2, 0.1, "INPUT"));
model.Layers.Add(new NeuralLayer(1, 0.1, "OUTPUT"));

// show before model
model.Build();

Console.WriteLine("{{ BEFORE TRAINING }}");
model.Print();

Console.WriteLine();

// available datasets
Dataset data1 = new Dataset(4);
data1.Add(0, 0);
data1.Add(0, 1);
data1.Add(1, 0);
data1.Add(1, 1);

Dataset data2 = new Dataset(4);
data2.Add(0);
data2.Add(0);
data2.Add(0);
data2.Add(1);

// perform training
// start the clock
stopwatch.Start();
model.Train(data1, data2, iterations: 10, learningRate: 0.1);
// stop the clock
stopwatch.Stop();
Console.WriteLine();

// show after model
Console.WriteLine("\n{{ AFTER TRAINING }}");
model.Print();

// elapsed time
Console.WriteLine("\nTime elapsed: {0}", stopwatch.Elapsed);

// wait for any key
Console.ReadKey();
}
}

class Pulse
{
public double Value { get; set; }
}

class Dendrite
{
public Dendrite()
{
InputPulse = new Pulse();
}

public Pulse InputPulse { get; set; }

public double SynapticWeight { get; set; }

public bool Learnable { get; set; } = true;
}

class Neuron
{
public List<Dendrite> Dendrites { get; set; }

public Pulse OutputPulse { get; set; }

public Neuron()
{
Dendrites = new List<Dendrite>();
OutputPulse = new Pulse();
}

public void Fire()
{
OutputPulse.Value = Sum();

OutputPulse.Value = Activation(OutputPulse.Value);
}

public void UpdateWeights(double new_weights)
{
foreach (var terminal in Dendrites)
{
terminal.SynapticWeight = new_weights;
}
}

private double Sum()
{
double computeValue = 0.0f;
foreach (var d in Dendrites)
{
computeValue += d.InputPulse.Value * d.SynapticWeight;
}

return computeValue;
}

private double Activation(double input)
{
double threshold = 1;
return input <= threshold ? 0 : threshold;
}
}

class NeuralLayer
{
public List<Neuron> Neurons { get; set; }

public string Name { get; set; }

public double Weight { get; set; }

public NeuralLayer(int count, double initialWeight, string name = "")
{
Neurons = new List<Neuron>();
for (int i = 0; i < count; i++)
{
Neurons.Add(new Neuron());
}

Weight = initialWeight;

Name = name;
}

public void Optimize(double learningRate, double delta)
{
Weight += learningRate * delta;
foreach (var neuron in Neurons)
{
neuron.UpdateWeights(Weight);
}
}

public void Forward()
{
foreach (var neuron in Neurons)
{
neuron.Fire();
}
}

public void Log()
{
Console.WriteLine("{0}, Weight: {1}", Name, Weight);
}
}

class NetworkModel
{
public List<NeuralLayer> Layers { get; set; }

public NetworkModel()
{
Layers = new List<NeuralLayer>();
}

public void AddLayer(NeuralLayer layer)
{
int dendriteCount = 1;

if (Layers.Count > 0)
{
dendriteCount = Layers.Last().Neurons.Count;
}

foreach (var element in layer.Neurons)
{
for (int i = 0; i < dendriteCount; i++) { element.Dendrites.Add(new Dendrite()); } } } public void Build() { int i = 0; foreach (var layer in Layers) { if (i >= Layers.Count - 1)
{
break;
}

var nextLayer = Layers[i + 1];
CreateNetwork(layer, nextLayer);

i++;
}
}

public void Train(Dataset data1, Dataset data2, int iterations, double learningRate = 0.1)
{
int epoch = 1;

while (iterations >= epoch)
{
// get input
var inputLayer = Layers[0];
List<double> outputs = new List<double>();

// loop through input
for (int i = 0; i < data1.Data.Length; i++)
{
// layer 1
for (int j = 0; j < data1.Data[i].Length; j++) { inputLayer.Neurons[j].OutputPulse.Value = data1.Data[i][j]; } // fire neurons and collect output ComputeOutput(); outputs.Add(Layers.Last().Neurons.First().OutputPulse.Value); } // Check the accuracy score against data2 with output double accuracySum = 0; int y_counter = 0; outputs.ForEach((x) => {
if (x == data2.Data[y_counter].First())
{
accuracySum++;
}

y_counter++;
});

// optimize weights
OptimizeWeights(accuracySum / y_counter);
// output accuracy data
Console.WriteLine("Epoch: {0}, Accuracy: {1} %", epoch, (accuracySum / y_counter) * 100);
epoch++;
}
}

public void Print()
{
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Neurons");
dt.Columns.Add("Weight");

foreach (var element in Layers)
{
DataRow row = dt.NewRow();
row[0] = element.Name;
row[1] = element.Neurons.Count;
row[2] = element.Weight;

dt.Rows.Add(row);
}

ConsoleTableBuilder builder = ConsoleTableBuilder.From(dt);
builder.ExportAndWrite();
}

private void ComputeOutput()
{
bool first = true;
foreach (var layer in Layers)
{
// do not use Input Layer
if (first)
{
first = false;
continue;
}

layer.Forward();
}
}

private void OptimizeWeights(double accuracy)
{
float lr = 0.1f;
//if 100%, skip
if (accuracy == 1)
{
return;
}

if (accuracy > 1)
{
lr = -lr;
}

// weights are updated
foreach (var layer in Layers)
{
layer.Optimize(lr, 1);
}
}

private void CreateNetwork(NeuralLayer connectingFrom, NeuralLayer connectingTo)
{
foreach (var from in connectingFrom.Neurons)
{
from.Dendrites = new List<Dendrite>();
from.Dendrites.Add(new Dendrite());
}

foreach (var to in connectingTo.Neurons)
{
to.Dendrites = new List<Dendrite>();
foreach (var from in connectingFrom.Neurons)
{
to.Dendrites.Add(new Dendrite() { InputPulse = from.OutputPulse, SynapticWeight = connectingTo.Weight });
}
}
}
}

class Dataset
{
public double[][] Data { get; set; }

int counter = 0;

public Dataset(int rows)
{
Data = new double[rows][];
}

public void Add(params double[] rec)
{
Data[counter] = rec;
counter++;
}
}
}