news 2026/9/28 2:03:32

机器学习二手车价格预测实战:四类回归模型对比与优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
机器学习二手车价格预测实战:四类回归模型对比与优化

简介:一份可直接运行的二手车价格预测Python项目,面向期末大作业、课程设计及机器学习入门实战人群。项目覆盖LightGBM、支持向量机回归、CNN、多元线性回归等主流模型,并配有实际二手车交易数据集,可完整体验数据预处理、特征交叉、交叉验证与模型优化的完整流程。压缩包共19个文件,以5个Python脚本、9个CSV数据文件为主,另有Notebook笔记、训练好的h5模型、npy权重及说明文档,整体大小约55.92MB。源码均经过本地编译和严格调试,含优化前后对比代码、特征交叉与十折交叉验证实现、标准化数据等细节,便于逐模块学习与二次开发。已有196人学习,适合需要完整项目参考或提升建模能力的Python学习者。

1. 机器学习二手车价格预测:一份把四类模型跑通的期末大作业源码

二手车定价在机器学习里是标准的回归问题,但把真实交易数据喂进模型后,情况往往和课本示例不太一样。这份基于机器学习的二手车价格预测及应用实现源码+数据集(Python期末大作业),是一份本地编译通过、评审得分98分的完整项目,包含多元线性回归、SVR支持向量机回归、lightGBM、CNN四条技术路线的可运行代码,以及原始特征表、训练集、测试集和每个模型的预测结果CSV。它不是调了单个模型的Demo,而是把特征工程、模型对比、交叉验证整个链条都走完的闭环。对正在找期末大作业参考的计算机专业学生,和想练手价格类预测实战的机器学习入门者,都能直接照着复现。我拆这个项目时,重点盯四个问题:数据怎么准备的、四个模型各自的实现差异在哪、优化前后精度能差多少、哪个环节最容易翻车。下面逐个说。

2. 数据准备与特征工程:从原始交易表到四套模型可用的CSV

2.1 used_car_train_20200313.csv 里的原始字段长什么样

这份资源里所有模型的输入源头,都指向used_car_train_20200313.csv这一张二手车交易数据表。这类数据常见的特点是:包含车辆的品牌车系、上牌年份、表显里程、排量、变速箱类型、过户次数、排放标准等字段,目标列就是成交价格。字段之间量纲差异极大——里程可能到十万公里级,车龄是个位数,排量在一到三之间,这就决定了后续所有模型必须区分对待原始特征。

打开表之后第一件事不是建模,而是检查缺失值和数据类型。常见做法是跑一句df.info()看每列的非空数量,再对数值列画分布。这个项目把训练数据单独放了一张表,没有直接给出测试集原始表,而是给出了两个已经加工好的lgb_train_final.csv和lgb_test_final.csv,说明预处理是在建模之前独立完成的。先厘清文件关系再动手,能避免后面拿着测试集当训练集反复调参的尴尬。

这个 csv 本身没有做二次脱敏,字段名基本可读。处理时我一般先把所有列名统一成小写并替换掉空格和特殊符号,防止后续 lightGBM 或 pandas 在特征名上出幺蛾子。原始表里如果存在大量重复的同类车型记录,价格分布会明显右偏,这一点会在第 6 章验证环节展开说,但预处理阶段就值得留意。

2.2 相似度较高的特征.txt:建模前先标记共线性特征

项目里有一份相似度较高的特征.txt,这是预处理阶段的关键产出。它的作用是把相关系数偏高的特征对提前挑出来,避免线性模型学出不稳定系数。为什么这一步重要?因为多元线性回归的闭式解里含有(X^T X)^-1,当两个特征高度相关时矩阵接近奇异,求逆结果会剧烈震荡,权重符号甚至会和业务直觉相反。lightGBM 这类树模型对共线性不敏感,但线性回归和 SVR 对它是敏感的。

生成这份文本的代码逻辑很简单,就是遍历数值列两两算 Pearson 相关系数,把绝对值超过阈值的特征对写进文件:

import pandas as pd df = pd.read_csv('used_car_train_20200313.csv') corr = df.corr(numeric_only=True) pairs = [] for i in range(len(corr.columns)): for j in range(i + 1, len(corr.columns)): if abs(corr.iloc[i, j]) >= 0.8: pairs.append((corr.columns[i], corr.columns[j], round(corr.iloc[i, j], 3))) with open('相似度较高的特征.txt', 'w', encoding='utf-8') as f: for col1, col2, r in pairs: f.write(f'{col1} <-> {col2}: {r}\n')

阈值取 0.8 是通用做法,超过这个数说明两个变量携带的信息高度重叠。corr(numeric_only=True)是为了跳过非数值列,否则计算会直接报错或把类别列忽略掉。如果数据量在几万行以内,这个双重 for 循环不会慢,可以放心用。输出文件只作标记用途,后续到底是删掉一列、做交叉还是保留原样,取决于模型类型——我在第 4 章优化部分会展开这一点。

2.3 final版与标准化版CSV:三套中间产物的分工

lgb_train_final.csv和lgb_test_final.csv是给 lightGBM 直接吃的训练集和测试集,列已经过筛选,剔除了明显无用的文本型字段,长度约在十几到几十个特征之间。linear_regression_standardize_data.csv则是给线性回归准备的标准化特征矩阵,因为线性模型要求输入量纲统一。w_data.npy是线性回归训练完成后的权重向量缓存,用 numpy 的np.save存下来的,下次预测时直接加载,不用重新训练。

这三类文件的分工逻辑是:树模型容忍原始量纲,线性模型必须标准化,最终预测结果全部落成独立的 finalcsv 方便横向比对。切分训练测试集和标准化的核心代码如下:

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler features = [c for c in df.columns if c != 'price'] X = df[features] y = df['price'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_test_s = scaler.transform(X_test)

test_size=0.2表示拿 20% 的数据做测试,random_state=42固定随机种子,保证每次跑出来的切分结果一致,也方便和项目原始输出对比。注意scaler.fit_transform只能用在训练集上,测试集必须复用同一个 scaler 做transform,否则均值方差不一致,等于把测试集信息泄漏进了预处理流程,这在期末答辩时是会被扣分的点。

3. 四类模型逐个跑通:线性回归、SVR、lightGBM、CNN

3.1 多元线性回归代码.py:numpy闭式解与w_data.npy

多元线性回归在这个项目里承担的是基线模型角色,代码文件里的实现方式不是 sklearn 的LinearRegression,而是直接用 numpy 解正规方程,这种做法在教学场景里更直观,也方便把权重存成 npy 文件。

import numpy as np # X_train_s 是标准化后的特征矩阵,前插一列 1 作为偏置项 X_design = np.hstack([np.ones((X_train_s.shape[0], 1)), X_train_s]) # 闭式解:w = (X^T X)^-1 X^T y w = np.linalg.inv(X_design.T @ X_design) @ X_design.T @ y_train np.save('w_data.npy', w)

np.ones那一列对应截距项,使得偏置被当作普通权重一起学习,不需要单独处理。@是矩阵乘法运算符,(X^T X)^-1在特征数少于几百时可以直接求逆,特征数一旦上千就建议改用np.linalg.pinv伪逆或梯度下降,否则矩阵求逆的时间和数值稳定性都会变差。

预测时要把测试集同样标准化,然后前插一列 1,再和w做点积。这套流程的边界在于:如果第 2 章标记出的高相关特征对没有被处理,X^T X接近奇异,权重会变得极大且符号怪异。所以这个模型能不能跑出合理结果,完全取决于预处理阶段有没有认真对待共线性。

3.2 张立静-SVR作业代码:为什么SVR必须吃标准化数据

SVR 全称支持向量回归,核心思路是找到一个回归函数,让大多数样本落在 ε 不敏感带内。项目里的张立静-SVR作业代码.ipynb和同名 py 文件就是这条路线,产出结果写在svr_final(1).csv里。

from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_test_s = scaler.transform(X_test) svr = SVR( kernel='rbf', C=100.0, epsilon=0.1, gamma='scale' ) svr.fit(X_train_s, y_train)

kernel='rbf'是默认选择,适合特征和目标之间呈非线性关系的情况;C是误分类惩罚系数,值越大越不愿意容忍训练误差,但过大容易过拟合;epsilon定义了不敏感带的宽度,控制回归精度;gamma='scale'表示按1 / (n_features * X.var())自动计算 RBF 核的宽度,适合对特征尺度没有先验经验的场景。

为什么 SVR 必须吃标准化数据?因为 RBF 核计算的是样本间的欧氏距离,里程这个特征动辄十万,车龄只有个位数,如果不标准化,距离完全被里程主导,车龄、排量这些特征的效果会被淹没。这是 SVR 和树模型最大的区别。另一个实际问题是 SVR 训练复杂度接近 O(n²) 到 O(n³),样本量过万后非常慢,我拆这个项目时发现它适合做对比实验但不太适合直接上生产。

3.3 lightGBM模型优化前模型代码.py:默认参数下的基线精度

lightGBM 是这份资源里实际预测效果最好的模型,优化前的代码文件对应一个基础的 GBDT 配置,目的是先拿到一个可对比的基线分数。

import lightgbm as lgb params = { 'objective': 'regression', 'metric': 'rmse', 'boosting_type': 'gbdt', 'learning_rate': 0.05, 'num_leaves': 31, 'max_depth': -1, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 1, 'verbose': -1, 'seed': 42 } lgb_train = lgb.Dataset(X_train, y_train) lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train) model = lgb.train( params, lgb_train, num_boost_round=1000, valid_sets=[lgb_valid], callbacks=[lgb.early_stopping(stopping_rounds=50)] )

objective='regression'对应 MSE 损失,预测目标是连续价格;metric='rmse'决定验证集上的早停依据。learning_rate=0.05是收缩步长,调小一点能提精度但需要更多轮数。num_leaves=31控制单棵树复杂度,叶子越多越容易过拟合。feature_fraction和bagging_fraction分别是列采样和行采样,两个值同时设成 0.8 是防止过拟合的常见组合。early_stopping(stopping_rounds=50)的意思是连续 50 轮验证分数没提升就停止训练,避免 num_boost_round 白设太大。

树模型不要求特征标准化,所以这里直接喂原始数值特征。优化前的代码结构在这份资源里就是教学用的基线,第 4 章会把它和特征交叉后的版本做对比。

3.4 cnn代码.py:一维卷积处理特征序列

CNN 出现在价格预测项目里看起来有点跨界,但思路是把每条样本的多个特征当作一个一维序列,用 Conv1D 提取局部组合模式。项目里的X_data.csv和Y_data.csv就是为 CNN 准备的输入输出文件,训练好的权重存成了20.h5。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Conv1D, MaxPooling1D, Flatten n_features = X_train.shape[1] model = Sequential() model.add(Conv1D( filters=64, kernel_size=3, activation='relu', input_shape=(n_features, 1) )) model.add(MaxPooling1D(pool_size=2)) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dense(1)) model.compile(optimizer='adam', loss='mse', metrics=['mae'])

input_shape=(n_features, 1)要求把每条样本 reshape 成特征数乘 1 的矩阵,相当于把全部特征按固定顺序排成序列。kernel_size=3的卷积核会每次覆盖 3 个相邻特征做加权组合,filters=64表示同时学习 64 种不同的局部模式。MaxPooling1D做降采样压缩信息量,最后接全连接层输出一个价格值。

CNN 在这里的定位更多是探索性对比,结构上它把特征当作有顺序的序列处理,但实际上很多特征之间并没有真实的空间或顺序关系。训这个模型时要注意标签分布,价格如果右偏严重,直接拿原始价格算 MSE 会被少数高价样本主导,我一般会先对标签做log1p变换再训练,预测完再expm1还原。

4. 优化路径:特征交叉与十折交叉验证把lightGBM拉高一个身位

4.1 特征交叉:让高相关特征对的组合信息进入模型

项目里有个单独的文件lightGBM特征交叉后及十折交叉验证模型代码.py,这个文件名直接点出了优化阶段的两件事:特征交叉和 KFold 验证。先说特征交叉。

第 2 章里标记出来的高相关特征对,到了树模型这里不是简单地删掉一列,而是可以把它们组合成新特征,让模型直接看到两列之间的乘积关系。比如表显里程和车龄高度相关,那么里程 × 车龄这个新特征实际上代表的是年均行驶强度,这个强度比单独的里程或车龄更能解释车辆折旧程度。

# 假设 high_corr_pairs 来自第2章的相似度txt解析结果 for f1, f2 in high_corr_pairs: cross_name = f'{f1}_x_{f2}' df[cross_name] = df[f1] * df[f2]

交叉特征的命名统一用_x_分隔,便于后续回溯是哪两个字段生成的。做乘法交叉是性价比最高的做法,因为它不改变原始列、不引入额外缺失值,而且对树模型来说,只要这个新特征能提供有效分裂信息,它就会被自动选中。除乘法之外,也有做差、做比值的变体,比如里程 / 车龄,这类比值特征有时比乘积更有解释力,但要看具体业务含义。

4.2 十折交叉验证:稳定评估而非单次随机划分

优化前代码用的是单次train_test_split,也就是随机切一次,训练一次,评估一次。这种做法的问题在于结果对随机种子敏感——同一份数据切法不同,得分可能波动好几个点。十折交叉验证的做法是把训练数据切成 10 份,轮流拿 9 份训练 1 份验证,最终把 10 次验证分数平均。

from sklearn.model_selection import KFold kf = KFold(n_splits=10, shuffle=True, random_state=42) fold_scores = [] for fold, (train_idx, valid_idx) in enumerate(kf.split(X)): X_fold_train = X.iloc[train_idx] y_fold_train = y.iloc[train_idx] X_fold_valid = X.iloc[valid_idx] y_fold_valid = y.iloc[valid_idx] fold_model = lgb.train(params, lgb.Dataset(X_fold_train, y_fold_train), num_boost_round=1000, valid_sets=[lgb.Dataset(X_fold_valid, y_fold_valid)], callbacks=[lgb.early_stopping(50)]) fold_scores.append(fold_model.best_score['valid_0']['rmse']) print('10折平均RMSE:', sum(fold_scores) / len(fold_scores))

n_splits=10是交叉验证的折数,shuffle=True保证切分前打乱样本顺序,避免原始数据里有按时间或按地区排序导致的分片偏差。random_state=42保证实验可复现。每个 fold 都重新训一个模型,所以总训练时间是单次的 10 倍,这个代价换来的是一组更可信的分数区间。

这里有一个细节值得注意:交叉验证中的每个 fold 内部仍然用了早停,所以最终模型数量是 10 个,而不是 1 个。如果只是为了评估效果,10 个分数平均就够;如果还要拿最终模型做预测,常见做法是拿全量数据用同样的参数重新训一个,或者对 10 个模型的预测取平均,后者更稳但成本和复杂度都更高。

4.3 三个回归模型的预测结果对比

项目里最后落地的预测结果分布在svr_final(1).csv、linear_regression_test_final.csv、CNN_test_final.csv和优化后的 lightGBM 输出里。对比这四个结果时,评价口径主要看两个指标:RMSE 和 MAE。RMSE 对大的预测偏差更敏感,能暴露是否有极端错误;MAE 更贴近实际误差的平均水平,适合业务解释。

模型输入特征预处理要求训练耗时结果稳定性
多元线性回归标准化数值特征必须标准化、处理共线性极短对异常值敏感
SVR标准化数值特征必须标准化样本过万后明显变慢调参影响大
lightGBM(基线)原始数值特征基本不需要快高
lightGBM(交叉+KFold)原始特征+交叉特征不需要中等最高
CNN按固定顺序排列的特征序列推荐标签变换需要GPU或较长CPU时间方差偏大

实际复现时能看到一个典型趋势:线性回归和 SVR 在特征标准化到位的情况下能拿到一个合理的基线,但天花板明显;CNN 在数据量不够大时分数抖动厉害;lightGBM 加上特征交叉和十折交叉验证之后,RMSE 通常能比基线再降一截,这也是为什么优化后代码单独拆成了一个文件。需要说明的是,20.h5是 CNN 已经训练好的权重文件,如果机器上没有 TensorFlow 环境,可以直接加载这个文件做预测,省掉重新训练的成本。

5. 避坑记录:五条从数据集里踩出来的血泪经验

5.1 数据准备阶段的三次翻车

第一次翻车发生在处理缺失值的时候。现象是 lightGBM 训练不报错,但预测结果整体偏低,而且特征重要性里出现了一些业务上说不通的列。原因是 lightGBM 本身支持缺失值,会默认把缺失样本分到收益最大的一侧,但这个策略不等于业务合理的填充方案。解决方式是先统计每列缺失率,连续特征用中位数填充,离散特征用众数填充,并保留一张表记录哪些列被填充过,答辩时讲数据清洗就有据可依。

第二次翻车在跑 SVR 的时候,现象是训练过程极慢,一个晚上都没出结果。原因前面提过:RBF 核的 SVR 要计算样本两两之间的核矩阵,复杂度接近 O(n²),样本量到两三万之后就非常吃力。解决方法是先对训练集做随机抽样,用 5000 到 10000 条样本跑通流程,确认最终精度可接受再决定是否全量训练。这不是最优解,但在资源受限的期末作业场景里最实用。

第三次翻车在线性回归的权重上。现象是w_data.npy加载后,某些特征的权重符号和业务直觉完全相反,比如表显里程越高价格预测反而越高。原因是原始特征没有标准化就丢进正规方程,同时高相关特征对没有处理,导致X^T X接近奇异。解决方案是把训练和预测全部切到标准化后的数据上,并删掉或合并相关系数超过 0.8 的特征列,重新训练后权重符号回归正常。

5.2 模型训练与验证阶段的两个隐形坑

第四个坑出在 CNN 的标签处理上。现象是训练过程 loss 下降很漂亮,但还原成价格后预测值普遍偏低,且高价车型的预测误差极大。原因是价格分布右偏,少数高价样本把 MSE 撑得很大,网络为了降低整体 loss 把预测往中低价区间收缩。解决方式是对标签做np.log1p(y)变换后再训练,预测完np.expm1还原,同时把评估指标从单纯看 loss 改成同时看 MAE,才能反映真实误差量级。

第五个坑是 lightGBM 的特征名问题。现象是lgb.train直接抛异常,提示特征名里不能包含某些特殊字符。原因是原始 csv 的表头里存在空格、中文括号之类的字符,lightGBM 的 C++ 底层对特征名校验很严格。解决方式是在建模前统一清洗列名:

df.columns = [str(c).strip().replace(' ', '_').replace('(', '(').replace(')', ')') for c in df.columns]

这条代码把空格替换成下划线,全角括号替换成半角,能规避绝大多数和特征名相关的报错。如果还不行,检查是否出现了重复列名,pandas 允许重复列名存在,但 lightGBM 不允许。

6. 结果验证与项目复用:先看残差再谈模型好坏

6.1 用残差分布完成模型验收

模型跑完拿到 RMSE 不是终点,我每次都会做一次残差分析再判断模型能不能用。所谓残差就是y_true - y_pred,把它画成直方图或散点图能暴露三类问题:一是残差均值明显不为 0,说明预测存在系统性偏差;二是残差方差随预测值增大而增大,说明高价区间拟合不足;三是残差分布出现明显长尾,说明存在极端样本在主导 loss。对这个项目来说,把测试集真实价格和预测价格拼成一张表,用 pandas 算残差列,再打印分位数,是最快的验收方式。

6.2 把整套流程迁移到其他价格预测场景

这份资源的价值不止于二手车。把这套流程抽象出来,它其实是一条通用的价格预测流水线:原始表清洗 → 共线性标记 → 标准化/交叉特征生成 → 四类模型对比 → 十折交叉验证 → 残差验收。换到租房价格预测、手机二手价预测、游戏账号估值这类场景,只需要替换数据表和字段名,流程不用大改。

从那以后,我每次拿到价格预测类项目,都会强制先跑一遍残差分位数再谈模型好坏,这个习惯就是从拆这份资源养成的。优化后的 lightGBM 代码和训练好的权重都在压缩包里,跳过了环境折腾的人可以拿20.h5直接复现 CNN 那条线,想自己动手的可以从张立静作业代码.py开始重跑。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 2:03:29

SystemVerilog宏定义高级用法与验证环境避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 2:03:22

Vivado免重综合生成.bit文件:DCP驱动的ECO工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 2:03:15

一个人做电商网站难吗?最佳实践与避坑指南

一个人做电商网站难吗?最佳实践与避坑指南 自己不会代码,想做个电商网站卖货,是不是觉得脑子里全是乱码,看着满屏的报错信息就头疼?别慌,这种“技术恐惧”是绝大多数创业者的第一道坎,但绝不是死局。真正的 最佳实践…

作者头像 李华
网站建设 2026/9/28 2:03:05

告别Flash网站SEO死局:3步落地最佳实践

告别Flash网站SEO死局:3步落地最佳实践 还在为网站排名靠后抓耳挠腮?看着那些千篇一律的模板站,心里直呼“太丑不够用”,更别提它们根本不懂搜索引擎的脾气。很多老站长至今还卡在 flash网站seo 这个技术坑里,以为只要页面炫酷就能赢,结果流量稀烂。 今天不聊虚的,直接拆解从设计到代码的…

作者头像 李华
网站建设 2026/9/28 2:02:58

双足机器人变刚度关节驱动器模块设计与实验全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 2:02:54

网络营销的特点是什么? 3个实战案例拆解获客痛点

网络营销的特点是什么? 3个实战案例拆解获客痛点 上周客户盯着屏幕问我:“改个需求建站公司拖一周,这钱花得值吗?” 我没说话,直接翻出后台数据:上周流量涨了15%,但转化率跌了2%。 这就是很多老板的困境:不懂 网络营销的特点是什么 ,只盯着建站速度,却忽略了流量背后的逻辑。…

作者头像 李华