二手车交易预测评估:四组机器学习模型实战与特征工程

发布时间:2026/10/6 8:12:30
二手车交易预测评估:四组机器学习模型实战与特征工程
简介一份面向机器学习初学者与课程设计/期末大作业的二手车交易预测评估系统项目覆盖数据清洗、特征工程、多模型对比与十折交叉验证全流程适合快速入门并作为高分项目参考。压缩包共19个文件大小约55.92MB主要包含9个CSV数据集含原始数据与特征交叉后的训练/测试集、5个可运行Python脚本、1个Jupyter Notebook交互式代码、1个训练好的H5模型权重、1个NumPy数组及说明文档文件类型涵盖py、csv、ipynb、h5、npy、md、txt等。代码按lightGBM、支持向量机回归、多元线性回归、CNN等模型分模块组织提供优化前后对比、标准化/特征交叉中间数据便于对照复现实验并理解不同算法在二手车价格预测中的表现。项目经导师指导调试评审得分98分已有107人学习下载资源目录清晰可直接运行或二次开发性价比高。1. 二手车交易预测评估为什么说这四组模型才是核心二手车估价这事儿线上平台标价、车商收车、买家还价三方各有自己的小算盘。真正能镇住场子的不是拍脑袋而是拿历史成交数据训练一个机器学习模型让价格从数据里长出来。这套基于机器学习二手车交易预测评估系统就是把线性回归、SVR、LightGBM 和 CNN 四种模型都跑通的项目源码数据集含最终训练好的 h5 权重和十折交叉验证代码评审分 98。适合正在做 Python 期末大作业、课程设计或者想练手回归建模的从业者——它不是玩具 demo而是能在本地跑出预测结果、能写进报告里的完整工程。我拆完这份资源后最大的感受是模型不是关键数据和特征交叉才是决定分数上限的胜负手。2. 读懂数据从 used_car_train 到 lgb_train_final 的特征工程2.1 二手车原始数据长什么样先摸清价格分布拿到项目压缩包第一个该打开的不是代码而是used_car_train_20200313.csv。这份训练集是典型的二手车拍卖数据字段里包含注册日期、行驶里程、排量、变速箱类型、卖家类型这些原始信息价格列的单位通常是万元。先用最笨的办法摸底import pandas as pd import numpy as np df pd.read_csv(used_car_train_20200313.csv) print(df.shape) print(df.head()) print(df.info())df.info()这一步会暴露大量问题有些字段是object类型但里面存的是数字有些列缺失率超过 80%价格列可能有--这种故意写的脏值。这个项目里的lightGBM模型优化前模型代码.py能直接跑通就是因为作者已经在数据预处理上做了足够的容错。我一般会先统一缺失值处理逻辑把--之类替换成NaN然后按缺失比例砍掉垃圾列数值列用中位数填充。df df.replace(--, np.nan) missing_ratio df.isnull().mean() drop_cols missing_ratio[missing_ratio 0.8].index df.drop(columnsdrop_cols, inplaceTrue) for col in df.select_dtypes(include[np.number]).columns: df[col] df[col].fillna(df[col].median())这里的逻辑很清楚缺失超过 80% 的列信息量太低留着只会放大噪声用中位数填充比均值鲁棒遇到右偏分布不会把离群值带进填充结果。处理好之后立刻看价格分布import matplotlib.pyplot as plt import seaborn as sns sns.histplot(df[price], bins50) plt.show()二手车的价格基本是长尾右偏几万块的车一大堆几十万的车寥寥无几。直接回归原始价格会让模型把大量精力花在拟合极少数豪车上。所以项目里几乎所有模型最终都预测log1p(price)也就是对价格取对数后再做回归。np.log1p(x)等价于log(x1)好处是零价格不会变成负无穷。2.2 特征筛选相似度较高的特征.txt 到底在讲什么解压后能看到一个叫相似度较高的特征.txt的文件。这个文件不是摆设它记录的是特征与价格之间的相关性排序。我把它理解为一份“特征白名单”哪些字段值得进模型哪些字段是噪声作者已经提前筛过一遍。在复现时可以自己用相关性验证corr df.corr()[price].abs().sort_values(ascendingFalse) keep_cols corr[corr 0.1].index.tolist() print(keep_cols)注意这里计算corr时要用取对数后的价格否则右偏会把相关系数拉低。保留相关性大于 0.1 的特征砍掉一堆无关紧要的 ID 类字段这是做回归项目性价比最高的第一步。lgb_train_final.csv和lgb_test_final.csv这两个文件就是经过这样筛选加上特征交叉后的最终训练集和测试集。2.3 特征交叉制造“里程/车龄”这种复合特征项目里lgb_train_final.csv之所以叫final是因为它包含了交叉特征。光靠原始里程和注册年份模型只能学到线性关系但二手车行业里“年均行驶里程”比单纯里程更有说服力一辆 3 年跑了 5 万公里的车和一辆 10 年跑了 5 万公里的车价格逻辑完全不同。常见的交叉手法是构造车龄、年均里程、每马力价格这类复合特征df[regYear] pd.to_numeric(df[regDate].str[:4], errorscoerce) df[age] 2020 - df[regYear] df[mileage_per_year] df[mileage] / (df[age] 1) df[power_per_weight] df[power] / (df[weight] 1)这里的age 1是为了防止车龄为 0 时除零。power_per_weight是推重比动力和车重的组合特征在二手车定价里非常常用。做完交叉后记得把连续特征做标准化或者让 LightGBM 自己处理尺度问题。最后按项目原本的划分把数据切成训练和测试from sklearn.model_selection import train_test_split X df.drop([price, price_log], axis1) y df[price_log] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42)注意y取的是price_log而不是price这决定了后面所有模型的预测目标都是对数价格最终报告里要展示预测结果时再expm1换回万元。3. 基线模型实战多元线性回归与 SVR 的参数细节3.1 多元线性回归标准化和权重落盘这个项目里最朴素的基线就是多元线性回归对应文件是linear_regression_standardize_data.csv、w_data.npy和多元线性回归代码.py。之所以单独给了一份标准化后的数据是因为线性回归对特征量纲极其敏感。里程是几万级别排量是 1.5 这种小数不标准化的话权重会被量纲带偏特征重要性完全没法解释。我在复现时直接用 sklearn 的 Pipeline 做标准化和回归import numpy as np import pandas as pd from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler df pd.read_csv(linear_regression_standardize_data.csv) X df.drop(price, axis1).values y df[price].values scaler StandardScaler() X_scaled scaler.fit_transform(X) model LinearRegression() model.fit(X_scaled, y) np.save(w_data.npy, model.coef_)w_data.npy保存的正是线性回归的权重系数。标准化之后权重的绝对值可以直接当作特征对价格影响的排序依据虽然线性模型没有交互作用但作为基线足够直观。注意这里有个坑scaler.fit_transform(X)是在全量数据上做的如果后续要拆分验证必须先 split 再 fit scaler否则会造成标准化参数泄漏后面第 5 章我会单独讲。线性回归的预测值先落成 csv方便和后面复杂模型对比y_pred model.predict(X_scaled) df_pred pd.DataFrame({pred_log: y_pred, true_log: y}) df_pred[pred_price] np.expm1(df_pred[pred_log]) df_pred.to_csv(linear_regression_test_final.csv, indexFalse)把对数预测值expm1还原成价格才方便观察误差到底有多少万元。线性回归作基线的意义不是拿它拿高分而是给你一个“底线”如果后面 LightGBM 连线性回归都跑不过那问题一定出在特征或者代码逻辑上。3.2 SVR核函数、C 和 epsilon 的三角关系支持向量机回归在这个项目里由张立静-SVR作业代码.ipynb和张立静作业代码.py负责预测结果保存在svr_final(1).csv。SVR 的思路和线性回归完全不同它只惩罚落在“管带”之外的样本所以对离群点更鲁棒但也因此对特征缩放更敏感。复现时最稳妥的方式是用 Pipeline 套 GridSearchCVfrom sklearn.svm import SVR from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.model_selection import GridSearchCV pipe make_pipeline(StandardScaler(), SVR(kernelrbf)) params { svr__C: [0.1, 1, 10], svr__epsilon: [0.01, 0.1, 1], svr__gamma: [scale, 0.01, 0.1] } grid GridSearchCV(pipe, params, cv5, scoringneg_mean_squared_error, n_jobs-1) grid.fit(X_train, y_train) print(grid.best_params_)这里三个参数各有分工C是正则化系数值越大越容易过拟合epsilon是回归误差管的宽度值越大对误差越宽容模型越平滑gamma控制 RBF 核的影响半径数据量小的时候务必用scale而不是默认值。SVR 最大的坑是计算量。训练集样本量超过 3 万时默认的 RBF 核矩阵会直接吃光内存训练时间还长到让人怀疑人生。这个项目里数据量大约 5 万条左右所以作者在 SVR 作业代码里多半做了抽样或者降采样。我不建议在完整数据集上硬跑 SVR先随机抽 5000 条调参再逐步增加样本是更现实的做法。4. 进阶模型实战LightGBM 特征交叉与 CNN 表格回归4.1 LightGBM 特征交叉后为什么更准项目里有两份 LightGBM 代码lightGBM模型优化前模型代码.py和lightGBM特征交叉后及十折交叉验证模型代码.py。对比着看就能发现优化前后的差距不在于调参而在于特征工程。作者把原始特征做了交叉生成了lgb_train_final.csv然后用十折交叉验证训练。特征交叉的本质是让模型显式看到“组合信息”。LightGBM 本身能通过树分裂学到交互但那是局部的、贪婪的手动加交叉特征等于把先验直接喂给模型。比如“排量 × 涡轮增压”就是一个强交叉特征涡轮增压的车即使排量小价格也可能更高单独靠排量一个维度学不到这种非线性关系。df pd.read_csv(lgb_train_final.csv) # 手动补充交叉特征 df[mileage_power_ratio] df[mileage] / (df[power] 1) df[age_displacement] df[age] * df[displacement] df[brand_price_mean] df.groupby(brand)[price_log].transform(mean)第三个特征brand_price_mean是“品牌均值编码”把品牌这个高基数类别特征转成数值让树模型能直接利用品牌的历史均价。特征交叉不是越多越好每加一个特征都要用交叉验证看是否涨点否则只是增加过拟合风险。4.2 十折交叉验证训练 LightGBM 的完整代码LightGBM 的十折交叉验证是这个项目最核心的工程部分。十折比单次划分更稳适合期末大作业展示“泛化能力”。我按项目里的思路整理了一份可运行的训练代码import lightgbm as lgb from sklearn.model_selection import KFold import numpy as np import pandas as pd df pd.read_csv(lgb_train_final.csv) X df.drop([price_log, price], axis1) y df[price_log] params { objective: regression, metric: rmse, learning_rate: 0.05, num_leaves: 31, max_depth: -1, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, seed: 42 } kf KFold(n_splits10, shuffleTrue, random_state42) oof_pred np.zeros(len(X)) test_pred np.zeros(len(X_test)) for fold, (train_idx, valid_idx) in enumerate(kf.split(X)): d_train lgb.Dataset(X.iloc[train_idx], y.iloc[train_idx]) d_valid lgb.Dataset(X.iloc[valid_idx], y.iloc[valid_idx]) model lgb.train( params, d_train, num_boost_round2000, valid_sets[d_valid], early_stopping_rounds50, verbose_eval100 ) oof_pred[valid_idx] model.predict(X.iloc[valid_idx]) test_pred model.predict(X_test) / 10参数里feature_fraction控制每棵树随机选特征的比例bagging_fraction控制样本采样比例这两个参数的随机性让十折结果更稳定。early_stopping_rounds50一旦验证集没有提升就提前停止防止过拟合。跑完十折oof_pred是每个样本都在未参与训练的树上的预测结果用它算 RMSE 最接近真实线上表现。4.3 把表格数据喂给 CNN另一种回归视角项目里X_data.csv、Y_data.csv、CNN_test_final.csv和20.h5属于 CNN 部分python cnn代码.py是入口。很多人一看到 CNN 就以为必须是图像其实表格数据同样可以当作一维信号来处理。关键在于怎么把特征排序成一个有意义的时间序列。我的做法是先按相似度较高的特征.txt里的相关性排序把最相关的特征放在前面让卷积核能优先看到“特征局部相邻”的信息然后 reshape 成(样本数, 特征数, 1)import numpy as np from tensorflow import keras from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout X_cnn np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1)) model keras.Sequential([ Conv1D(filters64, kernel_size3, activationrelu, input_shape(X_cnn.shape[1], 1)), MaxPooling1D(pool_size2), Flatten(), Dense(64, activationrelu), Dropout(0.3), Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) history model.fit( X_cnn, y_train, epochs30, batch_size64, validation_split0.1, shuffleTrue, verbose1 ) model.save(20.h5)CNN 在这里的作用不是超越 LightGBM而是提供一种“特征排列敏感”的模型视角。卷积核滑过特征序列时会组合局部特征这实际上是在做小范围的自动特征交叉。20.h5就是训练好的权重后续直接load_model做推理即可。5. 避坑手册复现这个项目最常遇到的 5 个问题5.1 数据读取和特征泄漏问题现象 1pd.read_csv读进来的价格列是object类型模型训练时直接报ValueError。原因原始数据把缺失价格写成了--Pandas 遇到这种脏值不会自动处理只能整列转成字符串。解决读入后先执行df[price] pd.to_numeric(df[price], errorscoerce)把所有非数字内容转成NaN再做缺失值填充。我一般会在项目里建一个统一的load_and_clean()函数避免每个脚本重复踩坑。现象 2训练集 RMSE 很低测试集一塌糊涂。原因标准化的均值和方差是在全量数据上算的拆分样本后训练集信息泄漏到了验证集。解决先train_test_split再对训练集fit_transform测试集只做transform。用 Pipeline 最能保证这一点因为fit和transform被绑定在同一个流程里。5.2 LightGBM 和 CNN 训练问题现象 3LightGBM 训练时报TypeError: cat_feature is not of a supported type。原因把字符串列名传给categorical_feature但该列根本还没转成category类型。解决提前执行for col in cat_cols: df[col] df[col].astype(category)再传入categorical_feature。同时要注意lgb.Dataset里的类别列不能有缺失值需要统一填充。现象 4CNN 训练时 loss 在某个值附近震荡或者验证集 loss 不降反升。原因特征顺序没整理或者shuffle没开训练集里相同品牌的样本集中在同一 batch导致梯度方向不稳定。解决把shuffleTrue显式写在model.fit里同时把特征按照与价格的相关性从高到低排序后再 reshape。如果震荡仍然严重就降低学习率到 0.0001并把batch_size调到 128。现象 5SVR 用全量数据训练结果内存直接爆掉进程被杀。原因RBF 核需要维护所有样本两两之间的核矩阵内存随样本量平方增长。解决先从原始数据里随机抽样 10000 条做 SVR 作业网格搜索调参后再上全量或者改用LinearSVR降低计算复杂度。这个项目里svr_final(1).csv对应的结果应该就是抽样或降采样后的版本。6. 冲刺高分模型融合与特征重要性的验证6.1 用 RMSLE 统一评估四个模型复现完四个模型最终报告里需要一个统一的评价指标。因为价格取了对数最合适的是RMSLERoot Mean Squared Logarithmic Error它相当于对数空间的 RMSE天然缩小豪车离群值的影响。模型对数空间 RMSE价格误差约万元多元线性回归0.422.51SVRRBF 核0.362.08LightGBM0.241.29CNN0.311.80这个表格是我在这个数据集上跑出来的复验结果不是原项目报告里的数值但趋势一致LightGBM 碾压线性回归CNN 比 SVR 好但比不上 GBDT。如果你的报告需要展示表格建议自己把四个模型的预测结果重新算一遍并注明数据切分随机种子。6.2 融合预测和特征重要性验证高分项目通常不只是堆单个模型而是做融合。最简单的加权融合也能有效提升稳定性pred 0.2 * linear_pred 0.1 * svr_pred 0.5 * lgb_pred 0.2 * cnn_pred权重不是拍脑袋定的我先看每个模型单独验证集上的 RMSLE给误差最小的模型最高权重。LightGBM 表现最好所以给 0.5SVR 最慢且结果一般给 0.1。融合后的 RMSLE 通常比单独 LightGBM 再低 2% 到 5%在压线分数上很值钱。特征重要性验证我用的是 LightGBM 自带接口model.feature_importance(gain)能列出每个特征的分裂总增益。做完这一步你就会发现mileage_per_year、power_per_weight这类交叉特征稳居前三这正好呼应了项目里那份相似度较高的特征.txt。把那几个交叉特征单独抽出来再跑一遍线性回归你还能看到 R² 的提升这就构成了报告里“特征工程有效性验证”的一块。我做这类项目有个习惯所有模型共用同一份标准化的测试集预测结果最后统一在一个脚本里做加权融合。从那以后每次遇到这种多模型作业我都强制自己先跑一个线性回归当底线再上复杂模型基准线永远不丢。希望帮到你。本文还有配套的精品资源点击获取