尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
二手车价格预测实战:特征工程与树模型融合避坑指南
简介天池二手车价格预测竞赛高分项目源码包适合机器学习入门者、竞赛新手及毕业设计/期末大作业场景覆盖从数据探索、特征工程到模型调参与结果提交的完整竞赛链路。资源共16个文件包含5个CSV数据文件训练集、测试集、提交样例与最终submission结果、2个IPython Notebook分别对应LightGBM与XGBoost的建模全流程代码、项目说明README以及XML/IML等工程配置辅助文件压缩包约32MB数据与代码齐全解压即可直接运行。已有829人学习。项目以两种梯度提升树算法为核心对特征进行构造、清洗与筛选并结合网格搜索/交叉验证等调参手段优化预测精度Notebook中保留分步骤实验记录便于逐行对照理解参数变化与效果差异。读者可依据源码与说明快速复现高分方案也能将特征工程和集成模型思路迁移到同类价格回归任务非常适合作为竞赛实践、毕业设计或期末大作业的参考资料。1. 二手车价格预测赛题先搞懂特征和MAE再谈跑模型天池竞赛里的二手车价格预测是典型的表格回归赛题给你一批包含车辆注册日期、里程、功率、品牌车型和十余个匿名数值特征的数据让你预测交易价格官方用 MAE 打分。这个赛题适合练手是因为数据规模在十万级、特征类型覆盖数值/类别/时间/匿名四类刚好踩中大多数业务模型的常见场景。一份二手车价格预测的项目源码和项目说明里值钱的部分基本就集中在这三件事数据 EDA、特征工程、多模型融合。它适合第一次参加天池比赛的新手也适合做过 yolov8 训练自己的数据集这类视觉项目、想补表格模型经验的人读完照着复现一套稳定进前排的基线是完全可行的。2. 数据集 EDA先用 pandas 摸清特征脾气别急着训练拿到任何一份二手车价格预测的数据集我习惯先花三十分钟做 EDA 而不是直接跑模型。原因是树模型跑基线很快但特征理解不够的话后面每个特征工程决定都是盲目的返工成本远高于这半小时。这个赛题的数据列大致包含 SaleID、regDate、creatDate、name、model、brand、bodyType、fuelType、gearbox、power、kilometer、notRepairedDamage、regionCode、seller、offerType以及 v_0 到 v_14 十五个匿名数值特征标签是 price。搞清楚每一列的取值逻辑后面所有特征工程才有依据。2.1 先看 shape、dtypes 和缺失量确定特征类型再动手import pandas as pd train pd.read_csv(train.csv) test pd.read_csv(test.csv) print(train.shape, test.shape) # 确认训练/测试规模 print(train.columns.tolist()) # 列出全部列名 print(train.dtypes.value_counts()) # 看数值列和类别列的比例这段代码解决三个问题数据量级、列结构是否一致、特征类型分布。天池这套赛题里 train/test 的列结构基本一致差在 test 没有 price。先确认 dtypes是为了决定哪些列走 category 处理、哪些走数值填充避免后面统一 fillna 把类别列填出错误语义。missing train.isnull().sum() missing missing[missing 0].sort_values(ascendingFalse) print(missing) cat_cols train.select_dtypes(object).columns nuniq train[cat_cols].nunique().sort_values(ascendingFalse) print(nuniq)缺失量要分两类看纯数值列如 power、kilometer 的缺失以及类别列如 bodyType、fuelType、gearbox 的缺失。notRepairedDamage 里有大量字符串 -读进来会被当成 object 类型它本质是二值特征后面要单独映射成 0/1 而不是直接 fillna。nunique 输出里 name 和 regionCode 会达到数千甚至更高的基数这个数字直接决定了不能用 one-hot也决定了目标编码是必选项。跟目标检测数据集先核对标注框不同表格数据集 EDA 的第一件事是核对类型和唯一值。2.2 重点看 price、power、kilometer 三列的分布异常值藏在描述统计里import matplotlib.pyplot as plt print(train[price].describe()) train[price].hist(bins100) plt.show()price 是回归目标它的分布决定了训练空间。做这个赛题时我会先打印 describe再看直方图。价格通常是右偏的头部存在几十万以上的高价样本而 MAE 对这种高位样本非常敏感预测偏一点就会把误差拉大。这也是后面要做 log 空间训练的直接原因但现在只是观察不改数据。print(train[power].describe()) print(train[kilometer].describe()) print(train[notRepairedDamage].value_counts(dropnaFalse))power 的最小值如果是 0说明存在缺省值被填成了 0这类样本对模型是干扰项但直接删又可能丢掉信息常见做法是先统计占比再决定。kilometer 最小值 0 也要单独看——有的是真实新车、有的是异常录入不能一刀切。notRepairedDamage 里的 - 要统计出来映射成 0/1/缺失三种状态而不是让 pandas 当成字符串丢给模型。这个阶段只做记录和标记真正处理放到特征工程章节。2.3 匿名特征 v_0 到 v_14先当黑匣子用别急着解读v_cols [c for c in train.columns if c.startswith(v_)] print(train[v_cols].isnull().sum().sum()) print(train[v_cols].min().min(), train[v_cols].max().max())v_0 到 v_14 是脱敏后的匿名数值特征EDA 阶段我只检查两件事有没有缺失、有没有极端离群值。很多项目源码里会给这十五个特征做 PCA、聚类甚至可视化降维但我个人经验是收益很不稳定。与其花时间猜它是什么业务含义不如先原样丢给 LightGBM让它用 feature importance 告诉我们哪些有效。这十五列本身就是强特征在多数高分方案里重要性排名靠前处理时只做缺失填充和类型压缩不做手工变换。3. 特征工程把高基数类别和时间戳变成模型容易学的强特征特征工程是二手车价格预测项目源码里最值得读的部分。纯数值特征和匿名特征其实没什么可做的真正的提升来自高基数类别特征和时间特征的处理方式。这一章按我自己的处理顺序来先对类别特征做目标编码再从注册日期和发布时间里挖车龄最后用一次快速训练做特征减法顺便把内存压下来。3.1 高基数类别特征用平滑目标编码替代 one-hotdef te_base(train, test, col, target, alpha5): 基础版平滑目标编码alpha 控制平滑强度 mean_all target.mean() grouped target.groupby(train[col]).agg([mean, count]) encoding (grouped[mean] * grouped[count] mean_all * alpha) / (grouped[count] alpha) tr_enc train[col].map(encoding).fillna(mean_all) te_enc test[col].map(encoding).fillna(mean_all) return tr_enc, te_enc for col in [brand, model, name, regionCode]: train[f{col}_te], test[f{col}_te] te_base(train, test, col, train[price], alpha5)这段代码的思路是对 brand、model、name 这类高基数类别统计每个类别下价格均值再向全局均值做平滑收缩类别样本量越少编码值越被拉向全局均值。alpha 越大收缩越强alpha5 是赛题里比较稳的起点。低频类别不会因为只有几条样本就被赋予极端编码这是它比直接 groupby 均值更可靠的原因。注意这个基础版是讲公式用的不能直接对 train 用全量数据编码再 map 回 train那叫目标编码泄露会造成验证分数虚高第 5 章会专门讲折内编码的正确写法。测试集用全量 train 算出的 encoding 字典直接映射这是规范的。3.2 从 regDate 和 creatDate 里挖车龄时间差比单独年份更有用for df in [train, test]: df[regDate] df[regDate].fillna(0).astype(int).astype(str).str.zfill(8) df[regDate] pd.to_datetime(df[regDate], format%Y%m%d, errorscoerce) df[creatDate] pd.to_datetime(df[creatDate], format%Y%m%d, errorscoerce) df[age_days] (df[creatDate] - df[regDate]).dt.days.abs() df[regYear] df[regDate].dt.yearregDate 是注册日期creatDate 是发布时间。二手车交易里发布通常晚于注册所以两者相减是负数取 abs 后就得到这辆车从注册到发布的天数这个特征比单独用年份或月份更能刻画车龄。处理时先 fillna(0) 再转字符串补零是为了让日期字段格式统一解析失败的行会被 errorscoerce 变成 NaT后续 age_days 的 NaN 用中位数填充。regYear 单独提出来是因为年份是强周期性特征模型直接学天数时会忽略跨年效应补一列原始年份能让树模型多做一次切分。3.3 用一次快速 LightGBM 做特征减法顺便把内存压下来import lightgbm as lgb from sklearn.model_selection import train_test_split features [c for c in train.columns if c not in [price, SaleID, regDate, creatDate]] X train[features].fillna(-999) y train[price] X_tr, X_va, y_tr, y_va train_test_split(X, y, test_size0.2, random_state42) model lgb.LGBMRegressor(n_estimators300, learning_rate0.1, random_state42) model.fit(X_tr, y_tr) imp pd.Series(model.feature_importances_, indexX_tr.columns).sort_values(ascendingFalse) print(imp.head(30))用默认参数跑三百轮看 importance 排序目的是删除排在最末尾、重要性接近 0 的列。这个赛题的基线特征通常有几十维删掉无效列能减少过拟合也让后续五折训练更快。fillna(-999) 是树模型通用的缺失处理-999 对树来说只是一个独立分支位不会被当成数值趋势。for c in X.columns: if X[c].dtype float64: X[c] X[c].astype(float32)这一步是降内存的常规操作pandas 默认读入的浮点列是 float64对决策树来说 float32 精度完全够。十万级数据量感受不明显但如果后面要做五折加多模型融合内存会从几十个特征叠加翻倍提前压到 float32 能避免训练到一半内存溢出。4. 模型训练与融合五折交叉验证下的三个树模型这个赛题的高分方案很少靠单模型靠的是验证策略稳定和特征一致性。我一般会在特征工程做完之后先固定一套五折交叉验证的框架再往里填 LightGBM、XGBoost、CatBoost 三个模型最后做加权融合。顺序不能反五折框架是地基模型是上层。4.1 先写一个可复用的 KFold 训练函数import numpy as np from sklearn.model_selection import KFold from sklearn.metrics import mean_absolute_error import lightgbm as lgb def lgb_kfold(X, y, params, n_folds5): kf KFold(n_splitsn_folds, shuffleTrue, random_state2024) oof np.zeros(len(X)) models [] for fold, (tr_idx, va_idx) in enumerate(kf.split(X)): dtr lgb.Dataset(X.iloc[tr_idx], labely.iloc[tr_idx]) dva lgb.Dataset(X.iloc[va_idx], labely.iloc[va_idx]) model lgb.train( params, dtr, num_boost_round5000, valid_sets[dva], callbacks[lgb.early_stopping(200), lgb.log_evaluation(200)] ) models.append(model) oof[va_idx] model.predict(X.iloc[va_idx], num_iterationmodel.best_iteration) fold_mae mean_absolute_error(y.iloc[va_idx], oof[va_idx]) print(ffold {fold} mae: {fold_mae:.4f}) print(foof mae: {mean_absolute_error(y, oof):.4f}) return oof, modelsKFold 的 n_splits5 是这个量级数据的主流选择shuffleTrue 且固定 random_state保证每次实验可比。early_stopping(200) 的意思是两百轮验证集指标不提升就停止防止模型在训练集上跑满 5000 轮导致过拟合。预测时用 best_iteration 而不是训练完毕的完整模型这是拿到稳定 OOF 的关键。OOF 是 out-of-fold 预测每折验证集拼起来的全量预测它既能评估真实泛化能力也能在融合阶段当训练数据。4.2 LightGBM 基线参数一组可以直接抄的配置参数建议值说明objectiveregression_l1直接优化 MAE贴合赛题指标learning_rate0.05调参前的固定起点太大容易过拟合num_leaves63数据量大可以给高一点但也别超过 2^max_depthmax_depth-1让 num_leaves 决定复杂度更灵活feature_fraction0.8每棵树随机取 80% 特征减少过拟合bagging_fraction0.8每棵树随机取 80% 样本bagging_freq1每一轮都做 bagging不要隔几轮再做min_child_samples20叶子节点最少样本数压过拟合用lambda_l10.1L1 正则对稀疏特征友好lambda_l21.0L2 正则稳定数值特征objective 用 regression_l1 而不是默认的 regression因为赛题评价指标是 MAE模型训练目标和评价指标一致才能让 early stopping 的决策可靠。learning_rate 从 0.05 起步调参时最后再动它。特征比例和采样比例都设在 0.8配合 min_child_samples20属于偏保守的基线配置保证五折之间波动小。如果 OOF MAE 明显偏高优先调 num_leaves 和 learning_rate不要动 bagging 参数。4.3 XGBoost 和 CatBoost 的差异点与加权融合pred_lgb np.mean([m.predict(X_test, num_iterationm.best_iteration) for m in lgb_models], axis0) pred_xgb np.mean([m.predict(X_test) for m in xgb_models], axis0) pred_cat np.mean([m.predict(X_test) for m in cat_models], axis0) pred_final 0.4 * pred_lgb 0.35 * pred_xgb 0.25 * pred_cat三个树模型里XGBoost 对数值特征更敏感适合把匿名特征 v_0 到 v_14 的价值榨出来CatBoost 对类别特征有原生支持但在这个赛题里因为已经做了目标编码优势会被削弱我一般只给它一个中等权重。融合权重不是玄学先用 OOF 上三个模型的 MAE 做反比加权得到初始值再微调。0.4、0.35、0.25 是常见的 LGB 优先分配但必须在验证集上确认过再提交。只追求权重到小数点后两位就够了纠结 0.05 的差异纯属浪费时间真正拉开分数差距的是第 5 章那些坑有没有避开。5. 避坑二手车价格预测项目里最容易翻车的 5 个细节这部分是我做这个赛题和后续类似项目时反复踩出来的血泪经验。每一条都从现象说起再给原因和解决路径。翻车不可怕可怕的是验证分数显示很好、提交后却完全对不上。5.1 目标编码泄露验证分数虚高的最隐蔽来源现象本地 OOF MAE 低到不敢信比公开基线好一大截但提交后榜单分数比本地高很多甚至不如没做特征工程的版本。原因用全量 train 的目标均值编码后再 map 回 train 自身等于每行样本都偷看了自己的标签模型在验证集上作弊。解决目标编码必须放进 KFold 内部对每个训练折单独计算 encoding映射到对应验证折测试集则用全量 train 算出的字典映射。判断泄露有个快方法训练完看 feature importance如果 name_te 或 model_te 排第一且远超其他特征基本就是泄露了。5.2 里程为 0 和极端价格别急着当脏数据删现象删掉 kilometer 为 0 和 price 高位的样本后本地验证分数反而更稳但提交分数明显变差。原因0 公里可能是厂商指导价场景或未上路的新车高位价格也是真实交易的一部分MAE 对大额误差本来就更敏感删除等于让模型从没见过这些模式。解决保留这些样本另外加一个 is_zero 标志列把是否 0 公里变成显式信息让模型自己决定怎么用。极端价格先看分位数只有明显脱离交易逻辑的值如价格为 1才考虑替换为缺失。5.3 类别特征做归一化、树模型配独热白白浪费特征现象对 brand、model 做了 StandardScalerLightGBM 分数没变化XGBoost 反而变差。原因树模型的切分只关心阈值归一化不会改变切分位置对类别特征做标准化还会破坏取值的可比性稀疏 one-hot 被标准化后变成一堆负数小值切分变得更碎。解决树模型体系里数值列保持原始尺度即可类别列要么目标编码、要么传给 LGBMRegressor 的 categorical_feature 参数。真需要归一化的场景是神经网络或 KNN表格赛题里树模型是主角别拿深度学习那套预处理硬套。5.4 发布时间的分布差异本地和线上分数对不上现象五折交叉验证稳定但提交后分数波动大同一套代码跑两次结果差很多。原因train 和 test 的 creatDate 分布可能不一致如果特征里包含了发布月份或星期模型会把时间分段信息当成重要规律学进去而测试集的时间分布变了这个规律就失效。解决做特征前先对比 train/test 的 creatDate 月份分布分布差异明显就删除发布月份相关特征或者把月份粗分箱成几段降低模型对时间段的过度依赖。验证策略上可以用时间切分代替随机 KFold按发布时间排序后取最后一段做验证。5.5 name 和 model 独热把内存撑爆高基数特征的正确打开方式现象训练几分钟后内存从 4G 涨到 30G进程直接崩溃重启。原因name 的唯一值可能有几千个one-hot 编码后直接多出几千维稀疏列内存和时间都失控。解决高基数类别列不要碰 one-hot用第 3 章的目标编码或者退一步用频数编码。如果一定要保留原始类别信息把 name 编码成数值 id 后交给 LightGBM 的 categorical_feature让树模型自己做类别切分内存压力会小很多。6. 上分手感log 空间训练和分组校准两个技巧到这一步基线已经稳定剩下的提分来自对误差结构的细微调整。我先说一个习惯每次改特征或改模型只动一个变量记录 OOF 变化否则分数涨了也不知道是谁的功劳。这个赛题有两个低成本的上分技巧几乎不需要再改模型结构。6.1 在 log 空间训练回到原空间评估y_log np.log1p(train[price]) # 训练时把 y_log 传给 lgb_kfold预测得到 pred_log pred_price np.expm1(pred_log)价格右偏时模型在 log 空间更容易拟合尤其是高价位段的相对误差会被压缩。评估时仍然按原空间的 MAE 算所以预测结果要 expm1 还原后再和真实价格比较。注意这个技巧只在目标右偏时有效如果价格接近正态分布log 变换带来的收益很小。6.2 融合预测后按 brand 分组校准bias (pred_oof - y).groupby(train[brand]).mean() pred_final - pred_final_brand.map(bias) * 0.5不同品牌的价格区间差异很大融合后的残差往往有品牌聚集性某些品牌整体被高估另一些被低估。按 brand 分组计算 OOF 残差均值再从预测值里减去这个偏置乘以 0.5相当于对系统偏差做一次温和修正。系数取 0.5 而不是 1是为了防止对 OOF 过度拟合留一半余量给测试集。我每次复现二手车价格预测这类赛题固定动作都是先跑一版 log 空间 LightGBM 拿到 OOF确认 no leak再做分组校准如果发现特征重要性里单列异常高第一反应永远是回去查编码有没有泄露而不是庆祝分数。这套习惯帮我省过很多次无效调参希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Java 3D场景图编程入门:从零构建可交互三维可视化

Java 3D场景图编程入门:从零构建可交互三维可视化

1. 先别急着搜版本号:这个“java3”说的是 Java 3D 场景图编程看到“java3”这个标题,我一开始也没反应过来,脑子里先浮现的是“Java 3?Java 不是从 1.4 直接跳到 5 了吗,哪来一个第三版”。估计很多读者也会有同样的困…

📅 2026/10/10 18:59:01
数据结构与算法刷题攻略:两遍学习法与复习重写实战

数据结构与算法刷题攻略:两遍学习法与复习重写实战

简介:一份面向程序员求职与算法进阶的刷题全攻略资料包,整合剑指Offer题解、程序员代码面试指南题解、九章算法讲解、牛客直通BAT算法课等经典内容,同时收纳大公司笔试真题与LintCode编程题练习,基本覆盖算法面试常见题型与解题思…

📅 2026/10/10 18:59:01
动态分时电价下电动汽车有序充放电调度:Matlab建模与PSO实时优化实现

动态分时电价下电动汽车有序充放电调度:Matlab建模与PSO实时优化实现

搞电动汽车充放电调度仿真的这几年,我坑踩了不少,也攒了不少能直接落地的经验。很多人拿到“基于动态分时电价的电动汽车有序充放电实时优化调度系统”这个题目,第一反应就是冲进Matlab里敲代码,结果模型还没理清,就被…

📅 2026/10/10 18:59:01
MORE NEWS

更多资讯

📰

知识蒸馏实战:用mattevans-distil压缩大模型到小设备

简介:mattevans-distil 是一个用 Go 语言编写的轻量级内存数据集过滤开源项目,面向需要在程序内对结构化数据做条件筛选的开发者,尤其适合刚接触 Go 数据处理、想通过源码理解过滤逻辑实现的学习者。项目围绕数据集过滤这一核心场景&#xff…

📰

贝叶斯深度学习实战:用PyTorch量化模型不确定性

简介:本资源是一份面向机器学习进阶学习者与科研实践者的贝叶斯深度学习入门实践代码包,聚焦模型不确定性建模这一关键能力,适用于医疗诊断、金融风控、推荐系统等需量化预测可信度的高要求场景。压缩包为RAR格式,仅含1个核心Pyth…

📰

中医舌苔诊断系统:YOLOv5+SAM+ResNet50全链路Web应用

简介:一份完整的中医舌苔智能诊断网页应用源码包,面向计算机、电子信息等专业课程设计、期末大作业与毕业设计场景,也适合深度学习与Web开发初学者。项目以Python为后端、Vue为前端,采用yolov5目标检测定位舌体、Segment Anything…

📰

基于BP神经网络的人脸识别Matlab实现:PCA降维与GA优化实战

简介:基于BP神经网络的人脸识别Matlab实现.zip是一套面向人脸识别入门与进阶的完整实验项目,主要适合具备Matlab和机器学习基础的学生、研究人员及开发者,用于理解反向传播神经网络的监督学习机制,以及从人脸检测、特征提取到分类…

📰

SpringBoot智慧养老平台实战:从需求拆解到部署上线全流程

项目标题里这几个关键词——SpringBoot、Java、智慧养老、银龄健康云服务平台——放一起,基本就能看出这是个典型的Java Web毕业设计选题。作为带过不少毕设项目的“过来人”,我第一反应是这个题目选得挺聪明:一方面技术栈主流、资料齐全&…

📰

Tinycast vs Raycast 横评:兼容 114/147 个命令之外,差距还剩什么?

Tinycast vs Raycast 横评:兼容 114/147 个命令之外,差距还剩什么? 【免费下载链接】tinycast Tinycast — a tiny, fully native macOS launcher, hotkeys, and clipboard history. 项目地址: https://gitcode.com/GitHub_Trending/ti/tin…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬