Cómo usar las funciones de validación cruzada de scikit-learn en clasificadores de etiquetas múltiples

20

Estoy probando diferentes clasificadores en un conjunto de datos donde hay 5 clases y cada instancia puede pertenecer a una o más de estas clases, así que estoy usando los clasificadores de etiquetas múltiples de scikit-learn, específicamente sklearn.multiclass.OneVsRestClassifier. Ahora quiero realizar una validación cruzada usando sklearn.cross_validation.StratifiedKFold. Esto produce el siguiente error:

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

Tenga en cuenta que el entrenamiento del clasificador de etiquetas múltiples no se bloquea, pero la validación cruzada sí. ¿Cómo debo realizar la validación cruzada para este clasificador de etiquetas múltiples?

También he escrito una segunda versión que divide el problema en capacitación y validación cruzada de 5 clasificadores separados. Esto funciona bien.

Aquí está mi código. La función test_classifier_multilabeles la que da problemas. test_classifieres mi otro intento (dividir el problema en 5 clasificadores y 5 validaciones cruzadas).

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

Estoy usando Ubuntu 13.04 y scikit-learn 0.12. Mis datos tienen la forma de dos matrices (X e Y) que tienen formas (98816, 4) y (98816, 5), es decir, 4 entidades por instancia y 5 etiquetas de clase. Las etiquetas son 1 o 0 para membresía indicada dentro de esa clase. ¿Estoy usando el formato correcto ya que no veo mucha documentación al respecto?

chippies
fuente

Respuestas:

10

El muestreo estratificado significa que la distribución de membresía de clase se conserva en su muestreo KFold. Esto no tiene mucho sentido en el caso de múltiples etiquetas donde su vector objetivo podría tener más de una etiqueta por observación.

Hay dos posibles interpretaciones de estratificadas en este sentido.

Para etiquetas donde al menos una de ellas está llena, eso le da etiquetas únicas. Puede realizar un muestreo estratificado en cada uno de los contenedores de etiquetas únicos.n i = 1 2 nnorteyo=1norte2norte

La otra opción es tratar de segmentar los datos de entrenamiento para que la masa de probabilidad de la distribución de los vectores de etiqueta sea aproximadamente la misma en los pliegues. P.ej

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)

Para obtener el entrenamiento normal, al probar los índices que KFold produce, desea volver a escribir eso, devuelve el np.setdiff1d de cada índice con np.arange (y.shape [0]), luego envuélvalo en una clase con un método iter .

Jessica Mick
fuente
Gracias por esta explicación Solo me gustaría verificar algo, ¿ OneVsRestClassifieracepta una matriz 2D (por ejemplo, yen su código de ejemplo) o una tupla de listas de etiquetas de clase? Pregunto porque miré el ejemplo de clasificación de etiquetas múltiples en scikit-learn justo ahora y vi que la make_multilabel_classificationfunción devuelve una tupla de listas de etiquetas de clase, por ejemplo, ([2], [0], [0, 2], [0]...)cuando se usan 3 clases.
chippies
2
Funciona en ambos sentidos. Cuando se pasa una lista de tuplas, se ajusta a un sklearn.preprocessing.LabelBinarizer. Usted sabe que algunos de los algoritmos funcionan en el caso multiclase multilabel. Notablemente RandomForest.
Jessica Mick
Muchas gracias, al menos esto me ayudó a superar los accidentes. Por el momento me he cambiado a la validación cruzada K-fold pero creo que usaré su código pronto. Ahora, sin embargo, la puntuación devuelta por cross_val_score solo tiene dos columnas, es decir, como si solo hubiera dos clases. Cambiar a metrics.confusion_matrixproduce 2x2 matrices de confusión. ¿Alguna de las métricas admite clasificadores de etiquetas múltiples?
chippies
He respondido mi propia subpregunta. Las métricas que admiten clasificadores de etiquetas múltiples solo aparecieron en scikit-learn 0.14-rc, por lo que tendré que actualizar si quiero esa habilidad, o hacerlo yo mismo. Gracias por la ayuda y el código.
chippies
Eliminé la matriz en la declaración de devolución. No hay razón para que siempre encuentre un conjunto de puntos de datos perfectamente particionados. Avísame si esto funciona. También debe escribir algunas pruebas en su código. Es cierto que exhalé este algoritmo después de mirar algoritmos de optimización convexos todo el día.
Jessica Mick
3

Es posible que desee verificar: En la estratificación de datos de etiquetas múltiples .

Aquí los autores primero cuentan la idea simple de tomar muestras de conjuntos de etiquetas únicos y luego introducen un nuevo enfoque de estratificación iterativa para conjuntos de datos de etiquetas múltiples.

El enfoque de la estratificación iterativa es codicioso.

Para una descripción general rápida, esto es lo que hace la estratificación iterativa:

Primero descubren cuántos ejemplos deberían incluirse en cada uno de los k-pliegues.

  • yojdoyoj

  • lrel

  • relkdokjll

  • kdo

La idea principal es centrarse primero en las etiquetas que son raras, esta idea proviene de la hipótesis de que

"Si las etiquetas raras no se examinan con prioridad, entonces pueden distribuirse de forma no deseada, y esto no puede repararse posteriormente"

Para entender cómo se rompen los lazos y otros detalles, recomendaré leer el periódico. Además, de la sección de experimentos, lo que puedo entender es que, dependiendo de la relación conjunto de etiquetas / ejemplos, uno podría querer usar el conjunto de etiquetas único o este método de estratificación iterativo propuesto. Para valores más bajos de esta relación, la distribución de las etiquetas entre los pliegues es cercana o mejor en algunos casos como estratificación iterativa. Para valores más altos de esta relación, se muestra que la estratificación iterativa ha mantenido mejores distribuciones en los pliegues.

foxis
fuente
1
enlace al PDF del documento mencionado: lpis.csd.auth.gr/publications/sechidis-ecmlpkdd-2011.pdf
Temak