尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【深度学习】之Pytorch自动微分

【深度学习】之Pytorch自动微分 在深度学习的优化算法中求导是必不可少的关键步骤。虽然基础的微积分求导规则并不复杂但当面对包含数百万参数、结构错综复杂的现代神经网络时手工计算梯度不仅极其痛苦而且非常容易出错。幸运的是现代深度学习框架如 PyTorch为我们提供了自动微分Automatic Differentiation功能。系统会在后台默默构建一张计算图Computational Graph记录数据是如何通过一系列操作产生最终输出的。用这张图系统就能通过反向传播算法自动计算出所有参数的偏导数。一.引例:求导例子演示函数 对列向量求导理解 PyTorch autograd 自动微分机制import torch # 创建初始向量 x x torch.arange(4.0) x输出tensor([0., 1., 2., 3.])在计算 y 关于的梯度前需要为张量开启梯度计算分配存储梯度的内存空间。PyTorch 不会每次求导都重新分配内存训练时会成千上万次更新同一个参数反复新建内存极易造成内存耗尽。 标量函数对向量的梯度返回向量形状与原向量完全一致。x.requires_grad_(True) # 等价写法 x torch.arange(4.0, requires_gradTrue) print(x.grad) # 尚未反向传播梯度默认 None输出None计算函数torch.dot()对两个一维向量做点积运算输出结果为标量。y torch.dot(x, x) y输出tensor(14., grad_fnDotBackward0)grad_fn记录运算的反向传播函数代表已经构建好计算图。调用.backward()执行反向传播自动计算 y 对每一个分量的梯度y.backward() x.grad输出tensor([0., 2., 4., 6.])数学推导梯度。验证梯度结果是否正确x.grad 2 * x输出tensor([True, True, True, True])重点PyTorch 梯度会累积累加执行新的求导运算前必须使用.grad.zero_()原地清零旧梯度否则新梯度会叠加在历史梯度之上得到错误结果。# 清除历史梯度下划线代表原地(in‑place)操作 x.grad.zero_() # 新函数 y sum(x) y x.sum() y.backward() x.grad输出tensor([1., 1., 1., 1.])数学解释每个分量偏导数梯度全部为 1。二.非标量变量的反向传播import torch x torch.arange(4.0).reshape(2, 2) # 告诉 PyTorch我们需要计算并存储 x 的梯度 x.requires_grad_(True) # 此时 x.grad 的默认值是 None print(x.grad) # 计算 x 和 x 的点积得到一个标量 y y torch.dot(x.flatten(), x.flatten())(注为了严格匹配向量点积这里将矩阵展平)我们只需要调用.backward()函数PyTorch 就会自动计算 y关于 x 每个分量的梯度y.backward() print(x.grad)根据微积分知识函数,。你可以通过x.grad 2 * x来验证计算的准确性。⚠️ 避坑指南梯度累积在 PyTorch 中默认情况下梯度是累积的。如果要计算一个新函数的梯度必须先清除之前记录的梯度值x.grad.zero_() # 清空之前的梯度 y x.sum() y.backward()这边x.grad是tensor([[1., 1.], [1., 1.]])原理对每个所以梯度张量全部为 1。注意在PyTorch中执行完一次.backward()之后就会把整个计算图直接释放销毁节省内存。 所以回头再次重复运行同一个单元格y.backward()此时y还在但是求导需要的中间计算图已经没了不能再 backward 一遍所以会直接抛 RuntimeError。或者非标量的可以写成以下代码# 对非标量调用backward需要传入一个gradient参数该参数指定微分函数关于self的梯度。 # 本例只想求偏导数的和所以传递一个1的梯度是合适的 x.grad.zero_() y x * x # 等价于y.backward(torch.ones(len(x))) y.sum().backward() x.grad三.分离计算x.grad.zero_() y x * x u y.detach() # u数值等于y但从计算图剪断把u当成常数 z u * x x, y, u, z数学u 是y.detach()值等于但是不再知道 u 是由 x 算出来的⚠️重点不是 因为 u 被 detach求导的时候把 u 当作常数。z.sum().backward() x.grad u求导演算u视为常数所以全部相等输出[True, True, True, True]。如果没有 detach不分离会怎么样不做uy.detach()直接z y * x此时梯度结果就会是3*x*x和上面完全不一样。但是y 本身还保留完整计算图u 只是 y 分离出来的副本y 本身没有被改动计算图信息还在 y 身上。 所以后面还可以对 y 做反向传播x.grad.zero_() y.sum().backward() x.grad 2 * x四.python控制流的梯度计算import torch def f(a): b a * 2 # while循环b的L2范数小于1000就不断乘2循环次数由a的大小决定 while b.norm() 1000: b b * 2 # if分支看b总和正负走不同分支 if b.sum() 0: c b else: c 100 * b return c # a是标量开启梯度追踪 a torch.randn(size(), requires_gradTrue) d f(a) d.backward() # 反向传播计算d对a的梯度存到a.grad print(a.grad d / a)最后输出是True
返回列表