【NLP】LSTM总结记录

11 篇文章 1 订阅
8 篇文章 1 订阅

目录

前言

RNN

梯度消失和梯度爆炸

梯度裁剪

relu、leakyrelu等激活函数

Batch Normalization(批规范化)

残差结构

LSTM(长短期记忆网络)

LSTM形式

理解LSTM结构

梯度爆炸和消失的解决

pytorch中的LSTM

参数的估计

GRU


前言

如果有一天,你发现有大佬会看你写的东西,你会感觉一切的一切都变得有意义吗?好比一个资质极差的凡人,终于发现自己可以凭借后天的极限奋斗,能有希望入道成仙,因而欢喜无尽呢?希望未来的那么一天,也有人会跟我说一句:吾道不孤也!借此文感谢boss上跟我聊过的这位大佬!

这篇文章写一些关于LSTM的总结。

RNN

为了更加好的建模一些序列化信息,比如文本,语音,我们有了RNN(Recurrent Neural Network)。在绝大多数文章中,他长这个样子:

如上图所示,为什么他可以 建模序列化的信息呢?因为RNN每次都会将前一次的输出结果,带到下一次的隐藏层中,一起训练。前面所有时刻的输入都对未来的输出产生影响。

t时刻隐藏层h_t 接受上一个时刻t-1的隐藏层h_{t-1},我们将这种RNN叫 Elman network,若 h_t 接受上一个时刻的输出y_{t-1},我们称这种RNN叫Jordan network。【维基百科

实际上,上面的图,不是很能辅助理解RNN的工作状态。借助一个例子,引自【知乎】。先看图:

 

如果有一条长文本,我给句子事先分割好句子,并且进行tokenize, dictionarize,接着再由look up table 查找到embedding,将token由embedding表示,再对应到上图的输入。流程如下:

step1, raw text:
接触LSTM模型不久,简单看了一些相关的论文,还没有动手实现过。然而至今仍然想不通LSTM神经网络究竟是怎么工作的。……

step2, tokenize (中文得分词):
sentence1: 接触 LSTM 模型 不久 ,简单 看了 一些 相关的 论文 , 还 没有 动手 实现过 。
sentence2: 然而 至今 仍然 想不通 LSTM 神经网络 究竟是 怎么 工作的。
……

step3, dictionarize:
sentence1: 1 34 21 98 10 23 9 23
sentence2: 17 12 21 12 8 10 13 79 31 44 9 23
……

step4, padding every sentence to fixed length:
sentence1: 1 34 21 98 10 23 9 23 0 0 0 0 0
sentence2: 17 12 21 12 8 10 13 79 31 44 9 23 0
……

step5, mapping token to an embeddings:
sentence1:

\begin{bmatrix} 0.335 &0.121 &0.011 &... \\ 0.435 &0.006 & 0.254& ...\\ 0.286& 0.175 & 0.555&... \\ ... & ...& ... & ... \end{bmatrix} ,每一列代表一个词向量,词向量维度自行确定;矩阵列数固定为time_step length。
sentence2:
……

step6, feed into RNNs as input:

假设 一个RNN的time_step 确定为 l,则padded sentence length(step5中矩阵列数)固定为 l,--这是句子的padding操作,一次RNNs的run只处理一条sentence。每个sentence的每个token的embedding对应了每个时序 t 的输入I^t_i。一次RNNs的run,连续地将整个sentence处理完。

step7, get output:
看图,每个time_step都是可以输出当前时序 t 的隐状态h^t_i ;但整体RNN的输出 o^t_i是在最后一个time_step  t = l 时获取,才是完整的最终结果。

step8, further processing with the output:
我们可以将output根据分类任务或回归拟合任务的不同,分别进一步处理。

梯度消失和梯度爆炸

RNN模型训练的一大难题是梯度消失和梯度爆炸问题。产生梯度消失和梯度爆炸是由于RNN的权值矩阵循环相乘导致的。下面给出RNN梯度消失和爆炸的解释,该部分引自【《RNN梯度消失和爆炸的原因》-沉默中的思索-知乎

我们拿一个三个时刻的序列处理过程来举例,S_{0} 为给定值。

preview

RNN最简单的前向传播过程如下:

\\S_{1}=W_{x}X_{1}+W_{s}S_{0}+b_{1} \\O_{1}=W_{o}S_{1}+b_{2}\\ S_{2}=W_{x}X_{2}+W_{s}S_{1}+b_{1}\\O_{2}=W_{o}S_{2}+b_{2}\\S_{3}=W_{x}X_{3}+W_{s}S_{2}+b_{1}\\O_{3}=W_{o}S_{3}+b_{2}

假设在t=3时刻,损失函数为L_{3}=\frac{1}{2}(Y_{3}-O_{3})^{2}

则对于这一次训练任务的损失函数为L=\sum_{t=0}^{T}{L_{t}}

使用随机梯度下降法训练RNN其实就是对W_{x} ,W_{s} ,W_{o} ,b_1,b_2 求偏导,并不断调整它们以使L尽可能达到最小的过程。我们只对t3时刻的  W_{x} ,W_{s} ,W_{o}求偏导(其他时刻类似):

\\\frac{\partial{L_{3}}}{\partial{W_{0}}}=\frac{\partial{L_{3}}}{\partial{O_{3}}}\frac{\partial{O_{3}}}{\partial{W_{o}}}

\frac{\partial{L_{3}}}{\partial{W_{x}}}=\frac{\partial{L_{3}}}{\partial{O_{3}}}\frac{\partial{O_{3}}}{\partial{S_{3}}}\frac{\partial{S_{3}}}{\partial{W_{x}}}+\frac{\partial{L_{3}}}{\partial{O_{3}}}\frac{\partial{O_{3}}}{\partial{S_{3}}}\frac{\partial{S_{3}}}{\partial{S_{2}}}\frac{\partial{S_{2}}}{\partial{W_{x}}}+\frac{\partial{L_{3}}}{\partial{O_{3}}}\frac{\partial{O_{3}}}{\partial{S_{3}}}\frac{\partial{S_{3}}}{\partial{S_{2}}}\frac{\partial{S_{2}}}{\partial{S_{1}}}\frac{\partial{S_{1}}}{\partial{W_{x}}}\\

\frac{\partial{L_{3}}}{\partial{W_{s}}}=\frac{\partial{L_{3}}}{\partial{O_{3}}}\frac{\partial{O_{3}}}{\partial{S_{3}}}\frac{\partial{S_{3}}}{\partial{W_{s}}}+\frac{\partial{L_{3}}}{\partial{O_{3}}}\frac{\partial{O_{3}}}{\partial{S_{3}}}\frac{\partial{S_{3}}}{\partial{S_{2}}}\frac{\partial{S_{2}}}{\partial{W_{s}}}+\frac{\partial{L_{3}}}{\partial{O_{3}}}\frac{\partial{O_{3}}}{\partial{S_{3}}}\frac{\partial{S_{3}}}{\partial{S_{2}}}\frac{\partial{S_{2}}}{\partial{S_{1}}}\frac{\partial{S_{1}}}{\partial{W_{s}}}

可以看出对于 W_{o} 求偏导并没有长期依赖,但是对于 W_{x} ,W_{s} 求偏导,会随着时间序列产生长期依赖。因为 S_t随着时间序列向前传播,而 S_t又是  W_{x} ,W_{s} 的函数。

根据上述求偏导的过程,我们可以得出任意时刻对 W_{x} ,W_{s} 求偏导的公式:

\frac{\partial{L_{t}}}{\partial{W_{x}}}=\sum_{k=0}^{t}{\frac{\partial{L_{t}}}{\partial{O_{t}}}\frac{\partial{O_{t}}}{\partial{S_{t}}}}(\prod_{j=k+1}^{t}{\frac{\partial{S_{j}}}{\partial{S_{j-1}}}})\frac{\partial{S_{k}}}{\partial{W_{x}}}

任意时刻对W_{s}求偏导的公式同上。

如果加上激活函数,S_{j}=tanh(W_{x}X_{j}+W_{s}S_{j-1}+b_{1}),则

\prod_{j=k+1}^{t}{\frac{\partial{S_{j}}}{\partial{S_{j-1}}}} = \prod_{j=k+1}^{t}{tanh^{'}}W_{s}

因为 tanh^{'}\leq1,如果W_{s}中的值也是大于0小于1的值,则当t很大时 ,\prod_{j=k+1}^{t}{tanh^{'}}W_{s}接近于0.

如果W_{s}中的值很大时,\prod_{j=k+1}^{t}{tanh^{'}}W_{s}接近于无穷,这就是RNN中梯度消失和爆炸的原因。

梯度裁剪

避免梯度爆炸可以采用梯度截断的方法。所谓梯度截断是指将梯度值超过阈值的梯度降到阈值以内。虽然梯度截断会一定程度上改变梯度的方向,但梯度截断的方向依旧是朝向损失函数减小的方向。

from torch.nn.utils import clip_grad_norm

def clip_gradient(optimizer, grad_clip):
    """
    Clips gradients computed during backpropagation to avoid explosion of gradients.

    :param optimizer: optimizer with the gradients to be clipped
    :param grad_clip: clip value
    """
    for group in optimizer.param_groups:
        for param in group["params"]:
            if param.grad is not None:
                param.grad.data.clamp_(-grad_clip, grad_clip)# 将梯度限制在(-grad_clip, grad_clip)内

我们还可以通过以下一些方法缓解梯度消失和梯度爆炸

relu、leakyrelu等激活函数

relu,在小于0的时候梯度为0,大于0的时候梯度恒为1,这样使得每层的网络得到的梯度更新速度都一样。

relu的主要贡献:解决了梯度消失和梯度爆炸的问题,计算方便计算速度快(梯度恒定为0或1)加速了网络的训练

但由于负数部分恒为0,导致一些神经元无法激活(可通过设置小学习率部分解决)且输出并不是零中心化的

leakyrelu就是为了解决relu的0区间带来的影响,在小于0的区间,梯度为很小的数(非零),leakyrelu解决了0区间带来的影响,而且包含了relu所有优点。

Batch Normalization(批规范化)

BN通过对每一层的输出归一化为均值和方差一致的方法,消除了 W_{s}过大过小带来的影响,也可以理解为BN将输出从饱和区拉到了非饱和区(比如Sigmoid函数)。

残差结构

。。。


LSTM也是为了解决梯度消失或爆炸问题。换句话说,它可以解决RNN的长期依赖问题。再换句话说,他可以拥有较RNN更长的序列信息学习能力。

LSTM(长短期记忆网络)

而LSTM之所以能够解决RNN的长期依赖问题,是因为LSTM引入了门(gate)机制用于控制特征的流通和损失。

LSTM形式

绝大部分文章中,LSTM长这个样子:

结合之前的RNN结构看,它说明了在每一个时刻,隐藏层都经过了几个特殊的运算(门的运算),然后将信息往后传递。每一个cell的具体信息如图:

各个门(f:遗忘门,i:输入门,o:输出门)的计算方式如下: 

理解LSTM结构

C_t 称作”cell state“,他是让梯度随着时间的流动不发生指数级消失或者爆炸的关键。也是LSTM保持长期记忆的关键部件。我们之前分析了,传递过程中,隐藏层权重W_s值的变化是导致梯度消失或爆炸的原因之一。

\prod_{j=k+1}^{t}{\frac{\partial{S_{j}}}{\partial{S_{j-1}}}} = \prod_{j=k+1}^{t}{tanh^{'}}W_{s}

考虑极端的条件,如果信息在传递过程中,不加以权重。那么我们可以用 C_t = C_{t-1} 来输送信息,而无需考虑梯度的消失或爆炸。

当然,我们不能单纯这样做,我们在C_{t-1} 的基础上,进行每一个timestep 的信息加减。具体来说,在t 时刻,是用遗忘门 f_t 与 前一个cell state C_{t-1} 作Hadamard product(按元素相乘),然后用输入门  i_t 与当前 cell g_t 作Hadamard product ,然后两者求和。

我们发现,计算式中,遗忘门,输入门,输出门都是[x_t,h_{t-1}] 的拼接参与运算,然后用sigmoid激活,h_{t-1} 是上一时刻的隐藏状态输出(短时记忆)。sigmoid的输出是在0-1之间的。这意味着,f_t \odot c_{t-1} 的时候相当于将c_{t-1} 的信息作了部分舍弃,我想这也就是他被称作"遗忘门"的原因。 同理 i_t \odot g_{t} 计算出需要有选择性增添的信息。两者求和,就完成了信息的忘记和增加。

输出也是一样的,用tanh 将当前的cell state 约束回[0,1] ,然后以o_t \odot tanh(c_{t}) 的方式进行有选择的输出。相当于对积累的记忆进行了挑选。

梯度爆炸和消失的解决

还记得之前RNN反向传播的计算公式吗?

\frac{\partial{L_{t}}}{\partial{W_{x}}}=\sum_{k=0}^{t}{\frac{\partial{L_{t}}}{\partial{O_{t}}}\frac{\partial{O_{t}}}{\partial{S_{t}}}}(\prod_{j=k+1}^{t}{\frac{\partial{S_{j}}}{\partial{S_{j-1}}}})\frac{\partial{S_{k}}}{\partial{W_{x}}}

其中,\prod_{j=k+1}^{t}{\frac{\partial{S_{j}}}{\partial{S_{j-1}}}} = \prod_{j=k+1}^{t}{tanh^{'}}W_{s},由于W_s中值的不同,最终导致,梯度消失或爆炸。

我们将LSTM抽象成这样:

f_t,i_t,o_t分别表示三个门,\bigotimes 表示sigmoid函数。他的值是介于0到1之间的,刚好用趋近于0时表示流入不能通过gate,趋近于1时表示流入可以通过gate。

f_{t}=\sigma({W_{f}X_{t}}+b_{f})

i_{t}=\sigma({W_{i}X_{t}}+b_{i})

o_{i}=\sigma({W_{o}X_{t}}+b_{o})

当前的状态 S_{t}=f_{t}S_{t-1}+i_{t}X_{t},展开后得:S_{t}=\sigma(W_{f}X_{t}+b_{f})S_{t-1}+\sigma(W_{i}X_{t}+b_{i})X_{t}

最后加上激活函数:S_{t}=tanh\left[\sigma(W_{f}X_{t}+b_{f})S_{t-1}+\sigma(W_{i}X_{t}+b_{i})X_{t}\right]

那么,在LSTM中:

\prod_{j=k+1}^{t}\frac{\partial{S_{j}}}{\partial{S_{j-1}}}=\prod_{j=k+1}^{t}tanh{'}\sigma(W_fX_t+b_f),

tanh{'} 的图像如下:

sigmoid 图像:

 \sigma(x)的值是【0,1】的,所以

\prod_{j=k+1}^{t}\frac{\partial{S_{j}}}{\partial{S_{j-1}}}=\prod_{j=k+1}^{t}tanh{'}\sigma(W_fX_t+b_f) \approx0|1这样就解决了传统RNN中梯度消失的问题。

pytorch中的LSTM

详细API 请参考:pytorch 官方文档链接

结合图示(上面RNN的展示图),我们知道,定义一个RNN,需要告诉他输入的向量长度(拿上面文本处理举例,即指定padding 后的 sentence length),隐藏层的节点数量,RNN堆叠的层数,默认是1层。

其他一些参数解释如下:

Parameters

  • input_size – The number of expected features in the input x

  • hidden_size – The number of features in the hidden state h

  • num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two LSTMs together to form a stacked LSTM, with the second LSTM taking in outputs of the first LSTM and computing the final results. Default: 1

  • bias – If False, then the layer does not use bias weights b_ih and b_hh. Default: True

  • batch_first – If True, then the input and output tensors are provided as (batch, seq, feature). Default: False

  • dropout – If non-zero, introduces a Dropout layer on the outputs of each LSTM layer except the last layer, with dropout probability equal to dropout. Default: 0

  • bidirectional – If True, becomes a bidirectional LSTM. Default: False

一个示例: 

#定义一个LSTM你需要定义输入的向量长度,隐藏节点数量,和LSTM堆叠的层数。

rnn = nn.LSTM(10, 20, 2) # 一个单词向量长度为10,隐藏层节点数为20,LSTM有2层
input = torch.randn(5, 3, 10) # 输入数据由3个句子组成(batch),每个句子由5个单词组成,单词向量长度为10
h0 = torch.randn(2, 3, 20) # LSTM层数*方向为2*1, batch为3, 隐藏层节点数为20
c0 = torch.randn(2, 3, 20) # 同上
output, (hn, cn) = rnn(input, (h0, c0))

print(output.shape, hn.shape, cn.shape)

>>> torch.Size([5, 3, 20]) 
torch.Size([2, 3, 20]) torch.Size([2, 3, 20])

可以发现,定义好LSTM之后, 我们需要初始化 h_0,c_0,pytorch 中两者默认初始化输入方式为:(nlayers* direction , batch_size , hidden),如之前例子所述,LSTM一次会跑完整个句子,output 在最后一个时刻跑完后产生,他实际上是包含所有时刻hidden的tensor,维度为[sequence_length *  batch_size * hidden_size]。hn,cn在每一个时刻都会产生。

参数的估计

LSTM参数个数与timesteps无关。如图,我们可以看到,涉及到的参数都在f_t,i_t,g_t,o_t 四步的运算中。输入都是[x_t,h_{t-1}] 的拼接,计算后输出的C_t 与 h_t 等维度。

那么假设输入x_t 的维度为 X , 隐藏层维度为 H,那么W 参数矩阵的维度应该为(X+H)* H, f_t,i_t,g_t,o_t 四步的运算的操作都可简化为f(Wx+b),  b 的 维度显然是H 的维度显然是 H * 1 ,于是总参数为:4 * ((X+H)* H + H)

GRU

GRU(Gate Recurrent Unit)是循环神经网络(Recurrent Neural Network, RNN)的一种。和LSTM(Long-Short Term Memory)一样,也是为了解决长期记忆和反向传播中的梯度等问题而提出来的。相比LSTM,使用GRU能够达到相当的效果,并且相比之下更容易进行训练,能够很大程度上提高训练效率。

GRU结构如下:

preview

GRU的两个门,分别是reset gate r_t 和 update gate z_t 计算方法和LSTM中门的计算方法类似。 选隐藏层(candidate hidden layer)\tilde{h}_t 相当于LSTM 中的 \tilde{c}_t ,r_t  控制信息的保留,最后 z_t 控制需要从前一时刻的隐藏层 h_{t-1}中遗忘多少信息,需要加入多少当前 时刻的隐藏层信息 \tilde{h}_t

参考文献:

  1. Chung J, Gulcehre C, Cho K, et al. Empirical evaluation of gated recurrent neural networks on sequence modeling[J]. arXiv: Neural and Evolutionary Computing, 2014.
  2. Understanding LSTM Networks
  3. https://en.wikipedia.org/wiki/Long_short-term_memory
  4. RNN梯度消失和爆炸的原因 - 知乎
  5. torch.nn — PyTorch 1.11.0 documentation

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半九拾

援助一个bug吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值