【Dive into Deep Learning / 动手学深度学习】第二章 - 第五节:自动微分

139 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

前言

Hello!

非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~  

自我介绍 ଘ(੭ˊᵕˋ)੭

昵称:海轰

标签:程序猿|C++选手|学生

简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金,有幸在竞赛中拿过一些国奖、省奖...已保研。

学习经验:扎实基础 + 多做笔记 + 多敲代码 + 多思考 + 学好英语!  

唯有努力💪  

知其然 知其所以然!

本文只记录感兴趣的部分

2.5. 自动微分

2.5.1. 标量变量的反向传播

求函数f(x)=2xTxf(x) = 2 x^Tx对列向量xx求导后的梯度

  1. 初始化向量xx 在这里插入图片描述
  2. 设置x可计算梯度 在这里插入图片描述

requires_grad: 如果需要为张量计算梯度,则为True,否则为False

  1. 计算y=2xTxy = 2x^Tx 在这里插入图片描述

dot用来计算两个向量之间内积(对应元素相乘,再累加,最终得到一个标量)

grad_fn: grad_fn用来记录变量是怎么来的,方便计算梯度(比如这里便于使用反向传播计算梯度)

  1. 使用反向传播法计算梯度

在这里插入图片描述

理论上f(x)=2xTxf(x) = 2 x^Txxx求导后结果为4x4x 初始时 x=[0,1,2,3]x = [0,1,2,3] 利用xxx=[0,1,2,3]x = [0,1,2,3]时的梯度为4x=[0,4,8,12]4x = [0, 4, 8, 12] 计算得出的结果和理论值一样

  1. 判断计算得出的结果和理论值是否一样

在这里插入图片描述


现在设y=x1+x2+x3+x4y = x_1 + x_2 + x_3 + x_4,其中x=[x1,x2,x3,x4]x = [x_1, x_2, x_3, x_4],求xx的梯度

import torch

x = torch.arange(4.0, requires_grad = True)
y = x.sum() # 相当于 x_1 + x_2 + ... + x_n = y
y.backward()
x.grad

# tensor([1., 1., 1., 1.])

解释:对x1x_1求偏导,结果为1,同理,对x2x3x4x_2、x_3、x_4求偏导,结果都为1

注意:在默认情况下,PyTorch会累积梯度

比如多次运行y.backward(),会有

  1. 第一次 在这里插入图片描述
  2. 第二次 在这里插入图片描述
  3. 第三次 在这里插入图片描述

解决办法:使用grad.zero_()清除梯度

在这里插入图片描述

这样多次运行,结果都是[1,1,1,1]

2.5.2. 非标量变量的反向传播

y=xxy = x * xxx求偏导(xx为一个向量)

说明:

  • y=xxy= x * x: 对应元素相乘,此时结果yy是一个非标量

在这里插入图片描述

x = [0, 1, 2, 3] ===> x * x = [0, 1 * 1 , 2 * 2 , 3 * 3] = [0, 1, 4, 9]

为了求xx的偏导,则需要先对yy进行累加求和,再使用反向传播

在这里插入图片描述

利用 y = x * xxx求导后,结果为2x2 x x = [0,1,2,3] ==> 2x = [0,2,4,6]

注:这里还是有点不懂 ??? 以下为书中原话:

在这里插入图片描述

2.5.3. 分离计算

在这里插入图片描述


若不丢弃计算图中如何计算y的任何信息,也就是不使用detach()函数

y=xxu=yz=uxy = x * x\\ u = y\\ z = u * x

等效于

z=xxxz = x * x * x

此时z关于x求导,梯度为(此时z为非标量,需要先求和)

在这里插入图片描述

得到梯度为[0,3,12,27] ,与理论值3x23 x^2 吻合


使用detach()函数后

在这里插入图片描述

这里可以理解为:

  • u = y.detach()使得u仅仅是一个向量[0,1,4, 9]
  • z = u * x, u仅仅作为一个常向量,对x求导,得到结果为u

总结:

  • 不使用detach(), 那么y是由x计算出来的,也就是可以用x的算式进行替换
  • 使用detach(), u仅仅表示一个向量,仅有y的值,其它信息都无

2.5.4. Python控制流的梯度计算

在这里插入图片描述

大概意思:

  • f(a)函数是一个分段函数,其中每一段都是线性的(直线)
  • 输入a的值不同,对应不同的线段(需要经过while循环、if-else等)
  • 但是最后依然是可以算出来x的梯度,不受循环等的影响
  • 比如经过多次循环、if-else, 得到的a与d的对应关系为 d = 1231 * a,梯度依然可以算出来为1231
  • 也就是说经过多次Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度

练习

在这里插入图片描述

import numpy as np
import torch
from d2l import torch as d2l
x = torch.arange(-4.0, 4.0, 0.1, requires_grad = True)
x

在这里插入图片描述

y = torch.sin(x)
y.sum().backward()
x.grad == torch.cos(x)

在这里插入图片描述

y2 = x.grad
y2

在这里插入图片描述

y2_np = y2.numpy()
y2_np

在这里插入图片描述

x_np = x.detach().numpy()
x_np

在这里插入图片描述

def f(x):
    return np.sin(x)
y1_np = f(x_np)
y1_np

在这里插入图片描述

d2l.plot(x_np, [y1_np, y2_np], 'x', 'f(x)', legend=['sin(x)', 'cos(x)'])

在这里插入图片描述

结语

学习资料:http://zh.d2l.ai/

文章仅作为个人学习笔记记录,记录从0到1的一个过程

希望对您有一点点帮助,如有错误欢迎小伙伴指正

在这里插入图片描述