Pytorch的model.train() & model.eval() & torch.no_grad() & 为什么测试的时候不调用loss.backward()计算梯度还要关闭梯度

使用PyTorch进行训练和测试时一定注意要把实例化的model指定train/eval

model.train()

启用 BatchNormalization 和 Dropout

告诉我们的网络,这个阶段是用来训练的,可以更新参数。

model.eval()

不启用 BatchNormalization 和 Dropout。告诉我们的网络,这个阶段是用来测试的

在train模式下,dropout网络层会按照设定的参数p设置保留激活单元的概率(保留概率=p); batchnorm层会继续计算数据的mean和var等参数并更新。

在val模式下,dropout层会让所有的激活单元都通过,而batchnorm层会停止计算和更新mean和var,直接使用在训练阶段已经学出的mean和var值。

nn.Module内部有一个self.training参数,调用model.eval()后,这个参数会被设为False,nn.Dropout操作就是识别这个参数来判断是training还是eval的

训练完train_datasets之后,model要来测试样本了。在model(test_datasets)之前,需要加上model.eval(). 框架会自动把BN和DropOut固定住,不会取平均,而是用训练好的值,不然的话,一旦test的batch_size过小,很容易就会被BN层导致生成图片颜色失真极大!!!!

bn_layer = nn.BatchNorm2d(num_features=3)
inputs = torch.randn(8, 3, 20, 20)

bn_layer.eval()
bn_outputs = bn_layer(inputs)

但是开了model.eval(),并不会影响模型梯度的计算

即因为我们是测试阶段才会开model.eval() , 所以开了之后没人再会去用loss.backward()

但是model.eval()不影响模型梯度的计算,即使这个时候用了loss.backward(),是可以正常更新参数的

import torch
import torch.nn as nn
class model(nn.Module):
    def __init__(self, b):
        super(model, self).__init__()
        self.b = b
    def forward(self, x):
        y = torch.pow(x, 2)
        return y

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
b = torch.tensor([1.0, 1.0, 0.0], requires_grad=True)
model = model(b)

model.eval()                        # model.eval()不影响梯度的计算
# with torch.no_grad():             # with torch.no_grad()会使得y的梯度计算参数为False
y = model(x)
print(y.requires_grad)

y.backward(torch.ones_like(x))      # pytorch无法进行tensor对tensor的求导,因此此处需要添加一个参数,得到一个标量,通过标量对tensor的求导,来计算想要的结果。
print(x.grad)

torch.no_grad()

torch.no_grad也是用在测试的时候,即和model.eval()用在一个地方,先model.eval()再torch.no_grad()

torch.no_grad() 是一个上下文管理器,负责关掉跟踪(反向)梯度计算,节省eval的时间和显存占用 

不会追踪计算步骤,即构建计算图就不会记录torch.no_grad()包住的部分,那么也就不会计算它们的梯度。即虽然你进行了乘法操作,但是没有记录这个操作,所以梯度返传的时候就不会算这一步的梯度。

说torch.no_grad()关闭梯度计算的说法也是对的,因为torch.no_grad包住的非叶子节点的requires_grad属性变为了False,只要requires_grad=False就不会计算梯度。当然在inference的时候本身也不会调用loss.backward()计算梯度,而如果你想调用你也是调用不了的。只是这不是inference的时候用torch.no_grad能减少显存占用的原因。

      被torch.no_grad()包裹起来的部分不会被追踪梯度,虽然仍可以前向传播进行计算得到输出,但计算过程(grad_fn)不会被记录,也就不能反向传播更新参数。具体地,对非叶子节点来说

  • 非叶子节点的requires_grad属性变为了False
  • 非叶子节点的grad_fn属性变为了None

      这样便不会计算非叶节点的梯度。因此,虽然叶子结点(模型各层的可学习参数)的requires_grad属性没有改变(依然为True),也不会计算梯度,grad属性为None,且如果使用loss.backward()会报错(因为第一个非叶子节点(loss)的requires_grad属性为False,grad_fn属性为None)。因此,模型的可学习参数不会更新。
 

只进行inference时,model.eval()是必须使用的,否则会影响结果准确性。 而torch.no_grad()并不是强制的,只影响运行效率

如果不在意显存大小和计算时间的话,仅仅使用model.eval()已足够得到正确的validation的结果;而with torch.zero_grad()则是更进一步加速和节省gpu空间(因为不用计算和存储gradient),从而可以更快计算,也可以跑更大的batch来测试

model.eval()模式不会影响各层的gradient计算行为,即gradient计算和存储与training模式一样,只是不进行反传(backprobagation)

而with torch.no_grad()则主要是用于停止autograd模块的工作,以起到加速和节省显存的作用,具体行为就是停止gradient计算,从而节省了GPU算力和显存,但是并不会影响dropout和batchnorm层的行为。

为什么测试的时候不调用loss.backward()计算梯度还要torch.no_grad()关闭梯度计算

      显然这不是多余的。因为实验完全可以证明,即使不调用loss.backward(), 测试的时候关闭梯度计算的显存会少很多。

      这是因为在训练过程中由于loss.backward() 会将计算图释放,从而释放显存空间。而在测试的时候没有这一机制,因此有可能随着测试的进行中间变量越来越多,从而容易导致out of memory的发生。

      使用torch.no_grad()的话,对包住的代码块直接不会构建计算图,也就不会计算梯度。因此 可以帮助节省内存空间。

      所以能够帮助节省空间是储在计算图上(即每个tensor的grad_fn值),而不是每个节点的梯度值上,因为就算不加torch.no_grad, 你不调用loss.backward(), 每个点也是没有梯度的。

这也就能符合pytorch官方文档的话了

torch.no_grad() impacts the autograd engine and deactivate it. It will reduce memory usage and speed up computations but you won’t be able to backprop

实验展示被with torch.no_grad()包住的代码,不用跟踪反向梯度计算

Pytorch的model.train() & model.eval() & torch.no_grad() & 为什么测试的时候不调用loss.backward()计算梯度还要关闭梯度_第1张图片

b = a * 2后输出tensor([2.2000], grad_fn=) 可以看到梯度函数是MulBackward0,表示乘法的反向梯度函数

 b.add_(2)后输出tensor([4.2000], grad_fn=) 表明是add的反向梯度函数

但是被torch.no_grad()包住后的b.mul_(2) 却输出tensor([8.4000], grad_fn=) 可以看到没有跟踪乘法的梯度,还是上面的加法的梯度函数,不过乘法是执行了的

你可能感兴趣的:(pytorch,1024程序员节)