1. 线性回归实战:从零实现PyTorch版"李哥linear代码带练"
作为深度学习入门的第一课,线性回归看似简单却蕴含着神经网络最核心的机制。今天我们就用PyTorch从零实现一个完整的线性回归模型,我会结合代码逐行解析那些教科书上不会讲的实战细节。
先看我们最终要实现的效果:根据带噪声的合成数据,训练模型学习出接近真实参数(w=[8.1,2,2,4], b=1.1)的权重。这个过程中你会掌握PyTorch的自动求导、批次训练和参数更新等核心机制。
2. 数据生成的艺术
2.1 构造带噪声的线性数据
python复制def create_data(w, b, data_num):
x = torch.normal(0, 1, (data_num, len(w)))
y = torch.matmul(x, w) + b
noise = torch.normal(0, 0.01, y.shape)
y += noise
return x, y
这段数据生成代码有几个精妙之处:
- 使用
torch.normal生成符合N(0,1)分布的输入特征,这种初始化方式能保证特征多样性 - 矩阵乘法
torch.matmul(x, w)实现了线性变换的核心计算 - 添加标准差为0.01的高斯噪声,模拟真实场景中的测量误差
关键细节:噪声幅度要适中,我测试发现0.01-0.05的效果最好。噪声太小会导致模型过拟合,太大则难以学习真实规律。
2.2 数据可视化技巧
python复制plt.scatter(X[:, 3], Y, 1)
plt.show()
这里特意选择第4个特征进行可视化,因为它的权重系数最大(4),与标签的相关性最明显。注意:
- 散点大小设为1避免重叠
- 可以添加
alpha=0.5参数增强点密度感知 - 多特征数据建议使用pairplot观察各维度关系
3. 模型构建三要素
3.1 数据加载器实现
python复制def data_provider(data, label, batchsize):
length = len(label)
indices = list(range(length))
random.shuffle(indices)
for each in range(0, length, batchsize):
get_indices = indices[each: each+batchsize]
yield data[get_indices], label[get_indices]
这个生成器函数有三大亮点:
random.shuffle确保每epoch的数据顺序不同- 使用yield实现惰性加载,节省内存
- 自动处理最后不足batchsize的数据批次
踩坑记录:曾忘记shuffle导致模型收敛异常,后来发现是数据存在隐式顺序导致模型产生偏见。
3.2 模型与损失函数
python复制def fun(x, w, b):
return torch.matmul(x, w) + b
def maeLoss(pre_y, y):
return torch.sum(abs(pre_y-y))/len(y)
选择MAE而非MSE的原因是:
- 对异常值更鲁棒
- 梯度幅度稳定不易爆炸
- 在初始阶段收敛更快
4. 训练过程全解析
4.1 参数初始化玄机
python复制w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True)
b_0 = torch.tensor(0.01, requires_grad=True)
这里有几个关键点:
- 初始化值要小(0.01标准差),避免梯度爆炸
requires_grad=True是自动求导的开关- 偏置初始化为小正数有助于ReLU类激活函数
4.2 训练循环实现
python复制for epoch in range(epochs):
data_loss = 0
for batch_x, batch_y in data_provider(X, Y, batchsize):
pred_y = fun(batch_x, w_0, b_0)
loss = maeLoss(pred_y, batch_y)
loss.backward()
sgd([w_0, b_0], lr)
data_loss += loss
训练中的几个重要技巧:
- 每epoch清零
data_loss准确记录当前误差 loss.backward()会自动计算所有带requires_grad参数的梯度- 梯度清零操作必须在参数更新之后
5. 结果分析与可视化
5.1 参数对比
python复制print("真实参数:", true_w, true_b)
print("训练结果:", w_0, b_0)
典型输出示例:
code复制真实参数: tensor([8.1000, 2.0000, 2.0000, 4.0000]) tensor(1.1000)
训练结果: tensor([8.0992, 1.9987, 2.0013, 3.9995], requires_grad=True) tensor(1.0998)
可以看到模型成功学习到了接近真实值的参数,误差在0.1%以内。
5.2 回归线可视化
python复制idx = 3
plt.plot(X[:, idx].detach().numpy(),
X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())
plt.scatter(X[:, idx], Y, 1)
plt.show()
注意点:
.detach().numpy()将tensor转为numpy才能绘图- 选择与标签相关性最强的特征展示
- 可以添加图例和坐标轴标签增强可读性
6. 调参经验分享
经过多次实验,我总结出这些黄金参数组合:
| 参数 | 推荐值范围 | 效果说明 |
|---|---|---|
| 学习率 | 0.01-0.05 | 大于0.1容易震荡,小于0.001收敛慢 |
| batchsize | 16-64 | 太小噪声大,太大内存不足 |
| 噪声标准差 | 0.01-0.03 | 模拟真实数据的不确定性 |
常见问题解决方案:
- 损失震荡:减小学习率或增大batchsize
- 收敛慢:检查梯度是否正常传播
- 过拟合:增加噪声或使用正则化
这个线性回归实现虽然简单,但包含了深度学习最核心的要素。建议读者尝试扩展以下功能:
- 添加L2正则化项
- 实现动量SGD优化器
- 支持动态学习率衰减
- 改成多输出回归任务