欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

PyTorch学习笔记2-自动微分

程序员文章站 2022-03-06 20:57:16
...

记得深度学习三巨头之一Yann LeCun曾经说过:“深度学习已死,可微编程永生”,就是说深度学习只是一种计算范式,而背后的可微分编程,具有更广阔的应用前景。在这里我们将探索PyTorch中的自动微分技术。

Tensor函数

PyTorch中的自动微分是基于Tensor的,以Tensor作为参数的函数,实际上是将Tensor中的每个元素应用该函数。只讲概念比较抽象,我们来看一个具体的例子。
我们定义xR5\boldsymbol{x} \in R^{5},定义如下函数:
y=f(x)=[f(x1)f(x2)f(x3)f(x4)f(x5)] y=f(\boldsymbol{x})=\begin{bmatrix} f(x_{1}) \\ f(x_{2}) \\ f(x_{3}) \\ f(x_{4}) \\ f(x_{5}) \end{bmatrix}
下面的程序我们以x2x^{2}为例:

import torch
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])
y = x ** 2
print(y)
# 输出结果为:tensor([ 1.,  4.,  9., 16., 25.])

在深度学习中,有三种最常见的情况:标量对向量的微分、向量对向量的微分、向量对矩阵的微分,其实还有张量对张量的微分,因为在实际中比较少见,所以这里就不再讨论了。

标量对向量的微分

通常神经网络的代价函数会是一个标量,对于多分类问题,神经网络的输出为一个向量,这时就需要求标量对向量的微分。我们定义如下函数:
y=i=15xi2 y = \sum_{i=1}^{5} x_{i}^{2}
我们知道求微分可以采用解析法、数值法和自动微分法,由于这个问题比较简单,我们可以直接应用解析法:
yxi=2xi,i=1,2,3,4,5 \frac{ \partial{y} }{ \partial{x_{i}} } = 2x_{i}, \quad i=1,2,3,4,5
我们定义标量对向量的微分为:
yx=[yx1yx2yx3yx4yx5]R1×n,xRn \frac{ \partial{y} }{ \partial{ \boldsymbol{x} } } = \begin{bmatrix} \frac{ \partial{y} }{ \partial{x_{1}} } & \frac{ \partial{y} }{ \partial{x_{2}} } & \frac{ \partial{y} }{ \partial{x_{3}} } & \frac{ \partial{y} }{ \partial{x_{4}} } & \frac{ \partial{y} }{ \partial{x_{5}} } \end{bmatrix} \in R^{1 \times n}, \quad \boldsymbol{x} \in R^{n}
我们可以通过PyTorch中的自动微分来完成这一任务:

x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], requires_grad=True, dtype=torch.float32)
y = torch.sum(x ** 2)
print(y.item()) # 打印计算值
y.backward() # 利用自动微分计算微分
print('shape:{0}; {1}'.format(x.grad.size(), x.grad)) # 打印y对x的微分值
# 打印结果:shape:torch.Size([5]); tensor([ 2.,  4.,  6.,  8., 10.])

第1行:

  • 第1行:我们在定义Tensor时,如果加入requires_grad,表明我们需要求对其的微分;
  • 第2行:计算函数值,在实际应用中就是神经网络的前向传播过程;
  • 第4行:调用自动微分,就是实际应用中的神经网络的反向传播过程;
    我们可以看到打印的结果与我们用解析法求出的内容一致。

向量对向量微分

实际上向量对向量微分叫做Jacobian矩阵,向量yRm\boldsymbol{y} \in R^{m}对向量xRn\boldsymbol{x} \in R^{n}的微分定义为:
yx=[y1x1y1x2...y1xny2x1y2x2...y2xn............ymx1ymx2...ymxn]Rm×n \frac{ \partial{\boldsymbol{y}} }{ \partial{\boldsymbol{x}} } = \begin{bmatrix} \frac{\partial{y_{1}}}{\partial{x_{1}}} & \frac{\partial{y_{1}}}{\partial{x_{2}}} & ... & \frac{\partial{y_{1}}}{\partial{x_{n}}} \\ \frac{\partial{y_{2}}}{\partial{x_{1}}} & \frac{\partial{y_{2}}}{\partial{x_{2}}} & ... & \frac{\partial{y_{2}}}{\partial{x_{n}}} \\ ... & ... & ... & ... \\ \frac{\partial{y_{m}}}{\partial{x_{1}}} & \frac{\partial{y_{m}}}{\partial{x_{2}}} & ... & \frac{\partial{y_{m}}}{\partial{x_{n}}} \\ \end{bmatrix} \in R^{m \times n}
我们假设有如下向量,例如是神经网络某层的输入向量:
zl=[1.02.03.04.05.0] \boldsymbol{z}^{l}=\begin{bmatrix} 1.0 \\ 2.0 \\ 3.0 \\ 4.0 \\ 5.0 \end{bmatrix}
经过该层的**函数(sigmoid函数)之后,得到本层的输出向量:
al=[z12z22z13z14z15] \boldsymbol{a}^{l}=\begin{bmatrix} z_{1}^{2} \\ z_{2}^{2} \\ z_{1}^{3} \\ z_{1}^{4} \\ z_{1}^{5} \\ \end{bmatrix}
因为本神经元的输入只会影响本神经元的输出,因此我们的al\boldsymbol{a}^{l}如上所示。根据解析法可得其Jacobian矩阵为:
alzl=[a1lz1l00000a2lz2l00000a3lz3l00000a3lz3l00000a3lz3l] \frac{ \partial{\boldsymbol{a}^{l}} }{ \partial{\boldsymbol{z}^{l}} }=\begin{bmatrix} \frac{ \partial{a}_{1}^{l} }{ \partial{z}_{1}^{l} } & 0 & 0 & 0 & 0 \\ 0 & \frac{ \partial{a}_{2}^{l} }{ \partial{z}_{2}^{l} } & 0 & 0 & 0 \\ 0 & 0 & \frac{ \partial{a}_{3}^{l} }{ \partial{z}_{3}^{l} } & 0 & 0 \\ 0 & 0 & 0 & \frac{ \partial{a}_{3}^{l} }{ \partial{z}_{3}^{l} } & 0 \\ 0 & 0 & 0 & 0 & \frac{ \partial{a}_{3}^{l} }{ \partial{z}_{3}^{l} } \end{bmatrix}
其实际上只有对角线上有值,因此在PyTorch中,为了更高效的处理问题,我们通常不需要原始的Jacobian矩阵,而是将其乘以一个与al\boldsymbol{a}^{l}同维且元素均为1的向量,得到的结果向量的元素为对角线上的元素。
alzl[1.01.01.01.01.0]=[a1lz1l00000a2lz2l00000a3lz3l00000a3lz3l00000a3lz3l][1.01.01.01.01.0]=[a1lz1la2lz2la2lz3la4lz4la5lz5l] \frac{ \partial{\boldsymbol{a}^{l}} }{ \partial{\boldsymbol{z}^{l}} } \cdot \begin{bmatrix} 1.0 \\ 1.0 \\ 1.0 \\ 1.0 \\ 1.0 \end{bmatrix}=\begin{bmatrix} \frac{ \partial{a}_{1}^{l} }{ \partial{z}_{1}^{l} } & 0 & 0 & 0 & 0 \\ 0 & \frac{ \partial{a}_{2}^{l} }{ \partial{z}_{2}^{l} } & 0 & 0 & 0 \\ 0 & 0 & \frac{ \partial{a}_{3}^{l} }{ \partial{z}_{3}^{l} } & 0 & 0 \\ 0 & 0 & 0 & \frac{ \partial{a}_{3}^{l} }{ \partial{z}_{3}^{l} } & 0 \\ 0 & 0 & 0 & 0 & \frac{ \partial{a}_{3}^{l} }{ \partial{z}_{3}^{l} } \end{bmatrix} \cdot \begin{bmatrix} 1.0 \\ 1.0 \\ 1.0 \\ 1.0 \\ 1.0 \end{bmatrix}= \cdot \begin{bmatrix} \frac{ \partial{a}_{1}^{l} }{ \partial{z}_{1}^{l} } \\ \frac{ \partial{a}_{2}^{l} }{ \partial{z}_{2}^{l} } \\ \frac{ \partial{a}_{2}^{l} }{ \partial{z}_{3}^{l} } \\ \frac{ \partial{a}_{4}^{l} }{ \partial{z}_{4}^{l} } \\ \frac{ \partial{a}_{5}^{l} }{ \partial{z}_{5}^{l} } \end{bmatrix}
在PyTorch中代码如下所示:

    x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], requires_grad=True)
    y = x**2
    y.backward(torch.ones_like(y))
    print('y_x:{0}'.format(x.grad))
    # 输出:y_x:tensor([ 2.,  4.,  6.,  8., 10.])

向量对矩阵求微分

在深度学习中,经常会出现上一层的输入向量zlRNl\boldsymbol{z}^{l} \in R^{N_{l}}对连接权值WlRNl×Nl1W^{l} \in R^{N_{l} \times N_{l-1}}的微分,公式为:
zl=Wlal1+bl \boldsymbol{z}^{l}=W^{l} \cdot \boldsymbol{a}^{l-1} + \boldsymbol{b}^{l}
其中al1RNl1\boldsymbol{a}^{l-1} \in R^{N_{l-1}}blRNl\boldsymbol{b}^{l} \in R^{N_{l}}
通过解析法,我们可以得到其微分公式为:
zlWl=[a1l1a2l1...al1l1a1l1a2l1...al1l1............a1l1a2l1...al1l1]=[(al1)T(al1)T...(al1)T]RNl×Nl1 \frac{ \partial{ \boldsymbol{z}^{l} } }{ \partial{W^{l}} } = \begin{bmatrix} a_{1}^{l-1} & a_{2}^{l-1} & ... & a_{l-1}^{l-1} \\ a_{1}^{l-1} & a_{2}^{l-1} & ... & a_{l-1}^{l-1} \\ ... & ... & ... & ... \\ a_{1}^{l-1} & a_{2}^{l-1} & ... & a_{l-1}^{l-1} \\ \end{bmatrix} = \begin{bmatrix} (\boldsymbol{a}^{l-1})^{T} \\ (\boldsymbol{a}^{l-1})^{T} \\ ... \\ (\boldsymbol{a}^{l-1})^{T} \end{bmatrix} \in R^{ N_{l} \times N_{l-1} }
PyTorch代码如下所示:

    a_l_1 = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], requires_grad=True)
    W_l = torch.tensor([
        [101.0, 102.0, 103.0, 104.0, 105.0],
        [201.0, 202.0, 203.0, 204.0, 205.0],
        [301.0, 302.0, 303.0, 304.0, 305.0]
    ], requires_grad=True)
    b_l = torch.tensor([1001.0, 1002.0, 1003.0], requires_grad=True)
    z_l = torch.matmul(W_l, a_l_1) + b_l
    z_l.backward(torch.ones_like(z_l))
    print('y_x:{0}'.format(W_l.grad))
    ''' 打印输出
    y_x:tensor([[1., 2., 3., 4., 5.],
        [1., 2., 3., 4., 5.],
        [1., 2., 3., 4., 5.]])
    '''

上述程序的打印结果与理论分析的结果一致,证明我们的求法是正确的。