关于GRU的笔记
支持隐状态的门控:这意味着模型有专门的机制来确定应该何时更新隐状态, 以及应该何时重置隐状态。 这些机制是可学习的,并且能够解决了上面列出的问题。 例如,如果第一个词元非常重要, 模型将学会在第一次观测之后不更新隐状态。 同样,模型也可以学会跳过不相关的临时观测。 最后,模型还将学会在需要的时候重置隐状态。 下面我们将详细讨论各类门控。
formula:门 是和隐藏状态同样的一个向量
重置门:
R
t
=
σ
(
X
t
∗
W
x
r
+
H
t
−
1
∗
W
h
r
+
b
r
)
R_t = σ(X_t * W_{xr} + H_{t-1} * W_{hr} + b_r)
Rt=σ(Xt∗Wxr+Ht−1∗Whr+br)
更新门:
Z
t
=
σ
(
X
t
∗
W
x
z
+
H
t
−
1
∗
W
h
z
+
b
z
)
Z_t = σ(X_t * W_{xz} + H_{t-1} * W_{hz} + b_z)
Zt=σ(Xt∗Wxz+Ht−1∗Whz+bz)
候选隐状态
☆
H
t
=
t
a
n
h
(
X
t
∗
W
x
h
+
(
R
t
⊙
H
t
−
1
)
∗
W
h
h
+
b
h
)
^☆H_t = tanh(X_t * W_{xh} + (R_t ⊙ H_{t-1}) * W_{hh} + b_h)
☆Ht=tanh(Xt∗Wxh+(Rt⊙Ht−1)∗Whh+bh)
当重置门的项接近于1时,就可以恢复到一个普通的循环神经网络RNN的模型对于重置门的项接近于0时,候选隐状态是以X_t作为输入的多层感知机的结果,它除去了隐状态H_t-1对当前的影响任何预先存在的隐状态都会被重置为默认值
隐状态
更新门Z_t仅需要在H_t-1和(star)H_t之间进行按元素的凸组合就可以实现这个目标。
H
t
=
Z
t
⊙
H
t
−
1
+
(
1
−
Z
t
)
⊙
☆
H
t
H_t = Z_t ⊙ H_{t-1} + (1 - Z_t) ⊙ ^☆H_t
Ht=Zt⊙Ht−1+(1−Zt)⊙☆Ht
每当更新门{Z_t}接近于1时,模型就倾向于只保留旧状态,此时来自于
X
t
X_t
Xt的信息基本上都会被忽略,当前的隐状态只依赖于上一次的
H
(
t
−
1
)
H_(t-1)
H(t−1)相反,当
Z
t
Z_t
Zt接近于0时,新的隐状态
H
t
H_t
Ht就会接近于候选隐状态
☆
H
t
^☆H_t
☆Ht
优点:这些设计可以帮助我们处理循环神经网络中的梯度消失的问题,并且更好地捕获时间步距离很长的序列的依赖关系。例如:如果整个子序列的所有时间步的更新门都接近于1,则无论序列的长度如何,在序列起始时间步的旧隐状态都很容易保留并传递到序列的结束。
综上
重置门有助于捕获序列中的短期依赖关系;
更新门有助于捕获序列中的长期依赖关系。
GRU从零开始实现
import torch
from torch import nn
from d2l import torch as d2l
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
# 初始化模型参数
def get_params(vocab_size, num_hiddens, device):
num_inputs = num_outputs = vocab_size
def normal(shape):
return torch.randn(size=shape, device=device) * 0.01
def three():
return (normal((num_inputs, num_hiddens)),
normal((num_hiddens, num_hiddens)),
torch.zeros(num_hiddens, device=device))
W_xz, W_hz, b_z = three() # 更新门参数
W_xr, W_hr, b_r = three() # 重置门参数
W_xh, W_hh, b_h = three() # 候选隐状态参数
# 输出层参数
W_hq = normal((num_hiddens, num_outputs))
b_q = torch.zeros(num_outputs, device=device)
# 附加梯度
params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]
for param in params:
param.requires_grad_(True)
return params
# 定义模型
def init_gru_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device),)
# gru模型
def gru(inputs, state, params):
W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
# 更新门:Z_t = σ(X_t * W_xz + H_T-1 * W_hz + bz)
Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)
# 重置门:R_t = σ(X_t * W_xr + H_t - 1 * W_hr + br)
R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)
# (star)H_t = tanh(X_t * W_xh + (R_t ⊙ H_t-1) * W_hh + bh)
H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)
# H_t = Z_t ⊙ H_t - 1 + (1 - Z_t) ⊙ (star)H_t
H = Z * H + (1 - Z) * H_tilda
Y = H @ W_hq + b_q
outputs.append(Y)
return torch.cat(outputs, dim=0), (H,)
# 训练与预测
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500,1
model = d2l.RNNModelScratch(len(vocab),num_hiddens,device,get_params,init_gru_state,gru)
d2l.train_ch8(model,train_iter,vocab,lr,num_epochs,device)
简洁实现GRU
import torch
from torch import nn
from d2l import torch as d2l
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
def get_params(vocab_size, num_hiddens, device):
num_inputs = num_outputs = vocab_size
def normal(shape):
return torch.randn(size=shape, device=device)*0.01
def three():
return (normal((num_inputs, num_hiddens)),
normal((num_hiddens, num_hiddens)),
torch.zeros(num_hiddens, device=device))
W_xz, W_hz, b_z = three() # 更新门参数
W_xr, W_hr, b_r = three() # 重置门参数
W_xh, W_hh, b_h = three() # 候选隐状态参数
# 输出层参数
W_hq = normal((num_hiddens, num_outputs))
b_q = torch.zeros(num_outputs, device=device)
# 附加梯度
params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]
for param in params:
param.requires_grad_(True)
return params
def init_gru_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device), )
def gru(inputs, state, params):
W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)
R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)
H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)
H = Z * H + (1 - Z) * H_tilda
Y = H @ W_hq + b_q
outputs.append(Y)
return torch.cat(outputs, dim=0), (H,)
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params,
init_gru_state, gru)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)