
在pytorch模型训练中,参数看似不更新是常见问题。本文将深入探讨这一现象的根本原因,即学习率、梯度大小与参数自身尺度的不匹配。我们将通过一个具体代码示例,分析为何微小的学习率结合相对较小的梯度会导致参数更新量微乎其微,从而在视觉上造成参数未更新的假象。文章将提供解决方案,并强调在优化过程中调试学习率和梯度变化的重要性。
在PyTorch中,模型的参数(通常是 torch.nn.Parameter 类型)通过优化器进行更新。每次迭代,优化器会执行以下核心步骤:
当观察到参数没有明显变化时,通常意味着 learning_rate * grad 这一项的值相对于 param 自身的值来说过于微小,以至于不足以在有限的迭代次数内产生可察觉的变动。
考虑以下一个简化的PyTorch优化示例,目标是使 x_param 经过转换后的权重向量 w 尽可能接近 target_weights_vec:
import torch
import numpy as np
np.random.seed(10)
def optimize(final_shares: torch.Tensor, target_weight, prices, loss_func=None):
# 确保 shares 非负
final_shares = final_shares.clamp(0.)
# 计算市值 (market value)
mv = torch.multiply(final_shares, prices)
# 计算权重 (weights)
w = torch.div(mv, torch.sum(mv))
# print(w) # 调试时可以打印权重变化
return loss_func(w, target_weight)
def main():
position_count = 16
cash_buffer = .001
# 初始参数值,范围在1到50之间
starting_shares = torch.tensor(np.random.uniform(low=1, high=50, size=position_count), dtype=torch.float64)
prices = torch.tensor(np.random.uniform(low=1, high=100, size=position_count), dtype=torch.float64)
prices[-1] = 1.
# 定义可学习参数
x_param = torch.nn.Parameter(starting_shares, requires_grad=True)
# 定义目标权重向量
target_weights = ((1 - cash_buffer) / (position_count - 1))
target_weights_vec = [target_weights] * (position_count - 1)
target_weights_vec.append(cash_buffer)
target_weights_vec = torch.tensor(target_weights_vec, dtype=torch.float64)
loss_func = torch.nn.MSELoss() # 使用均方误差损失
eta = 0.01 # 学习率
optimizer = torch.optim.SGD([x_param], lr=eta) # 使用SGD优化器
print(f"初始x_param均值: {x_param.mean().item():.4f}")
for epoch in range(10000):
optimizer.zero_grad()
loss_incurred = optimize(final_shares=x_param, target_weight=target_weights_vec,
prices=prices, loss_func=loss_func)
loss_incurred.backward()
# 打印梯度信息,辅助调试
# if epoch % 1000 == 0:
# print(f"Epoch {epoch}, Loss: {loss_incurred.item():.6f}, Avg Grad: {x_param.grad.abs().mean().item():.8f}")
optimizer.step()
print(f"训练后x_param均值: {x_param.mean().item():.4f}")
# 最终评估 (使用 .data 访问,不参与梯度计算)
final_loss = optimize(final_shares=x_param.data, target_weight=target_weights_vec,
prices=prices, loss_func=loss_func)
print(f"最终损失: {final_loss.item():.6f}")
if __name__ == '__main__':
main()运行上述代码,即使经过10000次迭代,x_param 的值可能看起来几乎没有变化。例如,初始 x_param 的平均值可能在24左右,而训练后仍然接近24,这让开发者误以为参数没有更新。
问题的核心在于学习率 eta (0.01) 相对于梯度 x_param.grad 的平均幅度以及参数 x_param 自身的平均值来说过小。
因此,即使参数在技术上确实更新了,但每次更新的幅度相对于参数自身的绝对值来说微不足道,以至于在有限的迭代次数内无法观察到显著的变化。
解决这类问题通常需要调整学习率,并结合调试手段来监控训练过程。
最直接的解决方案是增加学习率 eta。例如,将学习率从 0.01 提高到 100。
# ... (代码省略,与原代码相同)
def main():
# ... (代码省略)
eta = 100.0 # 将学习率提高到100
optimizer = torch.optim.SGD([x_param], lr=eta)
print(f"初始x_param均值: {x_param.mean().item():.4f}")
for epoch in range(10000):
optimizer.zero_grad()
loss_incurred = optimize(final_shares=x_param, target_weight=target_weights_vec,
prices=prices, loss_func=loss_func)
loss_incurred.backward()
# 打印梯度信息,辅助调试
if epoch % 1000 == 0:
print(f"Epoch {epoch}, Loss: {loss_incurred.item():.6f}, Avg Grad: {x_param.grad.abs().mean().item():.8f}")
# 打印参数变化,观察更新幅度
# if epoch > 0:
# print(f"Avg param change: {(x_param - prev_x_param).abs().mean().item():.8f}")
# prev_x_param = x_param.clone().detach() # 保存当前参数值
optimizer.step()
print(f"训练后x_param均值: {x_param.mean().item():.4f}")
final_loss = optimize(final_shares=x_param.data, target_weight=target_weights_vec,
prices=prices, loss_func=loss_func)
print(f"最终损失: {final_loss.item():.6f}")
# ... (代码省略)通过将学习率设置为 100,每次迭代的更新量将变为 100 * 1e-5 = 1e-3。此时,参数值改变 1 个单位仅需 1 / 1e-3 = 1000 次迭代,在 10000 次迭代中就能观察到显著的参数变化和损失下降。
为了更好地理解训练过程,建议在训练循环中加入以下调试代码:
当PyTorch参数看似不更新时,请首先检查以下几点:
通过上述分析和调试技巧,开发者可以更有效地诊断和解决PyTorch中参数不更新的问题,从而优化模型的训练过程。记住,理解学习率、梯度和参数尺度之间的相互作用是成功进行深度学习模型训练的关键。
以上就是PyTorch参数不更新:深入理解学习率与梯度尺度的影响的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号