动手学深度学习笔记(一)

矩阵计算


1. 标量导数

一般的标量导数表示切线的斜率

2. 亚导数

将导数拓展到不可微的函数。在函数的不可导点,将导数(斜率)取为一个范围内的任意值。

如,对于函数:
y = ∣ x ∣ y=|x| y=x

其导数可以记为:
∂ ∣ x ∣ ∂ x = { 1 i f   x > 0 − 1 i f   x < 0 a i f   x = 0 , a ∈ [ − 1 , 1 ] \frac{\partial |x|}{\partial x}= \begin{cases} 1 & if\ x>0 \\ -1 & if\ x<0 \\ a & if\ x=0,a\in [-1,1] \end{cases} xx=11aif x>0if x<0if x=0,a[1,1]

函数 max ⁡ ( x , 0 ) \max{(x,0)} max(x,0) 的导数可以记为:
∂ max ⁡ ( x , 0 ) ∂ x = { 1 i f   x > 0 0 i f   x < 0 a i f   x = 0 , a ∈ [ − 1 , 1 ] \frac{\partial \max{(x,0)}}{\partial x}= \begin{cases} 1 & if\ x>0 \\ 0 & if\ x<0 \\ a & if\ x=0,a\in [-1,1] \end{cases} xmax(x,0)=10aif x>0if x<0if x=0,a[1,1]

3. 梯度

导数在向量的拓展。

\quad 标量 x x x 向量 x \boldsymbol{x} x
标量 y y y ∂ y ∂ x ( 标 量 ) \frac{\partial y}{\partial x}(标量) xy ∂ y ∂ x ( 行 向 量 ) \frac{\partial y}{\partial \boldsymbol{x}}(行向量) xy
向量 y \boldsymbol{y} y ∂ y ∂ x ( 列 向 量 ) \frac{\partial \boldsymbol{y}}{\partial x}(列向量) xy ∂ y ∂ x ( 矩 阵 ) \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}}(矩阵) xy
  1. y y y 是标量, x = [ x 1 x 2 ⋮ x n ] \boldsymbol{x}=\left[\begin{matrix} x_1 \\ x_2 \\\vdots \\x_n \end{matrix}\right] x=x1x2xn 是向量,梯度按照如下方法计算,标量相对于一个列向量的导数是一个行向量(分子布局法),所以下面的计算都要转置。
    ∂ y ∂ x = [ ∂ y ∂ x 1 , ∂ y ∂ x 2 , ⋯   , ∂ y ∂ x n ] \frac{\partial y}{\partial \boldsymbol{x}} = \left[\begin{matrix} \dfrac{\partial y}{\partial x_1}, & \dfrac{\partial y}{\partial x_2}, & \cdots, & \dfrac{\partial y}{\partial x_n} \end{matrix}\right] xy=[x1y,x2y,,xny]

    如,对于 y = x 1 2 + 2 x 2 2 y=x_1^2 + 2x_2^2 y=x12+2x22,梯度 ∂ y ∂ x = [ 2 x 1 , 4 x 2 ] \frac{\partial y}{\partial{\boldsymbol{x}}}=\left[\begin{matrix}2x_1,4x_2\end{matrix}\right] xy=[2x1,4x2]

    一些梯度的计算:

    y s u m ( x ) sum(\boldsymbol{x}) sum(x) ∥ x ∥ 2 \|x\|^2 x2 ⟨ u , v ⟩ \langle \boldsymbol{u},\boldsymbol{v}\rangle u,v
    ∂ y ∂ x \dfrac{\partial y}{\partial{\boldsymbol{x}}} xy 1 T \boldsymbol{1}^T 1T 2 x T 2\boldsymbol{x}^T 2xT u T ∂ v ∂ x + v T ∂ u ∂ x \boldsymbol{u}^T\frac{\partial \boldsymbol{v}}{\partial \boldsymbol{x}}+\boldsymbol{v}^T\frac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}} uTxv+vTxu
  2. y = [ y 1 y 2 ⋮ y m ] \boldsymbol{y}=\left[\begin{matrix} y_1 \\ y_2 \\\vdots \\y_m \end{matrix}\right] y=y1y2ym 是矩阵, x x x 是一个标量,计算梯度的方式如下,得到的是一个列向量(分子布局法)。
    ∂ y ∂ x = [ ∂ y 1 ∂ x ∂ y 2 ∂ x ⋮ ∂ y m ∂ x ] \frac{\partial \boldsymbol{y}}{\partial x}= \left[\begin{matrix} \dfrac{\partial y_1}{\partial x} \\ \dfrac{\partial y_2}{\partial x} \\ \vdots \\ \dfrac{\partial y_m}{\partial x} \end{matrix}\right] xy=xy1xy2xym

  3. 对于向量 x = [ x 1 x 2 ⋮ x n ] \boldsymbol{x}=\left[\begin{matrix} x_1 \\ x_2 \\\vdots \\x_n \end{matrix}\right] x=x1x2xn 与向量 y = [ y 1 y 2 ⋮ y m ] \boldsymbol{y}=\left[\begin{matrix} y_1 \\ y_2 \\\vdots \\y_m \end{matrix}\right] y=y1y2ym,其求梯度的方式如下,最终得到一个矩阵。
    ∂ y ∂ x = [ ∂ y 1 ∂ x ∂ y 2 ∂ x ⋮ ∂ y m ∂ 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 ] \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}}= \left[\begin{matrix} \dfrac{\partial y_1}{\partial \boldsymbol{x}} \\ \dfrac{\partial y_2}{\partial \boldsymbol{x}} \\ \vdots \\ \dfrac{\partial y_m}{\partial \boldsymbol{x}} \end{matrix}\right]= \left[\begin{matrix} \dfrac{\partial y_1}{\partial x_1}, & \dfrac{\partial y_1}{\partial x_2}, & \cdots, & \dfrac{\partial y_1}{\partial x_n} \\ \dfrac{\partial y_2}{\partial x_1}, & \dfrac{\partial y_2}{\partial x_2}, & \cdots, & \dfrac{\partial y_2}{\partial x_n} \\ &\vdots && \\ \dfrac{\partial y_m}{\partial x_1}, & \dfrac{\partial y_m}{\partial x_2}, & \cdots, & \dfrac{\partial y_m}{\partial x_n} \end{matrix}\right] xy=xy1xy2xym=x1y1,x1y2,x1ym,x2y1,x2y2,x2ym,,,,xny1xny2xnym

y \boldsymbol{y} y x \boldsymbol{x} x A x \boldsymbol{Ax} Ax x T A \boldsymbol{x}^T\boldsymbol{A} xTA a u a\boldsymbol{u} au A u \boldsymbol{Au} Au
∂ y ∂ x \dfrac{\partial \boldsymbol{y}}{\partial{\boldsymbol{x}}} xy I \boldsymbol{I} I A \boldsymbol{A} A A T \boldsymbol{A}^T AT a ∂ u ∂ x a\dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}} axu A ∂ u ∂ x \boldsymbol{A}\dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}} Axu
  • a a a a \boldsymbol{a} a A \boldsymbol{A} A 中都不包含 x \boldsymbol{x} x

相关链接:

  1. 矩阵求导的本质与分子布局、分母布局的本质(矩阵求导——本质篇)
  2. 矩阵求导公式的数学推导(矩阵求导——基础篇)
  3. 矩阵求导公式的数学推导(矩阵求导——进阶篇)

4. 向量链式法则

  • 标量的链式法则:

    y = f ( u ) , u = g ( x ) y=f(u),u=g(x) y=f(u),u=g(x),则 y y y 相对于 x x x 的导数可以表示为:
    ∂ y ∂ x = ∂ y ∂ u ∂ u ∂ x \frac{\partial y}{\partial x}=\frac{\partial y}{\partial u}\frac{\partial u}{\partial x} xy=uyxu

  • 向量的链式求导法则:

    类型 求导
    y y y 是标量, u u u 是标量, x \boldsymbol{x} x 是向量 ∂ y ∂ x ( 1 , n ) = ∂ y ∂ u ( 1 , ) ∂ u ∂ x ( 1 , n ) \underset{(1,n)}{\dfrac{\partial y}{\partial \boldsymbol{x}}}= \underset{(1,)}{\dfrac{\partial y}{\partial u}} \underset{(1,n)}{\dfrac{\partial u}{\partial \boldsymbol{x}}} (1,n)xy=(1,)uy(1,n)xu
    y y y 是标量, u \boldsymbol{u} u 是向量, x \boldsymbol{x} x 是向量 ∂ y ∂ x ( 1 , n ) = ∂ y ∂ u ( 1 , k ) ∂ u ∂ x ( k , n ) \underset{(1,n)}{\dfrac{\partial y}{\partial \boldsymbol{x}}}= \underset{(1,k)}{\dfrac{\partial y}{\partial \boldsymbol{u}}} \underset{(k,n)}{\dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}}} (1,n)xy=(1,k)uy(k,n)xu
    y \boldsymbol{y} y 是向量, u \boldsymbol{u} u 是向量, x \boldsymbol{x} x 是向量 ∂ y ∂ x ( m , n ) = ∂ y ∂ u ( 1 , k ) ∂ u ∂ x ( k , n ) \underset{(m,n)}{\dfrac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}}}= \underset{(1,k)}{\dfrac{\partial \boldsymbol{y}}{\partial \boldsymbol{u}}} \underset{(k,n)}{\dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}}} (m,n)xy=(1,k)uy(k,n)xu

例子:

  • 对于一个表达式 z = ( ⟨ x , w ⟩ − y ) 2 z=(\langle \boldsymbol{x},\boldsymbol{w}\rangle - y)^2 z=(x,wy)2,其中 x , w ∈ R n , y ∈ R \boldsymbol{x},\boldsymbol{w}\in \mathbb{R}^n,y\in\mathbb{R} x,wRn,yR,计算 ∂ z ∂ w \dfrac{\partial z}{\partial \boldsymbol{w}} wz 的过程如下:

    1. 首先将其中的变量求导,记 a = ⟨ x , w ⟩ , b = a − y , z = b 2 a=\langle \boldsymbol{x},\boldsymbol{w}\rangle,\quad b=a-y,\quad z=b^2 a=x,w,b=ay,z=b2

    2. 使用链式求导对每个中间变量求导
      ∂ z ∂ w = ∂ z ∂ b ∂ b ∂ a ∂ a ∂ w = ∂ b 2 ∂ b ∂ ( a − y ) ∂ a ∂ ⟨ x , w ⟩ ∂ w = 2 b ⋅ 1 ⋅ x T = 2 ( ⟨ x , w ⟩ − y ) x T \begin{aligned} \dfrac{\partial z}{\partial \boldsymbol{w}} & = \dfrac{\partial z}{\partial b}\dfrac{\partial b}{\partial a}\dfrac{\partial a}{\partial \boldsymbol{w}} \\ & = \dfrac{\partial b^2}{\partial b}\dfrac{\partial (a-y)}{\partial a}\dfrac{\partial \langle \boldsymbol{x},\boldsymbol{w}\rangle}{\partial \boldsymbol{w}} \\ & = 2b \cdot 1 \cdot \boldsymbol{x}^T \\ & = 2(\langle \boldsymbol{x},\boldsymbol{w}\rangle -y)\boldsymbol{x}^T \end{aligned} wz=bzabwa=bb2a(ay)wx,w=2b1xT=2(x,wy)xT

5. 自动求导

自动求导是计算一个函数在指定值上的导数,即对于一个 y = f ( x ) y=f(x) y=f(x) 的表达式,对于某一个 x = c x=c x=c f ′ ( c ) f'(c) f(c) 的值是什么。

自动求导是根据计算图进行详细计算的,也就是给定一个表达式:

  1. 首先将该表达式分解成一个一个的操作子
  2. 使用操作子就可以将整个表达式表示为一个无环图

如,对于上面例子的表达式, z = ( ⟨ x , w ⟩ − y ) 2 z=(\langle \boldsymbol{x},\boldsymbol{w}\rangle - y)^2 z=(x,wy)2 a , b , z a,b,z a,b,z 就算单个的计算子,它们可以构成如下的计算图,其中每个圈表示一个表达式或输入。
动手学深度学习笔记(一)_第1张图片

构造出计算图后,根据链式求导法则:
∂ y ∂ x = ∂ y ∂ u n ∂ u n ∂ u n − 1 ⋯ ∂ u 2 ∂ u 1 ∂ u 1 ∂ x \dfrac{\partial y}{\partial x}=\dfrac{\partial y}{\partial u_n}\dfrac{\partial u_n}{\partial u_{n-1}}\cdots\dfrac{\partial u_2}{\partial u_{1}}\dfrac{\partial u_1}{\partial x} xy=unyun1unu1u2xu1

可以通过如下两种方式实现自动求导:

  1. 正向累积。先计算 ∂ u 1 ∂ x \dfrac{\partial u_1}{\partial x} xu1 ,再计算 ∂ u 2 ∂ u 1 \dfrac{\partial u_2}{\partial u_1} u1u2,之后依次向前计算
    ∂ y ∂ x = ∂ y ∂ u n ( ∂ u n ∂ u n − 1 ( ⋯ ( ∂ u 2 ∂ u 1 ∂ u 1 ∂ x ) ) ) \dfrac{\partial y}{\partial x}= \dfrac{\partial y}{\partial u_n} \left(\dfrac{\partial u_n}{\partial u_{n-1}} \left(\cdots \left(\dfrac{\partial u_2}{\partial u_{1}} \dfrac{\partial u_1}{\partial x} \right)\right)\right) xy=uny(un1un((u1u2xu1)))

    正向累计可以根据计算图,从下向上依次计算每个结点相对于下一个参数的导数。

  2. 反向累积(反向传播)。先计算 ∂ y ∂ u n \dfrac{\partial y}{\partial u_n} uny ,再计算 ∂ u n ∂ u n − 1 \dfrac{\partial u_n}{\partial u_{n-1}} un1un,之后依次向后计算。
    ∂ y ∂ x = ( ( ( ∂ y ∂ u n ∂ u n ∂ u n − 1 ) ⋯   ) ∂ u 2 ∂ u 1 ) ∂ u 1 ∂ x \dfrac{\partial y}{\partial x}=\left(\left( \left(\dfrac{\partial y}{\partial u_n} \dfrac{\partial u_n}{\partial u_{n-1}}\right) \cdots\right) \dfrac{\partial u_2}{\partial u_{1}}\right) \dfrac{\partial u_1}{\partial x} xy=(((unyun1un))u1u2)xu1

    反向累积需要根据计算图从上向下依次求导,求导过程中需要将正向计算中的结果拿来构成每个结点的求导结果并保存为中间结果。如下所示:
    动手学深度学习笔记(一)_第2张图片

  • 反向累积中,计算复杂度为 O ( n ) O(n) O(n),内存复杂度为 O ( n ) O(n) O(n),因为要存储正向的所有中间结果,所以更加耗费资源。
  • 正向累积中,计算一个变量的时间复杂度为 O ( n ) O(n) O(n)(即从下向上扫一遍),对于多个变量的计算会使计算复杂度非常大。内存复杂度为 O ( 1 ) O(1) O(1),不需要保存中间的结果。

6. PyTorch 自动求导

  • 若要计算 y = 2 x T x y=2\mathbf{x}^T\mathbf{x} y=2xTx 关于行向量 x \mathbf{x} x 的导数,可以通过如下步骤。

    1. 构造 x \mathbf{x} x 向量,并指定在 x.grad 中保存计算出来的梯度

      # 1. 先创建 x ,再指定 x 可以求导
      >>> x = torch.arange(4.0)
      >>> x
      tensor([0., 1., 2., 3.])
      >>> x.requires_grad_(True)
      tensor([0., 1., 2., 3.], requires_grad=True)
      >>> x.grad     #默认为 None
      
      # 2. 在创建 x 同时指定 x 可以求导
      >>> x = torch.arange(4.0, requires_grad=True)
      >>> x
      tensor([0., 1., 2., 3.], requires_grad=True)
      >>> x.grad     #默认为 None
      
    2. 利用 x \mathbf{x} x 计算 y y y

      >>> y = 2 * torch.dot(x,x)           #计算内积
      >>> y
      tensor(28., grad_fn=<MulBackward0>)  #grad_fn 保存 y 的运算信息,表明 y 是由 x 构建的
      >>> y.grad_fn
      <MulBackward0 object at 0x00000211287EEFD0>
      
    3. 调用反向传播函数 y.backward() 自动计算 y y y 关于 x \mathbf{x} x 每个分量的梯度

      >>> y.backward()
      >>> x.grad
      tensor([ 0.,  4.,  8., 12.])
      
      >>> x.grad == 4 * x
      tensor([True, True, True, True])
      

    其计算的本质就是构造出 y = f ( x ) y=f(x) y=f(x) 的表达式,上式为 y = 2 ( x 1 2 + x 2 2 + x 3 2 + x 4 2 ) y=2(x_1^2 + x_2^2 + x_3^2 + x_4^2) y=2(x12+x22+x32+x42) ,之后将向量 x \boldsymbol{x} x 中的每个值都带入计算,得到 x = [ x 1 , x 2 , ⋯   , x n ] \boldsymbol{x}=\left[\begin{matrix}x_1,x_2,\cdots,x_n\end{matrix}\right] x=[x1,x2,,xn] 中每个位置的变量对应的求导公式 y ′ = f ′ ( x ) = [ f ′ ( x 1 ) , f ′ ( x 2 ) , ⋯   , f ′ ( x n ) ] = [ 4 x 1 2 , 4 x 2 2 , 4 x 3 2 , 4 x 4 2 ] y'=f'(\boldsymbol{x})=\left[\begin{matrix}f'(x_1),f'(x_2),\cdots,f'(x_n)\end{matrix}\right]=\left[\begin{matrix} 4x_1^2,4x_2^2,4x_3^2,4x_4^2\end{matrix}\right] y=f(x)=[f(x1),f(x2),,f(xn)]=[4x12,4x22,4x32,4x42],再将值带入对应位置。

  • 下面展示了计算 x x x 的累加和的梯度

    在默认情况下,PyTorch 会 累加梯度,使用 x.grad.zero_() 可以清除之前的值

    >>> x.grad.zero_()
    tensor([0., 0., 0., 0.])
    >>> x.grad
    tensor([0., 0., 0., 0.])
    
    >>> y = x.sum()
    >>> y.backward()
    >>> x.grad
    tensor([1., 1., 1., 1.])
    
  • 使用 y.backward() 计算梯度时必须保证 y 是一个标量,在数学上是不允许对非标量求导的,如下所示,一般情况下我们首先对 y 求和,求和之后是一个标量,再进行求导。

    >>> x.grad.zero_()
    >>> y = x *x
    >>> y.backward()
    Traceback (most recent call last):
      File "", line 1, in <module>
      File "D:\Applications\Anaconda\lib\site-packages\torch\tensor.py", line 221, in backward
        torch.autograd.backward(self, gradient, retain_graph, create_graph)
      File "D:\Applications\Anaconda\lib\site-packages\torch\autograd\__init__.py", line 126, in backward
        grad_tensors_ = _make_grads(tensors, grad_tensors_)
      File "D:\Applications\Anaconda\lib\site-packages\torch\autograd\__init__.py", line 50, in _make_grads
        raise RuntimeError("grad can be implicitly created only for scalar outputs")
    RuntimeError: grad can be implicitly created only for scalar outputs
    
    >>> y.sum().backward()
    >>> x.grad
    tensor([0., 2., 4., 6.])
    
  • 将某些计算移动到记录的计算图之外,即通过 y.detach() 命令使得中间变量 y y y 成为一个常数,对于包含 y y y 的表达式,当计算梯度时候会将 y y y 视为常数

    #1. 对 y 执行 detach()
    >>> x.grad.zero_()
    tensor([0., 0., 0., 0.])
    >>> y = x * x
    >>> u = y.detach()
    >>> z = u * x
    >>> z.sum().backward()
    
    >>> x.grad == u
    tensor([True, True, True, True])
    >>> x.grad
    tensor([0., 1., 4., 9.])
    
    #2. 不对 y 执行 detach()
    >>> x.grad.zero_()
    tensor([0., 0., 0., 0.])
    >>> y = x * x
    >>> z = y * x
    >>> z.sum().backward()
    >>> x.grad
    tensor([ 0.,  3., 12., 27.])
    
    #3. 因为 y 是 x 的函数,所以直接对 y 进行梯度求解没有影响,但是不能对 u 求导
    >>> x.grad.zero_()
    tensor([0., 0., 0., 0.])
    >>> y.sum().backward()
    >>> x.grad
    tensor([0., 2., 4., 6.])
    
    >>> u.sum().backward()
    RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
    

你可能感兴趣的:(动手学深度学习,深度学习)