PyTorch中双向GRU输出与最后层隐藏状态为何不一致?
问题复现代码
import torch from torch import nn input_ids = torch.randn((1,4,3)) gru = nn.GRU(input_size=3, hidden_size=5, bidirectional=True, num_layers=1, batch_first=True) o, h = gru(input_ids) print(o[:,-1,:]) # 获取最后一层隐藏状态的最后时间步 print(torch.cat([h[0::2, :, :], h[1::2,:,:]], dim=-1)) # 拼接正向与反向隐藏状态
输出结果:
tensor([[-0.2308, 0.0597, -0.4346, -0.3713, 0.2811, 0.2582, -0.0627, 0.3114, -0.3813, 0.0590]], grad_fn=<SliceBackward0>) tensor([[[-0.2308, 0.0597, -0.4346, -0.3713, 0.2811, 0.3513, -0.0135, 0.1894, -0.4211, 0.0058]]], grad_fn=<CatBackward0>)
核心差异原因
单层单向GRU中,output的最后一个时间步结果和hidden状态完全一致,这是因为单向GRU的最终隐藏状态就是序列最后一个时间步的输出特征。但双向GRU的逻辑完全不同:
双向GRU包含正向、反向两个独立的GRU分支:
- 正向GRU按序列顺序(t0→t1→t2→t3)处理输入,最终隐藏状态
h[0]是处理完最后一个时间步t3后的状态,对应output最后时间步的前半部分特征; - 反向GRU按序列逆序(t3→t2→t1→t0)处理输入,最终隐藏状态
h[1]是处理完第一个时间步t0后的状态,而output最后时间步的后半部分特征,是反向GRU处理t3时的输出(即反向GRU的第一个时间步输出)。
这就是代码中两组结果不一致的核心原因——你拼接的反向状态和output最后时间步的反向状态,根本不是同一个时间步的输出。
反向传播的差异
反向传播没有额外特殊处理,梯度计算完全基于各自的计算图:
output[:,-1,:]的梯度对应序列最后一个时间步的正向+反向输出节点的梯度;h的梯度对应正向GRU最终状态、反向GRU最终状态节点的梯度。
由于前向传播时两者代表的状态本身不同,反向传播的梯度自然也会不同,但都是符合GRU计算逻辑的正确结果。
官方社区的解释
双向RNN的设计逻辑可总结为:
正向隐藏状态是处理完序列最后一个元素后的状态,反向隐藏状态是处理完序列第一个元素后的状态(因为反向RNN逆序处理序列)。输出的第t个时间步是正向RNN在t步的输出与反向RNN在t步的输出的拼接(其中反向RNN在t步的输出是处理原序列第L-1-t个元素后的输出)。
内容的提问来源于stack exchange,提问作者jupyter
相关产品推荐
相关产品推荐

