梯度下降法 sgd为什么是-号
在梯度下降法(SGD)中使用 “-” 号的本质,是为了让参数沿着损失函数下降最快的方向更新。以下从数学原理、几何直观和代码实现三个维度展开解析:
一、数学推导:梯度的负方向是函数下降最快的方向
1. 梯度的定义与方向
假设损失函数为 \(L(\theta)\),其中 \(\theta\) 是模型参数(如权重 w 和偏置 b)。梯度 \(\nabla L(\theta)\) 是一个向量,其每个元素表示 L 对对应参数的偏导数:\(\nabla L(\theta) = \left( \frac{\partial L}{\partial \theta_1}, \frac{\partial L}{\partial \theta_2}, \dots, \frac{\partial L}{\partial \theta_n} \right)\) 关键性质:梯度方向是函数值上升最快的方向,其反方向(负梯度)是函数值下降最快的方向。
2. 参数更新的数学推导
为了最小化损失函数 \(L(\theta)\),参数 \(\theta\) 需要向损失下降的方向移动。设学习率为 \(\eta\),则参数更新公式为:\(\theta_{t+1} = \theta_t - \eta \cdot \nabla L(\theta_t)\)
- 为什么减梯度?:假设当前梯度 \(\nabla L(\theta_t) = g\),则在 \(\theta_t\) 附近,损失函数的变化可近似为:\(L(\theta_t + \Delta\theta) \approx L(\theta_t) + g^T \cdot \Delta\theta\) 要让 \(L(\theta_t + \Delta\theta) < L(\theta_t)\),需满足 \(g^T \cdot \Delta\theta < 0\)。当 \(\Delta\theta = -\eta g\) 时,\(g^T \cdot \Delta\theta = -\eta \|g\|^2 < 0\),此时 \(\Delta\theta\) 是使 L 下降最快的方向(证明见多元函数泰勒展开)。
二、几何直观:从山坡下山的类比
- 场景类比:将损失函数 \(L(\theta)\) 想象成一个三维山坡,参数 \(\theta\) 是你在山坡上的位置,梯度 \(\nabla L(\theta)\) 是你当前位置的 “上坡方向”(坡度最陡处)。
- 为什么选负梯度?:要最快到达山脚(损失最小处),你应选择与上坡方向相反的路径(即负梯度方向),每一步的步长由学习率 \(\eta\) 决定。
- 反例对比:若误用 “+” 号(沿梯度方向更新),相当于主动往山坡上走,损失函数会越来越大,模型参数发散。
三、代码验证:SGD 中负号的必要性
以线性回归为例,用 PyTorch 实现 SGD 并对比正负号的效果:
python
运行
import numpy as np
import torch
import matplotlib.pyplot as plt
# 生成线性数据:y = 2x + 1 + 噪声
x = np.random.rand(100, 1) * 10
y_true = 2 * x + 1 + np.random.randn(100, 1) * 2
# 定义模型和损失函数
model = torch.nn.Linear(1, 1)
criterion = torch.nn.MSELoss()
# 正确SGD:参数沿负梯度更新
optimizer_correct = torch.optim.SGD(model.parameters(), lr=0.01)
model_correct = model.clone()
# 错误SGD:参数沿正梯度更新(误用+号)
optimizer_wrong = torch.optim.SGD(model.parameters(), lr=0.01)
model_wrong = model.clone()
# 训练过程记录
losses_correct = []
losses_wrong = []
for epoch in range(200):
# 正确更新
y_pred = model_correct(x_tensor)
loss = criterion(y_pred, y_tensor)
optimizer_correct.zero_grad()
loss.backward()
optimizer_correct.step() # 自动处理负号
losses_correct.append(loss.item())
# 错误更新:手动修改梯度符号
y_pred_wrong = model_wrong(x_tensor)
loss_wrong = criterion(y_pred_wrong, y_tensor)
optimizer_wrong.zero_grad()
loss_wrong.backward()
# 关键:将梯度取反(模拟误用+号)
with torch.no_grad():
for param in model_wrong.parameters():
param.grad = -param.grad # 原本应是param.grad,这里取反
optimizer_wrong.step()
losses_wrong.append(loss_wrong.item())
# 可视化结果
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(losses_correct, label='正确SGD(负梯度)')
plt.plot(losses_wrong, label='错误SGD(正梯度)')
plt.legend()
plt.title('损失函数变化对比')
plt.subplot(1, 2, 2)
# 训练后参数
w_correct, b_correct = model_correct.weight.item(), model_correct.bias.item()
w_wrong, b_wrong = model_wrong.weight.item(), model_wrong.bias.item()
plt.scatter(x, y_true, c='blue', label='真实数据')
plt.plot(x, w_correct * x + b_correct,
