Python实现模型校准(Model Calibration):温度缩放与Platt缩放的算法细节

Python实现模型校准(Model Calibration):温度缩放与Platt缩放的算法细节

各位朋友,大家好!今天我们来深入探讨一个在机器学习模型部署中至关重要但常常被忽视的领域:模型校准(Model Calibration)。具体来说,我们将聚焦于两种常见的校准方法:温度缩放(Temperature Scaling)和Platt缩放(Platt Scaling)。我们将从理论基础入手,然后深入探讨它们的算法细节,并通过Python代码进行实现。

1. 模型校准的必要性

在分类任务中,许多机器学习模型,例如神经网络、支持向量机和梯度提升机,不仅会预测一个类别,还会为每个类别生成一个置信度分数,通常表示为概率。理想情况下,这些概率应该反映模型预测的真实准确性。也就是说,如果模型预测一个样本属于某个类别的概率为80%,那么在所有预测为80%的样本中,实际属于该类别的样本比例应该也接近80%。

然而,在实践中,许多模型都存在“过度自信”或“欠自信”的问题。例如,一个模型可能会为所有预测都给出接近1或0的概率,即使它的实际准确率远低于100%。这种不校准的概率会给决策带来负面影响,尤其是在需要根据置信度进行风险评估的应用场景中,例如医疗诊断、金融风险评估等。

2. 校准曲线:可视化校准程度

为了评估模型的校准程度,我们通常使用校准曲线。校准曲线将预测的置信度分数与实际准确率进行对比。

  • 横轴(x轴): 将模型的预测概率划分为若干个区间(bins),例如0-0.1, 0.1-0.2, …, 0.9-1.0。
  • 纵轴(y轴): 对于每个区间,计算实际属于预测类别的样本比例(准确率)。

如果模型是完美校准的,那么校准曲线应该接近于对角线(y=x)。如果校准曲线位于对角线上方,则表明模型是“欠自信”的;如果位于对角线下方,则表明模型是“过度自信”的。

3. 温度缩放(Temperature Scaling)

温度缩放是一种简单有效的校准方法,特别适用于神经网络。它的核心思想是引入一个可学习的参数——温度 T,对模型的logits(未经过softmax的输出)进行缩放:

p_i = softmax(z_i / T)

其中:

  • p_i 是校准后的概率。
  • z_i 是模型原始的logits。
  • T 是温度参数,是一个标量。

温度 T 是通过优化校准集上的负对数似然(NLL)来学习的。当 T > 1 时,概率分布会变得更加平滑,从而降低模型的置信度;当 T < 1 时,概率分布会变得更加尖锐,从而提高模型的置信度。

3.1 温度缩放的算法细节

  1. 训练数据准备: 首先,你需要一个训练好的模型和一个独立的校准集(calibration set)。这个校准集不能是训练集或验证集,否则会导致过拟合。
  2. Logits提取: 使用校准集,从模型中提取每个样本的logits。
  3. 目标函数定义: 温度缩放的目标是最小化校准集上的负对数似然(NLL)。NLL的公式如下:

    NLL = - sum(log(p_i))

    其中p_i是样本i的真实标签对应的预测概率。

  4. 优化过程: 使用优化算法(例如L-BFGS、Adam等)来找到最优的温度 T,使得NLL最小。
  5. 校准: 使用学习到的温度 T 对新的数据进行概率校准。

3.2 Python实现温度缩放

import numpy as np
from scipy.optimize import minimize
from sklearn.metrics import log_loss

class TemperatureScaling:
    def __init__(self):
        self.temperature = 1.0  # 初始化温度为1

    def fit(self, logits, labels):
        """
        训练温度参数 T
        :param logits: 校准集的logits
        :param labels: 校准集的真实标签
        """
        def objective(temperature):
            """
            定义优化目标函数:负对数似然
            """
            temperature = np.clip(temperature, 1e-6, 1e6) # 避免温度过小或过大
            scaled_logits = logits / temperature
            probabilities = self.softmax(scaled_logits)
            # 使用log_loss计算NLL,更稳定
            nll = log_loss(labels, probabilities) # labels 必须是one-hot编码或者整数索引
            return nll

        # 使用L-BFGS-B优化算法
        result = minimize(objective, x0=np.array([self.temperature]), method='L-BFGS-B', bounds=[(1e-6, 1e6)]) #bounds防止温度为负数,或者为0
        self.temperature = result.x[0]
        print(f"Optimal temperature: {self.temperature}")

    def predict(self, logits):
        """
        使用学习到的温度进行预测
        :param logits: 新数据的logits
        :return: 校准后的概率
        """
        scaled_logits = logits / self.temperature
        return self.softmax(scaled_logits)

    def softmax(self, logits):
        """
        Softmax 函数
        """
        e_x = np.exp(logits - np.max(logits, axis=-1, keepdims=True)) # 防止溢出
        return e_x / np.sum(e_x, axis=-1, keepdims=True)

# 示例用法
if __name__ == '__main__':
    # 模拟一些logits和labels
    logits = np.array([[2.0, 1.0, 0.5],
                       [1.5, 2.5, 0.0],
                       [0.8, 1.2, 3.0]])
    labels = np.array([0, 1, 2]) # 假设是多分类问题

    # 创建并训练温度缩放模型
    temperature_scaling = TemperatureScaling()
    temperature_scaling.fit(logits, labels)

    # 使用训练好的模型进行预测
    calibrated_probabilities = temperature_scaling.predict(logits)
    print("Calibrated Probabilities:")
    print(calibrated_probabilities)

3.3 代码解释

  • TemperatureScaling 类封装了温度缩放的逻辑。
  • fit 方法使用 scipy.optimize.minimize 函数来优化温度 T。 我们使用L-BFGS-B算法,并限制温度的范围,防止优化过程中出现异常值。
  • predict 方法使用学习到的温度 T 对新的logits进行缩放,并返回校准后的概率。
  • softmax 方法用于将logits转换为概率。为了防止指数溢出,我们在计算softmax之前减去了logits的最大值。
  • 示例用法中,我们模拟了一些logits和labels,并展示了如何使用 TemperatureScaling 类进行训练和预测。

4. Platt缩放(Platt Scaling)

Platt缩放,也称为逻辑回归校准,最初是为支持向量机(SVM)设计的,但也可以应用于其他分类模型。它通过学习一个逻辑回归模型来校准模型的输出。Platt缩放将模型的输出(例如,SVM的决策函数值)作为输入,并将其映射到概率空间。

4.1 Platt缩放的算法细节

Platt缩放的核心是学习以下逻辑回归模型的参数 AB

P(y=1 | f) = 1 / (1 + exp(A * f + B))

其中:

  • P(y=1 | f) 是校准后的概率,表示给定模型输出 f 的情况下,样本属于类别1的概率。
  • f 是模型的输出,例如,SVM的决策函数值或神经网络倒数第二层的输出。
  • AB 是逻辑回归模型的参数,需要通过优化来学习。

Platt缩放的训练过程通常包括以下步骤:

  1. 训练数据准备: 准备一个训练好的模型和一个独立的校准集。
  2. 模型输出提取: 使用校准集,从模型中提取每个样本的输出 f
  3. 目标函数定义: Platt缩放的目标是最大化校准集上的似然函数,等价于最小化负对数似然(NLL)。
  4. 优化过程: 使用优化算法(例如,L-BFGS、梯度下降等)来找到最优的参数 AB,使得NLL最小。
  5. 校准: 使用学习到的参数 AB 对新的数据进行概率校准。

4.2 Python实现Platt缩放

import numpy as np
from scipy.optimize import minimize
from sklearn.metrics import log_loss

class PlattScaling:
    def __init__(self):
        self.A = 1.0  # 初始化参数A
        self.B = 0.0  # 初始化参数B

    def fit(self, model_outputs, labels):
        """
        训练 Platt 缩放的参数 A 和 B
        :param model_outputs: 校准集的模型输出 (例如 SVM 的决策函数值)
        :param labels: 校准集的真实标签 (0 或 1)
        """
        def objective(params):
            """
            定义优化目标函数:负对数似然
            """
            A, B = params
            probabilities = 1 / (1 + np.exp(A * model_outputs + B))
            # 使用log_loss 计算NLL,注意labels需要是0和1
            nll = log_loss(labels, probabilities)
            return nll

        # 使用 L-BFGS-B 优化算法
        result = minimize(objective, x0=np.array([self.A, self.B]), method='L-BFGS-B')
        self.A, self.B = result.x
        print(f"Optimal A: {self.A}, Optimal B: {self.B}")

    def predict(self, model_outputs):
        """
        使用学习到的参数 A 和 B 进行预测
        :param model_outputs: 新数据的模型输出
        :return: 校准后的概率
        """
        return 1 / (1 + np.exp(self.A * model_outputs + self.B))

# 示例用法
if __name__ == '__main__':
    # 模拟一些模型输出和标签
    model_outputs = np.array([1.2, -0.5, 0.8, -1.0, 0.3])
    labels = np.array([1, 0, 1, 0, 1])

    # 创建并训练 Platt 缩放模型
    platt_scaling = PlattScaling()
    platt_scaling.fit(model_outputs, labels)

    # 使用训练好的模型进行预测
    calibrated_probabilities = platt_scaling.predict(model_outputs)
    print("Calibrated Probabilities:")
    print(calibrated_probabilities)

4.3 代码解释

  • PlattScaling 类封装了 Platt 缩放的逻辑。
  • fit 方法使用 scipy.optimize.minimize 函数来优化参数 AB。同样,我们使用L-BFGS-B算法。
  • predict 方法使用学习到的参数 AB 对新的模型输出进行校准,并返回校准后的概率。
  • 示例用法中,我们模拟了一些模型输出和标签,并展示了如何使用 PlattScaling 类进行训练和预测。

5. 温度缩放 vs. Platt缩放

特性 温度缩放 Platt缩放
模型适用性 主要用于神经网络 可用于各种分类模型
参数数量 1个参数 (温度 T) 2个参数 (AB)
实现复杂度 简单 相对复杂
适用场景 当模型输出已经接近概率分布时,温度缩放通常表现良好 当模型输出与概率分布相差较大时,Platt缩放可能更有效
优点 计算效率高,易于实现 适用性更广,可以处理更复杂的校准问题
缺点 对模型输出的分布有一定要求 容易过拟合,需要更多的校准数据

6. 选择合适的校准方法

选择合适的校准方法取决于具体的应用场景和模型的特性。一般来说,可以考虑以下因素:

  • 模型类型: 对于神经网络,温度缩放通常是一个不错的选择,因为它简单有效。对于其他类型的模型,Platt缩放可能更适用。
  • 校准集大小: 如果校准集较小,则应选择参数较少的校准方法,例如温度缩放,以避免过拟合。
  • 模型输出的分布: 如果模型输出已经接近概率分布,则温度缩放可能就足够了。如果模型输出与概率分布相差较大,则可能需要使用更复杂的校准方法,例如Platt缩放。
  • 计算资源: 温度缩放的计算效率通常高于Platt缩放。

7. 评估校准效果

在应用校准方法后,需要评估校准效果。常用的评估指标包括:

  • 校准曲线: 观察校准曲线是否接近对角线。
  • 期望校准误差(Expected Calibration Error, ECE): ECE 衡量了预测置信度与实际准确率之间的平均差异。ECE越小,表示模型的校准效果越好。
  • 最大校准误差(Maximum Calibration Error, MCE): MCE 衡量了预测置信度与实际准确率之间的最大差异。MCE越小,表示模型的校准效果越好。
  • Brier Score: Brier Score 衡量了预测概率与真实结果之间的平均平方误差。Brier Score越小,表示模型的校准效果越好。

8. 多分类问题的校准

上述讨论主要集中在二分类问题上。对于多分类问题,温度缩放可以直接应用,而Platt缩放则需要进行一些修改。一种常见的做法是使用“一对所有”(one-vs-all)策略,为每个类别训练一个Platt缩放模型。

9. 校准后的概率使用场景

校准后的概率在许多实际应用中都非常有用,例如:

  • 风险评估: 在金融领域,可以使用校准后的概率来评估贷款违约的风险。
  • 医疗诊断: 在医疗领域,可以使用校准后的概率来辅助医生进行诊断。
  • 自动驾驶: 在自动驾驶领域,可以使用校准后的概率来评估感知系统的可靠性。
  • 决策制定: 在需要根据置信度进行决策的场景中,可以使用校准后的概率来提高决策的准确性。

10. 总结和建议

今天我们深入探讨了模型校准的重要性,并详细介绍了两种常见的校准方法:温度缩放和Platt缩放。通过Python代码示例,我们了解了它们的算法细节和实现过程。希望这些知识能够帮助你在实际应用中更好地校准你的模型,从而提高模型的性能和可靠性。

选择合适的校准方法并进行有效评估

校准方法的选择需要根据具体情况权衡,并且评估校准效果至关重要。

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注