循环神经网络(RNN)、长短时记忆网络(LSTM)及(GRU)模型

2,600 阅读4分钟

循环神经网络(RNN)

image.png 循环神经网络区别于其他神经网络在于循环二字,这里的循环是指隐含的输出(v)重新输入隐含权重参与模型训练,不同时刻的输入(X)也要放入隐含层权重中。隐含权重是循环到不同时间段其参数不一样,但隐含权重是共用的,注意在图中unfold下隐含权重矩阵只是一个矩阵(这里稍后解释:隐含的输出(v)和对应的输入(x)怎样共用一个矩阵),之所以呈现多个是为了对应不同的时刻。

在继续往下叙述之前,有一个优化问题要解决,即共用一个矩阵,这里可以通用一个简单的例子进行说明: 为方便理解,隐含变量为二维,输入的为一维,目的是得到新的隐含输入即二维。 image.png 我们可以发现,权重系数并排综合到一起,得到结果同样。这就提高了并行处理能力。

RNN源代码实现

import torch.nn as nn

class RNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(RNN, self).__init__()

        self.hidden_size = hidden_size

        self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
        self.i2o = nn.Linear(input_size + hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, input, hidden):
        combined = torch.cat((input, hidden), 1)
        hidden = self.i2h(combined)
        output = self.i2o(combined)
        output = self.softmax(output)
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, self.hidden_size)

循环神经网络的特点是每次的输入都有上一刻隐含层信息的参与,但也仅仅是上一刻,若神经网络输入不仅仅和上一刻时间的信息有关,换句话说,此刻时间的所需要训练的模型与很久之前信息也有关联,怎么让其参与进来?这就需要引入长短时记忆网络(LSTM),不过在谈论该模型之前,先研究再此基础上改进后发表的paper即GRU模型,该篇相比于LSTM模型更加容易理解。

GRU模型

GRU模型需要注意的是门控的作用,更确定说是在公式中作为权重的体现或者说是mask的效果。话不多说,理解这四个公式就理解了GRU模型,这样在看图就更加容易理解。

Rt=σ(XtWxr+Ht1Whr+br)Zt=σ(XtWxz+Ht1Whz+bz)R_t = \sigma(X_tW_{xr}+H_{t-1}W_{hr}+b_r)\\ Z_t = \sigma(X_tW_{xz}+H_{t-1}W_{hz}+b_z)

其中RtR_t称为重置门,它有助于捕捉时序数据中短期的依赖关系,ZtZ_t称为更新门,有助于捕捉时序数据中长期的依赖关系。其作用会在下面公式具体体现。

Ht~=tanh(XtWxh+RtHt1Whh+bn)\tilde{H_{t}}=tanh(X_t W_{xh} + R_t \odot H_{t-1} W_{hh} + b_n)

Ht~\tilde{H_{t}}我们称作候选隐含状态,主要保存短期记忆。之所以重置门可以有助于捕捉时序数据中的短期依赖关系是因为其作用于上一个隐含状态通过pair-wise相乘。我们可以决定上一个时刻隐含状态我们要到底保留多少,RtR_t某一维度对应的数值越大,即上一个隐含状态在该维度上保留的信息越多.

Ht=ZtHt1+(1Zt)Ht~H_t = Z_t \odot H_{t-1} + (1-Z_t)\odot \tilde{H_{t}}

在公式中,ZtZ_t作为更新门,我们不妨举个极端的例子,在这一时刻ZtZ_t为1,那么上一个时刻信息是不是保存了,相对应的短期记忆的候选状态就为零了。同理,我们继续往前递推,在上一个时刻时,ZtZ_t为1,那么上上时刻信息是不是保存了,相对应的短期记忆的候选状态也为零了。这样我们就可以保存到很久很久之前的信息,所以更新门有助于捕捉时序数据中长期的依赖关系。

那么下面这张图片就好理解了。

image.png

源代码实现

class GRUCell(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(GRUCell, self).__init__()
        self.hidden_size = hidden_size
        self.gate = nn.Linear(input_size + hidden_size, hidden_size)
        self.output = nn.Linear(hidden_size, output_size)
        self.sigmoid = nn.Sigmoid()
        self.tanh = nn.Tanh()
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, input, hidden):
        combined = torch.cat((input, hidden), 1)        
        z_gate = self.sigmoid(self.gate(combined))
        r_gate = self.sigmoid(self.gate(combined))
        combined01 = torch.cat((input, torch.mul(hidden,r_gate)), 1)  
        h1_state = self.tanh(self.gate(combined01))
        
        h_state = torch.add(torch.mul((1-z_gate), hidden), torch.mul(h1_state, z_gate))
        output = self.output(h_state)
        output = self.softmax(output)
        return output, h_state

    def initHidden(self):
        return torch.zeros(1, self.hidden_size)  

最后让我们来看看lstm到底有什么难理解的。

长短时记忆网络(lstm)

有了GRU模型的理解,我们可以直接通过公式理解lstm。

It=σ(XtWxi+Ht1Whi+bi)Ft=σ(XtWxf+Ht1Whf+bf)Ot=σ(XtWxo+Ht1Who+bo)I_t = \sigma(X_tW_{xi}+H_{t-1}W_{hi}+b_i)\\ F_t = \sigma(X_tW_{xf}+H_{t-1}W_{hf}+b_f)\\ O_t = \sigma(X_tW_{xo}+H_{t-1}W_{ho}+b_o)\\

其中ItI_tFtF_tOtO_t分别为输入门、遗忘门、输出门。

Ct~=tanh(XtWxc+Ht1Whc+bc)\tilde{C_{t}}=tanh(X_t W_{xc} + H_{t-1} W_{hc} + b_c)

Ct~\tilde{C_{t}}作为候选记忆细胞,主要用来保存短期记忆

Ct=FtCt1+ItCt~C_{t}=F_t\odot C_{t-1} + I_t\odot \tilde{C_{t}}

CtC_{t}为当前记忆细胞,用ItI_tFtF_t大小去衡量长期记忆和短期记忆孰多孰少。理解方式同GRU,只不过把ZtZ_t(1Zt)(1-Z_t)更换成两个变量形式。

Ht=Ottanh(Ct)H_t = O_t \odot tanh(C_t)

HtH_t为当前隐含状态,其中OtO_t当前记忆细胞一部分到隐含状态,这一点与GRU不同,并不是所有的都到隐含层,这样更有效或更灵活的在隐含层的基础上去输出或传递到下一个隐含层。

我们再次来看图

image.png

源代码实现:

import torch.nn as nn
import torch

class LSTMCell(nn.Module):
    def __init__(self, input_size, hidden_size, cell_size, output_size):
        super(LSTMCell, self).__init__()
        self.hidden_size = hidden_size
        self.cell_size = cell_size
        self.gate = nn.Linear(input_size + hidden_size, cell_size)
        self.output = nn.Linear(hidden_size, output_size)
        self.sigmoid = nn.Sigmoid()
        self.tanh = nn.Tanh()
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, input, hidden, cell):
        combined = torch.cat((input, hidden), 1)
        f_gate = self.sigmoid(self.gate(combined))
        i_gate = self.sigmoid(self.gate(combined))
        o_gate = self.sigmoid(self.gate(combined))
        z_state = self.tanh(self.gate(combined))
        cell = torch.add(torch.mul(cell, f_gate), torch.mul(z_state, i_gate))
        hidden = torch.mul(self.tanh(cell), o_gate)
        output = self.output(hidden)
        output = self.softmax(output)
        return output, hidden, cell

    def initHidden(self):
        return torch.zeros(1, self.hidden_size)

    def initCell(self):
        return torch.zeros(1, self.cell_size)

参考文献:
python深度学习基于pytorch
动手学习深度学习

该文若有帮助,不妨点个赞~