机器学习笔记(三):多层神经网络
基于生物神经元模型可得到多层感知器(Multi-layer Perceptron, MLP)的基本结构,最典型的MLP包括包括三层:输入层(Input Layer)、隐层(Hidden Layer)和输出层(Output Layer),MLP神经网络不同层之间是全连接的(全连接的意思就是:上一层的任何一个神经元与下一层的所有神经元都有连接)。
1. 神经元
神经元和感知器本质上是一样的,感知器的激活函数是阶跃函数;而神经元的激活函数往往选择为sigmoid函数或tanh函数。如下图所示:
计算一个神经元的输出的方法和计算一个感知器的输出是一样的。假设神经元的输入是向量$\vec{x}$,权重向量是$\vec{w}$(偏置项是$w_0$),激活函数是sigmoid函数,则其输出$y$:
$$
y = sigmoid(\vec{w}^T \cdot \vec{x})
$$
sigmoid函数的定义如下:
$$
sigmoid(x) = \frac{1}{1+e^{-x}}
$$
将其带入前面的式子,得到
$$
y = \frac{1}{1+e^{-\vec{w}^T \cdot \vec{x}}}
$$
sigmoid函数是一个非线性函数,值域是(0,1)。函数图像如下图所示
sigmoid函数的导数是:
$$
\begin{eqnarray}
y & = & sigmod(x) \tag{1} \
y’ & = & y(1-y)
\end{eqnarray}
$$
可以看到,sigmoid函数的导数可以用sigmoid函数自身来表示。这样,一旦计算出sigmoid函数的值,计算它的导数的值就非常方便。
2. 神经网络的结构
神经网络就是按照一定规则连接起来的多个神经元。上图展示了一个全连接(Full Connected, FC)神经网络,通过观察上面的图,可以发现它的规则包括:
- 神经元按照层来布局
- 最左边的层叫做输入层,负责接收输入数据;
- 最右边的层叫输出层,可以从这层获取神经网络输出数据;
- 输入层和输出层之间的层叫做隐藏层,因为它们对于外部来说是不可见的。
- 同一层的神经元之间没有连接
- 第N层的每个神经元和第N-1层的所有神经元相连(这就是full connected的含义),第N-1层神经元的输出就是第N层神经元的输入
- 每个连接都有一个权值
由此可知,神经网络主要有三个基本要素:权重、偏置和激活函数
- 权重:神经元之间的连接强度由权重控制,权重的大小表示可能性的大小
- 偏置:偏置的设置是为了正确分类样本,是模型中一个重要的参数,即保证通过输入算出的输出值不能随便激活。
- 激活函数:起非线性映射的作用,其可将神经元的输出幅度限制在一定范围内,一般限制在(-1~1)或(0~1)之间。最常用的激活函数是Sigmoid函数,可将(-∞,+∞)的数映射到(0~1)的范围内。
上面这些规则定义了全连接神经网络的结构。事实上还存在很多其它结构的神经网络,比如卷积神经网络(CNN)、循环神经网络(RNN),他们都具有不同的连接规则。
3. 计算神经网络的输出
神经网络实际上就是一个输入向量$\vec{x}$到输出向量$\vec{y}$的函数,即:
$$
\vec{y} = f_{network}(\vec{x})
$$
根据输入计算神经网络的输出
- 首先将输入向量$\vec{x}$的每个元素的值$x_i$赋给神经网络的输入层的对应神经元
- 然后根据式(1)依次向前计算每一层的每个神经元的值,直到最后一层输出层的所有神经元的值计算完毕
- 最后,将输出层每个神经元的值串在一起就得到了输出向量$\vec{y}$。
接下来举一个例子来说明这个过程,我们先给神经网络的每个单元写上编号。
- 输入层有三个节点,我们将其依次编号为1、2、3;
- 隐藏层的4个节点,编号依次为4、5、6、7;
- 最后输出层的两个节点编号为8、9。
因为这个神经网络是全连接网络,所以可以看到每个节点都和上一层的所有节点有连接。比如,隐藏层的节点4,它和输入层的三个节点1、2、3之间都有连接,其连接上的权重分别为$w_{41}$,$w_{42}$,$w_{43}$。那么,怎样计算节点4的输出值$a_4$呢?
为了计算节点4的输出值,必须先得到其所有上游节点(也就是节点1、2、3)的输出值。节点1、2、3是输入层的节点,所以,他们的输出值就是输入向量$\vec{x}$本身。按照上图画出的对应关系,可以看到节点1、2、3的输出值分别是$x_1$,$x_2$,$x_3$。要求输入向量的维度和输入层神经元个数相同,而输入向量的某个元素对应到哪个输入节点是可以自由决定的。
一旦有了节点1、2、3的输出值,就可以根据式1计算节点4的输出值$a_4$:
上式的$w_{4b}$是节点4的偏置项,图中没有画出来。而$w_{41}$,$w_{42}$,$w_{43}$分别为节点1、2、3到节点4连接的权重,在给权重$w_{ji}$编号时,目标节点的编号$j$放在前面,把源节点的编号$i$放在后面。
同样,可以继续计算出节点5、6、7的输出值$a_5$,$a_6$,$a_7$。这样,隐藏层的4个节点的输出值就计算完成了,就可以接着计算输出层的节点8的输出值$y_1$:
同理,我们还可以计算出$y_2$的值。这样输出层所有节点的输出值计算完毕,就得到了在输入向量$\vec{x} = (x_1, x_2, x_3)^T$时,神经网络的输出向量$\vec{y} = (y_1, y_2)^T$。可以看出:输出向量的维度和输出层神经元个数相同。
4. 神经网络的矩阵表示
神经网络的计算如果用矩阵来表示会很方便,此外可以用优化加速算法提高计算速度。
隐藏层4个节点的计算依次排列出来:
接着,定义网络的输入向量$\vec{x}$和隐藏层每个节点的权重向量$\vec{w}$。令
代入到前面的一组式子,得到:
现在,把上述计算$a_4$, $a_5$,$a_6$,$a_7$的四个式子写到一个矩阵里面,每个式子作为矩阵的一行,就可以利用矩阵来表示它们的计算了。令
带入前面的一组式子,得到
在(式2)中,
- $f$是激活函数,在本例中是$sigmod$函数;
- $W$是某一层的权重矩阵;
- $\vec{x}$是某层的输入向量;
- $\vec{a}$是某层的输出向量。
(式2)说明神经网络的每一层的作用实际上就是先将输入向量左乘一个数组进行线性变换,得到一个新的向量,然后再对这个向量逐元素应用一个激活函数。
每一层的算法都是一样的。比如,对于包含一个输入层,一个输出层和三个隐藏层的神经网络,我们假设其权重矩阵分别为$W_1$,$W_2$,$W_3$,$W_4$,每个隐藏层的输出分别是$\vec{a}_1$,$\vec{a}_2$,$\vec{a}_3$,神经网络的输入为$\vec{x}$,神经网络的输出为$\vec{y}$,如下图所示:
则每一层的输出向量的计算可以表示为:
这就是神经网络输出值的矩阵计算方法。
如果写成一个公式:
$$
\vec{y} = f(W4 \cdot f(W3 \cdot f(W2 \cdot f(W1 \cdot \vec{x}))))
$$
神经网络正向计算的过程比较简单,就是一层一层不断做运算,动态的演示如下图所示:
5. 神经网络的训练 - 反向传播算法
神经网络的每个连接上的权值如果知道,那么就可以将输入数据代入得到希望的结果。神经网络是一个模型,那么这些权值就是模型的参数,也就是模型要学习的东西。然而,一个神经网络的连接方式、网络的层数、每层的节点数这些参数,则不是学习出来的,而是人为事先设置的。对于这些人为设置的参数,我们称之为**超参数(Hyper-Parameters)**。
前面课程中所学的最小二乘、逻辑回归等可以直接优化损失函数来求解模型参数的更新值。在多层神经网络中:
- 最后一层的参数可以用这样的方式求解得到;
- 隐层节点没有输出的真值,因此无法直接构建损失函数来求解
如何化解这个难题?
反向传播算法其实就是链式求导法则的应用。然而,这个如此简单且显而易见的方法,却是在Roseblatt提出感知器算法将近30年之后才被发明和普及的。对此,Bengio这样回应道:
很多看似显而易见的想法只有在事后才变得显而易见。
按照机器学习的通用套路,我们先确定神经网络的目标函数,然后用随机梯度下降优化算法去求目标函数最小值时的参数值。
我们取网络所有输出层节点的误差平方和作为目标函数:
其中,$E_d$表示是样本$d$的误差, t是样本的标签值,y是神经网络的输出值。
然后,使用随机梯度下降算法对目标函数进行优化:
随机梯度下降算法也就是需要求出误差$E_d$对于每个权重$w_{ji}$的偏导数(也就是梯度),如何求解?
观察上图,我们发现权重$w_{ji}$仅能通过影响节点$j$的输入值影响网络的其它部分,设$net_j$是节点$j$的加权输入,即
$E_d$是$net_j$的函数,而$net_j$是$w_{ji}$的函数。根据链式求导法则,可以得到:
上式中,$x_{ji}$是节点传递给节点$j$的输入值,也就是节点$i$的输出值。
对于的$\frac{\partial E_d}{\partial net_j}$推导,需要区分输出层和隐藏层两种情况。
5.1 输出层权值训练
对于输出层来说,$net_j$仅能通过节点$j$的输出值$y_j$来影响网络其它部分,也就是说$E_d$是$y_j$的函数,而$y_j$是$net_j$的函数,其中$y_j = sigmod(net_j)$。所以我们可以再次使用链式求导法则:
考虑上式第一项:
考虑上式第二项:
将第一项和第二项带入,得到:
如果令$\delta_j = - \frac{\partial E_d}{\partial net_j}$,也就是一个节点的误差项$\delta$是网络误差对这个节点输入的偏导数的相反数。带入上式,得到:
将上述推导带入随机梯度下降公式,得到:
5.2 隐藏层权值训练
现在我们要推导出隐藏层的$\frac{\partial E_d}{\partial net_j}$。
首先,我们需要定义节点$j$的所有直接下游节点的集合$Downstream(j)$。例如,对于节点4来说,它的直接下游节点是节点8、节点9。可以看到$net_j$只能通过影响$Downstream(j)$再影响$E_d$。设$net_k$是节点$j$的下游节点的输入,则$E_d$是$net_k$的函数,而$net_k$是$net_j$的函数。因为$net_k$有多个,我们应用全导数公式,可以做出如下推导:
因为$\delta_j = - \frac{\partial E_d}{\partial net_j}$,带入上式得到:
至此,我们已经推导出了反向传播算法。需要注意的是,我们刚刚推导出的训练规则是根据激活函数是sigmoid函数、平方和误差、全连接网络、随机梯度下降优化算法。如果激活函数不同、误差计算方式不同、网络连接结构不同、优化算法不同,则具体的训练规则也会不一样。但是无论怎样,训练规则的推导方式都是一样的,应用链式求导法则进行推导即可。
5.3 具体解释
假设每个训练样本为$(\vec{x}, \vec{t})$,其中向量$\vec{x}$是训练样本的特征,而$\vec{t}$是样本的目标值。
首先,根据上一节介绍的算法,用样本的特征$\vec{x}$,计算出神经网络中每个隐藏层节点的输出$a_i$,以及输出层每个节点的输出$y_i$。
然后,按照下面的方法计算出每个节点的误差项$\delta_i$:
- 对于输出层节点$i$
其中,$\delta_i$是节点$i$的误差项,$y_i$是节点$i$的输出值,$t_i$是样本对应于节点$i$的目标值。举个例子,根据上图,对于输出层节点8来说,它的输出值是$y_1$,而样本的目标值是$t_1$,带入上面的公式得到节点8的误差项应该是:
- 对于隐藏层节点
其中,$a_i$是节点$i$的输出值,$w_{ki}$是节点$i$到它的下一层节点$k$的连接的权重,$\delta_k$是节点$i$的下一层节点$k$的误差项。例如,对于隐藏层节点4来说,计算方法如下:
最后,更新每个连接上的权值:
其中,$w_{ji}$是节点$i$到节点$j$的权重,$\eta$是一个成为学习速率的常数,$\delta_j$是节点$j$的误差项,$x_{ji}$是节点$i$传递给节点$j$的输入。例如,权重$w_{84}$的更新方法如下:
类似的,权重$w_{41}$的更新方法如下:
偏置项的输入值永远为1。例如,节点4的偏置项$w_{4b}$应该按照下面的方法计算:
计算一个节点的误差项,需要先计算每个与其相连的下一层节点的误差项,这就要求误差项的计算顺序必须是从输出层开始,然后反向依次计算每个隐藏层的误差项,直到与输入层相连的那个隐藏层,这就是反向传播算法的名字的含义。当所有节点的误差项计算完毕后,就可以根据式5来更新所有的权重。
6. 为什么要使用激活函数
激活函数在神经网络中非常重要,使用激活函数也是非常必要的,前面我们从人脑神经元的角度理解了激活函数,因为神经元需要通过激活才能往后传播,所以神经网络中需要激活函数,下面我们从数学的角度理解一下激活函数的必要性。
比如一个两层的神经网络,使用 f 表示激活函数,那么
$$
y = f( w_2 f(w_1 x) )
$$
如果不使用激活函数,那么神经网络的结果就是
$$
y = w_2 (w_1 x) = (w_2 w_1) x = \bar{w} x
$$
可以看到,将两层神经网络的参数合在一起,用 $\bar{w}$ 来表示,两层的神经网络其实就变成了一层神经网络,只不过参数变成了新的 $\bar{w}$,所以如果不使用激活函数,那么不管多少层的神经网络,$y = w_n \cdots w_2 w_1 x = \bar{w} x$,就都变成了单层神经网络,所以在每一层都必须使用激活函数。
最后看看激活函数对神经网络的影响
可以看到使用了激活函数之后,神经网络可以通过改变权重实现任意形状,越是复杂的神经网络能拟合的形状越复杂,这就是著名的神经网络万有逼近定理。神经网络使用的激活函数都是非线性的,每个激活函数都输入一个值,然后做一种特定的数学运算得到一个结果。
6.1 sigmoid 激活函数
$$\sigma(x) = \frac{1}{1 + e^{-x}}$$
6.2 tanh 激活函数
$$tanh(x) = 2 \sigma(2x) - 1$$
6.3 ReLU 激活函数
$$ReLU(x) = max(0, x)$$
当输入 $x<0$ 时,输出为 $0$,当 $x> 0$ 时,输出为 $x$。该激活函数使网络更快速地收敛。它不会饱和,即它可以对抗梯度消失问题,至少在正区域($x> 0$ 时)可以这样,因此神经元至少在一半区域中不会把所有零进行反向传播。由于使用了简单的阈值化(thresholding),ReLU 计算效率很高。
在网络中,不同的输入可能包含着大小不同关键特征,使用大小可变的数据结构去做容器,则更加灵活。假如神经元激活具有稀疏性,那么不同激活路径上:不同数量(选择性不激活)、不同功能(分布式激活)。两种可优化的结构生成的激活路径,可以更好地从有效的数据的维度上,学习到相对稀疏的特征,起到自动化解离效果。
稀疏特征并不需要网络具有很强的处理线性不可分机制,因此在深度学习模型中,使用简单、速度快的线性激活函数可能更为合适。如图,一旦神经元与神经元之间改为线性激活,网络的非线性部分仅仅来自于神经元部分选择性激活。
更倾向于使用线性神经激活函数的另外一个原因是,减轻梯度法训练深度网络时的Vanishing Gradient Problem。
看过BP推导的人都知道,误差从输出层反向传播算梯度时,在各层都要乘当前层的输入神经元值,激活函数的一阶导数。
$$
grad = error ⋅ sigmoid’(x) ⋅ x
$$
使用双端饱和(即值域被限制)Sigmoid系函数会有两个问题:
- sigmoid’(x) ∈ (0,1) 导数缩放
- x∈(0,1)或x∈(-1,1) 饱和值缩放
这样,经过每一层时,误差都是成倍的衰减,一旦进行递推式的多层的反向传播,梯度就会不停的衰减,消失,使得网络学习变慢。而校正激活函数的梯度是1,且只有一端饱和,梯度很好的在反向传播中流动,训练速度得到了很大的提高。
7. 算法与处理步骤
W = random
# 每次训练
for k in range(epoch)
# 正向计算
for j in range(NN_depth):
# 式2 ( a = xxx)
X_j = f( W_{j, j-1} X_{j-1})
# 反向误差计算
for j in range(NN_depth, 0, -1):
# 式3, 式4
delta = y_i(1-y_i)(t_i-y_i)
or
delta = a_i(1-a_i) \sum w_ki delta_k
# 式5
w_ji = w_j + epsilon delta_j x_ji
7.1 正向计算:
7.2 反向传播:
输出层的误差计算:
隐层的误差计算:
权值更新: