
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_naTrue) 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], q10, labelsFalse)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)但在实际训练中出现了两个典型问题梯度爆炸损失值突然变为NaN梯度消失深层参数几乎不更新改进后的架构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, modefan_in, nonlinearityrelu)关键改进点添加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_slope0.01) x F.leaky_relu(self.bn2(self.layer2(x)), negative_slope0.01) x F.dropout(x, p0.3, trainingself.training) x self.out(x) return x3. 训练调参从混沌到有序3.1 学习率与优化器的选择初始使用Adam优化器时即使设置learning_rate0.005也出现了训练不稳定的情况。通过wandb记录的实验数据揭示了问题本质wandb.init(projectkaggle_predict, config{ learning_rate: 0.001, weight_decay: 0.05, batch_size: 256 })实验对比结果优化器学习率weight_decay最佳RMSEAdam0.0050.050.62AdamW0.0010.10.57RAdam0.0020.050.54关键发现Adam优化器需要更小的初始学习率AdamW对权重衰减的处理更合理配合学习率预热(warmup)效果更好3.2 梯度裁剪的妙用即使调整了网络结构和优化器仍然偶尔会出现梯度爆炸。添加梯度裁剪后问题得到彻底解决optimizer torch.optim.AdamW(model.parameters(), lr0.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_norm1.0) optimizer.step()合适的裁剪阈值需要通过实验确定阈值训练稳定性最终性能无不稳定NaN5.0较稳定0.561.0很稳定0.530.1稳定但收敛慢0.554. 监控与调试用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 size4.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这种平滑处理能避免因单次波动导致的误判在实际应用中效果显著。