自动微分与线性回归模型

自动微分模块【掌握】

自动微分就是:给定最终结果后,自动把计算图里每个参数的导数算出来。

先抓住 4 句话:

  • 梯度本质上就是导数
    • 在深度学习里,通常说的是:损失函数对某个参数的导数(更准确地说是偏导数)
  • 梯度表示“往哪边变、变多快”
    • 在某一点上,导数越大,说明函数在这一点附近变化越快
  • 梯度指向增长最快的方向
    • 想让损失变小,走的不是梯度方向,而是负梯度方向
  • 反向传播传播的就是梯度
    • 它本质上就是利用链式法则,从后往前把各层参数的导数一步步算出来

可以把训练过程理解成下面这条线:

输入 -> 前向计算 -> 得到损失 -> 反向传播求梯度 -> 按负梯度方向更新参数

训练神经网络时,最常用的就是这套流程。为了自动完成“求梯度”这一步,PyTorch 提供了 torch.autograd 模块。它会根据计算图自动完成求导,所以我们不需要手写链式法则:

image-20251014102229291

接下来就围绕这个流程来学自动微分,重点看两个东西:

  • backward():触发反向传播,自动计算梯度
  • grad:读取某个参数当前保存的梯度

梯度基本计算

  • pytorch不支持向量张量对向量张量的求导,只支持标量张量对向量张量的求导
    • 简单理解就是损失值需要是标量,因为要对权重求导
  • 计算梯度: y.backward(), y是一个标量张量
  • 获取x点的梯度值: x.grad, 会累加上一次的梯度值
  • 标量张量梯度计算_单轮

    1- 初始化w权重值
      """
          本代码只实现反向传播
      """
      
      import torch
      
      if __name__ == '__main__':
          # 1- 初始化w权重值
          """
              requires_grad:允许对该数据进行梯度下降计算,必须为True
              dtype:因为更新梯度的过程中肯定会出现小数的情况,因此dtype必须是小数
          """
          # 多个权重
          # w = torch.tensor([10,20,30],requires_grad=True,dtype=torch.float32)
          # 一个权重
          w = torch.tensor(20,requires_grad=True,dtype=torch.float32)
      
          # 2- 定义Loss损失函数
          # 下面的公式因为目前没有学到深度学习的损失函数有哪些,因此下面的公式是自定义的,不用关心
          loss = 2*w**2
      
          # 3- 进行反向传播->让损失值越来越小
          loss.sum().backward()
      
          # 4- 更新w权重值
          # W1 = W0 - lr*grad
          """
              w.data = w.data - 0.1 * w.grad代码类似如下的过程
              a = 20
              a = a - 1
              
              w.data是从张量中将值取出来
          """
          w.data = w.data - 0.1 * w.grad
      
          print(f"更新后的权重值是{w}")
      
      
  • 标量张量梯度计算_多轮

    1- 初始化权重值
      
      import torch
      
      if __name__ == '__main__':
          # 1- 初始化权重值
          x = torch.tensor(20,requires_grad=True,dtype=torch.float32)
      
          # 进行多次梯度的更新
          epochs = 100
          for epoch in range(epochs):
              # 2- 定义损失函数
              y = 2*x**2
      
              # 3- 梯度清零:因为Pytorch会对梯度值进行累加
              if x.grad is not None:
                  x.grad.zero_()
      
              # 4- 反向传播
              # 这里可以用sum()或者mean()。常用的是sum()
              y.sum().backward()
      
              # 5- 更新梯度值
              # W1 = W0 - lr*grad
              old_x = x.data  # 原始的权重值
              lr = 0.01        # 学习率
              grad = x.grad   # 自动微分计算得到的梯度值。也就是导数值
      
              x.data = x.data - lr*grad
      
              print(f"第{epoch+1}次计算,原始的权重值{old_x},更新后的权重值{x.data}")
      
      

梯度计算注意点【熟悉】

  • 不能将自动微分的张量转换成numpy数组,会发生报错,可以通过detach()方法实现

    查看基础信息
      import torch
      
      if __name__ == '__main__':
          t1 = torch.tensor(20,requires_grad=True,dtype=torch.float32)
      
          d_2 = t1.data
          print(d_2)
          print(d_2.requires_grad)
          print(d_2.numpy())
      
          # 查看基础信息
          # print(t1)
          # print(type(t1))
          # print(t1.requires_grad)
      
          # 如果设置requires_grad=True有使用限制。无法直接转成numpy中的ndarray数组
          # arr_1 = t1.numpy()
          # print(arr_1)
      
          # 如果还想转成numpy中的ndarray数组。需要使用detach()
          """
              使用了detach()
                  1- 能够转成numpy中的ndarray数组
                  2- 分离后的新张量与原始的张量之间共享内存,但是内存地址不同
                  
              扩展:
                  还可以通过 张量对象.data 进行分离。
          """
          d_1:torch.Tensor = t1.detach()
          arr_1 = d_1.numpy()
          print(d_1)
          print(d_1.requires_grad) # False
          print(arr_1)
      
          # 分离后的新张量与原始的张量之间共享内存,但是内存地址不同
          print(id(t1))
          print(id(d_1))
          print(t1)
          print(d_1)
          print("-"*30)
      
          d_1.add_(1000)
      
          print(id(t1))
          print(id(d_1))
          print(t1)
          print(d_1)
      
      

自动微分模块应用【了解】

演示前向传播和反向传播的过程

前向传播
import torch

if __name__ == '__main__':
    # --------------- 前向传播 --------------- 
    # 1- 样本数据准备
    # 2条样本,每条样本有5个特征
    x = torch.ones(2,5)

    # 2- 样本数据的真实值
    y = torch.zeros(2,3)

    # 3- 初始化w权重和b偏置
    w = torch.randn(5,3,requires_grad=True,dtype=torch.float32)
    b = torch.randn(3,requires_grad=True,dtype=torch.float32)

    # 4- 前向传播计算得到预测值
    z = x @ w + b

    # 5- 定义损失函数
    loss_fn = torch.nn.MSELoss()

    # 6- 计算损失值
    loss_value = loss_fn(z,y)
    print(f"损失值是:{loss_value}")

    # --------------- 反向传播 --------------- 
    # 7- 反向传播
    loss_value.sum().backward()

    # 8- 更新梯度值,也就是要更新w和b
    print(f"w的梯度是{w.grad}")
    print(f"b的梯度是{b.grad}")

PyTorch模拟线性回归模型【了解】

PyTorch模拟线性回归模型

我们使用 PyTorch 的各个组件来构建线性回归模型。在pytorch中进行模型构建的整个流程一般分为四个步骤:

  • 准备训练集数据
  • 构建要使用的模型
  • 设置损失函数和优化器
  • 模型训练

1733908449747

要使用的API:

  • 使用 PyTorch 的 nn.MSELoss() 代替平方损失函数
  • 使用 PyTorch 的 data.DataLoader 代替数据加载器
  • 使用 PyTorch 的 optim.SGD 代替优化器
  • 使用 PyTorch 的 nn.Linear 代替假设函数
import torch
from torch import nn
from torch import optim # 优化器
from torch.utils.data import TensorDataset  # 张量数据集
from torch.utils.data import DataLoader # 数据加载器
from sklearn.datasets import make_regression # 产生随机的测试数据
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号

def create_dataset():
    # 为什么这里能够返回样本真实的斜率coef?
    # 因为这些样本是程序自动给我们生成的,那么程序它本身肯定是知道斜率是多少
    x,y,coef = make_regression(
        n_samples=100,  # 生成的样本数据条数
        n_features=1,   # 每条样本的特征个数
        n_targets=1,    # 每条样本的目标值个数
        bias=10.14,     # 线性方程中的截距,也就是b
        coef=True,      # 是否要返回斜率,也就是w
        noise=10,       # 噪声,让数据稍微分散一些
        shuffle=True,   # 样本数据是否打散
        random_state=1014# 随机数种子
    )

    # print(f"x-->{x},shape-->{x.shape},type-->{type(x)}")
    # print(f"y-->{y},shape-->{y.shape},type-->{type(y)}")
    # print(f"coef-->{coef},type-->{type(coef)}")

    # 步骤:不管是什么数据类型 -> 张量
    x = torch.tensor(x,dtype=torch.float32)
    y = torch.tensor(y,dtype=torch.float32)

    return x,y,coef

def train_model(x,y,coef):
    # 1- 构造得到数据加载器
    # 1.1- 将特征和目标值进行合并
    dataset = TensorDataset(x,y)

    # 1.2- 得到数据加载器
    """
        作用:为了防止内存溢出,也就是避免数据量过大导致内存不够
        batch_size:每次给到模型训练的数据条数
        shuffle:是否要对数据进行打散,防止样本不均衡
    """
    dataloader = DataLoader(dataset,batch_size=16,shuffle=True)

    # 2- 定义对象
    # 2.1- 创建线性模型
    """
        in_features:输入的特征个数,也就是输入层的神经元个数
        out_features:输出的特征个数,也就是隐藏层的神经元个数
        bias:是否要对偏置进行计算
    """
    model = nn.Linear(in_features=1,out_features=1,bias=True)

    # 2.2- 损失函数对象
    criterion = nn.MSELoss()

    # 2.3- 优化器
    """
        作用:优化器负责自动进行梯度更新计算  W1 = W0 - lr*grad
        params:优化器对哪些参数进行梯度更新,实际就是w和b
        lr:学习率
    """
    optimizer = optim.SGD(params=model.parameters(),lr=0.01)

    # 3- 模型训练
    epochs = 100 # 总共对全量样本训练多少轮次
    loss_list = [] # 用来记录每个轮次得到的损失值

    for epoch in range(epochs):

        # 每个轮次得到的损失值
        total_loss_value = 0.0
        # 每个轮次训练的样本数据条数
        total_sample_cnt = 0

        for x_train,y_train in dataloader:
            # 预测
            y_predict = model(x_train)
            # print(f"y_train-->{y_train.shape}")
            # print(f"y_predict-->{y_predict.shape}")

            # 计算损失值
            loss_value = criterion(y_predict,y_train.reshape(-1,1))

            # 记录下损失值和样本数据条数
            total_loss_value += loss_value.item() * len(x_train)
            total_sample_cnt += len(x_train)

            # 反向传播(下面是固定代码)
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播
            loss_value.sum().backward()
            # 更新权重和偏置:也就是自动计算W1 = W0 - lr*grad
            optimizer.step()

        avg_loss_value = total_loss_value/total_sample_cnt
        loss_list.append(avg_loss_value)
        print(f"第{epoch+1}次训练,平均损失{avg_loss_value}")

    # 深度学习中不用将整个算法模型进行保存,只需要保存关键参数即可
    print("训练好的模型参数信息",model.state_dict())

    # 4- 可视化展示
    # 4.1- 循环轮次epochs与损失值的关系
    plt.plot(range(epochs), loss_list)
    plt.xlabel("循环轮次epochs")
    plt.ylabel("损失值")
    plt.title("循环轮次epochs与损失值的关系")
    plt.grid()
    plt.show()

    # 4.2- 预测和真实结果对比
    """
        1- 展示100条样本的散点图:横轴特征x,纵轴目标y
        2- 真实线性回归曲线图
        3- 预测线性回归曲线图
    """
    plt.scatter(x, y)

    axis_x = torch.linspace(x.min(), x.max(), 1000)
    # 真实线性回归曲线图
    true_fn_torch = torch.tensor([tmp_x * coef + 10.14 for tmp_x in axis_x])

    # 预测线性回归曲线图
    pred_fn_torch = torch.tensor([tmp_x * model.weight.detach() + model.bias.detach() for tmp_x in axis_x])

    plt.plot(axis_x, true_fn_torch, label="真实值", color="red")
    plt.plot(axis_x, pred_fn_torch, label="预测值", color="blue")

    plt.grid()
    plt.legend()
    plt.title("真实和预测的对比")
    plt.show()


if __name__ == '__main__':
    # 1- 准备训练集数据
    x,y,coef = create_dataset()

    # 2- 模型训练
    train_model(x,y,coef)

1733909608918

1733909623818