什么时候应该校准预测概率的分类器?

机器算法验证 机器学习 分类 随机森林 scikit-学习 校准
2022-04-15 14:10:12

在 scikit-learn网站上,他们有一张非常漂亮的图片,显示需要校准 [一些] 分类器以纠正预测概率中的偏差:

在此处输入图像描述

他们对为什么要校准 bagged 和 boosted 树有一个很好的解释(他们也对其他分类器有解释):

诸如 bagging 和随机森林之类的方法可能难以在 0 和 1 附近做出预测,因为基础模型中的方差会使应该接近 0 或远离这些值的预测产生偏差。因为预测被限制在区间 [0,1] 内,由方差引起的误差往往是单边的,接近零和一。例如,如果一个模型应该预测一个案例的 p = 0,那么 bagging 可以实现这一点的唯一方法是如果所有 bagged trees 都预测为零。如果我们向 bagging 平均的树添加噪声,这种噪声将导致一些树在这种情况下预测大于 0 的值,从而使 bagged ensemble 的平均预测远离 0。

他们所指的来源中看到引用可能会很有趣:

其他模型(例如神经网络和袋装树)没有这些偏差,并且可以预测经过良好校准的概率

哪些状态袋装树木“经过良好校准”。

首先,我怀疑接近 0 或 1 的平均预测是否像上面第一个引用中所述的那样起作用。据我所知,二进制 RF 分类器计算数据点在整个森林中结束的 0/1 箱的数量,而不是单边概率。因此,如果我们有一个特定的数据点通过 100 个决策树的集合,并且该数据点在 bin #1 中结束了 99 次,那么 1 的概率是 0.99。同样,片面性没有问题。

不过,校准不同的分类器确实会对 logloss 产生显着的、有时是非单调的影响:

   Samples          Model  LogLoss Before  LogLoss After    Gain
0     1000       Logistic          0.3941         0.3854  1.0226
0    10000       Logistic          0.1340         0.1345  0.9959
0   100000       Logistic          0.1645         0.1645  0.9999
0     1000    Naive Bayes          0.3025         0.2291  1.3206
0    10000    Naive Bayes          0.4094         0.3055  1.3403
0   100000    Naive Bayes          0.4119         0.2594  1.5881
0     1000  Random Forest          0.4438         0.3137  1.4146
0    10000  Random Forest          0.3450         0.2776  1.2427
0   100000  Random Forest          0.3104         0.1642  1.8902
0     1000      Light GBM          0.2993         0.2219  1.3490
0    10000      Light GBM          0.2074         0.2182  0.9507
0   100000      Light GBM          0.2397         0.2534  0.9459
0     1000        Xgboost          0.1870         0.1638  1.1414
0    10000        Xgboost          0.3072         0.2967  1.0351
0   100000        Xgboost          0.1136         0.1186  0.9575
0     1000       Catboost          0.1834         0.1901  0.9649
0    10000       Catboost          0.1251         0.1377  0.9085
0   100000       Catboost          0.1600         0.1727  0.9264

问题

如果有人分享他们的意见,在什么条件下某个分类器会产生[无]偏概率估计,以及如何测试概率预测是否真正无偏,我将不胜感激。

表格和绘图的可重现代码:

from sklearn.datasets import make_classification
from sklearn.metrics import log_loss
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.calibration import calibration_curve, CalibratedClassifierCV
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from catboost import CatBoostClassifier

np.random.seed(42)

# Create classifiers
lrc = LogisticRegression(n_jobs=-1)
gnb = GaussianNB()
svc = SVC(C=1.0, probability=True,)
rfc = RandomForestClassifier(n_estimators=300, max_depth=3,n_jobs=-1)
xgb = XGBClassifier(
    n_estimators=300,
    max_depth=3,
    objective="binary:logistic",
    eval_metric="logloss",
    use_label_encoder=False,
)
lgb = LGBMClassifier(n_estimators=300, objective="binary", max_depth=3)
cat = CatBoostClassifier(n_estimators=300, max_depth=3, objective="Logloss", verbose=0)

df = pd.DataFrame()

plt.figure(figsize=(10, 10))
ax1 = plt.subplot2grid((3, 1), (0, 0), rowspan=2)
ax2 = plt.subplot2grid((3, 1), (2, 0))
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")

for clf, name in [
    (lrc, "Logistic"),
    (gnb, "Naive Bayes"),
#     (svc, "Support Vector Classification"),
    (rfc, "Random Forest"),
    (lgb, "Light GBM"),
    (xgb, "Xgboost"),
    (cat, "Catboost"),
]:
    print(name)
    for nsamples in [1000,10000,100000]:
        train_samples = 0.75 
        X, y = make_classification(
            n_samples=nsamples, n_features=20, n_informative=2, n_redundant=2
        )
        i = int(train_samples * nsamples)
        X_train = X[:i]
        X_test  = X[i:]
        y_train = y[:i]
        y_test  = y[i:]
        clf.fit(X_train, y_train)
        prob_pos = clf.predict_proba(X_test)[:, 1]
        fraction_of_positives, mean_predicted_value = calibration_curve(
            y_test, prob_pos, n_bins=10
        )

        if nsamples in [10000]:
            ax1.plot(
                mean_predicted_value,
                fraction_of_positives,
                "s-",
                label="%s" % (name + " nsamples " + str(nsamples),),
            )

            ax2.hist(
                prob_pos,
                bins=10,
                label="%s" % (name + " nsamples " + str(nsamples),),
                histtype="step",
                lw=2,
            )

        preds = clf.predict_proba(X_test)
        ll_before = log_loss(y_test, preds)
        preds = (
            CalibratedClassifierCV(clf, cv=5)
            .fit(X_train, y_train)
            .predict_proba(X_test)
        )
        ll_after = log_loss(y_test, preds)
        df = df.append(pd.DataFrame({
            "Samples": [nsamples],
            "Model": name,
            "LogLoss Before": round(ll_before,4),
            "LogLoss After": round(ll_after,4),
            "Gain": round(ll_before/ll_after,4)
        }))

ax1.set_ylabel("Fraction of positives")
ax1.set_ylim([-0.05, 1.05])
ax1.legend(loc="lower right")
ax1.set_title("Calibration plots  (reliability curve)")

ax2.set_xlabel("Mean predicted value")
ax2.set_ylabel("Count")
ax2.legend(loc="upper center", ncol=2)

plt.tight_layout()
print(df)

免责声明。我明白LogisticRegression是一种回归。

1个回答

当人们考虑校准概率时,有几种可能的情况:

  1. 模型指定错误或未经过最佳训练。当使用线性学习器对非线性关系进行建模时,就会出现这种情况;或由于过度正则化(模型欠拟合)而导致模型过于僵化;或者相反,模型过于灵活(过拟合或数据记忆)。欠拟合/过拟合也可能是由于学习时期太少/太多或装袋树。

  2. 选择了用于预测概率的错误目标函数。sklearn RandomForest 预测的扭曲概率就是这种情况,它们在其中使用"gini""entropy"用于目标函数。具有loglossas 目标函数的分类器应该产生无偏概率估计,因为它们有足够的数据可供学习。概率的片面性可以解释区间末端附近的概率扭曲,但这不能解释中间的扭曲。

  3. 使用优化的目标函数而不是精确的目标函数。XGBoost 随机森林实现就是这种情况:

XGBoost 对目标函数使用二阶逼近。这可能导致与使用目标函数的精确值的随机森林实现不同的结果。

在所有三种情况下(包括数据太少,当新数据到来时校准结果是否能很好地概括的情况尚不清楚),校准时间最好花在 (1) 正确的模型规范 (2) 选择正确的度量(目标函数)进行优化(3) 收集更多数据。

案例 1. 正确的目标,足够的数据(来自 sklearn 的逻辑回归)

在此处输入图像描述

案例 2. 错误的目标(来自 sklearn 的随机森林) 在此处输入图像描述

案例 3. 正确的目标,需要更多数据(来自 XGBoost 的随机森林) 在此处输入图像描述

案例 4. 正确的目标,需要更多的数据

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

附言

  1. “错误”指标没有任何问题,因为所有分类器,包括 sklearn 的随机森林、朴素贝叶斯或 SVC,在某些任务上都表现得非常好。这些在预测概率方面表现良好的期望是错误的和错误指定的。
  2. 校准良好指定和训练良好的模型,即使可能显示更好的样本内结果,也很可能适合测试,并且会导致对新数据的泛化更差。