刹车与油门:PyTorch Autograd 的赛车之旅

560 阅读7分钟

对于大模型的学习与探索不要停止,LLM 就是“未来已来”。

前面通过简单的实操上手 Pytorch:# 轻松上手:PyTorch 预测书店销售趋势,本篇带来 Pytorch 核心引擎:autograd。

那么,autograd 在 Pytorch 中是怎样的定位呢?

一句话:autograd 为 Tensors 上的所有操作提供自动微分。 用白话来作比喻:

神经网络通过不断调整参数(类似于汽车的油门),微分是告诉网络:如果你稍微调整一下参数,预测值会如何变化(汽车速度仪表盘)。

当你加速时,仪表盘速度逐渐增加;而当你减速时,仪表盘速度逐渐减小。这个速度的变化信息,就好比微分提供的导数信息。

autograd像是汽车的智能驾驶系统:它能够自动追踪神经网络中各个参数的变化,同时告诉你,每个参数的微小调整会对最终预测结果产生怎样的影响。

有了autograd,使得我们能够以一种更智能的方式来训练神经网络,让它逐渐学会正确的任务。

具体怎么实践呢?

简单计算

还记得:torch.Tensor张量?通过将属性.requires_grad设置为True,就能实现开始跟踪针对张量的所有操作。

完成计算后,再调用.backward()来自动计算所有梯度,并将梯度累积到.grad属性中。

我们先设置属性、然后打印看看:

import torch

# 创建一个张量并设置requires_grad=True,开始跟踪计算
x = torch.ones(2, 2, requires_grad=True)
print(x)

# 对张量进行操作
y = x + 2
print(y)
print(y.grad_fn)

image.png

y.grad_fn 显示 <AddBackward0 object at ...>。这表示 y 是通过加法操作得到的;

梯度计算

基于以上,再叠加一个乘法计算,然后来计算梯度:

import torch

# 创建一个张量并设置requires_grad=True,开始跟踪计算
x = torch.ones(2, 2, requires_grad=True)
print(x)

# 对张量进行操作
y = x + 2
print(y)
print(y.grad_fn)

# 叠加操作
z = y * y * 3
out = z.mean()

# 计算梯度
out.backward()

# 打印梯度 d(out)/dx
print(x.grad)

image.png

调用 out.backward() 表示计算 out 相对于所有具有 requires_grad=True 的张量的梯度;

最后,打印了 x 相对于 out 的梯度,d(out)/d(x);

拉力比赛

且慢,如果觉得上述不太好理解, 我们用“开车”比喻:

你参加了一场拉力赛,目标是尽量快地开车冲过终点(out),整个比赛道路是由一系列拉力赛中的弯道和直道组成。

1、 起点(x): 你的车停在起点,表示比赛的初始状态,就是在比赛开始前的你的位置。

2、第一段直道(y = x + 2): 你沿着第一段直道加速行驶,表示进行了一个加法操作,像是在拉力赛中的一段平稳的直道,你可以提速。

3、弯道(z = y * y * 3): 进入弯道,表示进行平方和乘法操作;弯道可能有些曲折,需要通过巧妙的驾驶技巧来维持速度。

4、 终点(out = z.mean()): 最终,你冲过了比赛的终点,这就是整个计算过程的结果。

比赛的过程中,你会思考一个问题:如果我在这个位置采取稍微不同的策略,我的比赛时间会有多大的变化?

这种变化就是梯度,它告诉你在每个位置上应该如何调整你的驾驶策略,以便更快地冲过终点。

将上述代码调整一下:

import torch

# 创建一个张量并设置requires_grad=True,开始跟踪计算
x = torch.ones(2, 2, requires_grad=True)
print("比赛起始位置 x:", x)

# 进入第一段直道
y = x + 2
print("进入第一段直道 y:", y)
print("y 的梯度(直道的加速度):", y.grad_fn.next_functions[0][0].variable.grad)

# 进入弯道
z = y * y * 3
out = z.mean()

# 计算梯度
out.backward()

# 打印梯度 d(out)/dx
print("x 的梯度(终点前的整体策略):", x.grad)

运行结果:

image.png

tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

这表示在整个拉力赛比赛中,如果在每个位置微调 x 的值,对最终结果的影响。

比如,取张量的左上角元素,其值为 4.5。这表示如果在起点位置微调拉力赛车的某个策略,比如调整油门或方向盘,将会使比赛结果 out 增加 4.5。同理,其他位置的元素也表示了在对应位置上微调 x 对最终结果的影响。

微调展示

基于上述理论上的原理,我们则可以开始一定程度的微调了,再借助 Matplotlib 库绘制曲线图;代码如下:

import torch
import matplotlib.pyplot as plt

# 创建一个张量并设置requires_grad=True,开始跟踪计算
x = torch.ones(2, 2, requires_grad=True)

# 存储梯度和结果的变化
grad_history = []
out_history = []

# 进行微调并记录结果变化
for i in range(50):
    # 进入第一段直道
    y = x + 2

    # 进入弯道
    z = y * y * 3
    out = z.mean()

    # 记录梯度和结果
    if x.grad is not None:
        grad_history.append(x.grad.view(-1).numpy().copy())
    out_history.append(out.item())

    # 计算梯度
    out.backward()

    # 根据梯度微调 x,模拟优化过程
    with torch.no_grad():
        x -= 0.1 * x.grad if x.grad is not None else 0  # 这里使用一个简单的学习率

    # 清零梯度,以便下一次计算
    x.grad.zero_()

# 绘制曲线图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(grad_history)
ax1.set_title('Gradient Changes')
ax1.set_xlabel('Iteration')
ax1.set_ylabel('Gradient Value')

ax2.plot(out_history)
ax2.set_title('Result Changes')
ax2.set_xlabel('Iteration')
ax2.set_ylabel('Result Value')

plt.show()
  1. 创建张量和跟踪计算:

    x = torch.ones(2, 2, requires_grad=True)
    

    先创建了一个2x2的张量 x,并告诉PyTorch我们希望追踪它的计算历史,因为我们将在优化过程中对它进行微调。

  2. 初始化存储变量:

    grad_history = []
    out_history = []
    

    初始化两个空列表,用于存储梯度和结果的变化。

  3. 进行微调并记录变化:

    for i in range(50):
        y = x + 2
        z = y * y * 3
        out = z.mean()
    

    在这个循环中,模拟一个简单的优化过程。首先,我们进入了一个“直道”(y = x + 2),然后进入了一个“弯道”(z = y * y * 3)。out是结果的均值。

  4. 计算梯度:

        out.backward()
    

    通过调用 backward() 方法,PyTorch 自动计算了关于 x 的梯度。这个梯度告诉我们在当前参数设置下,损失函数关于参数的变化方向和强度。

  5. 记录梯度和结果:

        if x.grad is not None:
            grad_history.append(x.grad.view(-1).numpy().copy())
    out_history.append(out.item())
    

    我们将梯度(经过一些处理以方便绘图)和结果的值记录到相应的列表中。

  6. 根据梯度微调参数:

        with torch.no_grad():
            x -= 0.1 * x.grad if x.grad is not None else 0
    

    这里使用一个简单的学习率(0.1)根据梯度微调张量 x,模拟优化算法的一次迭代。

  7. 清零梯度:

        x.grad.zero_()
    

    为了确保下一次计算的梯度是基于新的微调后的参数,我们在每次迭代之后都将梯度清零。

  8. 绘制曲线图:

    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
    ax1.plot(grad_history[1:])  # 忽略第一次迭代
    ax1.set_title('Gradient Changes')
    ax1.set_xlabel('Iteration')
    ax1.set_ylabel('Gradient Value')
    
    ax2.plot(out_history)
    ax2.set_title('Result Changes')
    ax2.set_xlabel('Iteration')
    ax2.set_ylabel('Result Value')
    
    plt.show()
    

image.png

从左图我们可以观察到,刚开始梯度值较大,表示在微调的初期,模型参数需要较大的调整。随着微调的进行,梯度值逐渐减小,说明模型参数逐渐接近最优点。当梯度值接近零时,说明模型参数已经趋于稳定,微调的幅度逐渐减小。


OK,以上,通过使用一个简单的学习率来微调模型参数,演示了学习率对优化过程的影响。

使用 PyTorch 的自动微分功能 Autograd,就可以轻松地计算模型参数的梯度,而不需要手动推导梯度公式啦~~

OK,以上便是本次分享,希望各位喜欢~ 欢迎点赞、收藏、评论 🤟 我是安东尼 🤠 人气技术博主 💥 坚持千日更文 ✍ 关注我,安东尼陪你一起度过漫长编程岁月