PyTorch学习笔记2-自动微分

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

Tensor函数

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

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 = 1 5 x i 2 y = \sum_{i=1}^{5} x_{i}^{2} y=i=15xi2
我们知道求微分可以采用解析法、数值法和自动微分法,由于这个问题比较简单,我们可以直接应用解析法:
∂ y ∂ x i = 2 x i , i = 1 , 2 , 3 , 4 , 5 \frac{ \partial{y} }{ \partial{x_{i}} } = 2x_{i}, \quad i=1,2,3,4,5 xiy=2xi,i=1,2,3,4,5
我们定义标量对向量的微分为:
∂ y ∂ x = [ ∂ y ∂ x 1 ∂ y ∂ x 2 ∂ y ∂ x 3 ∂ y ∂ x 4 ∂ y ∂ x 5 ] ∈ R 1 × n , x ∈ R n \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} xy=[x1yx2yx3yx4yx5y]R1×n,xRn
我们可以通过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矩阵,向量 y ∈ R m \boldsymbol{y} \in R^{m} yRm对向量 x ∈ R n \boldsymbol{x} \in R^{n} xRn的微分定义为:
∂ y ∂ x = [ ∂ y 1 ∂ x 1 ∂ y 1 ∂ x 2 . . . ∂ y 1 ∂ x n ∂ y 2 ∂ x 1 ∂ y 2 ∂ x 2 . . . ∂ y 2 ∂ x n . . . . . . . . . . . . ∂ y m ∂ x 1 ∂ y m ∂ x 2 . . . ∂ y m ∂ x n ] ∈ R m × 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} xy=x1y1x1y2...x1ymx2y1x2y2...x2ym............xny1xny2...xnymRm×n
我们假设有如下向量,例如是神经网络某层的输入向量:
z l = [ 1.0 2.0 3.0 4.0 5.0 ] \boldsymbol{z}^{l}=\begin{bmatrix} 1.0 \\ 2.0 \\ 3.0 \\ 4.0 \\ 5.0 \end{bmatrix} zl=1.02.03.04.05.0
经过该层的激活函数(sigmoid函数)之后,得到本层的输出向量:
a l = [ z 1 2 z 2 2 z 1 3 z 1 4 z 1 5 ] \boldsymbol{a}^{l}=\begin{bmatrix} z_{1}^{2} \\ z_{2}^{2} \\ z_{1}^{3} \\ z_{1}^{4} \\ z_{1}^{5} \\ \end{bmatrix} al=z12z22z13z14z15
因为本神经元的输入只会影响本神经元的输出,因此我们的 a l \boldsymbol{a}^{l} al如上所示。根据解析法可得其Jacobian矩阵为:
∂ a l ∂ z l = [ ∂ a 1 l ∂ z 1 l 0 0 0 0 0 ∂ a 2 l ∂ z 2 l 0 0 0 0 0 ∂ a 3 l ∂ z 3 l 0 0 0 0 0 ∂ a 3 l ∂ z 3 l 0 0 0 0 0 ∂ a 3 l ∂ z 3 l ] \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} zlal=z1la1l00000z2la2l00000z3la3l00000z3la3l00000z3la3l
其实际上只有对角线上有值,因此在PyTorch中,为了更高效的处理问题,我们通常不需要原始的Jacobian矩阵,而是将其乘以一个与 a l \boldsymbol{a}^{l} al同维且元素均为1的向量,得到的结果向量的元素为对角线上的元素。
∂ a l ∂ z l ⋅ [ 1.0 1.0 1.0 1.0 1.0 ] = [ ∂ a 1 l ∂ z 1 l 0 0 0 0 0 ∂ a 2 l ∂ z 2 l 0 0 0 0 0 ∂ a 3 l ∂ z 3 l 0 0 0 0 0 ∂ a 3 l ∂ z 3 l 0 0 0 0 0 ∂ a 3 l ∂ z 3 l ] ⋅ [ 1.0 1.0 1.0 1.0 1.0 ] = ⋅ [ ∂ a 1 l ∂ z 1 l ∂ a 2 l ∂ z 2 l ∂ a 2 l ∂ z 3 l ∂ a 4 l ∂ z 4 l ∂ a 5 l ∂ z 5 l ] \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} zlal1.01.01.01.01.0=z1la1l00000z2la2l00000z3la3l00000z3la3l00000z3la3l1.01.01.01.01.0=z1la1lz2la2lz3la2lz4la4lz5la5l
在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.])

向量对矩阵求微分

在深度学习中,经常会出现上一层的输入向量 z l ∈ R N l \boldsymbol{z}^{l} \in R^{N_{l}} zlRNl对连接权值 W l ∈ R N l × N l − 1 W^{l} \in R^{N_{l} \times N_{l-1}} WlRNl×Nl1的微分,公式为:
z l = W l ⋅ a l − 1 + b l \boldsymbol{z}^{l}=W^{l} \cdot \boldsymbol{a}^{l-1} + \boldsymbol{b}^{l} zl=Wlal1+bl
其中 a l − 1 ∈ R N l − 1 \boldsymbol{a}^{l-1} \in R^{N_{l-1}} al1RNl1 b l ∈ R N l \boldsymbol{b}^{l} \in R^{N_{l}} blRNl
通过解析法,我们可以得到其微分公式为:
∂ z l ∂ W l = [ 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 ] = [ ( a l − 1 ) T ( a l − 1 ) T . . . ( a l − 1 ) T ] ∈ R N l × N l − 1 \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} } Wlzl=a1l1a1l1...a1l1a2l1a2l1...a2l1............al1l1al1l1...al1l1=(al1)T(al1)T...(al1)TRNl×Nl1
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.]])
    '''

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值