参考:

PyTorch 学习笔记汇总

PyTorch中backward函数详解


1 动态图

PyTorch 采用的是动态图机制(Dynamic Computational Graph),而 Tensorflow 采用的是静态图机制。(Static Computational Graph)

动态图运算和搭建同时进行,也就是可以先计算前面的节点值,再根据这些值搭建后面的计算图。而静态图需要先搭建图,然后再输入数据进行运算。

PyTorch 动态图的优点是灵活,易调节,且使用简单方便。(类似 Python 库)但是其效率相对 Tensorflow 的静态图要低不少。

2.1 计算动态图

计算图是用来描述运算的有向无环图,有两个主要元素:节点 (Node) 和边 (Edge)。节点表示数据,如向量、矩阵、张量。边表示运算,如加减乘除卷积等。

一个简单的例子 ,该式子用计算图表示为:

img2

,其中

2.2 求导

对于上式,分别求 时,关于 的导数:

同理:

PyTorch中,torch.backward()会提供求导的功能,代码如下(注意,当计算图中的根不是标量时,即因变量为一个向量时,会构建出多个计算图对该向量中的每个元素分别进行求导,详见 pytorch中backward()函数详解 ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torch
m = torch.tensor([2.], requires_grad=True)
n = torch.tensor([3.], requires_grad=True)

a = torch.add(m, n)
b = torch.add(m, -n)
y = torch.mul(a, b)

y.backward()

print('m的梯度:', m.grad)
print('n的梯度', n.grad)
print('m is_leaf:', m.is_leaf)
print('n is_leaf:', n.is_leaf)
print('a is_leaf:', a.is_leaf)
print('b is_leaf:', b.is_leaf)
print('y is_leaf:', y.is_leaf)
1
2
3
4
5
6
7
m的梯度: tensor([4.])
n的梯度 tensor([-6.])
m is_leaf: True
n is_leaf: True
a is_leaf: False
b is_leaf: False
y is_leaf: False

并且由打印可知,可以看出 的 is_leaf 属性为 true,这是由于 是依赖 的,故为叶子节点。叶子节点的概念主要是为了节省内存,在计算图中的一轮反向传播结束之后,非叶子节点的梯度是会被释放的,所以直接访问非叶子节点的梯度是为空的。但是如果在反向传播结束之后仍然需要保留非叶子节点的梯度,可以对节点使用retain_grad()方法。

张量Tensor中,属性grad_fn记录了创建该张量时使用的方法(函数),故而可以在调用torch.backward()时自动求导。

2 线性回归

线性回归是分析一个变量y与另外一 (多) 个变量x之间的关系的方法。一般可以写成 线性回归的目的就是求解参数。其主要步骤:

① 确定模型(实际使用中,可能是一个隐函数):

② 选择损失函数,一般使用均方误差MSE(mean square error):。其中是预测值,是真实值;

③ 使用梯度下降法求解梯度,并根据学习率 更新参数,以此来最小化损失函数。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import torch
import matplotlib.pyplot as plt
torch.manual_seed(10)

lr = 0.05 # 学习率

# 创建训练数据
x = torch.rand(20, 1) * 10 # x data (tensor), shape=(20, 1)
# torch.randn(20, 1) 用于添加噪声
y = 2*x + (5 + torch.randn(20, 1)) # y data (tensor), shape=(20, 1)

# 构建线性回归参数
w = torch.randn((1), requires_grad=True) # 设置梯度求解为 true
b = torch.zeros((1), requires_grad=True) # 设置梯度求解为 true

# 迭代训练 100 次
for iteration in range(100):

# 前向传播,计算预测值
wx = torch.mul(w, x)
y_pred = torch.add(wx, b)

# 计算 MSE loss
loss = (0.5 * (y - y_pred) ** 2).mean()

# 反向传播
loss.backward()

# 更新参数
b.data.sub_(lr * b.grad)
w.data.sub_(lr * w.grad)

# 每次更新参数之后,都要清零张量的梯度
w.grad.zero_()
b.grad.zero_()

# 绘图,每隔 20 次重新绘制直线
if iteration % 5 == 0:

plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), y_pred.data.numpy(), 'r-', lw=5)
plt.text(2, 20, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.xlim(1.5, 10)
plt.ylim(8, 28)
plt.title("Iteration: {}\nw: {} b: {}".format(iteration, w.data.numpy(), b.data.numpy()))
plt.pause(0.5)

# 如果 MSE 小于 1,则停止训练
if loss.data.numpy() < 1:
break

img1