长短期记忆网络 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非常适合用于处理与时间序列高度相关的问题,例如机器翻译、对话生成、编码\\解码等。