1. 从神经网络谈起
了解神经网络的都知道,神经网络作为一种非线性模型,在监督学习领域取得了state-of-art的效果,其中反向传播算法的提出居功至伟,到如今仍然是主流的优化神经网络参数的算法. 递归神经网络、卷积神经网络以及深度神经网络作为人工神经网络的"变种",仍然延续了ANN的诸多特质,如权值连接,激励函数,以神经元为计算单元等,只不过因为应用场景的不同衍生了不同的特性,如:处理变长数据、权值共享等。为了介绍RNN,先简单的介绍ANN. ANN的结构很容易理解,一般是三层结构(输入层-隐含层-输出层). 隐含层输出 o j o_j oj 和输出层输出 o k o_k ok如下。其中 n e t j net_j netj为隐含层第 j j j个神经元的输入, u u u为输入层和隐含层的连接权值矩阵, v v v为隐含层和输出层之间的连接权值矩阵.
$ \begin{align} o_j & = f(net_j) \
o_k & = f(net_k) \
net_j & =\sum_i(x_{i}u_{i,j})+b_j \
net_k & =\sum_j(o_{j}v_{j,k})+b_k \end{align} $$
定义损失函数为 E p = 1 2 ∑ k ( o k − d k ) 2 E_p=\frac{1}{2}\sum_k (o_k - d_k)^2 Ep=21∑k(ok−dk)2 ,其中 p p p为样本下标, o k o^k ok为第 k k k个输出层神经元的输出, d k d^k dk为样本在第 k k k个编码值。然后分别对参数 v j , k v_{j,k} vj,k、 u i , j u_{i,j} ui,j 进行求导,可得:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
从对 ∂ E p ∂ u i , j \frac{\partial E_p} {\partial u_{i,j}} ∂ui,j∂Ep的推导可以得到反向传播的核心思路,令误差项 β k = ∂ E p ∂ n e t k \beta_k = \frac{\partial E_p} {\partial net_k} βk=∂netk∂Ep, 则有: β k = o l ( 1 − o l ) ∑ l β l w l k \beta_k=o_l(1-o_l)\sum_l\beta_lw_{lk} βk=ol(1−ol)l∑βlwlk
反向传播的实质是基于梯度下降的优化方法,只不过在优化的过程使用了一种更为优雅的权值更新方式。
2. 循环神经网络
传统的神经网络一般都是全连接结构,且非相邻两层之间是没有连接的。一般而言,定长输入的样本很容易通过神经网络来解决,但是类似于NLP中的序列标注这样的非定长输入,前向神经网络却无能为力。于是有人提出了循环神经网络(Recurrent Neural Network),这是一种无法像前向神经网络一样有可以具象的网络结构的模型,一般认为是网络隐层节点之间有相互作用的连接,其实质可以认为是多个具有相同结构和参数的前向神经网络的stacking, 前向神经网络的数目和输入序列的长度一致,且序列中毗邻的元素对应的前向神经网络的隐层之间有互联结构,其图示( 图片来源 )如下.
上图只是一个比较抽象的结构,下面是一个以时间展开的更为具体的结构[W,V,U略有不同,后文推荐以展开图中的参数表示为主)(图片来源).
从图中可以看出,输出层神经元的输入输出和前向神经网络中没有什么差异,仅仅在于隐层除了要接收输入层的输入外,还需要接受来自于自身的输入(可以理解为t时刻的隐层需要接收来自于t-1时刻隐层的输入, 当然这仅限于单向RNN的情况,在双向RNN还需要接受来自t+1时刻的输入).
RNN的隐层是控制信息传递的重要单元,不同时刻隐层之间的连接权值决定了过去时刻对当前时刻的影响,所以会存在时间跨度过大而导致这种影响会削弱甚至消失的现象,称之为梯度消失,改进一般都是针对隐层做文章,LSTM(控制输入量,补充新的信息然后输出),GRU(更新信息然后输出)等都是这类的改进算法.
下图为某时刻隐层单元的结构示意图(图片来源).
虽说处理的是不定长输入数据,但是某个时刻的输入还是定长的。令t时刻:输入 x t ∈ R x d i m x_t \in R^{xdim} xt∈Rxdim ,t隐层输出 h t ∈ R h d i m h_t\in R^{hdim} ht∈Rhdim, 输出层 y t ∈ R y d i m y_t \in R^{ydim} yt∈Rydim, RNN和CNN有着同样的共享权值的属性,输入层到隐层的权值矩阵 V ∈ R x d i m × h d i m V\in R^{xdim\times hdim} V∈Rxdim×hdim, 隐层到输出层的权值矩阵 W ∈ R h d i m × y d i m W \in R^{hdim\times ydim} W∈Rhdim×ydim, 不同时刻隐层自连接权值矩阵 U ∈ R h d i m × h d i m U\in R^{hdim\times hdim} U∈Rhdim×hdim1. RNN有着类似于CNN的权值共享特点,所以不同时刻的U,V,W都是相同的,所有整个网络的学习目标就是优化这些参数以及偏置. RNN和普通神经网络一样,也有超过三层的结构,下文的推导仅以三层为例.
3. RNN推导
令隐含层的激励函数$f(x)$, 输出层的激励函数为$g(x)$. 则有:KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ h^t & = f(net…
对于单个样本,定义我们的cost function E t = 1 2 ∣ ∣ d t − y t ∣ ∣ 2 E^t = \frac{1}{2}||d^t- y^t ||^2 Et=21∣∣dt−yt∣∣2,其中 d t d^t dt为t时刻的真实输出, y t y^t yt是t时刻的预测输出。则对权值 W j , k W_{j,k} Wj,k、 V i , j V_{i,j} Vi,j、 U j , r U_{j,r} Uj,r的求导分别如下。其中 j j j表示隐层单元下标, k k k表示输出层下标, i i i表示输入层下标, r r r表示下一时刻隐含层下标. n e t h j t net^t_{hj} nethjt表示t时刻隐层第j个神经元的加权输入, n e t y k t net^t_{yk} netykt表示t时刻输出层第k个神经元的加权输入。
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
令 δ y , k t \delta_{y,k}^t δy,kt为t时刻输出层y第k个神经元的误差项,令 δ h , j t \delta_{h,j}^t δh,jt为t时刻隐含层h第j个神经元的误差项, 则有:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \delta_{y,k}^t…
所以不难得到误差方向传递时的权值计算方式:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
写成矩阵的形式即是(其中
⋅
\cdot
⋅表示矩阵对应位置的元素相乘):
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \delta_{y}^t &…
上述 公式其实在了解反向传播之后就能够很容易推导,对于 W j k W_{jk} Wjk、 U j r U_{jr} Ujr的推导套用反向传播公式即可,而对于 V i , j V_{i,j} Vi,j需要加上来自于下一时刻的误差,如以上式子中红色部分所示.
4. RNN实现
推导出了公式,用代码实现就很简单了,可以看到前向传递和反向传播过程中的都用到了矩阵的相关运算,封装好一个矩阵计算的类,然后重载相关的运算符,便可以十分方便的进行相关的计算了。矩阵类的实现见https://github.com/kymo/SUDL/blob/master/matrix/matrix.h ,然后直接按照上述的矩阵运算方式填写代码就可以了,详见:https://github.com/kymo/SUDL/blob/master/rnn/rnn.cpp.5. LSTM推导
相比于普通的RNN而言,LSTM无非是多了几个门结构,求导的过程略微繁琐一点,但是只要清楚当前待求导变量到输出的路径,变可以依据链式求导法则得到对应的求导公式。LSTM是为了缓解RNN的梯度弥散问题而提出的一种变种模型,之所以能够缓解这个问题,是因为在原始的RNN中,梯度的传递是乘法的过程,如果梯度很小,那么从T时刻传递到后面的梯度只会越来越小,甚至消失,在优化空间中相当于一部分参数进行更新,而另外一部分参数几乎不变,那么问题的较优解也就很难收敛到。而LSTM通过推导会发现,梯度是以一种累加的方式进行反向传递的,从而一定程度上客服了累乘导致的梯度弥散的问题。下面要推导的LSTM 是不加peelhole的结构。要推导LSTM首先要了解LSTM的几个门结构:输入门、输出门以及遗忘门。从传统的神经网络的角度来看的话,三种门分别对应了三个并行的神经网络层,如下图所示(其中蓝色和黄色表示神经元层结构,有输入、输出和相应的激励函数;红色表示向量对应项相乘;淡绿色表示向量加)。三种门的计算方式如下:
KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ netf^{t} & = x…
另外,LSTM中也引入一个Cell State的中间状态,该状态有选择的保存了过去的历史信息,不会像RNN一样存在越久远的记忆越模糊的问题。该Cell State的更新方式主要是依赖于过去时刻的Cell State(
c
t
−
1
c^{t-1}
ct−1)以及当前时刻产生的新的信息(
c
~
t
\tilde{c}^t
c~t),当前产生的新的信息的计算方式如下:
KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ netc^{t} & = x…
遗忘门用来控制过去时刻的Cell State有多少需要被保留,输入门用来控制增加多少新的信息到Cell State中,公式表述如下:
KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ c^t & = f^t \c…
而
h
t
h_t
ht的更新则利用了
c
t
c^t
ct,具体如下:
KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ h^t & = h(c^t)…
输出层的计算方式和一般的RNN无异,令输出层、输入门、输出门、遗忘门、新cell state的误差项分别为 δ y t \delta_y^t δyt, δ i t \delta_i^t δit, δ o t \delta_o^t δot, δ f t \delta_f^t δft, δ c t \delta_c^t δct, 则有:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \delta_y^t & =…
另外我们也需要定义两个新的中间变量
ϵ
c
t
\epsilon^t_c
ϵct 以及
ϵ
h
t
\epsilon^t_h
ϵht ,其值为:
KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ \epsilon^t_h &…
so, 另外一波公式来袭~~
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ \delta_y^t & =…
okay,现在对权值进行求导,首先看
w
i
x
wix
wix ,根据链式求导法则有:
KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
剩下的参数不难推知:
KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ \frac{\partial…
总算推完了,代码实现就很简单了~详见 https://github.com/kymo/SUDL/blob/master/rnn/lstm.cpp
对于LSTM,GRU的推导也类似,了解清楚误差传递的来源和去向,就很容易得到对当前参数的推导链式规则了.