Por que binary_crossentropy e categorical_crossentropy oferecem desempenhos diferentes para o mesmo problema?

160

Estou tentando treinar uma CNN para categorizar o texto por tópico. Quando uso entropia cruzada binária, obtenho ~ 80% de precisão, com entropia cruzada categórica recebo ~ 50% de precisão.

Eu não entendo por que isso é. É um problema multiclasse, isso não significa que eu tenho que usar entropia cruzada categórica e que os resultados com entropia cruzada binária não têm sentido?

model.add(embedding_layer)
model.add(Dropout(0.25))
# convolution layers
model.add(Conv1D(nb_filter=32,
                    filter_length=4,
                    border_mode='valid',
                    activation='relu'))
model.add(MaxPooling1D(pool_length=2))
# dense layers
model.add(Flatten())
model.add(Dense(256))
model.add(Dropout(0.25))
model.add(Activation('relu'))
# output layer
model.add(Dense(len(class_id_index)))
model.add(Activation('softmax'))

Então eu compilei assim usando esta categorical_crossentropyfunção de perda:

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

ou

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

Intuitivamente, faz sentido o motivo pelo qual eu gostaria de usar a entropia cruzada categórica, não entendo por que obtenho bons resultados com binários e maus resultados com categóricos.

Daniel Messias
fonte
10
Se for um problema de várias classes, você precisará usar categorical_crossentropy. Os rótulos também precisam ser convertidos para o formato categórico. Veja to_categoricalpara fazer isso. Veja também definições de entropia cruzada categórica e binária aqui .
Autônomo
Meus rótulos são categóricos, criados usando to_categorical (um vetor quente para cada classe). Isso significa que a precisão de ~ 80% da entropia cruzada binária é apenas um número falso?
Daniel Messias
Acho que sim. Se você usa rótulos categóricos, ou seja, um vetores quentes, deseja categorical_crossentropy. Se você tiver duas classes, elas serão representadas como 0, 1em rótulos binários e 10, 01em formato de rótulo categórico.
Autônomo
1
Eu acho que ele apenas se compara ao primeiro número no vetor e ignora o resto.
Thomas Pinetz
2
@NilavBaranGhosh A representação será [[1, 0], [0, 1]] para uma classificação categórica envolvendo duas classes (não [[0, 0], [0, 1]] como você mencionou). Dense(1, activation='softmax')pois a classificação binária está simplesmente errada. Lembre-se de que a saída do softmax é uma distribuição de probabilidade que resume a um. Se você deseja ter apenas um neurônio de saída com classificação binária, use sigmoide com entropia cruzada binária.
Autônomo

Respostas:

204

O motivo dessa aparente discrepância de desempenho entre entropia cruzada categórica e binária é o que o usuário xtof54 já relatou em sua resposta abaixo , ou seja:

a precisão calculada com o método Keras evaluateestá totalmente errada ao usar binary_crossentropy com mais de 2 rótulos

Eu gostaria de elaborar mais sobre isso, demonstrar o problema subjacente real, explicá-lo e oferecer um remédio.

Esse comportamento não é um bug; o motivo subjacente é uma questão bastante sutil e não documentada sobre como o Keras realmente adivinha qual precisão usar, dependendo da função de perda que você selecionou, quando você inclui simplesmente metrics=['accuracy']na compilação do modelo. Em outras palavras, enquanto sua primeira opção de compilação

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

é válido, seu segundo:

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

não produzirá o que você espera, mas o motivo não é o uso de entropia cruzada binária (que, pelo menos em princípio, é uma função de perda absolutamente válida).

Por que é que? Se você verificar o código fonte das métricas , o Keras não definirá uma única métrica de precisão, mas várias diferentes, entre elas binary_accuracye categorical_accuracy. O que acontece sob o capô é que, como você selecionou a entropia cruzada binária como sua função de perda e não especificou uma métrica de precisão específica, Keras (erroneamente ...) deduz que você está interessado binary_accuracye é isso que ele retorna - enquanto na verdade você está interessado no categorical_accuracy.

Vamos verificar se é esse o caso, usando o exemplo MNIST CNN em Keras, com a seguinte modificação:

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])  # WRONG way

model.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=2,  # only 2 epochs, for demonstration purposes
          verbose=1,
          validation_data=(x_test, y_test))

# Keras reported accuracy:
score = model.evaluate(x_test, y_test, verbose=0) 
score[1]
# 0.9975801164627075

# Actual accuracy calculated manually:
import numpy as np
y_pred = model.predict(x_test)
acc = sum([np.argmax(y_test[i])==np.argmax(y_pred[i]) for i in range(10000)])/10000
acc
# 0.98780000000000001

score[1]==acc
# False    

Para remediar isso, ou seja, para usar de fato a entropia cruzada binária como sua função de perda (como eu disse, nada de errado com isso, pelo menos em princípio) enquanto você ainda obtém a precisão categórica exigida pelo problema em questão, solicite explicitamente categorical_accuracyno compilação do modelo da seguinte maneira:

from keras.metrics import categorical_accuracy
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=[categorical_accuracy])

No exemplo do MNIST, após o treinamento, a pontuação e a previsão do conjunto de testes, como mostro acima, as duas métricas agora são as mesmas, como deveriam ser:

# Keras reported accuracy:
score = model.evaluate(x_test, y_test, verbose=0) 
score[1]
# 0.98580000000000001

# Actual accuracy calculated manually:
y_pred = model.predict(x_test)
acc = sum([np.argmax(y_test[i])==np.argmax(y_pred[i]) for i in range(10000)])/10000
acc
# 0.98580000000000001

score[1]==acc
# True    

Configuração do sistema:

Python version 3.5.3
Tensorflow version 1.2.1
Keras version 2.0.4

ATUALIZAÇÃO : Após minha postagem, descobri que esse problema já havia sido identificado nesta resposta .

desertnaut
fonte
1
Há algo errado com o uso loss='categorical_crossentropy', metrics=['categorical_accuracy']da classificação multiclasse? Esta seria a minha intuição
NeStack
2
@ Neeack Não apenas não há nada errado, mas esta é a combinação nominal.
desertnaut
1
De acordo com o que você disse, desde que eu use loss = 'binary_crossentropy', não receberei o mesmo retorno, não importa quanto use metrics = 'binary_accuracy' ou metrics = 'precision'?
BioCoder
2
@BioCoder exatamente
desertnaut 21/11/19
54

Tudo depende do tipo de problema de classificação com o qual você está lidando. Existem três categorias principais

  • classificação binária (duas classes-alvo),
  • classificação multi-classe (mais de dois alvos exclusivos ),
  • classificação de vários rótulos (mais de dois destinos não exclusivos ), nos quais várias classes de destino podem estar ativadas ao mesmo tempo.

No primeiro caso, a entropia cruzada binária deve ser usada e os alvos devem ser codificados como vetores quentes.

No segundo caso, a entropia cruzada categórica deve ser usada e os alvos devem ser codificados como vetores quentes.

No último caso, a entropia cruzada binária deve ser usada e os alvos devem ser codificados como vetores quentes. Cada neurônio de saída (ou unidade) é considerado uma variável binária aleatória separada, e a perda para todo o vetor de saídas é o produto da perda de variáveis ​​binárias únicas. Portanto, é o produto da entropia cruzada binária para cada unidade de saída única.

A entropia cruzada binária é definida como

insira a descrição da imagem aqui

entropia cruzada categórica é definida como

insira a descrição da imagem aqui

onde co índice está sendo executado sobre o número de classes

Whynote
fonte
Sua resposta me parece muito verdadeira, mas ... Tentei seguir a resposta @desertnaut e fiz os seguintes testes: Com a função de perda binary_crossentropy e metrcis para precisão_categoria, eu tenho uma precisão melhor do que usando a função de perda de categoria e métricas de precisão categorical_crossentropy - e não consigo explicar Isso ...
Metal3d 10/0318
@ Metal3d: qual é a formulação do seu problema: etiqueta múltipla ou etiqueta única?
Whynote 14/0318
de rótulo único, e agora eu percebo por que as obras melhor :)
Metal3d
Tem certeza de que as entropias binárias e categóricas são definidas como nas fórmulas nesta resposta?
nbro 6/01
@ nbro, na verdade, o cíndice é redundante na fórmula de entropia cruzada binária, não precisa estar lá (já que existem apenas 2 classes e a probabilidade de cada classe está incorporada y(x). Caso contrário, essas fórmulas devem estar corretas, mas observe que essas não são perdas, essas são probabilidades.Se você quiser a perda, terá que sofrer uma logdelas.
Whynote
40

Me deparei com um problema "invertido" - eu estava obtendo bons resultados com categorical_crossentropy (com 2 classes) e ruim com binary_crossentropy. Parece que o problema estava com a função de ativação incorreta. As configurações corretas foram:

  • para binary_crossentropy: ativação sigmóide, alvo escalar
  • para categorical_crossentropy: ativação softmax, alvo codificado a quente
Alexander Svetkin
fonte
4
Você tem certeza sobre o destino escalar para binary_crossentropy. Parece que você deve usar o destino codificado "many-hot" (por exemplo, [0 1 0 0 1 1]).
Dmitry
5
Certo. Veja keras.io/losses/#usage-of-loss-functions , ele diz: "ao usar a perda categorical_crossentropy, seus destinos devem estar em formato categórico (por exemplo, se você tiver 10 classes, o destino para cada amostra deve ser 10). tridimensional que é todo-zeros espera um 1 no índice correspondente à classe da amostra) "
Alexander Svetkin 15/09/17
1
Mas estamos falando sobre binary_crossentropy - não categorical_crossentropy.
Dmitry
Essa resposta parece ser inconsistente com o stackoverflow.com/a/49175655/3924118 , em que o autor diz que os destinos devem ser codificados com um hot hot, enquanto, na sua resposta, você sugere que eles sejam escalares. Você deve esclarecer isso.
nbro 6/01
@AlexanderSvetkin, o destino deve ser codificado em um único local em todos os lugares, não apenas ao usar a entropia cruzada categórica
Whynote
28

É um caso realmente interessante. Na verdade, na sua configuração, a seguinte declaração é verdadeira:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

Isso significa que, até um fator de multiplicação constante, suas perdas são equivalentes. O comportamento estranho que você está observando durante uma fase de treinamento pode ser um exemplo do seguinte fenômeno:

  1. No início, a classe mais frequente está dominando a perda - então a rede está aprendendo a prever principalmente essa classe para todos os exemplos.
  2. Depois de aprender o padrão mais frequente, ele começa a discriminar entre classes menos frequentes. Mas quando você está usando adam- a taxa de aprendizado tem um valor muito menor do que tinha no início do treinamento (é por causa da natureza desse otimizador). Isso torna o treinamento mais lento e impede que sua rede, por exemplo, deixe um mínimo local ruim menos possível.

É por isso que esse fator constante pode ajudar no caso de binary_crossentropy. Após muitas épocas - o valor da taxa de aprendizado é maior do que no categorical_crossentropycaso. Normalmente, reinicio o treinamento (e a fase de aprendizado) algumas vezes quando percebo esse comportamento ou / e ajusto o peso de uma classe usando o seguinte padrão:

class_weight = 1 / class_frequency

Isso causa perda de classes menos frequentes, equilibrando a influência de uma perda de classe dominante no início de um treinamento e em uma parte adicional de um processo de otimização.

EDITAR:

Na verdade - eu verifiquei que, embora em caso de matemática:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

deve se manter - caso kerasisso não seja verdade, porque kerasnormaliza automaticamente todas as saídas para somar 1. Essa é a verdadeira razão por trás desse comportamento estranho, pois, no caso de multiclassificação, essa normalização prejudica um treinamento.

Marcin Możejko
fonte
Minha resposta ajudou?
Marcin Możejko 27/02
1
Esta é uma explicação muito plausível. Mas não tenho certeza se esse é realmente o principal motivo. Porque também observei em vários de meus alunos esse comportamento estranho ao aplicar binário-X-ent em vez de gato-X-ent (o que é um erro). E isso é verdade mesmo quando treinamos apenas duas épocas! Usar class_weight com anteriores de classe inversa não ajudou. Pode ser um ajuste rigoroso da taxa de aprendizado, mas os valores padrão parecem favorecer bin-X-ent. Eu acho que esta questão merece mais investigações ...
xtof54
1
Espere, desculpe, não recebo sua atualização: o softmax sempre faz as saídas somarem 1, então não nos importamos com isso? E por que isso prejudicaria o treinamento, desde que tenhamos apenas uma única classe de ouro correta por exemplo?
Xtof54
20

Depois de comentar a resposta do @Marcin, verifiquei com mais cuidado um código de meus alunos, onde encontrei o mesmo comportamento estranho, mesmo depois de apenas duas épocas! (Portanto, a explicação de @ Marcin não era muito provável no meu caso).

E descobri que a resposta é realmente muito simples: a precisão calculada com o método Keras evaluateestá totalmente errada ao usar binary_crossentropy com mais de 2 etiquetas. Você pode verificar isso recalculando a precisão você mesmo (primeiro chame o método Keras de "prever" e depois calcule o número de respostas corretas retornadas por previsão): você obtém a precisão verdadeira, que é muito menor do que a Keras "avalia".

xtof54
fonte
1
Vi também um comportamento semelhante na primeira iteração.
DOLBI
10

um exemplo simples em uma configuração de várias classes para ilustrar

suponha que você tenha 4 classes (codificadas em onehot) e abaixo é apenas uma previsão

true_label = [0,1,0,0] label predito = [0,0,1,0]

ao usar categorical_crossentropy, a precisão é apenas 0, só importa se você acertar a classe em questão.

no entanto, ao usar binary_crossentropy, a precisão é calculada para todas as classes, seria de 50% para esta previsão. e o resultado final será a média das precisões individuais para ambos os casos.

é recomendável usar categorical_crossentropy para problemas com várias classes (as classes são mutuamente exclusivas), mas binary_crossentropy para problemas com vários rótulos.

bazinga
fonte
8

Como se trata de um problema de várias classes, é necessário usar o categorical_crossentropy, a entropia cruzada binária produzirá resultados falsos, provavelmente apenas avaliará as duas primeiras classes apenas.

50% para um problema de várias classes pode ser bastante bom, dependendo do número de classes. Se você tiver n classes, 100 / n é o desempenho mínimo que você pode obter produzindo uma classe aleatória.

Dr. Snoopy
fonte
2

ao usar a categorical_crossentropyperda, seus alvos devem estar em formato categórico (por exemplo, se você tiver 10 classes, o alvo para cada amostra deve ser um vetor de 10 dimensões que seja todo-zeros, exceto um 1 no índice correspondente à classe da classe amostra).

Priyansh
fonte
3
Como exatamente isso responde à pergunta?
Desertnaut 13/06/19
2

Dê uma olhada na equação em que você pode descobrir que a entropia cruzada binária não apenas pune os rótulos = 1, preditos = 0, mas também rótulo = 0, preditos = 1.

No entanto, a entropia cruzada categórica apenas pune aqueles marcadores = 1, mas preditos = 1. É por isso que assumimos que existe apenas UM marcador positivo.

Kuang Yan
fonte
1

Você está passando uma matriz de forma alvo (x-dim, y-dim) enquanto estiver usando como perda categorical_crossentropy. categorical_crossentropyespera que os alvos sejam matrizes binárias (1s e 0s) de forma (amostras, classes). Se seus destinos forem classes inteiras, você poderá convertê-los para o formato esperado via:

from keras.utils import to_categorical
y_binary = to_categorical(y_int)

Como alternativa, você pode usar a função de perda sparse_categorical_crossentropy, que espera destinos inteiros.

model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
susan097
fonte
0

O binary_crossentropy (y_target, y_predict) não precisa ser aplicado no problema de classificação binária. .

No código fonte de binary_crossentropy () , a nn.sigmoid_cross_entropy_with_logits(labels=target, logits=output)função TensorFlow foi realmente usada. E, na documentação , diz o seguinte:

Mede o erro de probabilidade em tarefas discretas de classificação nas quais cada classe é independente e não é mutuamente exclusiva. Por exemplo, pode-se realizar a classificação de vários rótulos, onde uma imagem pode conter um elefante e um cachorro ao mesmo tempo.

翟志伟
fonte