简介面向二手车价格评估场景的深度学习预测工程提供从数据处理到模型训练、预测输出的完整实现。压缩包共20个文件以Python源码为核心辅以CSV数据集、ZIP压缩数据、XML配置以及Git忽略规则、开源许可证等工程文件整体约99.6MB。项目目前已有133人浏览学习适合具备一定Python基础、想了解回归预测建模流程的开发者和数据爱好者参考。代码覆盖数据清洗、特征权重混合、网络结构搭建、训练与验证等关键环节并附有二手车训练集与测试集数据目录中另有项目配置信息、说明文档与依赖资源便于快速理解工程结构。基于这套完整工程可以进一步针对真实交易数据做调参、迁移或二次开发也有利于对比传统人工估价与深度学习自动化评估的差异。1. 破局二手车价格预测这份深度学习源码能帮你落地什么二手车评估这个事传统干法是靠老师傅的经验你问三个车商可能给你三个价主观性太强很难稳定复现。这套基于深度学习的二手车价格预测模型设计源码正好把整个流程拆成了数据预处理、模型搭建、训练评估三个模块主干就四个 Python 文件main.py、model.py、utlis.py、basline.py配上一套二手车交易数据的 CSV 训练集和测试集从数据清洗直接跑到预测结果输出每一步都不是黑匣子。核心价值在于给你一套能改、能重训、能出预测结果的代码骨架而不是一个只能看不能动的演示工程。适合两类人一类是刚接触深度学习、想拿表格回归练手的另一类是手里有二手车交易数据、想快速搭一个价格评估工具的业务开发者。读完这篇你能搞懂每个文件的分工、训练流程的参数设置以及几个不跑一遍根本发现不了的坑。2. 先把数据收拾利索缺失值、异常值与特征编码的实操顺序2.1 先认识数据训练集与测试集 B 的边界在哪打开data目录两个核心文件是used_car_train_20200313.csv和used_car_testB_20200421.csv。从命名看前者是训练集后者是测试集 B。训练集里带price标签列测试集 B 没有需要模型去预测。第一次上手别急着写模型先把两个文件的字段对齐看一遍。常见字段包括SaleID、regDate、name、model、brand这类身份和车型信息bodyType、fuelType、gearbox这类配置信息power、kilometer、notRepairedDamage这类车况信息以及v_0到v_14这一组匿名的统计特征具体列名以实际 CSV 为准。import pandas as pd train pd.read_csv(data/used_car_train_20200313.csv, encodingutf-8) test pd.read_csv(data/used_car_testB_20200421.csv, encodingutf-8) print(训练集 shape:, train.shape) print(测试集 shape:, test.shape) print(训练集列名:, train.columns.tolist()) print(训练集 price 分布:\n, train[price].describe())这个读取操作有两个细节要注意。第一是编码这类公开数据偶尔出现 GBK 编码报UnicodeDecodeError时把encoding换成gbk再试第二是内存如果机器配置低读取时可以只选需要的列或指定dtype来压缩类型。训练集和测试集 B 的边界要时刻记住测试集只在最终预测时碰一次预处理阶段就混进来后面会有泄漏问题这一点第 5 章会展开。2.2 缺失值与异常值先统计再决定填还是删拿到数据第一件事不是建模是看缺失和异常。二手车数据里最常见的缺失集中在notRepairedDamage这个字段表示是否有未修复损伤缺失值往往意味着车主没填占比高时直接删掉列更省事占比低时用众数填充。另一个重灾区是power功率0 值通常是数据录入异常发动机功率不可能是 0这种要按异常值处理而不是当作有效数值。missing_ratio train.isnull().mean().sort_values(ascendingFalse) print(缺失率 Top10:\n, missing_ratio[missing_ratio 0]) # 策略1缺失率超过30%的列直接删除 high_missing_cols missing_ratio[missing_ratio 0.3].index.tolist() train train.drop(columnshigh_missing_cols) # 策略2低缺失率列用中位数填充 for col in [notRepairedDamage]: if col in train.columns: train[col] train[col].fillna(train[col].mode()[0]) # 异常值power为0的样本单独处理 print(power为0的数量:, (train[power] 0).sum())填充策略不是越复杂越好表格回归任务里中位数和众数通常够用。我一般会把缺失率 30% 作为删列的阈值低于这个值的用中位数填充数值列、众数填充类别列。power为 0 的样本如果占比小直接删除占比大可以考虑用同model车型的功率中位数替换。处理完记得把同样的操作应用到测试集 B但要基于训练集统计出的参数去填不能各填各的。2.3 类别字段编码与数值特征归一化二手车数据里brand、bodyType、fuelType、gearbox都是类别特征。深度学习模型不像树模型那样天然处理类别需要先编码。brand有几十个取值直接 one-hot 会让特征维度爆掉对 MLP 来说反而增加过拟合风险。常见做法是类别特征做 label encoding 或目标编码数值特征做标准化或归一化。from sklearn.preprocessing import LabelEncoder, StandardScaler # 类别特征标签编码 cat_cols [brand, bodyType, fuelType, gearbox, model] encoders {} for col in cat_cols: if col in train.columns: le LabelEncoder() train[col] le.fit_transform(train[col].astype(str)) encoders[col] le # 数值特征标准化只fit训练集 num_cols [power, kilometer] [c for c in train.columns if c.startswith(v_)] scaler StandardScaler() train[num_cols] scaler.fit_transform(train[num_cols])这套代码的关键点在最后一步scaler只在训练集上fit_transform测试集直接用训练好的scaler.transform不能重新 fit。这个坑非常隐蔽后面专门讲。LabelEncoder同理每个编码器要存下来预测阶段对测试集做同样的transform。特征处理顺序建议先编码再标准化数值列和类别列分开处理避免把编码后的整数当连续值做标准化。2.4 weight_mix.csv 到底是干什么的仓库里有个weight_mix.csv很多人第一次看不知道是干嘛的。按命名和常见项目结构推断它存的是多模型或多折预测结果的加权系数也可能存的是特征权重的混合配置具体要看utlis.py里加载它的逻辑。我见过不少项目用这种文件来记录模型融合的权重训练完多个模型后把每个模型的预测结果按这个权重做线性混合得到最终价格。import pandas as pd weight_mix pd.read_csv(weight_mix.csv) print(weight_mix 内容:\n, weight_mix) # 假设是三折预测结果按权重加权 preds_fold1 ... preds_fold2 ... preds_fold3 ... final_pred ( weight_mix.loc[0, w1] * preds_fold1 weight_mix.loc[0, w2] * preds_fold2 weight_mix.loc[0, w3] * preds_fold3 )这个文件的价值在于把融合权重从代码里抽出来方便调参时只改 CSV 不动代码。跑实验时我会维护一组权重记录每次调完融合比例就更新 CSV避免代码里写死一堆魔法数字。如果你打开发现里面是特征名和权重的两列结构那它就是特征权重配合basline.py做特征筛选用。两种用途都合理以仓库内实际文件结构为准。3. 模型设计的主干basline.py、model.py 与 main.py 各管哪一段3.1 basline.py先给模型立个及格线很多初学者一上来就堆深度网络结果训练半天跑不过一个线性回归问题就出在没有基线参照。basline.py这个文件的存在意义就是先跑一个简单的模型给出一个最基础的评估分数后面深度学习模型的所有努力都要拿来和这个分数对比。我习惯先用线性回归或者浅层决策树跑通数据流程确认数据没有泄漏、特征编码没报错再上深度模型。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error from sklearn.model_selection import train_test_split baseline_model LinearRegression() X train.drop(columns[price, SaleID]) y train[price] X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42 ) baseline_model.fit(X_train, y_train) val_pred baseline_model.predict(X_val) print(LinearRegression MAE:, mean_absolute_error(y_val, val_pred))这里我给的是线性回归示意仓库里basline.py可能用的是树模型或者更复杂的结构但目的是同一个用最少的调参成本跑出一个及格分数。选择mean_absolute_error作为指标是因为二手车价格预测这类任务绝对误差比均方误差更直观而且对异常大额车没那么敏感。跑通之后把分数记下来这就是后面深度模型的 PK 基准。3.2 model.py 里的网络结构表格数据最常用的 MLPmodel.py是模型定义的核心文件。二手车价格预测是典型的表格回归任务输入是编码和标准化后的特征向量输出是一个连续价格值。这种任务最常见的结构就是多层感知机 MLP几层全连接加 ReLU 激活最后输出层一个神经元不加激活直接输出价格。虽然现在有很多花哨的结构但表格数据上 MLP 依然是性价比最高的起点这也是我从model.py这类文件里最常看到的实现套路。import torch import torch.nn as nn class PricePredictor(nn.Module): def __init__(self, input_dim, hidden_dims[256, 128, 64], dropout0.3): super().__init__() layers [] prev_dim input_dim for h_dim in hidden_dims: layers.append(nn.Linear(prev_dim, h_dim)) layers.append(nn.BatchNorm1d(h_dim)) layers.append(nn.ReLU()) layers.append(nn.Dropout(dropout)) prev_dim h_dim layers.append(nn.Linear(prev_dim, 1)) self.net nn.Sequential(*layers) def forward(self, x): return self.net(x).squeeze(-1)网络结构有几个设计要点。第一隐藏层维度从 256 开始逐步递减让特征逐层压缩最后一层输出单个价格值第二每层全连接后面跟 BatchNorm表格数据特征尺度差异大BatchNorm 能稳定训练第三Dropout 设置在 0.3 左右防止小数据集上过拟合。输出层不加激活函数因为价格是连续值加 ReLU 反而会截断负区间。训练时损失函数选择MSELoss或L1Loss前者对大误差惩罚更狠后者更稳健具体选哪个可以两个都试。3.3 main.py训练主循环与验证集划分main.py是整个项目的入口把数据读取、预处理、模型构建、训练评估串在一起。从utlis.cpython-311.pyc这个文件能看出运行环境是 Python 3.11跑之前建议先读一下readme.txt里面有环境依赖和运行方式的说明。在训练之前要先把数据集切出验证集回归任务不能直接stratify按类别分层通常是直接随机切分但随机种子必须固定。from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, shuffleTrue ) model PricePredictor(input_dimX_train.shape[1]) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.L1Loss() for epoch in range(100): model.train() for batch_X, batch_y in train_loader: optimizer.zero_grad() pred model(batch_X) loss criterion(pred, batch_y) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_pred model(val_X) val_mae nn.L1Loss()(val_pred, val_y).item() print(fEpoch {epoch}, Loss: {loss.item():.4f}, Val MAE: {val_mae:.4f})验证集的作用有两个一是看模型的泛化能力二是用来做早停。每次 epoch 结束记录验证集 MAE连续多个 epoch 不下降就停止训练保存最优模型权重。这里random_state42要全局统一用同一个值shuffleTrue保证样本顺序不引入偏置。训练轮次不建议死磕一个固定值100 只是个上限配合早停才能拿到最优模型。4. 训练与调参把验证集误差压下去的关键步骤4.1 数据划分与随机种子先定规矩再调参模型跑不通很多时候不是模型问题是数据划分乱。训练集、验证集要一次性切好验证集在整个调参过程中只能用来评估不能参与训练。回归任务里验证集比例一般取 10% 到 20%数据量大可以少留一点几万条样本 20% 完全没问题。随机种子要同时固定 Python、NumPy 和 PyTorch 三个层面不然每次划分结果都不同实验对比就没意义了。import random import numpy as np import torch SEED 42 random.seed(SEED) np.random.seed(SEED) torch.manual_seed(SEED) torch.cuda.manual_seed_all(SEED)这段代码调参前先跑一遍把种子钉死。我见过有人调了半天学习率最后发现是随机种子没固定导致验证集划分每次都变同一个参数跑两次结果不一样还以为是模型不稳定。固定种子之后再调参每次改动才有可比性。4.2 优化器、学习率与 batch size 的搭配表格数据场景下Adam 优化器是默认选择学习率从1e-3起步batch size 常见是 128 或 256。这三个参数的搭配有固定套路batch size 越大梯度越平滑学习率可以适当调大batch size 小噪声大学习率要保守一些。跑起来之后如果损失震荡得厉害优先把学习率降到3e-4或1e-4而不是动网络结构。optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5, verboseTrue ) # 每个epoch结束 scheduler.step(val_mae)weight_decay设置1e-5做 L2 正则配合ReduceLROnPlateau动态降学习率验证集 MAE 连续 5 个 epoch 不降低学习率减半。这套组合比固定学习率硬跑效果好很多。batch size 如果显存紧张就降半但要注意降 batch size 后学习率也要跟着降。4.3 输出与标签的归一化对齐这个坑几乎每个新手都会踩一次。训练前如果对price做了标准化或者 log 变换模型输出的就是变换空间里的预测值必须用训练时记录的变换参数还原回来才能得到真实价格。很多人模型训练没问题一提交成绩惨不忍睹就是这里没对齐。from sklearn.preprocessing import MinMaxScaler price_scaler MinMaxScaler() y_train_scaled price_scaler.fit_transform(y_train.values.reshape(-1, 1)) # 训练用 y_train_scaled # 预测时反变换 val_pred_original price_scaler.inverse_transform(val_pred)MinMaxScaler会把价格压缩到 0 到 1 区间训练时 loss 会显得很小但别高兴太早那是缩放后的误差。预测完必须inverse_transform还原。我一般会把price_scaler和特征scaler一起保存下来预测脚本里直接joblib.load加载避免每次重新 fit。4.4 训练过程怎么盯loss 曲线和验证集 MAE训练不是把代码跑起来就完事要实时看两个信号训练 loss 和验证 MAE。训练 loss 下降但验证 MAE 不动是过拟合信号加重 Dropout 或减少模型容量两个都不降先查学习率是不是太小再看数据预处理有没有问题。我习惯把每个 epoch 的指标打印出来或者用 tensorboard 记录跑完直接看图。验证集 MAE 这个数字是对外汇报的硬指标。注意验证集和测试集 B 的数据分布不一定完全一致验证集表现好不代表线上好所以第 6 章还要讲如何做多折交叉验证和结果融合减少这种不确定性。5. 避坑指南五个容易让训练翻车的实际操作细节5.1 数据泄漏测试集 B 混进训练流程现象验证集 MAE 非常漂亮比如只有几百但把预测结果提交或拿测试集 B 一算误差大得离谱。原因数据预处理阶段把训练集和测试集 B 放在一起做了填充、编码或标准化。缺失值填充用的众数、LabelEncoder 的类别映射、StandardScaler 的均值和方差都包含了测试集 B 的信息等于模型提前偷看了答案。解决所有的预处理参数只在训练集上拟合测试集 B 只做 transform。具体做法是把数据读取、预处理封装成函数先fit训练集再拿着训练好的对象去transform测试集 B。检查方法很简单训练集和测试集 B 绝不共用同一个fit_transform调用。5.2 归一化参数没复用预测阶段重新 fit 了一次现象模型训练正常但预测价格整体偏小或偏大差一个固定倍数。原因预测脚本里对测试集 B 的特征重新做了fit_transform而训练时用的是另一套标准化参数。特征分布一变模型输入分布和训练时不匹配输出自然偏。解决把训练阶段得到的scaler和encoders用joblib.dump保存成文件预测脚本里joblib.load加载后直接transform。不要偷懒在预测脚本里重新写一遍预处理那样极容易写岔。验证方法预测前打印测试集特征的均值和方差确认和训练集标准化后的分布一致。5.3 价格右偏分布没处理大额车误差把整体 MAE 拉爆现象模型对几万块的车预测得不错对几十万上百万的车预测误差巨大整体 MAE 卡在某一个值上不去。原因二手车价格呈长尾分布少量大额车主导了 MSE 或 MAE 的数值。模型为了降低整体误差会把预测往中位数方向收缩导致大额车被系统性低估。解决对标签做log1p变换再训练把右偏分布拉成近似正态预测完用expm1还原。这是表格回归里非常成熟的做法基本不需要额外成本。如果用了MinMaxScaler建议换成log1p效果通常更好。5.4 weight_mix.csv 改完没重新加载权重改了结果不变现象修改了weight_mix.csv里的融合权重重新跑预测脚本输出结果和修改前一模一样。原因脚本启动时只读一次weight_mix.csv后续计算用的都是缓存在内存里的旧值或者脚本里根本没有读取这个文件权重写死在代码里。解决改完 CSV 后确认重启了 Python 进程不是在交互环境里继续跑。如果确认加载了还是不变grep 一下weight_mix在代码里的出现位置看是不是数据处理阶段用了它而预测阶段压根没用到。这种权重文件最容易出现“改了但没生效”的假象。5.5pycache里的 pyc 缓存干扰改了代码不生效现象修改了utlis.py里的某个函数重新运行main.py行为还是旧代码的表现。原因Python 运行时会缓存.pyc字节码文件__pycache__目录下的旧缓存没失效导致解释器加载了旧代码。换 IDE 或迁移项目后这个坑尤其常见.idea目录还在但虚拟环境路径变了。解决删掉__pycache__目录重新跑或者命令行用python -B main.py禁用字节码缓存。顺手看一眼.gitignore里有没有把__pycache__和.pyc忽略掉没有就补上避免缓存文件混进版本控制。6. 验证与集成把预测结果从“能跑”变成“能交差”6.1 离线评估与提交格式对齐验证集 MAE 是模型调参的指南针但最终预测结果能不能用要看输出格式对不对。测试集 B 没有price标签预测脚本输出的应该是一列SaleID对应一列price行数和测试集 B 完全一致。提交前把训练时对标签做的log1p、MinMaxScaler等逆运算全部还原生成真实价格。别小看这一步格式错一位整个模型就白跑了。6.2 多模型加权融合与 weight_mix.csv 的配合训练多个模型再加权融合是表格数据比赛里最稳定的提分手段。weight_mix.csv在仓库里的作用就在这把不同模型的预测结果按权重混合。权重怎么定可以用验证集效果做参考也可以用简单的网格搜索试几个组合选验证集 MAE 最低的那个。import pandas as pd weight_mix pd.read_csv(weight_mix.csv) weights weight_mix.loc[0].values # 假设一行就是一组权重 pred_mlp ... # 深度学习模型预测结果 pred_baseline ... # 基线模型预测结果 final_pred weights[0] * pred_mlp weights[1] * pred_baseline submission pd.DataFrame({ SaleID: test[SaleID], price: final_pred }) submission.to_csv(submission.csv, indexFalse) print(预测结果已保存共, len(submission), 条)权重一般不设成平均而是偏向表现更好的模型。比如 MLP 验证 MAE 明显优于基线权重可以给 0.7 比 0.3。融合有两个注意点一是参与融合的模型要在同一套验证集上评估过不能各用各的划分二是真实价格先还原再加权不要在 log 空间直接融合不同模型对异常值的敏感度不同还原后再平均更稳。我吃过一次亏直接在 log 空间做加权结果两个模型在同一个大额车上都低估融合后误差反而被放大从那以后我每次都强制走一遍“先还原、再融合、最后看一眼 top 10 大额车的误差”这个流程。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?