Как установить веса классов для несбалансированных классов в Керасе?


130

Я знаю, что в Керасе есть возможность при подборе class_weightsсловаря параметров, но я не смог найти ни одного примера. Будет ли кто-то так любезно предоставить?

Кстати, в этом случае подходящей практикой является просто взвесить класс меньшинства пропорционально его недопредставленности?


Есть ли новый обновленный метод с использованием Keras? почему словарь состоит из трех классов и для класса: 0: 1.0 1: 50.0 2: 2.0 ???? не должно быть: 2: 1.0?
Чак

Ответы:


112

Если вы говорите о обычном случае, когда ваша сеть производит только один вывод, то ваше предположение верно. Чтобы заставить ваш алгоритм обрабатывать каждый экземпляр класса 1 как 50 экземпляров класса 0, вы должны:

  1. Определите словарь с вашими ярлыками и их весом

    class_weight = {0: 1.,
                    1: 50.,
                    2: 2.}
    
  2. Подать словарь в качестве параметра:

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

РЕДАКТИРОВАТЬ: «обрабатывать каждый экземпляр класса 1 как 50 экземпляров класса 0 » означает, что в вашей функции потерь вы присваиваете этим значениям более высокое значение. Следовательно, потеря становится средневзвешенной величиной , где вес каждой выборки определяется class_weight и соответствующим классом.

От Keras docs: class_weight : Необязательный словарь, отображающий индексы класса (целые числа) в значение веса (с плавающей запятой), используемое для взвешивания функции потерь (только во время обучения).


1
Также взгляните на github.com/fchollet/keras/issues/3653, если вы работаете с 3D-данными.
Эрве

Для меня это дает ошибку, у которой нет атрибута формы.
Флавио

Я полагаю, что Keras может изменить способ, которым это работает, это для версии августа 2016 года. Я
проверю

4
@layser Это работает только для потери 'category_crossentropy'? Как вы передаете class_weight для keras для потери 'sigmoid' и 'binary_crossentropy'?
Наман

1
@layser Можете ли вы объяснить, что `обрабатывать каждый экземпляр класса 1 как 50 экземпляров класса 0`? Это то, что в тренировочном наборе строка, соответствующая классу 1, дублируется 50 раз, чтобы сделать ее сбалансированной, или следует какой-то другой процесс?
Дивьяншу Шехар

122

Вы можете просто реализовать class_weightиз sklearn:

  1. Давайте сначала импортируем модуль

    from sklearn.utils import class_weight
  2. Чтобы рассчитать вес класса, сделайте следующее

    class_weights = class_weight.compute_class_weight('balanced',
                                                     np.unique(y_train),
                                                     y_train)
    
  3. В-третьих, и, наконец, добавьте его в примерку модели.

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

Внимание : я отредактировал этот пост и изменил имя переменной с class_weight на class_weight s , чтобы не перезаписывать импортированный модуль. Отрегулируйте соответственно при копировании кода из комментариев.


21
Для меня class_weight.compute_class_weight производит массив, мне нужно изменить его на dict, чтобы работать с Keras. Более конкретно, после шага 2 используйтеclass_weight_dict = dict(enumerate(class_weight))
C.Lee

5
Это не работает для меня. Для трехклассной задачи в керасе y_trainесть (300096, 3)массив numy. Таким образом, class_weight=строка дает мне TypeError: unhashable type: 'numpy.ndarray'
Lembik

3
@Lembik У меня была похожая проблема, где каждая строка y является горячим закодированным вектором индекса класса. Я зафиксировал его путем преобразования одного горячего представления к междунар как это: y_ints = [y.argmax() for y in y_train].
tkocmathla

3
Что, если я делаю мультиклассовую маркировку, чтобы в моих векторах y_true было несколько единиц 1: например, [1 0 0 0 1 0 0], где некоторые x имеют метки 0 и 4. Даже тогда общее число # каждого из моих ярлыки не сбалансированы. Как бы я использовал вес класса с этим?
Аалок

22

Я использую это правило для 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.logсглаживает веса для очень несбалансированных классов! Это возвращает:

{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
Зачем использовать журнал вместо простого деления количества выборок для класса на общее количество выборок? Я предполагаю, что есть кое-что, что я не понимаю, входит в параметр class_weight на model.fit_generator (...)
startoftext

@startoftext Вот как я это сделал, но я думаю, что вы перевернули его. Я использовал n_total_samples / n_class_samplesдля каждого класса.
Коллин

2
В вашем примере класс 0 (имеет 2813 примеров) и класс 6 (имеет 7914 примеров) имеют вес ровно 1,0. Почему это? Класс 6 в несколько раз больше! Вы хотели бы, чтобы класс 0 был увеличен, а класс 6 уменьшен, чтобы привести их к тому же уровню.
Владислав Довгальец

9

ПРИМЕЧАНИЕ: см. Комментарии, этот ответ устарел.

Чтобы одинаково взвешивать все классы, теперь вы можете просто установить class_weight в "auto" следующим образом:

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

1
Я не смог найти ссылки class_weight='auto'ни в документации Keras, ни в исходном коде. Можете ли вы показать нам, где вы нашли это?
Фабио Перес

2
Этот ответ, вероятно, неправильный. Проверьте эту проблему: github.com/fchollet/keras/issues/5116
Фабио Перес

Странный. Я использовал class_balanced = 'auto' во время публикации комментария, но сейчас не могу найти ссылку на него. Возможно это было изменено, поскольку Керас быстро развивался.
Дэвид Groppe

Как указано в проблеме Keras, указанной выше , вы можете передать любую случайную строку, class_weightи это не будет иметь никакого эффекта. Поэтому этот ответ не является правильным.
ncasas

3

class_weight это хорошо, но, как сказал @Aalok, это не сработает, если вы используете горячее кодирование многослойных классов. В этом случае используйте sample_weight :

sample_weight: необязательный массив той же длины, что и x, содержащий веса, применяемые к потерям модели для каждого образца. В случае временных данных вы можете передать двумерный массив с формой (samples, sequence_length), чтобы применить различный вес к каждому временному шагу каждой выборки. В этом случае вы должны обязательно указать sample_weight_mode = "temporal" в compile ().

sample_weights используется для предоставления веса для каждого обучающего образца . Это означает, что вы должны передать одномерный массив с тем же количеством элементов, что и у ваших тренировочных выборок (с указанием веса для каждой из этих выборок).

class_weights используется для предоставления веса или смещения для каждого выходного класса . Это означает, что вы должны передать вес для каждого класса, который вы пытаетесь классифицировать.

sample_weight должен содержать массив numpy, так как его форма будет оценена.

Смотрите также этот ответ: https://stackoverflow.com/questions/48315094/using-sample-weight-in-keras-for-sequence-labelling


2

Добавление к решению на https://github.com/keras-team/keras/issues/2115 . Если вам нужно больше, чем взвешивание класса, где вы хотите разные затраты для ложных срабатываний и ложных отрицаний. С новой версией keras теперь вы можете просто переопределить соответствующую функцию потерь, как указано ниже. Обратите внимание, что weightsэто квадратная матрица.

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

Я нашел следующий пример кодирования весов классов в функции потерь с использованием набора данных minist. Смотрите ссылку здесь: 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()}

Это работает с генератором или стандартом. Ваш самый большой класс будет иметь вес 1, в то время как другие будут иметь значения больше 1 относительно самого большого класса.

Вес класса принимает входные данные типа словаря

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.