PyTorch LSTM输出对输入的自动导数错误、输入子集输出不匹配问题
PyTorch LSTM相同输入值输出不一致、自动导数与数值导数存在偏差问题
我正在使用LSTM(长短期记忆神经网络)进行特定时间序列学习,其具体应用场景此处不做展开。
在计算PyTorch LSTM输出相对于输入的自动导数时,我发现部分输入区间的结果与数值导数相比存在偏差。
此外,输入原始输入的子集时,得到的输出与原输入对应的输出不一致。我刚接触循环神经网络,对这类异常行为的成因不了解,希望有人可以帮忙解释。
该问题可通过以下单输入单输出的简单LSTM最小复现代码复现:
import torch from torch import nn from torch import autograd import matplotlib.pyplot as plt ### USER INPUT input_dim = 1 hidden_dim = 20 num_layers = 1 output_dim = 1 # PyTorch随机数生成器种子固定 torch.manual_seed(1234) ### 模型定义 class Lstm(nn.Module): """简易长短期记忆神经网络""" def __init__(self, input_dim: int, hidden_dim: int, num_layers: int, output_dim: int): """初始化简易LSTM 参数 ---------- input_dim : int 神经网络输入维度 hidden_dim : int 神经网络隐藏层维度 num_layers : int 神经网络隐藏层数 output_dim : int 神经网络输出维度 """ # 调用父类初始化方法 super().__init__() # 存储参数 self.input_dim = input_dim self.hidden_dim = hidden_dim self.num_layers = num_layers # 循环神经网络层 self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True) # 全连接层 self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, input: torch.Tensor) -> torch.Tensor: """LSTM前向传播 参数 ---------- input : torch.Tensor 神经网络输入 Returns ------- torch.Tensor 神经网络输出 """ t = input.float().unsqueeze(0) # 初始化隐藏层与细胞状态 batch_size = 1 # 简化起见仅使用单batch hidden = torch.zeros(self.num_layers, batch_size, self.hidden_dim) cell = torch.zeros(self.num_layers, batch_size, self.hidden_dim) # 输入与隐藏状态传入模型得到输出 out, hidden = self.lstm(t, (hidden, cell)) # 调整输出维度适配全连接层 out = out.squeeze() out = self.fc(out) return out ### 计算逻辑 # 创建单输入单输出维度的LSTM my_lstm = Lstm(input_dim, hidden_dim, num_layers, output_dim) # 构造两组重叠的时间序列输入 times1 = torch.linspace(0.0, 1.0, 100).unsqueeze(1) times2 = torch.linspace(0.2, 1.0, 80).unsqueeze(1) times1.requires_grad = True times2.requires_grad = True # 神经网络前向计算 outputs1 = my_lstm.forward(times1) outputs2 = my_lstm.forward(times2) # 计算第一组时间序列的自动导数 auto_deriv1 = autograd.grad(outputs1, times1, torch.ones(times1.shape), retain_graph=True, create_graph=True)[0] # 计算第一组时间序列的数值导数 numeric_deriv1 = (outputs1[1:-1] - outputs1[0:-2]) / (times1[1:-1] - times1[0:-2]) # 绘制输出对比图 plt.figure() plt.plot(times1.detach().numpy(), outputs1.detach().numpy(), 'b') plt.plot(times2.detach().numpy(), outputs2.detach().numpy(), 'r') plt.grid("on") plt.xlabel("LSTM输入") plt.ylabel("LSTM输出") plt.legend(["完整时间序列", "后半段时间序列"]) plt.savefig("lstm_output.png") # 绘制第一组时间序列的导数对比图 plt.figure() plt.plot(times1.detach().numpy(), auto_deriv1.detach().numpy(), 'b') plt.plot(times1.detach().numpy()[0:-2], numeric_deriv1.detach().numpy(), 'b--') plt.grid("on") plt.xlabel("LSTM输入") plt.ylabel("LSTM输出导数") plt.legend(["自动导数", "数值导数"]) plt.savefig("lstm_derivatives.png")
我得到的结果图如下:
- 两个时间序列的LSTM输出,我原本预期两条曲线应该完全重合。

- LSTM输出相对于输入的自动导数与数值导数对比,我原本预期两条曲线应该完全一致。

由于偏差主要出现在时间序列的起始段,我猜测这和所用神经网络的循环特性有关,但我无法理解为什么相同的输入值没有得到相同的输出,也不清楚自动导数错误的原因,恳请各位解答。
问题解答
两个异常表现本质上都是LSTM的固有特性,不存在框架bug或代码逻辑错误:
- 相同输入输出不一致的原因
LSTM当前时刻的输出不止取决于当前输入,还取决于之前所有时刻输入累积更新得到的隐藏状态、细胞状态。你两次前向传播时都将状态初始化为全0:- 完整序列
times1输入时,0.2对应第20个时间步的输入,此时的状态已经经过前19个时间步的更新,不是初始全0值 - 后半段序列
times2输入时,0.2对应第一个时间步的输入,此时的状态为初始全0值
输入相同但状态不同,输出自然存在差异,偏差会随着时间步推移逐渐缩小,和你观察到的曲线后半段几乎重合的现象完全吻合。
- 完整序列
- 自动导数与数值导数不一致的原因
你计算的数值导数是相邻时刻输出差除以输入差,本质上包含了当前输出对历史所有输入的梯度贡献,属于全导数近似;而autograd.grad计算的自动导数是当前时刻输出对当前时刻输入的偏导数,不包含历史输入的梯度影响,两者物理意义本身就不同,自然不会完全匹配。
内容的提问来源于stack exchange,提问作者Waldemar Martens
相关产品推荐
相关产品推荐

