《动手学深度学习》—— 第2章—2.5自动微分
2.5.自动微分
2.5.1.一个简单的例子
追踪梯度意味着计算和记录张量在各个操作中的梯度信息,以便在反向传播时使用这些信息来更新模型参数
原地操作:指直接修改张量本身的内容,而不返回新的张量。在 PyTorch 中,原地操作通常会在方法名称后加上一个下划线 (_) 来标识,表示该操作会直接改变原始数据,而不生成新的张量。
一个标量函数关于向量 𝑥 的梯度是向量,并且与 𝑥 具有相同的形状
x.requires_grad_(True) # 在原地修改张量,节省内存,无法恢复前状态
y = torch.dot(x, x) # 向量点积
y.backward()
x.grad # 在执行反向传播后存储x的所有梯度
2.5.2.非标量变量的反向传播
对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self(指的就是你调用 .backward() 的那个张量本身(例如 y))的梯度
-
为什么非标量 .backward() 需要 gradient 参数?
-
答:输出 y 是向量(比如长度是 n),因为你有多个输出值,各自都能对输入求偏导PyTorch 必须知道你希望对这些输出怎么组合成一个标量,然后再求梯度
-
这个“组合方式”就是你传给 .backward(gradient) 的 gradient 参数,它代表:∂L/∂y 这里的 𝐿 是你最终想优化的标量目标
-
# 等价于 y.backward(torch.ones(len(x))) y.sum().backward()传入全 1 向量(或 .sum())就是把所有输出加起来,相当于“求偏导数的和”
-
2.5.3.分离计算
x.grad.zero_() # 清空梯度
y = x * x
u = y.detach() # 保留 y 的数值结果,只是切断了梯度追踪,让 u 成为一个不带梯度信息的张量, 只是拿到了 y 的数值,被当作常量处理
z = u * x
z.sum().backward()
x.grad == u
x.grad.zero_() # 清空梯度
y.sum().backward()
x.grad == 2 * x
-
为什么计算二阶导数比一阶导数的开销要更大?
-
二阶导数本质上是“对梯度再求导”,所以在第一次 .backward() 时,必须保留计算图(retain_graph=True),否则二次求导时找不到原来的计算依赖
-
-
在运行反向传播函数之后,立即再次运行它,会发生什么?
-
默认情况下,PyTorch 在 .backward() 完成后会释放计算图来节省内存。所以如果你不保留图,第二次运行 .backward() 会报错
-
如果你确实需要多次 .backward(),必须在第一次调用时加:y.backward(retain_graph=True),这样 PyTorch 会保留计算图,允许后续的反向传播
-
-
在控制流的例子中,我们计算d关于a的导数,如果将变量a更改为随机向量或矩阵,会发生什么?
-
同一个函数,输入不同,梯度表达式也会不同(因为执行路径变了)
-
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)