PyTorch学习|动态计算图与线性回归


参考

PyTorch 学习笔记汇总
pytorch中backward()函数详解


PyTorch学习|动态计算图与线性回归

动态图

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

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

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

计算动态图

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

一个简单的例子$y=(m+n)(m-n)$ ,该式子用计算图表示为:

求导

对于上式,分别求y在$m=2,n=3$时,关于$m,n$导数:

同理:

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

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)
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

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

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

线性回归

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

① 确定模型(实际使用中,可能是一个隐函数): $y=wx+b$;

② 选择损失函数,一般使用均方误差MSE(mean square error):$\frac{1}{m} \sum_{i=1}^m\left(y_i-\hat{y}_i\right)^2$;

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

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


文章作者: Wei Hu
文章链接: https://heyhw.cn
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wei Hu !
  目录