在pytorch中求模型准确率时会记录梯度吗?
Will the gradients be recorded when finding the model's accuracy in pytorch?
我开始学习 PyTorch,我对一些事情感到困惑。据我了解,如果我们将 .requires_grad_()
设置为我们的参数,那么将记录查找这些参数梯度的必要计算。这样,我们就可以进行梯度下降了。但是,梯度值将 添加 在之前的梯度值之上,因此在我们执行梯度下降步骤后,我们应该使用 param.grad.zero_()
重置我们的梯度,其中 param
是权重或偏差项。我有一个只有输入层和一个输出神经元的模型,所以非常简单的东西(因为我只有一个输出神经元,你可以看出我只有 2 个可能 类)。我的参数名为 weights
和 bias
,我在这两个变量上设置了 requires_grad_()
。此外,我将训练数据放在名为 train_dl
的 DataLoader
中,将验证数据放在 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()
的某处添加另外 2 行 weights.grad.zero_()
和 bias.grad.zero_()
这样就不会发生这种情况?还是这种情况不会自动发生,所以默认情况下我得到了所需的行为,我只是误解了什么?
有两件事需要考虑:一个是梯度本身,另一个是在每次前向传递中构建的计算图。
为了计算前向传播后的梯度,我们需要记录对哪些张量以何种顺序进行了哪些操作,即计算图。因此,无论何时从具有 requires_grad==True
的其他张量计算新张量时,新张量都有一个属性 .grad_fn
指向先前的操作和涉及的张量。这基本上就是 backward()
“知道”去哪里的方式。如果你调用 backward()
它会考虑这个 .grad_fn
并递归地做反向传播。
所以目前你这样做的方式实际上会构建这个计算图,即使在计算准确性时也是如此。但是如果这张图永远不会被访问,垃圾收集器最终会销毁它。
需要注意的关键是,每个单独的评估都会产生一个 new 计算图(取决于您的模型,可能共享某些部分),但向后传递只会从您调用 .backward
的“节点”,因此在您的代码片段中,您永远不会从准确度计算中获得梯度,因为您从未调用 a.backward()
,您只会调用 loss.backward()
。
尽管计算图的重新编码确实需要一些开销,但是可以使用 torch.no_grad()
context manager 来禁用它,这是针对这个确切的用例而设计的。不幸的是,名称(以及文档)提到了梯度,但它实际上只是关于记录(前向)计算图。但很明显,如果你禁用它,结果你将无法计算向后传递。
存储在叶张量中的梯度(在本例中为 weights
和 bias
)在您调用 .backward()
作为这些张量的后代的张量时更新。由于您没有在 valid_accuracy
期间调用 backward
,因此不会影响渐变。也就是说,PyTorch 会将中间信息存储在临时计算图中(当 valid_accuracy
中的程序 returns 和所有引用计算图的张量超出范围时,这些信息将被丢弃),这需要时间和内存。
如果您确定不需要对模型的输出执行反向传播,您可以并且应该使用 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)
我开始学习 PyTorch,我对一些事情感到困惑。据我了解,如果我们将 .requires_grad_()
设置为我们的参数,那么将记录查找这些参数梯度的必要计算。这样,我们就可以进行梯度下降了。但是,梯度值将 添加 在之前的梯度值之上,因此在我们执行梯度下降步骤后,我们应该使用 param.grad.zero_()
重置我们的梯度,其中 param
是权重或偏差项。我有一个只有输入层和一个输出神经元的模型,所以非常简单的东西(因为我只有一个输出神经元,你可以看出我只有 2 个可能 类)。我的参数名为 weights
和 bias
,我在这两个变量上设置了 requires_grad_()
。此外,我将训练数据放在名为 train_dl
的 DataLoader
中,将验证数据放在 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()
的某处添加另外 2 行 weights.grad.zero_()
和 bias.grad.zero_()
这样就不会发生这种情况?还是这种情况不会自动发生,所以默认情况下我得到了所需的行为,我只是误解了什么?
有两件事需要考虑:一个是梯度本身,另一个是在每次前向传递中构建的计算图。
为了计算前向传播后的梯度,我们需要记录对哪些张量以何种顺序进行了哪些操作,即计算图。因此,无论何时从具有 requires_grad==True
的其他张量计算新张量时,新张量都有一个属性 .grad_fn
指向先前的操作和涉及的张量。这基本上就是 backward()
“知道”去哪里的方式。如果你调用 backward()
它会考虑这个 .grad_fn
并递归地做反向传播。
所以目前你这样做的方式实际上会构建这个计算图,即使在计算准确性时也是如此。但是如果这张图永远不会被访问,垃圾收集器最终会销毁它。
需要注意的关键是,每个单独的评估都会产生一个 new 计算图(取决于您的模型,可能共享某些部分),但向后传递只会从您调用 .backward
的“节点”,因此在您的代码片段中,您永远不会从准确度计算中获得梯度,因为您从未调用 a.backward()
,您只会调用 loss.backward()
。
尽管计算图的重新编码确实需要一些开销,但是可以使用 torch.no_grad()
context manager 来禁用它,这是针对这个确切的用例而设计的。不幸的是,名称(以及文档)提到了梯度,但它实际上只是关于记录(前向)计算图。但很明显,如果你禁用它,结果你将无法计算向后传递。
存储在叶张量中的梯度(在本例中为 weights
和 bias
)在您调用 .backward()
作为这些张量的后代的张量时更新。由于您没有在 valid_accuracy
期间调用 backward
,因此不会影响渐变。也就是说,PyTorch 会将中间信息存储在临时计算图中(当 valid_accuracy
中的程序 returns 和所有引用计算图的张量超出范围时,这些信息将被丢弃),这需要时间和内存。
如果您确定不需要对模型的输出执行反向传播,您可以并且应该使用 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)