如何在多标签分类器上使用scikit-learn的交叉验证功能


20

我正在一个5个类的数据集上测试不同的分类器,每个实例可以属于一个或多个这些类,因此我正在使用scikit-learn的多标签分类器sklearn.multiclass.OneVsRestClassifier。现在,我想使用进行交叉验证sklearn.cross_validation.StratifiedKFold。这将产生以下错误:

Traceback (most recent call last):
  File "mlfromcsv.py", line 93, in <module>
    main()
  File "mlfromcsv.py", line 77, in main
    test_classifier_multilabel(svm.LinearSVC(), X, Y, 'Linear Support Vector Machine')
  File "mlfromcsv.py", line 44, in test_classifier_multilabel
    scores = cross_validation.cross_val_score(clf_ml, X, Y_list, cv=cv, score_func=metrics.precision_recall_fscore_support, n_jobs=jobs)
  File "/usr/lib/pymodules/python2.7/sklearn/cross_validation.py", line 1046, in cross_val_score
    X, y = check_arrays(X, y, sparse_format='csr')
  File "/usr/lib/pymodules/python2.7/sklearn/utils/validation.py", line 144, in check_arrays
    size, n_samples))
ValueError: Found array with dim 5. Expected 98816

请注意,训练多标签分类器不会崩溃,但是交叉验证会崩溃。如何为此多标签分类器执行交叉验证?

我还编写了第二个版本,将问题分解为训练和交叉验证5个单独的分类器。这样很好。

这是我的代码。功能test_classifier_multilabel是给问题的一种。 test_classifier这是我的另一尝试(将问题分为5个分类器和5个交叉验证)。

import numpy as np
from sklearn import *
from sklearn.multiclass import OneVsRestClassifier
from sklearn.neighbors import KNeighborsClassifier
import time

def test_classifier(clf, X, Y, description, jobs=1):
    print '=== Testing classifier {0} ==='.format(description)
    for class_idx in xrange(Y.shape[1]):
        print ' > Cross-validating for class {:d}'.format(class_idx)
        n_samples = X.shape[0]
        cv = cross_validation.StratifiedKFold(Y[:,class_idx], 3)
        t_start = time.clock()
        scores = cross_validation.cross_val_score(clf, X, Y[:,class_idx], cv=cv, score_func=metrics.precision_recall_fscore_support, n_jobs=jobs)
        t_end = time.clock();
        print 'Cross validation time: {:0.3f}s.'.format(t_end-t_start)
        str_tbl_fmt = '{:>15s}{:>15s}{:>15s}{:>15s}{:>15s}'
        str_tbl_entry_fmt = '{:0.2f} +/- {:0.2f}'
        print str_tbl_fmt.format('', 'Precision', 'Recall', 'F1 score', 'Support')
        for (score_class, lbl) in [(0, 'Negative'), (1, 'Positive')]:
            mean_precision = scores[:,0,score_class].mean()
            std_precision = scores[:,0,score_class].std()
            mean_recall = scores[:,1,score_class].mean()
            std_recall = scores[:,1,score_class].std()
            mean_f1_score = scores[:,2,score_class].mean()
            std_f1_score = scores[:,2,score_class].std()
            support = scores[:,3,score_class].mean()
            print str_tbl_fmt.format(
                lbl,
                str_tbl_entry_fmt.format(mean_precision, std_precision),
                str_tbl_entry_fmt.format(mean_recall, std_recall),
                str_tbl_entry_fmt.format(mean_f1_score, std_f1_score),
                '{:0.2f}'.format(support))

def test_classifier_multilabel(clf, X, Y, description, jobs=1):
    print '=== Testing multi-label classifier {0} ==='.format(description)
    n_samples = X.shape[0]
    Y_list = [value for value in Y.T]
    print 'Y_list[0].shape:', Y_list[0].shape, 'len(Y_list):', len(Y_list)
    cv = cross_validation.StratifiedKFold(Y_list, 3)
    clf_ml = OneVsRestClassifier(clf)
    accuracy = (clf_ml.fit(X, Y).predict(X) != Y).sum()
    print 'Accuracy: {:0.2f}'.format(accuracy)
    scores = cross_validation.cross_val_score(clf_ml, X, Y_list, cv=cv, score_func=metrics.precision_recall_fscore_support, n_jobs=jobs)
    str_tbl_fmt = '{:>15s}{:>15s}{:>15s}{:>15s}{:>15s}'
    str_tbl_entry_fmt = '{:0.2f} +/- {:0.2f}'
    print str_tbl_fmt.format('', 'Precision', 'Recall', 'F1 score', 'Support')
    for (score_class, lbl) in [(0, 'Negative'), (1, 'Positive')]:
        mean_precision = scores[:,0,score_class].mean()
        std_precision = scores[:,0,score_class].std()
        mean_recall = scores[:,1,score_class].mean()
        std_recall = scores[:,1,score_class].std()
        mean_f1_score = scores[:,2,score_class].mean()
        std_f1_score = scores[:,2,score_class].std()
        support = scores[:,3,score_class].mean()
        print str_tbl_fmt.format(
            lbl,
            str_tbl_entry_fmt.format(mean_precision, std_precision),
            str_tbl_entry_fmt.format(mean_recall, std_recall),
            str_tbl_entry_fmt.format(mean_f1_score, std_f1_score),
            '{:0.2f}'.format(support))

def main():
    nfeatures = 13
    nclasses = 5
    ncolumns = nfeatures + nclasses

    data = np.loadtxt('./feature_db.csv', delimiter=',', usecols=range(ncolumns))

    print data, data.shape
    X = np.hstack((data[:,0:3], data[:,(nfeatures-1):nfeatures]))
    print 'X.shape:', X.shape
    Y = data[:,nfeatures:ncolumns]
    print 'Y.shape:', Y.shape

    test_classifier(svm.LinearSVC(), X, Y, 'Linear Support Vector Machine', jobs=-1)
    test_classifier_multilabel(svm.LinearSVC(), X, Y, 'Linear Support Vector Machine')

if  __name__ =='__main__':
    main()

我正在使用Ubuntu 13.04和scikit-learn 0.12。我的数据是具有形状(98816,4)和(98816,5)的两个数组(X和Y)的形式,即每个实例4个特征和5个类标签。标签为1或0,以指示该类中的成员身份。我使用的格式正确吗,因为我没有太多相关文档?

Answers:


10

分层采样意味着在KFold采样中保留了类成员分布。这在多标签的情况下没有多大意义,在这种情况下,目标向量每个观察可能有多个标签。

从这个意义上讲,分层有两种可能的解释。

ni=1n2ñ

另一种选择是尝试对训练数据进行分段,以使标记矢量分布的概率质量在折叠数上大致相同。例如

import numpy as np

np.random.seed(1)
y = np.random.randint(0, 2, (5000, 5))
y = y[np.where(y.sum(axis=1) != 0)[0]]


def proba_mass_split(y, folds=7):
    obs, classes = y.shape
    dist = y.sum(axis=0).astype('float')
    dist /= dist.sum()
    index_list = []
    fold_dist = np.zeros((folds, classes), dtype='float')
    for _ in xrange(folds):
        index_list.append([])
    for i in xrange(obs):
        if i < folds:
            target_fold = i
        else:
            normed_folds = fold_dist.T / fold_dist.sum(axis=1)
            how_off = normed_folds.T - dist
            target_fold = np.argmin(np.dot((y[i] - .5).reshape(1, -1), how_off.T))
        fold_dist[target_fold] += y[i]
        index_list[target_fold].append(i)
    print("Fold distributions are")
    print(fold_dist)
    return index_list

if __name__ == '__main__':
    proba_mass_split(y)

为了获得正常的训练,测试KFold生成的索引要重写为它返回具有np.arange(y.shape [0])的每个索引的np.setdiff1d,然后使用iter方法将其包装在类中。


感谢您的解释。我只想检查一下,是否OneVsRestClassifier接受2D数组(例如,y在您的示例代码中)或类标签列表的元组?我问是因为我刚才看了scikit-learn上的多标签分类示例,发现该make_multilabel_classification函数返回了一个类标签列表的元组,例如,([2], [0], [0, 2], [0]...)当使用3个类时?
Chippies

2
它可以双向工作。当传递一个元组列表时,它适合一个sklearn.preprocessing.LabelBinarizer。您知道一些算法可以在多类多标签的情况下使用。尤其是RandomForest。
杰西卡·米克

非常感谢,这至少使我摆脱了崩溃。目前,我已切换到K折交叉验证,但我想我会尽快使用您的代码。但是,现在,cross_val_score返回的分数只有两列,即好像只有两个类。更改为metrics.confusion_matrix2x2混淆矩阵。是否有任何指标支持多标签分类器?
Chippies

我已经回答了我自己的子问题。支持多标签分类器的度量标准仅出现在scikit-learn 0.14-rc中,因此,如果我想要该功能,则必须升级,否则请自行升级。感谢您的帮助和代码。
Chippies

我删除了return语句上的数组。没有理由总是找到一个完美分区的数据点集。让我知道是否可行。您还应该在代码中编写一些测试。一整天盯着凸优化算法后,我有点屏息了。
Jessica Mick

3

您可能需要检查:关于多标签数据的分层

在这里,作者首先讲述了从唯一标签集采样的简单思想,然后介绍了一种新的迭代分层方法标签集针对多标签数据集进行。

迭代分层的方法是贪婪的。

为了快速浏览,这是迭代分层的作用:

首先,他们找出每个k折应包含多少个示例。

  • ijcij

  • lDl

  • Dlkckjll

  • kc

主要思想是首先关注稀有标签,该思想来自以下假设:

“如果稀有标签没有得到优先检查,那么它们可能会以不希望的方式分发,并且随后将无法修复。”

要了解关系破裂的方式以及其他细节,我建议您阅读本文。另外,从实验部分我可以理解的是,根据标签集/示例的比例,可能要使用基于唯一标签集或此提议的迭代分层方法。对于该比率的较低值,在某些情况下,作为迭代分层,标签在折痕上的分布接近或更好。对于该比率的较高值,表明迭代分层在折痕中保持了更好的分布。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.