Neural Dot Net 第 1 部分 引言






4.67/5 (32投票s)
2003年6月24日
15分钟阅读

186048

7570
一个C#神经网络库。
引言
我不知道其他人是否和我一样,我一直对神经网络和人工智能本身很着迷。这种着迷,我想是因为我对此一无所知,而且它一直是我认为总有一天我会去看看它是如何工作的。这一系列文章就是这种着迷的结果,它让我发现了一些相当令人惊讶的事情,以及一些根本说不通的事情。比如单神经元神经网络的想法。请称它为函数,把它包装成一个类,但不要给它起名“单神经元神经网络”。这违背了我认为神经网络应该具备的一切,而且这些人难道不读他们写的书吗?我可以诚实地说,我读过关于神经网络的书,但都没有得到任何启发,因为理论阻碍了我的理解。有很多理论,有些很有趣,很多都在只有资深数学家才敢涉足的领域。但有些关于神经网络的书似乎用理论来摧毁你从书中学习任何东西的机会。还有“错误的开始”的角度,有时你可能还没有进入最佳状态来充分理解一本书。我最大的例子就是詹姆斯·乔伊斯的《尤利西斯》。我一生中曾三次尝试阅读这本书,第一次我读了几章就停止了,因为它让我非常厌烦。第二次我读完了,觉得很有趣。第三次我读了几章,觉得他在胡说八道。那么,《尤利西斯》是一本好书吗?值得读吗?我必须说它是一本很难读的书,如果你没有进入一个好的状态来最大化你的阅读体验,那么你就不会。这适用于任何关于神经网络的书,因为没有一本是容易的。诀窍在于不要被最初的困惑和不确定性吓倒,而是将其视为一项挑战,需要花费一些时间和学习才能逐渐明朗。
最后,在绝望中我转向了代码。作为一名程序员,我肯定能理解代码。我只具备非常基础的数学知识,页面上写着“squiggle x over different squiggle z times two”的部分,实际上意味着“从另一个数组中的点i
的值减去点n
的值,然后乘以二”。这说得通,我不知道他们为什么这么做,但这只是一个开始,而且在我工作过的代码中,我不得不维护的许多代码,在初次查看时远不如这个有意义。
在此需要指出的是,该项目的最初想法是按顺序发布,每个版本包含两个工作中的网络。这个想法已被放弃,现在发布的版本包含了最初设想的所有六个网络。本文档将偶尔提及这一原始意图。此外,一些类图在代码和图表之间存在细微差异。这是由于文档和代码的并行开发。当代码的更改影响到网络的运行方式时,我已尽力使图保持最新。
该项目是使用Developer Studio 2002和Developer Studio 2003以及.NET运行时版本1.0和1.1开发的。由于项目是在.NET框架1.0版本上开始的,我认为它应该可以在该框架上编译和运行,尽管Developer Studio 2002的用户需要重新构建项目文件。
网络本身设置为使用程序提供的默认值以达到最佳效果,并提供选项以便用户可以进行实验以了解会发生什么。在许多情况下,更改默认选项显然会改变网络的行为,甚至可能导致某些网络根本无法工作。由于没有尝试保存对网络的选定更改,因此如果网络损坏,重新启动程序即可恢复一切正常。
什么是神经网络?
你不讨厌那些看似简单的问题吗?我倒不如直接问“为什么?”。我想传统的观念是,神经网络就像一个微型计算机大脑,它的设计使得各个细胞可以相互通信,并共同解决更大的问题,但从编程的角度来考虑一下。你如何编写一个如此通用以至于能够解决你抛给它的任何问题,并且所有小的、相同的神经元都能协同工作来完成你交给它们的任何任务?这对我来说听起来真的很困难,通常发生的情况是你会得到两种不同类型的网络:一种专注于努力在生物学上准确并模仿大脑的神经元或节点,另一种则专注于手头的特定任务。我怀疑,总体而言,大多数网络都是准确性和功能性之间的折衷。我的意思是,拥有一个能完美模拟大脑功能的模型固然好,但如果你无法用它做任何事情,那么它只能用于研究目的,而现在人们倾向于需要结果而不是研究。这意味着纯粹的研究仍然掌握在少数幸运儿手中。
在他的著作《神经网络的本质》中,罗伯特·卡兰在第一章给出了一套简短的构成神经网络的规则。我在此给出这些规则有两个原因。一,它们可能是我见过的对神经网络最精确的定义;二,我能理解它们的意思。
1. 一组简单的处理单元。
神经网络由神经元或节点组成。它们被认为是简单的处理单元,尽管如果你见过其中一些背后的数学,你可能会想“简单对谁而言?”。但本质上,节点(为了避免混淆,我将继续称它们为节点,因为在文献中它们被称为神经元、节点,还有其他各种称呼,并且可以互换使用,所以从现在开始,它们就是节点,仅此而已。任何其他称呼都指代完全不同的东西。)从程序员的角度来看,节点就是一个执行特定任务或目标的类。与任何类一样,该任务或目标在代码中定义,而代码则由你最初想要该类做什么来定义。为了我们的目的,在配套代码中,一个神经元将是一组节点,最简单的形式包含四个节点:两个用于输入,一个用于偏置节点,一个用于网络工作节点,即在第三部分提供的示例代码中,神经元将包含一个Adaline节点。
2. 连接模式
这是网络的构建方式以及数据在网络中流动的方式。这也是网络得名的原因。例如,我们首先要处理的Adaline网络包含两个输入节点,一个偏置节点和一个Adaline节点。Adaline节点将通过调用run和learn函数来完成所有工作。每个神经元中的节点数量没有固定限制,数据流向也没有限制。数据起源于输入节点,但一旦网络变大,数据就会通过一个节点,并可以向前或向后传递给另一个节点,由该节点根据其认为合适的方式进行处理。
3. 信号通过网络传播的规则
这仅仅是常识。无论我们处理的是哪种类型的网络,我们都希望实现某些结果,而这些结果只能通过以特定方式处理网络所处理的数据来实现。这种方式可以是,将数据传递给输出节点,或通过网络反向传播以进行进一步处理,或者另一些情况下甚至向前传递以进行进一步处理。无论哪种方式,与其他任何计算机程序一样,我们需要执行一定数量的步骤,并且通常只有一两种方法可以得到最终的正确结果。
4. 组合输入信号的规则
这基本上是我们将在进入神经网络的数据上执行的操作。 at this point, it doesn't really matter that we know what the answer will be, just that we know what we want to do with the information in the first place. This could be a mathematical function or the comparison of strings or objects.
5. 计算输出信号的规则
这不一定是程序的最终输出,而是该代码段的输出。如果你以函数的形式考虑它,那么神经网络节点的输出值就是函数的返回值。这通常是一个数值,但没有理由必须是数值。例如,Adaline网络可以很容易地返回一个布尔值true或false,这本身并不会影响节点是否正常工作。
6. 调整权重的学习规则。
权重是赋予连接或链接的值,有助于学习过程。这由learn函数实时更新,并且自然应该有一个规则来指导这一过程。然而,考虑到网络的最终目标是学习生成对给定训练数据的正确答案,那么更新权重的完美规则似乎就是随机分配一个值,直到出现结果。理论上,这只会使网络花费更长的时间来工作,而不是预先编程的显式规则。
网络如何学习?
这个问题的简单答案是通过反复试验,但像往常一样,事情并非如此简单。为了探讨这个问题,我将讨论本系列文章第三部分将要介绍的Adaline网络。以下是Adaline 1程序标准运行输出的一部分。
Iteration number 172 produced 6 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 173 produced 5 Good values out of 250
Learn called at number 6 Pattern value = 1 Neuron value = -1
Iteration number 174 produced 6 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 175 produced 5 Good values out of 250
Learn called at number 6 Pattern value = 1 Neuron value = -1
Iteration number 176 produced 6 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 177 produced 5 Good values out of 250
Learn called at number 6 Pattern value = 1 Neuron value = -1
Iteration number 178 produced 6 Good values out of 250
Learn called at number 7 Pattern value = 1 Neuron value = -1
Iteration number 179 produced 7 Good values out of 250
Learn called at number 6 Pattern value = 1 Neuron value = -1
Iteration number 180 produced 6 Good values out of 250
Learn called at number 32 Pattern value = 1 Neuron value = -1
Iteration number 181 produced 32 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 182 produced 5 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 183 produced 5 Good values out of 250
Learn called at number 32 Pattern value = 1 Neuron value = -1
Iteration number 184 produced 32 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 185 produced 5 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 186 produced 5 Good values out of 250
Learn called at number 32 Pattern value = 1 Neuron value = -1
Iteration number 187 produced 32 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 188 produced 5 Good values out of 250
Learn called at number 5 Pattern value = 1 Neuron value = -1
Iteration number 189 produced 5 Good values out of 250
Iteration number 190 produced 250 Good values out of 250
从这个例子中可以看出,网络在得到所有正确答案之前进行了190次尝试。Adaline程序基本上比较两个介于-1到1之间的值。这些值被随机生成到一个文件中,该文件可以通过系列文章第三篇中提供的演示程序生成。基本上,程序计算的是,如果给它的第一个数字小于给它的第二个数字,那么程序将输出1,否则输出-1。技术上来说,这意味着网络将每个节点的输入和权重相加,然后将总和通过传递函数,该函数返回节点输出。生成的训练数据也生成了问题的正确答案,网络会自我测试以查看它是否得到了正确的答案。在这种情况下,网络大部分每次运行都能得到大约6个正确答案,但在第190次运行时,它得到了所有正确答案。程序被编写成一直循环处理数据,直到全部正确为止,它在第190次尝试时做到了。那么,这里发生了什么?嗯,当程序调用Adaline 1程序中的run函数时,run函数基本上是
for( int i=0; i<nCount; i++ )
{
dTotal += ( ( BasicLink )this.InputLinks[ i ] ).WeightedInputValue( nID );
}
this.NodeValues[ nID ] = TransferFunction( dTotal );
这段代码循环遍历节点的链接,获取加权的输入值,然后将完整总数添加到 `double` total 变量。目前,你只需要知道 `nID` 值等于 `Values` 类中存储的 `nodevalue` 常量(== 0),并且它正在从 `InputLinks` 数组中索引为 `i` 的输入节点中获取第一个值。加权输入值的重要部分如下所示:
dReturn = bnInputNode.GetValue( nID )
* ( ( double )arrayLinkValues[ Values.Weight ] );
这意味着将节点中的值乘以链接的权重值。链接的权重值实际上是链接中的第一个值,它在Adaline Link构造函数中设置为
arrayLinkValues[ Values.Weight ] = Values.Random( -1, 1 );
正如你所见,这是一个介于-1和1之间的随机数,这意味着网络第一次尝试得到正确答案只不过是猜测。但正如你在上面的循环中所见,run函数会循环遍历并使用权重值进行计算,将总数添加到 `dTotal`。然后 `dTotal` 变量被传递给 `TransferFunction`,这是另一段简单的代码。
if( dValue < 0 )
return -1.0;
return 1.0;
如果 `dTotal` 小于0则返回-1,如果 `dTotal` 大于0则返回1。所以,假设 `dTotal` 中有一个值,根据训练集,答案是-1,但网络返回1。答案是错误的,程序会打印出上面的一行,说它到目前为止得到了一定数量的正确答案,但现在它必须调用 `learn`,因为这个是错误的。`learn` 函数使用delta规则或Widrow-Hoff规则,在编程术语中是这样的:
NodeErrors[ Values.NodeError ] =
( ( double )NodeValues[ Values.NodeValue ] )*-2.0;
BasicLink link;
int nCount = InputLinks.Count;
double dDelta;
for( int i=0; i<nCount; i++ )
{
link = ( BasicLink )InputLinks[ i ];
/// delta rule
dDelta = ( ( double )NodeValues[ Values.LearningRate ] )
* ( ( double )link.InputValue( Values.NodeValue ) )
* ( ( double )NodeErrors[ Values.NodeError ] );
link.UpdateWeight( dDelta );
}
首先,节点的误差值设置为等于节点值 * -2.0,然后代码遍历当前节点的每个输入链接,并将链接的权重值更新为 `dDelta` 值,这是节点值学习率的结果,学习率在创建Adaline节点时设置为0.45,但你可以随意更改此值以查看它如何影响程序的学习率。总之,回到乘法,节点值的学习率乘以链接的输入值,然后结果乘以函数开始时设置的误差值。这里可能需要提到的是,这是一个简单的网络,旨在帮助人们学习和理解它是如何工作的。事情可以变得复杂得多。你只是被温柔地引入。还应该注意到,虽然这个例子很简单,但你在这里拥有的是一个决策的基础。只要你知道期望的输出是什么,这个程序就可以修改为你计算它,一旦它被训练好,它就可以搜索大量数据,检查不符合其所需参数的事物。
最后
以上绝非我对神经网络的所有看法,但希望它能让完全的初学者理解神经网络的基本原理,更重要的是,它们是如何学会做到它们所做的事情的。在第二部分,我将介绍所有即将推出的程序背后的基本类,在第三部分,我们将回到Adaline网络,进行近距离的深入探讨。最后一点需要理解的是,所有代码都是实验性的,唯一可以确定的是,随每个版本发布的代码都是可用的。因此,我还没有决定是否向后兼容。我将尽力确保所有以前的程序都能与最新版本的代码和库一起工作。保留神经网络测试程序将有助于强制执行这一点,但我已经有一些想法,一旦我达到想要尝试的程度,就需要对所有以前的代码进行重写。
历史
- 2003年6月24日:首次发布。
- 2003年10月19日:审阅并编辑以符合CP规范。
参考文献
- Tom Archer (2001) Inside C#, Microsoft Press
- Jeffery Richter (2002) Applied Microsoft .NET Framework Programming, Microsoft Press
- Charles Peltzold (2002) Programming Microsoft Windows With C#, Microsoft Press
- Robinson et al (2001) Professional C#, Wrox
- William R. Staneck (1997) Web Publishing Unleashed Professional Reference Edition, Sams.net
- Robert Callan, The Essence Of Neural Networks (1999) Prentice Hall
- Timothy Masters, Practical Neural Network Recipes In C++ (1993) Morgan Kaufmann (Academic Press)
- Melanie Mitchell, An Introduction To Genetic Algorithms (1999) MIT Press
- Joey Rogers, Object-Orientated Neural Networks in C++ (1997) Academic Press
- Simon Haykin Neural Networks A Comprehensive Foundation (1999) Prentice Hall
- Bernd Oestereich (2002) Developing Software With UML Object-Orientated Analysis And Design In Practice, Addison Wesley
- R Beale & T Jackson (1990) Neural Computing An Introduction, Institute Of Physics Publishing
谢谢
特别感谢 TortoiseCVS 的版本控制。
所有UML图均使用 Metamill version 2.2 生成。