Jak korzystać z funkcji weryfikacji krzyżowej scikit-learn w klasyfikatorach z wieloma etykietami

20

Testuję różne klasyfikatory na zbiorze danych, w którym jest 5 klas, a każda instancja może należeć do jednej lub więcej z tych klas, więc w szczególności używam klasyfikatorów wieloznakowych scikit-learn sklearn.multiclass.OneVsRestClassifier. Teraz chcę przeprowadzić weryfikację krzyżową za pomocą sklearn.cross_validation.StratifiedKFold. Powoduje to następujący błąd:

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

Należy pamiętać, że szkolenie klasyfikatora obejmującego wiele etykiet nie powoduje awarii, ale weryfikacja krzyżowa tak. Jak przeprowadzić weryfikację krzyżową tego klasyfikatora z wieloma etykietami?

Napisałem również drugą wersję, która rozkłada problem na szkolenie i weryfikację krzyżową 5 oddzielnych klasyfikatorów. To działa dobrze.

Oto mój kod. Ta funkcja test_classifier_multilabelpowoduje problemy. test_classifierto moja druga próba (podział problemu na 5 klasyfikatorów i 5 krzyżowych walidacji).

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

Używam Ubuntu 13.04 i scikit-learn 0.12. Moje dane mają postać dwóch tablic (X i Y), które mają kształty (98816, 4) i (98816, 5), tj. 4 cechy na instancję i 5 etykiet klas. Etykiety mają wartość 1 lub 0, wskazując członkostwo w tej klasie. Czy używam właściwego formatu, ponieważ nie widzę dużo dokumentacji na ten temat?

wióry
źródło

Odpowiedzi:

10

Próbkowanie warstwowe oznacza, że ​​rozkład członkostwa w klasie jest zachowany w próbkowaniu KFold. Nie ma to większego sensu w przypadku wielowarstwowego, w którym wektor docelowy może mieć więcej niż jedną etykietę na obserwację.

Istnieją dwie możliwe interpretacje stratyfikacji w tym sensie.

Dla etykiet, w których co najmniej jedna jest wypełniona, co daje unikalnych etykiet. Można wykonać próbkowanie warstwowe na każdym z unikatowych pojemników z etykietami.n i = 1 2 nnja=1n2)n

Inną opcją jest próba segmentacji danych treningowych, aby masa prawdopodobieństwa rozmieszczenia wektorów znaczników była w przybliżeniu taka sama na fałdach. Na przykład

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)

Aby uzyskać normalne szkolenie, testowanie indeksów generowanych przez KFold wymaga przepisania tego, aby zwracało np.setdiff1d każdego indeksu za pomocą np.arange (y.shape [0]), a następnie zawijało to w klasie metodą iteracyjną .

Jessica Mick
źródło
Dziękuję za to wyjaśnienie. Chciałbym tylko coś sprawdzić, czy OneVsRestClassifierakceptuje tablicę 2D (np. yW twoim przykładowym kodzie) czy krotkę list etykiet klas? Pytam, ponieważ właśnie spojrzałem na przykład klasyfikacji wielu etykiet na scikit-learn i zobaczyłem, że make_multilabel_classificationfunkcja zwraca krotkę list etykiet klas, np. Przy ([2], [0], [0, 2], [0]...)użyciu 3 klas?
chippies
2
Działa w obie strony. Po przekazaniu listy krotek pasuje do niej sklearn.preprocessing.LabelBinarizer. Wiesz, że kilka algorytmów działa w przypadku wielopłaszczyznowej wielopłaszczyznowej. Szczególnie RandomForest.
Jessica Mick,
Dziękuję bardzo, to przynajmniej doprowadziło mnie do katastrofy. Na razie przeszedłem na K-fold cross validation, ale myślę, że wkrótce użyję twojego kodu. Teraz jednak wynik zwracany przez cross_val_score ma tylko dwie kolumny, tj. Tak, jakby były tylko dwie klasy. Zmiana na metrics.confusion_matrixprodukuje macierze zamieszania 2x2. Czy któryś z wskaźników obsługuje klasyfikatory obejmujące wiele etykiet?
chippies
Odpowiedziałem na własne pytanie cząstkowe. Dane obsługujące klasyfikatory wieloznakowe pojawiły się tylko w scikit-learn 0.14-rc, więc będę musiał zaktualizować, jeśli chcę tę umiejętność, lub zrobię to sam. Dzięki za pomoc i kod.
chippies
Usunąłem tablice w instrukcji return. Nie ma powodu, dla którego zawsze znajdziesz idealnie podzielony zestaw punktów danych. Daj mi znać, jeśli to się uda. Powinieneś także napisać kilka testów w swoim kodzie. W pewnym sensie wypuściłem ten algorytm po całym dniu patrzenia na wypukłe algorytmy optymalizacji.
Jessica Mick,
3

Możesz sprawdzić: na stratyfikacji danych z wieloma etykietami .

Tutaj autorzy najpierw mówią o prostym pomyśle pobierania próbek z unikalnych zestawów etykiet, a następnie wprowadzają nowe podejście do iteracyjnej stratyfikacji zestawów danych z wieloma etykietami.

Podejście iteracyjne stratyfikacji jest zachłanne.

Oto krótki przegląd iteratywnej stratyfikacji:

Najpierw dowiedzą się, ile przykładów należy przejść do każdego z k-foldów.

  • jajotdojajot

  • lrel

  • relkdokjotll

  • kdo

Główną ideą jest skupienie się na etykietach, które są rzadkie, ta idea pochodzi z hipotezy, że

„jeśli rzadkie etykiety nie są badane w pierwszej kolejności, mogą być dystrybuowane w niepożądany sposób i nie można tego później naprawić”

Aby zrozumieć, jak zerwane są więzi i inne szczegóły, zalecam przeczytanie artykułu. Co więcej, z sekcji eksperymentów mogę zrozumieć, że w zależności od stosunku zestaw etykiet / przykładów można chcieć użyć unikalnego zestawu etykiet lub tej proponowanej iteracyjnej metody stratyfikacji. Dla niższych wartości tego współczynnika rozkład etykiet na fałdach jest bliski lub lepszy w kilku przypadkach jako iteracyjne rozwarstwienie. W przypadku wyższych wartości tego wskaźnika wykazano, że iteracyjne rozwarstwienie zachowało lepsze rozkłady fałdów.

phoxis
źródło
1
link do pliku PDF wspomnianego artykułu: lpis.csd.auth.gr/publications/sechidis-ecmlpkdd-2011.pdf
Temak