Como usar as funções de validação cruzada do scikit-learn em classificadores de vários rótulos

20

Estou testando diferentes classificadores em um conjunto de dados em que há 5 classes e cada instância pode pertencer a uma ou mais dessas classes; portanto, estou usando especificamente os classificadores de várias etiquetas do scikit-learn sklearn.multiclass.OneVsRestClassifier. Agora eu quero executar a validação cruzada usando o sklearn.cross_validation.StratifiedKFold. Isso produz o seguinte erro:

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 o treinamento do classificador com vários rótulos não falha, mas a validação cruzada. Como devo executar a validação cruzada para este classificador com vários rótulos?

Também escrevi uma segunda versão que divide o problema em treinamento e validação cruzada de 5 classificadores separados. Isso funciona muito bem.

Aqui está o meu código. A função test_classifier_multilabelé a que apresenta problemas. test_classifieré minha outra tentativa (dividir o problema em 5 classificadores e 5 validações 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()

Estou usando o Ubuntu 13.04 e o scikit-learn 0.12. Meus dados estão na forma de duas matrizes (X e Y) que possuem formas (98816, 4) e (98816, 5), ou seja, 4 recursos por instância e 5 rótulos de classe. Os rótulos são 1 ou 0 para indicar a associação nessa classe. Estou usando o formato correto, pois não vejo muita documentação sobre isso?

chippies
fonte

Respostas:

10

Amostragem estratificada significa que a distribuição de membros da classe é preservada na sua amostra do KFold. Isso não faz muito sentido no caso de várias etiquetas em que seu vetor de destino pode ter mais de um rótulo por observação.

Existem duas interpretações possíveis de estratificado nesse sentido.

Para rótulos em que pelo menos um deles é preenchido, fornece rótulos exclusivos. Você pode executar amostragem estratificada em cada uma das caixas de etiquetas exclusivas.n i = 1 2 nni=1n2n

A outra opção é tentar segmentar os dados de treinamento, de modo que a massa probabilística da distribuição dos vetores dos rótulos seja aproximadamente a mesma nas dobras. Por exemplo

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 obter o treinamento normal, testando os índices produzidos pelo KFold, você deseja reescrevê-lo para que ele retorne o np.setdiff1d de cada índice com np.arange (y.shape [0]) e, em seguida, agrupe-o em uma classe com um método iter .

Jessica Mick
fonte
Obrigado por esta explicação. Gostaria apenas de verificar uma coisa: o OneVsRestClassifieraceita uma matriz 2D (por exemplo, yno seu código de exemplo) ou uma tupla de listas de rótulos de classe? Eu perguntei porque vi o exemplo de classificação de vários rótulos no scikit-learn agora e vi que a make_multilabel_classificationfunção retorna uma tupla de listas de rótulos de classe, por exemplo, ([2], [0], [0, 2], [0]...)ao usar 3 classes?
chippies
2
Funciona nos dois sentidos. Quando uma lista de tuplas é passada, ela se ajusta a um sklearn.preprocessing.LabelBinarizer. Você conhece alguns dos algoritmos que funcionam no caso de várias etiquetas com várias classes. Notavelmente RandomForest.
Jessica Mick
Muito obrigado, isso pelo menos me fez passar pelas falhas. No momento, mudei para a validação cruzada K-fold, mas acho que usarei seu código em breve. Agora, no entanto, a pontuação retornada por cross_val_score possui apenas duas colunas, ou seja, como se houvesse apenas duas classes. Mudar para metrics.confusion_matrixproduz matrizes de confusão 2x2. Alguma das métricas suporta classificadores com vários rótulos?
Chippies
Eu respondi minha própria sub-pergunta. As métricas que suportam classificadores com vários rótulos apareceram apenas no scikit-learn 0.14-rc, portanto, precisarei fazer o upgrade se desejar essa capacidade ou fazê-lo sozinho. Obrigado pela ajuda e código.
Chippies
Eu removi a disposição na declaração de retorno. Não há motivo para você sempre encontrar um conjunto de pontos de dados perfeitamente particionado. Deixe-me saber se isso funciona. Você também deve escrever alguns testes no seu código. Eu meio que expirei esse algoritmo depois de olhar para algoritmos de otimização convexos o dia todo.
Jessica Mick
3

Você pode querer verificar: Na estratificação de dados com vários rótulos .

Aqui, os autores primeiro contam a idéia simples de amostragem de conjuntos de etiquetas exclusivos e, em seguida, introduzem uma nova abordagem de estratificação iterativa para conjuntos de dados com vários rótulos.

A abordagem da estratificação iterativa é gananciosa.

Para uma visão geral rápida, aqui está o que a estratificação iterativa faz:

Primeiro, eles descobrem quantos exemplos devem ser inseridos em cada uma das dobras em k.

  • Encontre o número desejado de exemplos por dobra por etiqueta , .j c j iEujcEuj

  • A partir do conjunto de dados que ainda será distribuído em dobras k, é identificado o rótulo para o qual o número de exemplos é o mínimo, .D leuDeu

  • Então, para cada ponto de dados em encontre a dobra para a qual é maximizada (quebre os vínculos aqui). Em outras palavras, significa: qual dobra tem a demanda máxima pelo rótulo ou o mais desequilibrado em relação ao rótulo . k c j k l lDeukckjeueu

  • Adicione o ponto de dados atual à dobra encontrada na etapa acima, remova o ponto de dados do conjunto de dados original e ajuste os valores de contagem de e continue até que todos os pontos de dados não sejam distribuídos nas dobras.ckc

A idéia principal é primeiro focar nos rótulos que são raros, essa idéia vem da hipótese de que

"se etiquetas raras não forem examinadas com prioridade, elas poderão ser distribuídas de maneira indesejada e isso não poderá ser reparado posteriormente"

Para entender como os laços são rompidos e outros detalhes, recomendo a leitura do artigo. Além disso, na seção de experimentos, o que eu entendo é que, dependendo da proporção do conjunto de rótulos / exemplos, pode-se querer usar o conjunto de rótulos exclusivo ou esse método de estratificação iterativa proposto. Para valores mais baixos dessa relação, a distribuição dos rótulos nas dobras é próxima ou melhor em alguns casos, como estratificação iterativa. Para valores mais altos dessa relação, a estratificação iterativa mostra ter mantido melhores distribuições nas dobras.

phoxis
fonte
1
link para o PDF do documento mencionado: lpis.csd.auth.gr/publications/sechidis-ecmlpkdd-2011.pdf
Temak