Jak ustawić wagi klas dla klas niezrównoważonych w Keras?

128

Wiem, że w Keras istnieje możliwość class_weightsdopasowania słownika parametrów, ale nie znalazłem żadnego przykładu. Czy ktoś byłby tak miły, aby go zapewnić?

Nawiasem mówiąc, w tym przypadku właściwą praktyką jest po prostu ważenie klasy mniejszości proporcjonalnie do jej niedostatecznej reprezentacji?

Hendrik
źródło
Czy dostępna jest nowa zaktualizowana metoda za pomocą Keras? dlaczego słownik składa się z trzech klas i dla klasy: 0: 1,0 1: 50,0 2: 2,0 ???? nie powinien także: 2: 1.0?
Chuck,

Odpowiedzi:

112

Jeśli mówisz o zwykłym przypadku, w którym twoja sieć wytwarza tylko jeden wynik, twoje założenie jest prawidłowe. Aby zmusić algorytm do traktowania każdej instancji klasy 1 jako 50 instancji klasy 0 , musisz:

  1. Zdefiniuj słownik za pomocą etykiet i powiązanych z nimi wag

    class_weight = {0: 1.,
                    1: 50.,
                    2: 2.}
  2. Podaj słownik jako parametr:

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

EDYCJA: „traktuj każdą instancję klasy 1 jako 50 instancji klasy 0 ” oznacza, że ​​w funkcji straty przypisujesz wyższą wartość do tych instancji. Stąd utrata staje się średnią ważoną, gdzie waga każdej próbki jest określona przez class_weight i odpowiadającą jej klasę.

Z dokumentacji Keras: class_weight : Opcjonalne słownikowe mapowanie indeksów klas (liczb całkowitych) na wartość wagi (liczby zmiennoprzecinkowej), używane do ważenia funkcji straty (tylko podczas treningu).

Layser
źródło
1
Zajrzyj również na github.com/fchollet/keras/issues/3653, jeśli pracujesz z danymi 3D.
herve
Dla mnie daje błąd, ponieważ nie ma atrybutu kształtu.
Flávio Filho
Wierzę, że Keras może zmienić sposób, w jaki to działa, dotyczy to wersji z sierpnia 2016 r.
Sprawdzę
4
@layser Czy działa to tylko w przypadku utraty „category_crossentropy”? Jak podajesz class_weight keras za utratę „sigmoid” i „binary_crossentropy”?
Naman
1
@layser Czy możesz wyjaśnić „aby traktować każdą instancję klasy 1 jako 50 instancji klasy 0”? Czy jest tak, że zestaw szkoleniowy, wiersz odpowiadający klasie 1 jest powielany 50 razy, aby go wyrównać, czy też następuje inny proces?
Divyanshu Shekhar
121

Można po prostu wdrożyć class_weightod sklearn:

  1. Najpierw zaimportujmy moduł

    from sklearn.utils import class_weight
  2. Aby obliczyć masę klasy, wykonaj następujące czynności

    class_weights = class_weight.compute_class_weight('balanced',
                                                     np.unique(y_train),
                                                     y_train)
  3. Po trzecie i na koniec dodaj go do dopasowania modelu

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

Uwaga : Zredagowałem ten post i zmieniłem nazwę zmiennej z class_weight na class_weight s , aby nie zastępować importowanego modułu. Dostosuj odpowiednio podczas kopiowania kodu z komentarzy.

PSc
źródło
20
Dla mnie class_weight.compute_class_weight tworzy tablicę, muszę zmienić ją na dyktę, aby móc pracować z Keras. Mówiąc dokładniej, po kroku 2 użyjclass_weight_dict = dict(enumerate(class_weight))
C. Patrz
5
To mi nie działa. Dla trzech klas problemów w keras y_trainjest (300096, 3)tablica numpy. Więc class_weight=wiersz daje mi TypeError: unhashable type: 'numpy.ndarray'
Lembik
3
@Lembik Miałem podobny problem, w którym każdy wiersz y jest jednokrotnie zakodowanym wektorem indeksu klas. I poprawiony poprzez przekształcenie jednego gorącego reprezentację int tak: y_ints = [y.argmax() for y in y_train].
tkocmathla
3
Co się stanie, jeśli wykonuję etykietowanie wieloklasowe, aby moje wektory y_true zawierały wiele 1: na przykład [1 0 0 0 1 0 0] na przykład, gdzie niektóre x ma etykiety 0 i 4. Nawet wtedy łączna liczba każdego z moich etykiety nie są zrównoważone. Jak miałbym używać z tym odważników klasowych?
Aalok,
22

Używam tego rodzaju reguły do 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.logwygładza ciężary dla bardzo niezrównoważonych klas! Zwraca to:

{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
źródło
3
Po co używać dziennika zamiast po prostu dzielenia liczby próbek dla klasy przez całkowitą liczbę próbek? Zakładam, że jest coś, czego nie rozumiem, wchodzi w param class_weight na model.fit_generator (...)
startoftext
@startoftext Tak to zrobiłem, ale myślę, że masz to odwrócone. Użyłem n_total_samples / n_class_samplesdla każdej klasy.
colllin,
2
W twoim przykładzie klasa 0 (ma 2813 przykładów) i klasa 6 (ma 7914 przykładów) ma wagę dokładnie 1,0. Dlaczego? Klasa 6 jest kilka razy większa! Chcielibyście, aby klasa 0 została zwiększona, a klasa 6 została zmniejszona, aby przenieść je na ten sam poziom.
Vladislavs Dovgalecs
9

UWAGA: patrz komentarze, ta odpowiedź jest nieaktualna.

Aby zrównoważyć wszystkie klasy, możesz teraz po prostu ustawić class_weight na „auto” w następujący sposób:

model.fit(X_train, Y_train, nb_epoch=5, batch_size=32, class_weight = 'auto')
David Groppe
źródło
1
Nie mogłem znaleźć żadnego odniesienia class_weight='auto'w dokumentacji Keras ani w kodzie źródłowym. Czy możesz nam pokazać, gdzie to znalazłeś?
Fábio Perez
2
Ta odpowiedź jest prawdopodobnie błędna. Sprawdź ten problem: github.com/fchollet/keras/issues/5116
Fábio Perez
Dziwny. Użyłem class_balanced = 'auto' w momencie, gdy opublikowałem komentarz, ale nie mogę teraz znaleźć odniesienia do niego. Być może zostało to zmienione, ponieważ Keras szybko ewoluował.
David Groppe,
Jak określono we wskazanym powyżej problemie Keras , możesz przekazać dowolny losowy ciąg znaków class_weighti nie będzie to miało żadnego efektu. Ta odpowiedź jest zatem nieprawidłowa.
ncasas
3

class_weight jest w porządku, ale jak powiedział @Aalok, to nie zadziała, jeśli jesteś klasą z kilkoma znakami. W takim przypadku użyj sample_weight :

sample_weight: opcjonalna tablica o tej samej długości co x, zawierająca wagi do zastosowania do utraty modelu dla każdej próbki. W przypadku danych czasowych możesz przekazać tablicę 2D o kształcie (próbki, długość_ sekwencji), aby zastosować inną wagę do każdego pomiaru czasu każdej próbki. W takim przypadku powinieneś koniecznie podać sample_weight_mode = "temporal" w compile ().

sample_weights służy do podania masy każdej próbki treningowej . Oznacza to, że powinieneś przekazać tablicę 1D z taką samą liczbą elementów jak twoje próbki treningowe (wskazując wagę każdej z tych próbek).

class_weights służy do zapewnienia wagi lub odchylenia dla każdej klasy wyjściowej . Oznacza to, że powinieneś przekazać wagę każdej klasie, którą próbujesz sklasyfikować.

sample_weight musi otrzymać tablicę numpy, ponieważ jego kształt zostanie oceniony.

Zobacz także tę odpowiedź: https://stackoverflow.com/questions/48315094/using-sample-weight-in-keras-for-sequence-labelling

Charly Empereur-mot
źródło
2

Dodanie do rozwiązania na https://github.com/keras-team/keras/issues/2115 . Jeśli potrzebujesz więcej niż ważenia klasowego, w którym potrzebujesz różnych kosztów dla fałszywie dodatnich i fałszywych negatywów. Dzięki nowej wersji keras możesz teraz zastąpić odpowiednią funkcję utraty, jak podano poniżej. Zauważ, że weightsjest to macierz kwadratowa.

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
źródło
0

Znalazłem następujący przykład kodowania wag klas w funkcji straty przy użyciu zestawu danych minist. Zobacz link tutaj: 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
źródło
0
from collections import Counter
itemCt = Counter(trainGen.classes)
maxCt = float(max(itemCt.values()))
cw = {clsID : maxCt/numImg for clsID, numImg in itemCt.items()}

Działa to z generatorem lub standardem. Twoja największa klasa będzie miała wagę 1, podczas gdy inne będą miały wartości większe niż 1 w stosunku do największej klasy.

klasy wag akceptuje dane wejściowe typu słownikowego

Allie
źródło