Extender modelos de 2 clases a problemas de múltiples clases


11

Este documento sobre Adaboost ofrece algunas sugerencias y códigos (página 17) para extender los modelos de 2 clases a problemas de clase K. Me gustaría generalizar este código, de modo que pueda conectar fácilmente diferentes modelos de 2 clases y comparar los resultados. Debido a que la mayoría de los modelos de clasificación tienen una interfaz de fórmula y un predictmétodo, algo de esto debería ser relativamente fácil. Desafortunadamente, no he encontrado una forma estándar de extraer las probabilidades de clase de los modelos de 2 clases, por lo que cada modelo requerirá un código personalizado.

Aquí hay una función que escribí para dividir un problema de clase K en problemas de clase 2 y devolver 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)
}

Aquí hay un método de predicción que escribí para iterar sobre cada modelo y hacer predicciones:

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

Y finalmente, aquí hay una función para convertir normalizar una data.framede las probabilidades pronosticadas y clasificar los casos. Tenga en cuenta que depende de usted construir la columna K data.framede probabilidades de cada modelo, ya que no hay una forma unificada de extraer las probabilidades de clase de un modelo de 2 clases:

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

Aquí hay un ejemplo 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

Aquí hay un ejemplo usando lda(sé que lda puede manejar múltiples clases, pero este es solo un ejemplo):

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

Estas funciones deberían funcionar para cualquier modelo de 2 clases con una interfaz de fórmula y un predictmétodo. Tenga en cuenta que tiene que dividir manualmente los componentes X e Y, lo cual es un poco feo, pero escribir una interfaz de fórmula está más allá de mí en este momento.

¿Este enfoque tiene sentido para todos? ¿Hay alguna manera de mejorarlo o hay un paquete existente para resolver este problema?


2
Wow, hasta que lo hayas preguntado y haya mirado, habría estado seguro de que algún paquete (como caruno de los *labpaquetes) habría proporcionado una función como la tuya. Lo siento, no puedo ayudar. He leído un poco sobre cómo funciona SVM de k-way y parece que fue más complicado de lo que hubiera pensado.
— Wayne

1
@Wayne: ¡Yo también! Estaba seguro de que habría alguna función general que haría esto, siempre que el modelo tenga un predictmétodo.
— Zach

Respuestas:


1

Una forma de mejorar es utilizar el enfoque de "ponderar todos los pares", que supuestamente es mejor que "uno contra todos" mientras sigue siendo escalable.

En cuanto a los paquetes existentes, glmnetadmite logit multinomial (regularizado) que se puede utilizar como un clasificador de varias clases.


Soy consciente de los muchos paquetes en R que admiten la clasificación de varias clases (como glmnet, bosques aleatorios, kernlab, rpart, nnet, etc.). Tengo más curiosidad por extender los paquetes de clasificación binaria (por ejemplo, gbm) a problemas multiclase. Analizaré "ponderar todos los pares".
— Zach

Además, es interesante que glmnetincluye una multinomialfunción de pérdida. Me pregunto si esta función de pérdida podría usarse en otros algoritmos en R, como adao gbm?
— Zach

Sí, algunos métodos pueden ampliarse para admitir la función de pérdida multinomial. Por ejemplo, la regresión logística del núcleo se extiende de esta manera aquí: books.nips.cc/papers/files/nips14/AA13.pdf Hasta donde se sabe, adaestá "reservado" para una función de pérdida específica (exponencial), pero uno podría extender otro impulso basado en métodos para admitir la función de pérdida multinomial; por ejemplo, consulte la página 360 de Los elementos del aprendizaje estadístico para obtener detalles sobre GBM multiclase: los árboles binarios K se crean para cada iteración de refuerzo donde K es el número de clases (solo un árbol por iteración es necesario en caso binario).
— Yevgeny
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.