[译]重塑神经网络-第3部分
By robot-v1.0
本文链接 https://www.kyfws.com/ai/reinventing-neural-networks-part-2-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 9 分钟阅读 - 4049 个词 阅读量 0重塑神经网络-第3部分(译文)
原文地址:https://www.codeproject.com/Articles/1231020/ReInventing-Neural-Networks-Part-2
原文作者:Byte-Master-101
译文由本站 robot-v1.0 翻译
前言
Now that we got the basics over with, It’s time for improvement!
现在我们已经掌握了基础知识,是时候进行改进了!
全系列:(The Full Series:)
- 第1部分(Part 1) :我们创造整体(: We create the whole)
NeuralNetwork
从头开始上课.(class from scratch.) - 第2部分(Part 2) :我们在Unity中创建一个环境,以便测试该环境中的神经网络.(: We create an envirnment in Unity in order to test the neural network within that environment.)
- 第三部分(Part 3) :通过在代码中添加一种新型的变异,我们对已经创建的神经网络进行了重大改进.(: We make a great improvement to the neural network already created by adding a new type of mutation to the code.)
介绍(Introduction)
在开始之前,我必须承认我已经16岁了,因为我有点注意到人们知道这一点后会打h,所以我想^也许人们知道了,我会从中得到更多.只是在说.(Before I start, I gotta acknowledge that I’m 16 Years old, cuz I kinda noticed people get hiccups when they know that, so I thought… maybe if people knew, I would get something more out of it. Just sayin.)
欢迎回来费拉斯!大约一个月前,我发布了本系列的第2部分.在那之后,我真的很忙于做很多其他的事情,这些事情不能让我继续做我真正想做的事情(对不起).但是,几天前(Welcome Back Fellas! About a month ago, I got Part 2 of this series posted. After that, I got really caught up doing a bunch of other stuff which couldn’t let me continue doing what I really wanna do (Sorry about that). However, a few days ago,)迈克尔`巴特利特(Michael Bartlett)给我发送了一封电子邮件,询问名为安全突变(或简称为SA)的GA运营商(指(sent me an email asking about a GA operator called safe mutation(or SA for short) (referring to) 这篇报告(this paper) ).().)
背景(Background)
要继续阅读本文,您需要具备基本的C#和Unity编程知识.此外,您还需要阅读(To follow along this article, you’ll need to have basic C# and Unity programming knowledge. Also you’re gonna need to have read) 第1部分(Part 1) 和(and) 第2部分(Part 2) 这个系列的(of this series.)
理解理论(Understading The Theory)
根据纸质发买(According to the paper sent buy)麦可(Michael),看来有两种类型的SM运算符:(, it appears that there are two types of SM operators:)
- 通过重新缩放进行安全突变:简而言之,这就像稍微调整权重以知道该位对输出有多大影响,并据此进行明智的突变.(Safe Mutation through Rescaling: In a nutshell, that’s just like tuning the weight a bit to know how much this bit affects the output, and according to that, make an informed mutation.)
- 通过梯度进行安全突变:这与反向传播中用于获取每个权重的梯度的方法类似,并据此进行了明智的突变.(Safe Mutation through Gradients: That’s a similar approach to the one used in backpropagation to get the gradient of each weight, and according to that, also make an informed mutation.) 尽管如此,但是并没有给我足够的信息来开始编码,所以我一直在寻找直到发现(That still, however didn’t give me enough info to start coding, so I kept looking till I found) 这篇报告(this paper) .本文展示了如果您仅使用眼前的一些信息,就能获得多大的改进.如果您查看该论文中的图4,您会发现无偏突变的结果最差,而节点突变的结果则呈指数级增长!哎呀,它甚至与分频器匹配!(. This paper shows how great of an improvement you can get if you just use some of the information that’s right in front of you. If you check Figure 4 in that paper, you can see that unbiased mutation gives the worst results, while node mutation gives exponentially better results! Heck yeah, it even matches crossover!)
它甚至与图7和图8上的安全突变(通过梯度)和反向传播进行了比较.它既可以与安全突变联系在一起,又可以通过节点突变来阻止反向传播…(It’s even compared with safe mutation(through gradients) and backpropagation on figures 7 and 8. It’s either a tie with safe mutation, and node mutation crushes backpropagation…)
好的,那么^那神奇的" Mutate Nodes"运算符是什么?好吧…普通突变只是选择了一些权重并将其变异如下:(Ok then… So what’s that magical “Mutate Nodes” operator? Well… normal mutation just kinda selects some weights and mutates them as follows:)
但是,节点突变会选择一些节点并对所有权重进行突变:(However, node mutation selects a few nodes and mutates all of the weights coming to it:)
使用代码(Using the code)
好吧,代码很简单.首先,添加(Well, the code it pretty straight forward. First, add the) MutateNodes
功能(function to the) NeuralSection
类:(class:)
/// <summary>
/// Mutate the NeuralSection's Nodes.
/// </summary>
/// <param name="MutationProbablity">The probability that a node is going to be mutated. (Ranges 0-1)</param>
/// <param name="MutationAmount">The maximum amount a Mutated Weight would change.</param>
public void MutateNodes(double MutationProbablity, double MutationAmount)
{
for (int j = 0; j < Weights[0].Length; j++) // For each output node
{
if (TheRandomizer.NextDouble() < MutationProbablity) // Check if we are going to mutate this node
{
for (int i = 0; i < Weights.Length; i++) // For each input node connected to the current output node
{
Weights[i][j] = TheRandomizer.NextDouble() * (MutationAmount * 2) - MutationAmount; // Mutate the weight connecting both nodes
}
}
}
}
然后,将调用者函数添加到(Then, add the caller function to the) NeuralNetwork
类:(class:)
/// <summary>
/// Mutate the NeuralNetwork's Nodes.
/// </summary>
/// <param name="MutationProbablity">The probability that a node is going to be mutated. (Ranges 0-1)</param>
/// <param name="MutationAmount">The maximum amount a Mutated Weight would change.</param>
public void MutateNodes(double MutationProbablity = 0.3, double MutationAmount = 2.0)
{
// Mutate each section
for (int i = 0; i < Sections.Length; i++)
{
Sections[i].MutateNodes(MutationProbablity, MutationAmount);
}
}
差不多了!那就是(This is pretty much it! That’s how the) NeuralNetwork.cs
现在应该看:(should look now:)
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>
/// Mutate the NeuralSection's Nodes.
/// </summary>
/// <param name="MutationProbablity">The probability that a node is going to be mutated. (Ranges 0-1)</param>
/// <param name="MutationAmount">The maximum amount a Mutated Weight would change.</param>
public void MutateNodes(double MutationProbablity, double MutationAmount)
{
for (int j = 0; j < Weights[0].Length; j++) // For each output node
{
if (TheRandomizer.NextDouble() < MutationProbablity) // Check if we are going to mutate this node
{
for (int i = 0; i < Weights.Length; i++) // For each input node connected to the current output node
{
Weights[i][j] = TheRandomizer.NextDouble() * (MutationAmount * 2) - MutationAmount; // Mutate the weight connecting both nodes
}
}
}
}
/// <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);
}
}
/// <summary>
/// Mutate the NeuralNetwork's Nodes.
/// </summary>
/// <param name="MutationProbablity">The probability that a node is going to be mutated. (Ranges 0-1)</param>
/// <param name="MutationAmount">The maximum amount a Mutated Weight would change.</param>
public void MutateNodes(double MutationProbablity = 0.3, double MutationAmount = 2.0)
{
// Mutate each section
for (int i = 0; i < Sections.Length; i++)
{
Sections[i].MutateNodes(MutationProbablity, MutationAmount);
}
}
}
而且…不,我不会那样离开你的.现在是时候对以前在Unity中进行的汽车演示进行一些微小的更改了,以便我们可以自己看到差异.我们先去(And… No I’m not gonna leave you like that. Now it’s time to make tiny little changes to the cars demo made in Unity previously, so that we can see the difference for ourselves. Let’s first go to) EvolutionManager.cs
在我们的统一项目中,并在脚本的开头添加以下变量:(in our untiy project and add this variable at the beginning of the script:)
[SerializeField] bool UseNodeMutation = true; // Should we use node mutation?
让我们通过替换对的调用来使用此变量(Let’s also put this variable to use by replacing the call to) Car.NextNetwork.Mutate()
在 - 的里面(inside the) StartGeneration()
具有以下功能:(function with that:)
if(UseNodeMutation) // Should we use Node Mutation
Car.NextNetwork.MutateNodes(); // Mutate its nodes
else
Car.NextNetwork.Mutate(); // Mutate its weights
这条路,(This way,) EvolutionManager.cs
应该看起来像这样:(should end up looking like that:)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EvolutionManager : MonoBehaviour
{
public static EvolutionManager Singleton = null; // The current EvolutionManager Instance
[SerializeField] bool UseNodeMutation = true; // Should we use node mutation?
[SerializeField] int CarCount = 100; // The number of cars per generation
[SerializeField] GameObject CarPrefab; // The Prefab of the car to be created for each instance
[SerializeField] Text GenerationNumberText; // Some text to write the generation number
int GenerationCount = 0; // The current generation number
List<Car> Cars = new List<Car>(); // This list of cars currently alive
NeuralNetwork BestNeuralNetwork = null; // The best NeuralNetwork currently available
int BestFitness = -1; // The FItness of the best NeuralNetwork ever created
// On Start
private void Start()
{
if (Singleton == null) // If no other instances were created
Singleton = this; // Make the only instance this one
else
gameObject.SetActive(false); // There is another instance already in place. Make this one inactive.
BestNeuralNetwork = new NeuralNetwork(Car.NextNetwork); // Set the BestNeuralNetwork to a random new network
StartGeneration();
}
// Sarts a whole new generation
void StartGeneration ()
{
GenerationCount++; // Increment the generation count
GenerationNumberText.text = "Generation: " + GenerationCount; // Update generation text
for (int i = 0; i < CarCount; i++)
{
if (i == 0)
Car.NextNetwork = BestNeuralNetwork; // Make sure one car uses the best network
else
{
Car.NextNetwork = new NeuralNetwork(BestNeuralNetwork); // Clone the best neural network and set it to be for the next car
if(UseNodeMutation) // Should we use Node Mutation
Car.NextNetwork.MutateNodes(); // Mutate its nodes
else
Car.NextNetwork.Mutate(); // Mutate its weights
}
Cars.Add(Instantiate(CarPrefab, transform.position, Quaternion.identity, transform).GetComponent<Car>()); // Instantiate a new car and add it to the list of cars
}
}
// Gets called by cars when they die
public void CarDead (Car DeadCar, int Fitness)
{
Cars.Remove(DeadCar); // Remove the car from the list
Destroy(DeadCar.gameObject); // Destroy the dead car
if (Fitness > BestFitness) // If it is better that the current best car
{
BestNeuralNetwork = DeadCar.TheNetwork; // Make sure it becomes the best car
BestFitness = Fitness; // And also set the best fitness
}
if (Cars.Count <= 0) // If there are no cars left
StartGeneration(); // Create a new generation
}
}
搅动所有内容并开始游戏后,您会得到:(After stirring it all up and hitting play, you get this:)
兴趣点(Points of Interest)
只需添加一个非常简单的补充,就可以从培训的上一篇文章中看到如此巨大的进步,真是太好了.现在我们有了一些改进,现在轮到我告诉我您对这一切的看法了.你认为我下一步该怎么做?而且,我应该制作有关AI和类似内容的youtube视频,还是应该坚持阅读文章?(It was fantastic to see such a great improvement from the last article in the training just by making a really simple addition. Now that we’ve got some improvement, it’s your turn to tell my what you think about all this. And what do you think I should do next? And, Should I make youtube videos regarding AI and stuff like that, or should I stick with articles?)
历史(History)
版本1.0:主要实现(Version 1.0: Main Implemetation)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET VS2013 Unity3D Unity machine-learning 新闻 翻译