1 . torch.autograd 为方便用户使用而专门开发的一套自动求导引擎,它能够根据输入和前向传播过程自动构建计算图,并执行反向传播。

1
2
3
4
5
6
7
8
9
import torch as t

# 在创建 tensor 的时候指定 requires_grad
a = t.randn(3, 4, requires_grad=True)
# 或者
a = t.randn(3, 4).requires_grad_()
# 或者
a = t.randn(3, 4)
a.requires_grad=True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
b = t.zeros(3, 4).requires_grad_()

c = a.add(b) # 也可以写成 c = a + b

d = c.sum()
d.backward() # 反向传播
print(d.requires_grad) # d 还是一个 requires_grad=True 的 tensor,对它的操作需要慎重

# 此处虽然没有指定 c 需要求导,但 c 依赖于 a,而 a 需要求导,
# 因此 c 的 requires_grad 属性会自动设置为 True
print(a.requires_grad, b.requires_grad, c.requires_grad) # (True, True, True)

# 判断是否为叶子节点
print(a.is_leaf, b.is_leaf, c.is_leaf) # (True, True, False)

# c.grad 是 None,因为 c 不是叶子节点,它的梯度是用来计算 a 的梯度,
# 所以虽然 c.requires_grad = True,但其梯度计算完之后就被释放了
print(c.grad is None) # True

2 . 验证 autograd 的计算结果与利用公式手动计算的结果一致。

$y=x^2 \cdot e^x$ 的导函数是:$\frac{d_{y}}{d_{x}}=2x \cdot e^x + x^2 \cdot e^x$,来看看 autograd 的计算结果与手动求导的计算结果是否有误差。

Input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch as t


def f(x):
y = x ** 2 * t.exp(x)
return y


def gradf(x):
'''手动求导函数'''
dx = 2 * x * t.exp(x) + x ** 2 * t.exp(x)
return dx


x = t.randn(3, 4, requires_grad=True)
y = f(x)

y.backward(t.ones(y.size()))
print(x.grad)
print(gradf(x))

Output:

3 . 每一个前向传播操作的函数都有与之对应的反向传播函数用来计算输入的各个 variable 的梯度,这些函数的函数名通常以 Backward 结尾。

Input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import torch as t

x = t.ones(1)
b = t.rand(1, requires_grad=True)
w = t.rand(1, requires_grad=True)
y = w * x # 等价于 y = w.mul(x)
z = y + b # 等价于 z = y.add(b)

print(x.requires_grad, b.requires_grad, w.requires_grad)

# grad_fn 可以查看这个 variable 的反向传播函数,
# z 是 add 函数的输出,所以它的反向传播函数是 AddBackward
print(z.grad_fn)

# next_functions 保存 grad_fn 的输入,是一个 tuple,tuple 的元素也是 Function
# 第一个是 y,它是乘法(mul)的输出,所以对应的反向传播函数 y.grad_fn 是 MulBackward
# 第二个是 b,它是叶子节点,由用户创建,grad_fn 为 None,但是需要求导,其梯度是累加的
print(z.grad_fn.next_functions)
print(z.grad_fn.next_functions[0][0] == y.grad_fn)

# 第一个是 w,叶子节点,需要求导,梯度是累加的
# 第二个是 x,叶子节点,不需要求导,所以为 None
print(y.grad_fn.next_functions)

# 叶子节点的 grad_fn 是 None
print(w.grad_fn, x.grad_fn)

Output:

计算 $w$ 的梯度的时候,需要用到 $x$ 的数值($\frac{\partial y}{\partial w}=x$),这些数值在前向过程中会保存成 buffer,在计算完梯度之后会自动清空,为了能够多次反向传播需要指定 retain_graph 来保留这些 buffer。

1
2
3
4
5
6
z.backward(retain_graph=True)
print(w.grad) # tensor([1.])

# 多次反向传播,梯度会累加,这也就是 w 中 AccumulateGrad 标识的含义
z.backward()
print(w.grad) # tensor([2.])

4 . PyTorch 使用的是动态图,它的计算图在每次前向传播时都是从头开始构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch as t


def abs(x):
if x.data[0] > 0:
return x
else:
return -x


x = t.ones(1, requires_grad=True)
y = abs(x)
y.backward()
print(x.grad) # tensor([1.])

x = -1 * t.ones(1) # 写成 x = -1 * t.ones(1, requires_grad=True) 时,x 不计算梯度
x = x.requires_grad_()
y = abs(x)
y.backward()
print(x.grad) # tensor([-1.])

变量的 requires_grad 属性默认 False,如果某一个节点 requires_grad 被设置为 True,那么所有依赖它的节点 requires_grad 都是 True

5 . 有时候可能不希望 autograd 对 tensor 求导,因为求导需要缓存许多中间结构,增加额外的内存/显存开销,同时降低运行速度,那么我们可以关闭自动求导,譬如在模型训练完毕转而进行测试推断的时候。

1
2
3
4
5
6
7
8
9
import torch as t

with t.no_grad(): # 也可以使用 t.set_grad_enable(False) 设置(无需 with),并且以下代码无缩进
x = t.ones(1)
w = t.rand(1, requires_grad=True)
y = x * w

# y 虽然依赖于 w 和 x,虽然 w.requires_grad=True,但是 y.requires_grad=False
print(x.requires_grad, w.requires_grad, y.requires_grad) # (False, True, False)

关闭自动求导后可以使用 t.set_grad_enable(True) 恢复设置。


笔记来源:《pytorch-book》