Pourquoi binary_crossentropy et categorical_crossentropy donnent des performances différentes pour le même problème?

160

J'essaie de former un CNN à catégoriser le texte par sujet. Lorsque j'utilise l'entropie croisée binaire, j'obtiens une précision d'environ 80%, avec une entropie croisée catégorique, j'obtiens une précision d'environ 50%.

Je ne comprends pas pourquoi. C'est un problème multiclasse, cela ne signifie-t-il pas que je dois utiliser une entropie croisée catégorique et que les résultats avec une entropie croisée binaire n'ont aucun sens?

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'))

Ensuite, je le compile soit comme ceci en utilisant categorical_crossentropycomme fonction de perte:

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

ou

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

Intuitivement, il est logique que je veuille utiliser l'entropie croisée catégorielle, je ne comprends pas pourquoi j'obtiens de bons résultats avec des résultats binaires et des résultats médiocres avec des résultats catégoriques.

Daniel Messias
la source
10
S'il s'agit d'un problème multiclasse, vous devez utiliser categorical_crossentropy. Les étiquettes doivent également être converties au format catégoriel. Voyez to_categoricalpour faire cela. Voir également les définitions des crossentropies catégoriques et binaires ici .
Autonome
Mes étiquettes sont catégoriques, créées à l'aide de to_categorical (un vecteur chaud pour chaque classe). Cela signifie-t-il que la précision d'environ 80% de la crossentropie binaire n'est qu'un faux nombre?
Daniel Messias
Je le pense. Si vous utilisez des étiquettes catégoriques, c'est-à-dire un vecteur chaud, alors vous voulez categorical_crossentropy. Si vous avez deux classes, elles seront représentées sous 0, 1forme d'étiquettes binaires et 10, 01au format d'étiquette catégorielle.
Autonome
1
Je pense qu'il compare simplement au premier nombre du vecteur et ignore le reste.
Thomas Pinetz
2
@NilavBaranGhosh La représentation sera [[1, 0], [0, 1]] pour une classification catégorielle impliquant deux classes (pas [[0, 0], [0, 1]] comme vous le mentionnez). Dense(1, activation='softmax')car la classification binaire est tout simplement fausse. Rappelez-vous que la sortie softmax est une distribution de probabilité qui se résume à un. Si vous souhaitez n'avoir qu'un seul neurone de sortie avec une classification binaire, utilisez un sigmoïde avec une entropie croisée binaire.
Autonome

Réponses:

204

La raison de cette différence de performance apparente entre l'entropie croisée catégorielle et binaire est ce que l'utilisateur xtof54 a déjà signalé dans sa réponse ci - dessous , à savoir:

la précision calculée avec la méthode Keras evaluateest tout simplement fausse lors de l'utilisation de binary_crossentropy avec plus de 2 étiquettes

Je voudrais développer davantage à ce sujet, démontrer le problème sous-jacent réel, l'expliquer et proposer une solution.

Ce comportement n'est pas un bogue; la raison sous-jacente est un problème plutôt subtil et non documenté quant à la façon dont Keras devine réellement la précision à utiliser, en fonction de la fonction de perte que vous avez sélectionnée, lorsque vous incluez simplement metrics=['accuracy']dans votre compilation de modèle. En d'autres termes, alors que votre première option de compilation

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

est valide, votre deuxième:

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

ne produira pas ce que vous attendez, mais la raison n'est pas l'utilisation de l'entropie croisée binaire (qui, du moins en principe, est une fonction de perte absolument valide).

Pourquoi donc? Si vous vérifiez le code source des métriques , Keras ne définit pas une seule métrique de précision, mais plusieurs différentes, parmi lesquelles binary_accuracyet categorical_accuracy. Ce qui se passe sous le capot, c'est que, puisque vous avez sélectionné l'entropie croisée binaire comme fonction de perte et que vous n'avez pas spécifié de métrique de précision particulière, Keras en déduit (à tort ...) que vous êtes intéressé par le binary_accuracy, et c'est ce qu'il renvoie - alors qu'en fait vous êtes intéressé par le categorical_accuracy.

Vérifions que c'est bien le cas, en utilisant l' exemple MNIST CNN dans Keras, avec la modification suivante:

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    

Pour remédier à cela, c'est-à-dire utiliser effectivement l'entropie croisée binaire comme fonction de perte (comme je l'ai dit, rien de mal à cela, du moins en principe) tout en obtenant la précision catégorique requise par le problème en question, vous devez demander explicitement categorical_accuracydans le compilation de modèles comme suit:

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

Dans l'exemple MNIST, après l'entraînement, la notation et la prédiction de l'ensemble de test comme je l'ai montré ci-dessus, les deux mesures sont maintenant les mêmes, comme elles devraient l'être:

# 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    

Installation du système:

Python version 3.5.3
Tensorflow version 1.2.1
Keras version 2.0.4

MISE À JOUR : Après mon message, j'ai découvert que ce problème avait déjà été identifié dans cette réponse .

desertnaut
la source
1
Y a-t-il un problème avec l'utilisation loss='categorical_crossentropy', metrics=['categorical_accuracy']de la classification multiclasse? Ce serait mon intuition
NeStack
2
@NeStack Non seulement il n'y a rien de mal, mais c'est la combinaison nominale.
desertnaut
1
D'après ce que vous avez dit, tant que j'utilise loss = 'binary_crossentropy', j'obtiendrai les mêmes retours sans matther J'utilise metrics = 'binary_accuracy' ou metrics = 'precision'?
BioCoder
2
@BioCoder exactement
desertnaut
54

Tout dépend du type de problème de classification auquel vous faites face. Il existe trois catégories principales

  • binaireclassification (deux classes cibles),
  • classification multi-classes (plus de deux cibles exclusives ),
  • classification multi-étiquettes (plus de deux cibles non exclusives ), dans laquelle plusieurs classes cibles peuvent être activées en même temps.

Dans le premier cas, l'entropie croisée binaire doit être utilisée et les cibles doivent être codées en tant que vecteurs à un point.

Dans le second cas, une entropie croisée catégorielle doit être utilisée et les cibles doivent être codées en tant que vecteurs à un point.

Dans le dernier cas, l'entropie croisée binaire doit être utilisée et les cibles doivent être codées en tant que vecteurs one-hot. Chaque neurone de sortie (ou unité) est considéré comme une variable binaire aléatoire distincte, et la perte pour l'ensemble du vecteur de sorties est le produit de la perte de variables binaires uniques. C'est donc le produit de l'entropie croisée binaire pour chaque unité de sortie unique.

L'entropie croisée binaire est définie comme

entrez la description de l'image ici

et l'entropie croisée catégorique est définie comme

entrez la description de l'image ici

cest l'index fonctionnant sur le nombre de classes

Whynote
la source
Votre réponse me semble très vraie, mais ... j'ai essayé de suivre la réponse de @desertnaut et j'ai fait ces tests: avec la fonction de perte binary_crossentropy et metrcis to categorical_accurency, j'ai une meilleure précision qu'en utilisant la fonction de perte categorical_crossentropy et les métriques de précision - et je ne peux pas expliquer que ...
Metal3d
@ Metal3d: quelle est la formulation de votre problème: multi-label ou mono-label?
Whynote
single-label, et maintenant je réalise pourquoi cela fonctionne mieux :)
Metal3d
Êtes-vous sûr que les entropies croisées binaires et catégorielles sont définies comme dans les formules de cette réponse?
nbro
@nbro, en fait, l' cindex est redondant dans la formule d'entropie croisée binaire, il n'a pas besoin d'être là (car il n'y a que 2 classes et la probabilité de chaque classe est intégrée y(x). Sinon, ces formules devraient être correctes, mais remarquez que ce ne sont pas des pertes, ce sont des probabilités. Si vous voulez la perte, vous devez prendre la logde celles-ci.
Whynote
40

Je suis tombé sur un problème "inversé" - j'obtenais de bons résultats avec categorical_crossentropy (avec 2 classes) et médiocres avec binary_crossentropy. Il semble que le problème provienne d'une mauvaise fonction d'activation. Les paramètres corrects étaient:

  • pour binary_crossentropy: activation sigmoïde, cible scalaire
  • pour categorical_crossentropy: activation softmax, cible encodée à chaud
Alexander Svetkin
la source
4
Êtes-vous sûr de la cible scalaire pour binary_crossentropy. Il semble que vous devriez utiliser une cible encodée "many-hot" (par exemple [0 1 0 0 1 1]).
Dmitry
5
Sûr. Voir keras.io/losses/#usage-of-loss-functions , il dit: "lors de l'utilisation de la perte categorical_crossentropy, vos cibles doivent être au format catégorique (par exemple, si vous avez 10 classes, la cible de chaque échantillon doit être un 10 -vecteur dimensionnel entièrement à zéro attendez un 1 à l'index correspondant à la classe de l'échantillon) "
Alexander Svetkin
1
Mais nous parlons de binary_crossentropy - pas de categorical_crossentropy.
Dmitry
Cette réponse semble être incompatible avec stackoverflow.com/a/49175655/3924118 , où l'auteur dit que les cibles devraient être encodées à chaud, tandis que, dans votre réponse, vous suggérez qu'elles devraient être scalaires. Vous devriez clarifier cela.
nbro
@AlexanderSvetkin, la cible devrait être encodée à chaud partout, pas seulement lors de l'utilisation de l'entropie croisée catégorique
Whynote
28

C'est un cas vraiment intéressant. En fait, dans votre configuration, la déclaration suivante est vraie:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

Cela signifie que jusqu'à un facteur de multiplication constant, vos pertes sont équivalentes. Le comportement étrange que vous observez pendant une phase d'entraînement peut être un exemple du phénomène suivant:

  1. Au début, la classe la plus fréquente domine la perte - le réseau apprend donc à prédire principalement cette classe pour chaque exemple.
  2. Après avoir appris le modèle le plus fréquent, il commence à distinguer les classes les moins fréquentes. Mais lorsque vous utilisezadam - le taux d'apprentissage a une valeur beaucoup plus petite que ce qu'il avait au début de la formation (c'est à cause de la nature de cet optimiseur). Cela ralentit la formation et empêche votre réseau, par exemple, de laisser un faible minimum local moins possible.

C'est pourquoi ce facteur constant pourrait aider en cas de binary_crossentropy. Après de nombreuses époques - la valeur du taux d'apprentissage est supérieure à celle du categorical_crossentropycas. Je redémarre généralement la formation (et la phase d'apprentissage) plusieurs fois lorsque je remarque un tel comportement ou / et que j'ajuste les poids d'une classe en utilisant le modèle suivant:

class_weight = 1 / class_frequency

Cela rend la perte d'une classe moins fréquente équilibrant l'influence d'une perte de classe dominante au début d'une formation et dans une autre partie d'un processus d'optimisation.

ÉDITER:

En fait - j'ai vérifié cela même si en cas de maths:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

devrait tenir - au cas où kerasce n'est pas vrai, car il kerasnormalise automatiquement toutes les sorties à résumer 1. C'est la raison réelle de ce comportement étrange car en cas de multiclassification, une telle normalisation nuit à un entraînement.

Marcin Możejko
la source
Ma réponse vous a-t-elle aidé?
Marcin Możejko
1
C'est une explication très plausible. Mais je ne suis pas sûr que ce soit vraiment la raison principale. Parce que j'ai également observé chez plusieurs de mes étudiants ce comportement étrange lors de l'application de binary-X-ent au lieu de cat-X-ent (ce qui est une erreur). Et cela est vrai même lorsque vous vous entraînez pour seulement 2 époques! L'utilisation de class_weight avec des priors de classe inverses n'a pas aidé. Peut-être qu'un réglage rigoureux du taux d'apprentissage aiderait, mais les valeurs par défaut semblent favoriser bin-X-ent. Je pense que cette question mérite plus d'investigations ...
xtof54
1
Attendez, non désolé, je ne reçois pas votre mise à jour: le softmax fait toujours la somme des sorties à 1, donc on s'en fiche? Et pourquoi cette formation nuirait-elle, tant que nous n'avons qu'une seule classe d'or correcte par exemple?
xtof54
20

Après avoir commenté la réponse @Marcin, j'ai vérifié plus attentivement le code d'un de mes élèves où j'ai trouvé le même comportement bizarre, même après seulement 2 époques! (L'explication de @ Marcin n'était donc pas très probable dans mon cas).

Et j'ai trouvé que la réponse est en fait très simple: la précision calculée avec la méthode Keras evaluateest tout simplement fausse lors de l'utilisation de binary_crossentropy avec plus de 2 étiquettes. Vous pouvez le vérifier en recalculant vous-même la précision (appelez d'abord la méthode Keras "prédire" puis calculez le nombre de réponses correctes renvoyées par prédire): vous obtenez la précision vraie, qui est bien inférieure à celle "évaluer" de Keras.

xtof54
la source
1
J'ai également vu un comportement similaire sur la première itération.
dolbi
10

un exemple simple dans un cadre multi-classes pour illustrer

supposons que vous ayez 4 classes (encodées en un instant) et ci-dessous une seule prédiction

true_label = [0,1,0,0] predicted_label = [0,0,1,0]

lorsque vous utilisez categorical_crossentropy, la précision n'est que de 0, cela ne se soucie que de savoir si vous obtenez la classe concernée.

cependant, en utilisant binary_crossentropy, la précision est calculée pour toutes les classes, elle serait de 50% pour cette prédiction. et le résultat final sera la moyenne des précisions individuelles pour les deux cas.

il est recommandé d'utiliser categorical_crossentropy pour les problèmes multi-classes (les classes sont mutuellement exclusives) mais binary_crossentropy pour les problèmes multi-labels.

bazinga
la source
8

Comme il s'agit d'un problème multi-classes, vous devez utiliser la fonction categorical_crossentropy, l'entropie croisée binaire produira de faux résultats et n'évaluera probablement que les deux premières classes uniquement.

50% pour un problème multi-classes peut être assez bon, selon le nombre de classes. Si vous avez n classes, alors 100 / n est la performance minimale que vous pouvez obtenir en sortant une classe aléatoire.

Dr Snoopy
la source
2

lors de l'utilisation de la categorical_crossentropyperte, vos cibles doivent être au format catégorique (par exemple, si vous avez 10 classes, la cible de chaque échantillon doit être un vecteur à 10 dimensions qui est entièrement zéros sauf pour un 1 à l'index correspondant à la classe du échantillon).

Priyansh
la source
3
Comment cela répond exactement à la question?
desertnaut
2

Jetez un œil à l'équation, vous pouvez trouver que l' entropie croisée binaire punit non seulement ceux étiquette = 1, prédit = 0, mais aussi étiquette = 0, prédit = 1.

Cependant, l' entropie croisée catégorique ne punit que les étiquettes = 1 mais prédites = 1, c'est pourquoi nous supposons qu'il n'y a qu'une seule étiquette positive.

Kuang Yan
la source
1

Vous passez un tableau cible de forme (x-dim, y-dim) lors de l'utilisation comme perte categorical_crossentropy. categorical_crossentropys'attend à ce que les cibles soient des matrices binaires (1 et 0) de forme (échantillons, classes). Si vos cibles sont des classes entières, vous pouvez les convertir au format attendu via:

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

Vous pouvez également utiliser la fonction de perte à la sparse_categorical_crossentropyplace, qui attend des cibles entières.

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

Le binary_crossentropy (y_target, y_predict) n'a pas besoin de s'appliquer dans un problème de classification binaire. .

Dans le code source de binary_crossentropy () , la nn.sigmoid_cross_entropy_with_logits(labels=target, logits=output)fonction TensorFlow était en fait utilisée. Et, dans la documentation , il est dit que:

Mesure l'erreur de probabilité dans les tâches de classification discrètes dans lesquelles chaque classe est indépendante et non mutuellement exclusive. Par exemple, on pourrait effectuer une classification multi-étiquettes où une image peut contenir à la fois un éléphant et un chien.

翟志伟
la source