¿Cómo establecer pesos de clase para clases desequilibradas en Keras?

130

Sé que hay una posibilidad en Keras con el class_weightsdiccionario de parámetros al ajuste, pero no he podido encontrar ningún ejemplo. ¿Alguien tan amable de proporcionar uno?

Por cierto, en este caso, la práctica adecuada es simplemente ponderar a la clase minoritaria proporcionalmente a su representación insuficiente.

Hendrik
fuente
¿Existe un nuevo método actualizado usando Keras? ¿Por qué el diccionario consta de tres clases y para la clase: 0: 1.0 1: 50.0 2: 2.0 ???? no debería: 2: 1.0 también?
Chuck

Respuestas:

112

Si está hablando del caso normal, donde su red produce solo una salida, entonces su suposición es correcta. Para forzar a su algoritmo a tratar cada instancia de la clase 1 como 50 instancias de la clase 0 , debe:

  1. Defina un diccionario con sus etiquetas y sus pesos asociados.

    class_weight = {0: 1.,
                    1: 50.,
                    2: 2.}
  2. Alimente el diccionario como parámetro:

    model.fit(X_train, Y_train, nb_epoch=5, batch_size=32, class_weight=class_weight)

EDITAR: "tratar cada instancia de la clase 1 como 50 instancias de la clase 0 " significa que en su función de pérdida asigna un valor más alto a estas instancias. Por lo tanto, la pérdida se convierte en un promedio ponderado, donde el peso de cada muestra se especifica por class_weight y su clase correspondiente.

De documentos de Keras: class_weight : índices de clase de asignación de diccionario (enteros) opcionales a un valor de peso (flotante), utilizado para ponderar la función de pérdida (solo durante el entrenamiento).

capa
fuente
1
También eche un vistazo a github.com/fchollet/keras/issues/3653 si está trabajando con datos 3D.
herve
Para mí da un error dic no tiene atributo de forma.
Flávio Filho
Creo Keras podría estar cambiando la forma en que esto funciona, esto es para la versión de agosto de 2016. Voy a verificar para usted en una semana
layser
44
@layser ¿Funciona esto solo para la pérdida de 'category_crossentropy'? ¿Cómo le das class_weight a los keras para la pérdida 'sigmoidea' y 'binary_crossentropy'?
Naman
1
@layser ¿Puede explicar `tratar cada instancia de la clase 1 como 50 instancias de la clase 0 '? ¿Es que la fila en el conjunto de entrenamiento correspondiente a la clase 1 se duplica 50 veces para equilibrarla o algún otro proceso sigue?
Divyanshu Shekhar
122

Simplemente podría implementar el class_weightde sklearn:

  1. Importaremos el módulo primero

    from sklearn.utils import class_weight
  2. Para calcular el peso de la clase, haga lo siguiente

    class_weights = class_weight.compute_class_weight('balanced',
                                                     np.unique(y_train),
                                                     y_train)
  3. En tercer y último lugar, agréguelo al ajuste del modelo

    model.fit(X_train, y_train, class_weight=class_weights)

Atención : edité esta publicación y cambié el nombre de la variable de class_weight a class_weight s para no sobrescribir el módulo importado. Ajuste en consecuencia cuando copie el código de los comentarios.

PSc
fuente
21
Para mí, class_weight.compute_class_weight produce una matriz, necesito cambiarla a un dict para poder trabajar con Keras. Más específicamente, después del paso 2, useclass_weight_dict = dict(enumerate(class_weight))
C.Lee
55
Esto no funciona para mi. Para un problema de tres clases en keras y_traines (300096, 3)numpy array. Entonces, la class_weight=línea me da TypeError: tipo no compartible: 'numpy.ndarray'
Lembik
3
@Lembik Tuve un problema similar, donde cada fila de y es un vector codificado en caliente del índice de clase. Me fijo mediante la conversión de la representación de una sola caliente para un int como esto: y_ints = [y.argmax() for y in y_train].
tkocmathla
3
¿Qué pasa si estoy haciendo un etiquetado multiclase para que mis vectores y_true tengan múltiples 1s en ellos: [1 0 0 0 1 0 0] por ejemplo, donde algunas x tienen etiquetas 0 y 4. Incluso entonces, el número total de cada uno de mis Las etiquetas no están equilibradas. ¿Cómo usaría pesos de clase con eso?
Aalok
22

Yo uso este tipo de regla para class_weight:

import numpy as np
import math

# labels_dict : {ind_label: count_label}
# mu : parameter to tune 

def create_class_weight(labels_dict,mu=0.15):
    total = np.sum(labels_dict.values())
    keys = labels_dict.keys()
    class_weight = dict()

    for key in keys:
        score = math.log(mu*total/float(labels_dict[key]))
        class_weight[key] = score if score > 1.0 else 1.0

    return class_weight

# random labels_dict
labels_dict = {0: 2813, 1: 78, 2: 2814, 3: 78, 4: 7914, 5: 248, 6: 7914, 7: 248}

create_class_weight(labels_dict)

math.logsuaviza los pesos para clases muy desequilibradas! Esto devuelve:

{0: 1.0,
 1: 3.749820767859636,
 2: 1.0,
 3: 3.749820767859636,
 4: 1.0,
 5: 2.5931008483842453,
 6: 1.0,
 7: 2.5931008483842453}
J.Guillaumin
fuente
3
¿Por qué usar log en lugar de simplemente dividir el recuento de muestras para una clase por el número total de muestras? Supongo que hay algo que no entiendo entra en el param class_weight en model.fit_generator (...)
startoftext
@startoftext Así lo hice, pero creo que lo tienes invertido. Yo solía n_total_samples / n_class_samplespara cada clase.
colllin
2
En su ejemplo, la clase 0 (tiene 2813 ejemplos) y la clase 6 (tiene 7914 ejemplos) tienen un peso exactamente 1.0. ¿Porqué es eso? ¡La clase 6 es varias veces más grande! Desea que la clase 0 se incremente y la clase 6 se reduzca para llevarlos al mismo nivel.
Vladislavs Dovgalecs
9

NOTA: Ver comentarios, esta respuesta está desactualizada.

Para ponderar todas las clases por igual, ahora puede simplemente establecer class_weight en "auto" de esta manera:

model.fit(X_train, Y_train, nb_epoch=5, batch_size=32, class_weight = 'auto')
David Groppe
fuente
1
No pude encontrar ninguna referencia class_weight='auto'en la documentación de Keras ni en el código fuente. ¿Puedes mostrarnos dónde encontraste esto?
Fábio Perez
2
Esta respuesta es probablemente incorrecta. Revise este tema: github.com/fchollet/keras/issues/5116
Fábio Perez
Impar. Estaba usando class_balanced = 'auto' en el momento en que publiqué el comentario, pero no puedo encontrar referencias a él ahora. Tal vez ha cambiado ya que Keras ha evolucionado rápidamente.
David Groppe
Como se especifica en el problema de Keras indicado anteriormente , puede pasar cualquier cadena aleatoria como class_weighty no tendrá ningún efecto. Por lo tanto, esta respuesta no es correcta.
ncasas
3

class_weight está bien, pero como dijo @Aalok, esto no funcionará si está codificando en caliente las clases de múltiples etiquetas. En este caso, use sample_weight :

sample_weight: matriz opcional de la misma longitud que x, que contiene pesos para aplicar a la pérdida del modelo para cada muestra. En el caso de los datos temporales, puede pasar una matriz 2D con forma (muestras, longitud de secuencia), para aplicar un peso diferente a cada paso de tiempo de cada muestra. En este caso, debe asegurarse de especificar sample_weight_mode = "temporal" en compile ().

sample_weights se usa para proporcionar un peso para cada muestra de entrenamiento . Eso significa que debe pasar una matriz 1D con el mismo número de elementos que sus muestras de entrenamiento (indicando el peso de cada una de esas muestras).

class_weights se usa para proporcionar un peso o sesgo para cada clase de salida . Esto significa que debe pasar un peso para cada clase que está tratando de clasificar.

sample_weight debe tener una matriz numpy, ya que se evaluará su forma.

Consulte también esta respuesta: https://stackoverflow.com/questions/48315094/using-sample-weight-in-keras-for-sequence-labelling

Charly Empereur-mot
fuente
2

Agregando a la solución en https://github.com/keras-team/keras/issues/2115 . Si necesita más que una ponderación de clase donde desee costos diferentes para falsos positivos y falsos negativos. Con la nueva versión de Keras ahora puede anular la función de pérdida respectiva como se indica a continuación. Tenga en cuenta que weightses una matriz cuadrada.

from tensorflow.python import keras
from itertools import product
import numpy as np
from tensorflow.python.keras.utils import losses_utils

class WeightedCategoricalCrossentropy(keras.losses.CategoricalCrossentropy):

    def __init__(
        self,
        weights,
        from_logits=False,
        label_smoothing=0,
        reduction=losses_utils.ReductionV2.SUM_OVER_BATCH_SIZE,
        name='categorical_crossentropy',
    ):
        super().__init__(
            from_logits, label_smoothing, reduction, name=f"weighted_{name}"
        )
        self.weights = weights

    def call(self, y_true, y_pred):
        weights = self.weights
        nb_cl = len(weights)
        final_mask = keras.backend.zeros_like(y_pred[:, 0])
        y_pred_max = keras.backend.max(y_pred, axis=1)
        y_pred_max = keras.backend.reshape(
            y_pred_max, (keras.backend.shape(y_pred)[0], 1))
        y_pred_max_mat = keras.backend.cast(
            keras.backend.equal(y_pred, y_pred_max), keras.backend.floatx())
        for c_p, c_t in product(range(nb_cl), range(nb_cl)):
            final_mask += (
                weights[c_t, c_p] * y_pred_max_mat[:, c_p] * y_true[:, c_t])
        return super().call(y_true, y_pred) * final_mask
Praveen Kulkarni
fuente
0

Encontré el siguiente ejemplo de codificación de ponderaciones de clase en la función de pérdida utilizando el conjunto de datos minist. Ver enlace aquí: https://github.com/keras-team/keras/issues/2115

def w_categorical_crossentropy(y_true, y_pred, weights):
    nb_cl = len(weights)
    final_mask = K.zeros_like(y_pred[:, 0])
    y_pred_max = K.max(y_pred, axis=1)
    y_pred_max = K.reshape(y_pred_max, (K.shape(y_pred)[0], 1))
    y_pred_max_mat = K.equal(y_pred, y_pred_max)
    for c_p, c_t in product(range(nb_cl), range(nb_cl)):
        final_mask += (weights[c_t, c_p] * y_pred_max_mat[:, c_p] * y_true[:, c_t])
    return K.categorical_crossentropy(y_pred, y_true) * final_mask
CathyQian
fuente
0
from collections import Counter
itemCt = Counter(trainGen.classes)
maxCt = float(max(itemCt.values()))
cw = {clsID : maxCt/numImg for clsID, numImg in itemCt.items()}

Esto funciona con un generador o estándar. Su clase más grande tendrá un peso de 1, mientras que las demás tendrán valores superiores a 1 en relación con la clase más grande.

los pesos de clase aceptan una entrada de tipo diccionario

Allie
fuente