人工神经网络 (Artificial Neural Networks, ANN):神经网络是由具有适应性的简单单元组成的广泛并行互联的网络,它的组织能够模拟生物神经系统对真实世界物体所做出的交互反应。

1 神经元模型

人工神经网络的最小单位是神经元,其结构模拟了生物的神经元,具体结构如下:

(来源:维基百科,使用 CC BY-SA 3.0 协议)

图中左侧 a1,a2,,an 代表了上一层神经元的输出,箭头上的数 w1,w2,,wn 代表上一层每一个神经元的连接权重,b 代表偏置,f() 代表激活函数,t 代表该神经元的输出。对于上面的符号,它们之间的关系是:

t=f(i=1nwiai+b)

写成向量形式:

t=f(wTx+b)

其中激活函数 f() 有很多选择,如:

函数名表达式取值范围
Sigmoidf(x)=11+exp(x)(0,1)
ReLUf(x)=max{0,x}[0,)
tanhf(x)=exp(x)exp(x)exp(x)+exp(x)(1,1)

2 网络结构

ANN 的结构种类有很多种,本轮讨论最经典的结构:多层前馈神经网络。它的结构特点是每层神经元与下一层神经元完全互联,神经元之间不存在同层连接,也不存在跨层连接。它包含三个层次,分别为输入层、隐藏层、输出层,输入层神经元接受外界输入,隐层和输出层神经元对信号进行处理加工,由输出层神经元输出。

一个简单的多层前馈神经网络如下:

从左到右分别是输入层、隐藏层、输出层,箭头上的数字代表连接权重 w,节点上的数字代表偏置 bx1,x2 为两个输入的数,y 为输出的数。该网络可以对下面的四个数据进行分类,其中蓝色区域代表 + 类,其他代表 类。

实际情况下,神经网络的结构不会这么简单,它的输入和输出节点数都可能很多,这样它的求解能力更强。例如下面的这个神经网络,接下来的文章将会以它作为例子:

该神经网络输入层有 d 个神经元,隐藏层有 q 个神经元,输出层有 l 个神经元。

  • 输入层:输入和输出第 i 个神经元的数据记为 xi.
  • 输入层 隐藏层:输入层第 i 个神经元 隐藏层第 j 个神经元的连接权值记为 vij.
  • 隐藏层:输入第 j 个神经元的值记为 αj=i=1dvijxi,偏置为 γj,输出为 bj=f(αj+γj).
  • 隐藏层 输出层:隐藏层第 j 个神经元 输出层第 k 个神经元的连接权值记为 wjk.
  • 输出层:输入第 k 个神经元的值记为 βk=j=1qwjkbj,偏置为 θk,输出为 yk=f(βk+θk).

其中,激活函数 f() 选择 Sigmoid 函数:f(x)=11+ex

综上,该神经网络接受的输入为 xd×1,输出为 yl×1,网络中包含的参数有:

  • vd×q:输入层到隐藏层的权值矩阵
  • γq×1:隐藏层的偏置向量
  • wq×l:隐藏层到输出层的权值矩阵
  • θl×1:输出层的偏置向量

参数量为 dq+q+ql+l

3 误差逆传播算法

神经网络要学习的实际上就是网络中的参数值,具体来说就是连接权值与偏置。我们需要找到一个合理的方法,通过输入的训练样本数据和样本标记,让神经网络自适应地学习出可以正确分类训练样本。后续就可以使用训练得到的参数,对实际数据进行分类。

误差逆传播 (error BackPropagation, BP) 算法是一个非常有代表的学习算法,利用它进行学习的神经网络叫做 BP 神经网络。

它分为两个阶段:

  • 数据流的正向传播:将数据样本输入神经网络,求解出输出。
  • 误差信号的反向传播:将输出与样本标签对比,将误差反向传播到每一个节点得到它们需要更新的梯度。

对于每一个训练样例,都会进行上面两个操作(实际的顺序和批次方式可以变化),对于正向传播过程,没有什么难点,就是 t=f(wTx+b) 代进去算出结果来。

该算法核心在于误差的反向传播。首先需要计算误差值,这里使用均方误差 (Mean Squared Error, MSE). 对于第 p 个训练样本 (xp,yp),如果神经网络的输出为 y^p=(y^1(p),y^2(p),,y^l(p)),那么均方误差为:

Ep=12j=1l(y^j(p)yj(p))2

表达式中的 1/2 只是为了便于求导时消去 2,它对误差的相对大小没有影响。

下面我们默认讨论第 p 个训练样本 (xp,yp),省略 p 角标。

3.1 更新 w

对于 wjk 它的更新是:

wjkwjk+Δwjk

BP 算法使用梯度下降策略进行更新,给定学习率 η,具体的更新公式为:

wjkwjkηEpwjk

对于 wjk,可以从网络结构上看到,然后作为求和中的一项影响到 βk 的值,然后作为激活函数的自变量影响到 y^k 的值,误差的传递是一个链式的,因此可以用链式求导:

Epwjk=Epy^ky^kβkβkwjk

对于第一项:

Epy^k=12y^kj=1l(y^jyj)2=12(2y^k2yk)=y^kyk

对于第二项:

y^kβk=y^k(1y^k)

这里能直接转换成这个样子的原因是,对于 Sigmoid 函数 f(x)=11+ex,它的导数值 f(x)=f(x)(1f(x)).

对于第三项:

βkwjk=wjki=1qwikbi=bj

为了简便,我们将第一项与第二项的积记为 gk

gk=(y^kyk)y^k(1y^k)

综上:

Δwjk=ηgkbj

3.2 更新 θ

对于 θk,它的更新是:

Δθk=ηEpθk

链式求导:

Epθk=Epy^ky^kθk

对于第二项:

y^kθk=y^k(1y^k)

综上:

Δθk=ηgk

3.3 更新 v

对于 vij,它的更新是:

Δvij=ηEpvij

链式求导:

Epvij=k=1l(Epy^ky^kβkβkbj)bjαjαjvij

这里为什么多出来了个一个求和?原因是输入层 隐藏层的连接权重会影响到所以输出层节点,所以每个输出层节点的误差都得传递先传递到隐藏层,如下图所示:

对于求和符号里的第三项:

βkbj=bjj=1qwjkbj=wjk

对于求和符号外面的第一项:

bjαj=bj(1bj)

对于求和符号外面的第二项:

αjvij=viji=1dvijxi=xi

为了简便,我们把求和符号和外面的第一项记为 ej

ej=k=1l(Epy^ky^kβkβkbj)bjαj=k=1l(gkwjk)bj(1bj)

综上:

Δvij=ηejxi

3.4 更新 γ

对于 γj,它的更新是:

Δγj=ηEpγj

链式求导:

Epγj=k=1l(Epy^ky^kβkβkbj)bjγj

对于求和符号外面的第一项:

bjγj=bj(1bj)

综上:

Δγj=ηej

3.5 总结

BP 算法的流程如下:

  • 输入:训练集 D={(xp,yp)}p=1m; 学习率 η.
  • 过程:

    1. (0,1) 范围随机初始化网络中的所有参数
    2. repeat
    3.     for all (xp,yp)D do
    4.         使用当前参数计算出当前样本输出 y^p
    5.         计算出 gk,ej
    6.         计算出 Δwjk,Δθk,Δvij,Δγj 并更新参数值
    7.     end for
    8. until 达到停止条件
  • 输出:参数确定的 BP 神经网络

其中:

{gk=(y^kyk)y^k(1y^k)ej=k=1l(gkwjk)bj(1bj){Δwjk=ηgkbjΔθk=ηgkΔvij=ηejxiΔγj=ηej

4 代码实现

以下是我使用 C++ 编写的简单神经网络的源代码,可以和上文的公式对照学习:

https://github.com/ChrisKimZHT/Neural-Net-cpp

文章目录