【深度学习理论】一文搞透 pytorch 中的 tensor、autograd、反向传播和计算图
动态计算图
https://www.cnblogs.com/picassooo/p/13748618.html
https://www.cnblogs.com/picassooo/p/13818952.html
# Pytorch中loss.backward()和torch.autograd.grad的使用和区别
总结:
loss.backward()和torch.autograd.grad区别
相同点:
这两种梯度方法都会在反向传播求导的时候释放计算图,如果需要再次做自动求导,因为计算图已经不再了,就会报错 。如果要在反向传播的时候保留计算图 ,可以设置retain_graph=True 。
不同点
- 累加
loss.backward()会将求导结果累加在grad上。这也是为什么我们在训练每个batch的最开始,需要对梯度清零的原因。
torch.autograd.grad不会将求导结果累加在grad上。 - 叶子节点梯度
torch.autograd.grad可以获取非叶子节点的梯度。
loss.backward()后,非叶子节点的导数计算完成之后就会被清空。
计算图保留
python
import torch
x=torch.tensor(3,dtype=torch.float,requires_grad=True)
y=x*x
z=2*y
z.backward(retain_graph=True)
print(x.grad) #12
z.backward()
print(x.grad) #24
不保留计算图会报错。
同一张计算图计算两次,相当于两次grad相加,以上代码效果等同于
python
import torch
x=torch.tensor(3,dtype=torch.float,requires_grad=True)
y=x*x
z=2*y
z.backward()
print(x.grad)
x.grad = x.grad *2
print(x.grad)
https://www.cnblogs.com/luckyscarlett/p/10555632.html
python
import torch
x = torch.randn((1,4),dtype=torch.float32,requires_grad=True)
y = x ** 2
z = y * 4
print(x)
print(y)
print(z)
loss1 = z.mean()
loss2 = z.sum()
print(loss1,loss2)
loss1.backward() # 这个代码执行正常,但是执行完中间变量都free了,所以下一个出现了问题
print(loss1,loss2)
loss2.backward() # 这时会引发错误
计算节点数值保存了,但是计算图x-y-z结构被释放了,而计算loss2的backward仍然试图利用x-y-z的结构,因此会报错。
使用
python
loss1.backward(retain_graph=True)# 这里参数表明保留backward后的中间参数。
即可
叶子节点
一般地,由用户自己创建的张量为叶子节点。另外,神经网络中各层的权值weight和偏差bias对应的张量也为叶子节点。由叶子节点得到的中间张量为非叶子节点。
Pytorch中的张量有一个is_leaf
的属性。若一个张量为叶子节点,则其is_leaf属性就为True,若这个张量为非叶子节点,则其is_leaf属性就为False。
在反向传播中,叶子节点可以理解为不依赖其它张量的张量
requires_grad_()
只有在是叶子节点且require_grad为True的时候,反向计算才会保留计算图。
requires_grad_作用是将Tensor的require_grad属性变为True
应用的情况为手动生成的Tensor,但忘记将该属性变为True
python
import torch
x=torch.tensor(3.) # 此处须为torch.float类型,如果不是,则需要进行如下的转换
print(x.requires_grad) #False
# 如果在创建张量x时,没有将requires_grad参数设置为True,则可以通过下式进行设定
x.requires_grad_(True)
grad_fn
如果一个张量是通过一些操作从其他张量创建的,那么这个张量将有一个 grad_fn
属性指向执行的函数。但是,对于叶子张量(即那些直接创建的张量,而不是通过计算得到的),它们默认没有 grad_fn
保留非叶子节点梯度
在非叶子节点之后,加上 "非叶子节点.retain_grad() 保留梯度
python
```python
import torch
x=torch.tensor(3,dtype=torch.float,requires_grad=True)
y=x*x
z=2*y
z.backward()
print(z.grad) # none
修改后代码:
python
import torch
x=torch.tensor(3,dtype=torch.float,requires_grad=True)
y=x*x
z=2*y
z.retain_grad()
z.backward()
print(z.grad) # 1
计算图可视化
python
from torch import nn
import torch
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.w = nn.Parameter(torch.randn(2,1))
self.b = nn.Parameter(torch.zeros(1,1))
def forward(self, x):
y = x@self.w + self.b
return y
net = Net()
net = Net()
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter('../data/tensorboard')
writer.add_graph(net,input_to_model = torch.rand(10,2))
writer.close()
detach()和clone()的区别
python
import torch
# 创建一个张量,并启用梯度追踪
x = torch.randn(3, requires_grad=True)
# 做一些计算
y = 2 * x # y 参与反向传播
# z = y.clone() # 创建 y 的副本,z 会有与 y 相同的计算图
z = y.detach() # 创建 y 的副本,z 会有与 y 相同的计算图
# 计算一个目标函数
loss = sum(5 * x + 10 * z)
# 执行反向传播
loss.backward()
# 打印梯度
print(f"x.grad: {x.grad}")
clone()
保存计算图,detach()
不保存计算图
clone().detach()和detach()
区别在于是否共享内存空间。修改一个另外的变量也会修改,但是是否有计算图有区别。
python
import torch
# 创建一个启用了梯度追踪的张量
sub_buffer = torch.ones(3, requires_grad=True)
# 使用 detach() 断开计算图
sub_buffer_detached = sub_buffer.detach()
sub_buffer_clone = sub_buffer.clone().detach()
# 检查原始张量和 detatched 张量的计算图
print(f"sub_buffer requires_grad: {sub_buffer.requires_grad}") # True
print(f"sub_buffer_detached requires_grad: {sub_buffer_detached.requires_grad}") # False
print(f"sub_buffer.grad_fn: {sub_buffer.grad_fn}") # 显示计算图信息
print(f"sub_buffer_detached.grad_fn: {sub_buffer_detached.grad_fn}") # 显示计算图信息
# 修改 detatched 张量的值并查看是否影响原始张量
sub_buffer_detached += 1
print(f"sub_buffer: {sub_buffer}")
print(f"sub_buffer_detached: {sub_buffer_detached}")
# 修改副本的值并查看是否影响原始张量
sub_buffer_clone += 1
print(f"sub_buffer: {sub_buffer}")
print(f"sub_buffer_clone: {sub_buffer_clone}")
print(f"sub_buffer id: {id(sub_buffer)}")
print(f"sub_buffer_clone id: {id(sub_buffer_clone)}") # 不同
print(f"sub_buffer_detached id: {id(sub_buffer_detached)}") # 相同
#检查是否断开计算图
print(f"sub_buffer requires_grad: {sub_buffer.requires_grad}")
print(f"sub_buffer_detached requires_grad: {sub_buffer_detached.requires_grad}")
结果
python
sub_buffer requires_grad: True
sub_buffer_detached requires_grad: False
sub_buffer.grad_fn: None
sub_buffer_detached.grad_fn: None
sub_buffer: tensor([2., 2., 2.], requires_grad=True)
sub_buffer_detached: tensor([2., 2., 2.])
sub_buffer: tensor([2., 2., 2.], requires_grad=True)
sub_buffer_clone: tensor([2., 2., 2.])
sub_buffer id: 140125728706704
sub_buffer_clone id: 140125728663328
sub_buffer_detached id: 140125728706784
sub_buffer requires_grad: True
sub_buffer_detached requires_grad: False
torch.autograd.backward
python
import torch
# 创建两个张量,并启用梯度追踪
x = torch.randn(3, requires_grad=True)
y = torch.randn(3, requires_grad=True)
# 计算一个输出张量
out = x * y # out 参与计算图
# 使用 detach() 从计算图中分离出一个张量
out_detached = out.detach() # 这里使用 detach,断开 out 的计算图
out_detached.requires_grad_()
output = out_detached * 66
# 定义梯度张量,通常用于自定义梯度传播
grad_out = torch.ones_like(out_detached) # grad_out 是给定的梯度
# 使用 backward 进行反向传播
torch.autograd.backward(tensors=output, grad_tensors=grad_out)
# 打印梯度
print(f"x.grad: {x.grad}") # 由于 out_detached 不计算梯度,x 不会被更新
print(f"y.grad: {y.grad}") # y 也不会被更新
print(f"out.grad: {out_detached.grad}") # y 也不会被更新
print(f"out.leaf: {out_detached.is_leaf}") # y 也不会被更新
输出
python
x.grad: None
y.grad: None
out.grad: tensor([66., 66., 66.])
out.leaf: True
torch.autograd.backward
反向传播,所得结果直接体现在叶子节点的梯度值上
再比如
python
import torch
# 创建两个张量,并启用梯度追踪
x = torch.randn(3, requires_grad=True)
y = torch.randn(3, requires_grad=True)
# 计算一个输出张量
out = x * y # out 参与计算图
# 使用 detach() 从计算图中分离出一个张量
out_detached = out.detach() # 这里使用 detach,断开 out 的计算图
out_detached.requires_grad_()
output = out_detached * 66
output2 = out_detached * 34
# 定义梯度张量,通常用于自定义梯度传播
grad_out = torch.ones_like(out_detached) # grad_out 是给定的梯度
# 使用 backward 进行反向传播
torch.autograd.backward(tensors=(output,output2),grad_tensors=(grad_out,grad_out))
# 打印梯度
print(f"x.grad: {x.grad}") # 由于 out_detached 不计算梯度,x 不会被更新
print(f"y.grad: {y.grad}") # y 也不会被更新
print(f"out.grad: {out_detached.grad}") # y 也不会被更新
print(f"out.leaf: {out_detached.is_leaf}") # y 也不会被更新
结果为
python
x.grad: None
y.grad: None
out.grad: tensor([100., 100., 100.])
out.leaf: True