Deep learning RNN中的梯度累积
在运行大型RNN网络时,我遇到了一些内存问题(GPU),但我想保持批大小合理,所以我想尝试梯度累积。在一次预测输出的网络中,这似乎是不言而喻的,但在RNN中,每个输入步骤都要进行多次正向传递。正因为如此,我担心我的实现没有按预期工作。我从用户albanD的优秀示例开始,但我认为在使用RNN时应该对其进行修改。我认为这是因为你积累了更多的梯度,因为你在每个序列中做了多次转发 我当前的实现看起来是这样的,同时允许PyTorch 1.6中的AMP,这似乎很重要-所有东西都需要在正确的位置调用。请注意,这只是一个抽象版本,看起来可能有很多代码,但主要是注释Deep learning RNN中的梯度累积,deep-learning,pytorch,recurrent-neural-network,gradient-descent,Deep Learning,Pytorch,Recurrent Neural Network,Gradient Descent,在运行大型RNN网络时,我遇到了一些内存问题(GPU),但我想保持批大小合理,所以我想尝试梯度累积。在一次预测输出的网络中,这似乎是不言而喻的,但在RNN中,每个输入步骤都要进行多次正向传递。正因为如此,我担心我的实现没有按预期工作。我从用户albanD的优秀示例开始,但我认为在使用RNN时应该对其进行修改。我认为这是因为你积累了更多的梯度,因为你在每个序列中做了多次转发 我当前的实现看起来是这样的,同时允许PyTorch 1.6中的AMP,这似乎很重要-所有东西都需要在正确的位置调用。请注意,
def系列(历次):
“”“主训练循环。`epoch`的循环次数。调用`process`.””
对于范围内的历元(1,历元+1):
列车损失=过程(“列车”)
有效损失=过程(“有效”)
# ... 检查我们是否比早期有所改进
如果lr_调度程序:
lr_调度程序。步骤(有效_丢失)
def流程(do):
“”“在训练集或验证集的数据加载器中运行单个历元。”。
还负责在每个“梯度积累”步骤后对模型进行优化。
对从中获得损失的每批调用“步骤”
如果do=“训练”:
模型列车()
火炬。设置\梯度\启用(真)
其他:
model.eval()
火炬。设置梯度启用(错误)
损失=0。
对于batch_idx,枚举中的批处理(数据加载程序[do]):
阶跃损耗,平均阶跃损耗=阶跃(批次)
损耗+=平均阶跃损耗
如果do=“训练”:
如果放大器:
scaler.scale(阶跃损耗).backward()
如果(批次\u idx+1)%gradient\u Cumulation\u steps==0:
#取消销售优化器指定参数的梯度
scaler.unscale(优化器)
#卡入到位
clip_grad_norm_(model.parameters(),2.0)
scaler.step(优化器)
scaler.update()
模型0_梯度()
其他:
步进损失向后()
如果(批次\u idx+1)%gradient\u Cumulation\u steps==0:
clip_grad_norm_(model.parameters(),2.0)
optimizer.step()
模型0_梯度()
#收益平均损失
返回丢失/长度(数据加载程序[do])
定义步骤():
“”“通过多次转发来处理一个步骤(一批),以获得给定序列的最终预测。”“”
#做些事情。。。初始化隐藏状态和第一次输入等。
损耗=火炬张量([0.])到(装置)
对于范围内的i(目标长度):
使用torch.cuda.amp.autocast(启用=安培):
#覆盖以前的解码器。\u隐藏
输出,解码器隐藏=模型(解码器输入,解码器隐藏)
#计算预测类(bs x类)和该词的正确类之间的损失_
项目损失=标准(输出、目标张量[i])
#我们计算平均步长的梯度,以便
#我们确实需要一个优化器。步骤,它考虑了平均步骤损失
#跨批次。所以基本上(A+B+C)/3=A/3+B/3+C/3
损失+=(项目损失/梯度累积步数)
topv,topi=output.topk(1)
解码器输入=topi.detach()
返回loss,loss.item()/target\u len
上述方法似乎没有如我所希望的那样有效,即它仍然很快遇到内存不足的问题。我想原因是
step
已经积累了这么多信息,但我不确定。为了简单起见,我只考虑amp
启用的梯度积累,没有amp的想法是一样的。您的步骤是在amp
下运行的,所以让我们坚持下去
步骤
这里有一个梯度累积的例子。您应该在步骤中执行此操作。每次运行loss.backward()
时,可以通过optimizer
优化的张量叶中累积梯度。因此,您的步骤应该如下所示(请参见注释):
无论怎样,当您分离
解码器_输入
时(就像没有历史记录的全新隐藏输入,参数将在此基础上优化,不是基于所有运行),过程中不需要向后
。此外,您可能不需要解码器_hidden
,如果它没有传递到网络,则会隐式传递填充有零的torch.tensor
此外,我们还应该除以gradient\u contraction\u steps*target\u len
,因为这是在单个优化步骤之前,我们将运行的backward
s的数量
由于你的一些变量定义不清,我想你只是对发生的事情做了一个计划
此外,如果您希望保留历史记录,则不应分离
解码器输入,在这种情况下,它将如下所示:
def step():
"""Processes one step (one batch) by forwarding multiple times to get a final prediction for a given sequence."""
loss = 0
for i in range(target_len):
with torch.cuda.amp.autocast(enabled=amp):
output, decoder_hidden = model(decoder_input, decoder_hidden)
item_loss = criterion(output, target_tensor[i]) / (
gradient_accumulation_steps * target_len
)
_, topi = output.topk(1)
decoder_input = topi
loss += item_loss
scaler.scale(loss).backward()
return loss.detach().cpu() / target_len
这有效地通过RNN多次,可能会提高OOM,但不确定您在这里追求什么。如果是这样的话,你就没什么办法了,因为RNN计算太长,无法放入GPU
过程
本规范仅提供了相关部分,因此:
loss = 0.0
for batch_idx, batch in enumerate(dataloaders[do]):
# Here everything is detached from graph so we're safe
avg_step_loss = step(batch)
loss += avg_step_loss
if do == "train":
if (batch_idx + 1) % gradient_accumulation_steps == 0:
# You can use unscale as in the example in PyTorch's docs
# just like you did
scaler.unscale_(optimizer)
# clip in-place
clip_grad_norm_(model.parameters(), 2.0)
scaler.step(optimizer)
scaler.update()
# IMO in this case optimizer.zero_grad is more readable
# but it's a nitpicking
optimizer.zero_grad()
# return average loss
return loss / len(dataloaders[do])
问题式
[…]在RNN中,您为每个输入步骤执行多个正向传递。
正因为如此,我担心我的实现没有像预期的那样有效
有意的
没关系。对于每一个前进,你通常应该做一个后退(这里似乎是这样,se
loss = 0.0
for batch_idx, batch in enumerate(dataloaders[do]):
# Here everything is detached from graph so we're safe
avg_step_loss = step(batch)
loss += avg_step_loss
if do == "train":
if (batch_idx + 1) % gradient_accumulation_steps == 0:
# You can use unscale as in the example in PyTorch's docs
# just like you did
scaler.unscale_(optimizer)
# clip in-place
clip_grad_norm_(model.parameters(), 2.0)
scaler.step(optimizer)
scaler.update()
# IMO in this case optimizer.zero_grad is more readable
# but it's a nitpicking
optimizer.zero_grad()
# return average loss
return loss / len(dataloaders[do])