SciKit-Learn 自定义评估指标:为特定机器学习任务量身定制

各位观众,各位朋友,走过路过不要错过!今天咱们不卖大力丸,只聊机器学习里的一件神秘武器:自定义评估指标

先问大家一个问题:你真的了解你的模型有多厉害吗?

是不是每次训练完,就盯着那几个默认的评分,比如Accuracy、Precision、Recall、F1-score? 它们就像商店里批量生产的衣服,虽然款式经典,但未必合你身。

想象一下,你是一位医生,要诊断病人是否得了罕见病。如果使用Accuracy,模型可能会告诉你:“没问题,99%的病人都很健康!” 因为罕见病患者只占总人口的1%。 但这显然毫无意义,你真正关心的是,模型能不能准确地揪出那些真正患病的人。

这时候,就需要我们的主角登场了:自定义评估指标! 它可以让你像裁缝一样,为你的机器学习任务量身定制一套评估标准,让模型真正理解你的需求。

一、为什么需要自定义评估指标?

简单来说,默认的评估指标不够用!

  • 数据不平衡: 就像刚才的罕见病例子,Accuracy 会被数量多的类别“绑架”,无法反映模型在少数类别上的表现。

  • 业务目标特殊: 不同的业务场景,对“好”的定义不同。例如,在金融风控中,宁可错杀一千,不可放过一个坏人;而在推荐系统中,更希望推荐准确率高,而不是召回率高。

  • 模型输出形式特殊: 有些模型输出的不是简单的类别标签,而是概率、排序或者其他形式,需要特定的评估方法。

二、SciKit-Learn 中的评估指标接口

SciKit-Learn 提供了强大的评估指标工具,我们可以利用它们来构建自己的评估函数。 核心接口是 sklearn.metrics.make_scorer。 它可以将一个普通的 Python 函数转换成一个可以被 SciKit-Learn 识别的 scorer 对象。

make_scorer 的基本用法如下:

from sklearn.metrics import make_scorer

def my_custom_metric(y_true, y_pred, **kwargs):
    """
    你的自定义评估指标函数

    Args:
        y_true: 真实标签
        y_pred: 模型预测结果
        **kwargs: 其他参数,例如 sample_weight

    Returns:
        评估指标的值
    """
    # 在这里编写你的评估逻辑
    return score

my_scorer = make_scorer(my_custom_metric, greater_is_better=True) # 如果指标值越大越好,则设置为 True

重要参数:

  • score_func: 你定义的评估函数。
  • greater_is_better: 指示指标值越大是否越好。 如果是,则设置为 True;否则,设置为 False。 默认为 True
  • needs_proba: 指示你的评估函数是否需要概率预测值(而不是硬标签)。 如果是,则设置为 True
  • needs_threshold: 指示你的评估函数是否需要决策阈值。 如果是,则设置为 True

三、案例实战:自定义评估指标

接下来,咱们通过几个具体的例子,来学习如何自定义评估指标。

案例一:加权 F1-score(Weighted F1-score)

假设你正在处理一个类别不平衡的数据集,你希望更关注少数类别。 我们可以使用加权 F1-score。

import numpy as np
from sklearn.metrics import f1_score, make_scorer

def weighted_f1(y_true, y_pred, weights):
    """
    计算加权 F1-score

    Args:
        y_true: 真实标签
        y_pred: 模型预测结果
        weights: 每个类别的权重(字典)

    Returns:
        加权 F1-score
    """
    f1 = 0
    for label in np.unique(y_true):
        f1 += weights[label] * f1_score(y_true == label, y_pred == label)
    return f1

# 示例:
y_true = np.array([0, 0, 0, 1, 1])
y_pred = np.array([0, 0, 1, 1, 0])
weights = {0: 0.2, 1: 0.8} # 类别 1 更重要

weighted_f1_scorer = make_scorer(weighted_f1, greater_is_better=True, weights=weights)

# 如何在 cross_val_score 中使用:
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
scores = cross_val_score(model, np.random.rand(5, 2), y_true, cv=2, scoring=weighted_f1_scorer) #scoring 参数指定评估函数
print(f"Cross-validation scores: {scores}")

代码解释:

  1. weighted_f1 函数计算加权 F1-score。 它遍历每个类别,计算该类别的 F1-score,并乘以该类别的权重。
  2. make_scorerweighted_f1 函数转换成一个 scorer 对象。
  3. cross_val_score 使用 weighted_f1_scorer 作为评估指标。 注意 scoring 参数的设置。

案例二:针对排序任务的 NDCG(Normalized Discounted Cumulative Gain)

假设你正在构建一个推荐系统,你需要评估模型对物品排序的能力。 NDCG 是一个常用的排序指标。

import numpy as np
from sklearn.metrics import make_scorer

def dcg_at_k(r, k):
    """
    计算 DCG(Discounted Cumulative Gain)

    Args:
        r: 相关性得分列表(例如,[3, 2, 3, 0, 1, 2])
        k: 截断位置

    Returns:
        DCG 值
    """
    r = np.asfarray(r)[:k]
    if r.size:
        return np.sum((np.power(2, r) - 1) / np.log2(np.arange(2, r.size + 2)))
    return 0.

def ndcg_at_k(r, k):
    """
    计算 NDCG(Normalized Discounted Cumulative Gain)

    Args:
        r: 相关性得分列表
        k: 截断位置

    Returns:
        NDCG 值
    """
    idcg = dcg_at_k(sorted(r, reverse=True), k)
    if not idcg:
        return 0.
    return dcg_at_k(r, k) / idcg

def ndcg_scorer(y_true, y_pred, k=10):
  """
  计算 NDCG,适用于 make_scorer

  Args:
      y_true: 真实相关性得分,与 y_pred 一一对应
      y_pred: 预测相关性得分,用于排序

  Returns:
      NDCG 值
  """
  # 将真实标签与预测值按照预测值排序,取前 k 个
  ranked_indices = np.argsort(y_pred)[::-1][:k]
  relevant_labels = y_true[ranked_indices]
  return ndcg_at_k(relevant_labels, k)

# 示例:
y_true = np.array([3, 2, 3, 0, 1, 2]) # 真实相关性得分
y_pred = np.array([0.8, 0.5, 0.7, 0.1, 0.3, 0.6]) # 预测相关性得分

ndcg_at_10_scorer = make_scorer(ndcg_scorer, greater_is_better=True)

# 如何使用:
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor

X = np.random.rand(6, 5) # 假设有 6 个样本,每个样本有 5 个特征
X_train, X_test, y_train, y_test = train_test_split(X, y_true, test_size=0.2, random_state=42)

model = GradientBoostingRegressor()
model.fit(X_train, y_pred[np.array([0,2,4,5])]) # 注意:这里训练的时候使用的是预测值,因为 NDCG 是排序指标

y_pred_test = model.predict(X_test)

score = ndcg_scorer(y_test, y_pred_test)
print(f"NDCG@10: {score}")

代码解释:

  1. dcg_at_k 函数计算 DCG。
  2. ndcg_at_k 函数计算 NDCG。
  3. ndcg_scorer 函数用于 make_scorer, 接受真实标签和预测标签, 然后按照预测标签排序, 并计算排序后的真实标签的 NDCG 值
  4. make_scorerndcg_scorer 函数转换成一个 scorer 对象。
  5. 在训练模型时,需要注意,NDCG 是一个排序指标,所以通常需要用模型预测的相关性得分进行训练。在预测的时候,用模型预测的相关性得分进行排序,然后计算 NDCG。

案例三:考虑误判代价的评估指标(Cost-sensitive Metric)

假设你正在构建一个疾病诊断模型,将健康的人误判为病人(假阳性)的代价远低于将病人误判为健康的人(假阴性)。 你可以自定义一个考虑误判代价的评估指标。

import numpy as np
from sklearn.metrics import confusion_matrix, make_scorer

def cost_sensitive_metric(y_true, y_pred, cost_fp=1, cost_fn=10):
    """
    考虑误判代价的评估指标

    Args:
        y_true: 真实标签
        y_pred: 模型预测结果
        cost_fp: 假阳性的代价
        cost_fn: 假阴性的代价

    Returns:
        代价
    """
    tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
    cost = cost_fp * fp + cost_fn * fn
    return -cost # 注意:这里返回的是负代价,因为 make_scorer 默认 greater_is_better=True

# 示例:
y_true = np.array([0, 0, 0, 1, 1])
y_pred = np.array([0, 0, 1, 0, 0])
cost_fp = 1
cost_fn = 10

cost_sensitive_scorer = make_scorer(cost_sensitive_metric, greater_is_better=True, cost_fp=cost_fp, cost_fn=cost_fn)

# 如何使用:
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC

param_grid = {'C': [0.1, 1, 10], 'gamma': [0.1, 1]}
grid_search = GridSearchCV(SVC(), param_grid, scoring=cost_sensitive_scorer, cv=2) #scoring 参数指定评估函数
grid_search.fit(np.random.rand(5, 2), y_true)

print(f"Best parameters: {grid_search.best_params_}")
print(f"Best score: {grid_search.best_score_}")

代码解释:

  1. cost_sensitive_metric 函数计算总代价。 它使用混淆矩阵计算假阳性和假阴性的数量,然后根据它们的代价计算总代价。
  2. make_scorercost_sensitive_metric 函数转换成一个 scorer 对象。 注意,由于我们希望代价越小越好,所以我们返回的是负代价,并设置 greater_is_better=True
  3. GridSearchCV 使用 cost_sensitive_scorer 作为评估指标来选择最佳模型参数。

四、高级技巧:利用 needs_probaneeds_threshold

有些评估指标需要模型的概率预测值或者决策阈值。 make_scorer 提供了 needs_probaneeds_threshold 参数来满足这些需求。

  • needs_proba=True: 你的评估函数需要 predict_proba 的输出(概率)。
  • needs_threshold=True: 你的评估函数需要一个决策阈值。 这通常用于评估模型在不同阈值下的表现,例如绘制 ROC 曲线。

案例四:使用概率预测值的评估指标(AUC-PR)

AUC-PR(Area Under the Precision-Recall Curve)是一个常用的评估指标,特别是在数据不平衡的情况下。 它需要模型的概率预测值。

from sklearn.metrics import precision_recall_curve, auc, make_scorer
import numpy as np

def auc_pr(y_true, y_proba):
    """
    计算 AUC-PR

    Args:
        y_true: 真实标签
        y_proba: 模型预测的概率值 (predict_proba 的输出)

    Returns:
        AUC-PR 值
    """
    precision, recall, _ = precision_recall_curve(y_true, y_proba)
    return auc(recall, precision)

auc_pr_scorer = make_scorer(auc_pr, greater_is_better=True, needs_proba=True)

# 示例:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

X = np.random.rand(100, 5)
y = np.random.randint(0, 2, 100)

model = LogisticRegression()
scores = cross_val_score(model, X, y, cv=5, scoring=auc_pr_scorer)
print(f"Cross-validation AUC-PR scores: {scores}")

代码解释:

  1. auc_pr 函数计算 AUC-PR。 它使用 precision_recall_curve 函数计算精确率和召回率,然后使用 auc 函数计算 AUC。
  2. make_scorerauc_pr 函数转换成一个 scorer 对象,并设置 needs_proba=True。 这告诉 SciKit-Learn 在调用评估函数时,传递模型的 predict_proba 输出。

案例五:使用决策阈值的评估指标

假设你想评估模型在不同决策阈值下的性能,例如绘制 ROC 曲线。

from sklearn.metrics import roc_curve, make_scorer
import numpy as np

def custom_roc_auc_score(y_true, y_score):
    """
    自定义 ROC AUC 评分函数

    Args:
        y_true: 真实标签
        y_score: 模型输出的得分(可以是概率或其他值)

    Returns:
        ROC AUC 值
    """
    fpr, tpr, thresholds = roc_curve(y_true, y_score)
    auc_value = np.trapz(tpr, fpr)
    return auc_value

# 创建自定义评分器
roc_auc_scorer = make_scorer(custom_roc_auc_score, greater_is_better=True)

# 示例使用
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

# 生成一些示例数据
X = np.random.rand(100, 2)
y = np.random.randint(0, 2, 100)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 训练 Logistic 回归模型
model = LogisticRegression()
model.fit(X_train, y_train)

# 预测测试集概率值
y_scores = model.predict_proba(X_test)[:, 1]  # 预测概率,取正类概率

# 使用自定义评分器评估模型
score = roc_auc_scorer(model, X_test, y_test) #注意: 这里传递的是 model, X_test, y_test
print(f"Custom ROC AUC Score: {score}")

五、注意事项

  • 一致性: 确保你的自定义评估指标与你的业务目标一致。
  • 可解释性: 尽量选择易于理解和解释的指标。
  • 避免过拟合: 不要过度优化评估指标,否则可能会导致模型在真实场景中表现不佳。
  • make_scorer 的参数传递: 使用 make_scorer 时,可以通过 **kwargs 传递额外的参数给你的评估函数。
  • 评分函数的形式: 当使用 make_scorer 时,评分函数的参数顺序必须是 y_true, y_pred, **kwargs。 如果你需要访问模型本身,或者特征矩阵 X,你需要使用 _score 函数的形式,例如 scorer(estimator, X, y)。 在这种情况下,你需要将模型作为 scorer 的第一个参数传递进去。

六、总结

自定义评估指标是机器学习工具箱中的一件利器。 它可以让你更好地理解你的模型,并使其更符合你的业务需求。 不要再满足于默认的评估指标了,勇敢地去定制你的专属评估标准吧!

记住,机器学习不仅仅是算法和代码,更重要的是理解你的数据和你的目标!

表格总结

特性 默认评估指标 自定义评估指标
适用性 通用场景,适用于大多数情况 特定场景,针对特定业务目标和数据特征
灵活性 较低,无法根据具体需求进行调整 较高,可以根据需求灵活定义评估逻辑和参数
可解释性 较高,容易理解和解释 视具体定义而定,需要仔细设计才能保证可解释性
示例 Accuracy, Precision, Recall, F1-score, AUC 加权 F1-score, NDCG, 考虑误判代价的评估指标, AUC-PR
SciKit-Learn 工具 内置的 metrics 模块 sklearn.metrics.make_scorer

最后,希望今天的分享对大家有所帮助! 祝大家都能训练出令人满意的模型! 谢谢大家!

发表回复

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