在前面的博客人工神经网络入门和训练深度神经网络,也介绍了与本文相似的内容。前面的两篇博客侧重的是如何使用TensorFlow实现,而本文侧重相关数学公式及其推导。html
一个神经元就是一个计算单元,传入$n$个输入,产生一个输出,再应用于激活函数。记$n$维输入向量为$x$,$n$维权重矩阵向量是$w$,偏置项为$b$,激活函数为sigmoid,最终激活后的输出为$a$:web
\begin{align*}
a = \frac{1}{1 + \exp(-(w^T x + b))}
\end{align*}安全
将权重和偏置项组合在一块儿,获得以下公式:网络
\begin{align*}
a = \frac{1}{1 + \exp(-[w^T \quad b] \cdot [x \quad 1])}
\end{align*}函数
图1更直观地描述了该公式:翻译
图1 单个神经元的输入及输出htm
将单个神经元扩展到一层,共$m$个神经元,每一个神经元的输入都是$x$,权重记作$\{ w^{(i)}, \cdots, w^{(m)} \}$,偏置项记作$\{ b^{(i)}, \cdots, b^{(m)} \}$,则每一个神经元激活后的输出:blog
\begin{align*}
a_1 &= \frac{1}{1 + \exp(-((w^{(1)})^T x + b_1))} \\
&\vdots \\
a_m &= \frac{1}{1 + \exp(-((w^{(m)})^T x + b_m))}
\end{align*}递归
下面咱们定义更抽象的形式,以便用于复杂的神经网络:ci
\begin{align*}
&W = \begin{bmatrix} - & w^{(1)T} & -\\ & \cdots & \\ - & w^{(m)T} & - \end{bmatrix} \in \mathbb{R}^{m \times n} \\
&b = \begin{bmatrix} b_1\\ \vdots \\ b_m \end{bmatrix} \in \mathbb{R}^m \\
&z = Wx + b \\
&\sigma(z) = \begin{bmatrix} \frac{1}{1 + \exp(-z_1)}\\ \vdots \\ \frac{1}{1 + \exp(-z_m)} \end{bmatrix} \\
&\begin{bmatrix} a^{(1)}\\ \vdots \\ a^{(m)} \end{bmatrix} = \sigma(z) = \sigma(Wx + b)
\end{align*}
图2 简单的前馈神经网络
如图2所示的神经网络,只有1个隐层,输出:
\begin{align*}
s = U^T a = U^T f(Wx + b)
\end{align*}
其中,$f$是激活函数。
维度分析:假设词向量维度为2,一次使用5个词做为输入,则输入$x \in \mathbb{R}^{20}$。若是隐层有8个sigmoid神经元,并在输出层产生1个未规范化的分值,那么$W \in \mathbb{R}^{8 \times 20},b \in \mathbb{R}^{8},U \in \mathbb{R}^{8 \times 1},s \in \mathbb{R}$。
最大间隔目标函数的思想是,确保正样本的分值高于负样本的分值。这与SVM的目标韩式较为类似。正样本经神经网络计算,获得的分值记作$s$,负样本记作$s_c$,目标函数就是最大化$(s - s_c)$,或者最小化$(s_c - s)$。只有在$s_c > s$时才须要更新神经网络的参数。所以,若是$s_c > s$,则损失是$s_c - s$;不然,损失是0。所以损失函数:
\begin{align*}
J = \max(s_c-s,0)
\end{align*}
训练神经网络的目标是使得$J$最小。
为了获得一个更安全的边界,咱们但愿正样本分值比负样本分值大出$\Delta$(大于0),所以:
\begin{align*}
J = \max(s_c - s + \Delta ,0)
\end{align*}
其实为了简化公式,能够直接取$\Delta = 1$(根据SVM提到的知识,对函数距离的缩放,不会影响最终结果。$s$和$s_c$都是函数距离),模型在训练过程当中其参数会自动适应这一约束,且不影响最终结果。此时目标函数:
\begin{align*}
J = \max(s_c - s + 1 ,0)
\end{align*}
咱们须要求得损失函数关于每一个参数的偏导数,而后使用梯度降低更新参数:
\begin{align*}
\theta^{(t+1)} = \theta^{(t)} - \alpha \nabla_{\theta^{(t)}} J
\end{align*}
反向传播使用链式求导法则,求得损失函数关于每一个参数的偏导数。为了进一步理解这一技术,首先看一下图3的神经网络:
图3
上图的神经网络只有一个隐层,一个输出。为简单起见,定义如下几率:
图4 与更新$W^{(1)}_{14}$相关的部分
如图4,若是要更新$W^{(1)}_{14}$,首先要意识到,只有在计算$z^{(2)}_1$时才会用到$W^{(1)}_{14}$。$z^{(2)}_1$仅仅用于计算了$a^{(2)}_1$,$a^{(2)}_1$与$W^{(2)}_1$用于计算最终的分值。首先有算是函数关于$s$和$s_c$的偏导数:
\begin{align*}
\frac{\partial J}{\partial s} = -\frac{\partial J}{\partial s_c} = -1
\end{align*}
为简单起见,咱们只计算$\frac{\partial s}{\partial w^{(1)}_{ij}}$:
\begin{align*}
\frac{\partial s}{\partial w^{(1)}_{ij}} &= \frac{\partial W^{(2)} a^{(2)}}{\partial w^{(1)}_{ij}} \tag{1} \\
&= \frac{\partial W^{(2)}_i a^{(2)}_i}{\partial w^{(1)}_{ij}} \tag{2} \\
&= W^{(2)}_i \frac{\partial a^{(2)}_i}{\partial w^{(1)}_{ij}} \tag{3} \\
\end{align*}
第(1)步很直观,由于$s = W^{(2)} a^{(2)}$。第(2)步是由于,只有在计算标量$a^{(2)}_i$时,才会用到向量$W^{(1)}_i$。第(3)步也很直观,咱们是在求关于$W^{(1)}_i$的偏导数,$W^{(2)}_i$直接看作常数。
而后应用链式法则:
\begin{align*}
W^{(2)}_i \frac{\partial a^{(2)}_i}{\partial w^{(1)}_{ij}} &= W^{(2)}_i \frac{\partial a^{(2)}_i}{\partial z^{(2)}_i} \frac{\partial z^{(2)}_i}{\partial w^{(1)}_{ij}} \\
&= W^{(2)}_i \frac{\partial f(z^{(2)}_i)}{\partial z^{(2)}_i} \frac{\partial z^{(2)}_i}{\partial w^{(1)}_{ij}} \\
&= W^{(2)}_i f'(z^{(2)}_i) \frac{\partial z^{(2)}_i}{\partial w^{(1)}_{ij}} \\
&= W^{(2)}_i f'(z^{(2)}_i) \frac{\partial}{\partial w^{(1)}_{ij}} (b^{(1)}_i + a^{(1)}_1W^{(1)}_{i1} + a^{(1)}_2W^{(1)}_{i2} + a^{(1)}_3W^{(1)}_{i3} + a^{(1)}_4W^{(1)}_{i4}) \\
&= W^{(2)}_i f'(z^{(2)}_i) a^{(1)}_j \\
&= \delta^{(2)}_i \cdot a^{(1)}_j
\end{align*}
$\delta^{(2)}_i$本质上是第2层第$i$个神经元反向传回的偏差。
如今咱们换一种方式,用偏差分配和反向传播来讨论如何更新图4中的更新$W^{(1)}_{14}$:
以上咱们用链式法则和偏差分配反向传播获得的结果是同样的。
偏置项更新:偏置项也能够当作输入向量的一个维度,只不过这个维度始终为1(这种1.1小节中的第二个公式就能够看出)。所以,第$k$层第$i$个神经元偏置项的偏导数直接就是$\delta^{(k)}_i$。例如,在上面咱们是要更新$b^{(1)}_1$,而不是$W^{(1)}_{14}$,那么梯度直接就是$f'(z^{(2)}_1) W^{(2)}_{1}$。
将$\delta^{(k)}$到$\delta^{(k-1)}$的偏差计算通常化:
图5 从$\delta^{(k)}$到$\delta^{(k-1)}$的偏差传播
用向量化的代码取代for循环,有助于提升代码的执行效率(能够充分利用GPU加速吧?)。
上面咱们给出了如何计算一个参数的梯度,如今咱们介绍更通常化的方法,一次性地更新整个权重矩阵和偏置向量。这一简单的扩张有助于为咱们创建一种直觉,偏差传播能够抽象到矩阵-向量级别。
给出一个权重$W^{(k)}_{ij}$,咱们定义其偏差梯度为$\delta^{(k+1)}_i \cdot a^{(k)}_j$。$W^{(k)}$是将$a^{(k)}$映射为$z^{(k+1)}$的权重矩阵。咱们能够创建整个矩阵$W^{(k)}$的偏差梯度:
\begin{align*}
\nabla_{W^{(k)}} = \begin{bmatrix}
\delta^{(k+1)}_1 a^{(k)}_1 & \delta^{(k+1)}_1 a^{(k)}_2 &\cdots \\
\delta^{(k+1)}_2 a^{(k)}_1 & \delta^{(k+1)}_2 a^{(k)}_2 &\cdots \\
\vdots & \vdots & \ddots
\end{bmatrix}
= \delta^{(k+1)} a^{(k)T}
\end{align*}
下面咱们来看如何计算偏差向量$\delta^{(k)}$。在图5中咱们已经知道,$\delta^{(k)}_j = f'(z^{(k)}_j) \sum_i \delta^{(k+1)}_i W^{(k)}_{ij}$,这能够通常化为以下的矩阵形式:
\begin{align*}
\delta^{(k)} = f'(z^{(k)}) \circ (W^{(k)T}\delta^{(k+1)}_i)
\end{align*}
其中,$\circ$运算符是指矩阵点乘($\mathbb{R}^N \circ \mathbb{R}^N \rightarrow \mathbb{R}^N$)。
计算效率:咱们探索了基于元素的更新和基于矩阵的更新。咱们必须意识到,向量化的实如今科学运算环境里效率更高,好比MATLAB和Python的NumPy/SciPy包。所以,咱们应该使用向量化的实现。更进一步,在反向传播时应该避免重复计算。好比,$\delta^{(k)}$直接依赖于$\delta^{(k+1)}$。咱们应该确保,在使用$\delta^{(k+1)}$更新完$W^{(k)}$以后,不能丢弃,而是要保存训练,用于后面计算$\delta^{(k)}$。重复这一过程$(k-1) \cdots (1)$。最终获得了一个计算上还负担得起的递归过程。
本文翻译自CS224n课程的官方笔记3,对应该课程的第四、5节。笔记的后半部分还介绍了神经网络的一些实践经验以及技巧,这与先前的博客训练深度神经网络存在较大重叠,因此并无写在这里。