把“大 batch”拆成多个“小 batch”,多次前向+反向,累积梯度后再更新参数
设真实 batch size = (B),损失函数:
[
mathcal{L} = frac{1}{B} sum_{i=1}^{B} ell(x_i, y_i)
]
梯度:
[
g = nabla_theta mathcal{L}
]
参数更新:
[
theta_{t+1} = theta_t - eta cdot g
将大 batch 拆成 (K) 个小 batch,每个小 batch 大小为 (b),满足:
[
B = K times b
]
[
g_k = nabla_theta frac{1}{b} sum_{i in text{batch}_k} ell(x_i, y_i)
]
[
g_{text{acc}} = sum_{k=1}^{K} g_k
]
[
theta_{t+1} = theta_t - eta cdot frac{g_{text{acc}}}{K}
for batch in dataloader:
optimizer.zero_grad()
loss = model(batch)
loss.backward()
optimizer.step()accumulation_steps = K
optimizer.zero_grad()
for i, batch in enumerate(dataloader):
loss = model(batch)
loss = loss / accumulation_steps # 除以 K
loss.backward()
if (i + 1) % K == 0:
optimizer.step()
optimizer.zero_grad()loss = loss / accumulation_steps
loss.backward()✅ 优点:
loss.backward()
if step % K == 0:
for p in model.parameters():
p.grad /= K❌ 缺点:
model.train()
optimizer.zero_grad()
accumulation_steps = 4
for step, (x, y) in enumerate(dataloader):
x, y = x.cuda(), y.cuda()
pred = model(x)
loss = criterion(pred, y)
loss = loss / accumulation_steps # 关键
loss.backward()
if (step + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()BN 的行为依赖于当前 batch 的统计信息:
✅ 解决方案:
经验法则:
学习率 ∝ 有效 batch size
| 误区 | 正确 |
|---|---|
| loss 不除 K | 必须除 |
| 每个 step 都 step | 每 K 次才 step |
| 累积 loss 而不是梯度 | 累积的是梯度 |
| BN 不受影响 | BN 会受影响 |
梯度累积 = 多次前向反向 + 梯度求和 + 延迟参数更新,用来在有限显存下模拟大 batch 训练。
如果你愿意,我也可以:
只要告诉我你当前的使用场景即可。