Kaggle房价预测实战:我用PyTorch MLP踩过的那些坑(附完整代码与Wandb调参记录)
2026/6/8 3:46:14 网站建设 项目流程

Kaggle房价预测实战:从数据清洗到模型调优的完整避坑指南

第一次接触Kaggle房价预测比赛时,我被数据集中那些看似简单却暗藏玄机的特征列弄得焦头烂额。作为沐神《动手学深度学习》课程的实践作业,这个项目远没有想象中那么简单——从数据预处理到模型构建,几乎每一步都布满了新手容易踩中的陷阱。本文将分享我在复现这个项目时遇到的实际问题与解决方案,希望能为同样在复现过程中遇到困难的初学者提供一份实用的排雷手册。

1. 数据预处理:那些容易被忽视的细节

数据清洗是任何机器学习项目的第一步,但往往也是最容易被低估的环节。在房价预测项目中,我最初天真地认为pandas的read_csv()读入数据后就可以直接开始建模,结果很快遇到了第一个障碍。

1.1 高基数类别特征的处理

当我开始检查数据集中的类别型特征时,发现了令人头疼的问题:

for in_object in all_features.dtypes[all_features.dtypes=='object'].index: print(in_object.ljust(20), len(all_features[in_object].unique()))

输出结果显示某些特征的唯一值数量惊人:

Heating 2660 Cooling 911 Parking 9913 Elementary School 3568 Appliances included 11290

这些高基数(high-cardinality)特征如果直接进行one-hot编码,会导致特征维度爆炸。我的第一次尝试就因为这个问题导致特征矩阵从原始的19列膨胀到470列,不仅增加了计算负担,还容易引发维度灾难。

解决方案

  • 仅保留基数较低的类别特征(如Type、Bedrooms)
  • 对高基数特征考虑使用目标编码(Target Encoding)或嵌入层(Embedding Layer)
  • 删除明显不相关的特征(如Address、Summary)

1.2 数值特征的标准化与对数变换

另一个常见陷阱是忽视数值特征的尺度差异。原始数据中,有些特征的值范围极大:

large_vel_cols = ['Lot', 'Total interior livable area', 'Tax assessed value', 'Annual tax amount', 'Listed Price', 'Last Sold Price']

对这些特征直接使用原始值会导致模型训练不稳定。我尝试了两种处理方法:

  1. 对数变换:对价格类特征取log(1+x)压缩尺度
  2. Z-score标准化:对所有数值特征进行(x-μ)/σ标准化
# 对数变换 for c in large_vel_cols: train_data[c] = np.log(train_data[c]+1) test_data[c] = np.log(test_data[c]+1) # Z-score标准化 numeric_features = all_features.dtypes[all_features.dtypes == 'float64'].index all_features[numeric_features] = all_features[numeric_features].apply( lambda x: (x - x.mean()) / (x.std()))

2. 模型构建:MLP架构的设计考量

确定了数据处理流程后,模型架构的选择同样充满挑战。作为初学者,我最初对神经网络层数、激活函数等超参数的选择毫无头绪。

2.1 网络深度与宽度

我尝试的第一个MLP结构非常简单:

class MLP(nn.Module): def __init__(self, in_features): super().__init__() self.layer1 = nn.Linear(in_features, 256) self.layer2 = nn.Linear(256, 64) self.out = nn.Linear(64, 1) def forward(self, X): X = F.relu(self.layer1(X)) X = F.relu(self.layer2(X)) return self.out(X)

这个结构看似合理,但在实际训练中却出现了梯度爆炸的问题。通过Wandb的监控,我发现损失值在某些epoch会突然变为nan。

改进方案

  • 添加Batch Normalization层稳定训练
  • 使用梯度裁剪(Gradient Clipping)
  • 尝试更小的初始学习率

2.2 激活函数的选择

最初我对激活函数的选择没有特别考虑,直接使用了ReLU。但在资料后,我了解到:

  • ReLU:计算高效,但存在"神经元死亡"问题
  • LeakyReLU:解决了死亡ReLU问题,但需要调参
  • Swish:自门控特性,在深度网络中表现更好

最终我选择了LeakyReLU作为折中方案:

def forward(self, X): X = F.leaky_relu(self.layer1(X), negative_slope=0.01) X = F.leaky_relu(self.layer2(X), negative_slope=0.01) return self.out(X)

3. 训练过程:监控与调参技巧

模型训练阶段是最能体现深度学习工程实践的环节,也是我踩坑最多的地方。

3.1 学习率与优化器配置

我的第一次训练尝试使用了以下配置:

optimizer = torch.optim.Adam(net.parameters(), lr=0.005, weight_decay=0.05)

结果训练过程极不稳定,损失值波动剧烈。通过Wandb的可视化,我逐步调整了以下参数:

参数初始值调整后效果
学习率0.0050.001训练更稳定
权重衰减0.050.01防止过拟合
批量大小256128内存更友好

3.2 使用Wandb进行实验跟踪

Wandb成为了我的调参救星。通过简单的集成,我可以实时监控训练过程:

import wandb wandb.init(project="kaggle_predict", config={ "learning_rate": lr, "weight_decay": weight_decay, "batch_size": batch_size, "architecture": "MLP" }) # 在训练循环中 for epoch in range(num_epochs): # ...训练代码... wandb.log({"loss": loss.item(), "epoch": epoch})

这让我能够:

  • 比较不同超参数组合的效果
  • 及时发现训练异常(如梯度爆炸)
  • 保存最佳模型检查点

4. 提交与结果分析

经过多次调整,我的最佳提交在Kaggle上获得了0.28的RMSE分数,虽然不及顶级方案,但对于第一次尝试来说已经是不错的成绩。

4.1 模型集成技巧

为了提高最终成绩,我尝试了以下几种集成方法:

  1. 检查点平均:保存训练过程中多个检查点,预测时取平均值
  2. 多模型融合:训练不同结构的MLP,组合它们的预测结果
  3. Bagging:对训练数据进行多次采样,训练多个模型
# 检查点平均示例 preds = [] for epoch in [100, 200, 300, 400]: net.load_state_dict(torch.load(f'checkpoint_{epoch}')) preds.append(net(test_features)) final_pred = torch.mean(torch.stack(preds), dim=0)

4.2 特征工程进阶

在基础方案之上,我还尝试了以下特征工程技巧:

  • 交互特征:将相关特征相乘或相除(如价格/面积)
  • 分箱处理:将连续变量离散化为多个区间
  • 时间特征:从日期中提取周数、季度等信息

这些技巧虽然提升了模型性能,但也增加了过拟合风险,需要谨慎使用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询