PyTorch深度学习框架60天进阶学习计划 - 第4天:自动微分系统


学习目标

理解requires_grad属性传播机制
掌握计算图构建原理
熟练使用detach()no_grad()控制梯度计算
实现自定义函数的自动微分
对比动态图与静态图模式差异
计算雅可比矩阵与二阶导数


目录

计算图与梯度传播原理
requires_grad属性工作机制
梯度计算控制技术
自定义函数微分实现
动态图与静态图对比
高阶导数计算实践
代码运行流程图
完整代码实现
梯度计算调试技巧


1. 计算图与梯度传播原理

PyTorch使用动态计算图记录张量操作,反向传播时自动计算梯度。

计算图结构示例

x = torch.tensor(2.0, requires_grad=True)
y = x  2
z = y + torch.exp(x)

节点关系:

x (requires_grad=True)
│
└─ y = x^2 (grad_fn=)
   │
   └─ z = y + e^x (grad_fn=)

梯度传播过程

z.backward()
print(x.grad)  # 输出:2x + e^x = 22 + e^2 ≈ 4 + 7.389 = 11.389

2. requires_grad属性工作机制

属性传播规则表:

操作类型 输出张量requires_grad 说明
新建张量 手动设置 torch.tensor(..., requires_grad=True)
数学运算 自动继承 任一输入为True则输出为True
in-place操作 保持原值 会破坏计算图,需谨慎使用
索引操作 自动继承 切片后的张量保留梯度信息

代码验证

a = torch.randn(3, requires_grad=True)
b = torch.ones(3)
c = a + b  # c.requires_grad = True
d = c[1:]  # d.requires_grad = True

print(c.requires_grad)  # 输出:True
print(d.requires_grad)  # 输出:True

3. 梯度计算控制技术

两种梯度控制方法对比:

方法 作用域 计算图影响 典型应用场景
detach() 单个张量 切断历史计算图 冻结网络部分参数
no_grad() 代码块 禁用全部梯度计算 模型推理/参数更新

detach()示例

x = torch.tensor(2.0, requires_grad=True)
y = x.detach()  2  # y的创建不记录计算历史

y.backward()  # 会报错,因为y与x的计算图已断开

no_grad()示例

with torch.no_grad():
    y = x  2 + 3*x  # 不会构建计算图
    z = y.mean()
    
z.backward()  # 报错:在no_grad块内操作无法反向传播

4. 自定义函数微分实现

使用@torch.jit.script创建静态图函数:

动态图模式

def dynamic_sigmoid(x):
    return 1 / (1 + torch.exp(-x))

x = torch.tensor([1.0], requires_grad=True)
y = dynamic_sigmoid(x)
y.backward()
print(x.grad)  # 输出:sigmoid(1)*(1-sigmoid(1)) ≈ 0.1966

静态图模式

@torch.jit.script
def static_sigmoid(x: torch.Tensor) -> torch.Tensor:
    return 1 / (1 + torch.exp(-x))

x = torch.tensor([1.0], requires_grad=True)
y = static_sigmoid(x)
y.backward()
print(x.grad)  # 输出相同结果,但计算图被优化

5. 动态图与静态图对比

特性对比表:

特性 动态图(Eager Mode) 静态图(Script Mode)
调试难度 支持实时调试 需要编译后调试
计算优化 无优化 编译器自动优化
控制流 支持Python原生控制流 需使用TorchScript语法
部署能力 不可直接部署 可导出为独立模型
内存占用 较高 较低

6. 高阶导数计算实践

雅可比矩阵计算

def jacobian_example():
    x = torch.tensor([1.0, 2.0], requires_grad=True)
    y = torch.stack([x[0]2 + x[1], x[1]3])
    
    jacobian = []
    for i in range(y.size(0)):
        grad = torch.autograd.grad(y[i], x, retain_graph=True)[0]
        jacobian.append(grad)
    
    return torch.stack(jacobian)

print(jacobian_example())
输出:tensor([[2., 1.],
              [0., 12.]])

二阶导数计算

x = torch.tensor(3.0, requires_grad=True)
y = x3 + 2*x

一阶导
dy_dx = torch.autograd.grad(y, x, create_graph=True)[0]
二阶导
d2y_dx2 = torch.autograd.grad(dy_dx, x)[0]

print(dy_dx.item())   # 3*3² + 2 = 29
print(d2y_dx2.item()) # 6*3 = 18

7. 代码运行流程图

前向传播
构建动态计算图
reshape操作
设置requires_grad=True
自动微分系统记录操作
遍历计算图计算梯度
梯度累加到叶子节点

8. 完整代码实现

import torch

二阶导数计算示例
def higher_order_derivatives():
    x = torch.tensor(2.0, requires_grad=True)
    
    # 一阶计算
    with torch.enable_grad():
        y = x  3
        dy_dx = torch.autograd.grad(y, x, create_graph=True)[0]
    
    # 二阶计算
    d2y_dx2 = torch.autograd.grad(dy_dx, x, retain_graph=True)[0]
    
    return dy_dx.item(), d2y_dx2.item()

print(higher_order_derivatives())  # 输出:(12.0, 12.0)

9. 梯度计算调试技巧

常见问题排查表:

现象 可能原因 解决方案
梯度为None 未设置requires_grad=True 检查张量梯度属性
计算图丢失 使用in-place操作 改用非in-place方法
内存泄漏 未及时释放计算图 使用detach()或no_grad()
梯度爆炸 学习率过大 添加梯度裁剪
梯度消失 网络层过深 使用残差连接

清华大学全三版的《DeepSeek教程》完整的文档需要的朋友,关注我私信:deepseek 即可获得。

怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐