Skip to content

知识点卡片:梯度下降与SGD

基本信息

属性内容
知识点梯度下降 (GD/SGD/Momentum)
掌握程度★★★★★
学习优先级P0
预估时间6小时
面试频率★★★★★

核心原理

梯度下降 (GD)

θ_{t+1} = θ_t - η ∇_θ L(θ_t)

使用全部数据计算梯度。
优点:梯度精确
缺点:每步需要计算所有样本,太慢

随机梯度下降 (SGD)

θ_{t+1} = θ_t - η ∇_θ L(θ_t; x_i, y_i)

每次使用一个样本计算梯度。
优点:更新快,可逃离局部最优
缺点:梯度噪声大,收敛震荡

Mini-batch SGD

θ_{t+1} = θ_t - η * (1/|B|) Σ_{i∈B} ∇_θ L(θ_t; x_i, y_i)

折中方案,实际使用。

Momentum

v_t = β v_{t-1} + (1-β) ∇_θ L(θ_t)
θ_{t+1} = θ_t - η v_t

类似物理动量:维持之前的方向,加速收敛
β通常=0.9

代码实现

python
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim

# 1. 从零实现SGD
class SGD:
    def __init__(self, lr=0.01):
        self.lr = lr

    def update(self, params, grads):
        for param, grad in zip(params, grads):
            param -= self.lr * grad

# 2. 从零实现Momentum
class Momentum:
    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.velocities = None

    def update(self, params, grads):
        if self.velocities is None:
            self.velocities = [np.zeros_like(p) for p in params]

        for i, (param, grad) in enumerate(zip(params, grads)):
            self.velocities[i] = self.momentum * self.velocities[i] + (1 - self.momentum) * grad
            param -= self.lr * self.velocities[i]

# 3. PyTorch使用
model = nn.Linear(10, 1)
optimizer_sgd = optim.SGD(model.parameters(), lr=0.01)
optimizer_momentum = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer_nesterov = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, nesterov=True)

三种方法对比

方法梯度来源更新频率收敛速度噪声
GD全数据集1次/epoch
SGD单样本N次/epoch
Mini-batch小批量N/B次/epoch最快可控

面试高频问题

Q1: Batch Size对训练的影响?

方面小Batch大Batch
梯度精度噪声大精确
收敛速度快(更新频繁)慢(更新少)
泛化能力通常更好可能更差
显存占用
并行效率

实践:通常选32/64/128,根据显存调整。小batch的噪声有助于逃离局部最优,是一种隐式正则化。

Q2: Momentum为什么加速收敛?

  • 在梯度方向一致时,速度累积(加速)
  • 在梯度方向震荡时,震荡互相抵消(稳定)
  • 类似物理中的惯性:保持运动方向,不容易被单个样本的梯度带偏

Q3: Nesterov Momentum与普通Momentum的区别?

普通Momentum:先计算当前位置梯度,再沿速度方向移动
v = βv + (1-β)∇L(θ)
θ = θ - ηv

Nesterov:先在速度方向移动,再在新位置计算梯度("前瞻")
θ_lookahead = θ - ηβv
v = βv + (1-β)∇L(θ_lookahead)
θ = θ - ηv

Nesterov更智能:在移动后再看梯度,可以"提前减速"
理论上有更好的收敛率(O(1/t²) vs O(1/t))

学习率的影响

python
# 学习率过大 → 震荡甚至发散
# 学习率过小 → 收敛太慢
# 学习率适中 → 平滑收敛

# 学习率折线图
lrs = [0.001, 0.01, 0.1, 1.0]
for lr in lrs:
    losses = []
    x = 5.0
    for _ in range(20):
        grad = 2 * (x - 3)  # f(x) = (x-3)²
        x = x - lr * grad
        losses.append((x - 3) ** 2)
    print(f"lr={lr}: final loss={losses[-1]:.6f}")
# lr=1.0震荡,lr=0.01正常收敛

练习题

python
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# 模拟不同优化器的轨迹
def rosenbrock(x, y):
    """香蕉函数:经典优化测试函数"""
    return (1 - x)**2 + 100 * (y - x**2)**2

# 梯度
def rosenbrock_grad(x, y):
    dx = -2*(1-x) - 400*x*(y - x**2)
    dy = 200*(y - x**2)
    return np.array([dx, dy])

# GD vs SGD vs Momentum
x_gd = np.array([-1.0, 1.0])
x_sgd = np.array([-1.0, 1.0])
x_mom = np.array([-1.0, 1.0])

lr = 0.001
v = np.zeros(2)
hist = {'gd': [], 'sgd': [], 'mom': []}

for i in range(1000):
    # GD
    grad_full = rosenbrock_grad(*x_gd)
    x_gd -= lr * grad_full
    hist['gd'].append(x_gd.copy())

    # SGD(加噪声模拟)
    grad_noisy = grad_full + np.random.randn(2) * 0.5
    x_sgd -= lr * grad_noisy
    hist['sgd'].append(x_sgd.copy())

    # Momentum
    grad_mom = rosenbrock_grad(*x_mom)
    v = 0.9 * v + (1 - 0.9) * grad_mom
    x_mom -= lr * v
    hist['mom'].append(x_mom.copy())

print(f"GD final: {x_gd}")
print(f"SGD final: {x_sgd}")
print(f"Momentum final: {x_mom}")

相关知识点