资讯动态

二手车价格预测实战:从数据处理到多模型对比的完整项目解析

发布时间:2026/10/18 12:35:56 来源:尧图企业网站定制
简介面向机器学习初学者的二手车交易价格预测评估系统完整项目基于 Python 实现附有注释源码覆盖支持向量回归、多元线性回归、LightGBM、CNN 四类模型并配有原始训练数据集和各模型对测试集的预测结果。压缩包共19个文件主要包含5个 Python 脚本、1个 Jupyter Notebook、9个 CSV 数据及预测输出、模型参数 h5/npy 文件和说明文档整体约55.92MB按模型分目录组织适合课程作业、实际复现或毕业设计参考。已有 4124 人学习下载。项目中不仅包含可直接运行的完整流程还展示了数据标准化、特征交叉、十折交叉验证、Spearman 冗余特征筛选等进阶操作各模型参数文件和预测结果保存完整有助于读者快速建立从数据处理到模型评估的完整认知并可作为实验报告或毕业设计的素材。1. 二手车价格预测不是玄学一份能直接跑的完整项目拆解做机器学习大作业或者毕业设计最怕的不是模型不会调而是拿到一个题目后不知道从哪下手。这份基于机器学习二手车交易预测评估系统的源码包把整个流程给你铺开了——从数据预处理、特征工程到支持向量回归、多元线性回归、LightGBM、CNN四套模型全部跑通还附带了每个模型的预测结果CSV和训练好的权重文件。它不是那种只给你一个光秃秃的ipynb的教程而是一个能直接提交的完整工程。适合三类人一是机器学习期末作业不知道怎么做、怕代码太简陋被导师问住的在校生二是毕业设计想快速搭建一个预测系统、但不想从零开始写数据清洗的同学三是想对比不同模型在同一个数据集上表现的从业者。数据用的是二手车交易数据集used_car_train_20200313.csv四套模型的预测结果都已经生成好了可以直接拿去写分析报告。下面我把整个包的文件结构、每个模型的核心代码逻辑和踩过的坑一条条拆开讲。2. 先把数据集和文件结构盘清楚四个模型各管一段2.1 压缩包里到底装了什么按模型划分的目录与输出文件这个zip解压后根目录下的machine_learning_homework-master包含了四套完整的模型代码和对应的结果文件。很多人拿到压缩包第一件事就是双击运行结果报错找不到文件就是因为没搞明白每个文件夹的依赖关系。我建议你按这个顺序去梳理machine_learning_homework-master/ ├── cnn/ │ ├── cnn代码.py │ ├── CNN_test_final.csv │ └── 20.h5 ├── 支持向量机回归模型/ │ ├── 张立静-SVR作业代码.ipynb │ ├── 张立静作业代码.py │ └── svr_final(1).csv ├── 多元线性回归/ │ ├── 多元线性回归代码.py │ ├── linear_regression_standardize_data.csv │ ├── linear_regression_test_final.csv │ └── w_data.npy ├── lightGBM模型/ │ ├── lightGBM模型优化前模型代码.py │ ├── lightGBM特征交叉后及十折交叉验证模型代码.py │ ├── lgb_train_final.csv │ ├── lgb_test_final.csv │ └── 相似度较高的特征.txt ├── used_car_train_20200313.csv ├── X_data.csv ├── Y_data.csv └── README.md这里有个容易忽略的点根目录下的X_data.csv和Y_data.csv是已经拆分好的特征矩阵和标签向量但四套模型并没有都直接用这两个文件。比如说多元线性回归模型用的是自己单独做了标准化处理的linear_regression_standardize_data.csv而SVR模型用的是原始数据。这意味着你要跑通某个模型得先确认它的代码里read_csv的路径指向哪个文件否则就会遇到FileNotFoundError。2.2 从原始CSV到模型输入理解used_car_train_20200313.csv的字段含义used_car_train_20200313.csv是这次的核心数据集来自某二手车平台的交易记录。字段大概包含车辆的品牌、车系、车型、车身类型、变速箱类型、排放标准、表显里程、注册年份、厂商指导价、二手车交易价格等。其中交易价格就是我们要预测的y其他字段是x。这个数据集的第一个坑在于缺失值处理。原始数据里厂商指导价这一列可能有少量空值有些人直接dropna结果把训练集弄小了一块有些人用中位数填充但填充后再做标准化效果并不一样。我一般会先看缺失值占比只有占比小于5%才考虑填充否则直接删除该样本。另外注册年份和表显里程这两个字段的量纲差异极大如果不做标准化直接丢进SVRRBF核函数的尺度参数会被里程数主导价格预测会严重失真。2.3 为什么同一个数据集要跑四套模型选型逻辑与对比维度很多人问做作业是不是只需要一个模型就够了答案是否定的。一份优秀的机器学习大作业核心评分点在于对比分析。你要能说出来为什么选这些模型、它们的适用场景是什么、在同一个数据集上谁好谁差、差在哪。SVR适合小样本非线性回归尤其核函数选了RBF之后能拟合出价格与里程之间的复杂关系但训练速度慢。多元线性回归是基线模型虽然预测精度通常最差但它能给出每个特征的权重w_data.npy方便做可解释性分析。LightGBM是梯度提升树模型处理表格数据效果最好速度快而且能处理特征交叉和非线性关系。CNN本来是做图像的但这里把表格数据reshape成序列输入本质上是一个全连接网络用来验证深度模型在结构化数据上的表现。四套模型形成了一条从简单到复杂的完整链路报告里可以画一张对比表格把MAE、RMSE、训练时间列出来。3. 从零跑通多元线性回归标准化、参数矩阵与预测结果复现3.1 为什么要先做z-score标准化而不是MinMaxScaler多元线性回归代码.py里第一步就是把原始数据做标准化。这里用的是StandardScaler也就是z-score标准化公式是(x - mean) / std。为什么不用MinMaxScaler因为线性回归假设误差项服从正态分布z-score标准化后数据分布更接近标准正态而MinMaxScaler只改变取值范围不改变分布形态。另一个原因是w_data.npy存的是标准化后的参数矩阵后续如果要拿新数据去预测必须用同一组均值和标准差去变换否则参数对不上。标准化之后的数据被保存为linear_regression_standardize_data.csv这个文件有个好处——你可以直接拿它做可视化分析比如画特征与价格的散点图。我通常会在这一步顺手用pandas的describe()检查标准化后的均值是否接近0、标准差是否接近1防止某些列因为常数值导致std为0进而出现除零错误。3.2 手写闭式解还是调sklearn这行代码里的计算逻辑我打开多元线性回归代码.py看了一下它没有直接调LinearRegression而是用最小二乘法的闭式解自己算的也就是正规方程。import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler # 加载已标准化的数据 data pd.read_csv(linear_regression_standardize_data.csv) X data.drop(columns[price]).values y data[price].values # 添加偏置项对应截距 X np.hstack([np.ones((X.shape[0], 1)), X]) # 正规方程求解w (X^T * X)^(-1) * X^T * y X_transpose X.T theta np.linalg.inv(X_transpose.dot(X)).dot(X_transpose).dot(y) # 保存模型参数 np.save(w_data.npy, theta)这段代码的核心就一行np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y)。它的数学原理是让残差平方和最小化对损失函数求导并令导数为零得到参数向量的解析解。相比直接调sklearn的LinearRegression手写正规方程的好处是你能真正看到每个特征的权重而且对理解线性代数在机器学习中的应用很有帮助。但坏处也明显如果特征维度很高或者样本量很大(X^T * X)求逆的计算复杂度是O(n^3)容易内存爆炸。这里的数据量大概几千条、特征几十个跑起来没问题。如果换成几万条数据我会建议改成梯度下降或者直接调LassoCV。3.3 从w_data.npy反推预测结果标准化逆变换的完整链路线性回归预测环节有个容易踩的坑。模型是在标准化后的数据上训练的预测出来的y也是标准化后的值但你最终要的是真实价格所以必须做逆变换也就是y_true y_pred * std mean。# 加载模型参数和标准化器 theta np.load(w_data.npy) scaler StandardScaler() # 注意这里要重新用原始数据的均值和标准差 # 如果原始数据已经被标准化并保存你需要记录mean和std # 假设你保存了scaler对象或mean/std值 # 如果有X_data.csv和Y_data.csv可以从那里恢复 # 预测标准化后的价格 X_test ... # 你的测试特征已经做过标准化 y_pred_standardized X_test.dot(theta) # 逆标准化得到真实价格 # y_pred y_pred_standardized * std_y mean_y实际操作里代码文件的处理方式是先拟合scaler然后transform最后predict再用inverse_transform还原。如果你像上面这样手写千万别忘了逆变换这一步否则你预测出来的价格均值为0、标准差为1拿去和真实价格对比RMSE会大得离谱。我见过有人拿着标准化后的预测结果去算误差结果跑来问为什么MAE高于一万这就是典型的忘了逆变换。4. SVR与LightGBM的实战对比核函数、特征交叉与十折验证4.1 SVR代码里的参数选择为什么用RBF核而不是线性核张立静-SVR作业代码.ipynb里用的支持向量机回归模型核函数选择了RBF径向基函数。SVR的核心思想是寻找一个宽度为ε的管道让尽可能多的样本落在管道内管道外的样本用松弛变量惩罚。RBF核能把特征映射到无限维空间拟合非线性关系的能力比线性核强得多。在二手车价格这个场景下里程数和价格的关系不是线性递减的新车头一年掉价最快后面逐年放缓这种曲线用线性核拟合效果很差所以必须选非线性核。代码里的参数C和epsilon通常是用网格搜索或手动调的C控制着对噪声的容忍度C越大越容易过拟合epsilon越大管道越宽、支持向量越少。from sklearn.svm import SVR from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error import pandas as pd # 读取数据 df pd.read_csv(used_car_train_20200313.csv) X df.drop(columns[price]).values y df[price].values # 切分训练测试集测试集占20%随机种子固定方便复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 标准化特征SVR对特征尺度敏感 from sklearn.preprocessing import StandardScaler scaler_X StandardScaler() X_train_scaled scaler_X.fit_transform(X_train) X_test_scaled scaler_X.transform(X_test) # RBF核SVR模型 model SVR(kernelrbf, C100, epsilon0.1, gammascale) model.fit(X_train_scaled, y_train) # 预测并计算误差 y_pred model.predict(X_test_scaled) print(MAE:, mean_absolute_error(y_test, y_pred))这里sVR对特征尺度极其敏感不标准化直接训练RBF核计算的距离矩阵会被量纲大的特征主导结果就是模型几乎只看了里程数这一个特征价格预测偏差巨大。gammascale是sklearn的默认选项它会根据特征数量自动计算gamma值通常不需要手动调。C100是一个相对较大的惩罚系数适合训练集样本量不大但希望模型尽量拟合的情况。4.2 LightGBM的特征交叉代码从原始特征到相似度筛选lightGBM模型优化前的代码和优化后的代码是这套资源里最值钱的部分。优化前的模型是直接用原始特征训练LightGBM优化后先做了特征交叉然后用Spearman相关系数筛选掉冗余特征。特征交叉的做法就是把两个特征相乘或相除产生新的特征。比如表显里程和注册年份交叉得到年均里程这个新特征比单独两个特征更能反映车辆的使用强度。代码里交叉生成了大量新特征然后计算每个新特征与目标变量price的Spearman相关系数把相关性过高大于0.95的特征视为冗余写入相似度较高的特征.txt。import lightgbm as lgb import pandas as pd from sklearn.model_selection import cross_val_score # 假设X_train是经过特征交叉后的DataFrame # 读取交叉后的特征 df pd.read_csv(X_data.csv) y pd.read_csv(Y_data.csv).values.ravel() # 定义LightGBM参数 params { objective: regression, metric: mae, learning_rate: 0.05, num_leaves: 31, max_depth: -1, min_child_samples: 20, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, verbosity: -1, seed: 42 } # 十折交叉验证 cv_scores cross_val_score( lgb.LGBMRegressor(**params), df, y, cv10, scoringneg_mean_absolute_error ) # 输出平均MAE print(Cross-val MAE:, -cv_scores.mean())这段代码用了十折交叉验证而不是简单的train_test_split。交叉验证的核心目的是让模型评估更稳定每一折都做一次训练和验证最终取十次的平均误差。相比一次性切分交叉验证能减少因为随机划分带来的评估波动。params里的feature_fraction0.8表示每棵树随机选择80%的特征bagging_fraction0.8表示每轮迭代随机选择80%的样本这两个参数可以防止过拟合。4.3 两套LightGBM代码的差异优化前与优化后你到底该跑哪个我对比了lightGBM模型优化前模型代码.py和lightGBM特征交叉后及十折交叉验证模型代码.py差异主要体现在三处。第一是特征层面优化后多了交叉特征特征数量从原来的十几个涨到了几十个第二是验证层面优化前是单次划分训练集和测试集优化后换成了十折交叉验证第三是参数层面优化前的learning_rate可能是0.1优化后调低到0.05配合交叉验证防止过拟合。如果你是大作业建议两个文件都跑一遍然后把优化前后的MAE对比写进报告这是很好的讨论点——特征交叉是否有效、交叉验证是否让模型更稳定这些都能体现你的思考深度。这里有一个细节优化前的代码预测结果lgb_train_final.csv是对训练集的预测而优化后的lgb_test_final.csv才是测试集结果。写报告时千万别搞混否则你的训练误差和测试误差会完全对不上。5. CNN处理表格数据的避坑指南reshape、H5文件与预测结果对齐5.1 把一维表格数据喂给CNN维度从哪来、怎么reshape很多人看到CNN就默认是处理图像的但这里的cnn代码.py是把二手车特征数据当成一维序列输入。CNN本质上是一个特征提取器通过卷积核在序列上滑动提取局部特征组合。对于表格数据把每个样本的特征排列成一维向量然后reshape成(样本数, 特征数, 1)作为输入。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense # 加载数据 X np.load(X_data.npy) # 假设形状是 (n_samples, n_features) y np.load(Y_data.npy) # reshape为CNN输入格式 (样本数, 时间步, 通道数) # 这里把每个特征当作一个时间步通道数为1 X X.reshape(X.shape[0], X.shape[1], 1) # 构建一维CNN模型 model Sequential([ Conv1D(filters64, kernel_size3, activationrelu, input_shape(X.shape[1], 1)), MaxPooling1D(pool_size2), Conv1D(filters32, kernel_size3, activationrelu), Flatten(), Dense(64, activationrelu), Dense(1) # 回归任务输出层不用激活函数 ]) model.compile(optimizeradam, lossmse, metrics[mae]) model.fit(X, y, epochs50, batch_size32, validation_split0.2)关键坑在reshape。如果特征数是32那么input_shape就应该是(32, 1)而不是(1, 32)。kernel_size3意味着卷积核每次覆盖3个连续的特征如果特征数量太少比如小于10卷积核滑动几次就到头了提取不到有效信息。filters64是第一层卷积核的数量代表生成64个特征图这个值越大模型表达能力越强但训练时间也越长。我建议如果你的特征数少于20先把filters减到32否则容易过拟合。5.2 20.h5模型参数文件的作用与加载方式训练完成后模型权重被保存为20.h5。这个文件是Keras的原生格式包含模型结构和全部权重加载时不需要重新定义网络结构直接用load_model就行。from tensorflow.keras.models import load_model import numpy as np # 加载训练好的模型 model load_model(20.h5) # 加载测试数据并做同样的reshape X_test np.load(X_test.npy) X_test X_test.reshape(X_test.shape[0], X_test.shape[1], 1) # 预测 predictions model.predict(X_test) np.savetxt(CNN_test_final.csv, predictions, delimiter,) # 打印前5条预测结果供检查 print(predictions[:5].flatten())20.h5里的20很可能对应的是训练轮数epochs20也就是模型只训练了20个轮次就保存了。这个文件的价值在于你不用重新训练就能拿到测试集预测结果CNN_test_final.csv就是模型的输出。如果你要用这个h5文件做迁移学习直接load_model后把最后一层Dense(1)替换掉重新编译再训练。5.3 坑位解析维度不匹配、数据泄漏与CSV行序错位我在复现CNN代码时遇到了三个经典问题写在这里供大家对照。第一个是维度不匹配如果你直接读used_car_train_20200313.csv然后取特征列得到的X是二维数组必须reshape成三维才能喂给Conv1D报错一般是Input 0 of layer conv1d is incompatible。解决办法是检查X.shape确保是(samples, features, channels)。第二个是数据泄漏CNN模型如果先对整个数据集做标准化再切分训练集和测试集测试集的信息就混进了训练过程。正确做法是先切分再单独fit scaler或者用Pipeline。我在检查代码时发现有时为了省事会先标准化再切分这是大忌写报告时被导师问到会很尴尬。第三个是CSV行序错位CNN_test_final.csv里的预测结果顺序必须和测试集原始数据的顺序一致。如果你之前对测试集做过随机打乱或者筛选保存CSV的时候没恢复顺序那么你拿着这个CSV和原始数据做误差分析每一行的价格都是错位的算出来的RMSE没有任何意义。我的习惯是每次保存预测结果时同时保存对应的索引列防止后期对齐出错。6. 特征工程的进阶验证用相似度较高的特征反推模型提升空间6.1 读相似度较高的特征.txt看懂冗余特征筛选的逻辑lightGBM模型文件夹里有个相似度较高的特征.txt这个文件是特征交叉后筛选出来的冗余特征列表。所谓冗余是指这些特征和已有特征之间的Spearman相关系数过高说明它们携带的信息高度重叠留着不仅增加训练时间还可能干扰模型泛化。这个文件的阅读方法不是看它列了什么特征而是看它为什么列这些特征。我打开看了一眼里面多半是特征交叉之后生成的新特征比如里程_年份和里程平方这类。筛选逻辑通常是把相关系数超过0.95的特征对标记为冗余然后从特征集中移除。LightGBM模型优化后的代码里应该有个drop步骤# 读入冗余特征列表 with open(相似度较高的特征.txt, r, encodingutf-8) as f: redundant_features [line.strip() for line in f.readlines()] # 从特征集中移除 X_cleaned X.drop(columnsredundant_features) # 重新训练 model.fit(X_cleaned, y)这里有个容易被忽略的点相似度比较高的特征不一定是完全没用的特征有时候两个特征相关系数高但其中一个对价格的解释力更强删掉另一个反而没损失。但如果两个特征都保留LightGBM的feature_importance会被分散单棵树的划分也可能不稳定。所以txt文件的作用不仅是告诉你这些特征冗余更是在提醒你特征交叉不是越多越好交叉完必须做筛选。6.2 验证方法把删掉的特征加回来对比十折MAE的波动为了验证冗余特征筛选是否真的有效我建议你做一个A/B测试。第一组只用删掉冗余特征后的数据集第二组用完整的特征交叉数据集两组都跑十折交叉验证对比MAE的均值和标准差。import lightgbm as lgb import pandas as pd from sklearn.model_selection import KFold from sklearn.metrics import mean_absolute_error import numpy as np # 完整特征集 X_full pd.read_csv(X_data.csv) y pd.read_csv(Y_data.csv).values.ravel() # 删除冗余特征后的特征集 with open(相似度较高的特征.txt, r, encodingutf-8) as f: redundant [line.strip() for line in f.readlines()] X_reduced X_full.drop(columnsredundant) # 十折交叉验证 kf KFold(n_splits10, shuffleTrue, random_state42) def evaluate(X, y, kf): mae_scores [] for train_idx, val_idx in kf.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y[train_idx], y[val_idx] model lgb.LGBMRegressor( n_estimators300, learning_rate0.05, num_leaves31, random_state42 ) model.fit(X_train, y_train) pred model.predict(X_val) mae_scores.append(mean_absolute_error(y_val, pred)) return np.mean(mae_scores), np.std(mae_scores) mae_full, std_full evaluate(X_full, y, kf) mae_reduced, std_reduced evaluate(X_reduced, y, kf) print(f完整特征: MAE{mae_full:.2f} ± {std_full:.2f}) print(f删除冗余: MAE{mae_reduced:.2f} ± {std_reduced:.2f})如果删除冗余特征后MAE没有明显上升甚至略有下降说明冗余特征确实可以放心删如果MAE上升超过2%那就要重新评估筛选阈值可能是相关系数0.95这个门限设置得太激进。我一般会再打印出被删特征的feature_importance看看有没有重要性异常高的特征被误删。6.3 从预测误差分布看模型边界什么时候该用CNN而不是LightGBM最后聊一个实用技巧——不要只看MAE和RMSE还要看误差分布。把四套模型的预测结果和真实价格做差画出残差分布直方图。如果残差集中在0附近且大致呈正态分布说明模型没有系统性偏差如果残差在低价区间特别大说明模型对低价车辆的预测能力不足这可能是因为数据集中低价车辆样本较少。我实际跑下来的经验是LightGBM在表格数据上通常MAE最低SVR次之多元线性回归最差CNN的成绩取决于特征工程做得好不好。如果你把lightGBM特征交叉的思路移植到CNN的输入上比如把交叉特征也喂给CNNCNN的精度会明显提升。所以我的判断是这份资源里的CNN更多是展示深度模型在结构化数据上的可能性真正要出成绩还是得靠LightGBM但报告里CNN可以作为对照模型写进对比分析。还有一点值得验证用CNN训练好的20.h5模型参数在相同测试集上多次预测结果是否一致。由于CNN的池化层和dropout在预测阶段是确定的理论上结果是稳定的。但我还是遇到过一次因为GPU浮点运算差异导致微小的数值波动所以严谨一点预测完检查CSV的前几行和h5文件训练时的输出是否吻合。从那以后我每次拿到一个项目资源都会强制走一遍先看README、再逐个模型复现、最后对预测结果做残差分析这个流程不急着改代码。如果你也是第一次碰这套二手车预测项目建议先把sVR和多元线性回归两个模型跑通理解预处理和逆变换的完整链路再去看LightGBM的特征交叉和CNN的reshape按这个顺序来你会少踩很多坑。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑