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的说法,那这个计算图应该是长这样:

如何将pytorch的AUC画图 pytorch autograd_深度学习

什么是叶子节点什么是结果节点呢?顾名思义,除了最后计算图的输出结果是结果节点,剩下的都是叶子节点。你可以用.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

如何将pytorch的AUC画图 pytorch autograd_如何将pytorch的AUC画图_02

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也是不构建计算图,不对操作进行追踪,但是会保留上一步的追踪不清空。