简介:新能源汽车残值评估是交易定价、保险风控与金融信贷的重要依据,传统重置成本法等方法难以体现电池、电机等核心技术特征。这份PDF以湖北工业大学学者发表于《湖北工业大学学报》的论文为内容,系统讲解基于梯度提升回归树(GBRT)的残值预测模型,涵盖网络爬虫数据采集、多维特征提取、模型参数优化及Stacking集成学习等关键环节。文件为1个PDF,大小1.14MB,内容紧凑,适合车辆工程、数据科学、金融保险等方向的研究者与从业者作为参考文献和专业指导。目前已有148人学习,资源对于想了解机器学习在新能源汽车残值评估中落地路径的读者,能提供从框架设计、算法原理到实验对比的完整参照,并有助于把握数据预处理、特征工程和集成建模的实用思路。
1. 新能源汽车残值评估,为什么传统方法搞不定
从事二手车估值或者车险定价的同行应该都有同感:燃油车的残值模型相对成熟,有大量历史成交数据可循;但到了新能源汽车这里,电池衰减、电机效率、政策补贴这些因素让旧方法几乎失灵。这篇论文给了一个很直接的结论——传统的重置成本法、清算价格法在测试集上的 R² 分别只有 0.626 和 0.556,而基于梯度提升树的机器学习方法能把 R² 推到 0.88 以上。换句话说,传统方法对新能源车残值的解释力连六成都不太到,剩下的四成全靠人工经验和运气。
这篇文章拆解的《基于机器学习的新能源汽车残值评估方法》,本质上是把「车况数据 → 残值」这件事完全交给模型:用爬虫抓取真实交易数据,做特征工程筛出强相关变量,再用 XGBoost、LightGBM 和二阶多项式做 Stacking 集成。整个过程不需要人工检测,输入当前车况就能实时输出残值。适合三类人看:正在做二手车估值系统、车险定价模型的算法工程师,术;想了解树模型在价格预测上怎么落地、特征怎么挖的学生;以及手里有交易数据但不知道怎么建模的从业者。下面按数据 → 特征 → 建模 → 集成的顺序拆开讲,每一步都给到能直接用的参数和代码。
2. 数据采集与预处理:什么样的数据才能训练残值模型
2.1 数据来源与字段语义:从爬虫到 DataFrame 的第一步
论文里的数据是用 Scrapy 框架从 autoscout24 采集的,8 个原始字段是:行驶里程(kilometers)、使用时长(service_life,单位月)、功率(power)、残值(residual,单位欧元)、车身颜色(body_color)、车门数(Nr_of_Doors)、车座数(Nr_of_Seats)、过户次数(transfers)。
如果你要复现这套方法,第一步不是写爬虫,而是先把原始数据洗干净。论文里没有给原始 CSV,但字段结构很清楚。我一般会用 pandas 读进来后先做一轮 describe 和 isnull 统计,先把每一列的缺失率、极值、分布形态摸清楚。这里用一段常见的读取与探索代码来演示:
import pandas as pd import numpy as np # 假设你已经把爬虫抓到的数据存成了 csv df = pd.read_csv("ev_residual.csv") # 看基本结构:行数、列名、缺失值情况 print(df.info()) print(df.isnull().mean().sort_values(ascending=False)) # 缺失率降序 # 关键数值列的分布描述 num_cols = ["kilometers", "service_life", "power", "residual", "transfers"] print(df[num_cols].describe()) # 类别列看取值分布,决定后续怎么编码 for c in ["body_color", "Nr_of_Doors", "Nr_of_Seats"]: print(c, df[c].value_counts(dropna=False))这段代码的目的是「先看数据再谈建模」。缺失率排序能让你一眼知道哪些列需要重点处理;describe 能暴露里程、残值这类连续变量是否有极端值;类别列的 value_counts 决定后面用众数填充还是用 one-hot 编码。注意dropna=False这个参数,它会单独把 NaN 统计出来,方便你判断缺失是随机还是结构化缺失。
2.2 缺失值处理:小缺失用众数,大缺失用逻辑回归
论文里最值得借鉴的就是缺失值处理的分层策略。车身颜色、车门数、车座数的缺失率分别只有 0.6%、0.6%、1.2%,直接取众数填充就可以了,对结果几乎没影响。真正麻烦的是过户次数,缺失率高达 25.3%——你不能直接丢掉四分之一的数据,也不能用众数硬填,因为过户次数对残值是有明显影响的。
论文用了一个很聪明的做法:把数据分成 A 组(过户次数不缺失)和 B 组(过户次数缺失),然后用 A 组训练一个三分类逻辑回归模型,输入特征是行驶里程和使用时间,输出类别是过户次数(0、1、2)。B 组的过户次数就靠这个模型预测补全。论文里验证集准确率做到了 98.1%。
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split # A 组:transfers 不缺失,用于训练 df_A = df[df["transfers"].notnull()].copy() df_B = df[df["transfers"].isnull()].copy() # 输入特征:行驶里程和使用时间,目标:过户次数(多分类) X_A = df_A[["kilometers", "service_life"]] y_A = df_A["transfers"].astype(int) X_train, X_val, y_train, y_val = train_test_split( X_A, y_A, test_size=0.2, random_state=42 ) # 逻辑回归多分类,用 saga 求解器适合中小规模数据 clf = LogisticRegression(multi_class="multinomial", solver="saga", max_iter=1000) clf.fit(X_train, y_train) print("验证集准确率:", clf.score(X_val, y_val)) # 预测 B 组的过户次数 df_B["transfers_pred"] = clf.predict(df_B[["kilometers", "service_life"]])注意到这里用的是里程和使用时间两个连续变量去预测过户次数这个离散标签,逻辑上说得通:开得久、开得多的车,过户次数通常也高一些。选saga而不是默认的liblinear,是因为多分类场景下 saga 支持 multinomial 损失,对三个类别同时优化效果更好。max_iter=1000也是关键,不设的话逻辑回归可能在收敛前就被截断,导致验证集上表现不佳。
2.3 异常值处理:箱线图之外还需要业务判断
论文里的异常值处理思路比单纯看分布更靠近业务:从箱线图看,车门数为 4 和 5 的样本里,残值有异常偏高的情况;过户次数为 1 和 2 的样本同样存在残值偏高。这里的「偏高」不是正态分布的偶然波动,更可能对应事故车修复后重新上架、或者特殊配置的顶配车。只用 3σ 原则删点会误伤正常样本,因为残值分布本身是有右拖尾的——论文里专门画了概率密度图,原始残值分布右拖尾明显,做对数变换后才能对标高斯分布。
实际操作时,我会按分组计算残值的中位数和 MAD(中位数绝对偏差),把偏离中位数超过 4 倍 MAD 的样本标记为异常。MAD 比标准差更抗干扰,不受右拖尾影响。这比论文里直接看箱线图更可复现:
from scipy.stats import median_abs_deviation def flag_outliers_by_group(df, group_col, value_col, thresh=4.0): flags = [] for _, grp in df.groupby(group_col): med = grp[value_col].median() mad = median_abs_deviation(grp[value_col]) if mad == 0: flags.extend([False] * len(grp)) else: diff = (grp[value_col] - med).abs() flags.extend((diff / mad > thresh).tolist()) return flags df["is_outlier"] = flag_outliers_by_group(df, "Nr_of_Doors", "residual") df = df[~df["is_outlier"]].drop(columns=["is_outlier"])这段代码把异常值定义从箱线图的肉眼判断变成可量化的阈值。4 倍 MAD 是个相对严格的阈值,正常二手车残值数据里大概会删掉 1%~2% 的样本,剩下的基本都是可信交易。如果你发现删掉的比例超过 5%,大概率是数据本身质量太差或字段含义不对,不是阈值设错了。
3. 特征工程:从 8 个原始字段到 7 个有效特征的完整链路
3.1 特征构造:比率特征、统计特征和 word2vec 的合理性
原始 8 个字段没法直接扔给模型。论文做了三个层面的特征构造:构造比率特征(每月平均行驶里程 = 行驶里程 / 使用时间)、添加分组统计特征(每个分类特征下连续变量的均值、方差、最大最小值)、以及用 word2vec 对类别特征做嵌入。
先说比率特征。为什么是「每月平均行驶里程」而不是单纯的行驶里程?因为两辆车里程一样,一辆开了两年、一辆开了五年,损耗逻辑完全不同。平均每月里程等于把使用强度单独提取出来,这个特征的业务含义比原始里程和使用时间更贴近残值衰减的机制。
第二个是统计特征。原理很朴素:同一个车身颜色下的车,如果行驶里程均值显著高于整体均值,说明这个颜色的车更偏向营运性质,残值要打折。论文的做法是对每个类别特征分组,求连续变量的均值、方差、最大最小值,再把结果拼回原样本。
第三个 word2vec 特征比较取巧。思路是把每个样本的类别特征组合成一个「句子」,然后用 word2vec 训练向量,让相似的类别组合在向量空间里距离更近。但实际做下来你会发现,类别组合的样本量如果不够,训练出的向量稳定性很差。论文里后面筛选特征时并没有把 word2vec 向量列进去,我推断这个特征在 RFECV 环节被淘汰了——因为最终筛选出的 7 个特征里没有向量化的影子。
# 构造比率特征 df["avg_monthly_km"] = df["kilometers"] / df["service_life"] # 分组统计特征:以车身颜色分组,统计行驶里程的中位数 df["km_median_by_color"] = df.groupby("body_color")["kilometers"].transform("median") # 以功率分组,统计行驶里程的均值 df["km_mean_by_power"] = df.groupby("power")["kilometers"].transform("mean") # 连续变量的二阶特征(论文明确提到行驶里程和使用时间的二阶特征) df["km_life_product"] = df["kilometers"] * df["service_life"]transform是分组统计特征的标准写法,它不会改变 DataFrame 的行数,而是把分组聚合结果广播回每一行。这里有个细节:如果直接用groupby().agg()再 merge,容易因为索引对不上产生错位,transform能避免这个经典问题。「公里数 × 使用时间」这个二阶特征对应的是累计损耗,业务上可以理解为「总使用负荷」,对残值往往是强负相关。
3.2 特征筛选:RFECV 加 LightGBM 基分类器的实操写法
特征构造完之后维度可能膨胀到几十上百,直接进模型会有两个问题:一个是无关特征带来噪声,另一个是训练时间变长。论文用了 LightGBM 作为基分类器做 3 折递归特征消除(RFECV),这个方法非常适合树模型——它利用特征重要度排序,每轮剔除最不重要的特征,然后交叉验证找最优特征子集。
from sklearn.feature_selection import RFECV from sklearn.model_selection import StratifiedKFold import lightgbm as lgb feature_cols = [ "kilometers", "service_life", "power", "transfers", "avg_monthly_km", "km_median_by_color", "km_mean_by_power", "km_life_product", "Nr_of_Doors", ] X = df[feature_cols] y = df["residual"] # LightGBM 回归器,保持和论文一致的参数风格 base_estimator = lgb.LGBMRegressor( n_estimators=100, max_depth=5, num_leaves=31, learning_rate=0.05, random_state=42, ) # 3 折递归特征消除 rfecv = RFECV( estimator=base_estimator, step=1, # 每轮删除 1 个特征 cv=StratifiedKFold(3), scoring="neg_mean_squared_error", min_features_to_select=3, ) rfecv.fit(X, y) print("最优特征数量:", rfecv.n_features_) selected = [f for f, s in zip(feature_cols, rfecv.support_) if s] print("筛选出的特征:", selected)选 RFECV 的 step=1 是为了看每个特征的边际贡献,虽然训练慢一些,但结果最干净。注意这里scoring用的是负均方误差而不是 R²,因为 RFECV 在筛选过程中更看重预测误差的绝对大小。还有一个容易踩的坑:如果特征列里有离散类别编码(比如车门数),LightGBM 会默认按连续变量处理,所以 RFECV 之前要把类别特征先转成 category dtype,或者像论文一样直接做 one-hot 后再筛。论文最终筛出的 7 个特征是:行驶里程、平均每月行驶里程、使用时间、不同功率下行驶里程的均值、车门数、不同车身颜色对应里程数的中位数、过户次数。这里面没有单纯的车身颜色和车座数——验证了它们对残值解释力很低。
3.3 连续变量归一化与离散变量 one-hot 的必要性
论文明确指出,归一化和标准化主要针对二阶多项式模型,树模型对此不要求。这一点很关键,很多初学者会把所有模型一视同仁地做归一化,白白增加代码量还看不出效果。XGBoost 和 LightGBM 是树模型,分裂点计算只依赖特征值排序,不依赖绝对值大小,所以没必要做归一化;但二阶多项式(本质上就是岭回归套多项式特征)需要梯度下降优化,特征的量纲差异会让损失函数等高线变得非常扁,收敛极慢。
from sklearn.preprocessing import MinMaxScaler, StandardScaler, OneHotEncoder # one-hot 编码离散变量 encoder = OneHotEncoder(sparse_output=False, handle_unknown="ignore") cat_cols = ["body_color", "Nr_of_Doors", "Nr_of_Seats", "transfers"] cat_encoded = encoder.fit_transform(df[cat_cols]) # handle_unknown="ignore" 保证新数据里有未出现的类别时不会报错 # 归一化连续变量,只给二阶多项式用 num_cols = ["kilometers", "service_life", "power", "residual"] scaler = MinMaxScaler() scaled_nums = scaler.fit_transform(df[num_cols])handle_unknown="ignore"这行很容易漏。线上推理的时候,如果来了一个训练集里没出现过的车身颜色,sklearn 会直接抛异常,整个预估服务挂掉。MinMaxScaler 适合残值这类有明确上下界的变量;如果是里程这种有长尾分布的,StandardScaler 通常更稳。树模型直接吃原始值就行,不用进这个 pipeline。
4. 三模型训练与调参:二阶多项式、XGBoost、LightGBM 的精确参数
4.1 训练策略:5 折交叉验证取均值,为什么不做单次 train_test_split
论文在模型训练阶段没有只用一次 train_test_split,而是对每个算法都训练 5 个模型,取预测均值。这样做的好处是降低数据切分随机性带来的方差,尤其在数据集规模不那么大的时候——论文里的交易样本量撑死几千条,单次划分很容易让某几个折的分布偏离整体。
每个算法的 5 个模型分别来自 5 折交叉验证的每一折训练集,最后对测试集的预测取这 5 个模型输出的均值。这种方式也被称为「交叉验证预测融合」,它和 Stacking 的区别在于——这里没有次级学习器,只是简单的算术平均。
from sklearn.model_selection import KFold from sklearn.metrics import r2_score import numpy as np kf = KFold(n_splits=5, shuffle=True, random_state=42) oof_preds = np.zeros(len(df)) test_preds = np.zeros(len(df_test)) for fold, (tr_idx, va_idx) in enumerate(kf.split(df)): X_tr, X_va = X.iloc[tr_idx], X.iloc[va_idx] y_tr, y_va = y.iloc[tr_idx], y.iloc[va_idx] model = lgb.LGBMRegressor( n_estimators=1000, max_depth=5, num_leaves=31, min_data_in_leaf=10, min_child_samples=10, reg_alpha=0.15, # L1 正则 learning_rate=0.05, random_state=42, ) model.fit(X_tr, y_tr) oof_preds[va_idx] = model.predict(X_va) test_preds += model.predict(X_test) / 5 print("OOF R²:", r2_score(y, oof_preds)) print("Test R²:", r2_score(y_test, test_preds))这段代码里的min_data_in_leaf=10和min_child_samples=10是论文给 LightGBM 的显式参数。前者控制叶子节点最少样本数,后者控制子节点分裂的最小样本数,两个一起设能有效防止树在稀疏特征上学出极端分裂。reg_alpha=0.15对应论文里的 L1 正则系数。如果你发现 OOF 分数和单折验证分数差很多,说明数据分布不均匀,考虑分层采样。
4.2 XGBoost 与 LightGBM 参数:论文给的最优配置逐项解读
论文明确给出了两个树模型的最优参数,这是整篇里信息密度最高的部分。
XGBoost 最优模型:300 棵基学习器,树深度 5,L1 正则项系数 0.1。300 棵树在中小数据集上是比较合理的量级——少于 100 棵树容易欠拟合(特征少、关系复杂),多于 1000 棵树则过拟合风险陡增,尤其当数据量只有几百条时。
LightGBM 最优模型:1000 棵 gbdt 树,深度 5,叶子数量 31,min_data_in_leaf 为 10,min_child_samples 为 10,L1 正则系数 0.15。注意这里 n_estimators 高达 1000 但仍然没有过拟合,原因是 learning_rate 调低了,配合 L1 正则让每棵树的贡献被刻意压缩。
叶子数量 31 是 LightGBM 特有的参数,对应最大叶子节点数,数值越大模型越复杂。它和 max_depth 是两个独立的控制维度:max_depth 限制树的纵向深度,num_leaves 限制横向叶子数。论文同时把深度限制到 5、叶子限制到 31,相当于在纵向和横向都加了约束。如果只设 depth 不设 leaves,LightGBM 的叶子优先分裂策略可能让某些分支异常深,导致过拟合。
import xgboost as xgb xgb_model = xgb.XGBRegressor( n_estimators=300, max_depth=5, reg_alpha=0.1, # L1 正则 reg_lambda=1.0, # L2 正则,默认值即可 learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, random_state=42, ) xgb_model.fit(X_train, y_train)subsample=0.8和colsample_bytree=0.8是论文没明说但工程上非常常见的参数。前者每棵树随机采样 80% 样本,后者每棵树随机选 80% 特征,两个一起用等于给模型加了两层随机性,能有效降低树之间的相关性,配合 L1 正则把泛化误差压下来。
4.3 特征重要性分析:怎么用重要度反馈调整特征集
论文提到 XGBoost 和 LightGBM 训练完可以打印特征重要性列表,然后过滤掉重要性较低的特征重新训练,能提升训练速度和拟合优度。这个步骤是调参之后二次特征筛选的闭环,很多人会跳过。
# LightGBM 特征重要性 importance = pd.Series( model.feature_importances_, index=feature_cols ).sort_values(ascending=False) print(importance) # 保留累计重要性前 80% 的特征 cumsum = importance.cumsum() / importance.sum() keep_cols = importance[cumsum <= 0.8].index.tolist() print("保留的特征:", keep_cols)特征重要性的排序能直接告诉你业务假设是否成立。比如论文最终筛出的 7 个特征里,行驶里程和使用时间排最前,功率没有单独入选——说明里程和时间对残值的解释力远大于功率本身。如果你的数据里功率排到了第一,反而要怀疑是不是采集的数据里混入了大量营运车辆。
5. 避坑手册:数据清洗、缺失值、过拟合与评估指标的四个坑
5.1 过户次数缺失 25% 不能直接删,也不能简单众数填充
现象:直接删除缺失过户次数的样本,建模数据量锐减四分之一;用众数填充则模型在测试集上的 R² 掉了 0.03 左右。
原因:过户次数对残值有真实的负相关影响,直接删等于丢信息,众数填充则把所有缺失样本归为一类,抹掉了分布差异。
解决:按论文方案,用不缺失样本训练逻辑回归三分类模型预测缺失值。关键在于输入特征选里程和使用时间,因为它们和过户次数的相关性最强(论文 Fig.5 的皮尔逊相关系数图里能直接看到)。
5.2 残值分布右拖尾,不做变换的线性模型全是坑
现象:用原始残值训练二阶多项式模型,训练集 R² 只有 0.76,残差图呈现明显的扇形模式——预测值越大误差越大。
原因:残值分布本身右拖尾(论文 Fig.3a),少数高残值样本把损失函数的主导权抢走了,线性模型为了拟合它们牺牲了大多数样本的精度。
解决:对残值做对数变换后训练,预测时再 exp 还原。论文用的就是这个思路,变换后分布明显更接近高斯(Fig.3b)。我一般会在训练前把y = np.log1p(y)过一遍,评估时用 expm1 还原再算 R²。
5.3 树模型不用归一化,但 one-hot 编码的稀疏问题必须处理
现象:对 LightGBM 做 MinMaxScaler 归一化,训练时间反而变长,精度没有变化;对车身颜色做 one-hot 后特征维度膨胀,训练变慢。
原因:树模型的分裂只依赖特征值排序,归一化不改变排序所以没有信息增益;one-hot 生成大量全零列,LightGBM 对这些稀疏列的分裂计算效率很低。
解决:树模型的类别特征直接转 category dtype 让 LightGBM 原生处理,或者只对高基数类别特征做 target encoding。论文里最终也没用 one-hot 后的颜色特征,而是用了「不同车身颜色对应里程数的中位数」这种目标编码方式。
5.4 用 test R² 调参等于作弊,学习曲线才是过拟合的金标准
现象:反复在测试集上试参数,测试 R² 越调越高,但换一批新数据就崩。
原因:测试集被当成了验证集在用,模型和参数间接「见过」了测试集的分布,泛化能力被高估。
解决:严格划分训练集 / 验证集 / 测试集三份,训练时只碰前两份。论文里的学习曲线(Fig.6)是判断过拟合的标准工具——当训练集 R² 明显高于验证集 R² 且验证集不再上升时,过拟合开始。
6. Stacking 集成与复现验证:从 R² 0.863 到 0.881 的最后一跃
单独模型的成绩单是:二阶多项式 test R² 0.832,XGBoost 0.863,LightGBM 0.859。三个模型单独看已经碾压传统方法(最高 0.626),但还能再提升——论文用 Stacking 把三个模型集成,test R² 到了 0.881,比三个单独模型的平均值提升 0.029。
Stacking 的原理是级联:底层是三个个体学习器(二阶多项式、XGBoost、LightGBM),它们的预测结果作为新特征输入到次级学习器(论文用了 CatBoost)。关键是次级学习器的训练数据不能来自个体学习器对训练集的预测——否则就是标准过拟合。正确做法是 5 折交叉验证:每个个体学习器对每一折的验证集做预测,拼出完整的 OOF 预测作为次级学习器的输入特征,测试集预测则取 5 折模型平均值。
from catboost import CatBoostRegressor from sklearn.linear_model import Ridge import xgboost as xgb import lightgbm as lgb kf = KFold(n_splits=5, shuffle=True, random_state=42) def get_oof_and_test(model, X, y, X_test): oof = np.zeros(len(X)) test = np.zeros(len(X_test)) for tr_idx, va_idx in kf.split(X): model.fit(X.iloc[tr_idx], y.iloc[tr_idx]) oof[va_idx] = model.predict(X.iloc[va_idx]) test += model.predict(X_test) / kf.n_splits return oof, test # 三个基学习器 ridge = Ridge(alpha=0.343) xgb_m = xgb.XGBRegressor(n_estimators=300, max_depth=5, reg_alpha=0.1) lgb_m = lgb.LGBMRegressor(n_estimators=1000, max_depth=5, num_leaves=31, min_data_in_leaf=10, reg_alpha=0.15) oof_ridge, test_ridge = get_oof_and_test(ridge, X_train, y_train, X_test) oof_xgb, test_xgb = get_oof_and_test(xgb_m, X_train, y_train, X_test) oof_lgb, test_lgb = get_oof_and_test(lgb_m, X_train, y_train, X_test) # 次级特征:三个模型的 OOF 预测 stack_X_train = np.column_stack([oof_ridge, oof_xgb, oof_lgb]) stack_X_test = np.column_stack([test_ridge, test_xgb, test_lgb]) # 次级学习器:CatBoost meta_model = CatBoostRegressor( depth=5, learning_rate=0.05, verbose=False, random_seed=42 ) meta_model.fit(stack_X_train, y_train) stack_pred = meta_model.predict(stack_X_test) print("Stacking Test R²:", r2_score(y_test, stack_pred))这套代码的复现价值在于:Ridge 的 alpha 参数用的是论文里给出的 L2 范数系数 0.343,XGBoost 和 LightGBM 的参数也严格按照论文的最终配置,不再二次调参。CatBoost 作为元模型的好处在于它对数值型特征的分布不敏感,输入是三个概率值(或回归值)时不需要额外做归一化。
这里有几个验证点值得照做。第一,单独复现三个模型各自的 test R²,如果 XGBoost 到不了 0.86 左右、LightGBM 到不了 0.859 附近,说明前面的特征工程或数据清洗和论文有偏差,回 5.2 节重查数据分布。第二,对比 Stacking 和三个模型的算术平均值,如果提升不足 0.02,有两种可能——三个模型高度线相关(栈结构信息重叠),或者 OOF 预测拼接过程有泄漏。第三,直接跑论文里的学习曲线逻辑,画出训练集 R² 和验证集 R² 随样本量的变化,验证 LightGBM 不欠拟合也不过拟合。
我一般会在集成后多做一个动作:打印 CatBoost 的特征重要度。它对应的是三个基模型对最终预测的贡献占比,如果某个基模型的贡献接近 0,说明这个模型和另外两个高度相关,从生产环境考虑可以精简掉,省一倍的推理时间。从那以后我每次做 Stacking 都会强制走一遍「OOF 拼接 → 特征重要度检查 → 精简基模型」这个流程,而不是无脑把模型叠上去。希望这篇拆解能帮你在做新能源车残值评估时少踩几个坑,把模型精度实打实地拉上去。
本文还有配套的精品资源,点击获取