当前位置:   article > 正文

GRU模块:nn.GRU层

nn.gru

摘要: 

       如果需要深入理解GRU的话,内部实现的详细代码和计算公式就比较重要,中间的一些过程及中间变量的意义需要详细关注。只有这样,才能准备把握这个模块的内涵和意义,设计初衷和使用方式等等。所以,仔细研究这个模块的实现还是非常有必要的。以此类推,对于其他的模块同样如此,只有把各个经典的模块内部原理、实现和计算调用都搞清楚了,才能更好的去设计和利用神经网络,建立内在的直觉和能力。

       本文中介绍GRU内部的代码实现与数学表达式一致,在实际使用中,一般是通过调用API来实现,即语句:self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=dropout),只需要设定相应的参数即可,免除了重新实现的繁琐,并且类似于pytorch框架中的API还做了计算上的优化,使用起来高效方便。

       先从输入输出的角度看,即代码中的这一行:output, state = self.rnn(X) 。在 GRU(Gated Recurrent Unit)中,outputstate 都是由 GRU 层的循环计算产生的,它们之间有直接的关系。state 实际上是 output 中最后一个时间步的隐藏状态。 

代码示例

  1. class Seq2SeqEncoder(d2l.Encoder):
  2. """⽤于序列到序列学习的循环神经⽹络编码器"""
  3. def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
  4. dropout=0, **kwargs):
  5. super(Seq2SeqEncoder, self).__init__(**kwargs)
  6. # 嵌⼊层
  7. self.embedding = nn.Embedding(vocab_size, embed_size)
  8. self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,
  9. dropout=dropout)
  10. def forward(self, X, *args):
  11. # 输出'X'的形状:(batch_size,num_steps,embed_size)
  12. X = self.embedding(X)
  13. # 在循环神经⽹络模型中,第⼀个轴对应于时间步
  14. X = X.permute(1, 0, 2)
  15. # 如果未提及状态,则默认为0
  16. output, state = self.rnn(X)
  17. # output的形状:(num_steps,batch_size,num_hiddens)
  18. # state的形状:(num_layers,batch_size,num_hiddens)
  19. return output, state

output:在完成所有时间步后,最后⼀层的隐状态的输出output是⼀个张量(output由编码器的循环层返回),其形状为(时间步数,批量⼤⼩,隐藏单元数)。

state:最后⼀个时间步的多层隐状态是state的形状是(隐藏层的数量,批量⼤⼩, 隐藏单元的数量)。

GRU模块的框图 

GRU 的基本公式

GRU 的核心计算包括更新门(update gate)和重置门(reset gate),以及候选隐藏状态(candidate hidden state)。数学表达式如下:

  1. 更新门 zt: 

    zt=σ(Wzht1+Uzxt)

       其中,σ 是sigmoid 函数,WzUz 分别是对应于隐藏状态和输入的权重矩阵,ht1 是上一个时间步的隐藏状态,xt 是当前时间步的输入。

  2. 重置门 rt
       

    rt=σ(Wrht1+Urxt)

       WrUr 是更新门中定义的相似权重矩阵。

  3. 候选隐藏状态 h~t
       

    h~t=tanh(Wrtht1+Uxt)

       这里,tanh 是激活函数, 表示元素乘法(Hadamard product),WU 是隐藏状态的权重矩阵。

  4. 最终隐藏状态 ht
       

    ht=(1zt)ht1+zth~t

output 和 state 的关系

  • output:在 GRU 中,output 包含了序列中每个时间步的隐藏状态。具体来说,对于每个时间步 t,output 的第 t 个元素就是该时间步的隐藏状态 ht

  • state:state 是 GRU 层最后一层的隐藏状态,也就是 output 中最后一个时间步的隐藏状态 hT1,其中 T 是序列的长度。

数学表达式

如果我们用 O 表示 output,S 表示 state,T 表示时间步的总数,那么:

O=[h0,h1,...,hT1]

S=hT1

因此,state 实际上是 output 中最后一个元素,即 S=O[T1]

在 PyTorch 中,output 和 state 都是由 GRU 层的 `forward` 方法计算得到的。`output` 是一个三维张量,包含了序列中每个时间步的隐藏状态,而 `state` 是一个二维张量,仅包含最后一个时间步的隐藏状态。

GRU的内部实现

上面一节的代码示例,是通过调用API实现的,即self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=dropout)。那么,GRU内部是如何实现的呢?

分为模型、模型参数初始化和隐状态初始化三个部分:

模型定义(模型定义与数学表示式一致,也可以参考上图):

  1. def gru(inputs, state, params):
  2. W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params
  3. H, = state
  4. outputs = []
  5. for X in inputs:
  6. Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)
  7. R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)
  8. H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)
  9. H = Z * H + (1 - Z) * H_tilda
  10. Y = H @ W_hq + b_q
  11. outputs.append(Y)
  12. return torch.cat(outputs, dim=0), (H,)

  模型参数初始化(权重是从标准差0.01的高斯分布中提取的,超参数num_hiddens定义隐藏单元的数量,偏置项设置为0):

  1. def get_params(vocab_size, num_hiddens, device):
  2. num_inputs = num_outputs = vocab_size
  3. def normal(shape):
  4. return torch.randn(size=shape, device=device)*0.01
  5. def three():
  6. return (normal((num_inputs, num_hiddens)),
  7. normal((num_hiddens, num_hiddens)),
  8. torch.zeros(num_hiddens, device=device))
  9. W_xz, W_hz, b_z = three() # 更新⻔参数
  10. W_xr, W_hr, b_r = three() # 重置⻔参数
  11. W_xh, W_hh, b_h = three() # 候选隐状态参数
  12. # 输出层参数
  13. W_hq = normal((num_hiddens, num_outputs))
  14. b_q = torch.zeros(num_outputs, device=device)
  15. # 附加梯度
  16. params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]
  17. for param in params:
  18. param.requires_grad_(True)
  19. return params

隐状态初始化函数(此函数返回一个形状为(批量大小,隐藏单元个数)的张量,张量的值都为0

  1. def init_gru_state(batch_size, num_hiddens, device):
  2. return (torch.zeros((batch_size, num_hiddens), device=device), )

最后由一个函数统一起来,实现模型:

model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params, init_gru_state, gru)

小结 

       总体上说,内部的代码实现与数学表达式一致,在实际使用中,一般是通过调用API来实现,即self.rnn = nn.GRU(embed_size, num_hiddens, num_layers, dropout=dropout),只需要设定相应的参数即可,免除了重新实现的繁琐,并且类似于pytorch框架中的API还做了计算上的优化,使用起来高效方便。但是,如果需要深入理解GRU的话,那么内部实现的详细代码和计算公式就比较重要,中间的一些过程和变量的意义需要详细关注,只有这样,才能准备把握这个模块的内涵和意义,设计初衷和使用方式等等,所以,仔细研究这个模块的实现还是非常有必要的。对于其他的模块同样如此,只有把各个经典的模块内部原理、实现和计算调用都搞清楚了,才能更好的去设计和利用神经网络,建立内在的直觉和能力。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/759030
推荐阅读
相关标签
  

闽ICP备14008679号