Scikit-learn中修改MSE准则实现近似MAE的可行性及调整方法
问题核心原因
你的修改思路完全不成立,树无法长出多节点是必然结果,核心问题出在两个层面:
- 对MSE、MAE的不纯度计算逻辑理解有偏差
原生MSE的节点不纯度本质是节点内样本y值的方差,计算逻辑为「节点内y平方的均值」减去「节点内y均值的平方」,你看到的原始代码就是在按这个逻辑计算左右子节点的不纯度。
你修改后的代码如下:
这段加了开平方操作的逻辑,仅仅是把原来的「均值平方项」换成了「均值的绝对值」,和MAE没有任何关系。MAE的节点不纯度是节点内样本y值到中位数的平均绝对距离,最优预测值是节点内y的中位数而非均值,你修改后的代码全程还是在使用和均值绑定的总和统计量,完全没有涉及中位数相关计算,最终算出来的所有分裂点的不纯度下降值都≤0,树找不到任何能带来收益的分裂点,自然会停在根节点,对所有样本输出同一个预测值。for k in range(self.n_outputs): impurity_left[0] -= ((self.sum_left[k] / self.weighted_n_left) ** 2.0)**0.5 impurity_right[0] -= ((self.sum_right[k] / self.weighted_n_right) ** 2.0)**0.5 - 没有抓住MAE性能差的本质
MSE训练速度快,是因为计算最优分裂点只需要维护两个充分统计量:节点内y的总和、y的平方和,遍历排序后的候选分裂阈值时,可以O(1)更新左右子节点的统计量,单节点分裂的复杂度主要来自样本排序,为O(nlogn)。
而原生MAE要精确计算中位数,必须维护节点内所有样本的y值排序信息,每一次候选分裂都要重新计算分位数,复杂度远高于MSE,这才是它在大数据集上扩展性差的核心原因,不是简单改两行不纯度计算代码就能对齐的。
可行的MAE近似方案
如果你需要在大数据集上跑MAE相关实验,可以用下面几个成熟的近似方案,速度和原生MSE接近,精度损失可控:
- 分桶近似MAE
提前对标签y做等频分桶(比如分100~200个桶),每个节点只需要维护每个桶内的样本计数,找中位数时只需要扫一遍桶,找到累计样本数占比50%的桶位置即可,不需要存储所有原始y值,计算速度和MSE基本一致。主流优化版树实现里的MAE准则默认就是用这个近似逻辑,你可以直接用支持该优化的树库跑实验,也可以照着这个逻辑修改sklearn的criterion源码,把精确y值统计换成桶计数统计。 - Huber损失替代
Huber损失是MSE和MAE的光滑折中:当预测残差小于阈值delta时用平方误差,大于delta时用绝对误差,鲁棒性和MAE接近,同时损失函数光滑,可以用和MSE完全一致的充分统计量快速计算,不需要算中位数,速度和原生MSE相当,调整合适的delta参数后,实验结果和MAE的差异非常小。 - 采样近似
如果一定要用sklearn原生的MAE实现,可以在每次节点分裂时,对节点内的样本做固定规模的下采样(比如每个节点最多抽10000个样本计算最优分裂点),精度损失可控的前提下,速度可以提升1~2个数量级,足以支撑大规模数据集的实验。
代码修改注意点
不要在不纯度计算公式上随意添加非线性变换(比如你加的开平方操作),决策树分裂的核心判断依据是「分裂后不纯度的下降幅度」,随意的非线性变换很容易把不纯度下降的计算逻辑搞反,导致所有候选分裂点的收益都为负,树无法正常生长。
你可以用一个简单的例子验证修改后的逻辑问题:假设节点内y值为[1,2,3],均值是2,原MSE计算的节点不纯度是2/3;按照你修改后的逻辑,计算出的节点不纯度为8/3,比父节点的不纯度还高,树当然不会选择分裂。
内容的提问来源于stack exchange,提问作者SuperCodeBrah
相关产品推荐
相关产品推荐

