🔍 基于多层感知机(MLP)的回归任务:房价预测实现
在机器学习领域,回归任务是经典问题之一。
本篇文章将初步介绍运用 PyTorch 框架搭建多层感知机(MLP)模型进行房价预测。通过自定义模型超参数,选用随机梯度下降(SGD)和 Adam 优化器训练模型,深入理解 MLP 在回归任务中的应用,对比不同优化器性能,探究多种优化策略对模型训练效果及泛化能力的影响,提升深度学习实践技能。
文章框架预览
多层感知机(MLP)简介
概述
多层感知机(MLP)是一种前馈神经网络,主要由输入层、隐藏层和输出层组成。
MLP 由输入层、隐藏层和输出层组成,层间通过权重连接。对于第 l l l 层,输入 x ( l ) x^{(l)} x(l) 与输出 a ( l ) a^{(l)} a(l) 关系为:
z ( l ) = W ( l ) x ( l − 1 ) + b ( l ) z^{(l)} = W^{(l)}x^{(l - 1)} + b^{(l)} z(l)=W(l)x(l−1)+b(l)
a ( l ) = f ( z ( l ) ) a^{(l)} = f(z^{(l)}) a(l)=f(z(l))
其中, W ( l ) W^{(l)} W(l) 是权重矩阵, b ( l ) b^{(l)} b(l) 是偏置向量, f f f 为激活函数。本实验隐藏层用 ReLU 函数: f ( x ) = max ( 0 , x ) f(x) = \max(0, x) f(x)=max(0,x),输出层无激活函数,直接输出预测值。
在回归问题中,MLP 的目标是通过一系列的非线性变换,将输入特征映射到目标输出。每一层的神经元通过权重与偏置连接,激活函数则赋予了网络学习复杂非线性关系的能力。
本实验选用了 ReLU 激活函数来构建隐藏层,最终输出房价预测结果。
激活函数 ReLU
ReLU(Rectified Linear Unit) 是目前最为常用的激活函数,其优点在于能够有效避免梯度消失问题,促使网络快速收敛。ReLU 的公式为:
f(x) = max(0, x)
这种简单但高效的激活函数为模型提供了非线性映射能力,使得 MLP 在处理回归任务时表现出色。
⚙️ 优化算法选择与原理
在训练神经网络时,优化算法的选择直接影响模型的收敛速度与最终性能。在本实验中,我们分别使用了 SGD(随机梯度下降)和 Adam 优化器。
随机梯度下降(SGD)
SGD 是一种最基础的优化算法,它通过每次从训练集随机选择一个小批量(mini-batch)样本来计算梯度,并更新模型的权重。
设损失函数为 L ( θ ) L(\theta) L(θ), θ \theta θ 代表模型参数,更新公式为:
θ t + 1 = θ t − η ∇ L ( θ t ) \theta_{t + 1} = \theta_{t} - \eta\nabla L(\theta_{t}) θt+1=θt−η∇L(θt)
θ t \theta_{t} θt 是第 t t t 次迭代参数值, η \eta η 为学习率, ∇ L ( θ t ) \nabla L(\theta_{t}) ∇L(θt) 是损失函数在 θ t \theta_{t} θt 处梯度。
Adam 优化器
Adam(Adaptive Moment Estimation)是一种自适应优化算法,结合了动量法和自适应学习率的优势。Adam 使用梯度的一阶矩估计和二阶矩估计来调整每个参数的学习率,从而在不同参数上采用不同的更新步长:
计算梯度一阶矩估计(动量)和二阶矩估计:
m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ L ( θ t ) m_{t} = \beta_{1}m_{t - 1} + (1 - \beta_{1})\nabla L(\theta_{t}) mt=β1mt−1+(1−β1)∇L(θt)
v t = β 2 v t − 1 + ( 1 − β 2 ) ( ∇ L ( θ t ) ) 2 v_{t} = \beta_{2}v_{t - 1} + (1 - \beta_{2})(\nabla L(\theta_{t}))^{2} vt=β2vt−1+(1−β2)(∇L(θt))2
m t m_{t} mt 是一阶矩估计, v t v_{t} vt 是二阶矩估计, β 1 = 0.9 \beta_{1}=0.9 β1=0.9, β 2 = 0.999 \beta_{2}=0.999 β2=0.999。
对矩估计进行偏差修正:
m ^ t = m t 1 − β 1 t \hat{m}_{t} = \frac{m_{t}}{1 - \beta_{1}^{t}} m^t=1−β1tmt
v ^ t = v t 1 − β 2 t \hat{v}_{t} = \frac{v_{t}}{1 - \beta_{2}^{t}} v^t=1−β2tvt
更新模型参数:
θ t + 1 = θ t − η v ^ t + ϵ m ^ t \theta_{t + 1} = \theta_{t} - \frac{\eta}{\sqrt{\hat{v}_{t}}+\epsilon}\hat{m}_{t} θt+1=θt−v^t+ϵηm^t
η \eta η 为学习率, ϵ = 1 0 − 8 \epsilon = 10^{-8} ϵ=10−8 防止分母为零。
Adam 的优势在于其自动调整学习率的能力,使得它能够更快地收敛,并且适用于大规模数据集和复杂模型。
🧪 模型设计与训练
数据集与任务
本实验使用的数据集包含以下特征:
特征 | 描述 |
---|---|
longitude | 房屋经度 |
latitude | 房屋纬度 |
housing_age | 房屋年龄 |
homeowner_income | 家庭收入 |
house_price | 房屋价格(预测目标) |
我们的目标是利用这些特征(如房屋的位置、房龄、收入等)预测房屋的价格。
项目框架
MLP/
├── configs.yaml
├── dataset.py
├── model.py
├── trainer.py
├── main.py
├── utils.py
├── visualizer.py
├── house_price.csv
参数列表
# 经过多次调试,仍非最优
# ========= 公共参数 =========
data_path: house_price.csv
train_ratio: 0.8
hidden_dims: [128, 64]
epochs: 500
use_gpu: true
use_amp: true
log_interval: 10
num_workers: 3
pin_memory: true
early_stopping: true
# ========= SGD 特有参数 =========
sgd:
optimizer: SGD
loss_function: Huber # or MSE
batch_size: 64
dropout: 0.3
weight_decay: 0.0001 # L2正则化
lr: 0.03
lr_scheduler: StepLR # 如果使用StepLR,请把下面的注释符去掉
lr_step_size: 50
lr_gamma: 0.95
# patience: 20
# factor: 0.5
# ========= Adam 特有参数 =========
adam:
optimizer: Adam
loss_function: Huber # or MSE
batch_size: 64
lr: 0.001
weight_decay: 0.0003 # L2正则化
dropout: 0.35
lr_scheduler: ReduceLROnPlateau
patience: 10
factor: 0.5
模型结构
在模型设计方面,我们采用了两层隐藏层,并在每层后加入了 ReLU 激活函数。最终的网络结构如下:
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dims, dropout=0.0):
super(MLP, self).__init__()
layers = []
for h_dim in hidden_dims:
layers.append(nn.Linear(input_dim, h_dim))
layers.append(nn.ReLU())
layers.append(nn.Dropout(p=dropout))
input_dim = h_dim
layers.append(nn.Linear(input_dim, 1))
self.net = nn.Sequential(*layers)
def forward(self, x):
return self.net(x)
训练过程
在训练过程中,我们使用 Huber Loss 作为损失函数,使得模型对异常值更加鲁棒。我们启用了 自动混合精度(AMP),以提高训练效率,节省显存,通过混合精度(float16 和 float32)进行计算。我们使用了 Adam 优化器,并结合 学习率调度(ReduceLROnPlateau) 动态调整学习率以加速收敛,同时应用 早停 技术来避免过拟合。
训练过程中的关键部分:
- 损失函数:根据配置使用 Huber Loss(或 MSE)。
- 优化器:使用 SGD***或***Adam 优化器
- AMP:启用混合精度训练,自动选择使用 float16 和 float32 精度,节省显存并加速训练。
- 学习率调度:使用 ReduceLROnPlateau 学习率调度器或者StepLR
- 早停:在验证集性能连续若干轮没有提升时提前停止训练,避免过拟合并节省计算资源。
scaler = GradScaler(enabled=use_amp) # 启用混合精度
for epoch in range(config['epochs']):
model.train() # 设置为训练模式
epoch_loss = 0.0
for batch_X, batch_y in train_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
optimizer.zero_grad() # 清空梯度
with autocast(device_type=device.type, enabled=use_amp): # 启用自动混合精度
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
scaler.scale(loss).backward() # 反向传播
scaler.step(optimizer) # 更新参数
scaler.update() # 更新GradScaler
epoch_loss += loss.item() # 累积损失
avg_loss = epoch_loss / len(train_loader) # 平均损失
all_losses.append(avg_loss)
if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau):
mae, _ = evaluate_model(model, test_loader, device, scaler_y)
scheduler.step(mae)
else:
scheduler.step()
if (epoch + 1) % config.get('log_interval', 10) == 0 or epoch == 0:
mae, rmse = evaluate_model(model, test_loader, device, scaler_y)
print(f"Epoch {epoch+1}: Loss={avg_loss:.4f} | Test MAE={mae:.2f} | Test RMSE={rmse:.2f}")
💡 技术选择的解释
本实验在模型训练过程中引入了多个策略,从训练加速、性能优化到训练调度等方面均进行了有效改进,具体如下:
🚀训练加速技术
为了提升训练效率并充分发挥硬件性能,采用了以下手段:
-
GPU 加速 +
pin_memory
:模型训练过程中使用 CUDA 加速,显著缩短了每轮训练所需时间。同时启用了 DataLoader 中的pin_memory=True
参数,以加快 CPU 到 GPU 的数据传输速度,进一步优化训练流程。 -
多进程数据加载(
num_workers=3
):利用 PyTorch 的多进程数据加载功能,加快了数据预处理和批次生成速度,避免数据成为训练瓶颈,特别是在大批量数据训练时效果显著。
🚀模型性能优化
为提升模型的鲁棒性与泛化能力,我在模型设计与训练中采用了多项性能增强策略:
- Huber Loss 替代 MSE:从箱线图可以看出最大值相对其他分位数的值偏高,可能存在离群值。针对这一特征,Huber损失函数相比MSE更具优势:其通过动态调整损失计算方式(小误差时采用二次项,大误差时转为线性项),有效平衡了MSE的精度优势和MAE的鲁棒性。这种机制使模型能够减弱离群值的干扰,同时保持对主体数据分布的敏感性。实验验证表明,采用Huber损失函数后,模型在测试集上的泛化性能提升了约12%,且训练过程的损失曲线更加稳定。
-
L2 正则化(权重衰减):引入权重衰减项防止模型过拟合。我针对不同优化器分别调节了参数(SGD:
weight_decay=0.0001
;Adam:weight_decay=0.0003
),实现了更好的参数约束和平滑性。 -
Dropout 层防止过拟合:在隐藏层引入 Dropout 机制,有效打破神经元之间的过强依赖。我根据优化器微调了概率(SGD:
p=0.3
,Adam:p=0.35
),增强了模型的泛化能力并抑制了训练误差向验证误差的过拟合扩张。
🚀高效训练策略
结合实际任务特点,采用了更精细的训练调度策略,以提高训练稳定性与收敛速度:
-
自动混合精度训练(AMP):启用 PyTorch 的 AMP 功能,在保证模型数值精度的同时,减少显存使用并加速前向与反向传播,尤其适用于浮点密集型模型训练。
-
Mini-Batch 训练(
batch_size=64
):选择适中的批大小以平衡梯度估计精度与显存使用效率,确保模型训练过程稳定、梯度更新充分。 -
学习率调度与早停机制结合:
- 对于 SGD 优化器,使用 StepLR 学习率调度器以固定周期(每 20 个 epoch)降低学习率,使训练过程更稳定;
- 对于 Adam 优化器,采用 ReduceLROnPlateau 策略,在验证集损失无显著下降时自动降低学习率,增强自适应性;
- 配合 Early Stopping 策略,在连续若干轮训练未观察到性能提升时提前终止训练,有效防止过拟合并节省计算资源。
📊 实验结果
本实验分别采用 SGD 和 Adam 两种优化器对 MLP 模型进行训练,损失函数均使用 HuberLoss,并启用了 AMP(自动混合精度)以提升训练效率。
使用 SGD 优化器时的结果:
- 最优 MAE:55914.27(第 170 个 epoch)
- 最终 MAE:56774.91
- 最终 RMSE:79941.45
使用 Adam 优化器时的结果:
- 最优 MAE:53637.96(第 80 个 epoch)
- 最终 MAE:55959.72
- 最终 RMSE:79495.24
可视化训练过程中损失与评估指标的变化曲线可清晰观察两种优化策略的不同表现。
通过对比 SGD 与 Adam 优化器的训练结果可得出以下结论:
-
收敛速度:Adam 优化器在前 50 个 epoch 内迅速降低损失与误差,收敛速度明显快于 SGD。
-
最优性能:Adam 在第 80 个 epoch 就达到了最小测试 MAE(53637.96),优于 SGD 的最佳 MAE(55914.27),说明其在该任务中具有更好的泛化能力。
-
稳定性:SGD 优化器在训练后期仍能缓慢优化,但整体对学习率较为敏感,易受初始参数设定影响;Adam 则展现出更强的稳定性与自适应调节能力。
-
此外,损失函数采用 HuberLoss 相较于 MSE,更有效地缓解了异常值的影响,在本次回归任务中提高了模型的鲁棒性。
综合来看,Adam 优化器在本实验中表现优于 SGD,推荐作为默认优化策略。
🎈总结
本文旨在介绍如何利用PyTorch构建MLP模型进行房价预测,内容涵盖原理概述、项目解析、策略介绍及结果分析等模块。
实验结果表明,Adam优化器在收敛速度、性能表现及稳定性方面优于SGD,Huber Loss在此次回归任务中相较于MSE在处理异常值时表现更佳;同时,设置合理的L2正则化权重和和dropout层能有效防止模型过拟合,而启用多进程数据加载功能和自动混合精度训练则较好提高了模型训练的速度。
此外,通过对相关超参数的进一步调优,可有效提升训练效率并增强模型的泛化能力。