65.9K
CodeProject 正在变化。 阅读更多。
Home

重塑神经网络

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (34投票s)

2017 年 12 月 8 日

CPOL

5分钟阅读

viewsIcon

59423

downloadIcon

2676

神经网络可以做很多令人惊叹的事情,你可以从零开始了解如何构建一个。你可能会惊讶于从头开始开发一个神经网络是多么容易!

完整系列

  • 第 1 部分:我们从头开始创建整个 NeuralNetwork 类。
  • 第 2 部分:我们在 Unity 中创建一个环境,以便在该环境中测试神经网络。
  • 第 3 部分:我们通过向代码添加一种新型的变异,对已经创建的神经网络进行重大改进。

引言

在你开始之前,我想告诉你我将转向 YouTube

在过去的二十年里,机器学习已经成为信息技术的主要支柱之一,并且在其中,它已成为我们生活中一个相当核心但通常隐藏的部分。随着可获得的数据量不断增加,有充分的理由相信智能数据分析将变得更加普遍,成为技术进步的必要要素。最近,不仅神经网络接管了“机器学习”的领域,而且我注意到缺乏解释如何从头开始实现神经网络的教程,所以我认为我应该做一个!

背景

在本文中,你将理解神经网络的核心基础知识,如何用纯 C# 实现一个神经网络并使用遗传变异来训练它。在阅读本文之前,你需要具备 C# 编程基础知识和面向对象编程的基础知识。请注意,神经网络将通过无监督学习/变异来学习。本文不会介绍监督学习/反向传播。但是,这是我的首要任务之一。

理解神经网络

如果你不知道神经网络是如何工作的,我建议你观看 3Blue1Brown 制作的这个视频

我没有制作那个视频,而且我认为我永远无法以更直观的方式解释神经网络。请注意,视频中介绍了 Sigmoid 激活函数,但我将使用“Leaky ReLU”而不是,因为它训练速度更快,并且不会引入 ReLU 遇到的相同问题(神经元死亡)。此外,视频中解释的偏置将表示为始终值为 1 的神经元。

你也可以观看文章开头发布的视频。那个是我制作的,它使用 Leaky ReLU 作为激活函数。

Using the Code

假设我们要创建一个像这样的神经网络

将网络分成几组是一个好习惯。像这样

这样,神经网络就是一个 NeuralSections 的数组。在 NeuralNetwork.cs 脚本中,我们应该首先导入所需的命名空间

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

然后,我们来声明 NeuralNetwork

public class NeuralNetwork
{
    ...
}

NeuralNetwork 类应该以…开始

    public UInt32[] Topology // Returns the topology in the form of an array
    {
        get
        {
            UInt32[] Result = new UInt32[TheTopology.Count];
            TheTopology.CopyTo(Result, 0);
            return Result;
        }
    }

    ReadOnlyCollection<UInt32> TheTopology; // Contains the topology of the NeuralNetwork
    NeuralSection[] Sections; // Contains the all the sections of the NeuralNetwork

    Random TheRandomizer; // It is the Random instance used to mutate the NeuralNetwork

然后,构造函数应确保输入有效,初始化 TheRandomizer,设置网络的拓扑结构,初始化网络的所有部分并构造这些部分

    /// <summary>
    /// Initiates a NeuralNetwork from a Topology and a Seed.
    /// </summary>
    /// <param name="Topology">The Topology of the Neural Network.</param>
    /// <param name="Seed">The Seed of the Neural Network. 
    /// Set to 'null' to use a Timed Seed.</param>
    public NeuralNetwork (UInt32[] Topology, Int32? Seed = 0)
    {
        // Validation Checks
        if (Topology.Length < 2)
            throw new ArgumentException("A Neural Network cannot contain less than 2 Layers.", 
                                        "Topology");

        for (int i = 0; i < Topology.Length; i++)
        {
            if(Topology[i] < 1)
                throw new ArgumentException("A single layer of neurons must contain, 
                                             at least, one neuron.", "Topology");
        }

        // Initialize Randomizer
        if (Seed.HasValue)
            TheRandomizer = new Random(Seed.Value);
        else
            TheRandomizer = new Random();

        // Set Topology
        TheTopology = new List<uint>(Topology).AsReadOnly();

        // Initialize Sections
        Sections = new NeuralSection[TheTopology.Count - 1];

        // Set the Sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i] = new NeuralSection(TheTopology[i], TheTopology[i + 1], TheRandomizer);
        }
    }

为了能够训练后代,还应该有一个可以克隆 NeuralNetwork 的构造函数

    /// <summary>
    /// Initiates an independent Deep-Copy of the Neural Network provided.
    /// </summary>
    /// <param name="Main">The Neural Network that should be cloned.</param>
    public NeuralNetwork (NeuralNetwork Main)
    {
        // Initialize Randomizer
        TheRandomizer = new Random(Main.TheRandomizer.Next());

        // Set Topology
        TheTopology = Main.TheTopology;

        // Initialize Sections
        Sections = new NeuralSection[TheTopology.Count - 1];

        // Set the Sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i] = new NeuralSection (Main.Sections[i]);
        }
    }

然后,是 FeedForward 函数。它接受输入数组,确保其有效,将其通过所有部分,并返回最后一个部分的输出

    /// <summary>
    /// Feed Input through the NeuralNetwork and Get the Output.
    /// </summary>
    /// <param name="Input">The values to set the Input Neurons.</param>
    /// <returns>The values in the output neurons after propagation.</returns>
    public double[] FeedForward(double[] Input)
    {
        // Validation Checks
        if (Input == null)
            throw new ArgumentException("The input array cannot be set to null.", "Input");
        else if (Input.Length != TheTopology[0])
            throw new ArgumentException
            ("The input array's length does not match the number of neurons 
              in the input layer.", "Input");

        double[] Output = Input;

        // Feed values through all sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Output = Sections[i].FeedForward(Output);
        }

        return Output;
    }

现在,我们需要让开发者能够变异 NeuralNetworkMutate 函数只是独立地变异每个部分

    /// <summary>
    /// Mutate the NeuralNetwork.
    /// </summary>
    /// <param name="MutationProbablity">The probability that a weight is going to be mutated. 
    /// (Ranges 0-1)</param>
    /// <param name="MutationAmount">
    /// The maximum amount a mutated weight would change.</param>
    public void Mutate (double MutationProbablity = 0.3, double MutationAmount = 2.0)
    {
        // Mutate each section
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i].Mutate(MutationProbablity, MutationAmount);
        }
    }

NeuralNetwork 类目前应该看起来像这样

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class NeuralNetwork
{
    public UInt32[] Topology // Returns the topology in the form of an array
    {
        get
        {
            UInt32[] Result = new UInt32[TheTopology.Count];

            TheTopology.CopyTo(Result, 0);

            return Result;
        }
    }

    ReadOnlyCollection<UInt32> TheTopology; // Contains the topology of the NeuralNetwork
    NeuralSection[] Sections; // Contains the all the sections of the NeuralNetwork

    Random TheRandomizer; // It is the Random instance used to mutate the NeuralNetwork

    /// <summary>
    /// Initiates a NeuralNetwork from a Topology and a Seed.
    /// </summary>
    /// <param name="Topology">The Topology of the Neural Network.</param>
    /// <param name="Seed">The Seed of the Neural Network.
    /// Set to 'null' to use a Timed Seed.</param>
    public NeuralNetwork (UInt32[] Topology, Int32? Seed = 0)
    {
        // Validation Checks
        if (Topology.Length < 2)
            throw new ArgumentException("A Neural Network cannot contain less than 2 Layers.", 
                                        "Topology");

        for (int i = 0; i < Topology.Length; i++)
        {
            if(Topology[i] < 1)
                throw new ArgumentException
                ("A single layer of neurons must contain, at least, one neuron.", "Topology");
        }

        // Initialize Randomizer
        if (Seed.HasValue)
            TheRandomizer = new Random(Seed.Value);
        else
            TheRandomizer = new Random();

        // Set Topology
        TheTopology = new List<uint>(Topology).AsReadOnly();

        // Initialize Sections
        Sections = new NeuralSection[TheTopology.Count - 1];

        // Set the Sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i] = new NeuralSection
                          (TheTopology[i], TheTopology[i + 1], TheRandomizer);
        }
    }

    /// <summary>
    /// Initiates an independent Deep-Copy of the Neural Network provided.
    /// </summary>
    /// <param name="Main">The Neural Network that should be cloned.</param>
    public NeuralNetwork (NeuralNetwork Main)
    {
        // Initialize Randomizer
        TheRandomizer = new Random(Main.TheRandomizer.Next());

        // Set Topology
        TheTopology = Main.TheTopology;

        // Initialize Sections
        Sections = new NeuralSection[TheTopology.Count - 1];

        // Set the Sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i] = new NeuralSection (Main.Sections[i]);
        }
    }

    /// <summary>
    /// Feed Input through the NeuralNetwork and Get the Output.
    /// </summary>
    /// <param name="Input">The values to set the Input Neurons.</param>
    /// <returns>The values in the output neurons after propagation.</returns>
    public double[] FeedForward(double[] Input)
    {
        // Validation Checks
        if (Input == null)
            throw new ArgumentException("The input array cannot be set to null.", "Input");
        else if (Input.Length != TheTopology[0])
            throw new ArgumentException
            ("The input array's length does not match the number of neurons 
              in the input layer.", 
             "Input");

        double[] Output = Input;

        // Feed values through all sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Output = Sections[i].FeedForward(Output);
        }

        return Output;
    }

    /// <summary>
    /// Mutate the NeuralNetwork.
    /// </summary>
    /// <param name="MutationProbablity">
    /// The probability that a weight is going to be mutated. (Ranges 0-1)</param>
    /// <param name="MutationAmount">The maximum amount a mutated weight would change.
    /// </param>
    public void Mutate (double MutationProbablity = 0.3, double MutationAmount = 2.0)
    {
        // Mutate each section
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i].Mutate(MutationProbablity, MutationAmount);
        }
    }
}

现在我们已经实现了 NeuralNetwork 类,是时候实现 NeuralSection 类了

public class NeuralNetwork
{
    ...
	private class NeuralSection
    {
	    ...
	}
	...
}

每个 NeuralSection 都应包含这些全局变量

        private double[][] Weights; // Contains all the weights of the section where [i][j] 
                                    // represents the weight from neuron i in the input layer 
                                    // and neuron j in the output layer

        private Random TheRandomizer; // Contains a reference to the 
                                      // Random instance of the NeuralNetwork

NeuralSection 类也应包含 2 个构造函数

        /// <summary>
        /// Initiate a NeuralSection from a topology and a seed.
        /// </summary>
        /// <param name="InputCount">The number of input neurons in the section.</param>
        /// <param name="OutputCount">The number of output neurons in the section.</param>
        /// <param name="Randomizer">The Ransom instance of the NeuralNetwork.</param>
        public NeuralSection(UInt32 InputCount, UInt32 OutputCount, Random Randomizer)
        {
            // Validation Checks
            if (InputCount == 0)
                throw new ArgumentException
                ("You cannot create a Neural Layer with no input neurons.", "InputCount");
            else if (OutputCount == 0)
                throw new ArgumentException
                ("You cannot create a Neural Layer with no output neurons.", "OutputCount");
            else if (Randomizer == null)
                throw new ArgumentException
                      ("The randomizer cannot be set to null.", "Randomizer");

            // Set Randomizer
            TheRandomizer = Randomizer;

            // Initialize the Weights array
            Weights = new double[InputCount + 1][]; // +1 for the Bias Neuron

            for (int i = 0; i < Weights.Length; i++)
                Weights[i] = new double[OutputCount];

            // Set random weights
            for (int i = 0; i < Weights.Length; i++)
                for (int j = 0; j < Weights[i].Length; j++)
                    Weights[i][j] = TheRandomizer.NextDouble() - 0.5f;
        }

        /// <summary>
        /// Initiates an independent Deep-Copy of the NeuralSection provided.
        /// </summary>
        /// <param name="Main">The NeuralSection that should be cloned.</param>
        public NeuralSection(NeuralSection Main)
        {
            // Set Randomizer
            TheRandomizer = Main.TheRandomizer;

            // Initialize Weights
            Weights = new double[Main.Weights.Length][];

            for (int i = 0; i < Weights.Length; i++)
                Weights[i] = new double[Main.Weights[0].Length];

            // Set Weights
            for (int i = 0; i < Weights.Length; i++)
            {
                for (int j = 0; j < Weights[i].Length; j++)
                {
                    Weights[i][j] = Main.Weights[i][j];
                }
            }
        }

现在是进行所有传播魔法的 FeedForward 函数

        /// <summary>
        /// Feed input through the NeuralSection and get the output.
        /// </summary>
        /// <param name="Input">The values to set the input neurons.</param>
        /// <returns>The values in the output neurons after propagation.</returns>
        public double[] FeedForward(double[] Input)
        {
            // Validation Checks
            if (Input == null)
                throw new ArgumentException
                      ("The input array cannot be set to null.", "Input");
            else if (Input.Length != Weights.Length - 1)
                throw new ArgumentException("The input array's length
                does not match the number of neurons in the input layer.", "Input");

            // Initialize Output Array
            double[] Output = new double[Weights[0].Length];

            // Calculate Value
            for (int i = 0; i < Weights.Length; i++)
            {
                for (int j = 0; j < Weights[i].Length; j++)
                {
                    if (i == Weights.Length - 1)    // If is Bias Neuron
                        Output[j] += Weights[i][j]; // Then, the value of the neuron 
                                                    // is equal to one
                    else
                        Output[j] += Weights[i][j] * Input[i];
                }
            }

            // Apply Activation Function
            for (int i = 0; i < Output.Length; i++)
                Output[i] = ReLU(Output[i]);

            // Return Output
            return Output;
        }

正如我们在 NeuralNetwork 类中所做的那样,NeuralSection 类中也应该有一个 Mutate 函数

        /// <summary>
        /// Mutate the NeuralSection.
        /// </summary>
        /// <param name="MutationProbablity">The probability that
        /// a weight is going to be mutated. (Ranges 0-1)</param>
        /// <param name="MutationAmount">The maximum amount a Mutated Weight would change.
        /// </param>
        public void Mutate (double MutationProbablity, double MutationAmount)
        {
            for (int i = 0; i < Weights.Length; i++)
            {
                for (int j = 0; j < Weights[i].Length; j++)
                {
                    if (TheRandomizer.NextDouble() < MutationProbablity)
                        Weights[i][j] = TheRandomizer.NextDouble() * 
                                        (MutationAmount * 2) - MutationAmount;
                }
            }
        }

最后,我们需要将 ReLU 激活函数添加到 NeuralSection 类中

        /// <summary>
        /// Puts a double through the activation function ReLU.
        /// </summary>
        /// <param name="x">The value to put through the function.</param>
        /// <returns>x after it is put through ReLU.</returns>
        private double ReLU(double x)
        {
            if (x >= 0)
                return x;
            else
                return x / 20;
        }

这样,脚本应该看起来像这样

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class NeuralNetwork
{
    public UInt32[] Topology // Returns the topology in the form of an array
    {
        get
        {
            UInt32[] Result = new UInt32[TheTopology.Count];

            TheTopology.CopyTo(Result, 0);

            return Result;
        }
    }

    ReadOnlyCollection<UInt32> TheTopology; // Contains the topology of the NeuralNetwork
    NeuralSection[] Sections; // Contains the all the sections of the NeuralNetwork

    Random TheRandomizer;     // It is the Random instance used to mutate the NeuralNetwork

    private class NeuralSection
    {
        private double[][] Weights; // Contains all the weights of the section 
                                    // where [i][j] represents the weight from neuron i in the 
                                    // input layer and neuron j in the output layer

        private Random TheRandomizer; // Contains a reference to the Random instance 
                                      // of the NeuralNetwork

        /// <summary>
        /// Initiate a NeuralSection from a topology and a seed.
        /// </summary>
        /// <param name="InputCount">The number of input neurons in the section.</param>
        /// <param name="OutputCount">The number of output neurons in the section.</param>
        /// <param name="Randomizer">The Ransom instance of the NeuralNetwork.</param>
        public NeuralSection(UInt32 InputCount, UInt32 OutputCount, Random Randomizer)
        {
            // Validation Checks
            if (InputCount == 0)
                throw new ArgumentException
                ("You cannot create a Neural Layer with no input neurons.", "InputCount");
            else if (OutputCount == 0)
                throw new ArgumentException
                ("You cannot create a Neural Layer with no output neurons.", "OutputCount");
            else if (Randomizer == null)
                throw new ArgumentException
                ("The randomizer cannot be set to null.", "Randomizer");

            // Set Randomizer
            TheRandomizer = Randomizer;

            // Initialize the Weights array
            Weights = new double[InputCount + 1][]; // +1 for the Bias Neuron

            for (int i = 0; i < Weights.Length; i++)
                Weights[i] = new double[OutputCount];

            // Set random weights
            for (int i = 0; i < Weights.Length; i++)
                for (int j = 0; j < Weights[i].Length; j++)
                    Weights[i][j] = TheRandomizer.NextDouble() - 0.5f;
        }

        /// <summary>
        /// Initiates an independent Deep-Copy of the NeuralSection provided.
        /// </summary>
        /// <param name="Main">The NeuralSection that should be cloned.</param>
        public NeuralSection(NeuralSection Main)
        {
            // Set Randomizer
            TheRandomizer = Main.TheRandomizer;

            // Initialize Weights
            Weights = new double[Main.Weights.Length][];

            for (int i = 0; i < Weights.Length; i++)
                Weights[i] = new double[Main.Weights[0].Length];

            // Set Weights
            for (int i = 0; i < Weights.Length; i++)
            {
                for (int j = 0; j < Weights[i].Length; j++)
                {
                    Weights[i][j] = Main.Weights[i][j];
                }
            }
        }

        /// <summary>
        /// Feed input through the NeuralSection and get the output.
        /// </summary>
        /// <param name="Input">The values to set the input neurons.</param>
        /// <returns>The values in the output neurons after propagation.</returns>
        public double[] FeedForward(double[] Input)
        {
            // Validation Checks
            if (Input == null)
                throw new ArgumentException
                      ("The input array cannot be set to null.", "Input");
            else if (Input.Length != Weights.Length - 1)
                throw new ArgumentException("The input array's length
                does not match the number of neurons in the input layer.", "Input");

            // Initialize Output Array
            double[] Output = new double[Weights[0].Length];

            // Calculate Value
            for (int i = 0; i < Weights.Length; i++)
            {
                for (int j = 0; j < Weights[i].Length; j++)
                {
                    if (i == Weights.Length - 1)    // If is Bias Neuron
                        Output[j] += Weights[i][j]; // Then, the value of the neuron 
                                                    // is equal to one
                    else
                        Output[j] += Weights[i][j] * Input[i];
                }
            }

            // Apply Activation Function
            for (int i = 0; i < Output.Length; i++)
                Output[i] = ReLU(Output[i]);

            // Return Output
            return Output;
        }

        /// <summary>
        /// Mutate the NeuralSection.
        /// </summary>
        /// <param name="MutationProbablity">The probability
        /// that a weight is going to be mutated. (Ranges 0-1)</param>
        /// <param name="MutationAmount">The maximum amount a Mutated Weight would change.
        /// </param>
        public void Mutate (double MutationProbablity, double MutationAmount)
        {
            for (int i = 0; i < Weights.Length; i++)
            {
                for (int j = 0; j < Weights[i].Length; j++)
                {
                    if (TheRandomizer.NextDouble() < MutationProbablity)
                        Weights[i][j] = TheRandomizer.NextDouble() * 
                                        (MutationAmount * 2) - MutationAmount;
                }
            }
        }

        /// <summary>
        /// Puts a double through the activation function ReLU.
        /// </summary>
        /// <param name="x">The value to put through the function.</param>
        /// <returns>x after it is put through ReLU.</returns>
        private double ReLU(double x)
        {
            if (x >= 0)
                return x;
            else
                return x / 20;
        }
    }

    /// <summary>
    /// Initiates a NeuralNetwork from a Topology and a Seed.
    /// </summary>
    /// <param name="Topology">The Topology of the Neural Network.</param>
    /// <param name="Seed">The Seed of the Neural Network.
    /// Set to 'null' to use a Timed Seed.</param>
    public NeuralNetwork (UInt32[] Topology, Int32? Seed = 0)
    {
        // Validation Checks
        if (Topology.Length < 2)
            throw new ArgumentException
            ("A Neural Network cannot contain less than 2 Layers.", "Topology");

        for (int i = 0; i < Topology.Length; i++)
        {
            if(Topology[i] < 1)
                throw new ArgumentException
                ("A single layer of neurons must contain, 
                  at least, one neuron.", "Topology");
        }

        // Initialize Randomizer
        if (Seed.HasValue)
            TheRandomizer = new Random(Seed.Value);
        else
            TheRandomizer = new Random();

        // Set Topology
        TheTopology = new List<uint>(Topology).AsReadOnly();

        // Initialize Sections
        Sections = new NeuralSection[TheTopology.Count - 1];

        // Set the Sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i] = new NeuralSection
                          (TheTopology[i], TheTopology[i + 1], TheRandomizer);
        }
    }

    /// <summary>
    /// Initiates an independent Deep-Copy of the Neural Network provided.
    /// </summary>
    /// <param name="Main">The Neural Network that should be cloned.</param>
    public NeuralNetwork (NeuralNetwork Main)
    {
        // Initialize Randomizer
        TheRandomizer = new Random(Main.TheRandomizer.Next());

        // Set Topology
        TheTopology = Main.TheTopology;

        // Initialize Sections
        Sections = new NeuralSection[TheTopology.Count - 1];

        // Set the Sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i] = new NeuralSection (Main.Sections[i]);
        }
    }

    /// <summary>
    /// Feed Input through the NeuralNetwork and Get the Output.
    /// </summary>
    /// <param name="Input">The values to set the Input Neurons.</param>
    /// <returns>The values in the output neurons after propagation.</returns>
    public double[] FeedForward(double[] Input)
    {
        // Validation Checks
        if (Input == null)
            throw new ArgumentException("The input array cannot be set to null.", "Input");
        else if (Input.Length != TheTopology[0])
            throw new ArgumentException("The input array's length
            does not match the number of neurons in the input layer.", "Input");

        double[] Output = Input;

        // Feed values through all sections
        for (int i = 0; i < Sections.Length; i++)
        {
            Output = Sections[i].FeedForward(Output);
        }

        return Output;
    }

    /// <summary>
    /// Mutate the NeuralNetwork.
    /// </summary>
    /// <param name="MutationProbablity">The probability
    /// that a weight is going to be mutated. (Ranges 0-1)</param>
    /// <param name="MutationAmount">The maximum amount a mutated weight would change.
    /// </param>
    public void Mutate (double MutationProbablity = 0.3, double MutationAmount = 2.0)
    {
        // Mutate each section
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i].Mutate(MutationProbablity, MutationAmount);
        }
    }
}

现在我们的实现已经准备就绪,我们必须尝试一些简单的东西。XOR 函数可以作为概念验证。如果你不知道 XOR 函数是什么,这就是你应该期望的结果

因为一张图片胜过千言万语,所以训练过程应该这样进行

如果我们将这个流程图变成代码,我们最终应该得到这个

using System;

namespace NeuralXOR
{
    class Program
    {
        static void Main(string[] args)
        {
            int Iteration = 0;                           // Current Training Iteration

            NeuralNetwork BestNetwork = new NeuralNetwork
            (new uint[] { 2, 2, 1 });                    // The best network currently made
            double BestCost = double.MaxValue;           // The cost that the 
                                                         // best network achieved
            double[] BestNetworkResults = new double[4]; // The results that the 
                                                         // best network calculated

            double[][] Inputs = new double[][]           // This represents the possible 
                                                         // inputs or the training dataset
            {
                    new double[] { 0, 0 },
                    new double[] { 0, 1 },
                    new double[] { 1, 0 },
                    new double[] { 1, 1 }
            };
            double[] ExpectedOutputs = new double[] { 0, 1, 1, 0 }; // This represents 
                                      // the expected outputs from the optimum NeuralNetwork

            while (true) // Keep Training forever
            {
                NeuralNetwork MutatedNetwork = new NeuralNetwork(BestNetwork); // Clone the 
                                                                               // current 
                                                                               // best network
                MutatedNetwork.Mutate(); // Mutate the clone
                double MutatedNetworkCost = 0;
                double[] CurrentNetworkResults = new double[4]; // The results that the mutated 
                                                                // network calculated

                // Calculate the cost of the mutated network
                for (int i = 0; i < Inputs.Length; i++)
                {
                    double[] Result = MutatedNetwork.FeedForward(Inputs[i]);
                    MutatedNetworkCost += Math.Abs(Result[0] - ExpectedOutputs[i]);

                    CurrentNetworkResults[i] = Result[0];
                }

                // Does the mutated network perform better than the last one
                if (MutatedNetworkCost < BestCost)
                {
                    BestNetwork = MutatedNetwork;
                    BestCost = MutatedNetworkCost;
                    BestNetworkResults = CurrentNetworkResults;
                }

                // Print only each 20000 iteration in order to speed up the training process
                if (Iteration % 20000 == 0)
                {
                    Console.Clear();                    // Clear the current console text

                    for (int i = 0; i < BestNetworkResults.Length; i++) // Print the best 
                                                                        // truth table
                    {
                        Console.WriteLine(Inputs[i][0] + "," +
                        Inputs[i][1] + " | " + BestNetworkResults[i].ToString("N17"));
                    }
                    Console.WriteLine("Cost: " + BestCost);       // Print the best cost
                    Console.WriteLine("Iteration: " + Iteration); // Print the current 
                                                                  // Iteration
                }

                // An iteration is done
                Iteration++;
            }
        }
    }
}

运行它,你会得到这样的结果

关注点

当我发现仅需 20,000 次迭代就可以将 XOR 函数的成本降低到 0.03 以下时,我感到非常惊讶。我本以为会需要更长的时间,但它就是不需要。如果有人有任何问题,或者只是想讨论任何事情,你都可以在下方留言。等待未来的文章,它将解释如何使用这个实现,使用强化学习让汽车在 Unity 中学会自己驾驶。我正在制作一个类似于 Samuel Arzt 制作的这个的东西

2017 年 12 月 11 日更新

我完成了第二篇文章,目前正在等待提交。你可以看看演示视频。它看起来有点吓人,但是……给你

2017 年 12 月 11 日的又一次更新

看起来 第二部分 已提交,伙计们。玩得开心!而且……永远不要想我们在这里就结束了。我目前的目标是实现 3 个交叉运算符,使进化更有效率,并为开发者提供更多多样性。之后,反向传播是目标。

2018 年 2 月 20 日更新

第三部分 已经上线运行!与第二部分和 第一部分 中讨论的系统相比,它有了显著的改进。告诉我你的想法!

历史

© . All rights reserved.