首先你得明白什么是梯度,可以看我之前写的一篇博客 :
本质上,梯度是一种方向导数,是一个矢量,因此这里的梯度累加并不是简单的相加,而是类似于初高中物理学的力的合成,梯度作为一种方向导数(矢量)的其累加的效果就是将各个小的梯度合成为一个指向Loss function 最终优化方向的梯度。
这里结合代码理解一下:
正常训练的过程
for i, (images, labels) in enumerate(train_data):
# 1. forwared 前向计算
outputs = model(images)
loss = criterion(outputs, labels)
# 2. backward 反向传播计算梯度
optimizer.zero_grad()
loss.backward()
optimizer.step()
梯度累加的训练过程
# 梯度累加参数
accumulation_steps = 4
for i, (images, labels) in enumerate(train_data):
# 1. forwared 前向计算
outputs = model(imgaes)
loss = criterion(outputs, labels)
# 2.1 loss regularization loss正则化
loss += loss / accumulation_steps
# 2.2 backward propagation 反向传播计算梯度
loss.backward()
# 3. update parameters of net
if ((i+1) % accumulation_steps)==0:
# optimizer the net
optimizer.step()
optimizer.zero_grad() # reset grdient
以上代码来自:
https://www.zhihu.com/question/435093513/answer/2302992975
可以看出,梯度累加的训练过程中,每次反向传播都会计算出一个梯度,但是并不会使用optimizer.step()
更新参数,直到达到设定的累计次数后,才一次性的将参数依据累积的梯度进行更新。(殊途同归,每次计算出来的梯度可以看作是一个具有微弱方向变化的矢量,不断叠加,最终合成一个具有特定方向的矢量。每次变化一点点,和将每次一点点的变化累积起来形成一个最终的变化方向,这在效果上是没有太大差异的),梯度累积可以实现和使用大的Batch Size 接近的效果,但是消耗的GPU显存却不会显著增加,这在GPU显存有限的情况下,是一种较为不错的训练方法。