# GRU
具体原理可以看沐神的b站课程,也可以看书,我仅对代码部分进行讲解。
# 从零开始实现
def get_params(vocab_size, num_hiddens, device): # vocab_size 表示词汇表的大小,num_hiddens 表示隐藏单元的数量
num_inputs = num_outputs = vocab_size # num_inputs 和 num_outputs,它们都被设置为 vocab_size,表示输入和输出数据的维度,因为这里我们是在字符级别的文本数据上进行建模,所以输入和输出的维度都等于词汇表的大小。
def normal(shape): # 生成随机初始化的张量,并将其元素初始化为标准差为 0.01 的随机值。这个函数将用于初始化模型参数。
return torch.randn(size=shape, device=device)*0.01
def three(): # W_xz、W_hz、b_z、W_xr、W_hr、b_r 和 W_xh、W_hh、b_h。这些参数用于门控循环单元(GRU)模型中的更新门、重置门和候选隐状态。
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: # 将所有的模型参数存储在 params 列表中,然后使用循环将每个参数的 requires_grad 属性设置为 True,以便在训练过程中计算梯度。
param.requires_grad_(True)
return params
定义隐状态的初始化函数init_gru_state
。此函数返回一个形状为(批量大小,隐藏单元个数)的张量,张量的值全部为零。
def gru(inputs, state, params): # 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 # 将模型的参数 params 解包成多个变量,其中包括更新门(W_xz, W_hz, b_z)、重置门(W_xr, W_hr, b_r)、候选隐状态(W_xh, W_hh, b_h)、以及输出层参数(W_hq, b_q)。
H, = state # 从初始状态 state 中解包隐藏状态 H。H 是一个张量,表示模型的隐藏状态。
outputs = [] # 用于存储模型在每个时间步上的输出。
for X in inputs: # torch.sigmoid 函数用于将门的值映射到 [0, 1] 的范围内。
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_tilda, torch.tanh 函数用于将候选隐状态的值映射到 [-1, 1] 的范围内。
H = Z * H + (1 - Z) * H_tilda # 根据更新门 Z 和候选隐状态 H_tilda 更新了隐藏状态 H。这是GRU模型的关键部分,它控制了信息的传递和保存。
Y = H @ W_hq + b_q # 计算了输出 Y,它是隐藏状态 H 通过全连接层(使用参数 W_hq 和 b_q)映射到输出空间的结果。
outputs.append(Y)
return torch.cat(outputs, dim=0), (H,) # 将所有的输出 Y 沿着第一个维度拼接在一起,形成一个长序列。同时,将更新后的隐藏状态 H 包装在元组中,并返回作为函数的输出。
训练
#简洁实现
直接用现成的API,代码简洁了许多,速度也更快了
num_inputs = vocab_size
gru_layer = nn.GRU(num_inputs, num_hiddens) # gru_layer 是一个 GRU 层,它接受 num_inputs 维的输入数据,有 num_hiddens 个隐藏单元。
model = d2l.RNNModel(gru_layer, len(vocab))
model = model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)