Como definir pesos de classe para classes desequilibradas em Keras?

130

Eu sei que existe uma possibilidade no Keras com o class_weightsdicionário de parâmetros no ajuste, mas não encontrei nenhum exemplo. Alguém seria tão gentil em fornecer um?

A propósito, nesse caso, a práxis apropriada é simplesmente ponderar a classe minoritária proporcionalmente à sua sub-representação?

Hendrik
fonte
Existe um novo método atualizado usando o Keras? por que o dicionário consiste em três classes e para a classe: 0: 1.0 1: 50.0 2: 2.0 ???? não deveria: 2: 1.0 também?
Chuck

Respostas:

112

Se você está falando sobre o caso regular, em que sua rede produz apenas uma saída, sua suposição está correta. Para forçar seu algoritmo a tratar todas as instâncias da classe 1 como 50 instâncias da classe 0, você deve:

  1. Defina um dicionário com seus rótulos e seus pesos associados

    class_weight = {0: 1.,
                    1: 50.,
                    2: 2.}
    
  2. Alimente o dicionário como um parâmetro:

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

EDIT: "tratar todas as instâncias da classe 1 como 50 instâncias da classe 0 " significa que na sua função de perda você atribui um valor mais alto a essas instâncias. Portanto, a perda se torna uma média ponderada, onde o peso de cada amostra é especificado por class_weight e sua classe correspondente.

Do Keras docs: class_weight : índices de classe de mapeamento de dicionário opcionais (números inteiros) para um valor de peso (flutuante), usado para ponderar a função de perda (apenas durante o treinamento).

layser
fonte
11
Consulte também github.com/fchollet/keras/issues/3653 se estiver trabalhando com dados 3D.
herve
Para mim, dá um erro dic não tem atributo de forma.
Flávio Filho
Acredito Keras pode estar mudando a maneira como isso funciona, isto é para a versão de Agosto de 2016. Vou verificar para você em uma semana
layser
4
@layser Isso funciona apenas para a perda de 'category_crossentropy'? Como você atribui class_weight a keras pela perda de 'sigmoid' e 'binary_crossentropy'?
Naman
11
@layser Você pode explicar `tratar todas as instâncias da classe 1 como 50 instâncias da classe 0 '? É que a linha do conjunto de treinamento, correspondente à classe 1, é duplicada 50 vezes para torná-la equilibrada ou segue algum outro processo?
Divyanshu Shekhar
122

Você pode simplesmente implementar o class_weightde sklearn:

  1. Vamos importar o módulo primeiro

    from sklearn.utils import class_weight
  2. Para calcular o peso da classe, faça o seguinte

    class_weights = class_weight.compute_class_weight('balanced',
                                                     np.unique(y_train),
                                                     y_train)
    
  3. Em terceiro e último lugar, adicione-o ao encaixe do modelo

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

Atenção : Editei este post e alterei o nome da variável de class_weight para class_weight s para não sobrescrever o módulo importado. Ajuste de acordo ao copiar o código dos comentários.

PSc
fonte
21
Para mim, class_weight.compute_class_weight produz uma matriz, preciso alterá-la para um ditado para trabalhar com Keras. Mais especificamente, após a etapa 2, useclass_weight_dict = dict(enumerate(class_weight))
C.Lee
5
Isso não funciona para mim. Para um problema de três classes no keras y_trainé um (300096, 3)array numpy. Assim, a class_weight=linha dá-me TypeError: Tipo unhashable: 'numpy.ndarray'
Lembik
3
@ Lembik Eu tive um problema semelhante, onde cada linha de y é um vetor codificado de um ponto quente do índice de classe. Eu fixo-lo, convertendo a representação de um quente para um int como este: y_ints = [y.argmax() for y in y_train].
precisa saber é o seguinte
3
E se eu estiver rotulando em várias classes para que meus vetores y_true tenham vários 1s neles: [1 0 0 0 1 0 0] por exemplo, onde algum x tem rótulos 0 e 4. Mesmo assim, o número total de cada um dos meus etiquetas não está equilibrada. Como eu usaria pesos de classe com isso?
Aalok
22

Eu uso esse tipo de regra para 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.logsuaviza os pesos para classes muito desequilibradas! Isso retorna:

{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
fonte
3
Por que usar log em vez de apenas dividir a contagem de amostras para uma classe pelo número total de amostras? Estou assumindo que existe algo que eu não entendo que entra no param class_weight no model.fit_generator (...)
startoftext
@startoftext Foi assim que fiz, mas acho que você o inverteu. Eu costumava n_total_samples / n_class_samplespara cada aula.
colllin
2
No seu exemplo, a classe 0 (tem 2813 exemplos) e a classe 6 (tem 7914 exemplos) têm peso exatamente 1,0. Por que é que? A classe 6 é algumas vezes maior! Você gostaria que a classe 0 fosse aumentada e a classe 6 reduzida para trazê-las para o mesmo nível.
Vladislavs Dovgalecs
9

NOTA: Veja os comentários, esta resposta está desatualizada.

Para ponderar todas as classes igualmente, agora você pode simplesmente definir class_weight como "auto" da seguinte forma:

model.fit(X_train, Y_train, nb_epoch=5, batch_size=32, class_weight = 'auto')
David Groppe
fonte
11
Não encontrei nenhuma referência class_weight='auto'na documentação do Keras nem no código-fonte. Você pode nos mostrar onde encontrou isso?
Fábio Perez
2
Esta resposta provavelmente está errada. Verifique esta edição: github.com/fchollet/keras/issues/5116
Fábio Perez
Ímpar. Eu estava usando class_balanced = 'auto' no momento em que postei o comentário, mas não consigo encontrar referência a ele agora. Talvez tenha sido alterado, pois Keras está evoluindo rapidamente.
David Groppe 16/03/19
Conforme especificado no problema Keras indicado acima , você pode transmitir qualquer sequência aleatória class_weighte ela não terá efeito. Esta resposta não está correta.
ncasas
3

class_weight é bom, mas como @Aalok disse que isso não funcionará se você for um tipo de codificação de classes com várias tags. Nesse caso, use sample_weight :

sample_weight: matriz opcional do mesmo comprimento que x, contendo pesos a serem aplicados à perda do modelo para cada amostra. No caso de dados temporais, você pode passar uma matriz 2D com forma (amostras, comprimento_de_ sequência), para aplicar um peso diferente a cada passo de tempo de cada amostra. Nesse caso, certifique-se de especificar sample_weight_mode = "temporal" em compile ().

sample_weights é usado para fornecer um peso para cada amostra de treinamento . Isso significa que você deve passar uma matriz 1D com o mesmo número de elementos que suas amostras de treinamento (indicando o peso de cada uma dessas amostras).

class_weights é usado para fornecer um peso ou viés para cada classe de saída . Isso significa que você deve passar um peso para cada classe que você está tentando classificar.

sample_weight deve receber uma matriz numpy, pois sua forma será avaliada.

Consulte também esta resposta: https://stackoverflow.com/questions/48315094/using-sample-weight-in-keras-for-sequence-labelling

Charly Empereur-mot
fonte
2

Adicionando à solução em https://github.com/keras-team/keras/issues/2115 . Se você precisar de mais do que ponderação de classe, em que deseja custos diferentes para falsos positivos e falsos negativos. Com a nova versão do keras agora você pode simplesmente substituir a respectiva função de perda, conforme indicado abaixo. Observe que weightsé uma matriz quadrada.

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
fonte
0

Encontrei o exemplo a seguir de codificação de pesos de classe na função de perda usando o conjunto de dados minist. Veja o link aqui: 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
fonte
0
from collections import Counter
itemCt = Counter(trainGen.classes)
maxCt = float(max(itemCt.values()))
cw = {clsID : maxCt/numImg for clsID, numImg in itemCt.items()}

Isso funciona com um gerador ou padrão. Sua classe maior terá um peso 1, enquanto os outros terão valores maiores que 1 em relação à maior classe.

pesos de classe aceita uma entrada de tipo de dicionário

aliado
fonte