Estendendo modelos de 2 classes a problemas de várias classes

11

Este documento no Adaboost fornece algumas sugestões e códigos (página 17) para estender os modelos de 2 classes para problemas da classe K. Gostaria de generalizar esse código, para que eu possa conectar facilmente diferentes modelos de 2 classes e comparar os resultados. Como a maioria dos modelos de classificação possui uma interface de fórmula e um predictmétodo, parte disso deve ser relativamente fácil. Infelizmente, não encontrei uma maneira padrão de extrair probabilidades de classe de modelos de 2 classes, portanto cada modelo exigirá algum código personalizado.

Aqui está uma função que escrevi para dividir um problema da classe K em problemas de 2 classes e retornar os modelos K:

oneVsAll <- function(X,Y,FUN,...) {
    models <- lapply(unique(Y), function(x) {
        name <- as.character(x)
        .Target <- factor(ifelse(Y==name,name,'other'), levels=c(name, 'other'))
        dat <- data.frame(.Target, X)
        model <- FUN(.Target~., data=dat, ...)
        return(model)
    })
    names(models) <- unique(Y)
    info <- list(X=X, Y=Y, classes=unique(Y))
    out <- list(models=models, info=info)
    class(out) <- 'oneVsAll'
    return(out)
}

Aqui está um método de previsão que escrevi para iterar sobre cada modelo e fazer previsões:

predict.oneVsAll <- function(object, newX=object$info$X, ...) {
    stopifnot(class(object)=='oneVsAll')
    lapply(object$models, function(x) {
        predict(x, newX, ...)
    })
}

E finalmente, aqui está uma função para normalizar uma data.framedas probabilidades previstas e classificar os casos. Observe que cabe a você construir a coluna K data.framede probabilidades de cada modelo, pois não há uma maneira unificada de extrair probabilidades de classe de um modelo de 2 classes:

classify <- function(dat) {
    out <- dat/rowSums(dat)
    out$Class <- apply(dat, 1, function(x) names(dat)[which.max(x)])
    out
}

Aqui está um exemplo usando adaboost:

library(ada)
library(caret) 
X <- iris[,-5]
Y <- iris[,5]
myModels <- oneVsAll(X, Y, ada)
preds <- predict(myModels, X, type='probs')
preds <- data.frame(lapply(preds, function(x) x[,2])) #Make a data.frame of probs
preds <- classify(preds)
>confusionMatrix(preds$Class, Y)
Confusion Matrix and Statistics

            Reference
Prediction   setosa versicolor virginica
  setosa         50          0         0
  versicolor      0         47         2
  virginica       0          3        48

Aqui está um exemplo usando lda(eu sei que o lda pode lidar com várias classes, mas este é apenas um exemplo):

library(MASS)
myModels <- oneVsAll(X, Y, lda)
preds <- predict(myModels, X)
preds <- data.frame(lapply(preds, function(x) x[[2]][,1])) #Make a data.frame of probs
preds <- classify(preds)
>confusionMatrix(preds$Class, Y)
Confusion Matrix and Statistics

            Reference
Prediction   setosa versicolor virginica
  setosa         50          0         0
  versicolor      0         39         5
  virginica       0         11        45

Essas funções devem funcionar para qualquer modelo de 2 classes com uma interface de fórmula e um predictmétodo. Observe que você precisa dividir manualmente os componentes X e Y, o que é um pouco feio, mas escrever uma interface de fórmula está além de mim no momento.

Essa abordagem faz sentido para todos? Existe alguma maneira de melhorá-lo ou existe um pacote existente para resolver esse problema?

Zach
fonte
2
Uau, até que você perguntasse e eu olhasse, eu teria certeza de que algum pacote (como carou um dos *labpacotes) teria fornecido uma função como a sua. Desculpe, não posso ajudar. Eu li um pouco sobre como o k-way SVM funciona e parece que foi mais complicado do que eu pensava.
Wayne
1
@Wayne: Eu também! Eu tinha certeza de que haveria alguma função geral que faria isso, desde que o modelo tivesse um predictmétodo.
Zach

Respostas:

1

Uma maneira de melhorar é usar a abordagem "todos os pares ponderados", que é supostamente melhor do que "um contra todos" enquanto ainda é escalável.

Quanto aos pacotes existentes, glmnetsuporta logit multinomial (regularizado) que pode ser usado como um classificador de várias classes.

Yevgeny
fonte
Estou ciente dos muitos pacotes no R que oferecem suporte à classificação de várias classes (como glmnet, florestas aleatórias, kernlab, rpart, nnet etc.). Estou mais curioso em estender os pacotes de classificação binária (por exemplo, gbm) a problemas de várias classes. Vou analisar "ponderado todos os pares".
Zach
Além disso, é interessante glmnetincluir uma multinomialfunção de perda. Gostaria de saber se essa função de perda poderia ser usada em outros algoritmos em R, como adaou gbm?
Zach
Sim, alguns métodos podem ser estendidos para oferecer suporte à função de perda multinomial. Por exemplo, a regressão logística do kernel é estendida dessa maneira aqui: books.nips.cc/papers/files/nips14/AA13.pdf Até onde se sabe, adaé "reservado" para uma função de perda específica (exponencial), mas pode-se estender outro aumento baseado em método para suportar a função de perda multinomial - por exemplo, consulte a página 360 de Os elementos do aprendizado estatístico para obter detalhes sobre GBM-K multi-classes de árvores binárias são construídas para cada iteração dinâmica em que K é o número de classes (apenas uma árvore por iteração é necessário em caso binário).
Yevgeny