11行Python代码建立神经网络

11行Python代码建立神经网络

概览:对我而言,可以获得乐趣的玩具代码是学编程最有效的方法。这个教程通过一个非常简单又好玩的Python实例讲述反向传播。

补充:很多人询问我文章的后续部分,我也正在计划写下一篇。写完的时候我会在推特(@iamtrask)上面发出来。如果你喜欢这些内容欢迎关注我的推特,并感谢你们所有的反馈信息!

先把代码给出来:

640.png

然而,这个代码有点太简洁了……让我们把它分解成几个简单的小部分。

第一部分:迷你玩具神经网络

一个经过反向传播训练的神经网络会尝试用输入信息预测输出信息。

输入 输出
0 0 1 0
1 1 1 1
1 0 1 1
0 1 1 0

我们要根据输入里的三栏数据预测输出结果。这个问题的简单解决办法是分析输入和输出值之间的统计关系。如果我们采用这一方法,我们会发现最左边一栏的数据和结果有完美关联。反向传播,在它最简单的形态里,像我们所采用的方法一样分析统计关系并建模。让我们进行到下一步。

两层神经网络:

640.jpg

经过训练后的输出结果:

[[ 0.00966449]

 [ 0.00786506]

 [ 0.99358898]

 [ 0.99211957]]

变量 定义
X 输入的矩阵数据,每一行都是训练案例
y 输出的矩阵数据,每一行都是训练案例
l0 神经网络第一层,由输入的数据赋值
l1 神经网络第二层,也称为隐藏层
syn0 权重的第一层,全称是Synapse 0,链接l0层和l1层网络
* 元素间的乘法,使相同维度的两个向量间相对应的值一对一地相乘,得到相同维度的最终向量
元素间的减法,使相同维度的两个向量间相对应的值一对一地相减,得到相同维度的最终向量
x.dot(y) 如果x和y是向量,这个变量就是一个点。如果两者都是矩阵,那它就代表矩阵-矩阵相乘。如果其中只有一个是矩阵,那么它就是矩阵和向量相乘

如你在“经过训练后的输出”中可见,这些代码运作正常!在我讲解过程之前,我建议你自己试试这段代码,对于它的运行有一个直观的感受。你应当能在ipython notebook里运行它(如果你别无选择也可以当作脚本运行,但我强烈建议用ipython notebook)。下面是几点要关注的重点:

在第一次迭代和最后一次迭代之后,比较l1的不同。

了解一下”nonlin”函数。就是它给我们输出了一个概率结果。

了解l1_error在迭代时如何改变的。

删除36行试试。秘密调料的关键就在此。

看看第39行。神经网络此前的所有准备都是为了运行这一行。

让我们分行讲解这段代码。

建议:在两个屏幕里把这篇博客打开,这样你可以边看代码边看下面的讲解。我写下面这段的时候就是这么做的。

Line 01:这一行导入numpy,是一个线性代数库。这是我们唯一需要的东西。

Line 04:这是我们的非线性部分。虽然它可以是其他数个函数,我们在这里把非线性部分映射至sigmoid函数。Sigmoid函数把任意值映射为0到1之间的数值。我们用它把数字转化成概率。它还有其他一些吸引人的属性可以用来训练神经网络。

640.png

Line 05:注意,这行代码可以生成sigmoid的导数(当deriv=True的情况下)。sigmoid函数其中一个非常有用的特性就是输出可以用于计算导数。如果一个sigmoid函数的输出是”out”,那么它的导数就是out*(1-out)。非常高效。

如果你对导数不熟悉,可以把它当成sigmoid函数某个给定点的斜率(如你可以在上图所见,不同的点有不同的斜率)。如果你想更多地了解导数,可以去可汗学院看看在线课程。

Line 10:这行代码将我们输入的数据初始化为numpy矩阵。每一行都是一个独立的“训练案例”。每一列代表一个输入节点。这样,神经网络就有了3个输入节点和4个训练案例。

Line 16:这行代码初始化我们的输出数据。在这个例子里,我把数据用水平的方式呈现(一行四列)。”.T”是一个转换函数。在转换后,y矩阵变成四行一列。跟我们的输出结果一样,每一行是一个训练案例,每一列(也就是唯一的那一列)是输出节点。所以,我们的神经网络有3个输入和1个输出。

Line 20:用seed函数给出随机数是一个很好的训练。你的函数仍然是随机分布的,但每一次训练中函数都是以同样的方式分布。这可以让你更容易地看出你对神经网络造成的改变。

Line 23:这是我们给神经网络的权重矩阵。名字”syn0”代表着”synapse zero”(突触)。我们的神经网络只有两层(输入和输出),所以我们只需要一个权重函数来连接它们。它的维度是(3,1),因为我们给出3个输入值,获得一个输出值。另外一个理解它的方法是:l0的大小是3,而l1的大小是1。我们要把l0的每一个节点连接到l1的每一个节点上,所以需要一个(3,1)的矩阵。

此外还要注意它随机值的平均值是0。解释这个需要用到权重初始化的一些理论知识。目前我们只需要知道在权重初始化的时候把均值设成0是很好的点子。

另外一个注意的地方是所谓的“神经网络”其实只是这个矩阵。我们有“两层网络”分别是l0和l1,但它们都是数据集里面的临时数据。我们并不储存它们。所有的学习结果都储存在syn0矩阵里。

Line 25:这里开始我们的神经网络训练代码。for循环多次迭代训练代码使我们的神经网络对于数据集产生最优的输出。

Line 28:因为我们的第一层网络l0仅是我们的输入数据。在此我们准确地描述它一下。X含有4个训练案例(4行)。我们在这里将把这些数据都一起处理。这样的做法称为批量处理训练(full batch training)。我们有4行不同的l0数据,但你也可以把这所有的四行当成一个训练案例。在这里两种方法没有差别。(如果需要,我们可以在这里加载1000或者10000行数据而不需要改变代码。)

Line 29:这是我们的预测步骤。简单说,我们让神经网络“尝试”地根据输入数据给出输出数据。然后我们分析它的结果,在迭代的时候做出调整,使每次都做得更好一些。

这一行代码包含两个步骤。第一个矩阵用l0乘以syn0。第二步将输出结果用sigmoid函数处理。想一下下面每个矩阵的维度:

(4 x 3) dot (3 x 1) = (4 x 1)

矩阵的相乘是有序的,等式中间的矩阵的维度必须保持一致。最终得出的矩阵维度来自于第一个矩阵的行数和第二个矩阵的列数。

由于我们加载了4个训练案例,最终我们得出4个猜测的答案,一个(4×1)的矩阵。每个输出值都代表神经网络对于一个给定输入值的猜测。这样也许可以更直观地了解为什么我们可以给训练案例“载入”一个随机的数值。矩阵相乘仍然可以进行计算。

Line 32:现在,l1已经对每个输入给出了一个“猜测”。那么我们就可以把正确答案从y中提取出来和l1的猜测数据进行对比,看看神经网络做得如何。l1_error是一个正数或负数的向量,反映预测值和真实值的差距。

Line 36:我们要进入最棒的部分了!这是这段代码的“神秘调料”!在这一行里有很多的信息,所以我们把它分为两个部分讲解。

第一部分:变量

640.png


如果l1代表下图中的三个点,那上面这行代码会给出下图中几条直线的斜率。注意对于非常大的数值(绿点 x=2)和非常小的数值(紫点 x= -1)有比较缓的斜率。斜率最大的点是蓝色 x=0处。这个变量至关重要。另外请注意所有的导数都在0和1之间。

640.jpg


整段代码:误差加权导数

640.png

数学上有更多更精确的表述方法,但我认为误差加权导数这个说法最为直观。l1_error是一个(4,1)的矩阵。nonlin(l1,True)返回一个(4,1)的矩阵。我们将两个矩阵逐元素相乘,结果储存在l1_delta的(4,1)矩阵中。

把“斜率”和误差相乘,是在减少高可信度的预测误差。我们再看一下sigmoid图。如果斜率非常缓(趋近于0),那么神经网络的值要么非常大,要么非常小。这说明神经网络对这个结果非常信任。相对地,如果神经网络猜测值介于(0,0.5)的点,那么说明它不是那么地确定。我们会更加着重于处理这些不太确定的预测,把非常可信的预测乘以一个接近0的数来忽略掉它们。

Line 39:我们现在已经准备好更新神经网络了!我们看看单一训练的例子。

640.jpg

在这一个训练案例里,我们已经准备好更新所有的权重。我们现在把最左边的权重更新为9.5。

weight_update = input_value * l1_delta

对于最左边的权重,上式等于1.0*l1_delta。显然这对于9.5的增加非常微小。为什么我们只用这么小的一个数值?当预测结果的置信度已经非常高,预测结果很大程度上是正确的。一个很小的误差和一个很小的斜率意味着一个非常非常小的更正。考虑到所有的权重,这对于图中三个量都是很小很小的增加。

640.jpg

然而,由于我们采用的是批量处理结构,上述的处理是对于全部四个训练案例的。所以上图更符合实际的情况。那么,第39行做了什么呢?它为每一个训练案例的每一个权重计算出了改变量,加和,并更新所有权重。所有的计算都在这一行里完成。了解一下矩阵乘法然后你就知道这一行的意思了!

重点总结:

我们已经知道了神经网络如何自动更新,我们再回到之前给的训练数据思考一下。当输入和输出数据都是1的时候,我们增加它们之间的权重。当输入是1而输出是0的时候,我们减少它们之间的权重。

输入 输出
0 0 1 0
1 1 1 1
1 0 1 1
0 1 1 0

因此,在我们的4个训练案例里面,第一个输入到输出的权重将单调递增或保持不变,另外两个权重值将在所有的训练案例中一起增加或减少。这个现象就是使神经网络根据我们的输入输出学习的原因。

第二部分:略微困难一点的问题

输入 输出
0 0 1 0
0 1 1 1
1 0 1 1
1 1 1 0

试着根据上表输入的数据预测输出值。最关键的一个细节是任何一列和输出值都没有关联。每一列都有50%的几率为1,50%的几率为0。

那么,规律是什么呢?看上去第三列是完全不相关的:它总是1。然而,第一列和第二列给出了更多的信息。如果第一列或者第二列其中一个是1(但并非两个都是1),那么结果就是1。这就是此例的规律。

这被称为非线性规律,输入和输出没有一一对应的关系。取而代之的是一个输入组合和输出之间有着一一对应的关系,例如第一列和第二列。

640.jpg

640.jpg

可能你不会相信,图形识别就是这样一个类似的问题。如果某人有100张大小相同的烟斗和自行车的照片,任意一个像素能直接关联到自行车或者烟斗。然而,某个(非随机的)像素的集合能够形成一辆自行车或者一个人的照片。

策略

为了把像素形成集合,变得和输出具有一一对应的关系,我们需要再添加一层网络。第一层网络会把输入集合起来,第二层将会在第一层的输出和最终的输出建立映射关系。在我们进入实施阶段之前,先看看下面这个表格。

输入(l0) 隐藏权重(l1) 输出(l2)
0 0 1 0.1 0.2 0.5 0.2 0
0 1 1 0.2 0.6 0.7 0.1 1
1 0 1 0.3 0.2 0.3 0.9 1
1 1 1 0.2 0.1 0.3 0.8 0

如果权重的初始值是随机赋予的,我们会得到第一层的隐藏状态值。注意到了吗?第二列(第二个隐藏节点)已经和输出有了些许的关联性!虽然不完美,但是已经初现端倪了。这是神经网络训练的一个重大部分!(按理说,这也是神经网络训练的唯一方式。)接下来的训练要做的事情就是强化这些关联性。一方面更新syn1使它和输出建立映射,另一方面更新syn0使输入值能产生更好的数据。

注:通过增加更多层去为更多关联性建立模型的领域被称为“深度学习”,这个名字源于模型中不断加深的各层。

三层神经网络

640.png

640.png

 

误差:0.496410031903

误差:0.00858452565325

误差:0.00578945986251

误差:0.00462917677677

误差:0.00395876528027

误差:0.00351012256786

变量 定义
X 输入的矩阵数据,每一行都是训练案例
y 输出的矩阵数据,每一行都是训练案例
l0 神经网络第一层,由输入的数据赋值
l1 神经网络第二层,也称为隐藏层
l2 神经网络的最后一层,是我们假设的内容,并随着训练进行,能够越来越接近正确答案
syn0 权重的第一层,全称是Synapse 0,连接l0层和l1层网络
syn1 权重的第二层,全称是Synapse 1,连接l1和l2层
l2_error 神经网络预测和真实值的误差
l2_delta 这是经过置信度缩放后的误差。它和误差差不多大,除非在置信度很高的情况下将其忽略
l1_error 根据syn1权重衡量l2_delta的权重,我们可以计算中间层/隐藏层的误差
l1_delta 这是经过置信度缩放后的l1的误差。同样,它和l1_error差不多大,除非在置信度很高的情况下将其忽略

建议:在两个屏幕里把这篇博客打开,这样你可以边看代码边看下面的讲解。我写下面这段的时候就是这么做的。

一切看上去都那么熟悉!实际上它只是两个之前的例子的叠加。第一层(l1)的输出是第二层(l2)的输入。唯一新增的东西是第43行。

Line 43:使用l2的“置信度加权误差”建立l1的误差。只需要把误差通过权重从l2传给l1。这给出所谓的“贡献加权误差”,因为我们知道了l1每一个节点的值“贡献”了多少l2的误差。这一步骤被称为“反向传播”,和算法同名。接下来使用2层神经网络里相同的步骤更新syn0。

第三部分:结论和未来展望

个人建议

如果你想认真地研究神经网络,那么我有一条建议。试着通过记忆重写出这个神经网络。我知道这听起来可能有点疯狂,但它真的有效。如果你希望根据新的学术文章建立相应架构,或者阅读并理解各种架构的范例代码,我所说的方法绝对是杀手级的练习。即使你使用Torch、Caffe或者Theano等框架,这个方法也一样有用。我在神经网络方面工作了几年后才开始采用这个练习,不过这是我在该领域里回报最高的时间投入(而且它并不会花太多时间)。

未来展望

这个玩具案例还需要添加很多东西使它变成先进的构造。如果你希望改良你的神经网络,下面是一些你可以尝试了解的东西。(也许我会就这些内容写一些后续文章。)

• Alpha

• Bias Units偏置单元

• Mini-Batches迷你批次

• Delta Trimming 增量微调

• Parameterized Layer Sizes参数化层尺寸

• Regularization规范化

• Dropout退出

• Momentum动量

• Batch Normalization 批量标准化

• GPU Compatability图形处理器兼容性

• Other Awesomeness You Implement其他你觉得碉堡的内容

英文原文:http://iamtrask.github.io/2015/07/12/basic-python-network/
译者:shambala

 

 

 


评论

还没有任何评论,你来说两句吧

发表评论

浙ICP备16008686 -
善始者实繁,克终者盖寡