在pytorch中查找模型的准确率时会记录梯度吗?

青葱年少 pytorch 217

原文标题Will the gradients be recorded when finding the model’s accuracy in pytorch?

我开始学习 PyTorch,但我对某些事情感到困惑。据我了解,如果我们将.requires_grad_()设置为我们的参数,那么将记录找到这些参数梯度的必要计算。这样,我们可以执行梯度下降。然而,梯度值将被添加到之前的梯度值之上,因此在我们执行梯度下降步骤之后,我们应该使用param.grad.zero_()重置我们的梯度,其中param是权重或偏差项。我有一个模型,它只有输入层和一个输出神经元,非常简单(因为我只有一个输出神经元,你可以看出我只有 2 个可能的类)。我有我的参数称为weightsbias,我在这两个变量上设置requires_grad_()。此外,我将训练数据放入DataLoader称为train_dl,将验证数据放入valid_dl。我使用了 MNIST 数据集的一个子集,但这对于这个问题真的不重要。这些是我使用的功能:

def forward_propagation(xb):
    z = xb @ weights + bias
    a = z.sigmoid()
    return a

def mse_loss(predictions, targets):
    loss = ((predictions - targets) ** 2).mean()
    return loss

def backward_propagation(loss):
    loss.backward()
    weights.data -= lr * weights.grad.data
    weights.grad.zero_()
    bias.data -= lr * bias.grad.data
    bias.grad.zero_()

def train_epoch():
    for xb, yb in train_dl:
        a = forward_propagation(xb)
        loss = mse_loss(a, yb)
        backward_propagation(loss)

正如你所看到的,我使用函数train_epoch()来执行:前向传播(其中一些梯度计算将被记录,因为这是我们第一次使用参数的地方),计算损失(这一步也将用于计算梯度),然后反向传播,我更新我的参数,然后将梯度重置为 0,这样它们就不会累积。我使用这段代码来训练我的模型,它运行良好,我对我得到的准确性感到满意。所以我认为它至少在某种程度上是有效的。

但我也使用此代码来查找我的模型的验证数据准确性:

def valid_accuracy():
    accuracies = []
    for xb, yb in valid_dl:
        a = forward_propagation(xb)
        correct = (a > 0.5) == yb
        accuracies.append(correct.float().mean())
    return round(torch.stack(accuracies).mean().item(), 4)

如您所见,在找到模型的准确性时,我执行前向传播(上述函数,我将权重乘以数据并添加偏差)。我的问题是:梯度也会被记录在这里吗?那么下次我使用.backward()onloss时,梯度是否会受到寻找精度所采取的步骤的影响?我认为就像现在一样,每次我找到模型的准确性时都会添加梯度值(我不想要也没有意义),但我不确定。我应该在函数中的某个地方valid_accuracy()另外 2 行带有weights.grad.zero_()bias.grad.zero_(),这样就不会发生这种情况吗?还是这种情况不会自动发生,所以默认情况下我得到了所需的行为,而我只是误解了一些东西?

原文链接:https://stackoverflow.com//questions/71550601/will-the-gradients-be-recorded-when-finding-the-models-accuracy-in-pytorch

回复

我来回复
  • flawr的头像
    flawr 评论

    有两件事需要考虑:一是梯度本身,二是在每个前向传递中构建的计算图。

    为了计算前向传递后的梯度,我们需要记录对哪些张量以什么顺序进行了哪些操作,即计算图。因此,每当从具有requires_grad==True的其他张量计算新张量时,新张量具有指向先前操作和所涉及张量的属性.grad_fn。这基本上是如何backward()“知道”去哪里的。如果你调用backward(),它会考虑 this.grad_fn 并递归地进行反向传递。

    所以目前你这样做的方式实际上会构建这个计算图,即使在计算准确性时也是如此。但是如果这个图从未被访问过,垃圾收集器最终会销毁它。

    需要注意的关键是每个单独的评估都会产生一个新的计算图(取决于你的模型可能共享一些部分),但是向后传递只会从你调用的“节点”开始.backward,所以在你的片段中你会赢永远不会从精度计算中得到梯度,因为你从不调用a.backward(),你只调用loss.backward()

    计算图的重新编码确实需要一些开销,但是可以使用torch.no_grad()上下文管理器禁用它,这是考虑到这个确切的用例。不幸的是,名称(以及文档)提到了梯度,但它实际上只是记录(前向)计算图。但很明显,如果你禁用它,你也将无法计算向后传递。

    2年前 0条评论
  • jodag的头像
    jodag 评论

    当您调用.backward()作为这些张量的后代的张量时,存储在叶张量(本例中为weightsbias)中的梯度会更新。由于您没有调用backwardduringvalid_accuracy,因此渐变不会受到影响。也就是说,PyTorch 会将中间信息存储在临时计算图中(当程序从valid_accuracy返回并且所有引用计算图的张量超出范围时将被丢弃),这需要时间和内存。

    如果您确定不需要对模型的输出执行反向传播,则可以并且应该使用torch.no_grad()上下文。这将禁用中间结果的记录。例如

    def valid_accuracy():
        with torch.no_grad():
            accuracies = []
            for xb, yb in valid_dl:
                a = forward_propagation(xb)
                correct = (a > 0.5) == yb
                accuracies.append(correct.float().mean())
            return round(torch.stack(accuracies).mean().item(), 4)
    
    2年前 0条评论