Come impostare i pesi delle classi per le classi sbilanciate in Keras?


130

So che esiste una possibilità in Keras con il class_weightsdizionario dei parametri adatto, ma non sono riuscito a trovare alcun esempio. Qualcuno sarebbe così gentile da offrirne uno?

A proposito, in questo caso la prassi appropriata è semplicemente quella di ponderare la classe di minoranza proporzionalmente alla sua sottorappresentazione?


Esiste un nuovo metodo aggiornato con Keras? perché il dizionario è composto da tre classi e per classe: 0: 1.0 1: 50.0 2: 2.0 ???? non dovrebbe: 2: 1.0 pure?
Chuck,

Risposte:


112

Se stai parlando del caso normale, in cui la tua rete produce un solo output, allora la tua ipotesi è corretta. Per forzare il tuo algoritmo a trattare ogni istanza di classe 1 come 50 istanze di classe 0 devi:

  1. Definire un dizionario con le etichette e i pesi associati

    class_weight = {0: 1.,
                    1: 50.,
                    2: 2.}
    
  2. Alimenta il dizionario come parametro:

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

EDIT: "tratta ogni istanza della classe 1 come 50 istanze della classe 0 " significa che nella tua funzione di perdita assegni un valore più alto a queste istanze. Quindi, la perdita diventa una media ponderata, in cui il peso di ciascun campione è specificato da class_weight e dalla sua classe corrispondente.

Dai documenti di Keras: class_weight : dizionari opzionali di mappatura del dizionario (numeri interi) su un valore di peso (float), utilizzato per ponderare la funzione di perdita (solo durante l'allenamento).


1
Dai anche un'occhiata a github.com/fchollet/keras/issues/3653 se lavori con dati 3D.
Herve

Per me dà un errore dic non ha attributo forma.
Flávio Filho,

Credo che Keras potrebbe cambiare il modo in cui funziona, questo è per la versione di agosto 2016. Verificherò per te tra una settimana
Lascio

4
@layser Funziona solo per la perdita di 'category_crossentropy'? Come si attribuisce class_weight alle keras per la perdita di 'sigmoid' e 'binary_crossentropy'?
Naman,

1
@layser Puoi spiegare `per trattare ogni istanza di classe 1 come 50 istanze di classe 0`? È che il set in formazione, la riga corrispondente alla classe 1 viene duplicato 50 volte per renderlo bilanciato o segue qualche altro processo?
Divyanshu Shekhar,

122

Potresti semplicemente implementare il class_weightda sklearn:

  1. Importiamo prima il modulo

    from sklearn.utils import class_weight
  2. Per calcolare il peso della classe, procedere come segue

    class_weights = class_weight.compute_class_weight('balanced',
                                                     np.unique(y_train),
                                                     y_train)
    
  3. In terzo luogo e infine aggiungerlo al raccordo del modello

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

Attenzione : ho modificato questo post e ho cambiato il nome della variabile da class_weight a class_weight s per non sovrascrivere il modulo importato. Regola di conseguenza quando copi il codice dai commenti.


21
Per me, class_weight.compute_class_weight produce un array, ho bisogno di cambiarlo in un dict per poter lavorare con Keras. Più specificamente, dopo il passaggio 2, utilizzareclass_weight_dict = dict(enumerate(class_weight))
C.Vedi

5
Questo non funziona per me. Per un problema di tre classi in keras y_trainè (300096, 3)l'array numpy. Quindi la class_weight=riga mi dà TypeError: unhashable type: 'numpy.ndarray'
Lembik

3
@Lembik Ho avuto un problema simile, in cui ogni riga di y è un vettore codificato a una scala dell'indice di classe. Ho riparato convertendo la rappresentazione di un caldo per un int come questo: y_ints = [y.argmax() for y in y_train].
tkocmathla,

3
E se sto facendo un'etichettatura multiclasse in modo che i miei vettori y_true abbiano più 1 in essi: [1 0 0 0 1 0 0] per esempio, dove alcuni x hanno etichette 0 e 4. Anche allora, il numero totale di ciascuno dei miei le etichette non sono bilanciate. Come userei i pesi di classe con quello?
Aalok,

22

Uso questo tipo di regola per 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.logleviga i pesi per lezioni molto squilibrate! Questo ritorna:

{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
Perché usare log invece di dividere semplicemente il numero di campioni per una classe per il numero totale di campioni? Suppongo che ci sia qualcosa che non capisco va nel parametro class_weight su model.fit_generator (...)
startoftext

@startoftext Ecco come l'ho fatto, ma penso che tu l'abbia invertito. Ho usato n_total_samples / n_class_samplesper ogni classe.
Colllin,

2
Nel tuo esempio la classe 0 (ha 2813 esempi) e la classe 6 (ha 7914 esempi) hanno peso esattamente 1.0. Perché? La classe 6 è alcune volte più grande! Si vorrebbe che la classe 0 fosse potenziata e la classe 6 ridotta per portarli allo stesso livello.
Vladislavs Dovgalecs,

9

NOTA: vedere i commenti, questa risposta è obsoleta.

Per ponderare tutte le classi allo stesso modo, ora puoi semplicemente impostare class_weight su "auto" in questo modo:

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

1
Non sono riuscito a trovare riferimenti class_weight='auto'nella documentazione di Keras né nel codice sorgente. Puoi mostrarci dove l'hai trovato?
Fábio Perez,

2
Questa risposta è probabilmente sbagliata. Controlla questo problema: github.com/fchollet/keras/issues/5116
Fábio Perez

Dispari. Stavo usando class_balanced = 'auto' al momento in cui ho pubblicato il commento, ma non riesco a trovare un riferimento ad esso ora. Forse è stato cambiato poiché Keras si è evoluto rapidamente.
David Groppe,

Come specificato nel numero Keras sopra indicato , è possibile passare qualsiasi stringa casuale come class_weighte non avrà alcun effetto. Questa risposta non è quindi corretta.
ncasas,

3

class_weight va bene ma, come ha detto @Aalok, questo non funzionerà se si utilizzano classi multilabel con codifica a caldo. In questo caso, utilizzare sample_weight :

sample_weight: array opzionale della stessa lunghezza di x, contenente pesi da applicare alla perdita del modello per ciascun campione. Nel caso dei dati temporali, è possibile passare un array 2D con forma (campioni, sequenza_lunghezza), per applicare un peso diverso a ogni timestep di ogni campione. In questo caso dovresti assicurarti di specificare sample_weight_mode = "temporale" in compile ().

sample_weights viene utilizzato per fornire un peso per ciascun campione di allenamento . Ciò significa che è necessario passare un array 1D con lo stesso numero di elementi dei campioni di allenamento (indicando il peso per ciascuno di tali campioni).

class_weights viene utilizzato per fornire un peso o un errore per ciascuna classe di output . Ciò significa che dovresti superare un peso per ogni classe che stai cercando di classificare.

sample_weight deve avere una matrice numpy, poiché la sua forma verrà valutata.

Vedi anche questa risposta: https://stackoverflow.com/questions/48315094/using-sample-weight-in-keras-for-sequence-labelling


2

Aggiunta alla soluzione su https://github.com/keras-team/keras/issues/2115 . Se hai bisogno di una ponderazione superiore alla classe in cui desideri costi diversi per falsi positivi e falsi negativi. Con la nuova versione di keras ora puoi semplicemente ignorare la rispettiva funzione di perdita come indicato di seguito. Si noti che weightsè una matrice quadrata.

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

Ho trovato il seguente esempio di codifica di pesi di classe nella funzione di perdita usando il set di dati minista. Vedi link qui: 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()}

Funziona con un generatore o uno standard. La tua classe più grande avrà un peso di 1 mentre le altre avranno valori maggiori di 1 rispetto alla classe più grande.

i pesi di classe accetta un input di tipo dizionario

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.