Come utilizzare le funzioni di validazione incrociata di scikit-learn su classificatori multi-etichetta


20

Sto testando diversi classificatori su un set di dati in cui ci sono 5 classi e ogni istanza può appartenere a una o più di queste classi, quindi sto usando i classificatori multi-etichetta di scikit-learn, in particolare sklearn.multiclass.OneVsRestClassifier. Ora voglio eseguire la validazione incrociata usando il sklearn.cross_validation.StratifiedKFold. Questo produce il seguente errore:

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

Si noti che l'addestramento del classificatore multi-etichetta non si arresta in modo anomalo, ma la convalida incrociata lo fa. Come devo eseguire la convalida incrociata per questo classificatore multi-etichetta?

Ho anche scritto una seconda versione che suddivide il problema in formazione e convalida incrociata di 5 classificatori separati. Questo funziona bene.

Ecco il mio codice La funzione test_classifier_multilabelè quella che dà problemi. test_classifierè il mio altro tentativo (suddividere il problema in 5 classificatori e 5 convalide incrociate).

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

Sto usando Ubuntu 13.04 e scikit-learn 0.12. I miei dati sono sotto forma di due array (X e Y) che hanno forme (98816, 4) e (98816, 5), ovvero 4 caratteristiche per istanza e 5 etichette di classe. Le etichette sono 1 o 0 per indicare l'appartenenza a quella classe. Sto usando il formato corretto in quanto non vedo molta documentazione a riguardo?

Risposte:


10

Il campionamento stratificato significa che la distribuzione dell'appartenenza alla classe viene preservata nel campionamento di KFold. Questo non ha molto senso nel caso multilabel in cui il tuo vettore target potrebbe avere più di un'etichetta per osservazione.

Esistono due possibili interpretazioni di stratificato in questo senso.

nΣio=1n2n

L'altra opzione è quella di provare a segmentare i dati di allenamento secondo cui la massa di probabilità della distribuzione dei vettori di etichette è approssimativamente la stessa sopra le pieghe. Per esempio

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)

Per ottenere il normale allenamento, testando gli indici prodotti da KFold, si desidera riscriverlo e restituire np.setdiff1d di ciascun indice con np.arange (y.shape [0]), quindi racchiuderlo in una classe con un metodo iter .


Grazie per questa spiegazione Vorrei solo controllare qualcosa, OneVsRestClassifieraccetta un array 2D (ad esempio ynel tuo codice di esempio) o una tupla di elenchi di etichette di classe? Lo chiedo perché ho guardato l'esempio di classificazione multi-etichetta su scikit-learn proprio ora e ho visto che la make_multilabel_classificationfunzione restituisce una tupla di elenchi di etichette di classe, ad esempio ([2], [0], [0, 2], [0]...)quando si usano 3 classi?
Chippies,

2
Funziona in entrambi i modi. Quando viene passato un elenco di tuple, si adatta a sklearn.preprocessing.LabelBinarizer. Sai alcuni degli algoritmi funzionano nel caso multilabel multiclasse. In particolare RandomForest.
Jessica Mick,

Grazie mille, questo almeno mi ha fatto superare gli incidenti. Per il momento sono passato alla validazione incrociata di K-fold ma penso che userò presto il tuo codice. Ora, tuttavia, il punteggio restituito da cross_val_score ha solo due colonne, vale a dire come se ci fossero solo due classi. Il passaggio a metrics.confusion_matrixproduce matrici di confusione 2x2. Alcune delle metriche supportano i classificatori multi-etichetta?
Chippies

Ho risposto alla mia sottointerrogazione. Le metriche che supportano i classificatori multi-etichetta sono apparse solo in scikit-learn 0.14-rc, quindi dovrò aggiornare se voglio quell'abilità o farlo da solo. Grazie per l'aiuto e il codice.
Chippies,

Ho rimosso la matrice sull'istruzione return. Non c'è motivo per cui troverai sempre un set di punti dati perfettamente partizionato. Fammi sapere se funziona. Dovresti anche scrivere alcuni test nel tuo codice. Ho praticamente espirato questo algoritmo dopo aver fissato gli algoritmi di ottimizzazione convessa per tutto il giorno.
Jessica Mick,

3

Potresti voler controllare: sulla stratificazione dei dati multi-etichetta .

Qui gli autori raccontano prima la semplice idea di campionare da etichette uniche e quindi introducono un nuovo approccio alla stratificazione iterativa per set di dati multi-etichetta.

L'approccio della stratificazione iterativa è avido.

Per una rapida panoramica, ecco cosa fa la stratificazione iterativa:

Per prima cosa scoprono quanti esempi dovrebbero essere inseriti in ciascuna delle k-fold.

  • iojcioj

  • lDl

  • DlKcKjll

  • Kc

L'idea principale è quella di concentrarsi prima sulle etichette che sono rare, questa idea deriva dall'ipotesi che

"se le etichette rare non vengono esaminate in via prioritaria, possono essere distribuite in modo indesiderato e questo non può essere riparato successivamente"

Per capire come si rompono i legami e altri dettagli, consiglierò di leggere l'articolo. Inoltre, dalla sezione degli esperimenti ciò che posso capire è che, a seconda del rapporto labelet / esempi, si potrebbe desiderare di utilizzare l'etichettatura univoca basata o questo metodo di stratificazione iterativa proposto. Per valori inferiori di questo rapporto, la distribuzione delle etichette tra le pieghe è vicina o migliore in alcuni casi come stratificazione iterativa. Per valori più alti di questo rapporto, la stratificazione iterativa ha mostrato di avere distribuzioni migliori nelle pieghe.


1
collegamento al PDF dell'articolo citato: lpis.csd.auth.gr/publications/sechidis-ecmlpkdd-2011.pdf
Temak
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.