二手车价格回归
- 目标
- 项目简述
- 文件描述和字段字典
- 文件描述
- 字段字典
- 项目开始
- 分割线
- 导入第三方库
- 读取数据
- 构建模型
- 总结
- 数据来源:
- 数据处理:
- 模型构建:
- 模型评估:
- 结果应用:
- 写在最后
目标
二手车价格预测项目的目标是构建一个能够准确预测二手车市场价格的模型。这有助于消费者、经销商和金融机构在交易过程中做出更明智的决策。
项目简述
本次项目主要是针对二手车在不同品牌、不同年份、不同工况、出事故的程度以及二手车的最终成色来对相类似的车辆进行预测。本次评估的方法主要是使用均方根误差进行评分,因为本次任务属于回归任务,并非分类任务,所以不能使用AUC和ROC来进行评估。
均方误差(Mean Squared Error, MSE)是衡量模型预测值与真实值之间差异的一种常用方法。它计算了预测误差的平方的平均值,因此,MSE的值越小,表示模型的预测越准确。
定义
假设我们有一组真实值和对应的模型预测值,则均方误差MSE定义为:
其中,n 是样本数量,yi是第 i 个样本的真实值,y^是第 i 个样本的预测值。
推导
均方误差的推导其实并不复杂,因为它直接基于误差的平方和的平均值。但我们可以从更一般的角度去理解它,即它是损失函数的一种形式,用于量化模型预测的不准确性。
误差计算:首先,对于每一个样本,我们计算其预测值与真实值之间的误差.
误差平方:由于误差可能是正也可能是负,直接求和可能会相互抵消,从而无法准确反映预测的整体偏差。因此,我们计算误差的平方.这样做可以确保所有的偏差都被视为正值,从而能够累加。
平均误差平方:最后,为了得到一个能够代表整个数据集预测准确性的单一数值,我们将所有误差的平方求和后除以样本数量 n。
这个过程就是均方误差的推导过程。均方误差作为损失函数,在机器学习和统计建模中被广泛使用,因为它对较大的误差给予了更高的惩罚,这有助于模型在训练过程中更加关注那些预测偏差较大的样本。
文件描述和字段字典
本次比赛(训练和测试)的数据集是根据在 Used Car Price Prediction 数据集上训练的深度学习模型生成的。特征分布与原始分布接近,但不完全相同。在本次竞赛中,您可以随意使用原始数据集,既可以探索差异,也可以了解在训练中加入原始数据集是否能提高模型性能。
文件描述
train.csv - 训练数据集; 是连续目标price
test.csv - 测试数据集;您的目标是预测 for each row 的值price
sample_submission.csv - 正确格式的样本提交文件
字段字典
‘brand’ - 品牌
‘model’ - 型号
‘model_year’ - 车型年份
‘milageage’ - 里程数(注意原字段可能有拼写错误,应为’mileage’)
‘fuel_type’ - 燃料类型
‘engine’ - 发动机
‘transmission’ - 变速器
‘ext_col’ - 外部颜色
‘int_col’ - 内部颜色
‘accident’ - 事故情况
‘clean_title’ - 清晰产权(通常指车辆没有未解决的事故或法律问题)
‘price’ - 价格
项目开始
导入一些必要的库,以便于后续能够正常使用他们,建议使用Jupyter Notebook,可能会存在部分的库没有,请按照下列的方式,打开命令符进行安装,因为使用清华源的库安装的速度更快。
optuna 库:
pip install optuna -i https://pypi.tuna.tsinghua.edu.cn/simple
lightgbm 库:
pip install lightgbm -i https://pypi.tuna.tsinghua.edu.cn/simple
catboost 库:
pip install catboost -i https://pypi.tuna.tsinghua.edu.cn/simple
xgboost库:
pip install xgboost -i https://pypi.tuna.tsinghua.edu.cn/simple
分割线
导入第三方库
# 导入numpy库,用于高效的数组和矩阵运算
import numpy as np
# 导入pandas库,用于数据分析和操作
import pandas as pd
# 从sklearn.base导入clone函数,用于复制估计器
from sklearn.base import clone
# 从sklearn.metrics导入所有度量函数,用于评估模型性能
from sklearn.metrics import *
# 导入seaborn库,用于数据可视化
import seaborn as sns
# 导入matplotlib.pyplot库,用于绘图
import matplotlib.pyplot as plt
# 启用Jupyter Notebook中的matplotlib内联显示
%matplotlib inline
# 导入optuna库,用于超参数优化
import optuna
# 从optuna.samplers导入TPESampler,一种基于树结构的并行参数采样方法
from optuna.samplers import TPESampler
# 从sklearn.model_selection导入所有模型选择工具,如交叉验证等
from sklearn.model_selection import *
# 从sklearn.preprocessing导入所有预处理工具,如标准化、归一化等
from sklearn.preprocessing import *
# 导入lightgbm的LGBMRegressor,一种基于梯度提升框架的回归模型
from lightgbm import LGBMRegressor
# 导入catboost的CatBoostRegressor,另一种基于梯度提升框架的回归模型,特别擅长处理分类特征
from catboost import CatBoostRegressor
# 导入xgboost的XGBRegressor,基于极端梯度提升算法的回归模型
from xgboost import XGBRegressor
# 导入re库,用于正则表达式操作
import re
# 导入random库,用于生成随机数
import random
# 导入time库,用于处理时间相关的操作
import time
# 导入logging库,用于记录日志
import logging
# 从IPython.display导入display和HTML,用于在Jupyter Notebook中显示HTML内容
from IPython.display import display, HTML
# 从IPython.display导入clear_output,用于清除Jupyter Notebook中的输出
from IPython.display import clear_output
# 从tqdm导入tqdm和trange,用于在循环中显示进度条
from tqdm import tqdm, trange
# 导入tabulate库,用于将表格数据以美观的格式打印出来
from tabulate import tabulate
# 导入colorama库中的Fore,用于在控制台输出彩色文本
from colorama import Fore
# 导入warnings库,并设置忽略所有警告
import warnings
warnings.filterwarnings("ignore")
读取数据
# 导入数据
# sample_sub = pd.read_csv(r'C:\Users\0\car_prediction/sample_submission.csv')
train = pd.read_csv(r'C:\Users\0\car_prediction/train.csv')
test = pd.read_csv(r'C:\Users\0\car_prediction/test.csv')
# 数据存放在绑定资源中,大家下载就好了,如果没有,请私信我!!!
# 查看原始数据
train.head(5)
# 去掉[id]列,因为他没有任何作用
train.drop(columns=['id'], inplace=True)
test.drop(columns=['id'], inplace=True)
做完以上的工作之后,我们需要对数据进行处理。
处理低频值:对于特定的分类变量(re_列表中的变量),计算每个值的出现次数,并将出现次数少于某个阈值(t)的值替换为"noise"。这有助于减少分类变量的类别数量,可能有助于某些机器学习模型的性能。
- 处理缺失值:对于所有指定的分类变量(cat_c列表中的变量),将缺失值(NaN)替换为"missing"。这是处理缺失值的一种常见方法,尤其是在分类变量中。
- 转换为category类型:将所有指定的分类变量转换为pandas的category类型。这有助于节省内存,因为category类型使用整数标签来表示不同的类别,而不是存储完整的字符串。此外,在某些情况下,这也可以提高性能,因为某些操作(如分组)在category类型上可能更快。
- 性能测量:使用%%time魔法命令来测量整个函数执行的时间,这在评估代码性能时非常有用。
%%time
# 这行是Jupyter Notebook特有的魔法命令,用于测量接下来代码块的执行时间
def update(df):
"""
更新DataFrame中的分类变量,处理低频值和缺失值。
参数:
df (DataFrame): 需要更新的DataFrame。
返回:
DataFrame: 更新后的DataFrame。
"""
t = 50 # 定义一个阈值,用于判断哪些值是低频的
cat_c = ['brand', 'model', 'fuel_type', 'engine', 'transmission', 'ext_col', 'int_col', 'accident', 'clean_title'] # 需要处理的分类变量列表
re_ = ['model', 'engine', 'transmission', 'ext_col', 'int_col'] # 需要特别处理低频值的分类变量列表
# 遍历需要特别处理低频值的分类变量
for col in re_:
value_counts = df[col].value_counts(dropna=False) # 计算每个值的出现次数,包括NaN
low_freq_values = value_counts[value_counts < t].index # 找出出现次数少于t的值的索引
df.loc[df[col].isin(low_freq_values), col] = "noise" # 将这些低频值替换为"noise"
# 遍历所有分类变量,处理缺失值并转换为category类型
for col in cat_c:
df[col] = df[col].fillna('missing') # 将缺失值替换为'missing'
df[col] = df[col].astype('category') # 将列转换为category类型,这有助于节省内存并可能提高某些操作的性能
return df # 返回更新后的DataFrame
# 确保 train DataFrame 经过必要的数据预处理,以便后续用于模型训练
# 这个过程包括处理 train DataFrame 中的低频值、替换缺失值为 'missing'、以及将指定列转换为 category 类型
train = update(train)
test = update(test)
# 查看处理后的数据,虽然看这没什么区别,但是上面的步骤不能少!!!
train.head(5)
# 数据的分布
train.describe()
# 数据的大小
train.shape
# 这里做了一个转换,主要是变量名有些长,我不太想写
df = train
# 查看列名
df.columns
画图分析,分析每一个特征和价格之间的关系。
# 定义包含数值列的列表,这里假设'milage'应该是'milage',但根据原始代码保持不变
numeric_cols = ['milage', 'price']
# 计算这些数值列之间的相关系数矩阵
corr_matrix = df[numeric_cols].corr()
print("Correlation Matrix for Numerical Variables:")
print(corr_matrix) # 打印出数值变量之间的相关系数矩阵,了解它们之间的线性关系强度
# 使用matplotlib和seaborn创建一个图形窗口,并设置大小
plt.figure(figsize=(32, 24))
# 第一个子图:里程数与价格之间的散点图
plt.subplot(3, 3, 1)
sns.scatterplot(x='milage', y='price', data=df)
plt.title('Milage vs Price') # 显示里程数与价格之间的关系
# 第二个子图:品牌与价格之间的箱型图
plt.subplot(3, 3, 2)
sns.boxplot(x='brand', y='price', data=df)
plt.title('Brand vs Price') # 显示不同品牌与价格之间的分布差异
plt.xticks(rotation=45, ha="right") # 旋转x轴标签,以便于阅读
# 第三个子图:事故情况(是/否)与价格之间的箱型图
plt.subplot(3, 3, 3)
sns.boxplot(x='accident', y='price', data=df)
plt.title('Accident (Yes/No) vs Price') # 显示是否有事故与价格之间的分布差异
# 第四个子图:燃料类型与价格之间的箱型图
plt.subplot(3, 3, 4)
sns.boxplot(x='fuel_type', y='price', data=df)
plt.title('Fuel Type vs Price') # 显示不同燃料类型与价格之间的分布差异
plt.xticks(rotation=45, ha="right") # 旋转x轴标签
# 第五个子图:发动机类型与价格之间的箱型图
plt.subplot(3, 3, 5)
sns.boxplot(x='engine', y='price', data=df)
plt.title('Engine Type vs Price') # 显示不同发动机类型与价格之间的分布差异
plt.xticks(rotation=45, ha="right") # 旋转x轴标签
# 第六个子图:变速器类型与价格之间的箱型图
plt.subplot(3, 3, 6)
sns.boxplot(x='transmission', y='price', data=df)
plt.title('Transmission Type vs Price') # 显示不同变速器类型与价格之间的分布差异
# 第七个子图:外部颜色与平均价格之间的条形图
plt.subplot(3, 3, 7)
ext_col_prices = df.groupby('ext_col')['price'].mean().sort_values() # 计算每种外部颜色的平均价格
ext_col_prices.plot(kind='bar')
plt.title('Exterior Color vs Average Price') # 显示外部颜色与平均价格之间的关系
plt.xticks(rotation=45, ha="right") # 旋转x轴标签
# 第八个子图:内部颜色与平均价格之间的条形图
plt.subplot(3, 3, 8)
int_col_prices = df.groupby('int_col')['price'].mean().sort_values() # 计算每种内部颜色的平均价格
int_col_prices.plot(kind='bar')
plt.title('Interior Color vs Average Price') # 显示内部颜色与平均价格之间的关系
plt.xticks(rotation=45, ha="right") # 旋转x轴标签
# 第九个子图:清晰产权(是/否)与价格之间的箱型图
plt.subplot(3, 3, 9)
sns.boxplot(x='clean_title', y='price', data=df)
plt.title('Clean Title (Yes/No) vs Price') # 显示清晰产权(是否有问题)与价格之间的分布差异
# 调整子图之间的布局,防止重叠
plt.tight_layout()
# 显示整个图形
plt.show()
构建模型
%%time
X = df.drop('price', axis=1)
y = df['price']
# 设置随机种子、折数等参数
SEED = 42
n_splits = 5
# 定义自动机器学习类 Auto_ML
class Auto_ML:
# 初始化方法
def __init__(self, models, n_splits=n_splits, seed=SEED):
self.models = models # 存储要训练的模型列表
self.n_splits = n_splits # 交叉验证的折数
self.seed = seed # 随机种子
self.results = {} # 存储每个模型的评估结果
# 训练所有模型的方法
def Train_M(self, X, y, test):
# 使用KFold进行交叉验证
kf = KFold(n_splits=self.n_splits, shuffle=True, random_state=self.seed)
# 初始化列表,用于存储所有模型的预测结果
all_oof_preds = []
test_preds_list = []
# 遍历所有模型
for model_name, model in tqdm(self.models, desc="Training Models"):
# 初始化每折的预测结果和验证集的RMSE列表
oof_preds = np.zeros(X.shape[0])
test_preds = np.zeros(test.shape[0])
val_rmse_list = []
# 对每个模型进行交叉验证
for fold_idx, (train_index, val_index) in tqdm(enumerate(kf.split(X)), desc=f"Model: {model_name}", total=self.n_splits):
# 划分训练集和验证集
X_train, X_val = X.iloc[train_index], X.iloc[val_index]
y_train, y_val = y.iloc[train_index], y.iloc[val_index]
# 克隆模型以避免在不同折之间共享状态
model_clone = clone(model)
model_clone.fit(X_train, y_train)
# 在验证集上进行预测
val_preds = model_clone.predict(X_val)
oof_preds[val_index] = val_preds
# 计算并存储验证集的RMSE
val_rmse = np.sqrt(mean_squared_error(y_val, val_preds))
val_rmse_list.append(val_rmse)
# 累加模型在测试集上的预测结果
test_preds += model_clone.predict(test)
# 计算测试集上所有折的预测结果的平均值
mean_test_preds = test_preds / self.n_splits
# 计算验证集上所有折的RMSE的平均值
mean_val_rmse = np.mean(val_rmse_list)
# 存储每个模型的评估结果
self.results[model_name] = {
'per_fold_rmse': val_rmse_list,
'mean_rmse': mean_val_rmse
}
# 存储所有模型的OOF预测结果和测试集预测结果
all_oof_preds.append(oof_preds)
test_preds_list.append(mean_test_preds)
# 清除输出,以便只显示最后一个模型的输出(这可能会影响调试)
clear_output(wait=True)
# 显示模型的评估结果
results_df = self.display_results()
# 将测试集预测结果组织成字典
test_preds_dict = {model_name: preds for model_name, preds in zip([model_name for model_name, _ in self.models], test_preds_list)}
# 返回所有模型的OOF预测结果和测试集预测结果字典
return all_oof_preds, test_preds_dict
# 显示模型评估结果的方法
def display_results(self):
# 创建结果数据的索引和列名
fold_columns = [f'Fold {i+1}' for i in range(self.n_splits)]
results_data = {}
# 遍历每个模型的评估结果
for model_name, metrics in self.results.items():
# 如果存在每折的RMSE,则添加到结果数据中
if 'per_fold_rmse' in metrics:
fold_rmse = metrics['per_fold_rmse']
results_data[model_name] = fold_rmse + [metrics['mean_rmse']]
else:
# 如果不存在每折的RMSE,则用NaN填充
results_data[model_name] = [np.nan] * self.n_splits + [metrics['mean_rmse']]
# 将结果数据转换为DataFrame并显示
results_df = pd.DataFrame(results_data, index=fold_columns + ['Mean'])
display(results_df)
# 返回结果DataFrame
return results_df
# 定义LightGBM模型参数
lgb_params ={'learning_rate': 0.017521301504983752, 'max_depth': 42, 'reg_alpha': 0.06876635751774487,
'reg_lambda': 9.738899198284985, 'num_leaves': 131, 'subsample': 0.2683765421728044,
'colsample_bytree': 0.44346036599709887} # Lb 72211
params1 = {'learning_rate': 0.015387355282525047, 'num_leaves': 287, 'max_depth': 10, 'min_child_samples': 32,
'subsample': 0.5678602068076838, 'colsample_bytree': 0.5254867750210618, 'reg_alpha': 8.515713311140541e-05,
'reg_lambda': 9.929128235845939, 'scale_pos_weight': 1.031529653438031, 'max_bin': 2894,
'min_split_gain': 8.135732868325528e-05, 'min_child_weight': 0.9684228603448732,'boosting_type': 'gbdt',
'objective': 'regression','metric': 'rmse'} # Lb : 72247
lgb1 = LGBMRegressor(**lgb_params, random_state=SEED, verbose=-1, n_estimators=200)
lgb2 = LGBMRegressor(**params1, random_state=SEED, verbose=-1, n_estimators=200)
meta_params = {
'num_leaves': 10, 'learning_rate': 0.03234796653849982, 'min_child_samples': 26,
'subsample': 0.9795993216040725
}
# 创建LightGBM模型实例
lgb1 = LGBMRegressor(**lgb_params, random_state=SEED, verbose=-1, n_estimators=200)
lgb2 = LGBMRegressor(**params1, random_state=SEED, verbose=-1, n_estimators=200)
meta_model = LGBMRegressor(**meta_params, objective='regression', metric='rmse', verbose=-1)
# 创建模型列表
models = [
('LGBM_Tunned', lgb1),
('LGBM_Tunned_1', lgb2),
]
# 创建Auto_ML实例并训练模型
Train_m = Auto_ML(models, n_splits=n_splits, seed=SEED)
oof_preds_all, test_preds_dict = Train_m.Train_M(X, y, test)
# 这里主要是查看预测出来的结果
test_preds_dict
# 进行模型融合,通过加权平均的方式将两个不同版本的LightGBM模型的预测结果结合起来,以期获得更准确的预测。
lmp1 = test_preds_dict['LGBM_Tunned']
lmp2 = test_preds_dict['LGBM_Tunned_1']
ep = lmp1*0.8 + lmp2*0.2
# 保存提交
sample_sub['price'] = ep
sample_sub.to_csv(r'C:\Users\0\car_prediction\Submission_Price_result.csv', index=False)
sample_sub.head()
总结
数据来源:
- 原始数据集:项目可能基于一个公开的二手车价格预测数据集,该数据集包含了影响二手车价格的多种特征,如车辆品牌、型号、年份、里程数、车况等。
- 合成数据集:为了竞赛或特定需求,还可能使用基于深度学习模型生成的合成数据集。这些合成数据集的特征分布接近但不完全等同于原始数据集,为模型训练提供了额外的挑战和机会。
数据处理:
- 数据清洗:去除缺失值、异常值,处理不一致的数据格式等。
- 特征工程:根据业务需求和数据特性,可能需要进行特征选择、特征转换(如编码分类变量、标准化或归一化数值变量)等。
- 数据划分:将数据集划分为训练集、验证集和测试集,以便进行模型训练和评估。
模型构建:
- 选择模型:根据项目需求和数据特性选择梯度提升树(如XGBoost、LightGBM)等。
- 模型训练:使用训练集数据对模型进行训练,并通过验证集调整模型参数以优化性能。
- 模型融合:为了提高预测准确性,可能会采用模型融合技术,如加权平均、堆叠(Stacking)等,将多个模型的预测结果结合起来。
模型评估:
- 使用测试集评估模型的性能,常见的评估指标包括均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE)等。
- 分析模型预测结果与实际价格之间的差异,识别可能的误差来源。
结果应用:
- 将训练好的模型部署到实际应用中,如二手车交易平台、金融机构等。
- 根据模型预测结果为用户提供二手车估价服务,辅助交易决策。
写在最后
二手车价格预测项目是一个典型的机器学习应用案例,它结合了数据科学、机器学习技术和业务知识。通过收集和处理二手车相关数据,构建并优化预测模型,最终实现了对二手车价格的准确预测。这个项目不仅有助于提升交易效率,还能为消费者和商家带来更大的价值。同时,它也展示了机器学习技术在解决实际问题中的巨大潜力。