Neuronal.NET - 易于实现的神经网络






4.92/5 (36投票s)
如何在 C# 中实现一个简单的神经网。
引言
本文将以字符识别为例,讲解如何实现一个易于使用的神经网络。
神经网络需要 3 个要素
- 训练集(给定问题的已知答案)
- 神经网络
- 一种告诉网络它已改进的方法
这个人工网络使用进化算法进行训练。
以下是训练新神经网络的最简短示例
首先,添加对 Synapse.dll 的引用。
就是这样。
using Brains;
Brain ANN = new Brain(5, 4, 3);
ANN.SetInput(List<double> traindata);
while (ANN.score<10)
{
ANN.score=grade(ANN.Outputs);
}
Bitmap finish=ANN.ToBitmap();
该网络将有 5 个输入、4 个输出和 3 个隐藏层。它几乎与第一张图相似。
当然,您必须定义评分函数。这只是一个双精度数,告诉网络它所做的操作是否符合您的要求。
作为用户,您看不到训练算法,所有神奇之处都发生在 ANN.score 的 setter 中
score 值的 setter 是进化算法的接口,它试图始终获得比之前更高的分数。
简而言之,它的工作原理是
- 变异
- 通过所选项目
- 繁殖
注意: 用户根据自己的规则设置分数,因此如果实际输出更接近期望输出,分数应该稍高一些。
背景
关于神经网络,您需要了解几件事
最重要的是神经元。这是一个具有许多输入和一个双精度输出的构造。
为了有用地使用神经元,它们在许多层中相互连接。其中大多数是隐藏的,只有输出层和输入层可以从“外部”看到。
如果您查看第一张图,可以看到每个圆圈都是一个神经元。
任何给定神经元(输入层除外)的输入将是前一层所有神经元的所有输出。输入层的输出是用户提供的列表的成员。
本项目使用 arctan(x)/pi 作为激活函数(连续函数,将所有有理数值归一化到 -1 和 1 之间)。
每个神经元的输出公式如下
Output=Tranferfuction(sum(inputs*weights))
每个神经元的权重,在此称为基因组,是神经网络正常工作的关键因素。每个神经元都为连接到它的每个神经元存储一个双精度值。在这种情况下,该因子可以在 -4 和 +4 之间。这允许在所有输出的总和中进行抵消。神经网络在后台维护一个字典以提高速度。
通过将权重设置为某些(未知)值,神经网络可以“学习”输入和输出之间的关系。
要实际训练神经网络,必须测试所有权重的组合,以找到全局最优解。无法知道您已找到最佳解决方案。
尽管训练算法试图达到全局最优,但不能保证找到最优解。
最简单的神经元实现将是
class neuron
{ neuron
{
double output
{
get {tranferfunction(sum(previouslayer));}
private set{}
}
}
这当然是一种简化。实际代码看起来更像这样
double sum()
{
double back = 0;
for (int i = 0; i < previous.Count; i++) { back += previous[i].output * genome[i]; }
if (Parent.Transferfunction==Transferfunction.Stepfunction)
{
back = back / previous.Count;
}
back = transfer(back);
return back;
}
double ret = double.PositiveInfinity;
public double output
{
get
{
if (!double.IsInfinity(ret)) { return ret; }
else {
double outp;
if (Parent.antworten.ContainsKey(name) == false)
{
outp = sum();
lock (Parent) { Parent.antworten.Add(name, outp); }
return outp;
}
else { Parent.antworten.TryGetValue(name, out outp); return outp; } }
}
set { ret = value; }
}
有关神经网络的精彩阅读,请参阅
- https://codeproject.org.cn/Articles/16419/AI-Neural-Network-for-beginners-Part-of
- http://www2.econ.iastate.edu/tesfatsi/NeuralNetworks.CheungCannonNotes.pdf
使用神经网络
这个示例项目将展示如何训练网络来区分 3 个字母:A、B 和 C。
这意味着我们有一个 Bitmap 作为输入,3 个输出。每个输出是介于 0 和 1 之间的数字。
您要做的第一件事是考虑一种方法,将位图转换为 0 和 1 的列表
下面的示例图片
apply_image(Bitmap image)
{
List<double>input = new List<double>();
brightness = image.GetPixel(x, y).GetBrightness();
if (brightness > 0.5) { input.Add(1); }
else input.Add(0);
}
如果图片分辨率为 320x120,那么网络的输入将过多。
相反,您只获取每 20个像素(或其他类似值)。这已经足够了。
下面的代码包含训练神经网络所需的所有信息。
神经网络的不确定性在于隐藏层的数量和大小。
感知机的通用规则是它们根本不需要隐藏层。
如果您想实现更高的功能,例如 XOR 问题或乘法,您将需要更多的隐藏层。
当然,训练神经网络进行乘法是浪费时间,因为乘法对任何程序员来说都是一项简单的任务。
注意:Score 属性会稍微更改现有网络,以便下次表现更好。
它会维护一个内部列表,其中包含配置和相应的分数,以生成更好的配置。
NET.StopTraining();
获取迄今为止最佳的配置。
B 的完美分数将是 0 1 0。
Bitmap A = new Bitmap(@"A.bmp");
Bitmap B = new Bitmap(@"B.bmp");
Bitmap C = new Bitmap(@"C.bmp");
Bitmap T1 = new Bitmap(@"TEST1.bmp");
Bitmap T2 = new Bitmap(@"TEST2.bmp");
Brain NET = new Brain(100, 3,0);
NET.Trainmode = Trainmode.Fast;
NET.Transferfunction = Transferfunction.Arctan;
NET.Outputmax = 1;
var inputs = apply_image(A);
double score = 0;
while (NET.Score<2.9)
{
score = 0;
inputs = apply_image(A);
NET.SetInput(inputs);
score += NET[0];
score -= NET[1];
score -= NET[2];
inputs = apply_image(B);
NET.SetInput(inputs);
score -= NET[0];
score += NET[1];
score -= NET[2];
inputs = apply_image(C);
NET.SetInput(inputs);
score -= NET[0];
score -= NET[1];
score += NET[2];
NET.Score = score;
}
NET.StopTraining();
//See whole code in demo project!!
神经网络应该能够正确判断案例 2 仍然是 A(较小,不在中心,并且有一些伪影)。
上述测试用例的最终输出如下
- A: 0.98 0.02 0.03
- 坏的 A: 0.86 0.13 0.09
- C: 0.02 0.01 0.98
这告诉我们训练已经成功,网络实际上“知道”A、B 和 C 之间的区别!
要自己测试代码,请查看演示项目。
学习算法
每个神经元都有一组称为基因组的权重。整个网络可以看作是双精度数的列表,因为只有每个神经元的每个权重才重要。
如果训练网络,则只需更改此列表。
您要做的第一件事是保留所有分数和基因组的列表。为此,它基本上是一个双精度数列表和一个分数。此片段发生在 score 的 setter 中,因为用户负责分数和 trainingset
。
list<genome> max;
if (value > max.score)
{
if (Trainmode == Trainmode.Fast)
{
if (best.Count == 0) {best.Add(new Genome(this));}
var max = best.MaxBy(x => x.score);
if (value > max.score) { score = value; Generation++;
best.Add(new Genome(this));
}
else { max.SetGenesTo(this); this.mutate(); }
}
从优秀基因组列表中,选择最好的一个。之后,一些权重(2%)会被随机更改。
这称为变异和选择。繁殖通过复制最佳基因组来实现。
实际实现更复杂,因为它处理更多基因组作为最佳基因组,以避免局部最小值。
您可以选择是想快速学习还是尝试避免非最优解。为此,我实现了 trainmode
属性。
您可以将其设置为 fast(快速)或 quality(高质量)。前者始终选择最佳基因组并进一步开发它。后者 trainmode
训练 10 个基因组,然后选择最好的一个。
之后,它会根据最好的基因组再次开发 10 个基因组。这样,训练速度会慢很多,但您可以取得更好的结果。
如果您想了解更多关于其他训练方法的信息,请参阅背景部分中的链接!!
关注点
在我编写代码的过程中,我一直想看看网络在做什么。这就是为什么我实现了 mynet.ToBitmap();
和 mynet.ShowGraph();
,它们将所有输入和参数转换为一张漂亮的图片。
大多数人将神经网络视为一种人工智能。本文使用的简单前馈网络并非智能。您更可以将神经网络视为一个数学公式。您不知道公式,但您知道某些输入参数(trainingset
)的答案。学习算法会改变矩阵乘法中的正确参数,从而找到训练集的通用公式。