您的位置:首页 > 理论基础 > 计算机网络

神经网络学习笔记(三)——长短期记忆网络LSTM

2020-08-21 10:32 791 查看

长短期记忆网络 LSTM

文章目录

  • 四、LSTM的简单使用
  • 五、总结
  • 一、概述

      长短期记忆网络——通常被称为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)=def​y=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)=def​y=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,jT​Woh​+δf,jT​Wfh​+δi,jT​Wih​+δC~t​,jT​WCh​
      又因为有
    {δ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~t​2)​
      带入上式可得出
    δ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,jT​Woh​+δf,jT​Wfh​+δi,jT​Wih​+δC~t​,jT​WCh​

      将误差项传递到上一层

      如果当前层为第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​=def​nettl−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,tT​Wfx​+δi,tT​Wix​+δC~,tT​WCx​+δo,tT​Wox​⊙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,j​hj−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,j​hj−1T​∂Wih​∂L​=∑t​δi,j​hj−1T​∂WCh​∂L​=∑t​δC~,j​hj−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,t​xtT​∂Wfx​∂L​=δf,t​xtT​∂Wix​∂L​=δi,t​xtT​∂WCx​∂L​=δC,t​xtT​​

    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非常适合用于处理与时间序列高度相关的问题,例如机器翻译、对话生成、编码\解码等。
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐