神经网络学习笔记(三)——长短期记忆网络LSTM
长短期记忆网络 LSTM
文章目录
- 长短期记忆网络 LSTM
- 一、概述
- 二、背景
- 三、LSTM原理
- 3.1 模型结构
- 3.2 前向传播
- 3.3 反向传播
- 3.4 LSTM的变体
- 3.4.1 Peephole Connection
- 3.4.2 Coupled
一、概述
长短期记忆网络——通常被称为LSTM,是一种特殊的RNN,能够学习长期依赖性。由Hochreiter和Schmidhuber(1997)提出,并且在接下来的工作中被许多人改进和推广。LSTM 在各种各样的问题上表现非常出色,现在被广泛使用。LSTM被明确设计用来避免长期依赖性问题。LSTM单元由单元,输入门,输出门和忘记门组成。该单元记住任意时间间隔内的值,并且三个门控制进出单元的信息流。
LSTM网络非常适合基于时间序列数据进行分类,处理和预测,因为在时间序列中的重要事件之间可能存在未知持续时间的滞后。开发LSTM是为了处理在训练传统RNN时可能遇到的爆炸和消失的梯度问题。对于间隙长度的相对不敏感性是LSTM相对于RNN,隐马尔可夫模型和其他序列学习方法在许多应用中的优势。
二、背景
传统RNN的关键点之一就是他们可以用来连接先前的信息到当前的任务上,例如使用过去的视频段来推测对当前段的理解。但是会有一些复杂的场景。上下文距离预测词较远,即相关信息和当前预测位置之间的间隔相当的大,在这个间隔不断增大时,传统RNN会丧失学习到连接如此远的信息的能力。
循环神经网络中的LSTM可以解决这种问题,即长短期记忆网络。LSTM引入了门(gate)机制用于控制特征的流通和损失,其中输入门用来接受近期有用的信息,遗忘门用来对久远的、无用的信息选择性的遗忘,输出门的输出为根据当前状态决定的输出。可以解决RNN无法处理长距离的依赖的问题。
三、LSTM原理
3.1 模型结构
原始RNN的隐藏层只有一个状态h,对于短期的输入非常敏感。LSTM再增加一个状态c,用来保存长期的状态,称为单元状态(cell state)。
在ttt时刻,LSTM的输入有三个:
- 当前时刻网络的输入值xtx_txt
- 上一时刻LSTM的输出值 ht−1h_{t-1}ht−1
- 上一时刻的单元状态ct−1c_{t-1}ct−1
LSTM的输出有两个:
- 当前时刻LSTM输出值hth_tht
- 当前时刻的单元状态ctc_tct
在LSTM模型结构中,采用门(gate)来控制长期状态,在一层模型里有三个门,分别作用为:
- 负责控制继续保存长期状态ccc
- 负责控制把即时状态输入到长期状态ccc
- 负责控制是否把长期状态ccc作为当前的LSTM的输出
gate实际上就是一层全连接层,输入是一个向量,输出是一个0到1之间的实数向量。公式为:g(x)=σ(Wx+b)g(x) = \sigma(Wx+b)g(x)=σ(Wx+b)
3.2 前向传播
LSTM每个模块中的具体结构如下:
遗忘门(forget gate):决定了上一时刻的单元状态ct−1c_{t-1}ct−1如何保留到当前时刻 ctc_tct。
遗忘阶段是对上一个节点传进来的输入进行选择性忘记。简单来说就是会 “忘记不重要的,记住重要的”。
具体来说是通过计算得到的ftf_tft(f表示forget)来作为遗忘门控,来控制上一个状态的 ct−1c_{t-1}ct−1的忘记的概率。
ft=σ(Wf⋅[ht−1,xt]+bf)f_t = \sigma(W_f·[h_{t-1},x_t]+b_f)ft=σ(Wf⋅[ht−1,xt]+bf)
输入门(input gate):决定了当前时刻网络的输入xtx_txt如何保存到单元状态ctc_tct。
这个阶段确定什么样的新信息被存放在细胞状态中。这里包含两个部分:1)sigmoid层为 “输入门层” ,主要对输入xtx_txt进行选择记忆。2)tanh层创建一个新的候选值向量C~t\tilde{C}_tC~t,加入到状态中。
it=σ(Wi⋅[ht−1,xt]+bi)i_t = \sigma(W_i·[h_{t-1},x_t]+b_i)it=σ(Wi⋅[ht−1,xt]+bi)
C~t=tanh(WC⋅[ht−1,xt]+bC)\tilde{C}_t = \tanh(W_C·[h_{t-1},x_t]+b_C)C~t=tanh(WC⋅[ht−1,xt]+bC)
细胞更新(Update Cell):决定了如何计算当前序列下的细胞值CtC_tCt。
新的细胞状态由两部分组成,1)旧细胞Ct−1C_{t-1}Ct−1与ftf_tft相乘,丢弃掉之前序列的信息;2)新的候选值C~t\tilde{C}_tC~t与比例系数iti_tit的积,保留当前的输入信息。
Ct=ft⊙Ct−1+it⊙C~tC_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_tCt=ft⊙Ct−1+it⊙C~t
其中,⊙\odot⊙为Hadamard积
输出门(output gate):控制单元状态CtC_tCt有多少输出到LSTM的当前输出值hth_tht。
隐藏状态hth_tht的更新由两部分组成:1)oto_tot, 它由上一序列的隐藏状态ht−1h_{t−1}ht−1和输入数据xtx_txt构成,通过激活函数sigmoid进行过滤;2)由隐藏状态CtC_tCt和tanh函数构成,tanh将CtC_tCt处理得到一个在(−1,1)(-1,1)(−1,1)之间的值,然后将其与sigmoid门相乘得到hth_tht。
ot=σ(Wo⋅[ht−1,xt]+bo)o_t = \sigma(W_o·[h_{t-1},x_t]+b_o)ot=σ(Wo⋅[ht−1,xt]+bo)
ht=ot⊙tanh(Ct)h_t = o_t \odot \tanh(C_t)ht=ot⊙tanh(Ct)
3.3 反向传播
算法框架:采用反向传播算法
- 前向计算每个神经元的输出值,即ftf_tft、iti_tit、ctc_tct、oto_tot、hth_tht五个向量的值。
- 反向计算每个神经元的误差项δ\deltaδ值。包括两个方向:1)沿时间的反向传播,即从当前ttt时刻开始,计算每个时刻的误差项;2)将误差项向上一层传播。
- 根据相应的误差项,计算每个权重的梯度。
对于激活函数,先求出其导数:
σ(z)=defy=11+e−z⇒σ′(z)=y(1−y)\sigma(z) \overset{\underset{def}{}}{=} y = \frac{1}{1+e^{-z}} \Rightarrow \sigma'(z) = y(1-y)σ(z)=defy=1+e−z1⇒σ′(z)=y(1−y)
tanh(z)=defy=ez−e−zez+e−z⇒tanh′(z)=1−y2\tanh(z) \overset{\underset{def}{}}{=} y = \frac{e^z-e^{-z}}{e^z+e^{-z}} \Rightarrow \tanh'(z) = 1-y^2tanh(z)=defy=ez+e−zez−e−z⇒tanh′(z)=1−y2
在ttt时刻,LSTM的输出值为hth_tht,定义此时的误差项δt\delta_tδt为
δt=def∂L∂ht\delta_t \overset{\underset{def}{}}{=} \frac{\partial L}{\partial h_t}δt=def∂ht∂L
误差项沿时间的反向传递
δt−1T=∂L∂ht−1=∂L∂ht∂ht∂ht−1=δtT∂ht∂ht−1\delta^T_{t-1} = \frac{\partial L}{\partial h_{t-1}} = \frac{\partial L}{\partial h_{t}} \frac{\partial h_t}{\partial h_{t-1}} = \delta_t^T \frac{\partial h_t}{\partial h_{t-1}}δt−1T=∂ht−1∂L=∂ht∂L∂ht−1∂ht=δtT∂ht−1∂ht
误差项沿时间的反向传递
由于有ht=ot⊙tanh(Ct)h_t = o_t \odot \tanh(C_t)ht=ot⊙tanh(Ct)和Ct=ft⊙Ct−1+it⊙C~tC_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_tCt=ft⊙Ct−1+it⊙C~t,利用全导数,计算得出
δt−1=δo,jTWoh+δf,jTWfh+δi,jTWih+δC~t,jTWCh\delta_{t-1} = \delta^T_{o,j}W_{oh} + \delta^T_{f,j}W_{fh} + \delta^T_{i,j}W_{ih} + \delta^T_{\tilde{C}_t,j}W_{Ch}δt−1=δo,jTWoh+δf,jTWfh+δi,jTWih+δC~t,jTWCh
又因为有
{δo,tT=δtT⊙tanh(Ct)⊙ot⊙(1−ot)δf,tT=δtT⊙ot⊙(1−tanh(Ct)2)⊙Ct−1⊙ft⊙(1−ft)δi,tT=δtT⊙ot⊙(1−tanh(Ct)2)⊙C~t⊙it⊙(1−it)δC~t,tT=δtT⊙ot⊙(1−tanh(Ct)2)⊙it⊙(1−C~t2)\begin{cases}
\delta_{o,t}^T = \delta_t^T \odot \tanh(C_t) \odot o_t \odot (1-o_t) \\
\delta_{f,t}^T = \delta_t^T \odot o_t \odot (1-\tanh(C_t)^2) \odot C_{t-1}\odot f_t \odot (1-f_t) \\
\delta_{i,t}^T = \delta_t^T \odot o_t \odot (1-\tanh(C_t)^2) \odot {\tilde{C}_t} \odot i_t \odot (1-i_t) \\
\delta_{{\tilde{C}_t},t}^T = \delta_t^T \odot o_t \odot (1-\tanh(C_t)^2) \odot i_t \odot (1-{\tilde{C}_t}^2)
\end{cases}⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧δo,tT=δtT⊙tanh(Ct)⊙ot⊙(1−ot)δf,tT=δtT⊙ot⊙(1−tanh(Ct)2)⊙Ct−1⊙ft⊙(1−ft)δi,tT=δtT⊙ot⊙(1−tanh(Ct)2)⊙C~t⊙it⊙(1−it)δC~t,tT=δtT⊙ot⊙(1−tanh(Ct)2)⊙it⊙(1−C~t2)
带入上式可得出
δkT=∏j=kt−1δo,jTWoh+δf,jTWfh+δi,jTWih+δC~t,jTWCh\delta_k^T = \prod_{j=k}^{t-1} \delta^T_{o,j}W_{oh} + \delta^T_{f,j}W_{fh} + \delta^T_{i,j}W_{ih} + \delta^T_{\tilde{C}_t,j}W_{Ch}δkT=j=k∏t−1δo,jTWoh+δf,jTWfh+δi,jTWih+δC~t,jTWCh
将误差项传递到上一层
如果当前层为第lll层,设l−1l-1l−1层的误差项是误差函数对l−1l-1l−1层加权输入的导数:
δtl−1=def∂Lnettl−1\delta_t^{l-1} \overset{\underset{def}{}}{=} \frac{\partial L}{net_t^{l-1}}δtl−1=defnettl−1∂L
则LSTM的输入可以表示为:
xtl=fl−1(netTl−1)x_t^l = f^{l-1}(net_T^{l-1})xtl=fl−1(netTl−1)
其中fl−1f^{l-1}fl−1表示第l−1l-1l−1层的激活函数。
将nettl−1net_t^{l-1}nettl−1展开,并利用全导数公式,得到
∂Lnettl−1=δf,tTWfx+δi,tTWix+δC~,tTWCx+δo,tTWox⊙f′(nettl−1)\frac{\partial L}{net_t^{l-1}} = \delta^T_{f,t}W_{fx} + \delta^T_{i,t}W_{ix} + \delta^T_{\tilde{C},t}W_{Cx} + \delta^T_{o,t}W_{ox} \odot f'(net_t^{l-1})nettl−1∂L=δf,tTWfx+δi,tTWix+δC~,tTWCx+δo,tTWox⊙f′(nettl−1)
权重梯度计算
最终的梯度值为各个时刻的梯度之和:
∂L∂Woh=∑t∂L∂Woh,t=∂L∂neto,t∂neto,t∂Woh,t=∑tδo,jhj−1T\frac{\partial L}{\partial W_{oh}} = \sum_t \frac{\partial L}{\partial W_{oh,t}} = \frac{\partial L}{\partial net_{o,t}} \frac{\partial net_{o,t}}{\partial W_{oh,t}} = \sum_t \delta_{o,j}h_{j-1}^T∂Woh∂L=t∑∂Woh,t∂L=∂neto,t∂L∂Woh,t∂neto,t=t∑δo,jhj−1T
同理可求得其它梯度为
{∂L∂Wfh=∑tδf,jhj−1T∂L∂Wih=∑tδi,jhj−1T∂L∂WCh=∑tδC~,jhj−1T\begin{cases}
\frac{\partial L}{\partial W_{fh}} = \sum_t \delta_{f,j}h_{j-1}^T \\
\frac{\partial L}{\partial W_{ih}} = \sum_t \delta_{i,j}h_{j-1}^T \\
\frac{\partial L}{\partial W_{Ch}} = \sum_t \delta_{\tilde{C},j}h_{j-1}^T
\end{cases}⎩⎪⎨⎪⎧∂Wfh∂L=∑tδf,jhj−1T∂Wih∂L=∑tδi,jhj−1T∂WCh∂L=∑tδC~,jhj−1T
对于偏置项bbb的梯度,按照同样的方式可以求得
{∂L∂bo=∑tδo,j∂L∂bi=∑tδi,j∂L∂bf=∑tδf,j∂L∂bC=∑tδC,j\begin{cases}
\frac{\partial L}{\partial b_o} = \sum_t \delta_{o,j} \\
\frac{\partial L}{\partial b_i} = \sum_t \delta_{i,j} \\
\frac{\partial L}{\partial b_f} = \sum_t \delta_{f,j} \\
\frac{\partial L}{\partial b_C} = \sum_t \delta_{C,j} \\
\end{cases}⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧∂bo∂L=∑tδo,j∂bi∂L=∑tδi,j∂bf∂L=∑tδf,j∂bC∂L=∑tδC,j
对于输入xxx的权重矩阵的梯度,只需要根据相应的误差项直接计算:
{∂L∂Wox=δo,txtT∂L∂Wfx=δf,txtT∂L∂Wix=δi,txtT∂L∂WCx=δC,txtT\begin{cases}
\frac{\partial L}{\partial W_{ox}} = \delta_{o,t} x_t^T \\
\frac{\partial L}{\partial W_{fx}} = \delta_{f,t} x_t^T \\
\frac{\partial L}{\partial W_{ix}} = \delta_{i,t} x_t^T \\
\frac{\partial L}{\partial W_{Cx}} = \delta_{C,t} x_t^T \\
\end{cases}⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧∂Wox∂L=δo,txtT∂Wfx∂L=δf,txtT∂Wix∂L=δi,txtT∂WCx∂L=δC,txtT
3.4 LSTM的变体
3.4.1 Peephole Connection
让门层也会接受细胞状态的输入。
{ft=σ(Wf⋅[Ct−1,ht−1,xt]+bf)it=σ(Wi⋅[Ct−1,ht−1,xt]+bi)ot=σ(Wo⋅[Ct−1,ht−1,xt]+bo)\begin{cases} f_t = \sigma(W_f·[C_{t-1},h_{t-1},x_t]+b_f) \\ i_t = \sigma(W_i·[C_{t-1},h_{t-1},x_t]+b_i) \\ o_t = \sigma(W_o·[C_{t-1},h_{t-1},x_t]+b_o) \end{cases}⎩⎪⎨⎪⎧ft=σ(Wf⋅[Ct−1,ht−1,xt]+bf)it=σ(Wi⋅[Ct−1,ht−1,xt]+bi)ot=σ(Wo⋅[Ct−1,ht−1,xt]+bo)
3.4.2 Coupled
将遗忘门和输入门合二为一,一同做出决定。即仅仅会当将要输入在当前位置时遗忘;仅仅输入新值到已经遗忘旧信息的那些状态。
Ct=ft⊙Ct−1+(1−ft)⊙C~tC_t = f_t \odot C_{t-1} + (1-f_t) \odot \tilde{C}_tCt=ft⊙Ct−1+(1−ft)⊙C~t
四、LSTM的简单使用
在自然语言处理中,LSTM适合用于处理与时间序列高度相关的问题。相较于传统的RNN模型,LSTM对长期记忆的表现更好。利用nn.LSTM模型对语料进行了文本分类的处理。
首先从文件中读取相应训练集和测试集的语料,利用jieba对文本进行分词,然后利用nltk去停用词。这里nltk_data中并没有中文的停用词,我从网上找了一个中文的停用词文件放在对应的stopwords目录下,并命名为chinese。
def tokenizer(text): sentence = jieba.lcut(text, cut_all=False) stopwords = stopwords.words('chinese') sentence = [_ for _ in sentence if _ not in stopwords] return sentence
利用torchtext包处理预处理好的语料,将所提供的语料集转化为相应的词向量模型。由于每一个词均为一个向量,作为LSTM模型的输入。
train_set, validation_set = data.TabularDataset.splits( path='corpus_data/', skip_header=True, train='corpus_train.csv', validation='corpus_validation.csv', format='csv', fields=[('label', label), ('text', text)], ) text.build_vocab(train_set, validation_set)
将处理好的词向量输入到lstm模型中进行处理。
self.lstm = nn.lstm(embedding_dim, self.hidden_size, batch_first=True) self.linear = nn.Linear(self.hidden_size, label_num) def forward(self, x): # 输入x的维度为(batch_size, max_len), x = self.embedding(x) # 经过embedding,x的维度为(batch_size, time_step, embedding_dim) # 隐层初始化 # h0,c0维度为(direction_num, batch_size, hidden_size) h0 = torch.rand(1, x.size(0), self.hidden_size) c0 = torch.zeros(1, x.size(0), self.hidden_size) # out维度为(batch_size, seq_length, hidden_size) out, (hn, cn) = self.lstm(x, (h0,c0)) # 只需要最后一步的输出状态 out = out[:, -1, :] out = self.linear(out) return out
利用训练集对模型进行训练,同时评估训练效果,并利用测试集对模型的准确性进行评估。为了防止偶然性产生的不确定,每一轮迭代会产生100个模型,分别评估其效率,进行调优后再用测试集测试其效率。
在训练初期,准确率大概在50%左右。在第4轮迭代时准确率有显著提升,第5,6轮时基本能达到85%到90%准确度,收敛效果好于普通RNN。
多轮迭代之后,模型的准确率可以达到90%以上。
五、总结
- 通过门控状态来控制传输状态,记住需要长时间记忆的,忘记不重要的信息,对很多需要“长期记忆”的任务来说,尤其好用。
- 门机制极大的减轻了梯度消失问题,简化了调参复杂度;门机制提供了特征过滤,丰富了向量的表示信息。
- 每个LSTM的cell里面都有4个全连接层,如果LSTM的时间跨度很大,并且网络又很深,则计算量会很大。
- 在语言处理任务中,LSTM非常适合用于处理与时间序列高度相关的问题,例如机器翻译、对话生成、编码\解码等。
- [神经网络学习笔记]长短期记忆模型(Long-Short Term Memory,LSTM)综述
- 深度学习笔记(八)LSTM长短期记忆网络
- 循环神经网络RNN和长短期循环神经网络LSTM理论学习笔记
- 5 什么是LSTM-RNN(长短期记忆循环神经网络)?
- 循环神经网络:从RNN到LSTM(学习笔记01)
- 深度学习笔记八:长短时记忆网络LSTM(基本理论)
- 【深度学习】RNN(循环神经网络)之LSTM(长短时记忆)
- 理解长短期记忆(LSTM) 神经网络
- 深度学习笔记——循环神经网络RNN/LSTM
- 神经网络梯度爆炸、消失问题、门控循环单元GRU、长短期记忆LSTM
- LSTM入门必读:从入门基础到工作方式详解 By 机器之心2017年7月24日 12:57 长短期记忆(LSTM)是一种非常重要的神经网络技术,其在语音识别和自然语言处理等许多领域都得到了广泛的应用
- 长短期记忆(LSTM)网络预测系列(python)_大结局(内含学习目录)
- RNN循环神经网络以及LSTM长短期记忆模型-简介
- 王小草【深度学习】笔记第六弹--循环神经网络RNN和LSTM
- 深度学习——循环神经网络/递归神经网络(RNN)及其改进的长短时记忆网络(LSTM)
- 长短期记忆(LSTM)系列_LSTM的特性学习(2)——如何用stateful配置有状态的LSTM网络 代码示例
- tensorflow 学习笔记12 循环神经网络RNN LSTM结构实现MNIST手写识别
- 长短期记忆(LSTM)系列_LSTM的特性学习(3)——如何构建“序列 —>序列”(多对多)的LSTM网络模型
- TensorFlow人工智能引擎入门教程之九 RNN/LSTM循环神经网络长短期记忆网络使用
- 神经网络学习笔记(八):线性回归模型(下)