sklearn - 使用多个评分的交叉验证

33 投票
5 回答
27123 浏览
提问于 2025-04-18 04:34

我想计算不同分类器在交叉验证测试中的召回率精准率F值scikit-learn提供了一个叫做cross_val_score的方法,但遗憾的是,这个方法不能返回多个值。

我可以通过调用三次 cross_val_score来计算这些指标,但这样效率不高。有没有更好的解决办法呢?

到目前为止,我写了这个函数:

from sklearn import metrics

def mean_scores(X, y, clf, skf):

    cm = np.zeros(len(np.unique(y)) ** 2)
    for i, (train, test) in enumerate(skf):
        clf.fit(X[train], y[train])
        y_pred = clf.predict(X[test])
        cm += metrics.confusion_matrix(y[test], y_pred).flatten()

    return compute_measures(*cm / skf.n_folds)

def compute_measures(tp, fp, fn, tn):
     """Computes effectiveness measures given a confusion matrix."""
     specificity = tn / (tn + fp)
     sensitivity = tp / (tp + fn)
     fmeasure = 2 * (specificity * sensitivity) / (specificity + sensitivity)
     return sensitivity, specificity, fmeasure

这个函数基本上是把混淆矩阵中的值加起来,一旦你得到了假正例假负例等,就可以很容易地计算出召回率、精准率等等……不过我还是不太喜欢这个解决方案 :)

5 个回答

0

如果你想要同时查看多个指标多种类别的情况,这可能会对你有帮助。在最新的文档中,适用于scikit-learn 0.19及以上版本,你可以传入自己的字典,里面包含你想要的指标函数。

custom_scorer = {'accuracy': make_scorer(accuracy_score),
                 'balanced_accuracy': make_scorer(balanced_accuracy_score),
                 'precision': make_scorer(precision_score, average='macro'),
                 'recall': make_scorer(recall_score, average='macro'),
                 'f1': make_scorer(f1_score, average='macro'),
                 }
scores = cross_validation.cross_val_score(clf, X_train, y_train,
        cv = 10, scoring = custom_scorer)

2

你可以使用下面的代码来计算准确率、精确率、召回率以及其他一些指标,这样在每次交叉验证的时候只需要训练一次模型。

def get_true_and_pred_CV(estimator, X, y, n_folds, cv, params):
    ys = []
    for train_idx, valid_idx in cv:
        clf = estimator(**params)
        if isinstance(X, np.ndarray):
            clf.fit(X[train_idx], y[train_idx])
            cur_pred = clf.predict(X[valid_idx])
        elif isinstance(X, pd.DataFrame):
            clf.fit(X.iloc[train_idx, :], y[train_idx]) 
            cur_pred = clf.predict(X.iloc[valid_idx, :])
        else:
            raise Exception('Only numpy array and pandas DataFrame ' \
                            'as types of X are supported')

        ys.append((y[valid_idx], cur_pred))
    return ys


def fit_and_score_CV(estimator, X, y, n_folds=10, stratify=True, **params):
    if not stratify:
        cv_arg = sklearn.cross_validation.KFold(y.size, n_folds)
    else:
        cv_arg = sklearn.cross_validation.StratifiedKFold(y, n_folds)

    ys = get_true_and_pred_CV(estimator, X, y, n_folds, cv_arg, params)    
    cv_acc = map(lambda tp: sklearn.metrics.accuracy_score(tp[0], tp[1]), ys)
    cv_pr_weighted = map(lambda tp: sklearn.metrics.precision_score(tp[0], tp[1], average='weighted'), ys)
    cv_rec_weighted = map(lambda tp: sklearn.metrics.recall_score(tp[0], tp[1], average='weighted'), ys)
    cv_f1_weighted = map(lambda tp: sklearn.metrics.f1_score(tp[0], tp[1], average='weighted'), ys)

    # the approach below makes estimator fit multiple times
    #cv_acc = sklearn.cross_validation.cross_val_score(algo, X, y, cv=cv_arg, scoring='accuracy')
    #cv_pr_weighted = sklearn.cross_validation.cross_val_score(algo, X, y, cv=cv_arg, scoring='precision_weighted')
    #cv_rec_weighted = sklearn.cross_validation.cross_val_score(algo, X, y, cv=cv_arg, scoring='recall_weighted')   
    #cv_f1_weighted = sklearn.cross_validation.cross_val_score(algo, X, y, cv=cv_arg, scoring='f1_weighted')
    return {'CV accuracy': np.mean(cv_acc), 'CV precision_weighted': np.mean(cv_pr_weighted),
            'CV recall_weighted': np.mean(cv_rec_weighted), 'CV F1_weighted': np.mean(cv_f1_weighted)}

我经常使用这些函数,而不是用cross_val_score来一次性计算多个统计数据。你可以根据需要更改质量指标。

4

你可以使用这个:

from sklearn import metrics
from multiscorer import MultiScorer
import numpy as np

scorer = MultiScorer({
    'F-measure' : (f1_score, {...}),
    'Precision' : (precision_score, {...}),
    'Recall' : (recall_score, {...})
})

...

cross_val_score(clf, X, target, scoring=scorer)
results = scorer.get_results()

for name in results.keys():
     print '%s: %.4f' % (name, np.average(results[name]) )

multiscorer的源代码可以在Github上找到

7

你提供的解决方案正好实现了 cross_val_score 的功能,非常适合你的情况。看起来这是个不错的选择。

cross_val_score 有一个参数 n_jobs=,可以让评估过程并行进行。如果你需要这个功能,可以考虑用并行循环替代你的 for 循环,使用 sklearn.externals.joblib.Parallel

另外,关于多个评分的问题,scikit-learn 的问题追踪系统里正在讨论这个话题。你可以在 这里找到一个相关的讨论线程。所以虽然未来的 scikit-learn 版本可能会支持多个评分输出,但目前这是不可能的。

有一种不太正统(免责声明!)的方法可以绕过这个问题,就是稍微修改一下 cross_validation.py 的代码,去掉一个检查评分是否为数字的条件。不过,这个建议非常依赖版本,所以我会以 0.14 版本为例。

1) 在 IPython 中输入 from sklearn import cross_validation,然后输入 cross_validation??。注意显示的文件名,然后在编辑器中打开它(你可能需要管理员权限)。

2) 你会找到 这段代码,我已经标记了相关的行(1066)。它的内容是

    if not isinstance(score, numbers.Number):
        raise ValueError("scoring must return a number, got %s (%s)"
                         " instead." % (str(score), type(score)))

这些行需要被删除。为了保留原来的内容(如果你想要恢复的话),可以用以下内容替换

    if not isinstance(score, numbers.Number):
        pass
        # raise ValueError("scoring must return a number, got %s (%s)"
        #                 " instead." % (str(score), type(score)))

如果你的评分返回的结果不会让 cross_val_score 在其他地方出错,这样应该能解决你的问题。如果是这样,请告诉我。

19

现在在scikit-learn中,cross_validate是一个新功能,可以用来在多个指标上评估模型。这个功能在GridSearchCVRandomizedSearchCV中也可以使用(文档)。它最近已经被合并到主版本中,并将在v0.19中提供。

根据scikit-learn文档

cross_validate函数与cross_val_score有两个不同之处:1. 它允许指定多个评估指标。2. 除了测试得分外,它还返回一个字典,包含训练得分、拟合时间和评分时间。

典型的使用方式如下:

from sklearn.svm import SVC
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_validate
iris = load_iris()
scoring = ['precision', 'recall', 'f1']
clf = SVC(kernel='linear', C=1, random_state=0)
scores = cross_validate(clf, iris.data, iris.target == 1, cv=5,
                        scoring=scoring, return_train_score=False)

另请参见这个例子

撰写回答