【李沐深度学习笔记】自动求导实现

课程地址和说明

自动求导实现p2
本系列文章是我学习李沐老师深度学习系列课程的学习笔记,可能会对李沐老师上课没讲到的进行补充。

自动求导

# 创建变量
import torch
x = torch.arange(4, dtype=torch.float32) #只有浮点数才能求导
# 计算y关于x的梯度之前,需要一个地方存储梯度
x.requires_grad_(True)
print("x为:",x)
# 计算y
y = 2 * torch.dot(x,x) # y=2倍的x向量的内积,即y=2x^2
print("y为:",y)
# 通过调用反向传播函数来自动计算y关于x每个分量的梯度
y.backward() # 求导,y'=4x
print("x的梯度为:",x.grad)
print("y的导数是否为y'=4x:",x.grad == 4 * x)

运行结果:

x为: tensor([0., 1., 2., 3.], requires_grad=True)
y为: tensor(28., grad_fn=<MulBackward0>)
x的梯度为: tensor([ 0., 4., 8., 12.])
y的导数是否为y’=4x: tensor([True, True, True, True])

默认情况下PyTorch会把梯度累积起来

所以我们在算下一步的时候需要讲梯度清零

  • 举一个例子,之前的章节说过,当 y = s u m ( x → ) = ∑ i = 1 m x i = x 1 + x 2 + ⋯ + x m y=sum(\overrightarrow x)=\sum\limits_{i=1}^{m} x_{i}=x_{1}+x_{2}+\dots +x_{m} y=sum(x )=i=1mxi=x1+x2++xm a a a为任意常数, u = g ( x → ) u=g(\overrightarrow x) u=g(x )时,有:
    ∂ y ∂ x → = [ ∂ f ( x → ) ∂ x 1 ∂ f ( x → ) ∂ x 2 ⋮ ∂ f ( x → ) ∂ x m ] m × 1 = [ 1 1 ⋮ 1 ] m × 1 \frac{\partial {y}}{\partial\overrightarrow x}=\begin{bmatrix} \frac{\partial {f(\overrightarrow x)}}{\partial{x_{1}}}\\ \frac{\partial {f(\overrightarrow x)}}{\partial{x_{2}}}\\ \vdots \\ \frac{\partial {f(\overrightarrow x)}}{\partial{x_{m}}} \end{bmatrix}_{m\times 1}=\begin{bmatrix} 1\\ 1\\ \vdots \\ 1 \end{bmatrix}_{m\times 1} x y= x1f(x )x2f(x )xmf(x ) m×1= 111 m×1最终结果是单位向量。
    用PyTorch复现为:
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
# 重新定义,y=x1+x2+...+xn
y = sum(x)
# 向后传播求导
y.backward()
# 打印求导结果
print(x.grad)

运行结果:
tensor([1., 1., 1., 1.])

深度学习中,我们的目的不是计算微分矩阵,而是批量中每个样本单独计算的偏导数之和。

# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
# 重新定义,y=x1+x2+...+xn
y = x * x # 变为了标量
print("y=",y)
# 向后传播求导
y.sum().backward() # 变量需要先让y求和再向后传播求导
print("y.sum()=",y.sum())
# 打印求导结果
print(x.grad)

运行结果:
y= tensor([0., 1., 4., 9.], grad_fn=<MulBackward0>)
y.sum()= tensor(14., grad_fn=<SumBackward0>)
tensor([0., 2., 4., 6.])

将某些计算移动到记录的计算图之外

# 清空累积的梯度
x.grad.zero_()
# 定义y = x * x
y = x* x
# 定义u为常数,不再是y = x * x这样的函数,其常数值为x * x
u = y.detach()
print(u)
# z = u * x,相当于常数u乘以向量x
z = u * x
# z = [u1,u2,u3,u4](常数向量)×[x1,x2,x3,x4]^T(变量向量)
# z = u1x1+u2x2+u3x3+u4x4
# 所以z'x1=u1,以此类推,其偏导数的向量最终就和u相等
z.sum().backward()
print(x.grad  == u)

运行结果:
tensor([0., 1., 4., 9.])
tensor([True, True, True, True])

# 清空累积的梯度
x.grad.zero_()
# 现在y为 x^2(x1^2+x2^2+x3^2+x4^2),y'=2x(y'x1=2x1,以此类推)
# 对y求导
y.sum().backward()
# 证明y的导数是否是2x
print(x.grad ==2 *x)

运行结果:
tensor([True, True, True, True])

【提问】请用通俗的语言告诉我在PyTorch中, y.sum().backward()与y.backward()这两种向后求导方式有什么区别?
【chatGPT回答】在PyTorch中,y.sum().backward()和y.backward()是两种不同的向后求导方式,它们在计算梯度的方式上有一些区别。

  1. y.sum().backward()
  • 这个方式首先对y进行了求和操作,将一个张量中的所有元素相加得到一个标量(一个单一的数值)。
  • 然后对这个标量进行反向传播,计算相对于y的梯度。
  • 这种方式通常用于处理损失函数的梯度计算,因为损失函数通常返回一个标量,你希望计算相对于模型参数的梯度。
  1. y.backward():
  • 这个方式直接对张量y进行反向传播,计算相对于y的梯度。
  • 这种方式通常用于计算中间变量的梯度,而不是损失函数的梯度。
    总的来说,主要区别在于
  • y.sum().backward()用于计算损失函数相对于模型参数的梯度,因为损失函数通常返回一个标量值。
  • y.backward()用于计算中间变量相对于其他变量的梯度,通常在计算模型的中间层时使用。
    选择哪种方式取决于你的具体需求和计算图的结构。通常,当你需要计算损失函数相对于模型参数的梯度时,你会使用y.sum().backward(),而在其他情况下,你会使用y.backward()来计算相对于中间变量的梯度。

即使构建函数的计算图需要通过Python控制流(例如,条件循环或任意函数调用),我们仍然可以计算得到的变量的梯度

# 清空累积的梯度
x.grad.zero_()
# 设定一个函数
def f(a):b = a * 2# 如果b向量的模长(L2范式)小于1000,就不断给它乘以2while b.norm() < 1000:b = b * 2if b.sum() > 0:c = belse:c = 100 * breturn c
# 取一个随机数标量作为其值
a = torch.randn(size=(),requires_grad=True)
print("a为:",a)
d = f(a)
print("f(a)为:",d)
d.backward()
# 此函数相当于f(a)=ba,b是系数,如果是向量也是如此
# 所以其导数(或向量对应梯度向量)一定是斜率b
print(a.grad == d/a)

运行结果:
a为: tensor(1.6346, requires_grad=True)
f(a)为: tensor(1673.8451, grad_fn=)
tensor(True)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/116373.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《算法竞赛·快冲300题》每日一题:“矩阵”

《算法竞赛快冲300题》将于2024年出版&#xff0c;是《算法竞赛》的辅助练习册。 所有题目放在自建的OJ New Online Judge。 用C/C、Java、Python三种语言给出代码&#xff0c;以中低档题为主&#xff0c;适合入门、进阶。 文章目录 题目描述题解C代码Java代码Python代码 “ 质…

【刷题】2023年第十四届蓝桥杯大赛软件类省赛C/C++大学A组真题

蓝桥杯2023年第十四届省赛真题-平方差 - C语言网 (dotcpp.com) 初步想法&#xff0c;x y2 − z2&#xff08;yz)(y-z) 即xa*b&#xff0c;ayz&#xff0c;by-z 2yab 即ab是2的倍数就好了。 即x存在两个因数之和为偶数就能满足条件。 但时间是&#xff08;r-l&#xff09;*x&am…

建构居住安全生态,鹿客科技2023秋季发布会圆满举办

9月20日&#xff0c;以「Lockin Opening」为主题的2023鹿客秋季发布会在上海隆重举办&#xff0c;面向居住安全领域鹿客带来了最新的高端旗舰智能锁新品、多眸OS1.0、Lockin Care服务以及全联接OPENING计划。此外&#xff0c;现场还邀请了国家机构、合作伙伴、技术专家等业界同…

MyBatis基础之SqlSession

SqlSession 线程安全问题 当你翻看 SqlSession 的源码时&#xff0c;你会发现它只是一个接口。我们通过 MyBatis 操作数据库&#xff0c;实际上就是通过 SqlSession 获取一个 JDBC 链接&#xff0c;然后操作数据库。 SqlSession 接口有 3 个实现类&#xff1a; #实现类1Defa…

Go的error接口

从本书的开始&#xff0c;我们就已经创建和使用过神秘的预定义error类型&#xff0c;而且没有解释它究竟是什么。实际上它就是interface类型&#xff0c;这个类型有一个返回错误信息的单一方法&#xff1a; type error interface { Error() string } 创建一个error最简单的方…

C++ Primer 第5章 语句

C Primer 第5章 语句 5.1 简单语句一、空语句二、别漏写分号&#xff0c;也别多写分号三、复合语句&#xff08;块&#xff09; 5.2 语句作用域5.3 条件语句5.3.1 if语句一、使用if else语句二、嵌套if语句三、注意使用花括号四、悬垂else五、使用花括号控制执行路径 5.3.2 swi…

知识图谱:信息抽取简易流程

目录 一、标注训练数据 二、训练数据模型 三、实现NER 一、标注训练数据 使用工具:Brat ## BRAT安装 0、安装条件 (1)运行于Linux系统 (2)brat(v1.3p1)仅支持python2版本运行使用,否则会报错 File "standalone.py", line 257except SystemExit, sts:^Syn…

qt 遍历当前windows下可用盘符

foreach(QFileInfo my_info, QDir::drives()){qDebug()<< my_info.absolutePath();}输出如下&#xff1a;

c语言 static

1、静态局部变量在程序加载时初始化&#xff0c;静态局部变量的初始值写入到了data段&#xff1a; 如下代码test_symbol.c int f() {static int x 0;return x; }int g() {static int x 9;return x; }使用命令gcc -c test_symbol.c -o test_symbol 编译 使用命令 readelf -a …

LeetCode 1194.锦标赛优胜者

数据准备 Create table If Not Exists Players (player_id int, group_id int); Create table If Not Exists Matches (match_id int, first_player int, second_player int, first_score int, second_score int); Truncate table Players; insert into Players (player_id, g…

Java基础-环境篇:JDK安装与环境变量配置jdk8/11/17(保姆式详解)

目录 一、Java简介 Java版本 名词解释JDK、JRE JDK版本选择 二、JDK的下载 下载方式1&#xff1a; &#xff08;1&#xff09;在Developers页面中间的技术分类部分&#xff0c;选择Java&#xff0c;单击进入&#xff0c;如图所示&#xff1a; &#xff08;2&#xff09;…

【lesson10】fork创建进程的现象解答

文章目录 fork现象fork问题 fork现象 我们先来看一段代码。 大家觉得这段代码的printf会打印几次&#xff1f; 结果&#xff1a; 我们可以清楚的看到&#xff0c;第二个printf打印了2次。 我们再来看一段不可思议的代码&#xff1a; 运行结果&#xff1a; 我们可以看到这r…