在pytorch中查找模型的准确性时,是否会记录梯度?



我开始学习PyTorch,但我对一些事情感到困惑。据我所知,如果我们将.requires_grad_()设置为参数,那么将记录找到这些参数的梯度所需的计算。通过这种方式,我们可以执行梯度下降。然而,梯度值将被添加到先前梯度值之上,因此在我们执行梯度下降步骤之后,我们应该使用param.grad.zero_()重置梯度,其中param是权重或偏差项。我有一个模型,它只有一个输入层和一个输出神经元,所以非常简单(因为我只有一个输出神经,你可以看出我只有两个可能的类)。我有我的参数weightsbias,正是在这两个变量上我设置了requires_grad_()。此外,我将训练数据放在一个名为train_dlDataLoader中,并将验证数据放在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)

正如你所看到的,在寻找模型的准确性时,我执行正向传播(上面的函数,我将权重乘以数据并添加偏差)。我的问题是:梯度也会记录在这里吗?那么,下次我在loss上使用.backward()时,梯度会受到寻找精度的步骤的影响吗?我认为现在的情况是,每次我发现模型的准确性时,都会添加梯度值(我不想要,也没有意义),但我不确定。我是否应该在函数valid_accuracy()中的某个位置再加上两行weights.grad.zero_()bias.grad.zero_(),这样就不会发生这种情况?或者这不是自动发生的,所以我默认得到了想要的行为,而我只是误解了什么?

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

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

因此,目前的方法实际上会构建这个计算图,即使在计算精度时也是如此。但是,如果这个图从未被访问过,垃圾收集器最终会销毁它

需要注意的关键是,每个单独的评估都会产生一个新的计算图(取决于您的模型,可能会共享一些部分),但向后传递将仅从";节点";从中您调用了.backward,因此在您的片段中,您永远不会从精度计算中获得梯度,因为您从未调用a.backward(),您只调用loss.backward()

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

当您对作为这些张量的子项的张量调用.backward()时,存储在叶张量(本例中为weightsbias)中的梯度会更新。由于在valid_accuracy期间没有调用backward,因此渐变不会受到影响。也就是说,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)

最新更新