0. autograd
0.1 autograd
(官方翻译来的这段话)pytorch中可以使用autograd实现神经网络反向传播过程的自动计算。 当我们使用autograd的时候,前向传播会定义一个计算图,图中的节点都是张量,图的边是函数,用于从输入张量产生输出张量。通过这个图的反向传播就可以轻松获得gradient。
0.2 计算图
下边的图画的有点意识流,能看懂就好,因为官方说法是“节点是张量,边是函数”。我觉得我画出来之后,考虑了边是函数,但是看着不像我传统意义上学的那种“图”。
import torch
x = torch.tensor([1.0], requires_grad=True)
print(x) # 输出:tensor([1.])
y = x + 1
print(y) # 输出:tensor([2.])
按照上边0.1的说法,那这个计算图应该是长这样:
什么是叶子节点什么是结果节点呢?顾名思义,除了最后计算图的输出结果是结果节点,剩下的都是叶子节点。你可以用.is_leaf
来检测。
import torch
x = torch.tensor([1.0], requires_grad=True)
k = torch.tensor([2.0])
y = k * x
print(k.is_leaf) # 输出:True
print(x.is_leaf) # 输出:True
print(y.is_leaf) # 输出:False
1. .requires_grad=True
上边代码中有个.requires_grad=True
,那这是什么呢?
默认情况下Tensor的requires_grad属性都是False,这样就不会追踪其gradient。
某个张量该属性设置为.requires_grad=True
时,构建计算图的时候,这个张量参与的操作会被 追踪(track) 并保存在结果节点的grad_fn
属性中。被追踪之后,反向传播的时候就会将计算链路上所有需要梯度的地方都把梯度求出来,求完之后存到该张量的.grad
属性中。
import torch
# 默认情况下requires_grad=False
x = torch.tensor([1.0])
print(x) # 输出:tensor([1.])
x += 1
print(x) # 输出:tensor([2.])
# 设置requires_grad=True
y = torch.tensor([2.0], requires_grad=True)
print(y) # 输出:tensor([2.], requires_grad=True)
z = y + 1.0
print(z) # 输出:tensor([3.], grad_fn=<AddBackward0>)
print(y) # 输出:tensor([2.], requires_grad=True)
print(y.grad_fn)# None
注意:
- 我在最后三行分别输出了y和z,可以看到在对y设定
.requires_grad=True
之后,不是将操作存在y的grad_fn
属性中,而是存在计算图的结果节点的grad_fn
属性中。
2. Tensor的 .grad_fn属性
上边提到当你设置.requires_grad=True
之后,该张量上的操作就会追踪。
但是只有非叶节点才有该属性,叶子节点会显示None。
3. .grad
import torch
y = torch.tensor([2.0], requires_grad=True)
print(y) # 输出:tensor([2.], requires_grad=True)
print(y.grad) # 输出:None
z = y + 1.0
print(z) # 输出:tensor([3.], grad_fn=<AddBackward0>)
print(y) # 输出:tensor([2.], requires_grad=True)
print(y.grad) # 输出:None
print(z.grad) # 输出:None
# 调用反向传播
z.backward()
print(y.grad) # 输出:tensor([1.])
print(z.grad) # 输出:None
注意:
- y的操作被追踪之后,其
y.grad
中是空的,只有在反向传播执行的时候才会计算每一环节所需要的梯度并将结果存储在y.grad
中 - 代码运行之后出现两个UserWarning,说不能访问非叶节点的
.grad
属性。这是因为我在上边特意写了两个print(z.grad)
。z是结果节点,无法输出其grad属性。
- 默认情况下非叶节点
.grad
是禁用的,强行打印会输出None - 想要保留结果节点的
.grad
有两种方法,其中一个是在构造计算图的操作之后加上.retain_grad()
,另一种是用.register_hook
我没自己看,感兴趣的自己去查
4. with torch.no_grad()
如果你不想某个操作被追踪,那你就需要使用with torch.no_grad():
。
被该语句包裹起来的代码就不会被追踪gradient。
import torch
# 设置requires_grad=True
y = torch.tensor([1.0], requires_grad=True)
print(y) # tensor([1.], requires_grad=True)
z = y + 1.0
print(z) # tensor([2.], grad_fn=<AddBackward0>)
print(z.grad_fn) # <AddBackward0 object at 0x0000017C3CFD21C0>
with torch.no_grad():
z = y - 2
print(z) # tensor([-1.])
print(z.grad_fn) # None
- 第二个输出中
grad_fn
显示是追踪了一个加法操作 - 第三个输出单独打印一下
grad_fn
,再次显示是追踪了加法操作 - 第四个输出是在设定了
with torch.no_grad():
之后,发现输出中没有grad_fn
属性了 - 第五个输出单独打印一下
grad_fn
确实是不存在,即with torch.no_grad():
包裹之下,那个减法操作没有被追踪并且该属性还被清空了。
with torch.no_grad()
有什么意义?
在使用pytorch时,并不是所有的操作都需要进行计算图的生成。而对于tensor的计算操作,默认是要进行计算图的构建的,在这种情况下,可以使用 with torch.no_grad():
,强制之后的内容不进行计算图构建。
5. .data
除了使用with torch.no_grad()
还可以使用某个张量的.data
。
import torch
# 设置requires_grad=True
y = torch.tensor([1.0], requires_grad=True)
print(y) # tensor([1.], requires_grad=True)
z = y + 1.0
print(z) # tensor([2.], grad_fn=<AddBackward0>)
print(z.grad_fn) # <AddBackward0 object at 0x0000017C3CFD21C0>
z.data = y.data - 2
print(z) # tensor([-1.])
print(z.grad_fn) # <AddBackward0 object at 0x00000226475F21C0>
相对于上边的with torch.no_grad()
,.data
也是不构建计算图,不对操作进行追踪,但是会保留上一步的追踪不清空。