笔记|线性回归:最小二乘 + SGD
·
一、整体项目流程总览
整套线性回归分为 6 大模块,对应代码分段:
- 生成仿真数据集(带高斯噪声的标准线性数据 y=Xw+b+ϵ)
- 自定义随机批量数据迭代器(替代
torch.utils.data.DataLoader) - 初始化待训练权重 w、偏置 b,开启梯度追踪
- 手写前向传播线性模型、均方损失函数
- 手写 SGD 小批量随机梯度下降优化器(参数更新 + 梯度清零)
- 完整训练循环,多轮迭代更新参数,输出损失与拟合误差
二、完整可运行代码
import random
import torch
from d2l import torch as d2l
#生成数据集
def synthetic_data(w, b, num_examples): #@save
"""生成y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w)))#特征配置
y = torch.matmul(X, w) + b # 矩阵相乘
y += torch.normal(0, 0.01, y.shape)#加性噪音
return X, y.reshape((-1, 1))#返回x为1000个样本,每个样本有2个特征(1000×2),满足正态分布。y的话就是要reshape成1000×1的列向量
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
print('features:', features[0],'\nlabel:', labels[0])
#d2L画图
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1)
d2l.plt.show()
def data_iter(batch_size, features, labels):
"""
自定义数据集批量迭代器,用于分批次随机读取训练数据
:batch_size: 每一批次的样本数量(批量大小)
:features: 全部样本的特征张量,shape=[样本总数, 特征维度]
:labels: 全部样本的标签张量,shape=[样本总数, 输出维度] 或 [样本总数,]
:yield: 每次循环返回一批次 (批次特征, 批次标签),生成器惰性加载,节省内存
"""
# 获取整个数据集的样本总数量为1000
num_examples = len(features)
# 生成 0~999 的索引列表,每个数字代表一个样本的下标
indices = list(range(num_examples))
# 随机打乱索引顺序,实现样本随机采样(打乱训练集,避免模型学到样本顺序规律)
random.shuffle(indices)
# 从0开始,以batch_size为步长遍历所有索引,切割出每一批的索引区间
# range(start, end, step):i = 0, batch_size, 2*batch_size ...
for i in range(0, num_examples, batch_size):
# 截取当前批次对应的索引:indices[i : i+batch_size]
# min(i + batch_size, num_examples) 防止最后一批不足batch_size时下标越界
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)]
)
# 根据批次索引切片取出对应特征和标签,通过yield返回(生成器)
# 每次调用迭代器只加载一批数据,不会一次性加载全部批次,内存友好
yield features[batch_indices], labels[batch_indices]
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
#初始化参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
#定义模型
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
#定义损失函数
def squared_loss(y_hat, y): #@save
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
#定义优化算法:只做参数更新+梯度清零
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():#临时关闭张量自动求导计算图
for param in params:
param -= lr * param.grad / batch_size#更新w的公式
param.grad.zero_()#梯度清0
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
三、分模块深度解析
3.1 仿真数据集生成模块
现实中我们很难拿到完全干净的线性数据,因此手动构造带噪声数据模拟真实场景:
torch.normal(0,1)生成标准正态分布特征,保证特征取值均匀;- 基础线性公式 y=Xw+b 构建理想真值;
- 叠加微小正态噪声
torch.normal(0,0.01),模拟采集数据的测量误差; - 标签
y统一 reshape 为列向量,避免后续矩阵运算维度不匹配报错。
可视化部分:我们取第二个特征和标签绘制散点图,能直观看到清晰的线性正 / 负相关趋势,验证数据集构造无误。
3.2 手动批量迭代器(核心面试考点)
PyTorch 高层 APIDataLoader内部逻辑和这里完全一致,核心逻辑 3 步:
- 生成全部样本索引,用
random.shuffle打乱顺序 ——避免模型记住样本排列顺序,造成过拟合; - 按
batch_size分段切分索引,通过索引切片取出对应数据; - 使用
yield生成器惰性输出,不会一次性把全部数据存入内存,大数据集下内存占用极低。
小细节:min(i + batch_size, num_examples)用来兜底最后一批不足batch_size的样本,防止索引越界。
3.3 参数初始化与梯度追踪
- 权重
w使用极小正态分布初始化:全部置 0 会导致梯度对称、收敛变慢,小随机值打破对称性; requires_grad=True:开启张量的自动求导记录,PyTorch 会跟踪该张量所有运算构建计算图;- 偏置
b初始化为 0 是线性回归通用做法。
3.4 前向模型 & 均方损失
- 线性模型
linreg仅做矩阵乘法 + 偏置加法,是最简单的单层神经网络; - 均方损失除以 2 是数学技巧:求导后平方项的 2 会抵消,简化梯度计算,不影响最优解。
3.5 手写 SGD 优化器(深度学习底层核心)
这是本文最关键的部分,全程不调用torch.optim.SGD,手动实现参数更新:
with torch.no_grad():参数更新属于权重赋值,不需要记录梯度,关闭计算图节省显存;- 更新公式:w=w−η⋅batch_size∇L,梯度除以批次大小得到平均梯度;
param.grad.zero_():梯度会默认累加,每轮更新后必须清零,否则梯度累积导致更新错误。
3.6 完整训练循环逻辑
- 外层
epoch:一轮 epoch 代表完整遍历一次全部 1000 条样本; - 内层循环:遍历所有小批量,前向计算损失 →
backward()反向传播求梯度 → SGD 更新参数; - 每轮 epoch 结束后,在全量数据上计算平均损失,直观观察损失持续下降;
- 训练结束对比预测
w、b与预设真实值,误差极小代表模型拟合成功。
四、运行结果解读
- 损失输出:3 轮训练损失持续下降,说明 SGD 梯度下降有效收敛;
- 参数误差:输出
true_w - w、true_b - b数值非常接近 0,模型学习到了我们预设的真实线性关系 y=2x1−3.4x2+4.2; - 散点图:特征与标签呈明显线性分布,验证数据集构造合理。
五、常见易错点总结(踩坑指南)
- 维度不匹配:标签 y 必须 reshape 为列向量,否则
(y_hat - y)会触发广播错误; - 梯度不清零:忘记
param.grad.zero_()会导致梯度叠加,参数永远无法收敛; - no_grad 缺失:参数更新时不关闭自动求导,会持续构建冗余计算图,显存爆炸;
- 梯度不除以 batch_size:使用批次总梯度更新,步长过大,训练震荡不收敛;
- 不打乱数据索引:样本顺序固定,模型会学习顺序特征,泛化能力变差。
完整训练流程:人工构造带噪声的训练数据集 → 手动实现随机批量迭代器 → 初始化权重偏置 → 手写线性前向模型、均方损失、SGD 优化器 → 完整训练循环,最后对比预测参数与真实参数。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)