Kaggle房价预测实战:用PyTorch搭建MLP时,我是如何解决特征爆炸和梯度问题的?
2026/6/8 3:15:04 网站建设 项目流程

Kaggle房价预测实战:用PyTorch搭建MLP时,我是如何解决特征爆炸和梯度问题的?

当我在Kaggle房价预测比赛中第一次尝试用PyTorch搭建多层感知机(MLP)时,本以为凭借基础的深度学习知识就能轻松应对。然而现实却给了我当头一棒——数据预处理阶段就遇到了特征维度爆炸的问题,训练过程中又频繁出现梯度异常。这篇文章将分享我从失败到成功的完整调试历程,特别是那些在教程中很少提及的实战"坑点"。

1. 数据预处理:从特征爆炸到智能降维

1.1 One-Hot编码的陷阱

最初我像处理常规分类特征一样,对所有非数值型特征进行了one-hot编码:

print('before one hot code',all_features.shape) # (79065, 19) all_features = pd.get_dummies(all_features,dummy_na=True) print('after one hot code',all_features.shape) # (79065, 470)

这个简单的操作让特征维度从19激增到470!对于"Cooling features"这样的字段,竟然有596个不同取值。这直接导致后续模型参数数量暴增,训练速度大幅下降。

解决方案

  • 对高基数分类特征采用频次过滤:只保留出现次数前N的类别
  • 对"Type"这类低基数特征(174个类别)保留完整one-hot编码
  • 对连续数值特征进行分箱处理,减少one-hot后的维度

1.2 特征工程的优化策略

通过分析数据分布,我发现几个关键优化点:

特征类型原始处理方式优化方案维度影响
分类特征全量one-hot频次过滤+嵌入层减少82%
数值特征直接归一化分箱+one-hot增加15%
文本特征完全丢弃TF-IDF特征提取增加50维

最终采用的混合处理方案:

# 对高基数特征进行频次过滤 high_cardinality = ['Cooling features','Heating features'] for col in high_cardinality: top_30 = all_features[col].value_counts().index[:30] all_features[col] = all_features[col].where(all_features[col].isin(top_30), 'other') # 对数值特征进行分箱处理 num_features = ['Lot','Total interior livable area'] for col in num_features: all_features[col+'_bin'] = pd.qcut(all_features[col], q=10, labels=False)

2. 模型设计:平衡深度与稳定性的艺术

2.1 MLP架构的迭代过程

初始的3层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)

但在实际训练中出现了两个典型问题:

  1. 梯度爆炸:损失值突然变为NaN
  2. 梯度消失:深层参数几乎不更新

改进后的架构

class ImprovedMLP(nn.Module): def __init__(self, in_features): super().__init__() self.bn0 = nn.BatchNorm1d(in_features) self.layer1 = nn.Linear(in_features,128) self.bn1 = nn.BatchNorm1d(128) self.layer2 = nn.Linear(128,64) self.bn2 = nn.BatchNorm1d(64) self.layer3 = nn.Linear(64,32) self.out = nn.Linear(32,1) # 初始化技巧 for layer in [self.layer1, self.layer2, self.layer3]: nn.init.kaiming_normal_(layer.weight, mode='fan_in', nonlinearity='relu')

关键改进点:

  • 添加BatchNorm层稳定梯度流动
  • 采用Kaiming初始化适配ReLU激活函数
  • 适当减少每层神经元数量,增加网络深度

2.2 激活函数与归一化的选择

测试了不同组合的效果对比:

配置方案验证集RMSE训练稳定性
ReLU + 无BN0.58经常爆炸
LeakyReLU + LayerNorm0.55较稳定
Swish + BatchNorm0.53最稳定

最终采用的激活函数组合:

def forward(self, x): x = self.bn0(x) x = F.leaky_relu(self.bn1(self.layer1(x)), negative_slope=0.01) x = F.leaky_relu(self.bn2(self.layer2(x)), negative_slope=0.01) x = F.dropout(x, p=0.3, training=self.training) x = self.out(x) return x

3. 训练调参:从混沌到有序

3.1 学习率与优化器的选择

初始使用Adam优化器时,即使设置learning_rate=0.005也出现了训练不稳定的情况。通过wandb记录的实验数据揭示了问题本质:

wandb.init(project="kaggle_predict", config={ "learning_rate": 0.001, "weight_decay": 0.05, "batch_size": 256 })

实验对比结果:

优化器学习率weight_decay最佳RMSE
Adam0.0050.050.62
AdamW0.0010.10.57
RAdam0.0020.050.54

关键发现

  • Adam优化器需要更小的初始学习率
  • AdamW对权重衰减的处理更合理
  • 配合学习率预热(warmup)效果更好

3.2 梯度裁剪的妙用

即使调整了网络结构和优化器,仍然偶尔会出现梯度爆炸。添加梯度裁剪后问题得到彻底解决:

optimizer = torch.optim.AdamW(model.parameters(), lr=0.001) for X, y in train_iter: optimizer.zero_grad() outputs = model(X) loss = criterion(outputs, y) loss.backward() # 添加梯度裁剪 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step()

合适的裁剪阈值需要通过实验确定:

阈值训练稳定性最终性能
不稳定NaN
5.0较稳定0.56
1.0很稳定0.53
0.1稳定但收敛慢0.55

4. 监控与调试:用wandb洞察训练过程

4.1 关键指标的监控策略

配置wandb监控以下核心指标:

wandb.log({ 'train_loss': current_loss, 'learning_rate': optimizer.param_groups[0]['lr'], 'grad_norm': compute_grad_norm(model), 'weight_norm': compute_weight_norm(model) })

通过分析这些指标,可以诊断出各种训练问题:

问题现象可能原因解决方案
梯度范数骤增学习率太大减小LR或增加梯度裁剪
权重范数持续增大权重衰减不足增加weight_decay
损失震荡严重batch size太小增加batch size

4.2 有效的早停策略

单纯的验证集监控可能导致过早停止,改进方案:

# 平滑处理验证损失 smoothed_val_loss = 0.0 best_loss = float('inf') patience = 0 for epoch in range(epochs): # ...训练过程... smoothed_val_loss = 0.9 * smoothed_val_loss + 0.1 * current_val_loss if smoothed_val_loss < best_loss: best_loss = smoothed_val_loss patience = 0 # 保存最佳模型 else: patience += 1 if patience > 10: break

这种平滑处理能避免因单次波动导致的误判,在实际应用中效果显著。

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

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

立即咨询