Verwendung der Cross-Validation-Funktionen von scikit-learn für Multi-Label-Klassifikatoren


20

Ich teste verschiedene Klassifikatoren in einem Datensatz, in dem es 5 Klassen gibt und jede Instanz zu einer oder mehreren dieser Klassen gehören kann. Daher verwende ich speziell die Multi-Label-Klassifikatoren von scikit-learn sklearn.multiclass.OneVsRestClassifier. Jetzt möchte ich eine Kreuzvalidierung mit der durchführen sklearn.cross_validation.StratifiedKFold. Dies erzeugt den folgenden Fehler:

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

Beachten Sie, dass das Training des Multi-Label-Klassifikators nicht zum Absturz führt, die Kreuzvalidierung jedoch. Wie muss ich eine Kreuzvalidierung für diesen Multi-Label-Klassifikator durchführen?

Ich habe auch eine zweite Version geschrieben, die das Problem in Training und Kreuzvalidierung von 5 verschiedenen Klassifikatoren aufteilt. Das funktioniert gut.

Hier ist mein Code. Die Funktion test_classifier_multilabelist diejenige, die Probleme gibt. test_classifierist mein anderer Versuch (das Problem in 5 Klassifikatoren und 5 Kreuzvalidierungen aufteilen).

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()

Ich benutze Ubuntu 13.04 und scikit-learn 0.12. Meine Daten liegen in Form von zwei Arrays (X und Y) mit den Formen (98816, 4) und (98816, 5) vor, dh 4 Features pro Instanz und 5 Klassenbeschriftungen. Die Bezeichnungen sind entweder 1 oder 0, um die Zugehörigkeit zu dieser Klasse anzuzeigen. Verwende ich das richtige Format, da ich nicht viel Dokumentation dazu sehe?

Antworten:


10

Geschichtete Stichprobe bedeutet, dass die Verteilung der Klassenzugehörigkeit in Ihrer KFold-Stichprobe erhalten bleibt. Dies ist in dem Fall, in dem Ihr Zielvektor möglicherweise mehr als eine Markierung pro Beobachtung aufweist, nicht sehr sinnvoll.

In diesem Sinne gibt es zwei mögliche Interpretationen von geschichtet.

ni=1n2n

Die andere Möglichkeit besteht darin, zu versuchen, die Trainingsdaten so zu segmentieren, dass die Wahrscheinlichkeitsmasse der Verteilung der Markierungsvektoren über die Falten ungefähr gleich ist. Z.B

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)

Wenn Sie die von KFold erstellten Indizes testen möchten, um ein normales Training zu erhalten, schreiben Sie diese Indizes neu. Sie geben die np.setdiff1d jedes Indexes mit np.arange (y.shape [0]) zurück und schließen diese dann mit einer iter- Methode in eine Klasse ein .


Danke für diese Erklärung. Ich möchte nur etwas überprüfen, OneVsRestClassifierakzeptiert das ein 2D-Array (z. B. yin Ihrem Beispielcode) oder ein Tupel von Listen mit Klassenbeschriftungen? Ich frage, weil ich mir gerade das Beispiel für die Klassifizierung mit mehreren Bezeichnungen auf scikit-learn angesehen habe und festgestellt habe, dass die make_multilabel_classificationFunktion ein Tupel von Listen mit Klassenbezeichnungen zurückgibt, z. B. ([2], [0], [0, 2], [0]...)wenn 3 Klassen verwendet werden.
Chippies

2
Es funktioniert in beide Richtungen. Wenn eine Liste von Tupeln übergeben wird, passt sie zu einem sklearn.preprocessing.LabelBinarizer. Sie wissen, dass einige der Algorithmen im Multiklassen-Multilabel-Fall funktionieren. Vor allem RandomForest.
Jessica Mick

Vielen Dank, das hat mich zumindest über die Abstürze hinweg gebracht. Im Moment habe ich auf die K-fache Kreuzvalidierung umgestellt, aber ich denke, ich werde Ihren Code bald verwenden. Jetzt hat der von cross_val_score zurückgegebene Score jedoch nur zwei Spalten, als ob es nur zwei Klassen gäbe. Das Ändern auf metrics.confusion_matrixerzeugt 2x2 Verwirrungsmatrizen. Unterstützt eine der Metriken Multi-Label-Klassifikatoren?
Chippies

Ich habe meine eigene Unterfrage beantwortet. Metriken, die Multi-Label-Klassifikatoren unterstützen, sind nur in scikit-learn 0.14-rc enthalten. Ich muss also ein Upgrade durchführen, wenn ich diese Fähigkeit möchte, oder ich mache es selbst. Danke für die Hilfe und den Code.
Chippies

Ich habe die Anordnung in der return-Anweisung entfernt. Es gibt keinen Grund, warum Sie immer einen perfekt partitionierten Satz von Datenpunkten finden werden. Lassen Sie mich wissen, ob das funktioniert. Sie sollten auch einige Tests in Ihren Code schreiben. Ich habe diesen Algorithmus irgendwie ausgeatmet, nachdem ich den ganzen Tag auf konvexe Optimierungsalgorithmen gestarrt hatte.
Jessica Mick

3

Möglicherweise möchten Sie Folgendes überprüfen: Auf die Schichtung von Mehrfachetikettendaten .

Hier erzählen die Autoren zunächst die einfache Idee des Samplings aus eindeutigen Labelsets und führen dann einen neuen Ansatz zur iterativen Schichtung ein von Multi-Label-Datasets ein.

Der Ansatz der iterativen Schichtung ist gierig.

Im Folgenden finden Sie eine kurze Übersicht über die iterative Schichtung:

Zuerst finden sie heraus, wie viele Beispiele in jede der k-Faltungen passen sollten.

  • Finden Sie die gewünschte Anzahl von Beispielen pro Falte ich pro Etikett j, cichj .

  • Aus dem Datensatz, der noch in k-Faltungen verteilt werden muss, das Etikett l identifiziert wird, für die die Anzahl der Beispiele das Minimum ist, Dl .

  • Dann für jeden Datenpunkt in Dl finde die Falte k für welche ckjmaximiert ist (hier Krawatten brechen). Was mit anderen Worten bedeutet: Welche Falte hat die maximale Nachfrage nach Etikettl, oder ist in Bezug auf das Etikett am unausgewogensten l.

  • Fügen Sie den aktuellen Datenpunkt zum Falz hinzu k Entfernen Sie aus dem obigen Schritt den Datenpunkt aus dem ursprünglichen Datensatz und passen Sie die Zählwerte von an c und fahren Sie fort, bis alle Datenpunkte nicht mehr in den Falten verteilt sind.

Die Hauptidee ist, sich zunächst auf die seltenen Etiketten zu konzentrieren. Diese Idee stammt aus der Hypothese, dass

"Wenn seltene Etiketten nicht vorrangig geprüft werden, können sie auf unerwünschte Weise verteilt und anschließend nicht repariert werden."

Um zu verstehen, wie Bindungen gebrochen sind und andere Details, empfehle ich, die Zeitung zu lesen. Aus dem experimentellen Teil kann ich auch entnehmen, dass man je nach dem Verhältnis von Labelset zu Beispielen möglicherweise das auf Unique Labelset basierende oder das vorgeschlagene iterative Stratifizierungsverfahren verwenden möchte. Für niedrigere Werte dieses Verhältnisses ist die Verteilung der Etiketten über die Falten in einigen Fällen als iterative Schichtung eng oder besser. Bei höheren Werten dieses Verhältnisses hat sich gezeigt, dass die iterative Schichtung bessere Verteilungen in den Falten bewahrt hat.


1
Link zum PDF des genannten Papiers: lpis.csd.auth.gr/publications/sechidis-ecmlpkdd-2011.pdf
Temak
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.