简介:这份资源是面向计算机、人工智能、大数据及数学等相关专业学生的机器学习实战项目包,聚焦二手车交易价格预测这一典型回归赛题,适合正在做课程设计、期末大作业或毕业设计的学习者参考,也可作为入门机器学习建模流程的练手素材。压缩包共16个文件,约32.04MB,以csv数据集、ipynb代码笔记、xml与iml工程配置、md说明文档及txt依赖清单为主,其中csv承载训练与测试样本,ipynb分别给出LightGBM与XGBoost两套建模实现,md与txt则用于说明项目结构与运行环境。目前已有318人学习下载。读者可从中获得完整的赛题方案:包括数据读取与特征处理、两种集成学习模型的训练与调参、预测结果输出与提交流程,并附有项目说明帮助理解目录组织与代码逻辑,便于在此基础上复现、改进或迁移到其他价格预测场景。
1. 二手车价格预测项目:从数据到模型,一份能跑通的机器学习实战源码
二手车定价这件事,做过的人都知道有多玄学。同一款车,不同城市、不同过户次数、不同排量,价格能差出一大截。靠人工经验估价,不仅慢,而且很难规模化。这份「基于机器学习的二手车交易价格预测算法源码+项目说明.zip」就是冲着这个问题来的——它用 LightGBM 和 XGBoost 两套梯度提升框架,把二手车交易价格预测做成了一个完整的、可复现的工程项目。
资源包里包含训练集、测试集、提交样例、两个 Jupyter Notebook 以及依赖清单,结构清晰,下载解压后按顺序跑就能出结果。适合正在做机器学习课程设计、期末大作业或毕设的同学,也适合想拿一个真实回归任务练手的从业者。它不教你从零推导梯度提升,但能让你在真实数据上把「特征工程 → 模型训练 → 预测提交」这条链路完整走一遍。
2. 数据与任务拆解:先搞清楚预测目标再动手
拿到一份机器学习源码,最忌讳上来就jupyter notebook一路 Shift+Enter。先花十分钟把数据和任务搞清楚,后面能省掉大量返工。
2.1 三份 CSV 各是什么,字段怎么读
资源包里的data目录下有三个核心文件:
| 文件名 | 用途 | 说明 |
|---|---|---|
used_car_train_20200313.csv | 训练集 | 含标签price,用于训练模型 |
used_car_testB_20200421.csv | 测试集 | 不含price,用于生成预测结果 |
used_car_sample_submit.csv | 提交样例 | 规定提交格式,通常是SaleID+price |
训练集和测试集的字段结构基本一致,常见字段包括SaleID(交易唯一标识)、name(车型编码)、regDate(注册日期)、model(车型)、brand(品牌)、bodyType(车身类型)、fuelType(燃油类型)、gearbox(变速箱)、power(马力)、kilometer(里程)、notRepairedDamage(是否有未修复损伤)、regionCode(地区编码)、seller(卖家类型)、offerType(报价类型)、creatDate(交易时间)以及price(价格,仅训练集有)。
这里有个容易翻车的点:price在原始数据里往往不是标准正态分布,而是明显右偏。如果直接拿来做回归,模型会被高价样本带偏。常见做法是先对price做对数变换,预测完再指数还原。这一步在 Notebook 里通常会有体现,但如果你自己改代码,别忘了这个操作。
2.2 任务类型与评估指标
这是一个典型的有监督回归任务:输入是车辆的各种属性,输出是一个连续的价格值。评估指标一般是 MAE(平均绝对误差)或 RMSE(均方根误差)。MAE 对异常值更鲁棒,RMSE 对大误差惩罚更重。这份源码里两个 Notebook 分别用 LightGBM 和 XGBoost 实现,你可以对比两者在同一数据上的表现差异。
提示:如果你的课程设计或毕设要求写实验对比,这份源码天然提供了两个模型的对照,直接拿来用比从零搭省事得多。
2.3 环境准备与依赖安装
资源包根目录有requirements.txt,先看它写了什么,再决定怎么装。常见依赖包括numpy、pandas、scikit-learn、lightgbm、xgboost、matplotlib、seaborn、jupyter。
# 创建独立环境,避免污染全局包 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖 pip install -r requirements.txt # 如果 lightgbm 或 xgboost 安装慢,可以换国内镜像源 pip install lightgbm xgboost -i https://pypi.tuna.tsinghua.edu.cn/simple逻辑说明:用虚拟环境隔离依赖是基本操作,避免不同项目之间版本冲突。requirements.txt里如果没锁版本号,建议手动加上lightgbm>=3.3.0和xgboost>=1.5.0这类下限,防止装到过老版本导致 API 不兼容。参数方面,Python 版本建议 3.8 以上,LightGBM 和 XGBoost 对 3.7 以下支持越来越差。
3. LightGBM 与 XGBoost 双模型实战:从特征工程到提交文件
这一章是整份源码的核心。两个 Notebook 的结构类似,但细节处理上有差异。我按实际跑通的顺序拆一遍。
3.1 数据加载与缺失值处理
先看数据规模。训练集通常有 15 万条左右,测试集 5 万条左右,字段 30 个上下。加载时注意read_csv的sep参数,有些版本用空格分隔,有些用逗号,看文件实际内容。
import pandas as pd import numpy as np # 加载数据,注意编码和分隔符 train = pd.read_csv('data/used_car_train_20200313.csv', sep=' ') test = pd.read_csv('data/used_car_testB_20200421.csv', sep=' ') sample_submit = pd.read_csv('data/used_car_sample_submit.csv') print('训练集形状:', train.shape) print('测试集形状:', test.shape) print('提交样例形状:', sample_submit.shape) # 查看缺失值分布 missing = train.isnull().sum() missing = missing[missing > 0].sort_values(ascending=False) print('缺失字段及数量:\n', missing)逻辑说明:sep=' '是因为部分二手车数据集用空格分隔而非逗号,如果报错就改成sep=','。缺失值统计是为了决定填充策略——bodyType、fuelType、gearbox这几个字段通常有缺失,常见做法是用众数填充或单独标记为「未知」类别。notRepairedDamage字段里可能有'-'这种非数值字符,需要先替换成NaN再处理。
参数方面,read_csv的low_memory=False可以在字段类型混杂时避免警告,但会吃内存,15 万条数据问题不大。
3.2 特征工程:日期拆解与类别编码
原始字段里regDate和creatDate是日期字符串,直接扔给模型没用。需要拆成年、月、日,甚至算出「车龄」这个更有业务含义的特征。
# 日期特征拆解 train['regDate'] = pd.to_datetime(train['regDate'], format='%Y%m%d', errors='coerce') train['creatDate'] = pd.to_datetime(train['creatDate'], format='%Y%m%d', errors='coerce') train['car_age'] = (train['creatDate'] - train['regDate']).dt.days # 测试集同样处理 test['regDate'] = pd.to_datetime(test['regDate'], format='%Y%m%d', errors='coerce') test['creatDate'] = pd.to_datetime(test['creatDate'], format='%Y%m%d', errors='coerce') test['car_age'] = (test['creatDate'] - test['regDate']).dt.days # 类别特征编码:brand、bodyType、fuelType、gearbox 等 from sklearn.preprocessing import LabelEncoder cat_cols = ['brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage'] for col in cat_cols: le = LabelEncoder() # 合并训练集和测试集一起编码,保证类别一致 full_data = pd.concat([train[col], test[col]], axis=0).astype(str) le.fit(full_data) train[col] = le.transform(train[col].astype(str)) test[col] = le.transform(test[col].astype(str))逻辑说明:car_age用交易日期减注册日期,得到车辆使用天数,比单独用regDate更有预测价值。LabelEncoder把类别转成整数,注意必须把训练集和测试集拼在一起fit,否则测试集出现训练集没见过的类别时会报错。这是血泪经验——很多人分开编码,结果测试集里有个别品牌训练集没有,直接崩掉。
参数方面,errors='coerce'让无法解析的日期变成NaT,后续可以填充或删除。car_age如果出现负值(交易日期早于注册日期),说明数据有问题,可以截断到 0 或直接剔除。
3.3 LightGBM 模型训练与调参
LightGBM 的优势是训练快、内存占用低、对类别特征支持好。源码里的1--LightGBM.ipynb通常会用lgb.Dataset封装数据,然后lgb.train训练。
import lightgbm as lgb from sklearn.model_selection import train_test_split # 选择特征列,排除 SaleID 和 price feature_cols = [c for c in train.columns if c not in ['SaleID', 'price', 'regDate', 'creatDate']] X = train[feature_cols] y = np.log1p(train['price']) # 对数变换,缓解右偏 # 划分验证集 X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42) # 构建 Dataset lgb_train = lgb.Dataset(X_train, y_train) lgb_val = lgb.Dataset(X_val, y_val, reference=lgb_train) # 参数设置 params = { 'objective': 'regression', 'metric': 'mae', 'boosting_type': 'gbdt', 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': -1, 'random_state': 42 } # 训练 model = lgb.train( params, lgb_train, num_boost_round=1000, valid_sets=[lgb_val], callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)] ) # 预测并还原 pred_log = model.predict(X_val, num_iteration=model.best_iteration) pred = np.expm1(pred_log)逻辑说明:np.log1p是log(1+x),避免price为 0 时报错。early_stopping(50)表示验证集指标 50 轮不提升就停,防止过拟合。num_leaves=31是常用起点,数据量大可以调到 63 或 127,但要注意过拟合。feature_fraction和bagging_fraction是列采样和行采样,增加随机性提升泛化。
参数怎么改:如果验证集 MAE 偏高,先降learning_rate到 0.02 并增加num_boost_round;如果训练集和验证集差距大,降num_leaves或加min_data_in_leaf。这些在 Notebook 里可能没写全,但你自己调的时候用得上。
3.4 XGBoost 模型训练与对比
2--XGBoost.ipynb的逻辑类似,但 XGBoost 对缺失值处理和 LightGBM 略有不同。XGBoost 默认把缺失值分到增益最大的方向,LightGBM 也支持,但参数名不一样。
import xgboost as xgb # 构建 DMatrix dtrain = xgb.DMatrix(X_train, label=y_train) dval = xgb.DMatrix(X_val, label=y_val) # XGBoost 参数 xgb_params = { 'objective': 'reg:squarederror', 'eval_metric': 'mae', 'max_depth': 6, 'eta': 0.05, 'subsample': 0.8, 'colsample_bytree': 0.8, 'seed': 42 } # 训练 xgb_model = xgb.train( xgb_params, dtrain, num_boost_round=1000, evals=[(dval, 'val')], early_stopping_rounds=50, verbose_eval=100 ) # 预测 xgb_pred_log = xgb_model.predict(xgb.DMatrix(X_val), iteration_range=(0, xgb_model.best_iteration)) xgb_pred = np.expm1(xgb_pred_log)逻辑说明:max_depth=6是 XGBoost 常用深度,比 LightGBM 的num_leaves更保守。eta相当于 LightGBM 的learning_rate。iteration_range指定用最佳迭代轮数预测,避免用全部轮数导致过拟合。两个模型跑完后可以算一下各自的 MAE,通常 LightGBM 在这个数据集上会略快略好,但差距不会太大。
3.5 生成提交文件
预测完测试集后,按sample_submit.csv的格式写出 CSV。
# 用 LightGBM 预测测试集 test_features = test[feature_cols] test_pred_log = model.predict(test_features, num_iteration=model.best_iteration) test_pred = np.expm1(test_pred_log) # 生成提交文件 submission = pd.DataFrame({ 'SaleID': test['SaleID'], 'price': test_pred }) submission.to_csv('submission/lgb_submission.csv', index=False) print('提交文件已生成,形状:', submission.shape)逻辑说明:SaleID必须和测试集顺序一致,price是还原后的真实价格。index=False避免多出一列索引。资源包里已经有xgb_submission.csv和submit1.0.csv,你可以拿自己的结果和它们对比,看是否接近。
4. 避坑与排查:跑这份源码时最容易翻车的五个地方
这一章按「现象 → 原因 → 解决」写,都是实际跑这类项目时高频遇到的问题。
4.1 现象:read_csv报错ParserError或字段错位
原因:分隔符不对。有些二手车数据集用空格分隔,有些用逗号,还有的字段里本身含逗号但没加引号。解决:先用head -n 3看文件前几行,确认分隔符。如果是空格,用sep=' ';如果字段错位,用sep='\s+'匹配多个空格。还不行就engine='python'让 pandas 自己推断。
4.2 现象:LightGBM 训练时报警告No further splits with positive gain
原因:min_data_in_leaf或min_child_samples设得太大,叶子节点样本不够。解决:把min_data_in_leaf从默认 20 降到 10 或 5,或者增加num_leaves。如果数据量小,这个警告可以忽略,不影响最终结果。
4.3 现象:预测出来的价格全是NaN
原因:特征列里混入了非数值列,比如regDate没拆解就直接扔进去,或者notRepairedDamage里的'-'没处理。解决:训练前用X.select_dtypes(include=[np.number])筛一遍,确保全是数值。另外检查np.expm1的输入是不是NaN,如果是,说明模型预测阶段就出了问题。
4.4 现象:提交文件行数和sample_submit.csv对不上
原因:测试集预测时用了dropna()或过滤了某些行,导致SaleID缺失。解决:预测阶段不要做任何行过滤,缺失值在特征工程阶段就填充好。生成提交文件后,用assert len(submission) == len(sample_submit)校验一下。
4.5 现象:两个 Notebook 跑出来的 MAE 差异很大
原因:特征工程不一致。LightGBM 的 Notebook 可能对某些类别特征做了特殊处理,XGBoost 的没有。解决:把两个 Notebook 的特征处理逻辑对齐,用同一份X_train和y_train。如果还是差异大,检查随机种子和验证集划分是否一致。
5. 进阶技巧:用交叉验证和特征重要性把模型再推一步
跑通只是第一步。如果你要拿这份源码做课程设计或毕设,光有两个模型的对比还不够,加一点进阶操作能让报告厚实不少。
5.1 五折交叉验证替代单次划分
单次train_test_split的结果波动大,换一组随机种子 MAE 可能差几百。用 K 折交叉验证取平均,结果更稳。
from sklearn.model_selection import KFold kf = KFold(n_splits=5, shuffle=True, random_state=42) cv_scores = [] for fold, (tr_idx, val_idx) in enumerate(kf.split(X)): X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx] y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx] lgb_train = lgb.Dataset(X_tr, y_tr) lgb_val = lgb.Dataset(X_val, y_val, reference=lgb_train) model = lgb.train(params, lgb_train, num_boost_round=1000, valid_sets=[lgb_val], callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)]) pred = np.expm1(model.predict(X_val, num_iteration=model.best_iteration)) mae = np.mean(np.abs(pred - np.expm1(y_val))) cv_scores.append(mae) print(f'Fold {fold+1} MAE: {mae:.2f}') print(f'平均 MAE: {np.mean(cv_scores):.2f} ± {np.std(cv_scores):.2f}')逻辑说明:KFold的shuffle=True保证每折数据分布均匀。early_stopping在每折内部独立触发,避免用全局最佳轮数导致信息泄露。最终报告里写「五折交叉验证平均 MAE ± 标准差」,比单次划分有说服力得多。
5.2 特征重要性分析与特征筛选
LightGBM 训练完可以直接输出特征重要性,把排名靠后的特征删掉再训练,有时能提升泛化。
# 获取特征重要性 importance = pd.DataFrame({ 'feature': feature_cols, 'importance': model.feature_importance(importance_type='gain') }).sort_values('importance', ascending=False) print(importance.head(15)) # 筛选重要性大于 0 的特征重新训练 selected_features = importance[importance['importance'] > 0]['feature'].tolist() print(f'筛选后特征数: {len(selected_features)}')逻辑说明:importance_type='gain'表示用信息增益衡量,比split更稳定。通常car_age、kilometer、brand、power会排在前列。如果某些特征重要性为 0,说明模型完全没用它,可以删掉减少噪声。但注意,删特征后要重新跑交叉验证确认 MAE 没变差,别盲目删。
5.3 模型融合的简单尝试
LightGBM 和 XGBoost 的预测结果做加权平均,往往比单模型好一点。
# 假设 lgb_pred 和 xgb_pred 都是测试集预测结果 final_pred = 0.6 * lgb_pred + 0.4 * xgb_pred submission = pd.DataFrame({'SaleID': test['SaleID'], 'price': final_pred}) submission.to_csv('submission/ensemble_submission.csv', index=False)权重怎么定:看两个模型在验证集上的 MAE,MAE 小的给高权重。如果懒得算,0.5:0.5 也不会差太多。这一步在课程设计里写「模型融合提升泛化能力」,答辩时能多讲两分钟。
从那以后我每次拿到这类源码包,都先跑一遍原始 Notebook 确认能出结果,再动特征工程和参数。先保证基线能跑通,再谈优化,这个习惯帮我省了无数次「改了半天发现原始代码就有问题」的后悔药。希望这份拆解能帮你顺利跑通自己的二手车价格预测项目,少踩几个坑。
本文还有配套的精品资源,点击获取