LSTM长短时记忆网络:推导与实现(pytorch)

LSTM长短时记忆网络:推导与实现(pytorch)

背景

人类不会每秒钟都从头开始思考。当你阅读这篇文章时,你会根据你对以前单词的理解来理解每个单词。你不会把所有东西都扔掉,重新开始思考。你的思想是持久的。

传统的神经网络无法做到这一点,这是一个主要缺点。递归神经网络(Rnn)解决了这个问题,这是一种带有记忆的模型,可以将其认为是同一网络的多个副本,每个副本将消息传递给继任者。

RNN的优势是,它们可能能够将先前的信息与当前任务联系起来,例如使用以前的视频帧为理解当前帧提供信息。但是在实际情况中,它不一定能做到这一点。

有时我们只需要查看最近的信息即可执行当前任务。例如,考虑一个语言模型,它试图根据前一个单词来预测下一个单词。如果我们试图预测"云在天空中"中的最后一个词,我们不需要任何进一步的上下文,很明显下一个词将是天空。在这种情况下,相关信息与需要信息的地方之间的差距很小,RNN可以学习使用过去的信息。

但在某些情况下,我们需要更多的背景信息。不妨试着预测经文中的最后一个字:"我在法国长大...我能说一口流利的法语。最近的信息表明,下一个词可能是一种语言的名称,但如果我们想缩小哪种语言的范围,我们需要从更远的地方开始了解法国的上下文。相关信息与需要信息的点之间的差距完全有可能变得非常大。

不幸的是,随着这种差距的扩大,RNN变得无法学习连接信息。这就是传统RNN的缺点,很难处理长距离的依赖。

长短期记忆网络(通常简称为"LSTM")解决了这个问题,这是一种特殊的RNN,能够学习长期依赖关系。我们将推导LSTM中每一层的结构,并实现一个pytorch版本LSTM。

推导

长短时记忆网络的思路很简单。传统RNN的隐藏层只有一个状态,即h,它对于短期的输入非常敏感,假如我们再增加一个状态,即c,让它来保存长期的状态,那么问题就解决了:

新增加的状态c,称为单元状态(cell state)。我们把上图按照时间维度展开:

在长短时记忆网络的前向计算中,通过门(gate)控制向量的变化。门实际上就是一层全连接层,它的输入是一个向量,输出是一个0到1之间的实数向量。那么门可以表示为: g ( x ) = σ ( W x + b ) g(x)=\sigma(Wx+b) g(x)=σ(Wx+b)  由于sigmod函数的性质,门的输出是0到1之间的实数向量,那么,当门输出为0时,任何向量与之相乘都会得到0向量;输出为1时,任何向量与之相乘都不会有任何改变。

LSTM用两个门来控制单元状态c的内容,一个是遗忘门(forget gate),它决定了上一时刻的单元状态有多少保留到当前时刻ct;另一个是输入门(input gate),它决定了当前时刻网络的输入xt有多少保存到单元状态ct。LSTM用输出门(output gate)来控制单元状态ct有多少输出到LSTM的当前输出值ht。

遗忘门

遗忘门通过门控制上一时刻的输入的单元状态 c t − 1 c_{t-1} ct−1有多少被保留下来,门的权值通过上一时刻的输出值 h t − 1 h_{t-1} ht−1和这一时刻的输入值 x t x_t xt得到,即: f t = σ ( W f ⋅ [ h t − 1 , x t ] + b f ) f_t=\sigma(W_f\cdot[h_{t-1},x_t]+b_f) ft=σ(Wf⋅[ht−1,xt]+bf)  这个过程可以被分解为 f t = W f h h t − 1 + W f h x t f_t=W_{fh}h_{t-1}+W_{fh}x_t ft=Wfhht−1+Wfhxt  在我们对门的实现中,我们都遵循这种方式。下图显示了遗忘门的计算:

输入门

输入门决定了将当前的输入有多少被保留到c中,门的权值计算方式与遗忘门相同,即: i t = σ ( W i ⋅ [ h t − 1 , x t ] + b i ) i_t=\sigma(W_i\cdot[h_{t-1},x_t]+b_i) it=σ(Wi⋅[ht−1,xt]+bi)  通过门值,我们计算当前输入的单元状态 c ^ t \hat c_t c^t,它是通过上一次的输出和本次输入计算的: c ^ t = t a n h ( W c ⋅ [ h t − 1 , x t ] + b c ) \hat c_t=tanh(W_c\cdot [h_{t-1},x_t]+b_c) c^t=tanh(Wc⋅[ht−1,xt]+bc)  下图显示了输入门的计算:

到这里,我们可以计算当前时刻的单元状态 c t c_t ct,它是由上一次的单元状态 c t − 1 c_{t-1} ct−1乘以遗忘门权值 f t f_t ft,再用当前输入的单元状态 c ^ t \hat c_t c^t乘以输入门权值 i t i_t it,再将两个积加和产生的: c t = f t ⋅ c t − 1 + i t ⋅ c ^ t c_t=f_t\cdot c_{t-1}+i_t\cdot \hat c_t ct=ft⋅ct−1+it⋅c^t  这样,我们就把LSTM关于当前的记忆 c ^ t \hat c_t c^t和长期的记忆 c t − 1 c_{t-1} ct−1组合在一起,形成了新的单元状态 c t c_t ct。由于遗忘门的控制,它可以保存很久很久之前的信息,由于输入门的控制,它又可以避免当前无关紧要的内容进入记忆。

输出门

输出门的权值计算方式与上面两个门相同: o t = σ ( W o ⋅ [ h t − 1 , x t ] + b i ) o_t=\sigma(W_o\cdot[h_{t-1},x_t]+b_i) ot=σ(Wo⋅[ht−1,xt]+bi)  LSTM最终的输出,是由输出门和单元状态共同确定的: h t = o t ⋅ t a n h ( c t ) h_t=o_t\cdot tanh(c_t) ht=ot⋅tanh(ct)

至此,LSTM的推导就讲完了。

LSTM的改进:GRU

LSTM也有许多缺点,因此提出了很多变体,GRU(Gated Recurrent Unit)就是比较成功的一种,针对LSTM有三个不同的门,参数较多,训练困难的缺点,GRU将LSTM中的输入门和遗忘门合二为一,称为更新门(update gate),控制前边记忆信息能够继续保留到当前时刻的数据量;另一个门称为重置门(reset gate),控制要遗忘多少过去的信息。其结构如下所示,读者可以自行对比:

实现

我们使用pytorch实现了一个LSTMlayer的模型:

python 复制代码
class LstmLayer(torch.nn.Module):
    def __init__(self, bert_model, fea_dim, dropout):
        super(LstmLayer, self).__init__()

        self.fea_dim = fea_dim
        # 激活函数
        self.sigmod = nn.Sigmoid()
        self.tanh = nn.Tanh()

        # 遗忘门权重矩阵Wfh, Wfx
        self.Wfh = torch.nn.Linear(self.fea_dim, 1)
        self.Wfx = torch.nn.Linear(self.fea_dim, 1)
        # 输入门权重矩阵Wfh, Wfx
        self.Wih = torch.nn.Linear(self.fea_dim, 1)
        self.Wix = torch.nn.Linear(self.fea_dim, 1)
        # 单元状态更新权重矩阵Wch, Wcx
        self.Wch = torch.nn.Linear(self.fea_dim, self.fea_dim)
        self.Wcx = torch.nn.Linear(self.fea_dim, self.fea_dim)
        # 输出门权重矩阵Woh, Wox
        self.Woh = torch.nn.Linear(self.fea_dim, self.fea_dim)
        self.Wox = torch.nn.Linear(self.fea_dim, self.fea_dim)


    def forward(self,x_t,c_t,h_t):
        # 遗忘门
        fg = self.calc_gate(x_t,h_t, self.Wfx, self.Wfh,self.sigmod)
        c_t = fg * c_t
        # 输入门
        ig = self.calc_gate(x_t,h_t, self.Wix, self.Wih,self.sigmod)
        c_t_temp = ig * self.tanh(self.Wch(h_t) + self.Wcx(x_t))
        c_out = c_t_temp + c_t
        # 输出门
        og = self.calc_gate(x_t,h_t, self.Wox, self.Woh,self.sigmod)
        h_out = self.tanh(c_out) * og
        return c_out,h_out

    def calc_gate(self, x,h, Wx, Wh, activator):
        # 计算门权值
        gate_weight = Wx(x) + Wh(h)
        gate_out = activator(gate_weight)
        return gate_out
相关推荐
bastgia30 分钟前
Tokenformer: 下一代Transformer架构
人工智能·机器学习·llm
菜狗woc38 分钟前
opencv-python的简单练习
人工智能·python·opencv
15年网络推广青哥42 分钟前
国际抖音TikTok矩阵运营的关键要素有哪些?
大数据·人工智能·矩阵
weixin_387545641 小时前
探索 AnythingLLM:借助开源 AI 打造私有化智能知识库
人工智能
engchina2 小时前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
paixiaoxin2 小时前
CV-OCR经典论文解读|An Empirical Study of Scaling Law for OCR/OCR 缩放定律的实证研究
人工智能·深度学习·机器学习·生成对抗网络·计算机视觉·ocr·.net
OpenCSG3 小时前
CSGHub开源版本v1.2.0更新
人工智能
weixin_515202493 小时前
第R3周:RNN-心脏病预测
人工智能·rnn·深度学习
Altair澳汰尔3 小时前
数据分析和AI丨知识图谱,AI革命中数据集成和模型构建的关键推动者
人工智能·算法·机器学习·数据分析·知识图谱
机器之心3 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端