ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

二手车估价建模实战:从数据预处理到随机森林Baseline构建

二手车估价建模实战:从数据预处理到随机森林Baseline构建 1. 项目概述与核心目标最近在复盘一个挺有意思的数学建模项目是2021年MathorCup高校数学建模挑战赛的A赛道题目是关于二手车估价问题。这个项目可以说是数据科学和机器学习在商业领域一个非常经典的落地场景它把一堆看似杂乱无章的二手车信息比如品牌、车龄、里程数、排量、过户次数等等转化成一个可以量化的、相对准确的估价。很多朋友可能觉得数学建模或者机器学习离自己很远其实不然这个二手车估价问题本质上就是一个回归预测问题和我们平时用数据预测房价、预测销量在核心逻辑上是一脉相承的。今天我就以这个赛题的第一问为蓝本和大家深入聊聊从拿到原始数据到构建一个可用模型的全过程也就是数据预处理、特征工程和模型训练这条完整链路。我会重点分享一个稳健的Baseline基线模型构建思路并附上关键的数据处理代码和思考过程希望能给正在入门数据科学或者准备参加类似竞赛的朋友一些实实在在的参考。这个项目的核心目标非常明确给定一批二手车的多维度特征数据我们需要训练一个模型使其能够根据这些特征尽可能准确地预测出该辆二手车的交易价格。这听起来简单但实操起来从原始数据到最终模型中间每一步都藏着不少“坑”。数据里可能有缺失值、异常值特征之间量纲不统一有些特征对价格的影响是非线性的还有些特征可能是冗余的……如果不经过妥善处理直接把原始数据丢给模型效果往往惨不忍睹。因此一个标准的流程通常包括数据预处理清洗和准备数据、特征工程从原始数据中提炼出对预测更有用的信息和模型训练选择并优化算法。我们构建的Baseline就是这条标准流程下第一个能跑通、效果尚可的模型版本它为后续的深度优化提供了一个可靠的起点和对比基准。2. 数据预处理从“脏数据”到“干净数据”数据预处理是整个建模流程的基石地基没打牢后面盖的楼再漂亮也可能塌。对于二手车数据我们首先得理解数据“脏”在哪里然后有针对性地进行清洗。2.1 数据加载与初步探索第一步永远是先看看我们手里有什么牌。通常我们会用Pandas来加载数据并进行快速的探索性数据分析EDA。import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 假设数据文件为 ‘used_cars.csv‘ df pd.read_csv(‘used_cars.csv‘) # 查看数据概览 print(“数据形状”, df.shape) # 查看有多少行样本和列特征 print(“\n前5行数据”) print(df.head()) print(“\n数据基本信息”) print(df.info()) # 查看每列的数据类型和非空值数量 print(“\n数值型特征的统计描述”) print(df.describe())运行df.info()能立刻告诉我们哪些列有缺失值。df.describe()则展示了数值型特征如价格、里程、车龄的均值、标准差、最小值、最大值和分位数这对于发现异常值至关重要。例如如果发现“行驶里程”的最大值是9999999公里这显然是不合理的异常值。2.2 缺失值处理二手车数据中缺失值很常见。比如“变速箱类型”可能为空“车辆颜色”可能没记录。处理缺失值没有放之四海而皆准的方法需要根据特征的重要性和缺失比例来决定。1. 删除法如果某个特征缺失率极高比如超过50%并且业务上认为其不重要可以考虑直接删除该列。如果某些样本在关键特征上缺失也可以考虑删除整行但要注意样本量是否足够。2. 填充法这是更常用的方法。数值型特征常用均值、中位数或众数填充。对于价格、里程等用中位数填充比均值更稳健因为中位数对异常值不敏感。例如df[‘mileage‘].fillna(df[‘mileage‘].median(), inplaceTrue)。分类型特征如品牌、车型常用众数出现最频繁的类别填充或者直接填充一个“未知”类别。例如df[‘brand‘].fillna(‘Unknown‘, inplaceTrue)。更高级的方法可以使用模型预测来填充比如用KNN或随机森林基于其他特征来预测缺失值但这在Baseline阶段可能过于复杂。注意填充缺失值是一个需要谨慎的步骤。特别是对于目标变量价格绝对不能填充如果价格缺失这个样本通常不能用于有监督学习应考虑删除或专门处理。2.3 异常值检测与处理异常值会严重扭曲模型的训练尤其是对线性模型和基于距离的模型。二手车数据中异常值可能表现为1块钱的保时捷或者100万公里的“新车”。检测方法描述统计法通过describe()查看max,min结合业务常识判断。3σ原则/箱线图法对于近似正态分布的数据可以将超出均值±3倍标准差的值视为异常。更通用的是箱线图将小于Q1-1.5IQR或大于Q31.5IQR的数据点视为异常值IQR是四分位距。处理方法删除直接删除异常样本。适用于异常值很少且明显是错误记录的情况。修正如果怀疑是录入错误比如多写了一个0可以根据业务逻辑修正为合理值。盖帽法将超出特定分位数如1%和99%的值用该分位数的值替代。这是一种温和的处理方式。# 对‘price‘列进行1%和99%分位数的盖帽处理 lower_bound df[‘price‘].quantile(0.01) upper_bound df[‘price‘].quantile(0.99) df[‘price‘] df[‘price‘].clip(lower_bound, upper_bound)分箱将连续值离散化到几个区间中异常值会被归入最高或最低的箱子里减弱其影响。2.4 数据类型转换与编码模型只能处理数值所以我们必须将非数值特征转化为数值。1. 有序分类变量编码比如“车况”分为“精品”、“良好”、“一般”、“较差”这些类别有内在的顺序。我们可以使用LabelEncoder或自定义映射进行序数编码。python condition_map {‘精品‘: 4, ‘良好‘: 3, ‘一般‘: 2, ‘较差‘: 1} df[‘condition_encoded‘] df[‘condition‘].map(condition_map)2. 无序分类变量编码独热编码比如“颜色”红、白、黑、“品牌”大众、丰田、本田这些类别没有顺序关系。直接映射成1,2,3会让模型误以为它们有大小关系。这时必须使用独热编码One-Hot Encoding。python # 使用pandas的get_dummies进行独热编码注意要删除原列并避免虚拟变量陷阱 df pd.get_dummies(df, columns[‘color‘, ‘brand‘], prefix[‘color‘, ‘brand‘], drop_firstTrue) # drop_firstTrue可以避免多重共线性问题实操心得对于类别数量特别多的特征如“车型”可能有上千种独热编码会导致特征维度爆炸。此时可以考虑使用频次编码用该类别的出现频次代替、目标编码用该类别下目标变量的均值代替但要小心数据泄露或者干脆在Baseline阶段先不使用该特征。3. 日期时间处理如果“上牌日期”是字符串需要转换为datetime类型然后可以提取出“车龄”当前年份-上牌年份这个更有用的特征。python df[‘registration_date‘] pd.to_datetime(df[‘registration_date‘]) current_year pd.Timestamp.now().year df[‘car_age‘] current_year - df[‘registration_date‘].dt.year df.drop(‘registration_date‘, axis1, inplaceTrue) # 提取后可以删除原日期列3. 特征工程挖掘数据背后的“金矿”特征工程是机器学习项目成败的关键其目标是创造对预测目标更有信息量的特征。好的特征能让简单模型表现优异坏的特征则会让复杂模型也无能为力。3.1 特征构造这是基于领域知识创造新特征的过程对于二手车估价尤为重要。品牌溢价特征单纯有“品牌”类别还不够。我们可以根据市场认知构造一个“品牌档次”特征如豪华、合资、自主或者直接引入该品牌新车的平均价格作为参考。功率重量比如果有“排量”或“功率”以及“车重”数据可以计算“功率重量比”这可能比单纯的排量更能反映车辆性能。保值率相关特征车龄和里程是影响保值率的核心。可以构造“年均行驶里程”总里程/车龄来反映车辆使用强度。甚至可以根据公开的该车型保值率曲线估算一个理论残值作为特征。布尔特征从描述文本中提取信息如“是否有天窗”、“是否带导航”、“是否有事故记录”转化为0/1特征。3.2 特征变换对现有特征进行数学变换以改善其与目标变量的关系或满足模型假设。对数变换对于价格、里程等右偏长尾分布的数据进行对数变换可以使其分布更接近正态分布有助于稳定模型方差并可能使与目标的关系更线性。df[‘log_price‘] np.log1p(df[‘price‘]) # 使用log1p防止对0取对数 # 注意如果目标变量‘price‘做了变换最终预测结果需要逆变换回来多项式特征如果怀疑特征与价格之间存在非线性关系如车龄对价格的影响可能先快后慢可以创建车龄的平方项、立方项与模型交互。但要注意这会增加特征维度可能引发过拟合在Baseline阶段需谨慎使用。分箱离散化将连续特征如“车龄”分成几个区间如0-3年4-6年7-10年10年以上有时能让模型更容易捕捉到阶段性的变化规律。3.3 特征选择不是所有特征都是有益的。冗余或无关的特征会增加模型复杂度降低训练速度甚至导致过拟合。过滤法基于特征与目标变量的统计关系进行筛选。方差选择删除方差接近于0的特征即该特征在所有样本上取值几乎相同。相关系数计算每个特征与目标价格的相关系数对于回归问题用皮尔逊系数。可以删除与目标相关性极低的特征。同时也要检查特征之间的相关性如果两个特征高度相关多重共线性可以考虑删除其中一个。# 计算特征与目标的相关性 correlation_matrix df.corr() target_correlation correlation_matrix[‘price‘].abs().sort_values(ascendingFalse) # 可以设定一个阈值比如保留相关性大于0.05的特征包裹法如递归特征消除RFE通过模型性能来评价特征子集的好坏。效果较好但计算成本高Baseline阶段可以不用。嵌入法利用模型训练过程本身来评估特征重要性如Lasso回归的系数、树模型如随机森林的特征重要性。这是Baseline后阶段优化的利器。from sklearn.ensemble import RandomForestRegressor model RandomForestRegressor(n_estimators100, random_state42) model.fit(X_train, y_train) importances model.feature_importances_ # 将特征重要性排序并可视化可以辅助决定保留哪些特征4. 模型训练Baseline构建完成预处理和特征工程后我们得到了一个干净的数据集。现在可以开始构建第一个Baseline模型了。Baseline的目标是快速建立一个性能尚可、流程完整的模型作为后续优化的基准。4.1 数据划分首先必须将数据划分为训练集和测试集有时还会再分一个验证集。绝对不能用全部数据来训练和评估否则无法评估模型的泛化能力即对新数据的预测能力。from sklearn.model_selection import train_test_split # 假设X是特征DataFramey是目标价格Series X df.drop(‘price‘, axis1) # 特征 y df[‘price‘] # 目标 # 常用70%-80%作为训练集20%-30%作为测试集 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) print(f“训练集大小{X_train.shape}, 测试集大小{X_test.shape}“)重要提示random_state参数用于固定随机种子确保每次运行划分结果一致这对结果复现至关重要。4.2 特征缩放许多机器学习算法如K近邻、支持向量机、神经网络以及基于梯度下降的线性模型对特征的尺度非常敏感。如果“价格”几十万和“车龄”个位数尺度相差巨大模型会过度关注大尺度的特征。因此需要进行特征缩放。标准化将特征缩放为均值为0标准差为1。适用于特征分布近似正态的情况。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 在训练集上拟合缩放器并转换 X_test_scaled scaler.transform(X_test) # 使用训练集的参数转换测试集避免数据泄露归一化将特征缩放到一个固定的范围通常是[0, 1]。对存在异常值的数据不太稳健。核心原则缩放器scaler必须只在训练集上fit然后用这个fit好的参数去转换训练集和测试集。绝对不能用全部数据fit也不能用测试集的信息去fit缩放器否则就造成了“数据泄露”会严重高估模型性能。4.3 Baseline模型选择与训练对于回归问题Baseline模型通常选择简单、快速、可解释性强的模型。线性回归最简单的Baseline。如果特征工程做得好线性回归可能就有不错的效果。它还能提供特征的系数帮助我们理解哪个特征对价格影响大。决策树回归对数据分布假设少能自动处理非线性关系无需特征缩放。但单棵树容易过拟合。随机森林回归是构建Baseline的强力选择。它通过集成多棵决策树来降低过拟合风险通常能取得比线性回归更好的效果且能输出特征重要性。虽然训练比单模型慢但对于中等规模数据完全可以接受。这里我们选择随机森林回归作为Baseline模型因为它稳健且通常表现不错。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 初始化模型使用一些合理的默认参数 baseline_model RandomForestRegressor(n_estimators100, # 树的数量 max_depthNone, # 树的最大深度先不限制 min_samples_split2, min_samples_leaf1, random_state42) # 固定随机种子 # 在训练集上训练模型 baseline_model.fit(X_train_scaled, y_train) # 在训练集和测试集上进行预测 y_train_pred baseline_model.predict(X_train_scaled) y_test_pred baseline_model.predict(X_test_scaled)4.4 模型评估我们不能只看模型在训练集上的表现更要关注它在没见过的测试集上的表现。def evaluate_model(y_true, y_pred, set_name): mae mean_absolute_error(y_true, y_pred) mse mean_squared_error(y_true, y_pred) rmse np.sqrt(mse) # 均方根误差与目标变量同单位更易解释 r2 r2_score(y_true, y_pred) print(f“{set_name} 评估结果“) print(f“ MAE平均绝对误差: {mae:.2f}“) print(f“ RMSE均方根误差: {rmse:.2f}“) print(f“ R² Score: {r2:.4f}“) return mae, rmse, r2 print(“ Baseline模型性能 “) train_mae, train_rmse, train_r2 evaluate_model(y_train, y_train_pred, “训练集“) test_mae, test_rmse, test_r2 evaluate_model(y_test, y_test_pred, “测试集“)关键指标解读MAE/RMSE衡量预测值与真实值的平均偏差。例如RMSE为5000元意味着平均预测误差在5000元左右。这个值需要结合目标变量的范围来看如果二手车均价是20万那么5000元的误差是可以接受的如果均价是5万那这个误差就太大了。R² Score决定系数表示模型能够解释的目标变量方差的比例。越接近1越好。如果测试集R²为0.85说明模型解释了价格85%的波动。Baseline模型的R²如果能达到0.7-0.8以上通常就是一个不错的起点。分析过拟合与欠拟合如果训练集R²很高如0.99但测试集R²很低如0.6说明模型过拟合了它记住了训练数据的噪声而没学到通用规律。对策简化模型如减少树的最大深度max_depth、增加min_samples_split、增加训练数据、进行更强的特征选择。如果训练集和测试集的R²都很低如都低于0.5说明模型欠拟合连训练数据都没学好。对策使用更复杂的模型、增加有价值的特征、减少正则化强度。4.5 特征重要性分析随机森林的一个巨大优势是能提供特征重要性这不仅是特征选择的依据也能给我们带来业务洞察。# 获取特征重要性 importances baseline_model.feature_importances_ feature_names X_train.columns indices np.argsort(importances)[::-1] # 按重要性降序排列 # 打印最重要的20个特征 print(“特征重要性排名Top 20:“) for i in range(min(20, len(indices))): print(f“{i1:2d}. {feature_names[indices[i]]:30s} {importances[indices[i]]:.4f}“) # 可视化 plt.figure(figsize(10, 6)) plt.title(‘Feature Importances‘) plt.bar(range(20), importances[indices[:20]], align‘center‘) plt.xticks(range(20), [feature_names[i] for i in indices[:20]], rotation90) plt.tight_layout() plt.show()通过这个分析你可能会发现“车龄”、“里程”、“品牌档次”是最重要的特征这完全符合我们的常识。如果某个精心构造的特征重要性排得很靠前说明特征工程是成功的如果某个特征重要性几乎为0可以考虑在后续迭代中移除它。5. 常见问题与排查技巧实录在实际操作中你一定会遇到各种各样的问题。下面是我在类似项目中踩过的一些“坑”以及解决方法。5.1 数据泄露这是新手最容易犯也最致命的错误。数据泄露指的是在模型训练过程中无意中使用了测试集或未来数据的信息导致模型在测试集上表现虚高但在真实场景中一塌糊涂。典型场景与排查在特征缩放或缺失值填充时使用了全量数据包含测试集来计算均值、标准差等参数。正确做法是scaler.fit_transform(X_train)然后scaler.transform(X_test)。在构造时间相关特征时使用了未来的信息。例如用“该车型本月平均成交价”作为特征但在计算这个平均值时不小心包含了当前待预测样本本身或未来的数据。必须确保每个样本的特征计算只依赖于它“当时”可用的历史信息。目标编码时使用了测试集的标签信息。如果用某个类别下目标变量的均值来编码这个均值必须仅从训练集中计算。排查技巧如果你的模型在测试集上的表现好得不可思议比如R² 0.95甚至比训练集还好第一反应就应该是检查数据泄露。可以尝试用一个极其简单的模型比如用全局均值预测作为基准如果复杂模型比它好不了多少那可能才是真实水平。5.2 类别不平衡与罕见值在分类问题中常见但在回归问题中目标变量价格的分布也可能极度不平衡。比如数据中大部分是10万以下的车突然有几辆几百万的豪车。影响模型会倾向于优化数量多的普通车而对豪车的预测误差很大整体误差指标如RMSE可能被这几辆豪车主导。处理方法样本加权在训练时给罕见的高价车样本更高的权重。分层抽样在划分训练/测试集时确保高价车在两边都有分布。分组建模先根据价格区间将车分为“经济型”、“中端”、“豪华型”等组分别训练模型。目标变量变换如前所述对价格进行对数变换可以压缩高端区间的尺度使分布更均衡。5.3 模型评估指标选择不当不同的误差指标强调不同的方面。MAE vs RMSEMAE对所有误差一视同仁。RMSE会放大较大的误差因为先平方。如果你的业务对“大误差”特别敏感比如估价过低导致卖亏可以更关注RMSE如果更看重“平均误差水平”则看MAE。R²的局限性R²衡量的是解释方差的比例。如果你的数据方差本身很小即使预测得很准R²也可能不高。永远不要只看R²一定要结合MAE/RMSE并理解其绝对数值的业务含义。建议在Baseline阶段同时计算并监控MAE、RMSE和R²。在业务汇报时选择最贴合业务痛点的1-2个指标作为主要优化目标。5.4 超参数调优的误区Baseline模型通常使用默认参数。后续优化时会进行超参数调优如随机森林的n_estimators,max_depth,min_samples_split等。常见误区在测试集上调参这相当于让测试集参与了训练会再次导致数据泄露和性能高估。必须使用交叉验证在训练集内部进行调优。过度调优花费大量时间把某个模型的性能从R²0.82提升到0.823边际效益极低。不如把时间花在特征工程或尝试其他模型上。忽略随机种子树模型和数据集划分都有随机性。调参对比时必须固定random_state否则性能波动可能导致错误结论。正确的调优流程from sklearn.model_selection import GridSearchCV, KFold # 定义参数网格 param_grid { ‘n_estimators‘: [100, 200, 300], ‘max_depth‘: [10, 20, None], ‘min_samples_split‘: [2, 5, 10] } # 使用交叉验证进行网格搜索 kfold KFold(n_splits5, shuffleTrue, random_state42) # 5折交叉验证 grid_search GridSearchCV(estimatorbaseline_model, param_gridparam_grid, cvkfold, scoring‘neg_mean_squared_error‘, # 用负MSE网格搜索会找最大值 n_jobs-1, # 使用所有CPU核心 verbose1) grid_search.fit(X_train_scaled, y_train) print(“最佳参数”, grid_search.best_params_) print(“最佳交叉验证分数负MSE:”, grid_search.best_score_) # 用最佳参数重新训练最终模型 best_model grid_search.best_estimator_5.5 线上线下效果不一致这是所有机器学习项目的终极挑战。模型在测试集上表现良好但上线后效果变差。可能原因及对策数据分布漂移上线后收到的数据分布与训练时不同例如训练数据多是夏季的上线后是冬季的或者新车系上市。需要建立模型性能监控体系定期用新数据重新训练模型在线学习或定期全量训练。特征获取不一致离线训练时特征是从清洗好的数据库提取的线上服务时特征是从实时日志里计算的计算逻辑有细微差别。必须保证特征处理管道线上线下完全一致。业务逻辑变化二手车市场政策、消费者偏好发生变化。需要与业务方保持沟通及时调整模型或特征。构建一个稳健的Baseline不仅仅是跑通一个流程更是建立一个可复现、可评估、可迭代的基准。它让我们对问题的难度、数据的质量、特征的效力有了量化的认识。在这个基础上后续的优化方向才会更加清晰——是应该花更多时间清洗数据、构造更巧妙的特征、尝试更复杂的模型如梯度提升树XGBoost/LightGBM还是调整模型融合策略。记住在机器学习项目中数据和特征的质量往往比模型的选择更重要。花在数据预处理和特征工程上的时间通常能获得比单纯调参更大的回报。
返回列表