Deep learning RNN中的梯度累积

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,这似乎很重要-所有东西都需要在正确的位置调用。请注意,

在运行大型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])