资讯动态

用Pytorch实现Titanic结构化数据建模:从数据清洗到模型训练完整指南

发布时间:2026/10/21 8:13:27 来源:尧图企业网站定制
1. 项目概述1.1 为什么用Pytorch做Titanic结构化数据建模Titanic生存预测几乎是每个接触机器学习的人都会碰到的第一个完整项目它数据量不大、字段清晰、目标明确非常适合用来打通“数据处理 → 特征工程 → 模型训练 → 评估预测”的完整链路。但大多数教程都是用sklearn或者XGBoost一带而过真正用Pytorch从零写一遍的人并不算多。用Pytorch来做这件事绝对不是炫技或者绕远路。结构化数据建模本质上是一个“把表格变成能喂给神经网络的张量”的过程这个过程在Pytorch里有非常清晰的对应关系DataLoader管理数据批次nn.Module构建网络损失函数和优化器驱动参数更新。当你想脱离调包侠的角色、真正理解一个模型是怎么学习的时候Pytorch会逼着你把每一步都想清楚。这套流程对于一个想深入深度学习、后续要处理更大规模数据的开发者来说是很好的一次“全链路基本功训练”。我用Pytorch重写过Titanic项目好几遍每次都有收获这次把完整的建模和训练过程整理出来供同样在这条路上摸索的人参考。Titanic包含了乘客的年龄、性别、船票等级、船舱、票价、登船港口等十几个字段目标是根据这些信息预测乘客是否生还。用Pytorch建模时你很快会遇到几个核心问题文本型字段怎么变成数值、缺失值怎么处理、类别字段用OneHot还是Embedding、网络结构怎么定、训练到什么程度算收敛。这些问题没有一个标准答案但每一步都踩过坑之后你对结构化数据建模的理解会上一个台阶。1.2 这个项目适合谁如果你已经会一点Python知道张量、梯度、反向传播这些基本概念但一直没找到一个合适的项目来实践Pytorch的完整流程那这个项目非常合适。它的数据量小训练速度快一次迭代只要几毫秒调参成本极低你完全可以在短时间内反复实验验证各种想法。如果你已经用sklearn做过Titanic觉得分类器“fit一下“就完事了想知道背后发生了什么强烈建议用Pytorch再写一遍。当你自己实现DataLoader、自己写训练循环、自己观察loss曲线变化的时候模型在你眼里就不再是一个黑盒。接下来我会按照我做这个项目时的实际操作顺序来写先讲整体思路和方案选型再拆解数据预处理、特征工程、模型构建、训练评估最后把踩过的坑和排查经验一并列出来。2. 整体设计与方案选型2.1 结构化数据建模在Pytorch中的技术路线结构化数据建模的技术路线依据核心数据对象的不同有本质差别。传统表格建模通常走“GBDT三件套”的路子先做特征工程然后丢给XGBoost、LightGBM或者CatBoost简单高效在中小规模表格数据上往往能拿到很强劲的效果。深度学习路线则有几种选择多层感知机、带Embedding的深度模型、TabNet这类专门为表格设计的网络以及把表格特征和Transformer结合的各种变体。考虑到Titanic数据集的大小只有几百条样本、十几个特征我最终选择的是“数据清洗 特征工程 多层感知机”这条最朴素的路线。原因很简单数据量太小模型一深就过拟合没必要上复杂结构其次是MLP足够直观训练过程中的每一步都容易解释。方案选型的另一个关键点是处理类别特征。在深度学习里类别字段最直观的做法是直接OneHot但这里有个小陷阱OneHot之后特征维度急剧膨胀在小数据集上容易把模型拖垮。也可以用Embedding把离散字段映射成低维稠密向量这个思路很优雅但对几百条数据来说Embedding层往往学不到什么有效的表征。我在实际测试中对比过Titanic这种数据量下OneHot和Embedding的最终精度差别极小但OneHot的调试成本明显更低所以后续内容以OneHot为主也会提一下Embedding方案的具体做法。2.2 工具选型与数据链路规划Pytorch做结构化数据的标准链路可以拆成四段数据读取、数据预处理、数据加载、模型训练。每一段都有多种实现方式我的选型如下环节推荐方案备选方案选型理由数据读取pandaspolars生态成熟资料丰富Titanic数据本身不大性能瓶颈不在读取预处理pandas numpysklearn Pipeline字段少、逻辑清晰直接手写更直观方便逐字段调试数据加载torch.utils.data.Dataset DataLoader直接切分张量Dataset机制是Pytorch标准做法后续切换数据集不用改业务代码特征编码OneHotEmbedding层OneHot简单直观配合标准化和合适的网络可以拿到可接受的精度训练框架手写训练循环Pytorch Lightning / Ignite手写循环能看清每一步发生了什么适合理解底层原理可视化matplotlibtensorboardmatplotlib画loss曲线足够用tensorboard对这种小项目来说偏重值得多说一句的是Dataset和DataLoader这套组合。很多初学者习惯直接pandas读进来、切分好、转成张量、一次喂给模型这在小项目里完全可行但接触真实项目时会遇到瓶颈。真实场景的数据通常无法全部载入内存必须靠Dataset按需读取、DataLoader自动打乱和分批这套机制越早熟悉越好。我在这个项目里没有偷懒坚持用Dataset封装数据后面扩展到大一点的数据集时几乎是无缝切换。2.3 为什么选择全连接网络的深层原因针对Titanic数据的特点来说字段之间大多是弱相关的独立信号比如性别与生存率明显相关、票价和船舱等级存在关联、年龄对结果也有一定影响但没有哪种复杂的局部模式需要卷积或者序列模型来捕捉。全连接网络每一层都在做特征的加权组合和非线性变换本质上就是在学习“哪些字段的组合能够预测生还”这和问题本身的结构是匹配的。在隐藏层层数上我踩过几次坑之后锁定了三层结构输入层 → 隐藏层1带ReLU和Dropout→ 隐藏层2 → 输出层。为什么不用更深的网络因为数据量太小了Titanic训练集只有七百多条样本五层以上的网络几乎必然过拟合训练集准确率能到95%以上验证集却一路下跌。在数据量不足的情况下浅层网络加上正则化手段反而能拿到更好的泛化效果。网络宽度的选择也值得聊一下。隐藏层宽度从32到128我都试过从结果看64个神经元的配置在验证集上的表现比较稳定。宽度太小模型容量不足学不到字段间的复杂关系宽度太大参数总量爆炸在小数据集上很容易”背下来“全部训练样本。3. 数据清洗与特征工程3.1 字段概览与缺失值处理策略Titanic的原始数据里字段大致可以分成四类数值连续型年龄、票价、数值离散型船舱等级、同代亲属数、异代亲属数、类别型性别、登船港口、船舱号、文本标识型姓名、乘客ID。真正能作为模型输入的特征需要从这些原始字段里筛选和转换。先做缺失值统计。Titanic训练集里Age字段缺失约20%Cabin字段缺失约77%Embarked字段只缺两条。不同的缺失情况和不同的处理方式Age连续字段缺失两成常用的思路是用中位数或均值填充。我用的是“按性别和船舱等级分组后取中位数填充”为什么这么加细因为当时的生存率资料里头等舱乘客的平均年龄明显高于三等舱女性和男性的年龄分布也不一样直接填全局中位数会抹掉这些结构信息。Cabin缺失太多直接作为类别特征填充会出现一个巨大的“缺失”类别意义不大。我选择把Cabin字段简化成一个布尔值有船舱号记为1没有记为0。这个新特征在验证集上确实带来了一点提升说明“是否分配了船舱”本身就是一个有信息量的信号。Embarked只缺两条直接用众数填充没有争议。Titanic的测试集在提交预测时没有真实标签所以需要将训练集和测试集一起进行预处理确保两个数据集的特征处理逻辑完全一致。我在代码里显式记录了填充用的统计量确保测试集用同一套统计量做变换避免信息泄漏。3.2 新特征构造与筛选思路特征工程说到底是“把业务理解转化成数字信号”的过程。Titanic里最经典的一个手工特征是FamilySize计算公式为同代亲属数加异代亲属数再加1乘客自己。直观理解是一个人出行可能更容易遇险时找不到同伴一家人出行则更可能互相照应但家族太大比如超过4人可能反而资源不足。我算了一下单人乘客和家族人数在2到4人的乘客生存率有明显差异所以把这个连续值保留了下来没有做分箱。还有其他几个值得尝试的特征组合比如从姓名中提取称谓Mr、Mrs、Miss、Master等这是某些高分方案里会用到的特征因为称谓隐含了年龄、婚姻状态、社会地位等信息。我在Pytorch版本里也试过这个特征能让验证集准确率有小幅提升但考虑到它和性别、年龄特征有较强共线性在追求简洁模型时不作为主特征保留。特征筛选的方法也很朴素先构造一批候选特征然后逐个加入模型看验证集的变化。这种“前向选择”在小数据集上操作成本极低因为每次训练只要几秒钟。我最终选定的特征集包括性别、船舱等级、年龄、票价、同代亲属数、异代亲属数、家庭规模、是否有船舱号、登船港口总共10到12个输入维度。3.3 标准化与编码的配合细节Pytorch模型对输入特征的尺度很敏感这一点容易被新手忽略。年龄和票价广大比如票价范围0到512性别OneHot之后只有0和1。如果不做标准化模型梯度更新会被大数值特征主导小数值特征几乎学不到东西。常用的解决方法是Z-score标准化把每个连续特征减去均值再除以标准差。这里有个Pytorch特有的细节要在训练集上计算均值和标准差然后用同样的参数去标准化验证集和测试集。我在第一版代码里不小心对整个数据集做了标准化再切分导致验证集信息泄漏验证精度虚高后来重新调整才解决。具体做法是先用训练集的均值和标准差保存下来然后统一应用到所有数据集。对于OneHot编码后的类别字段不需要再做标准化因为它们本身就是0/1取值尺度已经统一了。所以整体处理流程是类别字段先OneHot编码数值字段先填充缺失值再标准化。4. Pytorch数据管线搭建4.1 Dataset类的封装技巧Pytorch的Dataset类实际上就是在定义“如何用一个索引取出一条样本”。对Titanic这个项目来说数据已经全部在内存里了不需要做文件I/O所以Dataset类实现起来很简单构造时传入特征矩阵和目标向量__len__返回样本数量__getitem__返回第i条样本的张量对。一个值得留意的细节是数据类型的统一。pandas读进来的数据大概率是float64而Pytorch默认浮点类型是float32如果不转换会在训练时报类型不匹配的错误。我在计算特征矩阵的时候就统一用astype(np.float32)处理了。标签列需要转换为float32而不是int64因为后面要计算BCE损失BCELoss要求标签也是浮点类型。我自己封装Dataset时还有一个习惯把特征列名也保存下来在调试时可以清楚地知道每个维度代表什么字段。等模型效果不理想需要回头看特征时这个信息非常有用。4.2 DataLoader的参数配置与实践DataLoader的在用关键在于batch size的选择和shuffle的设置。Titanic全量训练集也就七百多条样本batch size设小一点能让梯度更新更频繁模型更容易逃离局部最优设太大每个batch样本太少、梯度噪声过大。我测试了几次后锁定了batch size为32这个值是平衡训练速度和稳定性的常见选择。shuffle这个参数在训练集上必须设为True否则每轮epoch内样本顺序固定模型的梯度更新会带有顺序偏差收敛效果变差。验证集则不应该shuffle因为验证时不需要梯度更新保持原始顺序让评估结果可复现。我在验证集上用了一个比较大的batch size比如全量验证集一起推这样计算指标时省去了遍历的麻烦。4.3 类别特征的OneHot与Embedding两种路线OneHot是最直接的编码方式。性别转成两列登船港口转成三列船舱等级转成三列。用pandas的get_dummies很容易操作但要注意get_dummies对训练集和测试集可能产生不同的列比如测试集里恰好缺少某个类别的样本就会少一列。解决办法是训练集和测试集一起做get_dummies或者用reindex对齐列。Embedding是另一种思路。先把类别字段转成连续的整数索引然后在模型的第一层嵌一个nn.Embedding层让模型自己学习每个类别的向量表示。这个方案对数据量大的场景更友好但Titanic这种小数据量学出来的Embedding向量往往会过拟合。我做过对比实验在验证集上OneHot的表现比Embedding略好或持平而且Embedding的调试成本更高所以在最终版本选了OneHot。5. 模型构建与训练细节5.1 网络结构设计与参数初始化我最终采用的网络结构如下import torch import torch.nn as nn class TitanicMLP(nn.Module): def __init__(self, input_dim): super(TitanicMLP, self).__init__() self.net nn.Sequential( nn.Linear(input_dim, 64), nn.BatchNorm1d(64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, 32), nn.BatchNorm1d(32), nn.ReLU(), nn.Dropout(0.2), nn.Linear(32, 1) ) def forward(self, x): return self.net(x)这里有几个设计考量。第一层用64个神经元是考虑到输入本身的维度不高过大的宽度会导致参数冗余。BatchNorm1d在这里的作用是稳定训练过程它会把每层的激活值拉回到合适的范围对收敛速度帮助很大。Dropout则是对抗过拟合的主要手段我分别在两个隐藏层配置了0.3和0.2的丢弃率这个比例是调出来的——太高模型学不动太低起不到正则化作用。输出层只有1个神经元不带激活函数。原因是要配合BCEWithLogitsLoss这个损失函数内部已经集成了Sigmoid和二元交叉熵比分开写BCELoss加Sigmoid数值上更稳定也更推荐。初始化策略不复杂直接沿用Pytorch的默认初始化。在小网络上默认初始化通常够用真正值得注意的是BatchNorm和Dropout在训练和评估模式之间的切换这个等一下会在训练循环里讲到。5.2 损失函数与优化器选型二分类问题的标准损失函数是二元交叉熵。BCEWithLogitsLoss是首选它把Sigmoid和交叉熵合并成一步计算既节省了内存也避免了Sigmoid后取log可能出现的数值稳定性问题。我在调试时会额外计算训练集和验证集上的准确率但loss仍然是监控收敛的唯一指标。优化器选的是Adam学习率设为0.001。Adam的优势是对学习率的敏感度低于SGD自带自适应调整机制对新手比较友好。但要注意Adam不等于不需要调学习率0.001是常见起点如果loss曲线震荡得很厉害需要调低如果收敛太慢可以适当调高。在一些版本里我会加一个学习率调度器比如验证集loss连续几个epoch不下降就降低学习率。这个小技巧对这个项目有实际意义——当模型接近收敛时固定学习率容易在最优解附近来回震荡降低学习率可以让参数落得更精准。5.3 训练循环的标准写法与坑点训练循环看起来不复杂但细节决定成败。我整理了一份在实战中验证过的标准写法def train_one_epoch(model, dataloader, optimizer, criterion): model.train() total_loss 0.0 correct 0 total 0 for features, labels in dataloader: optimizer.zero_grad() logits model(features).squeeze(1) loss criterion(logits, labels) loss.backward() optimizer.step() total_loss loss.item() * features.size(0) preds (torch.sigmoid(logits) 0.5).float() correct (preds labels).sum().item() total labels.size(0) return total_loss / total, correct / total几个容易被忽略的细节optimizer.zero_grad()必须在每个batch开始前调用否则梯度会累加model.train()和model.eval()的切换很重要因为BatchNorm和Dropout在两种模式下行为不同推理阶段要用torch.no_grad()包裹防止计算图占用内存。验证循环的写法有一个常见误区验证阶段仍然需要算loss但不调用backward()和step()。验证集的作用是模拟测试环境观测模型是否过拟合。我还会额外计算验证集准确率但对早停的判断通常只以验证loss为准因为准确率在小数据集上波动大不如loss平滑。5.4 早停与模型保存策略在小数据集上训练过拟合几乎是必然发生的只是时间早晚的问题。对付这个问题最有效的手段就是早停每个epoch结束计算验证集loss如果连续n个epoch都没有刷新最低值就停止训练并回滚到最优那轮的参数。保存模型的正确做法是保存状态字典而不是整个模型对象。torch.save(model.state_dict(), best_model.pt)保存的是参数后续加载时先初始化一个相同结构的模型再load_state_dict即可。保存整个模型对象虽然方便但会在代码重构、模型结构变更时埋下隐患不建议这样做。6. 常见问题与排查技巧实录6.1 验证集准确率一直上不去的排查步骤如果模型训练了足够多的epoch训练集准确率很高但验证集准确率停留在0.7以下要分几步排查。先看数据是不是出了问题。Titanic的标签分布本身是偏向“未生还”的生还率大约38%所以随便预测全部为0准确率也有62%。如果你的模型准确率连这个基线都没明显超过先检查有没有特征泄露、标签有没有对齐、DataLoader有没有shuffle出问题。其次是检查标准化和OneHot有没有对齐。最常见的坑就是训练集和测试集分别做了标准化统计量不一致导致验证集在进入模型时分布已经完全错位。这种问题不会报错只会安静地拉低精度排查成本很高。第三是检查训练循环里的模式切换。如果忘了调用model.eval()BatchNorm会继续用当前batch的统计量Dropout也会继续随机丢弃验证结果就会忽高忽低不确定性拉满。最后是检查网络是不是太浅或者太深。太浅学不到模式太深在小数据上过拟合两个极端在指标上的表现可能相似但通过观察训练集和验证集loss曲线可以区分。6.2 loss震荡与不收敛的常见原因loss曲线像心电图一样剧烈震荡时优先怀疑学习率过大。处理办法是把学习率降一个数量级后重新训练看曲线是否变得平滑。如果降低学习率后曲线平稳但收敛慢再考虑加学习率调度器。还有一个容易被忽略的因素是数据的全局顺序。如果shuffle设为FalseReproducibility会受影响模型训练结果会受数据原始顺序影响。这个问题在复现别人的实验时最容易暴露——明明用的同一份数据训练集和验证集顺序不同结果可能完全对不上。6.3 复现性设置与随机种子管理Pytorch的随机性来源有三个模型参数初始化的随机种子、DataLoader打乱数据的随机种子、GPU上的CUDA随机种子。想要完全复现实验需要显式设置所有随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)这个函数写好后在训练脚本开头调用一次。在同一个项目中不同实验之间保持随机种子一致才能公平对比不同设置的效果。否则你以为是在对比模型结构其实是在对比随机运气。6.4 特征与参数的调参心得做Pytorch版Titanic的一个额外好处是可以顺带体验“调参”的完整循环。我在做对比实验时遵循一个原则一次只改一个变量。比如固定网络结构和学习率只对比特征集A和特征集B的差别固定特征和学习率只对比有Dropout和没有Dropout的差别。这样每一轮实验的结论都是干净的可归因的。另一个经验是先用小规模快速验证想法。我通常只训练50个epoch就停下来看趋势不追求完全收敛。如果50轮内验证集准确率都没有明显超过基线这个改动大概率不成立可以直接放弃不必浪费时间训练几百轮。7. 训练结果分析与扩展思路7.1 训练曲线的解读方法训练完成后我习惯同时画出训练集和验证集的loss曲线来判断模型的健康程度。两组曲线同时下降且差距不大说明模型处于健康状态训练loss降得很低而验证loss反弹说明过拟合需要增强正则化两组loss都停在比较高的位置说明模型容量不足或者学习率太低。Titanic这个规模的数据每次训练只需要几十秒非常适合反复做这种诊断实验。我实际训练时记录了每一轮的loss和准确率方便随时复盘。在自己的脚本里加上“每隔n个epoch打印一次”的日志逻辑是保证实验可追溯的简单且有效的手段。7.2 从Titanic到更大规模结构化数据项目的迁移思路把这个项目的经验迁移到更大的结构化数据项目时有几个可复用的模式。Pytorch的Dataset接口封装得足够好换数据集时只需要改特征处理的部分数据加载和训练的代码甚至可以完全复用。大规模数据下特征处理建议用scikit-learn的ColumnTransformer统一管理比手写pandas流水线更清晰。模型结构也建议保持“输入层→隐藏层→Dropout→输出层”的基础骨架根据数据规模调整宽度和深度。针对更复杂的结构化数据可以考虑引入更高级的特征组合方式。例如通过Target Encoding或Frequency Encoding捕捉高基数类别特征中的模式。在具备一定数据量的前提下将类别通过Embedding嵌入模型也是一种非常有效的手段。7.3 后续可以尝试的进阶方向这个项目做完之后如果想继续深入有几个方向都有价值。可以试试加入K折交叉验证在Titanic这个小数据集上K折得到的评估分数比单次划分稳定得多。可以尝试把OneHot换成Embedding比较在相同数据条件下两条路径的表现差异。也可以试着给不同特征分组比如连续特征走一个分支、离散特征走一个分支在模型中融合。结合个人实际经验来看用Pytorch重写Titanic最有价值的收获不是最终准确率比sklearn高了多少而是完整理解了建模链条上每一个环节为什么存在。你在调DataLoader、调BatchNorm、调loss计算过程中获得的感觉才是后续从事相关工作真正用得上的东西。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑