简介:面向机器学习初学者的二手车交易价格预测评估系统完整项目,基于 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%才考虑填充,否则直接删除该样本。另外,"注册年份"和"表显里程"这两个字段的量纲差异极大,如果不做标准化直接丢进SVR,RBF核函数的尺度参数会被里程数主导,价格预测会严重失真。
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_size=0.2, random_state=42 ) # 标准化特征,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(kernel='rbf', C=100, epsilon=0.1, gamma='scale') 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核计算的距离矩阵会被量纲大的特征主导,结果就是模型几乎只看了里程数这一个特征,价格预测偏差巨大。gamma='scale'是sklearn的默认选项,它会根据特征数量自动计算gamma值,通常不需要手动调。C=100是一个相对较大的惩罚系数,适合训练集样本量不大但希望模型尽量拟合的情况。
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, cv=10, scoring='neg_mean_absolute_error' ) # 输出平均MAE print('Cross-val MAE:', -cv_scores.mean())这段代码用了十折交叉验证,而不是简单的train_test_split。交叉验证的核心目的是让模型评估更稳定,每一折都做一次训练和验证,最终取十次的平均误差。相比一次性切分,交叉验证能减少因为随机划分带来的评估波动。params里的feature_fraction=0.8表示每棵树随机选择80%的特征,bagging_fraction=0.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(filters=64, kernel_size=3, activation='relu', input_shape=(X.shape[1], 1)), MaxPooling1D(pool_size=2), Conv1D(filters=32, kernel_size=3, activation='relu'), Flatten(), Dense(64, activation='relu'), Dense(1) # 回归任务,输出层不用激活函数 ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) model.fit(X, y, epochs=50, batch_size=32, validation_split=0.2)关键坑在reshape。如果特征数是32,那么input_shape就应该是(32, 1),而不是(1, 32)。kernel_size=3意味着卷积核每次覆盖3个连续的特征,如果特征数量太少(比如小于10),卷积核滑动几次就到头了,提取不到有效信息。filters=64是第一层卷积核的数量,代表生成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"很可能对应的是训练轮数epochs=20,也就是模型只训练了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', encoding='utf-8') as f: redundant_features = [line.strip() for line in f.readlines()] # 从特征集中移除 X_cleaned = X.drop(columns=redundant_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', encoding='utf-8') as f: redundant = [line.strip() for line in f.readlines()] X_reduced = X_full.drop(columns=redundant) # 十折交叉验证 kf = KFold(n_splits=10, shuffle=True, random_state=42) 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_estimators=300, learning_rate=0.05, num_leaves=31, random_state=42 ) 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的输入上,比如把交叉特征也喂给CNN,CNN的精度会明显提升。所以我的判断是:这份资源里的CNN更多是展示深度模型在结构化数据上的可能性,真正要出成绩还是得靠LightGBM,但报告里CNN可以作为对照模型写进对比分析。
还有一点值得验证:用CNN训练好的20.h5模型参数,在相同测试集上多次预测结果是否一致。由于CNN的池化层和dropout在预测阶段是确定的,理论上结果是稳定的。但我还是遇到过一次因为GPU浮点运算差异导致微小的数值波动,所以严谨一点,预测完检查CSV的前几行和h5文件训练时的输出是否吻合。
从那以后我每次拿到一个项目资源,都会强制走一遍"先看README、再逐个模型复现、最后对预测结果做残差分析"这个流程,不急着改代码。如果你也是第一次碰这套二手车预测项目,建议先把sVR和多元线性回归两个模型跑通,理解预处理和逆变换的完整链路,再去看LightGBM的特征交叉和CNN的reshape,按这个顺序来,你会少踩很多坑。希望帮到你。
本文还有配套的精品资源,点击获取