深度学习02-线性回归:从理论到PyTorch实现
文章内容来源:《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 ) 参数值,需要定义评估准则。
- 平方损失函数:回归问题常采用平方损失函数,公式为:
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 - 代价函数:机器学习常用代价函数 ( 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=1∑N(h(x(i);w,b)−y(i))2
其中 ( N ) 是数据点的数量,( x^{(i)} ) 和 ( y^{(i)} ) 分别表示第 ( i ) 届奥运会的举办年和取胜时间。 - 优化目标:通过调节 ( 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=1∑N(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}∂w∂J(w,b)=∂h∂J(w,b)⋅∂w∂h
∂∂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}∂b∂J(w,b)=∂h∂J(w,b)⋅∂b∂h - 其中,( \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)})∂h∂J(w,b)=N2i=1∑N(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}=1∂w∂h=x(i),∂b∂h=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)}∂w∂J(w,b)=N2i=1∑N(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)})∂b∂J(w,b)=N2i=1∑N(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米自由泳数据的线性回归案例,我们完成了“理论+实践”的闭环:
- 理论层面:掌握了线性回归的模型定义、线性假设、代价函数评估,以及梯度下降的算法原理与推导;
- 实践层面:通过PyTorch从头实现了模型、损失函数、梯度计算与训练流程,初步掌握了用PyTorch解决机器学习问题的核心流程。
线性回归作为深度学习的“入门钥匙”,为后续学习卷积神经网络、循环神经网络等复杂模型奠定了基础。
更多推荐



所有评论(0)