news 2026/6/15 10:04:54

11行Python就干翻神经网络?BP算法牛到让你头皮发麻

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
11行Python就干翻神经网络?BP算法牛到让你头皮发麻

瞅瞅, 今儿看一跟神经网络相干的文章, 作者仅用十一行代码就达成了一个神经网络, 原文地址是: A in 11 lines of (Part 1), 那叫一个佩服得五体投地, 翻译, 如下这般。

1986年, 由和为首的科学家小组提出了BP(Back)神经网络, 它是一种多层前馈网络, 是按照误差逆传播算法训练的, 是目前应用最为广泛的神经网络模型当中的一个。BP网络能够学习以及存贮好多的输入-输出模式映射关系, 并不需要在事前揭示描述这种映射关系的数学方程。它的学习规则是运用梯度下降法, 借助反向传播来持续调整网络的权值以及阈值, 从而让网络的误差平方和达到最小。BP神经网络模型的拓扑结构, 涵盖输入层, 也就是input, 还包含隐层, 即layer, 并且有输出层, 同样是layer。

主旨要义: 直接着手进行代码操作乃是最为上乘的学习方法。此篇教程借助运用语言去达成一段极为简易的示例代码, 以此来阐释back(BP反向传播算法)算法。

直接上代码:

X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ]) y = np.array([[0,1,1,0]]).T syn0 = 2*np.random.random((3,4)) - 1 syn1 = 2*np.random.random((4,1)) - 1 for j in xrange(60000): l1 = 1/(1+np.exp(-(np.dot(X,syn0)))) l2 = 1/(1+np.exp(-(np.dot(l1,syn1)))) l2_delta = (y - l2)*(l2*(1-l2)) l1_delta = l2_delta.dot(syn1.T) * (l1 * (1-l1)) syn1 += l1.T.dot(l2_delta) syn0 += X.T.dot(l1_delta)

只用11行代码便达成了个神经网络…于此态势之际呐, 这同样是个极为简洁的神经网络。接下来我会依照各个步骤去讲解此神经网络的实现情形, 以此助力读者去知晓并且运用BP反向传播算法。

第一部分: 简洁的神经网络

神经网络使用反向传播算法训练输入数据来预测输出数据。

察看上面的表格, 思索怎样运用三个维度的输入数据去推测一个维度的输出。我们能够借助一个简便方法来处理这个难题: 计量统计一下输入值跟输出值之间的关联。运用这个办法, 我们能够发觉左边的输入数据跟右边的输出数据是紧密相连的。从直观意义来讲, BP反向传播算法就是经由这种方式来权衡数据间的统计关系进而获取模型的。下面着手开始实践。

两层的神经网络

import numpy as np # sigmoid function def nonlin(x,deriv=False): if(deriv==True): return x*(1-x) return 1/(1+np.exp(-x)) # input dataset X = np.array([ [0,0,1], [0,1,1], [1,0,1], [1,1,1] ]) # output dataset y = np.array([[0,0,1,1]]).T # seed random numbers to make calculation # deterministic (just a good practice) np.random.seed(1) # initialize weights randomly with mean 0 syn0 = 2*np.random.random((3,1)) - 1 for iter in xrange(10000): # forward propagation l0 = X l1 = nonlin(np.dot(l0,syn0)) # how much did we miss? l1_error = y - l1 # multiply how much we missed by the # slope of the sigmoid at the values in l1 l1_delta = l1_error * nonlin(l1,True) # update weights syn0 += np.dot(l0.T,l1_delta) print "Output After Training:" print l1

输出结果:

Output After Training: [[ 0.00966449] [ 0.00786506] [ 0.99358898] [ 0.99211957]]

变量定义

输入数据集,形式为矩阵,每 1 行代表 1 个训练样本。

输出数据集,形式为矩阵,每 1 行代表 1 个训练样本。

l0

网络第 1 层,即网络输入层。

l1

网络第 2 层,常称作隐藏层。

syn0

第一层权值,突触 0 ,连接 l0 层与 l1 层。

元素进行相乘操作, 从而使得两个具有相同长度的向量相乘, 这等同于让它们相对应的元素分别进行相乘, 最终所得到的结果是一个具有相同长度的向量。

元素进行相减操作, 所以两个具有相同长度的向量进行相减, 这等同于它们对应的对等元素各自分别进行相减, 最终得到的结果是一个与它们长度相同的向量。

x.dot(y)

要是x和y是向量, 那就开展点积操作;倘若是矩阵, 那就开展矩阵相乘操作;要是其中有是向量有一个是基质, 那就开展向量与矩阵相乘操作。

在“After”那儿能够看到, 运行成功了!在我对运行过程展开描述以前, 建议读者先自行运行一回, 从而拥有一个对于代码运行的直观感受。最好能在当中完整地将代码跑通(自己去写脚本也可以, 不过强烈建议这么做)。下面是理解代码的几个关键要点:

下面,我们一行一行地来分析代码,理解其原理。

提供这样的建议, 将这篇篇文章借助两个屏幕展开来打开, 如此一来便能够在阅读代码的同时, 去阅读文章, 在撰写博客期间于当时我正是按照这样的方式去做的, :)。

进行第1步, 导入线性代数工具库numpy, 它是我们唯一依赖的库。

第4行: 这儿是”非线性”部分, 它能是好多不一样的函数, 然而在这儿, 所用到的非线性映射是一个叫 “” 的函数, 也就是(S(x)=1/(1+e-x)), 该函数能够把随便一个数映射到0至1之间, 我们借助它把数值转变成概率, 对于神经网络的训练而言, 此函数还有其他好几个挺不错的特性标点符号。

第 5 行: 留意, 当 deriv 等于 true 之时, 此函数还能够返回函数的倒数, 函数的出色特性之中的一个就是仅仅凭借它的输出值便能够得出它的导数, 要是函数的输出值是 out, 那么其导数值是 out 乘以(1 将 out 减掉), 效率相当高。

要是并非对导数熟知, 能够把导数认定为函数处于一个给定的点那儿的斜率, 不同的点存在不一样的斜率, 想要知道更多有关导数的知识, 能够参考可汗学院的导数求解进程。

第10行, 这行代码把我们要输入的数据集初始化成numpy的矩阵这样的状态。在该矩阵里, 每一行都是单独的一个“训练实例”。并且, 每一列都和一个输入节点相对应着。所以, 我们的神经网络存在3个输入节点以及拥有4个训练实例。

第16行, 这一行对我们的输出数据集进行初始化, 于本例而言, 出于节省空间的目的, 我是以水平格式来定义数据集的(呈1行和4列的状态), ”.T” 所代表的是转置函数, 在经过转置操作后, y矩阵变为拥有4行和1列, 如同输入那般, 每一行均为一个训练实例, 而且每一列都是一个输出节点, 所以, 我们的神经网络具备3个输入以及1个输出。

为随机数设定随机种子, 这当属一个不错的习惯。经过此举, 我们所获取的初始权重集依旧呈现出随机分布的状态, 然而, 在每次开启训练之际,所得到的权重初始集分布却是全然一致的。如此情形, 有助于我们去观察策略变动究竟是怎样对神经网络的训练产生影响的。

第23行: 这是神经网络的权重矩阵, 可以把syn0输出出来进行观察, syn0等于, 它与输入节点数是保持一致的。 syn0是” zero”的简称, 是说从零层突出这个方向来讲的。 因为我们就只有2层, 也就是输入层和输出层, 所以我们仅仅只需要一个权重矩阵来连接它们就行。 权重矩阵的维度是(3,1), 具体原因在于我们有3个输入节点以及1个输出节点。换个解释途径, 由于l0层层面的幅度是3, 而l1层大小界定为1, 鉴于此, 要是我们打算让l0与l1产生连接关系, 那就必然需要一个维度规格为(3,1)的处于中间位置的矩阵。

与此同时, 需留意随机进行初始化操作的权重矩阵其均值是为0的。对于权重初始化这个事情, 这里面存在着诸多的学问。鉴于目前我们仅仅是处于练习的阶段, 所以在对权值开展初始化动作的时候设定均值为0便行了。

所谓“神经网络”其所指的便是这一矩阵, 这是另一个需要加以留意的要点。虽说存在l0层以及l1层的情况, 然而它们俱皆是由基础数据集引申出的瞬时变幻数值罢了, 对此我们并无予以存储的必要。于学习训练进程当中, 仅仅存储syn0。

第25行, 本行开始即为实际的神经网络代码。对于循环是以迭代的方式多次轮回执行训练代码, 借此让我们的网络能够更优良地将训练集拟合。

网络的最开始在第28行的那所谓的第一层: 名为l0的, 它成为我们输入数据的所在处。接下来要进行关于它的详实阐述。那儿摆列着含有4个训练成分的输入集合X。于此刻, 我们会一并对所有这些成分展开处理。这样被实践运用的训练模式被叫做“整批”训练。这样以来, 哪怕我们存在着4个彼此具有差异的l0行的呈现状态, 但是, 你完全能够就那整个的情况把它归作是单独的某一个且具有训练性质的实例, 如此的做法并不会产生什么不同的结果。(瞧, 我们能够在不去变动代码架构的这个条件范围以内), 而且呢是一次性地装入进1000个甚至是10000个这样的实例成分。

在第29行, 存在着神经网络的预测流程。最初, 使得这个网络依据输入去试着推测输出。而后, 凭借对预测结果的考究做出某些调整, 以便神经网络于下一轮迭代里展现得更为出色一些。

这一行, 事实上涵盖着两个步骤, 其一乃是 l0 矩阵同 syn0 矩阵展开乘法运算, 其二是促使我们的输出融入函数作进一步处置, 并一并观察于矩阵做乘法运算之际那维度变换运作流程和进程:。

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

矩阵相乘存在着约束条件, 并且, 比如说等式靠中间位置的那两个维度一定得保持一致。最终所生成的矩阵是这样的, 它的行数是第一个矩阵的行数, 它的列数是第二个矩阵的列数。

之所以最终得到了 4 个猜测结果(也就是所得即为一个被明确指出是(4 x 1)的矩阵), 那是因为装进去了 4 个训练实例。每一个输出, 针对给定输入时, 网络对正确结果所做的一个猜测与之对应。这还能从直观层面进行解析表明: 为何能够“载入”任意数量的训练实例。要是处于这种情形呢, 矩阵乘法照样效果良好。 :)。

第32行: 所以, 针对每一输入, l1都会有与之对应的一个“猜测”结果。接着把真实结果y与猜测结果l1相减, 如此便能对比得出网络预测的效果究竟如何。其是一个由正数和负数共同组成的向量, 此向量能够反映出网络误差到底有多大。

第36行, 干货抵达了!此处便是秘密武器所处之地!本行代码所包含的信息量相对较大, 因而要把它拆分成两个部分去进行分析。

求导

nonlin(l1,True)

要是, l1能够被表示成3个点, 呈现出如下的图示模样, 那么, 以上所写的代码, 便能够产生如下图示的三条斜线。需要留意的是, 在x等于2.0的地方, 也就是那个绿色的点, 当输出值很大的时候, 以及在x等于 -1.0的位置, 也就是那个紫色的点, 当输出值很小的时候, 斜线都会显得非常特别地平缓。正如你能够看到的那样, 具有最高斜度的那个点处于x等于0的地方, 也就是那个蓝色的点位置。这一具体特性是非常关键重要的。另外, 还能够发现, 所有的导数值都处于0到1的范围之内。

整体认知:误差项加权导数值

l1_delta = l1_error * nonlin(l1,True)

关于“误差项加权导数值”这一术语, 在数学范畴内存在更为严谨的表述, 然而我认为该定义精确地领悟到了算法的主旨。它是(4,1)规模大小的矩阵, 当输入为(l1,True)时返回的即为(4,1)规模的矩阵。而我们所施行的操作是将其一五一十地进行元素相乘, 所获取的是(4,1)规模大小的矩阵, 此矩阵其中每一个元素皆是元素相乘之后的结果。

当把“斜率”与误差相乘之际, 事实上是以高概率使预测误差变小。且翻开函数曲线图示观瞧!只要斜率极度平缓(趋近于0之际), 网络输出不是值极大的一个数便是值极小的一个数耶。这也就是说神经网络碰到此刻极端确定是否为这般情形。可是假定网络判定结果出没于(x等于0.5, y等于0.5)周边之时, 它也就没那么笃定。对于这样一种有着“似是而非”状况的预测情形, 我们针对它进行最大程度的调整, 然而对于那种确定无疑的情形却不会过多去做处理, 给其乘上一个无限接近于0的数, 如此一来, 与之相对应的调整量便能够忽略不计了。

在第39行的位置, 网络已被更新且处于准备就绪的状态!接下来我们一同去看一下紧接着排列在后面的一个相对较为简单的训练示例。

在这一回的训练示例当中, 我们业已为那个权值更新做好了所有的准备工作。接下来, 让我们着手去更新位于最左边的那个权值, 其数值为9.5。

权重更新量 = 输入值 *

对于处于最左边位置的权值, 在上面式子里头, 那就是1.0乘上的值。能够想象得出, 这针对权值9.5的增量, 是能够忽略不计的。为何仅有如此小的更新量呢? 原因在于我们对于预测得出的结果是十分确信的, 并且预测结果有着很大把握会是正确的。当误差以及斜率都偏向于小时, 那就意味着会有一个较小的更新量。把所有的连接权值都考虑进去, 这三个权值的增量都是极为小的。

因为采用的是“整批”训练的机制, 所以上述更新步骤是在全部的4个训练实例上开展的, 这看起来也有点类似图像。那么, 第39行做了啥事情呢? 在这简洁的一行代码里, 它一共完成了如下几个操作行为: 首先计算每一个训练实例中每一个权值对应的权值更新量, 接着把每个权值的所有更新量予以累加, 随后更新这些权值。亲自去推导下这个矩阵相乘操作, 你就能明白它是怎样达成如此这般的。

延伸

时下, 我们目前已然清楚神经网络是怎样开展实施更新的了。回过身转过来去瞧瞧训练数据, 去做一些深度的思索考量。当输入以及输出都为 1 的时候, 我们加大它们之间的连接权重;当输入是 1 然而输出为 0 的时候, 我们降低其连接权重。

于是, 在像上方表明的4个训练示型里, 首个输入结点跟输出节点之间的权值会持续加大亦或是维持不变, 然而另外两个权值于训练进程里呈现为同时加大或者同比减小, 即忽略其间的进程。这样的现象致使网络能够依据输入和输出之间的关联进而展开学习。

第二部分:一个稍显复杂的问题

试着在给定了前两列输入的情形下去展开预测输出列, 重点在于这两列跟输出之间不存在丝毫关联, 每一列都有着百分之五十的可能性预测结果是1, 同样有着百分之五十的可能性预测为0。

那此刻现在的输出形式模样是怎样的呀呢? 看着看上去似乎瞧上去好像与第三列一点儿丝毫毫无关联关系, 它的其值一直始终都为1。然而但是呢第1列和第2列却能够予以提供给出更多的信息资讯说明: 当上述其中1列的值为1(可是并非但不是不同时都为1)的时候时刻, 输出就便会为1。这就是那才是我们所要找寻寻找的模式样式!

以上能够被视作是一种“非线性”模式, 这是由于单个输入跟输出之间并不存在一个一对一的关系, 然而输入的组合和输出之间存在着一对一的关系, 在此处也就是第1列与第2列的组合。

图像识别同样是类似的那种问题, 要是存在 100 张尺寸一样的烟斗图片以及脚踏车图片, 那么并未存在任何能够直接表明某张图片究竟是脚踏车还是烟斗的单个像素点位置, 单纯从统计的角度去看, 这些像素有可能也是呈随机分布状态的。然而, 某些像素的组合并非是随机的, 也就是说, 恰恰是这种组合才塑造形成了一辆脚踏车或者是一个人。

我们的策略

如上述所表明的那样, 像素组合之后所产生的事物跟输出之间具备着一对一的关联关系。倘若想要将这种组合率先达成, 那么我们在网络层面上就得额外增添一个层级。最初的那一层对进入的事物予以组合操作, 接着便再顺次进行一次转变, 也就是先把最初获取的那种组合结果当作进入的事物般, 透过第二层的映射方式, 从而获取到最终的输出成果。在给出具体的达成方式之前, 我们先来瞧一瞧这张表格。

通过随机初始化权重, 我们获取到了第1层的隐态值 , 你是否注意到 , 第二列也就是第二个隐层结点 , 已经与输出具备了一定的相关度 , 虽说并非十分完美 , 但确实已然存在 , 在神经网络训练里 , 寻找这种相关性占据了很大比重 , 甚至能够认定 , 这也是训练神经网络的唯一途径 , 在随后的训练过程中 , 我们所要做的就是把这种关联进一步予以增大。对于syn1权值矩阵而言, 它会把隐层的组合输出映射至最终结果, 然而, 在对syn1进行更新这个行为发生的同一时刻, 还存在着需要对syn0权值矩阵予以更新这一事, 以此能够从输入数据里更优地生成那些组合。

把更多中间层添加进来作用是实现对那更多关系组合的建模, 这一策略就是号称深度学习让大家都知道的, 因为它建模采取的方式正好就是持续增加更深层次的网络层。

3层神经网络

import numpy as np def nonlin(x,deriv=False): if(deriv==True): return x*(1-x) return 1/(1+np.exp(-x)) X = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]]) y = np.array([[0], [1], [1], [0]]) np.random.seed(1) # randomly initialize our weights with mean 0 syn0 = 2*np.random.random((3,4)) - 1 syn1 = 2*np.random.random((4,1)) - 1 for j in xrange(60000): # Feed forward through layers 0, 1, and 2 l0 = X l1 = nonlin(np.dot(l0,syn0)) l2 = nonlin(np.dot(l1,syn1)) # how much did we miss the target value? l2_error = y - l2 if (j% 10000) == 0: print "Error:" + str(np.mean(np.abs(l2_error))) # in what direction is the target value? # were we really sure? if so, don't change too much. l2_delta = l2_error*nonlin(l2,deriv=True) # how much did each l1 value contribute to the l2 error (according to the weights)? l1_error = l2_delta.dot(syn1.T) # in what direction is the target l1? # were we really sure? if so, don't change too much. l1_delta = l1_error * nonlin(l1,deriv=True) syn1 += l1.T.dot(l2_delta) syn0 += l0.T.dot(l1_delta)

一切看上去都极为眼熟!这不过是借由这般两个之前的达成相互摞叠造就的, 第一层也即l1之输出会成为第二层的输入。唯一显现的新秀事即为第43行代码。

第43行: 构建l1层相应的误差, 是通过对l2层的误差进行“置信度加权”来达成的。要做到这点, 只需简单地经由l2与l1间的连接权值去传递误差。将这种做法称作“贡献度加权误差”,是因为我们所学习的内容是, l1层每一个结点的输出值对l2层节点误差的贡献程度究竟有多大。然后, 对syn0权值矩阵进行更新, 采用的是在之前2层神经网络实现中同样的步骤。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 10:01:02

遗传算法工程化实践:从跑不通到稳定产出的自适应调优指南

1. 这不是又一篇“遗传算法入门”——它解决的是你写完代码却跑不出结果的真问题“遗传算法入门”这个词,我见过太多次了。三年前在某高校做算法工作坊时,一位研二同学举手说:“老师,我照着教程把选择、交叉、变异全写完了&#x…

作者头像 李华
网站建设 2026/6/15 9:59:50

终极指南:如何使用XUnity.AutoTranslator让外文游戏瞬间变中文

终极指南:如何使用XUnity.AutoTranslator让外文游戏瞬间变中文 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 还在为看不懂日语、英语或其他语言游戏而烦恼吗?XUnity.AutoTransla…

作者头像 李华
网站建设 2026/6/15 9:48:00

别只当计算器用!WolframAlpha隐藏的5个高效学习与科研场景

WolframAlpha:超越计算器的科研学习智能引擎 当大多数人提起WolframAlpha时,第一反应往往是"那个能解复杂方程的计算工具"。但如果你也这样想,可能错过了它90%的价值。作为一款融合了 计算知识引擎 与 结构化数据库 的智能平台…

作者头像 李华
网站建设 2026/6/15 9:46:53

生产级机器学习模型服务:Triton部署与可观测性实战

1. 项目概述:这不是一次“部署”,而是一场从实验室到产线的系统性迁移“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题里藏着太多被日常讨论轻描淡写带过的重量。它不是教你怎么把model.save()那行代码跑通&#x…

作者头像 李华