长短期记忆网络——通常被称为LSTM,是一种特殊的RNN,能够学习长期依赖性。由Hochreiter和Schmidhuber(1997)提出,并且在接下来的工作中被许多人改进和推广。LSTM 在各种各样的问题上表现非常出色,现在被广泛使用。LSTM被明确设计用来避免长期依赖性问题。LSTM单元由单元,输入门,输出门和忘记门组成。该单元记住任意时间间隔内的值,并且三个门控制进出单元的信息流。
LSTM网络非常适合基于时间序列数据进行分类,处理和预测,因为在时间序列中的重要事件之间可能存在未知持续时间的滞后。开发LSTM是为了处理在训练传统RNN时可能遇到的爆炸和消失的梯度问题。对于间隙长度的相对不敏感性是LSTM相对于RNN,隐马尔可夫模型和其他序列学习方法在许多应用中的优势。
传统RNN的关键点之一就是他们可以用来连接先前的信息到当前的任务上,例如使用过去的视频段来推测对当前段的理解。但是会有一些复杂的场景。上下文距离预测词较远,即相关信息和当前预测位置之间的间隔相当的大,在这个间隔不断增大时,传统RNN会丧失学习到连接如此远的信息的能力。
循环神经网络中的LSTM可以解决这种问题,即长短期记忆网络。LSTM引入了门(gate)机制用于控制特征的流通和损失,其中输入门用来接受近期有用的信息,遗忘门用来对久远的、无用的信息选择性的遗忘,输出门的输出为根据当前状态决定的输出。可以解决RNN无法处理长距离的依赖的问题。
原始RNN的隐藏层只有一个状态h,对于短期的输入非常敏感。LSTM再增加一个状态c,用来保存长期的状态,称为单元状态(cell state)。
在 t t t时刻,LSTM的输入有三个:
LSTM的输出有两个:
在LSTM模型结构中,采用门(gate)来控制长期状态,在一层模型里有三个门,分别作用为:
gate实际上就是一层全连接层,输入是一个向量,输出是一个0到1之间的实数向量。公式为: g ( x ) = σ ( W x + b ) g(x) = \sigma(Wx+b) g(x)=σ(Wx+b)
遗忘门(forget gate):决定了上一时刻的单元状态 c t − 1 c_{t-1} ct−1如何保留到当前时刻 c t c_t ct。
遗忘阶段是对上一个节点传进来的输入进行选择性忘记。简单来说就是会 “忘记不重要的,记住重要的”。
具体来说是通过计算得到的 f t f_t ft(f表示forget)来作为遗忘门控,来控制上一个状态的 c t − 1 c_{t-1} ct−1的忘记的概率。
f t = σ ( W f ⋅ [ h t − 1 , x t ] + b f ) f_t = \sigma(W_f·[h_{t-1},x_t]+b_f) ft=σ(Wf⋅[ht−1,xt]+bf)
输入门(input gate):决定了当前时刻网络的输入 x t x_t xt如何保存到单元状态 c t c_t ct。
这个阶段确定什么样的新信息被存放在细胞状态中。这里包含两个部分:1)sigmoid层为 “输入门层” ,主要对输入 x t x_t xt进行选择记忆。2)tanh层创建一个新的候选值向量 C ~ t \tilde{C}_t C~t,加入到状态中。
i t = σ ( W i ⋅ [ h t − 1 , x t ] + b i ) i_t = \sigma(W_i·[h_{t-1},x_t]+b_i) it=σ(Wi⋅[ht−1,xt]+bi)
C ~ t = tanh ( W C ⋅ [ h t − 1 , x t ] + b C ) \tilde{C}_t = \tanh(W_C·[h_{t-1},x_t]+b_C) C~t=tanh(WC⋅[ht−1,xt]+bC)
细胞更新(Update Cell):决定了如何计算当前序列下的细胞值 C t C_t Ct。
新的细胞状态由两部分组成,1)旧细胞 C t − 1 C_{t-1} Ct−1与 f t f_t ft相乘,丢弃掉之前序列的信息;2)新的候选值 C ~ t \tilde{C}_t C~t与比例系数 i t i_t it的积,保留当前的输入信息。
C t = f t ⊙ C t − 1 + i t ⊙ C ~ t C_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t Ct=ft⊙Ct−1+it⊙C~t
其中, ⊙ \odot ⊙为Hadamard积
输出门(output gate):控制单元状态 C t C_t Ct有多少输出到LSTM的当前输出值 h t h_t ht。
隐藏状态 h t h_t ht的更新由两部分组成:1) o t o_t ot, 它由上一序列的隐藏状态 h t − 1 h_{t−1} ht−1和输入数据 x t x_t xt构成,通过激活函数sigmoid进行过滤;2)由隐藏状态 C t C_t Ct和tanh函数构成,tanh将 C t C_t Ct处理得到一个在 ( − 1 , 1 ) (-1,1) (−1,1)之间的值,然后将其与sigmoid门相乘得到 h t h_t ht。
o t = σ ( W o ⋅ [ h t − 1 , x t ] + b o ) o_t = \sigma(W_o·[h_{t-1},x_t]+b_o) ot=σ(Wo⋅[ht−1,xt]+bo)
h t = o t ⊙ tanh ( C t ) h_t = o_t \odot \tanh(C_t) ht=ot⊙tanh(Ct)
算法框架:采用反向传播算法
对于激活函数,先求出其导数:
σ ( z ) = d e f y = 1 1 + 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 ) = d e f y = e z − e − z e z + e − z ⇒ tanh ′ ( z ) = 1 − y 2 \tanh(z) \overset{\underset{def}{}}{=} y = \frac{e^z-e^{-z}}{e^z+e^{-z}} \Rightarrow \tanh'(z) = 1-y^2 tanh(z)=defy=ez+e−zez−e−z⇒tanh′(z)=1−y2
在 t t t时刻,LSTM的输出值为 h t h_t ht,定义此时的误差项 δ t \delta_t δt为
δ t = d e f ∂ L ∂ h t \delta_t \overset{\underset{def}{}}{=} \frac{\partial L}{\partial h_t} δt=def∂ht∂L
误差项沿时间的反向传递
δ t − 1 T = ∂ L ∂ h t − 1 = ∂ L ∂ h t ∂ h t ∂ h t − 1 = δ t T ∂ h t ∂ h t − 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
误差项沿时间的反向传递
由于有 h t = o t ⊙ tanh ( C t ) h_t = o_t \odot \tanh(C_t) ht=ot⊙tanh(Ct)和 C t = f t ⊙ C t − 1 + i t ⊙ C ~ t C_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t Ct=ft⊙Ct−1+it⊙C~t,利用全导数,计算得出
δ t − 1 = δ o , j T W o h + δ f , j T W f h + δ i , j T W i h + δ C ~ t , j T W C h \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 , t T = δ t T ⊙ tanh ( C t ) ⊙ o t ⊙ ( 1 − o t ) δ f , t T = δ t T ⊙ o t ⊙ ( 1 − tanh ( C t ) 2 ) ⊙ C t − 1 ⊙ f t ⊙ ( 1 − f t ) δ i , t T = δ t T ⊙ o t ⊙ ( 1 − tanh ( C t ) 2 ) ⊙ C ~ t ⊙ i t ⊙ ( 1 − i t ) δ C ~ t , t T = δ t T ⊙ o t ⊙ ( 1 − tanh ( C t ) 2 ) ⊙ i t ⊙ ( 1 − C ~ t 2 ) \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)
带入上式可得出
δ k T = ∏ j = k t − 1 δ o , j T W o h + δ f , j T W f h + δ i , j T W i h + δ C ~ t , j T W C h \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
将误差项传递到上一层
如果当前层为第 l l l层,设 l − 1 l-1 l−1层的误差项是误差函数对 l − 1 l-1 l−1层加权输入的导数:
δ t l − 1 = d e f ∂ L n e t t l − 1 \delta_t^{l-1} \overset{\underset{def}{}}{=} \frac{\partial L}{net_t^{l-1}} δtl−1=defnettl−1∂L
则LSTM的输入可以表示为:
x t l = f l − 1 ( n e t T l − 1 ) x_t^l = f^{l-1}(net_T^{l-1}) xtl=fl−1(netTl−1)
其中 f l − 1 f^{l-1} fl−1表示第 l − 1 l-1 l−1层的激活函数。
将 n e t t l − 1 net_t^{l-1} nettl−1展开,并利用全导数公式,得到
∂ L n e t t l − 1 = δ f , t T W f x + δ i , t T W i x + δ C ~ , t T W C x + δ o , t T W o x ⊙ f ′ ( n e t t l − 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 ∂ W o h = ∑ t ∂ L ∂ W o h , t = ∂ L ∂ n e t o , t ∂ n e t o , t ∂ W o h , t = ∑ t δ o , j h j − 1 T \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 ∂ W f h = ∑ t δ f , j h j − 1 T ∂ L ∂ W i h = ∑ t δ i , j h j − 1 T ∂ L ∂ W C h = ∑ t δ C ~ , j h j − 1 T \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
对于偏置项 b b b的梯度,按照同样的方式可以求得
{ ∂ L ∂ b o = ∑ t δ o , j ∂ L ∂ b i = ∑ t δ i , j ∂ L ∂ b f = ∑ t δ f , j ∂ L ∂ b C = ∑ 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
对于输入 x x x的权重矩阵的梯度,只需要根据相应的误差项直接计算:
{ ∂ L ∂ W o x = δ o , t x t T ∂ L ∂ W f x = δ f , t x t T ∂ L ∂ W i x = δ i , t x t T ∂ L ∂ W C x = δ C , t x t T \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
{ f t = σ ( W f ⋅ [ C t − 1 , h t − 1 , x t ] + b f ) i t = σ ( W i ⋅ [ C t − 1 , h t − 1 , x t ] + b i ) o t = σ ( W o ⋅ [ C t − 1 , h t − 1 , x t ] + b o ) \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)
将遗忘门和输入门合二为一,一同做出决定。即仅仅会当将要输入在当前位置时遗忘;仅仅输入新值到已经遗忘旧信息的那些状态。
C t = f t ⊙ C t − 1 + ( 1 − f t ) ⊙ C ~ t C_t = f_t \odot C_{t-1} + (1-f_t) \odot \tilde{C}_t Ct=ft⊙Ct−1+(1−ft)⊙C~t
在自然语言处理中,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。