Керас: взвешенная бинарная кроссентропия

Я пытался реализовать взвешенную бинарную кроссентропию с помощью Keras, но я не уверен, что код правильный. Результаты тренировок кажутся немного запутанными. После нескольких эпох я получаю точность ~0,15. Я думаю, что это слишком много (даже для случайного предположения).

В общем, выходных данных около 11%, а нулей - 89%, поэтому весовые коэффициенты w_zero=0,89 и w_one=0,11.

Мой код:

def create_weighted_binary_crossentropy(zero_weight, one_weight):

    def weighted_binary_crossentropy(y_true, y_pred):

        # Original binary crossentropy (see losses.py):
        # K.mean(K.binary_crossentropy(y_true, y_pred), axis=-1)

        # Calculate the binary crossentropy
        b_ce = K.binary_crossentropy(y_true, y_pred)

        # Apply the weights
        weight_vector = y_true * one_weight + (1. - y_true) * zero_weight
        weighted_b_ce = weight_vector * b_ce

        # Return the mean error
        return K.mean(weighted_b_ce)

    return weighted_binary_crossentropy

Может кто-то видит, что не так?

Спасибо

7 ответов

Решение

Обычно класс меньшинства будет иметь более высокий вес класса. Будет лучше использовать one_weight=0.89, zero_weight=0.11 (кстати, вы можете использовать class_weight={0: 0.11, 1: 0.89}, как предлагается в комментарии).

При дисбалансе класса ваша модель видит намного больше нулей, чем единиц. Он также научится прогнозировать больше нулей, чем единиц, потому что таким образом можно уменьшить потери при обучении. Вот почему вы видите точность, близкую к пропорции 0,11. Если вы берете среднее значение по модельным прогнозам, оно должно быть очень близко к нулю.

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

Обратите внимание, что лучшие веса не обязательно 0,89 и 0,11. Иногда вам, возможно, придется попробовать что-то вроде логарифмов или квадратных корней (или любого веса, удовлетворяющего one_weight > zero_weight) чтобы это работало.

Вы можете использовать модуль sklearn для автоматического расчета весов для каждого класса следующим образом:

# Import
import numpy as np
from sklearn.utils import class_weight

# Example model
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=100))
model.add(Dense(1, activation='sigmoid'))

# Use binary crossentropy loss
model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])

# Calculate the weights for each class so that we can balance the data
weights = class_weight.compute_class_weight('balanced',
                                            np.unique(y_train),
                                            y_train)

# Add the class weights to the training                                         
model.fit(x_train, y_train, epochs=10, batch_size=32, class_weight=weights)

Обратите внимание, что вывод class_weight.compute_class_weight() представляет собой массив типа numpy: [2.57569845 0.68250928].

С помощью class_weights в model.fit немного отличается: он фактически обновляет образцы, а не вычисляет взвешенный убыток.

Я также обнаружил, что class_weights, также как и sample_weights, игнорируются в TF 2.0.0, когда xотправляется в model.fit как TFDataset или генератор. Я считаю, что это исправлено в TF 2.1.0+.

Вот моя взвешенная двоичная функция кросс-энтропии для меток с горячим кодированием.

import tensorflow as tf
import tensorflow.keras.backend as K
import numpy as np
# weighted loss functions


def weighted_binary_cross_entropy(weights: dict, from_logits: bool = False):
    '''
    Return a function for calculating weighted binary cross entropy
    It should be used for multi-hot encoded labels

    # Example
    y_true = tf.convert_to_tensor([1, 0, 0, 0, 0, 0], dtype=tf.int64)
    y_pred = tf.convert_to_tensor([0.6, 0.1, 0.1, 0.9, 0.1, 0.], dtype=tf.float32)
    weights = {
        0: 1.,
        1: 2.
    }
    # with weights
    loss_fn = get_loss_for_multilabels(weights=weights, from_logits=False)
    loss = loss_fn(y_true, y_pred)
    print(loss)
    # tf.Tensor(0.6067193, shape=(), dtype=float32)

    # without weights
    loss_fn = get_loss_for_multilabels()
    loss = loss_fn(y_true, y_pred)
    print(loss)
    # tf.Tensor(0.52158177, shape=(), dtype=float32)

    # Another example
    y_true = tf.convert_to_tensor([[0., 1.], [0., 0.]], dtype=tf.float32)
    y_pred = tf.convert_to_tensor([[0.6, 0.4], [0.4, 0.6]], dtype=tf.float32)
    weights = {
        0: 1.,
        1: 2.
    }
    # with weights
    loss_fn = get_loss_for_multilabels(weights=weights, from_logits=False)
    loss = loss_fn(y_true, y_pred)
    print(loss)
    # tf.Tensor(1.0439969, shape=(), dtype=float32)

    # without weights
    loss_fn = get_loss_for_multilabels()
    loss = loss_fn(y_true, y_pred)
    print(loss)
    # tf.Tensor(0.81492424, shape=(), dtype=float32)

    @param weights A dict setting weights for 0 and 1 label. e.g.
        {
            0: 1.
            1: 8.
        }
        For this case, we want to emphasise those true (1) label, 
        because we have many false (0) label. e.g. 
            [
                [0 1 0 0 0 0 0 0 0 1]
                [0 0 0 0 1 0 0 0 0 0]
                [0 0 0 0 1 0 0 0 0 0]
            ]



    @param from_logits If False, we apply sigmoid to each logit
    @return A function to calcualte (weighted) binary cross entropy
    '''
    assert 0 in weights
    assert 1 in weights

    def weighted_cross_entropy_fn(y_true, y_pred):
        tf_y_true = tf.cast(y_true, dtype=y_pred.dtype)
        tf_y_pred = tf.cast(y_pred, dtype=y_pred.dtype)

        weights_v = tf.where(tf.equal(tf_y_true, 1), weights[1], weights[0])
        ce = K.binary_crossentropy(tf_y_true, tf_y_pred, from_logits=from_logits)
        loss = K.mean(tf.multiply(ce, weights_v))
        return loss

    return weighted_cross_entropy_fn

Вы можете вычислить такие веса и получить такую ​​двоичную кросс-энтропию, которая программно установит one_weight на 0.11 и one на 0.89:

one_weight = (1-num_of_ones)/(num_of_ones + num_of_zeros)
zero_weight = (1-num_of_zeros)/(num_of_ones + num_of_zeros)

def weighted_binary_crossentropy(zero_weight, one_weight):

    def weighted_binary_crossentropy(y_true, y_pred):

        b_ce = K.binary_crossentropy(y_true, y_pred)

        # weighted calc
        weight_vector = y_true * one_weight + (1 - y_true) * zero_weight
        weighted_b_ce = weight_vector * b_ce

        return K.mean(weighted_b_ce)

    return weighted_binary_crossentropy

Я думаю, что использование веса класса в model.fit не правильно. {0:0.11, 1:0.89}, 0 здесь индекс, а не 0 класс. Документация Keras: https://keras.io/models/sequential/ class_weight: необязательный словарь, отображающий индексы класса (целые числа) в значение веса (с плавающей запятой), используемый для взвешивания функции потерь (только во время обучения). Это может быть полезно для того, чтобы сказать модели "уделять больше внимания" выборкам из недопредставленного класса.

Для меня лучший способ сделать это так:

      def custom_weighted_binary_crossentropy(zero_weight, one_weight):

    def weighted_binary_crossentropy(y_true, y_pred):
        y_true = K.cast(y_true, dtype=tf.float32)

        epsilon = tf.keras.backend.epsilon()
        y_pred = tf.clip_by_value(y_pred, epsilon, 1. - epsilon)

        # Compute cross entropy from probabilities.
        bce = y_true * tf.math.log(y_pred + epsilon)
        bce += (1 - y_true) * tf.math.log(1 - y_pred + epsilon)
        bce = -bce

        # Apply the weights to each class individually
        weight_vector = y_true * one_weight + (1. - y_true) * zero_weight
        weighted_bce = weight_vector * bce

        # Return the mean error
        return tf.reduce_mean(weighted_bce)

    return weighted_binary_crossentropy

В случае, когда вам нужно иметь взвешенную потерю проверки с другим весом, чем потеря тренировки, вы можете использовать параметр validation_data из tensorflow.keras.model.fit(), поместив свой набор данных проверки в виде кортежа массивов Numpy, содержащих ваш данные проверки, этикетки и вес для каждого образца.

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

Перейдите по ссылке здесь:https://www.tensorflow.org/api_docs/python/tf/keras/Model#fit

документация tenorflow

Другие вопросы по тегам