Recently, on Habré has appeared many articles on neural networks. Of these, articles about the Rosenblatt Perceptron seemed very interesting: Rosenblatt's Perceptron - what is forgotten and invented by history? and What is the role of the first "random" layer in the Rosenblatt perceptron . In them, as in many others, a lot has been written about how networks cope with problem solving, and generalize to some extent their knowledge. But I would like to somehow visualize these generalizations and the decision process. To see in practice what the perceptron learned there, and to feel how successfully he succeeded. It is possible to experience bitter irony regarding the achievement of mankind in the field of AI.new double[]{0.2, 0.7} => new NeuralTask { Preview = new double[]{0.25, 0,75, 1}, Input=new double[] {0,1,1,1}, Output=new double[]{1}} var Convertion = (double[] random, double value) => { var input = new double[]{Math.Floor(random [0]*0x4)/0x4, Math.Floor(random [1]*0x4)/0x4}, byte x = (byte)(input[0] * 4); byte y = (byte)(input[1] * 4); int res = (y > value * 4 ? 1 : 0); return new NeuralTask() { input = new double[4]{ (x&2)>>1, x&1, (y&2)>>1, y&1}, output = new double[1] { res }, preview = new double[3] { input[0], input[1], res } }; }; 

public class Synaps { private double weight; /// <summary> </summary> virtual public double Weight { get { return weight; } set { if (weight != value) { weight = value; if (axon != null) ChangeActionPotentialHandler(axon.ActionPotential); } } } /// <summary> . , , , , .</summary> private IAxon axon; // , . public IAxon Axon { get { return axon; } set { // , . if (axon != null) axon.RemoveChangeActionPotentialHandler(ChangeActionPotentialHandler); axon = value; if (axon != null) { axon.AddChangeActionPotentialHandler(ChangeActionPotentialHandler); ChangeActionPotentialHandler(axon.ActionPotential); } } } public double ActionPotential; /// <summary> , .</summary> protected Action WhenActionPotentialhanged; public void AddActionPotentialChangeHandler(Action handler) { WhenActionPotentialhanged += handler; } public void RemoveActionPotentialChangeHandler(Action handler) { WhenActionPotentialhanged -= handler; } virtual protected void ChangeActionPotentialHandler(double axonActionPotential) { ActionPotential = axonActionPotential * weight; // , . if (WhenActionPotentialhanged != null) WhenActionPotentialhanged); // } } public interface IAxon { /// <summary> .</summary> double ActionPotential { get; } /// <summary> .</summary> void AddChangeActionPotentialHandler(Action<double> handler); void RemoveChangeActionPotentialHandler(Action<double> handler); /// <summary>, .</summary> PointF Position { get; set; } /// <summary> .</summary> string Name { get; set; } } public class SensoryNeuron : IAxon { protected double actionPotential; public double ActionPotential { get { return actionPotential; } set { if (actionPotential != value) { actionPotential = value; if (WhenChangeActionPotential != null) WhenChangeActionPotential(actionPotential); } } } } /// <summary> . </summary> public class Neuron : IAxon { /// <summary> , . , , .</summary> public Synaps[] Synapses = new Synaps[0]; /// <summary> , , .</summary> protected bool synapsPotentialChanged = false; /// <summary> . , , . </summary> public void AppendSinaps(Synaps target) { // . , , , , . Synapses = Synapses.Concat(new Synaps[1] { target }).ToArray(); target.AddActionPotentialChangeHandler(ChangeSynapsPotentialHandler); // . synapsPotentialChanged = true; } virtual protected void ChangeSynapsPotentialHandler() { synapsPotentialChanged = true; } /// <summary> . , .</summary> protected DTransferFunction transferFunctionDelegate; public virtual DTransferFunction TransferFunction { get { return transferFunctionDelegate; } set { transferFunctionDelegate = value; } } /// <summary> . </summary> public virtual void Excitation() { if (!synapsPotentialChanged) return; // synapsPotentialChanged = false; synapsPotentials = 0; for (int i = 0; i < Synapses.Length; i++) synapsPotentials += Synapses[i].ActionPotential; double newValue = transferFunctionDelegate(synapsPotentials); if (actionPotential != newValue) { // . , . actionPotential = newValue; if (WhenChangeActionPotential != null) WhenChangeActionPotential(actionPotential); } } } /// <summary> . </summary> /// <param name="argument"> </param> /// <returns> </returns> public delegate double DTransferFunction(double argument); DTransferFunction BarrierTransferFunction = (double x) => x <= 0 ? 0 : 1; abstract public class NeuralNetwork { /// <summary> </summary> public SensoryNeuron[] Input = new SensoryNeuron[0]; /// <summary> /// , . . /// , . /// </summary> public Neuron[] ExcitationOrder = new Neuron[0]; /// <summary> </summary> public Neuron[] Output = new Neuron[0]; /// <summary> , </summary> /// <param name="input"> - </param> /// <param name="output"> </param> abstract public void create(uint input, uint output); public void execute(double[] data) { // for (int i = 0; i < Input.Length && i < data.Length; i++) { Input[i].ActionPotential = data[i]; } for (int i = 0; i < ExcitationOrder.Length; i++) ExcitationOrder[i].Excitation(); } public double[] Result() { // return output.Select(s => s.ActionPotential).ToArray(); //TODO Linq . double[] res = new double[Output.Length]; for (int i = 0; i < res.Length; i++) res[i] = Output[i].ActionPotential; return res; } } public class PerceptronClassic : NeuralNetwork { // , public int neuronCountsOverSensoric = 15; // public int ANeuronSynapsCount; // public Neuron[] Layer; // override public void create(uint inputCount, uint outputCount) { rnd = rndSeed >= 0 ? new Random(rndSeed) : new Random(); // this.Input = new SensoryNeuron[inputCount]; for (int i = 0; i < inputCount; i++) Input[i] = new SensoryNeuron() {Name = "S" + i}; // - Layer = new Neuron[inputCount + neuronCountsOverSensoric]; for (int i = 0; i < Layer.Length; i++) { // Layer[i] = new RosenblattNeuron(); // SensoryNeuron[] sub = Input.OrderBy((cell) => rnd.NextDouble()).Take(ANeuronSynapsCount).ToArray(); // , for (int j = 0; j < sub.Length; j++) { Synaps s = new Synaps(); s.Axon = sub[j]; s.Weight = rnd.Next(2) * 2 - 1; Layer[i].AppendSinaps(s); } } // . for (int i = 0; i < Layer.Length; i++) Layer[i].Name = "A" + i; // - Output = new Neuron[outputCount]; for (int i = 0; i < Output.Length; i++) { Output[i] = new RosenblattNeuron(); Output[i].Name = "R" + i; // for (int j = 0; j < Layer.Length; j++) { Synaps s = new Synaps(); s.Axon = Layer[j]; Output[i].AppendSinaps(s); // 0 } } // int lastIndex = 0; ExcitationOrder = new Neuron[Layer.Length + Output.Length]; foreach (Neuron cell in Layer) ExcitationOrder[lastIndex++] = cell; foreach (Neuron cell in Output) ExcitationOrder[lastIndex++] = cell; } } /// <summary> .</summary> public class ErrorCorrection : LearningAlgorythm { // , override protected double LearnNet(double[] required) { double error = 0; for (int i = 0; i < required.Length && i < net.Output.Length; i++) { if (required[i] != net.Output[i].ActionPotential) { error += 1; // for (int j = 0; j < (net as PerceptronClassic).Layer.Length; j++) // , if ((net as PerceptronClassic).Layer[j].ActionPotential > 0) // foreach (RosenblattNeuron cell in net.Output) // , // – , , – . . cell.Synapses[j].Weight += required[i] <= 0 ? -1 : 1; } } return error; } /// <summary> </summary> public void LearnTasksSet() { if (data == null) { Console.WriteLine(" "); return; } data.Reset(); LearnedTaskSetsCount++; ErrorsInSet = LearnedTasksInSetCount = 0; int max = 1000; while (data.MoveNext() && --max >= 0) LearnCurrentTask(); // , . } /// <summary> .</summary> /// <param name="loops"> </param> public void LearnSetManyTimesUntilSuccess(int loops) { for (int i = 0; i < loops; i++) { LearnTasksSet(); if (ErrorsInSet == 0) { break; } } } } Enumerable<NeuralTask> and with each Enumerable<NeuralTask> it rearranges points in a sequence in random order.





So what's the problem? What is the reason for such a sad picture? Let's try to solve the problem analytically. Suppose we have a perceptron, but only the weights of the first layer, as well as the second, are amenable to learning. In the second layer, we have only three neurons. The first is connected to the first 8 inputs and has not a barrier, but simply a linear activation function. The second neuron is the same, but is only responsible for converting the second 8 bits to normal coordinates. The third is connected with all, has a barrier function and is intended to give 1 if at least one input has at least something. In the next layer, two of the neurons are summed, again without a barrier function, but with very important weighting factors reflecting the parameters of the function. And finally, the last neuron will compare two input signals. Simple, logical and not a bit interesting. However, this is almost the minimum possible number of neurons and synapses involved for a given task. And now try to imagine how many neurons you need to express any of these operations in a single-layer perceptron, whose synapse weights in the first layer can only be -1 and 1. For example, casting 8 bits to one number. I'll tell you - you need about 512 pieces of neurons, and we haven't started comparing this yet. /// <summary> . </summary> /// <param name="argument"> </param> /// <param name="funcResult"> , </param> /// <returns> .</returns> public delegate double DTransferFunctionDerivative(double argument, double funcResult); DTransferFunction Function = (x) => Math.Tanh(x), DTransferFunctionDerivative Derivative = (x, th) => (1 - th) * (1 + th) public class NeuronWithDerivative : Neuron { /// <summary> . .</summary> protected DTransferFunctionDerivative transferFunctionDerivativeDelegate; /// <summary></summary> override public DTransferFunctionDerivative TransferFunction { get { return transferFunctionDerivativeDelegate; } set { transferFunctionDerivativeDelegate = value; } } /// <summary> </summary> protected double actionPotentialDerivative = 0; /// <summary> .</summary> public double ActionPotentialDerivative { get { return actionPotentialDerivative; } } /// <summary> . , . .</summary> public double BackProprigationParametr = 0; public override void Excitation() { base.Excitation(); actionPotentialDerivative = transferFunctionDerivativeDelegate(synapsPotentials, actionPotential); } } static class Tools { /// <summary> .</summary> /// <param name="source"> .</param> /// <param name="func">, .</param> public static void Each<SequenceType>(this IEnumerator<SequenceType> source, Action<SequenceType> func) { while (source.MoveNext()) func(source.Current); } /// <summary> .</summary> /// <param name="source"> .</param> /// <param name="func">, . - . . - .</param> public static void Each<SequenceType>(this IEnumerator<SequenceType> source, Action<SequenceType, int> func) { for (int i = 0; source.MoveNext(); i++) func(source.Current, i); } /// <summary> .</summary> /// <param name="source"> .</param> /// <param name="func">, .</param> public static void Each<SequenceType>(this IEnumerable<SequenceType> source, Action<SequenceType> func) { source.GetEnumerator().Each(func); } /// <summary> .</summary> /// <param name="source"> .</param> /// <param name="func">, . - . . - .</param> public static void Each<SequenceType>(this IEnumerable<SequenceType> source, Action<SequenceType, int> func) { source.GetEnumerator().Each(func); } } // . public class RumelhartPerceptron : NeuralNetwork { /// <summary> . .</summary> DTransferFunctionDerivative TransferFunctionDerivative; /// <summary> , </summary> public int[] NeuronsCount = new int[0]; override public void create(uint input, uint output) { // . Input = (new SensoryNeuron[input]).Select((empty, index) => new SensoryNeuron(){Name = "S[" + index + "]"}).ToArray(); // Func<string, NeuronWithDerivative> create = (name) => { NeuronWithDerivative neuron = new NeuronWithDerivative(); neuron.Name = name; neuron.TransferFunction = TransferFunction; neuron.TransferFunctionDerivative = TransferFunctionDerivative; return neuron; }; Func<IAxon, Synaps> createSynaps = (axon) => { Synaps s = new Synaps(); s.Axon = axon; return s; }; /// <summary> </summary> // NeuronWithDerivative[][] Layers = NeuronsCount.Select((count, layer) => Enumerable.Range(0, count).Select(index => create("A[" + layer + "][" + index + "]")).ToArray()).ToArray(); // // Output = Enumerable.Range(0, (int)output).Select(index => create("R[" + index + "]")).ToArray(); // . Layers[0].Each(neuron => { Input.Select(createSynaps).Each(synaps => { neuron.AppendSinaps(synaps); }); }); // , , . Layers.Skip(1).Each((layer, layerIndex) => { layer.Each(neuron => { Layers[layerIndex].Select(createSynaps).Each(synaps => { neuron.AppendSinaps(synaps); }); }); }); // Output.Each(neuron => { Layers.Last().Select(createSynaps).Each(synaps => { neuron.AppendSinaps(synaps); }); }); // ExcitationOrder = Layers.SelectMany(layer => layer).Concat(Output).ToArray(); // -1 +1 Random rnd = new Random(); ExcitationOrder.Each(neuron => neuron.Synapses.Each(synaps => synaps.Weight = rnd.NextDouble() * 2 - 1)); } } public class BackPropagationLearning : LearningAlgorythm { // , public double LearningSpeed = 0.01; override protected double LearnNet(double[] required) { double[] errors = net.Output.Select((neuron, index) => neuron.ActionPotential - required[index]).ToArray(); // . , . net.ExcitationOrder.Cast<NeuronWithDerivative>().Each(neuron => { neuron.BackProprigationParametr = 0; }); // BackProprigationParametr dE/dS[i] = dE/dO[i] * F'[i](S[i]) // . BP[i] = dE/dO[i] * F'[i] = 2*(O[i]-T[i])*F'[i]; net.Output.Cast<NeuronWithDerivative>().Each((neuron, index) => { neuron.BackProprigationParametr = 2 * errors[index] * neuron.ActionPotentialDerivative; }); // , BP[j] = SUM( dE/dO[i] * F'[i] * W[j,i] ) * F'[j] = SUM ( BP[i] * W[j,i] * F'[j]) net.ExcitationOrder.Reverse().Cast<NeuronWithDerivative>().Each(neuron => { neuron.Synapses.SkipWhile(synaps => !(synaps.Axon is NeuronWithDerivative)).Each(synaps => { (synaps.Axon as NeuronWithDerivative).BackProprigationParametr += neuron.BackProprigationParametr * (synaps.Axon as NeuronWithDerivative).ActionPotentialDerivative * synaps.Weight; }); }); // , delta W[i,j] = -speed * dE/dS[j] * X[i]; net.ExcitationOrder.Reverse().Cast<NeuronWithDerivative>().Each(neuron => { neuron.Synapses.Each(synaps => { synaps.Weight += -LearningSpeed * neuron.BackProprigationParametr * synaps.Axon.ActionPotential; }); }); // . ( , ). return errors.Select(e => e * e).Average(); } public void LearnSomeTime(int sek) { DateTime begin = DateTime.Now; while (TimeSpan.FromTicks(DateTime.Now.Ticks - begin.Ticks).Seconds < sek) { LearnTasksSet(); } } } 




And here the most interesting begins.
What's next?A [0] [1], we will transmit information about the first coordinate and have weights of synapses respectively 1 and 0, neuron A [0] [2] - information about the second coordinate, and have synapses with weights 0 and 1. We want so that the function of the first coordinate is compared with the second coordinate. To do this, we simply transmit the second coordinate to the second neuron of the second layer. Assign synapse weights, respectively, 0.0 and 1. And in the first synapse of the second layer, we want to get the value of k * xb. Accordingly, k = -0.5 b would be equal to 0.75 if the activation functions of the neurons did not bend the values. About the input x = 1 on the neuron A [0] [1] there will be only 0.76 potential. So for comparison, we need about b = 0.65. With this value, the neuron A [1] [0] should have approximately the same value as the neuron A [1] [1] for points lying on our original line. Well now,in order to compare these two values, let's endow the output neuron R [0] with values ​​at -1 and 1. Synapses. Let's display what we have in the picture. Right beauty! Blue zero values ​​are approximately where they are needed. Top green. Red below. Of course, for the time being it is not green enough and not red enough. However, the final fine-tuning of the weights of synapses will be able to do the error back-propagation algorithm not only worse, but better than me. We start the algorithm, and after a small number of steps, we have a fairly tolerable approximation.However, the final fine-tuning of the weights of synapses will be able to do the error back-propagation algorithm not only worse, but better than me. We start the algorithm, and after a small number of steps, we have a fairly tolerable approximation.However, the final fine-tuning of the weights of synapses will be able to do the error back-propagation algorithm not only worse, but better than me. We start the algorithm, and after a small number of steps, we have a fairly tolerable approximation.
<rumelhart> <input> <SensoryNeuron name="S[0]" potential="0,0396039603960396"/> <SensoryNeuron name="S[1]" potential="0,232673267326733"/> </input> <excitationOrder> <Neuron name="A[0][0]" potential="1"> <synaps weight="999,800400355468" axon="S[0]" potential="39,5960554596225" /> <synaps weight="999,545226476388" axon="S[1]" potential="232,5674536851" /> </Neuron> <Neuron name="A[0][1]" potential="0,116342019068401"> <synaps weight="1,13712492177543" axon="S[0]" potential="0,0450346503673436" /> <synaps weight="0,308744483692756" axon="S[1]" potential="0,0718365877898986" /> </Neuron> <Neuron name="A[0][2]" potential="0,29693700450834"> <synaps weight="-0,0240967983057654" axon="S[0]" potential="-0,000954328645772886" /> <synaps weight="1,31992553337836" axon="S[1]" potential="0,307111386479124" /> </Neuron> <Neuron name="A[1][0]" potential="0,683083451961352"> <synaps weight="1,02404884109051" axon="A[0][0]" potential="1,02404884109051" /> <synaps weight="-0,649771926175146" axon="A[0][1]" potential="-0,0755957778251805" /> <synaps weight="-0,382508459201211" axon="A[0][2]" potential="-0,113580916074308" /> </Neuron> <Neuron name="A[1][1]" potential="0,0324886810522597"> <synaps weight="-0,404744328902586" axon="A[0][0]" potential="-0,404744328902586" /> <synaps weight="0,161865952018599" axon="A[0][1]" potential="0,0188318116762727" /> <synaps weight="1,40909563283595" axon="A[0][2]" potential="0,418412636280091" /> </Neuron> </excitationOrder> <output> <Neuron name="R[0]" potential="-0,707598983150799"> <synaps weight="-1,36308077548559" axon="A[1][0]" potential="-0,931097921420856" /> <synaps weight="1,50019153981243" axon="A[1][1]" potential="0,0487392444542643" /> </Neuron> </output> </rumelhart> 


Source: https://habr.com/ru/post/219647/
All Articles