为何无法过拟合单批次数据?损失持续震荡问题排查
尝试训练一个用于回归任务的简单前馈神经网络,目标是过拟合包含32个样本(9个特征)的单批次数据,以此验证模型实现是否正确。但无论如何调整学习率和隐藏层大小,损失始终呈现震荡状态。
数据已做标准化处理,网络结构为带ReLU激活函数的单隐藏层,代码如下:
BATCH_SIZE = 32 LR = 0.0001 NUM_EPOCHS = 100 HIDDEN_SIZE = 512 class FullyConnected(nn.Module): def __init__(self, hidden_size=HIDDEN_SIZE): super().__init__() self.fc1 = nn.Linear(in_features=in_features, out_features=hidden_size) self.fc2 = nn.Linear(in_features=hidden_size, out_features=1) def forward(self, x): out = self.fc1(x) out = F.relu(out) out = self.fc2(out) return out
训练循环仅针对单批次数据进行过拟合,代码如下:
model = FullyConnected().to(device) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=LR) model.train() X, y = next(iter(train_dataloader)) for epoch in range(NUM_EPOCHS): y_pred = model(X) loss = criterion(y_pred, y) loss.backward() optimizer.step() optimizer.zero_grad()
为何学习率已设置较小,仍无法过拟合单批次且损失异常震荡?
梯度清零时机错误
训练循环里的梯度清零步骤放在了参数更新之后,这会导致第一轮迭代时梯度未清零(使用初始随机梯度),后续轮次也可能残留上一轮的梯度,直接引发参数更新混乱,造成损失震荡。
修正后的训练循环:for epoch in range(NUM_EPOCHS): optimizer.zero_grad() # 先清零梯度 y_pred = model(X) loss = criterion(y_pred, y) loss.backward() optimizer.step()训练轮次不足
100轮的训练次数对于过拟合单批次数据来说可能不够。即使是小数据集,也需要足够多的轮次让模型参数收敛到能完全拟合该批次的状态。可以尝试将轮次提升至500甚至1000,观察损失是否会逐渐下降并稳定。目标值未标准化
虽然你对特征做了标准化,但如果目标值y未做同样处理(比如缩放到均值为0、方差为1的范围),过大的目标值会导致初始损失过高,即使小学习率也会引发参数更新的剧烈波动。确认y是否完成标准化处理。Adam优化器动量参数不匹配小数据集
Adam默认的betas=(0.9, 0.999)在小数据集上可能导致动量累积不稳定,进而引发损失震荡。可以尝试换成SGD优化器(搭配1e-3左右的学习率),或者调整Adam的betas为(0.5, 0.9),降低一阶动量的累积速度。隐藏层ReLU激活导致梯度消失
如果隐藏层神经元大部分处于“死亡”状态(输出为0),会导致梯度无法有效传递到输入层,参数更新停滞或混乱。可以尝试减小隐藏层大小(比如从512降到64),或者在ReLU后添加批量归一化层,提升梯度流动的稳定性:class FullyConnected(nn.Module): def __init__(self, hidden_size=HIDDEN_SIZE): super().__init__() self.fc1 = nn.Linear(in_features=in_features, out_features=hidden_size) self.bn = nn.BatchNorm1d(hidden_size) self.fc2 = nn.Linear(in_features=hidden_size, out_features=1) def forward(self, x): out = self.fc1(x) out = self.bn(out) out = F.relu(out) out = self.fc2(out) return out
内容的提问来源于stack exchange,提问作者SlimakSlimak

