pytorch网络冻结的三种方法区别:detach、requires_grad、with_no_grad
文章目录
1、requires_grad
requires_grad=True # 要求计算梯度;
requires_grad=False # 不要求计算梯度;
在pytorch中,tensor
有一个 requires_grad
参数,如果设置为True
,那么它会追踪对于该张量的所有操作。在完成计算时可以通过调用backward()
自动计算所有的梯度,并且,该张量的所有梯度会自动累加到张量的.grad
属性;反之,如果设置为False
,则不会记录这些操作过程,自然而然就不会进行计算梯度的工作 。 tensor
的requires_grad
的属性默认为False
.
x = torch.tensor([1.0, 2.0])
x.requires_grad
"""
结果:
False
"""
我们可以先看一下requires_grad参数设置分别为True和False时的情况。
# 设置好requires_grad的值为True
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = torch.tensor([3.0, 4.0], requires_grad=False)
y1 = 2.0 * x + 2.0 * y
print(x, x.requires_grad)
print(y, y.requires_grad)
print(y1, y1.requires_grad)
y1.backward(torch.tensor([1.0, 1.0]))
print(x.grad)
print(y.grad)
"""
结果:
tensor([1., 2.], requires_grad=True) True
tensor([3., 4.]) False
tensor([ 8., 12.], grad_fn=<AddBackward0>) True
tensor([2., 2.])
None
"""
在上面的实验中,发现在计算中如果存在tensor
张量x
的requires_grad
为True
的情况,那么计算之后的结果y1
的requires_grad
也为True,且计算梯度时仅会计算x
的梯度,因为前面设置了y
张量的requires_grad
为False
,所以最后y
张量的grad
属性值为None
。
关于张量tensor
的梯度计算可以参考另一篇博客:Tensor及其梯度
所以在深度学习训练时,要冻结部分权值参数不进行参数更新的话,可以在优化器初始化之前将参数组进行筛选,将不想进行训练的参数的requires_grad
设置为False
。代码示例参考如下:
cnn = CNN() #构建网络
for n,p in cnn.named_parameters():
print(n,p.requires_grad)
if n=="conv1.0.weight":
p.requires_grad = False
optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad,cnn.parameters()), lr=learning_rate)
也可以把requires_grad
属性置为 False
这个操作放在optimizer
之后,参数都不会进行更新。但是区别在于,先进行requires_grad
属性置为False
的操作,再optimizer
初始化,不会将该层的参数放进优化器中更新,而先进行optimizer
初始化,再进行requires_grad
属性置为False
的操作,会将所有的参数放进优化器中,但不更新该指定层参数,只更新剩下的参数。对比看来,optimizer
中的参数量会相比前者会更大一点。
所以一般最好是将requires_grad
属性置为 False
这个操作放在optimizer
之前。
注意事项:
1、requires_grad
属性置为 False
或者默认时,不能在对该tensor
计算梯度,否则会进行报错。因为并没有追踪到任何计算历史,所以就不存在梯度的计算了。
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = torch.tensor([3.0, 4.0], requires_grad=False)
y1 = 2.0 * x + 2.0 * y
# y1.backward(torch.tensor([1.0, 1.0]))
# print(x.grad)
y.backward(torch.tensor([1.0, 1.0]))
"""
结果:
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
"""
2、整数型的tensor
并没有requires_grad
这个属性,只有浮点类型的tensor
可以计算梯度
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
d:\test.ipynb Cell 9 in <cell line: 2>()
1 a = torch.tensor([1,2])
----> 2 b = torch.tensor([3,4], requires_grad=True)
3 c = a+b
4 print(a.requires_grad)
RuntimeError: Only Tensors of floating point and complex dtype can require gradients
2、detach()
detach
方法就是返回了一个新的张量,该张量与当前计算图完全分离,且该张量的计算将不会记录到梯度当中。
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = torch.tensor([3.0, 4.0], requires_grad=True)
z = torch.tensor([3.0, 2.0], requires_grad=True)
x = x * 2
z1 = z.detach()
x1 = x.detach()
y1 = 2.0 * x1 + 2.0 * y + 3 * z1
y1.backward(torch.tensor([1.0, 1.0]))
print(x.requires_grad)
print(x.grad)
print(y.requires_grad)
print(y.grad)
print(z.requires_grad)
print(z.grad)
print(z1.requires_grad)
print(z1.grad)
"""
结果:
True
None
True
tensor([2., 2.])
True
None
False
None
C:\Users\26973\AppData\Local\Temp\ipykernel_37516\3652236761.py:12: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the gradient for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations.
print(x.grad)
"""
从上面实验可以看到,使用detach()
方法后,可以截断反向传播的梯度流,其作用有点类似于将requires_grad
属性置为False
的情况。
与requires_grad_()
将requires_grad
属性置为False
不同的是 detach()
函数会返回一个新的Tensor
对象b
, 并且新Tensor
是与当前的计算图分离的,其requires_grad
属性为False
,反向传播时不会计算其梯度。 b
与a
共享数据的存储空间,二者指向同一块内存。
而requires_grad_()
函数会改变Tensor
的requires_grad
属性并返回Tensor
,修改requires_grad
的操作是原位操作(in place
)。其默认参数为requires_grad=True
。requires_grad=True
时,自动求导会记录对Tensor
的操作,requires_grad_()
的主要用途是告诉自动求导开始记录对Tensor
的操作。
关于detach()返回的张量与原张量共享数据的存储空间,二者指向同一块内存可以由以下代码看出:
z1[0] = 5.0
print(z)
print(z1)
"""
结果:
tensor([5., 2.], requires_grad=True)
tensor([5., 2.])
"""
当我们修改z1
中的数据时,z
中的数据也随之修改。
**总结:**当我们在计算到某一步时,不需要在记录某一个张量的梯度时,就可以使用detach()
将其从追踪记录当中分离出来,这样一来该张量对应计算产生的梯度就不会被考虑了。比较常见的就是在GAN
生成模型中,当训练一次生成器后,再训练判别器时,需要对生成器生成的fake
进行损失计算,但是又不希望这部分损失对生成器进行权值的更新,这个时候需要冻结生成器那部分的权值,因此通常将生成器生成的fake
张量使用fetch()
进行阶段,再输入到判别器进行运算,这样最后使用loss.backward()
时仅会对判别器部分的梯度进行计算
import torch
import numpy as np
y = torch.tensor([3.0, 4.0], requires_grad=True)
z = torch.tensor([3.0, 2.0], requires_grad=True)
z1 = z.detach()
z2 = z1 + y
y1 = torch.sum(3 * z2)
y1.backward()
print(z2, z2.requires_grad)
print(y.grad)
print(z2.grad)
print(z1.grad)
"""
结果:
tensor([6., 6.], grad_fn=<AddBackward0>) True
tensor([3., 3.])
None
None
"""
这里假设z
是生成器生成的图片,z1
表示的是使用detch()
截断后的张量,y
表示的判别器内部的一些运算张量,z2
表示经过判别器后的结果,y1
假设是计算loss
的损失函数,我们可以看到,使用y1.backward()
后,不会对生成器生成的z
产生任何的梯度,在优化器优化时自然而然不会对其进行优化。而对于后面的步骤仍然会跟踪记录其所有的计算过程,比如对于z1
在判别器中进行运算仍然会记录其过程,并仅会对判别器内部的参数y
进行梯度计算,从而进行优化。
(注:这里z2.grad
之所以也为None
,是因为z2
节点不是叶子节点,它是由z1
和y
进行累加而来的,所以在z2
处不会有grad
属性,这部分可以看其给出的警告)
UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the gradient for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations.
3、with_no_grad
torch.no_grad()
是一个上下文管理器,用来禁止梯度的计算,通常用来网络推断中,它可以减少计算内存的使用量。
# 设置好requires_grad的值为True
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
y1 = x ** 2
with torch.no_grad(): # 这里使用了no_grad()包裹不需要被追踪的计算过程
y2 = y1 * 2
y3 = x ** 5
y4 = y1 + y2 + y3
print(y1, y1.requires_grad)
print(y2, y2.requires_grad)
print(y3, y3.requires_grad)
print(y4, y4.requires_grad)
y4.backward(torch.ones(y4.shape)) # y1.backward() y2.backward()
print(x.grad)
"""
结果:
tensor([1., 4.], grad_fn=<PowBackward0>) True
tensor([2., 8.]) False
tensor([ 1., 32.]) False
tensor([ 4., 44.], grad_fn=<AddBackward0>) True
tensor([2., 4.])
"""
可以看出,其实使用with torch.no_grad()
这个后,被其包裹的所有运算都是不计算梯度的,其效果与detach()
类似,所以使用下列代码的运行结果是一样的:
# 设置好requires_grad的值为True
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
y1 = x ** 2
# with torch.no_grad(): # 这里使用了no_grad()包裹不需要被追踪的计算过程
y2 = y1 * 2
y3 = x ** 5
y2 = y2.detach()
y3 = y3.detach()
y4 = y1 + y2 + y3
print(y1, y1.requires_grad)
print(y2, y2.requires_grad)
print(y3, y3.requires_grad)
print(y4, y4.requires_grad)
y4.backward(torch.ones(y4.shape)) # y1.backward() y2.backward()
print(x.grad)
"""
结果:
tensor([1., 4.], grad_fn=<PowBackward0>) True
tensor([2., 8.]) False
tensor([ 1., 32.]) False
tensor([ 4., 44.], grad_fn=<AddBackward0>) True
tensor([2., 4.])
"""
detach()
是考虑将单个张量从追踪记录当中脱离出来;
而torch.no_grad()
是一个warper
,可以将多个计算步骤的张量计算脱离出去,本质上没啥区别。
4、总结:
- requires_grad:在最开始创建Tensor时候可以设置的属性,用于表明是否追踪当前Tensor的计算操作。后面也可以通过requires_grad_()方法设置该参数,但是只有叶子节点才可以设置该参数。
- detach()方法:则是用于将某一个Tensor从计算图中分离出来。返回的是一个内存共享的Tensor,一变都变。
- torch.no_grad():对所有包裹的计算操作进行分离。但是torch.no_grad()将会使用更少的内存,因为从包裹的开始,就表明不需要计算梯度了,因此就不需要保存中间结果。
参考博客:
https://blog.csdn.net/qq_37344125/article/details/107426741(写的较好,建议大家也可以看下这篇博客)
https://blog.csdn.net/m0_56413813/article/details/121542920
https://blog.csdn.net/qq_39208832/article/details/117415229
https://blog.csdn.net/weixin_43145941/article/details/114757673
https://blog.csdn.net/qq_33188180/article/details/112369276
https://blog.csdn.net/sinat_33486980/article/details/117952661