回归模型如何遵守单调关系?用梯度提升约束并检查反例

用 HistGradientBoostingRegressor 为数值列指定单调约束,对照无约束模型,在固定其他特征的网格上检查下降反例,并解释列顺序、类别、缺失输入和误差代价。

如果业务已经确认“其他条件相同,某个数值特征增大时预测不应下降”,可以在训练回归模型时加入单调约束。scikit-learn 的 HistGradientBoostingRegressor 通过 monotonic_cst 指定各列方向,再用固定其他特征的输入网格检查是否出现下降反例。

单调约束表达的是你要求模型遵守的关系,不是从数据中证明了因果关系。它也不保证误差更小、标签正确或适用于所有业务;错误的方向约束反而会限制模型表达真实关系。

回归模型如何遵守单调关系?用梯度提升约束并检查反例

先写清方向:哪一列增加,其他什么保持不变?

以两个数值特征为例:feature_0 被要求单调不下降,feature_1 不加约束。输入列顺序是 [feature_0, feature_1],因此设置 monotonic_cst=[1, 0]。值 1 表示单调增加,-1 表示单调减少,0 表示不约束;树模型可以有平坦区间,这里的增加是非递减,不要求每个相邻预测都严格上升。

HistGradientBoostingRegressor API 文档给出参数含义和位置映射;scikit-learn 梯度提升指南说明,在其他特征固定时约束一个特征的变化,不能同时改变其他特征后仍按同一个方向解释。

例如同一份数据里 feature_0 变大但 feature_1 也变了,两条记录的预测下降不一定违反约束。检查时必须只移动待检的数值列,固定其余列;也不能只比较数据表排序后相邻行。

运行无约束与有约束两个模型

下例使用六百条随机生成的教学数据,目标是 3 × feature_0 加上 feature_1 的正弦变化和噪声。它不是实际业务数据。完整代码已在 Windows、Python 3.11.15、scikit-learn 1.9.1、NumPy 2.4.6 下实际运行。

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install scikit-learn==1.9.1 numpy==2.4.6
.\.venv\Scripts\python.exe monotonic_regression.py

把下段完整代码保存为 monotonic_regression.py,再运行最后一条命令。macOS 或 Linux 使用 python3 建环境,并把后续解释器路径换成 .venv/bin/python。

两个模型使用相同训练/测试划分和树参数,只在第二个模型设置单调约束。官方的 单调约束示例也比较了有约束与无约束的梯度提升效果;本文使用自己的生成数据和直接网格检查。

import numpy as np
import sklearn
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import train_test_split

# Synthetic data: an increasing relation in feature 0 plus noise.
rng = np.random.default_rng(42)
X = rng.uniform(-2,2,size=(600,2))
y = 3*X[:,0] + np.sin(4*X[:,1]) + rng.normal(0,1.3,size=600)
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.25,random_state=42)
params = dict(max_iter=100,max_leaf_nodes=15,min_samples_leaf=10,random_state=42)
plain = HistGradientBoostingRegressor(**params).fit(X_train,y_train)
constrained = HistGradientBoostingRegressor(monotonic_cst=[1,0],**params).fit(X_train,y_train)
print('sklearn=',sklearn.__version__)
print('train/test=',len(y_train),len(y_test))
print('MAE_plain=',round(mean_absolute_error(y_test,plain.predict(X_test)),4))
print('MAE_constrained=',round(mean_absolute_error(y_test,constrained.predict(X_test)),4))
print('constraint=',constrained.monotonic_cst)
grid = np.linspace(-2,2,101)
rows=[]
for fixed_feature_1 in (-1.0,0.0,1.0):
    probe = np.column_stack([grid,np.full_like(grid,fixed_feature_1)])
    p_plain=plain.predict(probe)
    p_constrained=constrained.predict(probe)
    falls_plain=int(np.sum(np.diff(p_plain)<-1e-10))
    falls_constrained=int(np.sum(np.diff(p_constrained)<-1e-10))
    print('fixed_feature_1=',fixed_feature_1,'falls_plain=',falls_plain,
          'falls_constrained=',falls_constrained)
    rows.extend(zip(grid,probe[:,1],p_plain,p_constrained))
    assert np.isfinite(p_constrained).all()
    assert falls_constrained==0
np.savetxt('monotonic_probe.csv',np.array(rows),delimiter=',',
           header='feature_0,feature_1,pred_plain,pred_constrained',comments='')

在固定输入网格里寻找下降反例

sklearn= 1.9.1
train/test= 450 150
MAE_plain= 1.3226
MAE_constrained= 1.3522
constraint= [1, 0]
fixed_feature_1= -1.0 falls_plain= 23 falls_constrained= 0
fixed_feature_1= 0.0 falls_plain= 27 falls_constrained= 0
fixed_feature_1= 1.0 falls_plain= 24 falls_constrained= 0

核对训练/测试为 450/150 行,constraint 为 [1, 0]。分别把 feature_1 固定为 -1、0、1,让 feature_0 在 -2 到 2 之间递增;每个切片有 101 个预测,比较相邻预测的差值。

本次运行无约束模型的三个切片分别出现 23、27、24 次下降,有约束模型都为 0。检查阈值是差值小于 -1e-10,目的是容纳数值误差。不要要求换数据后的无约束模型一定出现反例,它也可能恰好满足单调关系。

打开 monotonic_probe.csv,按 feature_1 分组、feature_0 排序后复查 pred_constrained 的相邻差值。断言只验证本文选择的三个网格切片,不等于穷尽全部输入;模型的约束能力来自拟合时的参数,网格用于确认列顺序、调用路径和结果是否符合预期。

该次独立测试的 MAE:无约束模型约 1.3226,有约束模型约 1.3522,后者误差略大。MAE 是平均绝对误差,单位跟目标值一致。单调性通过不代表效果更好,正式项目仍要在适合业务的独立数据上检查误差及约束带来的代价。

换成真实数据前,检查列、范围和缺失值

  • 列顺序:数组约束按位置对应特征,列重排会改变含义。保持训练与预测相同的列顺序;使用有列名的 DataFrame 时也可按 API 支持的名称字典明确映射。
  • 方向依据:写明关系在什么条件和范围下成立。仅仅观测到正相关,不能自动作为单调约束依据;存在阈值、饱和或反向区间时要重新核对要求。
  • 类别特征:官方指南说明,无序类别不能施加单调约束。不要把地区、品类编码成 1、2、3 后,凭编号大小指定递增关系。
  • 缺失输入:该估计器原生支持 NaN,并按训练学习的分支规则处理;但 NaN 没有大小顺序。本文只验证有限数值输入,没有验证缺失值场景的业务行为,真实项目应单独检查缺失模式与预测。
  • 检查范围:网格应覆盖计划使用的数值范围,选取多组其他特征值;超出采集范围的组合即使单调,也不代表预测可信。

把特征转换后输入模型时,约束作用于转换后的列。比如取负号会翻转方向,多项式扩展会生成额外列;不能把原始字段的约束数组原封不动套在新特征矩阵上。

出现反例时,按什么顺序定位?

  1. 打印拟合后的模型 monotonic_cst,核对约束对象确实是当前用于预测的模型。
  2. 核对训练、预测以及检查网格的列顺序,不让流水线或导出步骤交换列。
  3. 检查网格是否只改变待检列,其他输入完全固定;没有固定其他列就不算有效反例。
  4. 查看下降幅度,区分容差范围内的浮点差异与实际下降;同时检查输出是否包含 NaN 或无穷值。
  5. 记录版本、最小输入与预测结果,在本地最小代码中复现后再检查其他转换或服务层处理。

如果是无约束模型出现反例,说明其当前预测没有满足你提出的方向要求;如果约束后误差明显增大,重新审查方向是否适合任务、标签和使用范围,不能只为通过约束检查而忽略业务误差。

单调约束能保证预测不出现负数吗?

不能。方向约束控制一个特征变化时的预测方向,数值取值范围是另一项要求。预测必须非负时,需要选合适的目标建模或输出方案并独立验证;单调也不意味着某个特征造成了目标变化。

Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30617.html

赞 (0)
AI小管家的头像AI小管家
没有 GPU 怎么学神经网络?用 MLPClassifier 跑通 CPU 示例
上一篇 11小时前
机器学习工具怎么选?比较 scikit-learn、PyTorch 与 XGBoost 的适用任务
下一篇 11小时前

相关推荐

联系我们

联系我们

1

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部