Pregunta en una oración: ¿Alguien sabe cómo determinar los buenos pesos de clase para un bosque aleatorio?
Explicación: estoy jugando con conjuntos de datos desequilibrados. Quiero usar el R
paquete randomForest
para entrenar un modelo en un conjunto de datos muy sesgado con solo pocos ejemplos positivos y muchos ejemplos negativos. Lo sé, hay otros métodos y al final los utilizaré, pero por razones técnicas, construir un bosque aleatorio es un paso intermedio. Así que jugué con el parámetro classwt
. Estoy configurando un conjunto de datos muy artificial de 5000 ejemplos negativos en el disco con radio 2 y luego muestro 100 ejemplos positivos en el disco con radio 1. Lo que sospecho es que
1) sin ponderación de clase, el modelo se vuelve 'degenerado', es decir, predice en FALSE
todas partes.
2) con una ponderación de clase justa veré un 'punto verde' en el medio, es decir, predecirá el disco con radio 1 como TRUE
si hubiera ejemplos negativos.
Así es como se ven los datos:
Esto es lo que sucede sin ponderación: (llamada es: randomForest(x = train[, .(x,y)],y = as.factor(train$z),ntree = 50)
)
Para verificar, también probé lo que sucede cuando balanceo violentamente el conjunto de datos reduciendo el muestreo de la clase negativa para que la relación sea 1: 1 nuevamente. Esto me da el resultado esperado:
Sin embargo, cuando calculo un modelo con una ponderación de clase de 'FALSO' = 1, 'VERDADERO' = 50 (esta es una ponderación justa ya que hay 50 veces más negativos que positivos), entonces obtengo esto:
Solo cuando establezco los pesos en algún valor extraño como 'FALSO' = 0.05 y 'VERDADERO' = 500000, obtengo resultados sensatos:
Y esto es bastante inestable, es decir, cambiar el peso 'FALSO' a 0.01 hace que el modelo se degenere nuevamente (es decir, predice en TRUE
todas partes).
Pregunta: ¿Alguien sabe cómo determinar los buenos pesos de clase para un bosque aleatorio?
Código R:
library(plot3D)
library(data.table)
library(randomForest)
set.seed(1234)
amountPos = 100
amountNeg = 5000
# positives
r = runif(amountPos, 0, 1)
phi = runif(amountPos, 0, 2*pi)
x = r*cos(phi)
y = r*sin(phi)
z = rep(T, length(x))
pos = data.table(x = x, y = y, z = z)
# negatives
r = runif(amountNeg, 0, 2)
phi = runif(amountNeg, 0, 2*pi)
x = r*cos(phi)
y = r*sin(phi)
z = rep(F, length(x))
neg = data.table(x = x, y = y, z = z)
train = rbind(pos, neg)
# draw train set, verify that everything looks ok
plot(train[z == F]$x, train[z == F]$y, col="red")
points(train[z == T]$x, train[z == T]$y, col="green")
# looks ok to me :-)
Color.interpolateColor = function(fromColor, toColor, amountColors = 50) {
from_rgb = col2rgb(fromColor)
to_rgb = col2rgb(toColor)
from_r = from_rgb[1,1]
from_g = from_rgb[2,1]
from_b = from_rgb[3,1]
to_r = to_rgb[1,1]
to_g = to_rgb[2,1]
to_b = to_rgb[3,1]
r = seq(from_r, to_r, length.out = amountColors)
g = seq(from_g, to_g, length.out = amountColors)
b = seq(from_b, to_b, length.out = amountColors)
return(rgb(r, g, b, maxColorValue = 255))
}
DataTable.crossJoin = function(X,Y) {
stopifnot(is.data.table(X),is.data.table(Y))
k = NULL
X = X[, c(k=1, .SD)]
setkey(X, k)
Y = Y[, c(k=1, .SD)]
setkey(Y, k)
res = Y[X, allow.cartesian=TRUE][, k := NULL]
X = X[, k := NULL]
Y = Y[, k := NULL]
return(res)
}
drawPredictionAreaSimple = function(model) {
widthOfSquares = 0.1
from = -2
to = 2
xTable = data.table(x = seq(from=from+widthOfSquares/2,to=to-widthOfSquares/2,by = widthOfSquares))
yTable = data.table(y = seq(from=from+widthOfSquares/2,to=to-widthOfSquares/2,by = widthOfSquares))
predictionTable = DataTable.crossJoin(xTable, yTable)
pred = predict(model, predictionTable)
res = rep(NA, length(pred))
res[pred == "FALSE"] = 0
res[pred == "TRUE"] = 1
pred = res
predictionTable = predictionTable[, PREDICTION := pred]
#predictionTable = predictionTable[y == -1 & x == -1, PREDICTION := 0.99]
col = Color.interpolateColor("red", "green")
input = matrix(c(predictionTable$x, predictionTable$y), nrow = 2, byrow = T)
m = daply(predictionTable, .(x, y), function(x) x$PREDICTION)
image2D(z = m, x = sort(unique(predictionTable$x)), y = sort(unique(predictionTable$y)), col = col, zlim = c(0,1))
}
rfModel = randomForest(x = train[, .(x,y)],y = as.factor(train$z),ntree = 50)
rfModelBalanced = randomForest(x = train[, .(x,y)],y = as.factor(train$z),ntree = 50, classwt = c("FALSE" = 1, "TRUE" = 50))
rfModelBalancedWeird = randomForest(x = train[, .(x,y)],y = as.factor(train$z),ntree = 50, classwt = c("FALSE" = 0.05, "TRUE" = 500000))
drawPredictionAreaSimple(rfModel)
title("unbalanced")
drawPredictionAreaSimple(rfModelBalanced)
title("balanced with weights")
pos = train[z == T]
neg = train[z == F]
neg = neg[sample.int(neg[, .N], size = 100, replace = FALSE)]
trainSampled = rbind(pos, neg)
rfModelBalancedSampling = randomForest(x = trainSampled[, .(x,y)],y = as.factor(trainSampled$z),ntree = 50)
drawPredictionAreaSimple(rfModelBalancedSampling)
title("balanced with sampling")
drawPredictionAreaSimple(rfModelBalancedWeird)
title("balanced with weird weights")
fuente
Respuestas:
No use un límite estricto para clasificar una membresía dura, y no use KPI que dependan de una predicción de membresía tan dura. En cambio, trabaje con una predicción probabilística, utilícela
predict(..., type="prob")
y evalúelas utilizando las reglas de puntuación adecuadas .Este hilo anterior debería ser útil: ¿por qué la precisión no es la mejor medida para evaluar los modelos de clasificación? Como era de esperar, creo que mi respuesta sería particularmente útil (perdón por la desvergüenza), al igual que una respuesta anterior mía .
fuente