27 votos

Cómo utilizar las funciones de validación cruzada de scikit-learn en clasificadores multietiqueta

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 clasificadores multi-etiqueta de scikit-learn, específicamente sklearn.multiclass.OneVsRestClassifier . Ahora quiero realizar una validación cruzada utilizando el 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

Observe que el entrenamiento del clasificador multietiqueta no se bloquea, pero sí la validación cruzada. Cómo debo realizar la validación cruzada para este clasificador multietiqueta?

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

Este es mi código. La función test_classifier_multilabel es el que da problemas. test_classifier es mi otro intento (dividiendo 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 están en forma de dos matrices (X e Y) que tienen formas (98816, 4) y (98816, 5), es decir, 4 características por instancia y 5 etiquetas de clase. Las etiquetas son 1 o 0 para indicar la pertenencia a esa clase. ¿Estoy utilizando el formato correcto, ya que no veo mucha documentación al respecto?

12voto

markymuy Puntos 21

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

Hay dos interpretaciones posibles de estratificado en este sentido.

Para nn etiquetas en las que al menos una de ellas está llena que le da ni=12n etiquetas únicas. Podría realizar un muestreo estratificado en cada uno de los grupos de etiquetas únicas.

La otra opción es intentar segmentar los datos de formación para que la masa de probabilidad de la distribución de los vectores de etiquetas sea aproximadamente la misma en todos los pliegues. Por ejemplo

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 los índices normales de entrenamiento y prueba que produce KFold, debes reescribirlo para que devuelva el valor np.setdiff1d de cada índice con np.arange(y.shape[0]), y luego envolverlo en una clase con un atributo iter método.

4voto

tbow Puntos 11

Quizá quieras comprobarlo: Sobre la estratificación de datos multietiqueta .

En este caso, los autores explican primero la idea simple de muestreo a partir de conjuntos de etiquetas únicos y, a continuación, introducen un nuevo enfoque estratificación iterativa para conjuntos de datos multietiqueta.

El enfoque de la estratificación iterativa es codicioso.

A modo de resumen, he aquí lo que hace la estratificación iterativa:

Primero averiguan cuántos ejemplos deben ir en cada uno de los pliegues k.

  • Encontrar el número deseado de ejemplos por pliegue i por etiqueta j , cji .

  • A partir del conjunto de datos que aún no se han distribuido en k pliegues, la etiqueta l para el que el número de ejemplos es mínimo, Dl .

  • A continuación, para cada punto de datos en Dl encontrar el pliegue k para lo cual cjk se maximiza (romper los empates aquí). Lo que en otras palabras significa: qué pliegue tiene la máxima demanda de etiqueta l o es el más desequilibrado con respecto a la etiqueta l .

  • Añade el punto de datos actual al pliegue k obtenido en el paso anterior, elimine el punto de datos del conjunto de datos original y ajuste los valores de recuento de c y continuar hasta que todos los puntos de datos no estén distribuidos en los pliegues.

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, es posible que se distribuidas de forma no deseada, y esto no puede repararse posteriormente"

Para entender cómo se deshacen los empates y otros detalles, recomendaré la lectura del documento. 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 utilizar el método basado en el conjunto único de etiquetas o este método de estratificación iterativa propuesto. Para los valores más bajos de esta relación, la distribución de las etiquetas a través de los pliegues están cerca o mejor en algunos casos como la estratificación iterativa. Para valores más altos de este ratio, se demuestra que la estratificación iterativa ha mantenido mejores distribuciones en los pliegues.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X