KNeighborsClassifier 的 brute 与 p=1 组合拒绝字符串标签
KNeighborsClassifier 的 brute 分类概率优化需要整数类索引,旧路径误传字符串时应更新修复版本或明确编码类别。
KNeighborsClassifier 已经拟合成功,预测时却报 invalid literal for int() ... 'bar'。若类别就是正常的字符串,而配置为 algorithm="brute", p=1,先检查分类概率的优化路径,不要把合法类别名改成能被 int() 解析的文本。
scikit-learn 1.8.0 的这项故障把 self.classes_ 中原始类别值交给底层 ArgKminClassMode。该接口需要的是类索引,训练样本的类别成员关系已经编码成整数,但唯一类别列表仍然是字符串,于是底层转换失败。
固定距离与算法复验
下面保留字符串标签,显式采用 Manhattan 距离对应的 p=1,避免 auto 选择另一条算法路径:
import numpy as np
import sklearn
from sklearn.neighbors import KNeighborsClassifier
rng = np.random.default_rng(0)
X = rng.normal(size=(5, 3))
y = np.array(["foo", "bar", "foo", "bar", "foo"])
query = rng.normal(size=(2, 3))
model = KNeighborsClassifier(algorithm="brute", p=1, n_neighbors=3).fit(X, y)
prediction = model.predict(query)
probability = model.predict_proba(query)
print(sklearn.__version__, model.classes_, prediction)
assert probability.shape == (len(query), len(model.classes_))
np.testing.assert_allclose(probability.sum(axis=1), 1.0)
assert set(prediction).issubset(set(y))
scikit-learn 1.9.0 包含修复,传给底层的是 0 到 len(classes_)-1 的整数索引,而不是类别名。这项修复也涉及 RadiusNeighborsClassifier 的对应分类概率路径。
更新后应覆盖 predict 和 predict_proba,并检查概率列仍对应 model.classes_。只运行 fit,不会执行原来失败的优化步骤;把 algorithm 改成 auto 后恰好成功,也不能证明固定 brute 路径已经恢复。
需要保留旧版时,显式管理类别编码
受版本约束时,可先把字符串类别编码为连续整数,模型输出后再恢复:
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder().fit(y)
model = KNeighborsClassifier(algorithm="brute", p=1, n_neighbors=3)
model.fit(X, encoder.transform(y))
prediction = encoder.inverse_transform(model.predict(query))
probability_labels = encoder.inverse_transform(model.classes_)
print(prediction, probability_labels)
编码器需要与模型一起保存。类别排序应来自同一个编码器,不能在预测服务中另外排序后解释概率列。不要仅为避开错误把 p 改成 2,那会从 Manhattan 改为 Euclidean 距离,邻居选择和业务结果都可能改变。
本题处理的是底层分类概率优化收到错误类别表示,不是样本数小于 n_neighbors、距离矩阵形状不符或类别本身缺失。不同算法、metric、权重与输入类型可能选择不同路径;验收应保留原组合,再覆盖项目实际使用的概率输出与标签还原。


