sklearn中OneVsRestClassifier决策树predict_proba返回NaN问题
OneVsRestClassifier封装决策树时predict_proba输出全NaN问题
需要获取多分类预测结果中每个类别的概率数组用于绘制ROC曲线,但使用sklearn的OneVsRestClassifier封装DecisionTreeClassifier训练多分类模型时,部分样本的predict_proba输出全为NaN值,复现信息如下:
复现代码
- 示例数据集构造(预测目标列为
lettr):
import pandas as pd df = pd.DataFrame({'lettr': ['T','I','D','N','G','S','B','A','J','M','X','O','G','M','R','F','O','C','T', 'J'], 'x-box': [2, 5, 4, 7, 2, 4, 4, 1, 2, 11, 3, 6, 4, 6, 5, 6, 3, 7, 6, 2], 'y-box': [8, 12, 11, 11, 1, 11, 2, 1, 2, 15, 9, 13, 9, 9, 9, 9, 4, 10, 11, 2], 'width': [3, 3, 6, 6, 3, 5, 5, 3, 4, 13, 5, 4, 6, 8, 5, 5, 4, 5, 6, 3], 'high': [5, 7, 8, 6, 1, 8, 4, 2, 4, 9, 7, 7, 7, 6, 7, 4, 3, 5, 8, 3], 'onpix':[1, 2, 6, 3, 1, 3, 4, 1, 2, 7, 4, 4, 6, 9, 6, 3, 2, 2, 5, 1], 'x-bar':[8, 10, 10, 5, 8, 8, 8, 8, 10, 13, 8, 6, 7, 7, 6, 10, 8, 6, 6, 10], 'y-bar':[13, 5, 6, 9, 6, 8, 7, 2, 6, 2, 7, 7, 8, 8, 11, 6, 7, 8, 11, 6], 'x2bar':[0, 5, 2, 4, 6, 6, 6, 2, 2, 6, 3, 6, 6, 6, 7, 3, 7, 6, 5, 3], 'y2bar':[6, 4, 6, 6, 6, 9, 6, 2, 6, 2, 8, 3, 2, 5, 3, 5, 5, 8, 6, 6], 'xybar':[6, 13, 10, 4, 6, 5, 7, 8, 12, 12, 5, 10, 6, 7, 7, 10, 7, 11, 11, 12], 'x2ybr':[10, 3, 3, 4, 5, 6, 6, 2, 4, 1, 6, 7, 5, 5, 3, 5, 6, 7, 9, 4], 'xy2br':[8, 9, 7, 10, 9, 6, 6, 8, 8, 9, 8, 9, 11, 8, 9, 7, 8, 11, 4, 9], 'x-ege':[0, 2, 3, 6, 1, 0, 2, 1, 1, 8, 2, 5, 4, 8, 2, 3, 2, 2, 3, 0], 'xegvy':[8, 8, 7, 10, 7, 8, 8, 6, 6, 1, 8, 9, 8, 9, 7, 9, 8, 8, 12, 7], 'y-ege':[0, 4, 3, 2, 5, 9, 7, 2, 1, 1, 6, 5, 7, 8, 5, 6, 3, 5, 2, 1], 'yegvx':[8, 10, 9, 8, 10, 7, 10, 7, 7, 8, 7, 8, 8, 6, 11, 9, 8, 9, 4, 7], })
- 数据集拆分代码:
from sklearn.model_selection import train_test_split y = df.iloc[:, 0] X = df.iloc[:,1:] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
- 模型训练与预测代码:
from sklearn.multiclass import OneVsRestClassifier from sklearn.tree import DecisionTreeClassifier RF = OneVsRestClassifier(DecisionTreeClassifier()) RF.fit(X_train,y_train) y_pred = RF.predict(X_test) pred_prob = RF.predict_proba(X_test)
异常表现
predict方法可正常输出预测类别数组,输出示例:
print(y_pred) # 输出: # array(['G', 'T', 'B', 'T'], dtype='<U1')
predict_proba返回的概率矩阵存在全NaN行,输出示例:
print(pred_prob) # 输出: # array([[ 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.], # [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan], # [ 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], # [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]])
补充信息
- 原始训练集约4000行数据,通常约500个样本会出现上述NaN问题
- 数据集无缺失值,除预测目标列
lettr外其余特征列均为int类型 - 换用KNN分类器时不存在该异常
问题根源
OneVsRestClassifier会为每个类别单独训练一个二分类器,判断样本是否属于当前类。默认参数下DecisionTreeClassifier的min_samples_leaf=1,即叶子节点最少仅需1个样本。当训练集拆分后,某个类别的二分类子训练集样本分布极端,训练出的子树所有叶子节点都只包含单一类别的样本时,子分类器的概率输出会出现0除以0的归一化计算错误,最终多分类概率汇总时就会生成全NaN的行。predict能正常运行是因为它只需要对比所有子分类器的输出相对大小取最大值,不需要做概率归一化,即使部分子分类器输出异常也能返回结果;predict_proba需要把所有子分类器的输出归一化到和为1的合法概率区间,遇到无效计算值就会返回NaN。
修复方案
两种方案均可解决问题:
- 给决策树设置合理的叶子节点最小样本数,避免叶子节点样本全为单类导致的概率计算异常:
RF = OneVsRestClassifier(DecisionTreeClassifier(min_samples_leaf=5))
- 直接使用决策树原生的多分类能力,无需套
OneVsRestClassifier封装。决策树本身原生支持多分类任务,自带的predict_proba不会出现这类NaN问题:
RF = DecisionTreeClassifier() RF.fit(X_train,y_train) pred_prob = RF.predict_proba(X_test)
内容的提问来源于stack exchange,提问作者Socka
相关产品推荐
相关产品推荐

