Comment définir les poids de classe pour les classes déséquilibrées dans Keras?


130

Je sais qu'il existe une possibilité dans Keras avec le class_weightsdictionnaire de paramètres lors de l'ajustement, mais je n'ai trouvé aucun exemple. Quelqu'un aurait-il la gentillesse d'en fournir un?

À propos, dans ce cas, la pratique appropriée consiste simplement à pondérer la classe minoritaire proportionnellement à sa sous-représentation.


Existe-t-il une nouvelle méthode mise à jour utilisant Keras? pourquoi le dictionnaire est composé de trois classes et pour la classe: 0: 1.0 1: 50.0 2: 2.0 ???? ne devrait pas: 2: 1.0 aussi?
Chuck

Réponses:


112

Si vous parlez du cas normal, où votre réseau ne produit qu'une sortie, alors votre hypothèse est correcte. Afin de forcer votre algorithme à traiter chaque instance de la classe 1 comme 50 instances de la classe 0, vous devez:

  1. Définir un dictionnaire avec vos étiquettes et leurs poids associés

    class_weight = {0: 1.,
                    1: 50.,
                    2: 2.}
    
  2. Introduisez le dictionnaire comme paramètre:

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

EDIT: "traiter chaque instance de la classe 1 comme 50 instances de la classe 0 " signifie que, dans votre fonction de perte, vous affectez une valeur plus élevée à ces instances. Par conséquent, la perte devient une moyenne pondérée, où le poids de chaque échantillon est spécifié par classe_weight et sa classe correspondante.

De Keras docs: class_weight : index facultatif de mappage de dictionnaire (entiers) sur une valeur de poids (float), utilisé pour pondérer la fonction de perte (uniquement pendant l'entraînement).


1
Consultez également github.com/fchollet/keras/issues/3653 si vous travaillez avec des données 3D.
Herve

Pour moi, cela donne une erreur dic ne possède pas d'attribut de forme.
Flávio Filho

Je pense que Keras pourrait changer la façon dont cela fonctionne, ceci est pour la version d'août 2016. Je vérifierai pour vous dans une semaine
layser

4
@layser Cela fonctionne-t-il uniquement pour la perte 'category_crossentropy'? Comment donner class_weight à keras pour les pertes 'sigmoid' et 'binary_crossentropy'?
Naman

1
@layser Pouvez-vous expliquer `traiter chaque instance de la classe 1 comme 50 instances de la classe 0`? Est-ce que la ligne en formation, la ligne correspondant à la classe 1, est dupliquée 50 fois afin de la rendre équilibrée ou qu'un autre processus s'ensuit?
Divyanshu Shekhar

122

Vous pouvez simplement implémenter le class_weightde sklearn:

  1. Importons d'abord le module

    from sklearn.utils import class_weight
  2. Pour calculer le poids de la classe, procédez comme suit:

    class_weights = class_weight.compute_class_weight('balanced',
                                                     np.unique(y_train),
                                                     y_train)
    
  3. Troisièmement et enfin l'ajouter à l'ajustement du modèle

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

Attention : J'ai édité ce post et changé le nom de la variable class_weight en class_weight s afin de ne pas écraser le module importé. Ajustez en conséquence lors de la copie du code à partir des commentaires.


21
Pour moi, class_weight.compute_class_weight produit un tableau, je dois le changer en dict pour pouvoir travailler avec Keras. Plus précisément, après l'étape 2, utilisezclass_weight_dict = dict(enumerate(class_weight))
C.Lee

5
Cela ne marche pas pour moi. Pour un problème à trois classes dans keras, y_trainc'est (300096, 3)numpy array. Donc, la class_weight=ligne me donne TypeError: type inshashable: 'numpy.ndarray'
Lembik

3
@ Lembik J'ai eu un problème similaire, où chaque ligne de y est un vecteur codé à une chaleur de l'index de la classe. Je l' ai fixé par conversion de la représentation d' un chaud à un int comme suit: y_ints = [y.argmax() for y in y_train].
tkocmathla

3
Et si j’effectue un étiquetage multiclass pour que mes vecteurs y_true aient plusieurs 1: [1 0 0 0 1 0 0], par exemple, où certains x ont les étiquettes 0 et 4. Même dans ce cas, le nombre total de chacun de mes les étiquettes n'est pas équilibré. Comment pourrais-je utiliser les poids de classe avec ça?
Aalok

22

J'utilise ce genre de règle pour 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.loglisse les poids pour les classes très déséquilibrées! Cela retourne:

{0: 1.0,
 1: 3.749820767859636,
 2: 1.0,
 3: 3.749820767859636,
 4: 1.0,
 5: 2.5931008483842453,
 6: 1.0,
 7: 2.5931008483842453}

3
Pourquoi utiliser log au lieu de simplement diviser le nombre d'échantillons d'une classe par le nombre total d'échantillons? Je suppose qu'il y a quelque chose que je ne comprends pas qui entre dans le param class_weight de model.fit_generator (...)
startoftext

@startoftext C'est comme ça que je l'ai fait, mais je pense que vous l'avez inversé. J'ai utilisé n_total_samples / n_class_samplespour chaque classe.
Colllin

2
Dans votre exemple, la classe 0 (avec 2813 exemples) et la classe 6 (avec 7914 exemples) ont un poids exactement égal à 1,0. Pourquoi donc? La classe 6 est quelques fois plus grande! Vous voudriez que la classe 0 soit agrandie et la classe 6 réduite pour les amener au même niveau.
Vladislavs Dovgalecs

9

NOTE: Voir les commentaires, cette réponse est obsolète.

Pour pondérer équitablement toutes les classes, vous pouvez maintenant simplement définir class_weight sur "auto" comme suit:

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

1
Je n'ai trouvé aucune référence de class_weight='auto'dans la documentation de Keras ni dans le code source. Pouvez-vous nous montrer où vous avez trouvé cela?
Fábio Perez

2
Cette réponse est probablement fausse. Consultez ce numéro: github.com/fchollet/keras/issues/5116
Fábio Perez

Impair. J'utilisais class_balanced = 'auto' au moment où j'ai posté le commentaire, mais je ne peux pas trouver de référence pour le moment. Peut-être cela a-t-il changé car Keras évolue rapidement.
David Groppe

Comme spécifié dans le numéro de Keras indiqué ci-dessus , vous pouvez transmettre n'importe quelle chaîne aléatoire class_weightsans effet. Cette réponse n'est donc pas correcte.
NCASAS

3

class_weight c'est bien, mais comme @Aalok l'a dit, cela ne fonctionnera pas si vous encodez des classes multilabel à chaud. Dans ce cas, utilisez sample_weight :

sample_weight: tableau facultatif de même longueur que x, contenant les poids à appliquer à la perte du modèle pour chaque échantillon. Dans le cas de données temporelles, vous pouvez passer un tableau 2D avec une forme (samples, sequence_length) pour appliquer un poids différent à chaque pas de chaque échantillon. Dans ce cas, vous devez vous assurer de spécifier sample_weight_mode = "temporal" dans compile ().

sample_weights est utilisé pour fournir un poids pour chaque échantillon d'apprentissage . Cela signifie que vous devez passer un tableau 1D avec le même nombre d'éléments que vos échantillons d'apprentissage (en indiquant le poids de chacun de ces échantillons).

class_weights est utilisé pour fournir un poids ou un biais pour chaque classe de sortie . Cela signifie que vous devez attribuer une pondération à chaque classe que vous essayez de classer.

sample_weight doit recevoir un tableau numpy, car sa forme sera évaluée.

Voir aussi cette réponse: https://stackoverflow.com/questions/48315094/using-sample-weight-in-keras-for-sequence-labelling


2

Ajout à la solution à l' adresse https://github.com/keras-team/keras/issues/2115 . Si vous avez besoin de plus que la pondération de classe, vous souhaitez des coûts différents pour les faux positifs et les faux négatifs. Avec la nouvelle version de keras, vous pouvez maintenant remplacer la fonction de perte respective indiquée ci-dessous. Notez que weightsc'est une matrice carrée.

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

0

J'ai trouvé l'exemple suivant de codage des poids de classe dans la fonction de perte à l'aide du jeu de données minist. Voir le lien ici: 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

0
from collections import Counter
itemCt = Counter(trainGen.classes)
maxCt = float(max(itemCt.values()))
cw = {clsID : maxCt/numImg for clsID, numImg in itemCt.items()}

Cela fonctionne avec un générateur ou une norme. Votre classe la plus grande aura un poids de 1 tandis que les autres auront des valeurs supérieures à 1 par rapport à la classe la plus grande.

les poids de classe acceptent une entrée de type dictionnaire

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.