Je pose des questions sur les classes C pour une fonction de perte NLLLoss .
La documentation déclare:
La perte de probabilité log négative. Il est utile de former un problème de classification avec des classes C.
En gros, tout dépend de ce que vous savez ce qu'est une classe C, et je pensais savoir ce qu'était une classe C, mais la documentation n'a pas beaucoup de sens pour moi. Surtout quand il décrit les entrées attendues de (N, C) where C = number of classes
. C'est là que je suis confus, car je pensais qu'une classe C se référait uniquement à la sortie . Ma compréhension était que la classe C était un vecteur brûlant de classifications. J'ai souvent trouvé dans les didacticiels que NLLLoss
était souvent associé à un LogSoftmax
pour résoudre des problèmes de classification.
Je m'attendais à utiliser NLLLoss
dans l'exemple suivant:
# Some random training data
input = torch.randn(5, requires_grad=True)
print(input) # tensor([-1.3533, -1.3074, -1.7906, 0.3113, 0.7982], requires_grad=True)
# Build my NN (here it's just a LogSoftmax)
m = nn.LogSoftmax(dim=0)
# Train my NN with the data
output = m(input)
print(output) # tensor([-2.8079, -2.7619, -3.2451, -1.1432, -0.6564], grad_fn=<LogSoftmaxBackward>)
loss = nn.NLLLoss()
print(loss(output, torch.tensor([1, 0, 0])))
Ce qui précède soulève l'erreur suivante sur la dernière ligne:
ValueError: Attendu 2 dimensions ou plus (obtenu 1)
Nous pouvons ignorer l'erreur, car je ne comprends clairement pas ce que je fais. Ici, je vais expliquer mes intentions du code source ci-dessus.
input = torch.randn(5, requires_grad=True)
Tableau 1D aléatoire à coupler avec un vecteur chaud de [1, 0, 0]
Pour l'entraînement. J'essaye de faire des bits binaires à un vecteur chaud de nombres décimaux.
m = nn.LogSoftmax(dim=0)
La documentation de LogSoftmax
indique que la sortie aura la même forme que l'entrée, mais je n'ai vu que des exemples de LogSoftmax(dim=1)
et j'ai donc été bloqué en essayant de faire fonctionner cela parce que Je ne trouve pas d'exemple relatif.
print(loss(output, torch.tensor([1, 0, 0])))
Alors maintenant, j'ai la sortie du NN, et je veux connaître la perte de ma classification [1, 0, 0]
. La nature des données n'a pas vraiment d'importance dans cet exemple. Je veux juste une perte pour un vecteur chaud qui représente la classification.
À ce stade, je reste bloqué en essayant de résoudre les erreurs de la fonction de perte liées aux structures de sortie et d'entrée attendues. J'ai essayé d'utiliser view(...)
sur la sortie et l'entrée pour corriger la forme, mais cela m'obtient simplement d'autres erreurs.
Cela revient donc à ma question initiale et je vais montrer l'exemple de la documentation pour expliquer ma confusion:
m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
input = torch.randn(3, 5, requires_grad=True)
train = torch.tensor([1, 0, 4])
print('input', input) # input tensor([[...],[...],[...]], requires_grad=True)
output = m(input)
print('train', output, train) # tensor([[...],[...],[...]],grad_fn=<LogSoftmaxBackward>) tensor([1, 0, 4])
x = loss(output, train)
Encore une fois, nous avons dim=1
Sur LogSoftmax
ce qui me trouble maintenant, car regardez les données input
. C'est un tenseur 3x5
Et je suis perdu.
Voici la documentation sur la première entrée de la fonction NLLLoss
:
Entrée: (N, C) (N, C) où C = nombre de classes
Les entrées sont groupées par le nombre de classes?
Ainsi, chaque ligne de l'entrée du tenseur est associée à chaque élément du tenseur d'entraînement?
Si je change la deuxième dimension du tenseur d'entrée, alors rien ne casse et je ne comprends pas ce qui se passe.
input = torch.randn(3, 100, requires_grad=True)
# 3 x 100 still works?
Donc je ne comprends pas ce qu'est une classe C ici, et je pensais qu'une classe C était une classification (comme une étiquette) et significative uniquement sur les sorties du NN.
J'espère que vous comprenez ma confusion, car la forme des entrées pour le NN ne devrait-elle pas être indépendante de la forme du seul vecteur chaud utilisé pour la classification?
Les exemples de code et les documentations indiquent que la forme des entrées est définie par le nombre de classifications, et je ne comprends pas vraiment pourquoi.
J'ai essayé d'étudier les documentations et les tutoriels pour comprendre ce qui me manquait, mais après plusieurs jours sans pouvoir dépasser ce point, j'ai décidé de poser cette question. Cela a été humiliant parce que je pensais que ce serait l'une des choses les plus faciles à apprendre.
Fondamentalement, il vous manque un concept de batch
.
En bref, chaque entrée de perte (et celle qui est passée par le réseau) nécessite une dimension batch
(c'est-à-dire combien d'échantillons sont utilisés).
Rompre, étape par étape:
Chaque étape sera comparée à chaque étape pour la rendre plus claire (documentation en haut, votre exemple ci-dessous)
input = torch.randn(3, 5, requires_grad=True)
input = torch.randn(5, requires_grad=True)
Dans le premier cas (docs), saisissez avec 5
fonctionnalités sont créées et 3
des échantillons sont utilisés. Dans votre cas, il n'y a que la dimension batch
(5
samples), vous n'avez aucune fonctionnalité qui sont obligatoires. Si vous vouliez avoir un échantillon avec 5
fonctionnalités que vous devriez faire:
input = torch.randn(5, requires_grad=True)
LogSoftmax
est effectué à travers la dimension des fonctionnalités, vous le faites à travers le lot.
m = nn.LogSoftmax (dim = 1) # appliquer sur les fonctionnalités m = nn.LogSoftmax (dim = 0) # appliquer sur le lot
Cela n'a généralement aucun sens pour cette opération car les échantillons sont indépendants les uns des autres.
Comme il s'agit d'une classification multiclasse et que chaque élément du vecteur représente un échantillon, on peut passer autant de nombres que l'on veut (tant qu'il est plus petit que le nombre de fonctionnalités, en cas d'exemple de documentation, c'est 5
, Par conséquent [0-4]
c'est bien ).
train = torch.tensor([1, 0, 4])
train = torch.tensor([1, 0, 0])
Je suppose que vous vouliez également passer le vecteur one-hot comme cible. PyTorch ne fonctionne pas de cette façon car c'est mémoire inefficace (pourquoi stocker tout comme encodé à chaud alors que vous pouvez simplement identifier exactement la classe, dans votre cas, ce serait 0
).
Seules les sorties du réseau de neurones sont codées à chaud afin de rétropropropager l'erreur à travers tous les nœuds de sortie, ce n'est pas nécessaire pour les cibles.
Vous ne devriez pas utiliser torch.nn.LogSoftmax
du tout pour cette tâche. Utilisez simplement torch.nn.Linear
comme dernière couche et utilisez torch.nn.CrossEntropyLoss
avec vos cibles.
Je suis d'accord avec vous pour dire que la documentation de nn.NLLLoss()
est loin d'être idéale, mais je pense que nous pouvons clarifier votre problème ici, premièrement, en précisant que "classe" est souvent utilisé comme synonyme de "catégorie" dans un Contexte d'apprentissage automatique.
Par conséquent, lorsque PyTorch parle de classes C
, il se réfère en fait au nombre de catégories distinctes sur lequel vous essayez d'entraîner votre réseau. Ainsi, dans l'exemple classique d'un réseau neuronal catégoriel essayant de classer entre "chats" et "chiens", C = 2
, Puisqu'il s'agit d'un chat ou d'un chien.
Spécifiquement pour ce problème de classification, il soutient également que nous n'avons que ne seule valeur de vérité sur le tableau de nos catégories (une image ne peut pas représenter à la fois un chat ET un chien, mais toujours un seul), ce qui C'est pourquoi nous pouvons commodément indiquer la catégorie correspondante d'une image par son index (disons que 0
indiquerait un chat, et 1
un chien). Maintenant, nous pouvons simplement comparer la sortie réseau à la catégorie que nous voulons.
MAIS, pour que cela fonctionne, nous devons également être clair à quoi ces valeurs de perte font référence (dans notre sortie réseau), car notre réseau fera généralement des prédictions via un softmax sur différents neurones de sortie , ce qui signifie que nous avons généralement plus d'une valeur unique. Heureusement, nn.NLLLoss
De PyTorch le fait automatiquement pour vous.
Votre exemple ci-dessus avec LogSoftmax
ne produit en fait qu'une seule valeur de sortie, ce qui est un cas critique pour cet exemple. De cette façon, vous n'avez fondamentalement qu'une indication de l'existence ou non de quelque chose, mais cela n'a pas beaucoup de sens à utiliser dans un exemple de classification, plus encore dans un cas de régression (mais cela nécessiterait un tout autre fonction de perte pour commencer).
Enfin, vous devez également considérer le fait que nous avons généralement des tenseurs 2D en entrée, car le traitement par lots (le calcul simultané de plusieurs échantillons) est généralement considéré comme une étape nécessaire pour faire correspondre les performances. Même si vous choisissez une taille de lot de 1, cela nécessite toujours que vos entrées soient de dimension (batch_size, input_dimensions)
, Et par conséquent vos tenseurs de sortie de forme (batch_size, number_of_categories)
.
Cela explique pourquoi la plupart des exemples que vous trouvez en ligne exécutent la LogSoftmax()
sur dim=1
, Car il s'agit de "l'axe de distribution" et non de l'axe des lots (qui serait dim=0
).
Si vous voulez simplement résoudre votre problème, le moyen le plus simple serait d'étendre votre tenseur aléatoire d'une dimension supplémentaire (torch.randn([1, 5], requires_grad=True)
), puis de comparer par une seule valeur dans votre tenseur de sortie (print(loss(output, torch.tensor([1]))
)