初识人工神经网络(1)——基本原理

最近看了《Python 神经网络编程》一书之后,对于神经网络的基本原理有了一个初步的理解,于是产出此篇文章作为系统性的梳理和总结。

概述

计算机设计的初衷是为了解决大量的数学运算,因此适用于解决具有固定模式或计算步骤的问题。对于不具备固定模式或计算步骤的问题,比如图像识别、语音识别等,传统的计算机程序很难有效地予以解决。

人工智能的重要研究方向之一就是通过计算机来解决这类相对困难的问题。机器学习便是人工智能领域中的一个重要分支,而人工神经网络则是机器学习中一种被广泛使用的算法。

分类器

为了能够理解神经网络的核心思想,我们先来介绍一个分类器的例子。

假如花园中有两种虫子:毛虫细而长,瓢虫宽而短。我们希望设计一个分类器,当给定一个虫子的长度和宽度,分类器能够自动进行分类。

设计分析

我们考虑对虫子的长度和宽度进行分析,绘制一个二维坐标系,可以发现两种虫子是存在一定的聚类特征的,如下图所示。

因此,分类器的设计目标就是通过数据训练进行学习,从而找到一条分界线,将两种类型的虫子进行有效分类。如下所示,在训练阶段,分界线会不断地进行修正,最终到达一个相对正确的位置。

分类器最终结构如下所示,它有一个输入和一个输出,通过分类器实现内部分类逻辑,最终输出分类结果。

样本训练

那么分类器该如何通过数据训练来调整分界线的斜率呢?首先,我们需要定义一个用于表示分界线的函数(或称测试函数),如下所示。

\[\begin{aligned} y = Ax \end{aligned}\]

然后,我们随机初始化 \(A\) 的值,假设初始化值为 0.25,那么将得到如下所示的直线 \(y = 0.25x\)

接下来,我们开始输入训练样本,如下所示。当输入两个虫子的样本数据后,我们发现分界线并没有正确对虫子进行有效分类。此时,我们需要对斜率进行调整,这也是训练的核心目标。

我们观察第一个训练样本数据:宽度 3.0,长度 1.0,瓢虫。我们将 \(x = 3.0\) 代入函数 \(y = Ax\),得到 \(y = 0.25 * 3.0 = 0.75\)。然而,样本数据告诉我们 \(y = 1.0\),此时我们看到了误差的存在。值得注意的是,分界线是为了对实现分类,我们需要让 \(x\) 代入函数后得到的 \(y\) 值大于 \(1.0\)。为了避免调整过大,我们将 \(y\) 的目标值设置为 \(1.1\)

由此,我们计算输出值和目标值之间的误差,基于此计算出调整的斜率差值。下图显示了初始分界线和一次训练后的分界线。

\[\begin{aligned} 实际值: & y = Ax = 0.25 * 3.0 = 0.75 \\ 目标值: & t = (A + \Delta A)x = 1.1 \\ 误差值: & E = t - y = (\Delta A)x = 1.1 - 0.75 = 0.35 \\ 斜率差值: & \Delta A = E / x = 0.35 / 3.0 = 0.1167 \\ 斜率修正值: & (A + \Delta A) = 0.25 + 0.1167 = 0.3667 \end{aligned}\]

然后,我们再观察第二个训练样本数据:宽度 1.0,长度 3.0,毛虫。采用类似的方法,我们可以计算出调整后的斜率为 \(2.9\)。下图显示了初始分界线和两次训练后的分界线。

\[\begin{aligned} 实际值: & y = Ax = 0.3667 * 1.0 = 0.3667 \\ 目标值: & t = (A + \Delta A)x = 2.9 \\ 误差值: & E = t - y = (\Delta A)x = 2.9 - 0.3667 = 2.5333 \\ 斜率差值: & \Delta A = E / x = 2.5333 / 1.0 = 2.5333 \\ 斜率修正值: & (A + \Delta A) = 0.3667 + 2.5333 = 2.9 \end{aligned}\]

适度改进

如果我们仔细观测两次训练,会发现最终改进的直线与最后一次训练样本非常匹配。这种方式实际上抛弃了所有先前训练样本的学习结果,只对最后一次训练样本进行了学习。

那么如何解决这个问题呢?一个重要的思路就是 适度改进(Moderate)

我们可以在改进公式中增加一个调节系数 \(L\) ,也称为 学习率(Learning Rate),如下所示。

\[\begin{aligned} \Delta A = L (E / x) \end{aligned}\]

基于新的调整公式,设定学习率 \(L = 0.5\),我们再来计算一下斜率的改进过程。

\[\begin{aligned} 第一次训练: & \\ 实际值: & y = Ax = 0.25 * 3.0 = 0.75 \\ 目标值: & t = (A + \Delta A)x = 1.1 \\ 误差值: & E = t - y = (\Delta A)x = 1.1 - 0.75 = 0.35 \\ 斜率差值: & \Delta A = L (E / x) = 0.5 * 0.35 / 3.0 = 0.0583 \\ 斜率修正值: & (A + \Delta A) = 0.25 + 0.0583 = 0.3083 \\ \\ 第二次训练: & \\ 实际值: & y = Ax = 0.3083 * 1.0 = 0.3083 \\ 目标值: & t = (A + \Delta A)x = 2.9 \\ 误差值: & E = t - y = (\Delta A)x = 2.9 - 0.3083 = 2.5917 \\ 斜率差值: & \Delta A = L (E / x) = 0.5 * 2.5917 / 1.0 = 1.2958 \\ 斜率修正值: & (A + \Delta A) = 0.3083 + 1.2958 = 1.6042 \end{aligned}\]

分类器组合

上述,我们介绍了单一分类器通过训练样本进行学习调整相关参数,最终可用于解决特定问题。

然而,现实中很多问题并不是一个分类器能够解决的,比如:如何在网格节点中输出逻辑异或(XOR)的值?

此时,我们无论如何都无法通过一条分界线来正确进行分类。于是,我们开始考虑采用多个分类器进行组合,共同完成对复杂问题的求解,这就是神经网络的基本思想。

神经网络

人工神经网络(Artificial Neural Network,ANN,简称神经网络),其设计思想借鉴了动物大脑的生物神经网络,构建了一套类似神经元互连的分层组织结构。

神经元

无论是人工神经网路,还是生物神经网络,神经元都是其中的基本组成单元,两种神经元的结构也基本差不多。

生物神经元

如下图所示,生物神经元主要包含三部分:

  • 树突:用于接收外部电信号
  • 轴突:用于传导电信号
  • 突触:用于将电信号传递至其他神经元或细胞

此外,神经元还会通过阈值(threshold)抑制输入,直到电信号超出阈值,才会触发输入。因为神经元不希望传递各种微小的噪音信号,而只传递有意识的明显信号。

人工神经元

如下图所示,人工神经元主要包含三个部分:

  • 输入:类似于树突,可以包含一个或多个输入端,用于接收外部信号
  • 输出:类似于轴突和突触,用于传递和输出信号
  • 节点:类似于细胞核,用于处理信号。本质上是两个函数,分别是:
    • 求和函数:对所有输入进行求和
    • 激活函数:或称阈值函数,用于过滤噪音信号,类似于生物神经元中抑制噪音电信号。

对于激活函数,一个简单的阶跃函数即可实现类似的效果。但是为了更接近自然的效果,这里采用一种平滑且经典的 Sigmoid 函数(简称 S 函数)作为激活函数,其函数表达式如下所示。除此之外,神经网络中常用的激活函数还有很多,比如:双曲正切函数(Hyperbolic Tangent)、ReLU函数(Rectified Linear Unit)、Leaky ReLU函数、ELU函数(Exponential Linear Unit)、SELU(Scaled Exponential Linear Unit)、Softmax 等等,有兴趣的朋友可以自行了解。

\[\begin{aligned} y = \frac{1}{1+e^{-x}} \end{aligned}\]

基本结构

通过构建多层神经元,每一层中的神经元都与在其前后层中的所有神经元相互连接,即可得到一个分层的人工神经网络。

根据分层所在的位置,我们将分层分为三种类型:输入层、隐藏层、输出层。任意一个神经网络的输入层和输出层各自只有一个,隐藏层可以有多个。下图所示是一个三层结构的神经网络,可以看到每个节点都与前一层或后一层的其他每个节点相互连接。

基于训练样本,神经网络会进行学习,那么它有没有可以调整的参数呢?类似上文介绍的分类器,我们可以调整斜率参数。在神经网络中,则是 通过调整节点之间的连接强度作为训练样本的学习反馈

下图所示展示了节点之间各个连接的连接强度,使用 权重(Weight)表示,比如:\(w_{1,2}\) 表示当前层节点 1 与后一层节点 2 之间的连接强度。通常权重值的范围位于 \([0, 1]\) 之间,当权重值为 0 时,则表示连接断开。

信号转换

了解了神经网络的基本结构之后,我们来介绍一下输入信号是如何在神经网络中经过一层一层的神经元,最终转换成输出信号的。在这个过程中,我们会结合矩阵运算来进行表达。

下图所示是一个具有 3 个分层,每个分层 3 个节点的神经网络,为了保持图示清晰,我们没有标注所有权重。

下面,我们来依次看一下输入层、隐藏层、输出层对于信号的转换和处理。

输入层

在人工神经网络中,输入层的节点通常不会进行求和函数和激活函数的处理。输入层的节点主要负责接收原始的输入信号,并将其直接透传给下一层的隐藏层或输出层。

对于输入层,我们可以使用矩阵 \(I_{input}\) 来表示输入信号,使用矩阵 \(O_{input}\) 表示输出信号。由此得到如下所示表示:

\[\begin{aligned} O_{input} = I_{input} = \left( \begin{matrix} 0.9 \\ 0.1 \\ 0.8 \end{matrix} \right) \end{aligned}\]

隐藏层

对于隐藏层,我们可以使用矩阵 \(W_{input\_hidden}\)来表示输入层与隐藏层之间的连接权重,如下所示:

\[\begin{aligned} W_{input\_hidden} = \left( \begin{matrix} 0.9 & 0.3 & 0.4 \\ 0.2 & 0.8 & 0.2 \\ 0.1 & 0.5 & 0.6 \end{matrix} \right) \end{aligned}\]

隐藏层神经元节点接收输入信号后,会依次使用求和函数、激活函数进行处理,然后进行输出。关于求和函数,我们可以使用矩阵点乘来表示,这里使用 \(X_{hidden}\) 来表示隐藏层求和函数的计算结果。

\[\begin{aligned} X_{hidden} = & W_{input\_hidden} \cdot O_{input} \\ = & \left( \begin{matrix} 0.9 & 0.3 & 0.4 \\ 0.2 & 0.8 & 0.2 \\ 0.1 & 0.5 & 0.6 \end{matrix} \right) \cdot \left( \begin{matrix} 0.9 \\ 0.1 \\ 0.8 \end{matrix} \right) \\ = & \left( \begin{matrix} 1.16 \\ 0.42 \\ 0.62 \end{matrix} \right) \end{aligned}\]

关于激活函数,我们使用 \(sigmoid\) 来表示,隐藏层的最终输出仍然可以矩阵来表示,这里使用 \(O_{hidden}\) 表示隐藏层的最终输出。

\[\begin{aligned} O_{hidden} = & sigmoid \left( X_{hidden} \right) \\ = & sigmoid \left( \begin{matrix} 1.16 \\ 0.42 \\ 0.62 \end{matrix} \right) \\ = & \left( \begin{matrix} 0.761 \\ 0.603 \\ 0.650 \end{matrix} \right) \end{aligned}\]

由此我们得到隐藏层的输出信号,如下图所示。

输出层

输出层对于信号的转换和处理,本质上和隐藏层没有任何区别,计算的方法和流程是一样的。

我们使用 \(W_{hidden\_output}\) 表示隐藏层与输出层之间的连接权重,结合隐藏层的输出 \(O_{hidden}\),通过矩阵点乘来运用求和函数,得到 \(X_{output}\),如下所示。

\[\begin{aligned} X_{output} = & W_{hidden\_output} \cdot O_{hidden} \\ = & \left( \begin{matrix} 0.3 & 0.7 & 0.5 \\ 0.6 & 0.5 & 0.2 \\ 0.8 & 0.1 & 0.9 \end{matrix} \right) \cdot \left( \begin{matrix} 0.761 \\ 0.603 \\ 0.650 \end{matrix} \right) \\ = & \left( \begin{matrix} 0.975 \\ 0.888 \\ 1.254 \end{matrix} \right) \end{aligned}\]

最后再应用激活函数,得到输出层的结果 \(O_{output}\) ,如下所示。

\[\begin{aligned} O_{output} = & sigmoid \left( X_{output} \right) \\ = & sigmoid \left( \begin{matrix} 0.975 \\ 0.888 \\ 1.254 \end{matrix} \right) \\ = & \left( \begin{matrix} 0.726 \\ 0.708 \\ 0.778 \end{matrix} \right) \end{aligned}\]

由此我们得到输出层的输出信号,如下图所示。

训练反馈

在实际应用神经网络之前,我们必须使用大量训练样本对其进行训练,训练的核心目的是 调整各节点之间链接权重的值,使其调整为合适的值,从而让神经网络能够输出相对准确的结果。

值的约束

首先我们来看下神经网络中的值,其主要包含四种:目标值、输入值、输出值、权重值。这些值的范围与神经网络运行和训练密切相关,下面我们分别来了解一下。

输出值 & 目标值

输出值的范围与激活函数有关。下图所示是 Sigmoid 激活函数的曲线图,其输出值的范围为 \((0, 1)\)。训练样本的目标值的范围也应该与输出值的范围一样;否则,神经网络会驱使更大(或更小)的权重,导致学习能力过犹不足。

输入值

类似输出值,输入值范围也与激活函数有关。对于 sigmoid 函数,当输入值超出某个范围之后,输出值会变得非常平坦,换句话说就是梯度(斜率)差异很小,从而会导致学习能力降低。

很显然,梯度变化适中的区域更适合用于神经网络的训练,如下图所示,输入值的范围在 \([-1, 1]\) 之内的激活函数梯度变化适中。不过,我们要注意输入值为 0 的情况,此时结合任意权重值后,信号都会变成 0,权重更新表达式也会变成 0,从而导致学习能力丧失(后续权重更新中会详细进行介绍)。因此我们要避免输入值等于 0,一般建议将输入值的范围设置为 \((0, 1]\)

权重值

根据前面的介绍,我们知道权重值会影响求和函数的结果,并最终作为激活函数的输入。因此,初始权重值同样也会影响神经网络的学习能力,过大(或过小)的初始值会造成过大(或过小)的信号传递给激活函数,导致神经网络饱和,从而降低神经网络学习更好权重的能力。

如下所示,是我们认为 sigmoid 激活函数的梯度适中的区域。对此,我们也可以简单地将权重的初始值范围设置为 \([-1, 1]\),并随机均匀进行取值。

当然,我们还有更好的权重值初始化方案。对此,我们回顾一下求和函数的输入和输出。一个神经元节点的求和函数输出等于与之相连的前一层所有节点的输出的加权求和。因此我们应该根据输出值的范围逆向推导各个连接的权重值,很显然,这与每个节点的连接的节点数量有关。对此,数学家提出了一种基于经验法则的权重初始化方案,即 在一个节点传入连接数量平方根倒数的大致范围内随机采样,作为权重初始值。比如,每个节点具有 100 条输入连接,那么权重的范围应该在 \([-1/\sqrt{100}, 1/\sqrt{100}]\) 之间,即 \([-0.1, 0.1]\) 之间。

从直觉上讲,这种方案是有意义的。一个节点的输入连接越多,就会有越多的信号叠加在一起。因此,如果连接更多,那么减小权重的范围是有道理的。

当然,这种优化方案还定义了权重初始值应该遵循正态分布,下图总结了这种基于正态分布的权重初始化方案。

误差分割

神经网络是基于训练样本的目标值与实际运行的输出值之间的误差来进行反馈学习,从而调整各个连接的权重。然而,每个节点可能有多个输入连接,每个连接有各自的权重值。为了合理分配误差值,神经网络会根据连接的权重来进行分割误差。

误差传播

了解了误差分割后,我们再来看看神经网络是如何传播误差的。

输出层

我们先来看输出层的误差传播。下图展示了一个具有 2 个输入节点和 2 个输出节点的神经网络。

对于输出层节点 1,我们定义其实际输出值为 \(o_1\),目标输出值为 \(t_1\),那么由此可计算得出误差值 \(e_1 = (t_1 - o_1)\)。然后我们可以按照连接权重来分割误差,很显然,\(e_1\)\(e_2\) 的误差传播组成如下所示。

\[\begin{aligned} e_1 = e_1 \frac{w_{1,1}}{w_{1,1} + w_{2,1}} + e_1 \frac{w_{2,1}}{w_{1,1} + w_{2,1}} \\ \\ e_2 = e_2 \frac{w_{1,2}}{w_{1,2} + w_{2,2}} + e_2 \frac{w_{2,2}}{w_{1,2} + w_{2,2}} \end{aligned}\]

隐藏层

我们再来看隐藏层的误差传播。下图展示了一个包含输入层、隐藏层、输出层的 3 层神经网络。

对于隐藏层的各个节点,它们并没有所谓的目标值,因此无法直接计算其误差。对此,我们可以通过将输出层的误差进行反向传播,层层传递。

首先,我们根据输出层的误差传播可以计算等到输出层前置连接 \(w_{1,1}\)\(w_{1,2}\)\(w_{2,1}\)\(w_{2,2}\) 各自的误差分量,然后即可计算得到隐藏层节点的误差值 \(e_1\)\(e_2\)

由此方法继续计算,可以进一步得出隐层层前置连接各自的误差分量,如下所示。

矩阵运算

对于各个层的误差值的计算,我们同样可以使用矩阵来表示。我们使用 \(error_{output}\) 来表示输出层误差,使用 \(error_{hidden}\) 来表示隐藏层误差,其表示如下。

\[\begin{aligned} error_{output} = & \left( \begin{matrix} e_1 \\ e_2 \end{matrix} \right) \\ \\ error_{hidden} = & \left( \begin{matrix} \frac{w_{1,1}}{w_{1,1} + w_{2,1}} & \frac{w_{1,2}}{w_{1,2} + w_{2,2}} \\ \frac{w_{2,1}}{w_{2,1} + w_{1,1}} & \frac{w_{2,2}}{w_{2,2} + w_{1,2}} \end{matrix} \right) \cdot \left( \begin{matrix} e_1 \\ e_2 \end{matrix} \right) \end{aligned}\]

\(error_{hidden}\) 的矩阵定义比较复杂,有没有办法进行简化呢?我们观察到,最终要的是输出误差与链接权重 \(w_{i,j}\) 的乘法。更大的权重意味着更多的输出误差给隐藏层。这里的分母只是一种归一化因此,如果我们忽略这个因子,那么我们仅仅时区了误差的大小,但是能够换来更加简单的矩阵表示,如下所示。

\[\begin{aligned} error_{hidden} = & \left( \begin{matrix} w_{1,1} & w_{1,2} \\ w_{2,1} & w_{2,2} \end{matrix} \right) \cdot \left( \begin{matrix} e_1 \\ e_2 \end{matrix} \right) \end{aligned}\]

对比之前的权重矩阵 \(W_{hidden\_output}\),可以发现这里的矩阵其实就是 \(W_{hidden\_output}\) 的转置矩阵,因此我们可以将表示式进一步转换成如下所示。

\[\begin{aligned} error_{hidden} = W^T_{hidden\_output} \cdot error_{output} \end{aligned}\]

权重更新

了解了误差分割与传播后,我们再来看看神经网络是如何通过误差来对权重进行更新,从而达到学习的目的。

事实上,我们很容易就可以基于神经网络的正向信号转换过程,推导出反向权重更新过程。由于正向过程中,神经元对信号依次进行求和函数、激活函数的处理,并且层与层之间是相互依赖的。对于一个 3 x 3 的神经网络,输出层的某个节点的计算公式将非常复杂,如下所示。一旦神经玩网络的节点数量、层级数量增加,计算公式会更加复杂,基于此进行逆向的权重更新,将会变得极其复杂!

梯度下降法

为此,研究人员提出了一种 梯度下降(Gradient Descent) 的方法来绕过这个问题,解决这个问题。

我们来举一个例子说明一下说明是梯度下降法。想象一下,一个非常复杂、有山峰山谷的群山峻岭。在黑夜中,伸手不见五指。此时,你在某个山坡上,需要到坡底,手里只有一把手电筒,你该怎么做呢?你只能通过手电筒看到脚下的土地是上坡还是下坡,于是你就小步地往这个方向走。通过这种方式,不需要完整的底图,也不需要事先指定路线,缓慢地前进,慢慢地下山。这种方法,在数学上被称为梯度下降。

那么梯度下降法与神经网络有什么关系呢?其实,我们可以寻找一个误差函数 \(y = f(x)\)\(y\) 表示误差值,\(x\) 表示连接的权重值。对于神经网络的样本训练而言,本质上就是找到最小误差值所对应的权重值,从而更新权重,达到学习的目的。

为了正确理解梯度下降的思想,我们来使用一个简单的例子来演示一下。假设,误差函数为 \(y = (x-1)^2 + 1\)。我们希望找到 \(x\),从而最小化误差 \(y\)。那么我们可以通过判断斜率(也称梯度)的方式来寻找,当斜率为负时,\(x\) 可以尝试适当增大;当斜率为正时,\(x\) 可以尝试适当减小,通过这种方式逐步逼近,从而找到最小值,如下图所示。

为了提高训练学习的效率,我们可以动态调节步长。当梯度较大时,可以使用较大的步长,提高学习效率;当梯度较小时,可以使用较小的步长,避免调整过度。

实际情况下,神经网络中的误差函数并不一定那么简单,它可能会有多个局部最小值。为了避免终止于错误的函数最小值,我们可以使用不同的起点(权重初值值)来进行多次训练。如下图所示,我们使用梯度下降法进行了三次尝试,其中有一次终止于错误的最小值。

误差函数

神经网络具有非常多的节点和连接,那么我们该如何使用误差函数来描述这么多的连接权重并实现权重更新呢?这里我们针对每一个层都会使用一个误差函数来批量描述连接权重与误差的关系。

误差表示

由于每一个层都包含多个节点,我们需要对每个节点的误差进行聚合。此时需要考虑多个误差值的情况下如何表示总体误差,对此下面列出了针对 3 个输出节点的神经网络的几种误差表示方法。

方案一:误差 = 目标值 - 实际值。这种方案描述误差的方法非常直观,但是在处理多节点的误差聚合时,会出现总和为 0 的情况,这会导致神经网络无法得到很好的训练。

方案二:误差 = |目标值 - 实际值|。这种方案解决了多节点的误差聚合可能为 0 的问题。但是它的斜率(或称梯度),在最小值附近会出现跳变,从而导致梯度下降法无法很好地发挥作用。下图所示对应的函数及其导数。

方案三:误差 = (目标值 - 实际值)^2。这种方案即解决了多节点的误差聚合可能为 0 的问题,也解决了斜率跳变的问题。因此,我们采用这种方案作为误差的计算方式。

斜率推导

要使用梯度下降法,我们必须要计算出误差函数相对权重的斜率,这里涉及到微积分的知识。这里如果你对微积分不太熟悉也没关系,我们只要知道最后推导出来的公式即可。

下图所示分别展示了包含一个权重和两个权重的误差函数及其斜率的示意图。当误差函数中每增加一个权重变量时,函数就会增加一个维度。

下面,我们先来看隐藏层和输出层之间的连接权重。我们使用 \(w_{i,k}\) 表示隐藏层和输出层之间的连接权重,使用 \(n\) 表示输层节点的数量,然后对误差函数 \(E\) 进行展开,得到如下表示。

\[\begin{aligned} \frac{\partial E}{\partial w_{j,k}} = \frac{\partial \sum_n(t_n - o_n)^2}{\partial w_{j,k}} \end{aligned}\]

注意,在节点 \(n\) 的输出 \(o_n\) 只取决于链接到这个节点的连接。对于单个输出节点 \(k\),其只依赖于与它连接的节点及其权重。因此,我们可以进一步简化表达式,如下所示。

\[\begin{aligned} \frac{\partial E}{\partial w_{j,k}} = \frac{\partial (t_k - o_k)^2}{\partial w_{j,k}} \end{aligned}\]

为了继续推导,我们利用链式法则对斜率表达式进行拆分,进而推导,如下所示。

\[\begin{aligned} \frac{\partial E}{\partial w_{j,k}} = & \frac{\partial E}{\partial o_k} \cdot \frac{\partial o_k}{\partial w_{j,k}} \\ = & -2(t_k - o_k) \cdot \frac{\partial o_k}{\partial w_{j,k}} \\ \end{aligned}\]

输出层的输出 \(o_k\) 依赖隐藏层的输出 \(o_j\),然后经过输出层节点应用求和函数、激活函数,从而转换成 \(o_k\)。因此,我们可以继续推导。

\[\begin{aligned} \frac{\partial E}{\partial w_{j,k}} = & \frac{\partial E}{\partial o_k} \cdot \frac{\partial o_k}{\partial w_{j,k}} \\ = & -2(t_k - o_k) \cdot \frac{\partial o_k}{\partial w_{j,k}} \\ = & -2(t_k - o_k) \cdot \frac{\partial sigmoid(\sum_j w_{j,k} \cdot o_j)}{\partial w_{j, k}} \\ \end{aligned}\]

接下来涉及到微分 sigmoid 函数,这里我们直接使用数学家们已经推导出来的结果进行应用,如下所示。

\[\begin{aligned} \frac{\partial sigmoid(x)}{\partial x} = sigmoid(x) (1 - sigmoid(x)) \end{aligned}\]

将该结果代入上述斜率推导中,得到:

\[\begin{aligned} \frac{\partial E}{\partial w_{j,k}} = & -2(t_k - o_k) \cdot sigmoid(\sum_j w_{j,k} \cdot o_j) (1- sigmoid(\sum_j w_{j,k} \cdot o_j)) \cdot \frac{\partial \sum_j w_{j,k} \cdot o_j}{\partial w_{j, k}} \\ = & -2(t_k - o_k) \cdot sigmoid(\sum_j w_{j,k} \cdot o_j) (1- sigmoid(\sum_j w_{j,k} \cdot o_j)) \cdot o_j \end{aligned}\]

由于在梯度下降法中,我们只关注斜率的方向,所以可以进一步去掉系数 2,从而得到如下斜率表达式。

由于权重改变方向与梯度方向相反,结合学习率 \(\alpha\),我们可以得到权重更新前后的关系式,如下所示。

矩阵表示

为了推导矩阵表示,我们尝试使用矩阵乘法的形式进行计算,可以得到如下所示的表示。

我们可以发现矩阵表达式中的最后一部分,其实就是前一层 \(o_j\) 的输出的转置。最后,我们可以得到权重更新矩阵的矩阵表达式,如下所示。

总结

本文我们主要介绍了人工神经网络工作的基本原理,从而能够对它产生一个初步认知,有利于后续的进阶学习。

首先,我们以一个分类器结构介绍了机器学习的基本思想。在此基础上进行扩展,组合多个分类器,这也是人工神经网络的基本思想。

然后,我们介绍了神经网络正向的信号转换过程,其中涉及连接权重、求和函数、激活函数等。此外,我们还介绍了神经网络的训练的相关概念,包括:误差分割、误差传播、权重更新等。关于权重更新,我们重点介绍了误差函数及斜率的推导。

后续有时间我们将进一步介绍如何使用 Python 打造一个简单的人工神经网络。

参考

  1. 《Python神经网络编程》