RidgeClassifier 用 uint 标签后决策值巨大,检查负类编码溢出
RidgeClassifier 内部负类编码保留无符号类型时会产生巨大决策值,需检查二值化结果并采用包含修复的版本。
同一份二分类数据,y 用有符号整数时决策值正常,换成 uint64 后却出现约十的十九次方量级的异常值。scikit-learn 1.8.0 存在一项内部标签二值化回归:分类器需要用负值表示负类,转换结果却保留了无符号目标的 dtype,-1 就包装成了大的正数。
这不是把类别编号 0、1 写错。即使原始标签完全合法,内部用于回归求解的二值化目标也可能已经变了。只核对 classes_ 仍为两个正确类别,不足以排除这项故障。
先检查二值化,再比较预测
下面的检查不依赖模型精度,直接查看分类器所需的负类编码:
import numpy as np
import sklearn
from sklearn.preprocessing import label_binarize
y = np.array([0, 1, 0, 1], dtype=np.uint8)
encoded = label_binarize(y, classes=[0, 1], neg_label=-1, pos_label=1)
print(sklearn.__version__, encoded.dtype, encoded.ravel())
assert np.array_equal(encoded.ravel(), [-1, 1, -1, 1])
scikit-learn 1.9.0 包含修复:需要保存负类值时,不再把无符号标签 dtype 当作有符号输出 dtype;相关类别比较也处理了整数类型对齐。原回归不是所有旧版本都必然存在,应以实际版本与这条编码检查为准。
更新后,用项目自己的同一组特征分别拟合 uint 标签和等值有符号标签,比较预测、决策函数与类别表。不要重新随机生成两份训练数据再比较,否则无法判断差异来自类型还是样本。
旧版本可对可表示的标签做有符号转换
当类别编号能放入 int64 时,可在进入分类器前明确转换:
import numpy as np
from sklearn.linear_model import RidgeClassifier
assert np.issubdtype(y.dtype, np.unsignedinteger)
if int(y.max()) > np.iinfo(np.int64).max:
raise ValueError("类别编号超出 int64,需单独建立类别编码")
y_signed = y.astype(np.int64)
model = RidgeClassifier().fit(X, y_signed)
这里的 X 和 y 是项目原来的训练数组。转换用于避开受影响的内部无符号路径,不改变类别含义;超出有符号范围的 uint64 不能直接照搬。确需重新编码时,应保留编码与原标签的映射,预测后还原业务标签。
验收要保留一个包含负类的最小编码检查,再运行真实训练与预测路径。若直接传给 label_binarize 的 neg_label 本来就是 0,则不会检验到 -1 的包装问题;仅验证普通 one-hot 结果可能漏掉这条回归。
这项修复也不能解释所有整型特征、类别不平衡或分类概率差异。特征 dtype 与目标标签 dtype 是两个独立输入,应记录实际发生改变的是哪一个。更新后若仍有差异,继续比较相同参数、相同训练集与新的内部编码结果。


