文章内容来源:《PyTorch编程技术与深度学习》 作者:袁梅宇

一、线性回归模型概述

线性回归是一种简单直观的机器学习模型,它通过学习属性与目标属性的线性关系来揭示数据规律。正因为其简单性,很适合作为深度学习入门的开篇,能让我们把更多精力放在PyTorch编程实现的技巧上,而非复杂的业务逻辑。


二、基于奥运会男子100米自由泳数据的线性回归

(一)数据集介绍

我们以历届奥运会男子100米自由泳冠军纪录数据集为例。该数据集的横坐标为奥运会举办年,纵坐标为取胜时间,数据集文件为Freestyle100m.csv,存放在datasets目录下,数据来源为http://www.olympic.org/。从数据的散点图(如图3.1所示)可以看到,随着奥运会举办年份的推移,男子100米自由泳的取胜时间大致呈线性下降趋势,这为我们使用线性回归模型提供了基础。

(二)模型定义

线性回归的目标是找到一个函数,将输入属性(奥运会举办年)映射到输出属性(取胜时间)。我们把奥运会举办年记为 ( x ),取胜时间记为 ( y ),模型预测的取胜时间记为 ( \hat{y} )。由于这里 ( x ) 只含有一个输入变量(奥运会举办年),所以是单变量线性回归问题,模型可记为:
y^=h(x;θ)\hat{y} = h(x;\theta)y^=h(x;θ)
其中 ( \theta ) 是模型的参数集合。

(三)模型假设

最直观的假设是输入属性 ( x ) 与输出属性 ( \hat{y} ) 的关系是线性的,用公式表示为:
y^=h(x;w,b)=wx+b\hat{y} = h(x;w,b) = wx + by^=h(x;w,b)=wx+b
这里的 ( \theta ) 就是 ( (w,b) )。线性回归的学习任务就是用一条直线来拟合数据,通过学习找到 ( w ) 和 ( b ) 的最佳参数值,其中 ( w ) 可视为直线的斜率,( b ) 为直线的纵截距(也称为偏置)。

(四)模型评估

为了找到最好的 ( w ) 和 ( b ) 参数值,需要定义评估准则。

  1. 平方损失函数:回归问题常采用平方损失函数,公式为:
    L(y,h(x;w,b))=(h(x;w,b)−y)2L(y,h(x;w,b))=(h(x;w,b)-y)^2L(y,h(x;w,b))=(h(x;w,b)y)2
  2. 代价函数:机器学习常用代价函数 ( J(w,b) ) 衡量模型与数据点的接近程度,对于本数据集,代价函数为:
    J(w,b)=1N∑i=1N(h(x(i);w,b)−y(i))2J(w,b)=\frac{1}{N}\sum_{i = 1}^{N}(h(x^{(i)};w,b)-y^{(i)})^2J(w,b)=N1i=1N(h(x(i);w,b)y(i))2
    其中 ( N ) 是数据点的数量,( x^{(i)} ) 和 ( y^{(i)} ) 分别表示第 ( i ) 届奥运会的举办年和取胜时间。
  3. 优化目标:通过调节 ( w ) 和 ( b ) 使代价函数最小,即:
    argminw,b1N∑i=1N(h(x(i);w,b)−y(i))2\underset{w,b}{argmin}\frac{1}{N}\sum_{i = 1}^{N}(h(x^{(i)};w,b)-y^{(i)})^2w,bargminN1i=1N(h(x(i);w,b)y(i))2

(五)梯度下降算法

梯度下降是求函数最小值的常用算法,可用于求解代价函数 ( J ) 的最小值。其基本思想是:随机选取参数初值,计算代价,迭代寻找使代价下降最多的参数,直至达到局部最优。

1. 梯度计算(链式求导法则)

对于本线性回归问题,需计算 ( \frac{\partial}{\partial w}J(w,b) ) 和 ( \frac{\partial}{\partial b}J(w,b) ),推导过程如下:

  • 由链式求导法则可得:
    ∂∂wJ(w,b)=∂∂hJ(w,b)⋅∂h∂w\frac{\partial}{\partial w}J(w,b)=\frac{\partial}{\partial h}J(w,b)\cdot\frac{\partial h}{\partial w}wJ(w,b)=hJ(w,b)wh
    ∂∂bJ(w,b)=∂∂hJ(w,b)⋅∂h∂b\frac{\partial}{\partial b}J(w,b)=\frac{\partial}{\partial h}J(w,b)\cdot\frac{\partial h}{\partial b}bJ(w,b)=hJ(w,b)bh
  • 其中,( \frac{\partial}{\partial h}J(w,b) ) 的计算结果为:
    ∂∂hJ(w,b)=2N∑i=1N(h(x(i);w,b)−y(i))\frac{\partial}{\partial h}J(w,b)=\frac{2}{N}\sum_{i = 1}^{N}(h(x^{(i)};w,b)-y^{(i)})hJ(w,b)=N2i=1N(h(x(i);w,b)y(i))
  • 又因为 ( h(x{(i)};w,b)=wx{(i)} + b ),所以:
    ∂h∂w=x(i),∂h∂b=1\frac{\partial h}{\partial w}=x^{(i)},\quad \frac{\partial h}{\partial b}=1wh=x(i),bh=1
2. 最终梯度公式

结合上述推导,最终得到梯度表达式:
∂∂wJ(w,b)=2N∑i=1N(h(x(i);w,b)−y(i))x(i)\frac{\partial}{\partial w}J(w,b)=\frac{2}{N}\sum_{i = 1}^{N}(h(x^{(i)};w,b)-y^{(i)})x^{(i)}wJ(w,b)=N2i=1N(h(x(i);w,b)y(i))x(i)
∂∂bJ(w,b)=2N∑i=1N(h(x(i);w,b)−y(i))\frac{\partial}{\partial b}J(w,b)=\frac{2}{N}\sum_{i = 1}^{N}(h(x^{(i)};w,b)-y^{(i)})bJ(w,b)=N2i=1N(h(x(i);w,b)y(i))

3. 超参数设置

将梯度公式代入梯度下降算法,可优化参数 ( w ) 和 ( b )。算法需手工设置学习率 ( \eta )

  • 学习率过小:收敛速度极慢;
  • 学习率过大:可能跳过最低点,导致无法收敛甚至发散。
    这类需人为设定的参数称为“超参数”,需根据编程经验调整。

三、PyTorch实现线性回归

(一)从头开始实现线性回归

1. 定义线性回归模型

实现公式 ( h(x;w,b)=wx + b ),代码如下:

def model(x, w, b):
    """回归模型:计算wx + b"""
    return w * x + b
2. 定义损失函数与损失求导
  • 损失函数:实现 ( J(w,b)=\frac{1}{N}\sum_{i = 1}{N}(h(x{(i)};w,b)-y{(i)})2 )
  • 损失求导:实现 ( \frac{\partial}{\partial h}J(w,b)=\frac{2}{N}\sum_{i = 1}{N}(h(x{(i)};w,b)-y^{(i)}) )

代码如下:

def loss_fn(y_pred, y):
    """损失函数:计算均方误差"""
    loss = (y_pred - y) ** 2
    return loss.mean()

def grad_loss_fn(y_pred, y):
    """损失函数求导:计算2*(预测值-真实值)"""
    return 2 * (y_pred - y)
3. 定义梯度函数

实现 ( \frac{\partial J}{\partial w}=\frac{\partial J}{\partial h}\cdot\frac{\partial h}{\partial w} ) 和 ( \frac{\partial J}{\partial b}=\frac{\partial J}{\partial h}\cdot\frac{\partial h}{\partial b} ),代码如下:

def grad_fn(x, y, y_pred):
    """梯度函数:计算w和b的梯度并返回"""
    grad_w = grad_loss_fn(y_pred, y) * x  # 对应∂J/∂w
    grad_b = grad_loss_fn(y_pred, y)      # 对应∂J/∂b
    return torch.stack([grad_w.mean(), grad_b.mean()])  # 拼接梯度并返回均值
4. 定义模型训练函数

采用“批量梯度下降法”(用全部训练样本更新一次参数),迭代遵循“前向传播→计算损失→计算梯度→更新参数”步骤,代码如下:

def model_training(x, y, n_epochs, learning_rate, params, print_params=True):
    """模型训练函数"""
    for epoch in range(1, n_epochs + 1):
        w, b = params  # 取出当前参数w和b
        
        # 1. 前向传播:计算预测值
        y_pred = model(x, w, b)
        # 2. 计算损失
        loss = loss_fn(y_pred, y)
        # 3. 计算梯度
        grad = grad_fn(x, y, y_pred)
        # 4. 更新参数:params = params - 学习率*梯度
        params = params - learning_rate * grad
        
        # 每500轮打印一次结果(第1轮也打印)
        if epoch == 1 or epoch % 500 == 1:
            print(f'轮次:{epoch},\t损失:{float(loss):.6f}')
            if print_params:
                print(f'当前参数w、b:{params.detach().numpy()}')
                print(f'当前梯度:{grad.detach().numpy()}\n')
    
    return params  # 返回训练后的参数
5. 初始化参数并训练

初始化 ( w = 0 )、( b = 0 ),调用训练函数,代码如下:

import torch

# 模型参数初始化(w和b初始化为0)
w = torch.zeros(1)
b = torch.zeros(1)

# 假设x(年份)和y(时间)已通过数据集加载(此处省略数据加载代码)
# 调用训练函数,设置50000轮迭代、学习率0.000017
params = model_training(
    x=x,
    y=y,
    n_epochs=50000,
    learning_rate=0.000017,
    params=torch.tensor([0.0, 0.0])  # 初始参数[w, b]
)

# 打印最终训练得到的w和b
print(f'梯度下降找到的最优参数:w={params[0]:.6f},\t b={params[1]:.6f}\n')
6. 训练结果

运行后,得到的 ( w ) 和 ( b ) 可构建线性模型,该模型(直线)能较好拟合数据集散点(如图3.2所示)。

(二)利用PyTorch编程特性简化实现

上述“从头实现”的方式能加深对模型的理解,而PyTorch提供自动求导(autograd) 等特性,可大幅简化代码(例如无需手动推导梯度公式)。但“从头实现”是理解PyTorch编程逻辑和线性回归模型的核心基础,建议先掌握该过程再学习简化方法。


四、总结

通过奥运会男子100米自由泳数据的线性回归案例,我们完成了“理论+实践”的闭环:

  1. 理论层面:掌握了线性回归的模型定义、线性假设、代价函数评估,以及梯度下降的算法原理与推导;
  2. 实践层面:通过PyTorch从头实现了模型、损失函数、梯度计算与训练流程,初步掌握了用PyTorch解决机器学习问题的核心流程。

线性回归作为深度学习的“入门钥匙”,为后续学习卷积神经网络、循环神经网络等复杂模型奠定了基础。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐