重塑神经网络






4.90/5 (34投票s)
神经网络可以做很多令人惊叹的事情,你可以从零开始了解如何构建一个。你可能会惊讶于从头开始开发一个神经网络是多么容易!
完整系列
- 第 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;
}
现在,我们需要让开发者能够变异 NeuralNetwork
。Mutate
函数只是独立地变异每个部分
/// <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 日更新
第三部分 已经上线运行!与第二部分和 第一部分 中讨论的系统相比,它有了显著的改进。告诉我你的想法!