分类器的不确定度估计

我们还没有谈到 scikit-learn 接口的另一个有用之处,就是分类器能够给出预测的不确定度估计。一般来说,你感兴趣的不仅是分类器会预测一个测试点属于哪个类别,还包括它对这个预测的置信程度。在实践中,不同类型的错误会在现实应用中导致非常不同的结果。想象一个用于测试癌症的医疗应用。假阳性预测可能只会让患者接受额外的测试,但假阴性预测却可能导致重病没有得到治疗。

scikit-learn 中有两个函数可用于获取分类器的不确定度估计:decision_functionpredict_proba。大多数分类器(但不是全部)都至少有其中一个函数,很多分类器两个都有。我们来构建一个 GradientBoostingClassifier 分类器(同时拥有 decision_functionpredict_proba 两个方法),看一下这两个函数对一个模拟的二维数据集的作用:

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_circles
import numpy as np
from sklearn.model_selection import train_test_split

X, y = make_circles(noise=0.25, factor=0.5, random_state=1)

# 为了便于说明,我们将两个类别重命名为"blue"和"red"
y_named = np.array(["blue", "red"])[y]

# 我们可以对任意个数组调用train_test_split
# 所有数组的划分方式都是一致的
X_train, X_test, y_train_named, y_test_named, y_train, y_test = \
    train_test_split(X, y_named, y, random_state=0)

# 构建梯度提升模型
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train_named)

1、决策函数

对于二分类的情况,decision_function 返回值的形状是 (n_samples,),为每个样本都返回一个浮点数:

print("X_test.shape: {}".format(X_test.shape))
# X_test.shape: (25, 2)
print("Decision function shape: {}".format(gbrt.decision_function(X_test).shape))
# Decision function shape: (25,)

对于类别 1 来说,这个值表示模型对该数据点属于 “正” 类的置信程度。正值表示对正类的偏好,负值表示对 “反类”(其他类)的偏好:

# 显示decision_function的前几个元素
print("Decision function:\n{}".format(gbrt.decision_function(X_test)[:6]))
'''
Decision function:
[ 4.13592603 -1.70169917 -3.95106099 -3.62609552  4.28986642  3.66166081]
'''

我们可以通过仅查看决策函数的正负号来再现预测值:

print("Thresholded decision function:\n{}".format(gbrt.decision_function(X_test) > 0))
'''
Thresholded decision function:
[ True False False False  True  True False  True  True  True False  True
  True False  True False False False  True  True  True  True  True False
 False]
'''
print("Predictions:\n{}".format(gbrt.predict(X_test)))
'''
Predictions:
['red' 'blue' 'blue' 'blue' 'red' 'red' 'blue' 'red' 'red' 'red' 'blue'
 'red' 'red' 'blue' 'red' 'blue' 'blue' 'blue' 'red' 'red' 'red' 'red'
 'red' 'blue' 'blue']
'''

对于二分类问题,“反”类始终是 classes_ 属性的第一个元素,“正”类是 classes_ 的第二个元素。因此,如果你想要完全再现 predict 的输出,需要利用 classes_ 属性:

# 将布尔值True/False转换成0和1
greater_zero = (gbrt.decision_function(X_test) > 0).astype(int)
# 利用0和1作为classes_的索引
pred = gbrt.classes_[greater_zero]
# pred与gbrt.predict的输出完全相同
print("pred is equal to predictions: {}".format(np.all(pred == gbrt.predict(X_test))))
# pred is equal to predictions: True

decision_function 可以在任意范围取值,这取决于数据与模型参数:

decision_function = gbrt.decision_function(X_test)
print("Decision function minimum: {:.2f} maximum: {:.2f}".format(
    np.min(decision_function), np.max(decision_function)))
# Decision function minimum: -7.69 maximum: 4.29

由于可以任意缩放,因此 decision_function 的输出往往很难解释。

在下面的例子中,我们利用颜色编码在二维平面中画出所有点的 decision_function,还有决策边界。我们将训练点画成圆,将测试数据画成三角:

fig, axes = plt.subplots(1, 2, figsize=(13, 5))
mglearn.tools.plot_2d_separator(gbrt, X, ax=axes[0], alpha=.4,
                                fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(gbrt, X, ax=axes[1],
                                            alpha=.4, cm=mglearn.ReBl)

for ax in axes:
    # 画出训练点和测试点
    mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
                             markers='^', ax=ax)
    mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
                             markers='o', ax=ax)
    ax.set_xlabel("Feature 0")
    ax.set_ylabel("Feature 1")
cbar = plt.colorbar(scores_image, ax=axes.tolist())
axes[0].legend(["Test class 0", "Test class 1", "Train class 0",
                "Train class 1"], ncol=4, loc=(.1, 1.1))
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lBjlyp78-1607396891117)(../素材/梯度提升模型在一个二维玩具数据集上的决策边界(左)和决策函数(右).png)]

既给出预测结果,又给出分类器的置信程度,这样给出的信息量更大。但在上面的图像中,很难分辨出两个类别之间的边界。


2、预测概率

predict_proba 的输出是每个类别的概率,通常比 decision_function 的输出更容易理解。对于二分类问题,它的形状始终是 (n_samples, 2)

print("Shape of probabilities: {}".format(gbrt.predict_proba(X_test).shape))
# Shape of probabilities: (25, 2)

每行的第一个元素是第一个类别的估计概率,第二个元素是第二个类别的估计概率。由于 predict_proba 的输出是一个概率,因此总是在 0 和 1 之间,两个类别的元素之和始终为 1:

# 显示predict_proba的前几个元素
print("Predicted probabilities:\n{}".format(gbrt.predict_proba(X_test[:6])))
'''
Predicted probabilities:
[[0.01573626 0.98426374]
 [0.84575653 0.15424347]
 [0.98112869 0.01887131]
 [0.97407033 0.02592967]
 [0.01352142 0.98647858]
 [0.02504637 0.97495363]]
'''

由于两个类别的概率之和为 1,因此只有一个类别的概率超过 50%。这个类别就是模型的预测结果。

在上一个输出中可以看到,分类器对大部分点的置信程度都是相对较高的。不确定度大小实际上反映了数据依赖于模型和参数的不确定度过拟合更强的模型可能会做出置信程度更高的预测,即使可能是错的复杂度越低的模型通常对预测的不确定度越大。如果模型给出的不确定度符合实际情况,那么这个模型被称为校正calibrated)模型。在校正模型中,如果预测有 70% 的确定度,那么它在 70% 的情况下正确。

在下面的例子中,我们再次给出该数据集的决策边界,以及类别 1 的类别概率:

fig, axes = plt.subplots(1, 2, figsize=(13, 5))

mglearn.tools.plot_2d_separator(
    gbrt, X, ax=axes[0], alpha=.4, fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(
    gbrt, X, ax=axes[1], alpha=.5, cm=mglearn.ReBl, function='predict_proba')

for ax in axes:
    # 画出训练点和测试点
    mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
                             markers='^', ax=ax)
    mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
                             markers='o', ax=ax)
    ax.set_xlabel("Feature 0")
    ax.set_ylabel("Feature 1")
cbar = plt.colorbar(scores_image, ax=axes.tolist())
axes[0].legend(["Test class 0", "Test class 1", "Train class 0",
                "Train class 1"], ncol=4, loc=(.1, 1.1))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bhg0ZE4B-1607396891120)(../素材/梯度提升模型的决策边界(左)和预测概率.png)]

这张图中的边界更加明确,不确定的小块区域清晰可见。

scikit-learn 网站给出了许多模型的对比,以及不确定度估计的形状。


3、多分类问题的不确定度

到目前为止,我们只讨论了二分类问题中的不确定度估计。但 decision_functionpredict_proba 也适用于多分类问题。我们将这两个函数应用于鸢尾花(Iris)数据集,这是一个三分类数据集:

from sklearn.datasets import load_iris

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, random_state=42)

gbrt = GradientBoostingClassifier(learning_rate=0.01, random_state=0)
gbrt.fit(X_train, y_train)
print("Decision function shape: {}".format(gbrt.decision_function(X_test).shape))
# Decision function shape: (38, 3)
# 显示决策函数的前几个元素
print("Decision function:\n{}".format(gbrt.decision_function(X_test)[:6, :]))
'''
Decision function:
[[-1.995715    0.04758267 -1.92720695]
 [ 0.06146394 -1.90755736 -1.92793758]
 [-1.99058203 -1.87637861  0.09686725]
 [-1.995715    0.04758267 -1.92720695]
 [-1.99730159 -0.13469108 -1.20341483]
 [ 0.06146394 -1.90755736 -1.92793758]]
'''

对于多分类的情况,decision_function 的形状为 (n_samples, n_classes),每一列对应每个类别的 “确定度分数”,分数较高的类别可能性更大,得分较低的类别可能性较小。你可以找出每个数据点的最大元素,从而利用这些分数再现预测结果:

print("Argmax of decision function:\n{}".format(
      np.argmax(gbrt.decision_function(X_test), axis=1)))
'''
Argmax of decision function:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1
 0]
'''
print("Predictions:\n{}".format(gbrt.predict(X_test)))
'''
Predictions:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1
 0]
'''

predict_proba 输出的形状相同,也是 (n_samples, n_classes)。同样,每个数据点所有可能类别的概率之和为 1:

# 显示predict_proba的前几个元素
print("Predicted probabilities:\n{}".format(gbrt.predict_proba(X_test)[:6]))
'''
Predicted probabilities:
[[0.10217718 0.78840034 0.10942248]
 [0.78347147 0.10936745 0.10716108]
 [0.09818072 0.11005864 0.79176065]
 [0.10217718 0.78840034 0.10942248]
 [0.10360005 0.66723901 0.22916094]
 [0.78347147 0.10936745 0.10716108]]
'''
# 显示每行的和都是1
print("Sums: {}".format(gbrt.predict_proba(X_test)[:6].sum(axis=1)))
# Sums: [1. 1. 1. 1. 1. 1.]

同样,我们可以通过计算 predict_probaargmax 来再现预测结果:

print("Argmax of predicted probabilities:\n{}".format(
      np.argmax(gbrt.predict_proba(X_test), axis=1)))
'''
Argmax of predicted probabilities:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
'''
print("Predictions:\n{}".format(gbrt.predict(X_test)))
'''
Predictions:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
'''

总之,predict_probadecision_function 的形状始终相同,都是 (n_samples, n_classes)——除了二分类特殊情况下的 decision_function。对于二分类的情况,decision_function 只有一列,对应 “正” 类 classes_[1]。这主要是由于历史原因。

如果有 n_classes 列,你可以通过计算每一列的 argmax 来再现预测结果。但如果类别是字符串,或者是整数,但不是从 0 开始的连续整数的话,一定要小心。如果你想要对比 predict 的结果与 decision_functionpredict_proba 的结果,一定要用分类器的 classes_ 属性来获取真实的属性名称:

logreg = LogisticRegression()

# 用Iris数据集的类别名称来表示每一个目标值
named_target = iris.target_names[y_train]
logreg.fit(X_train, named_target)
print("unique classes in training data: {}".format(logreg.classes_))
# unique classes in training data: ['setosa' 'versicolor' 'virginica']

print("predictions: {}".format(logreg.predict(X_test)[:10]))
'''
predictions: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor' 'setosa'
	'versicolor' 'virginica' 'versicolor' 'versicolor']
'''

argmax_dec_func = np.argmax(logreg.decision_function(X_test), axis=1)
print("argmax of decision function: {}".format(argmax_dec_func[:10]))
# argmax of decision function: [1 0 2 1 1 0 1 2 1 1]

print("argmax combined with classes_: {}".format(
        logreg.classes_[argmax_dec_func][:10]))
'''
argmax combined with classes_: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor' 'setosa'
 'versicolor' 'virginica' 'versicolor' 'versicolor']
'''

4、总结

本章首先讨论了模型复杂度,然后讨论了泛化或者说学习一个能够在前所未见的新数据上表现良好的模型。这就引出了欠拟合过拟合的概念,前者是指一个模型无法获取训练数据中的所有变化,后者是指模型过分关注训练数据,但对新数据的泛化性能不好

然后本章讨论了一系列用于分类和回归的机器学习模型,各个模型的优点和缺点,以及如何控制它们的模型复杂度。我们发现,对于许多算法而言,设置正确的参数对模型性能至关重要有些算法还对输入数据的表示方式很敏感,特别是特征的缩放。因此,如果盲目地将一个算法应用于数据集,而不去理解模型所做的假设以及参数设定的含义,不太可能会得到精度高的模型。

本章包含大量有关算法的信息,在继续阅读后续章节之前你不必记住所有这些细节。但是,这里提到的有关模型的某些知识(以及在特定情况下使用哪种模型)对于在实践中成功应用机器学习模型是很重要的。关于何时使用哪种模型,下面是一份快速总结。

  • 最近邻

    适用于小型数据集,是很好的基准模型,很容易解释。

  • 线性模型

    非常可靠的首选算法,适用于非常大的数据集,也适用于高维数据。

  • 朴素贝叶斯

    只适用于分类问题。比线性模型速度还快,适用于非常大的数据集和高维数据。精度通常要低于线性模型。

  • 决策树

    速度很快,不需要数据缩放,可以可视化,很容易解释。

  • 随机森林

    几乎总是比单棵决策树的表现要好,鲁棒性很好,非常强大。不需要数据缩放。不适用于高维稀疏数据。

  • 梯度提升决策树

    精度通常比随机森林略高。与随机森林相比,训练速度更慢,但预测速度更快,需要的内存也更少。比随机森林需要更多的参数调节。

  • 支持向量机

    对于特征含义相似的中等大小的数据集很强大。需要数据缩放,对参数敏感。

  • 神经网络

    可以构建非常复杂的模型,特别是对于大型数据集而言。对数据缩放敏感,对参数选取敏感。大型网络需要很长的训练时间。

面对新数据集,通常最好先从简单模型开始,比如线性模型、朴素贝叶斯或最近邻分类器,看能得到什么样的结果。对数据有了进一步了解之后,你可以考虑用于构建更复杂模型的算法,比如随机森林、梯度提升决策树、SVM 或神经网络。

现在你应该对如何应用、调节和分析我们介绍过的模型有了一定的了解。本章主要介绍了二分类问题,因为这通常是最容易理解的。不过本章大多数算法都可以同时用于分类和回归,而且所有分类算法都可以同时用于二分类和多分类。你可以尝试将这些算法应用于 scikit-learn 的内置数据集,比如用于回归的 boston_housingdiabetes 数据集,或者用于多分类的 digits 数据集。在不同的数据集上实验这些算法,可以让你更好地感受它们所需的训练时间、分析模型的难易程度以及它们对数据表示的敏感程度。

虽然我们分析了不同的参数设定对算法的影响,但在生产环境中实际构建一个对新数据泛化性能很好的模型要更复杂一些。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐