【重点】GRU – 门控循环神经网络
摘要:GRU通过重置门和更新门来控制信息的流动,简化了LSTM的结构并提高了计算效率。
1 模型原理
1.1 超参数
input_size
:input_size
是指每个时间步长输入的特征维度。在处理序列数据时,每个时间步可能包含多个特征。例如,在一个天气预测问题中,每天的输入可能包含温度、湿度、风速等多个特征。这些特征的数量就是input_size
。sequence_length
:sequence_length
决定了模型会处理多少个时间步的数据。假设我们有一个每日温度数据集,并且我们希望使用过去7天的温度来预测第8天的温度。每个输入序列的长度就是7,即sequence_length = 7
。
1.2 输入
- 序列数据:
(batch_size, sequence_length, input_size)
- 隐藏状态:默认全0,
(num_layers * num_directions, batch_size, hidden_size)
1.3 输出
序列数据:(batch_size, sequence_length, hidden_size)
1.4 计算
对于每一个时间步:
- 重置门和更新门:将当前输入
xt
和上一个隐藏状态h(t-1)
拼接后乘上权重,相加后再套一个sigmoid
,最后得到一个0-1
的值。 - 候选隐状态:将当前输入
xt
和重置门点乘,乘上权重后套一个tanh
。 - 隐状态:(1-更新门) 上一个隐状态+更新门 候选隐状态。
1.5 后处理
使用 out = self.fc(out[:, -1, :])
获得最终输出结果。
out[:, -1, :]
:提取最后一个时间步的隐藏状态,形状为(batch_size, hidden_size)
。self.fc
:将隐藏状态的最后一个时间步映射为输出,形状为(batch_size, output_size)
。
2 Q&A
2.1 hidden_size的意义
hidden_size
的意义是什么,为什么不能直接用 input_size
?
input_size
是输入特征的数量,比如每个时间步的特征数。它由输入数据的特征决定。hidden_size
是隐藏状态的特征数量,用于控制网络的表示能力和容量。- 如果使用
input_size
代替hidden_size
,模型的容量和复杂度将受到限制,尤其当input_size
很小时,网络的表达能力会严重受限,无法有效学习到数据中的复杂模式。
2.2 sigmoid的意义
为什么输入门、遗忘门和输出门的计算使用了 sigmoid
?
sigmoid
将输出映射为[0, 1]
。当输出接近0时,表示几乎没有信息通过;当输出接近1时,表示信息完全通过。这种特性使其非常适合用作门控机制。
2.3 tanh的意义
为什么候选单元状态、隐藏状态的计算使用了 tanh
?
tanh
将输出映射为[-1, 1]
,候选单元状态可以具有正负值,提高模型表示能力。
2.4 权重的意义
为什么在计算各单元时需要用到权重?
- 提高模型表示能力。
- 改变矩阵形状。拼接后矩阵形状为
(batch_size, input_size + hidden_size)
,乘上权重后的形状为(batch_size, hidden_size)
。
2.5 代码中的偏置
为什么在“手动实现”的代码中只有 w
没有 b
。
- 因为
nn.Linear
已经内置了b
。
2.6 LSTM和GRU的区别
- LSTM:具有三个门(输入门、遗忘门、输出门)和一个单元状态。参数数量比GRU更多,有更好的表现能力。
- GRU:只有两个门(重置门、更新门),并且没有单独的单元状态,隐藏状态同时扮演了单元状态的角色。简化了LSTM的结构并提高了计算效率。
3 代码实现
3.1 手动实现
import torch
import torch.nn as nn
class GRUCell(nn.Module):
def __init__(self, input_size, hidden_size):
super(GRUCell, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
# 定义用于计算重置门、更新门和候选隐藏状态的全连接层
self.W_r = nn.Linear(input_size + hidden_size, hidden_size)
self.W_z = nn.Linear(input_size + hidden_size, hidden_size)
self.W_h = nn.Linear(input_size + hidden_size, hidden_size)
def forward(self, x, h_prev):
# 将当前输入 x 和前一时间步的隐藏状态 h_prev 拼接在一起
combined = torch.cat((x, h_prev), dim=1)
# 重置门
r_t = torch.sigmoid(self.W_r(combined)) # (batch_size, hidden_size)
# 更新门
z_t = torch.sigmoid(self.W_z(combined)) # (batch_size, hidden_size)
# 计算候选隐藏状态
h_prev_reset = r_t * h_prev
combined_reset = torch.cat((x, h_prev_reset), dim=1)
h_tilde = torch.tanh(self.W_h(combined_reset) + self.b_h)
# 更新隐藏状态
h_t = (1 - z_t) * h_prev + z_t * h_tilde
return h_t
# 超参数
input_size = 10 # 输入特征的维度
hidden_size = 20 # 隐藏状态的维度
# 实例化GRU单元
gru_cell = GRUCell(input_size, hidden_size)
# 输入数据
batch_size = 5
x = torch.randn(batch_size, input_size) # 当前时间步的输入
h_prev = torch.zeros(batch_size, hidden_size) # 前一时间步的隐藏状态
# 前向传播
h_t = gru_cell(x, h_prev)
print("Hidden state:", h_t)
Copy
3.2 调用库函数
import torch
import torch.nn as nn
class GRUModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers):
super(GRUModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 手动初始化隐藏状态
# h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
# out, _ = self.gru(x, h0) # 使用默认隐藏状态(全0)
out, _ = self.gru(x)
# 解码最后一个时间步的隐状态
out = self.fc(out[:, -1, :])
return out
# 超参数
input_size = 10
hidden_size = 20
output_size = 1
num_layers = 2
# 实例化模型、损失函数和优化器
model = GRUModel(input_size, hidden_size, output_size, num_layers)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 示例输入 (batch_size, sequence_length, input_size)x = torch.randn(5, 3, input_size)
y = torch.randn(5, output_size)
# 前向传播
output = model(x)
loss = criterion(output, y)
print(f'Output: {output}')
print(f'Loss: {loss.item()}')
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
版权声明:
作者:Zhang, Hongxing
链接:http://zhx.info/archives/124
来源:张鸿兴的学习历程
文章版权归作者所有,未经允许请勿转载。
THE END
二维码