\n\n\n\n Mon modèle d'IA a obtenu NaN dans la perte : voici comment je l'ai corrigé - AiDebug \n

Mon modèle d’IA a obtenu NaN dans la perte : voici comment je l’ai corrigé

📖 13 min read2,567 wordsUpdated Mar 27, 2026

Salut tout le monde, Morgan ici, de retour avec un autre exploration des détails de l’IA. Aujourd’hui, nous parlons de quelque chose sur lequel j’ai passé plus d’heures que je ne veux l’admettre : l’odieux “NaN in Loss” erreur. Ce n’est pas juste un avertissement ; c’est un problème qui arrête tout, un genre de choc tête-sur-bureau qui peut plonger votre modèle d’IA parfaitement conçu dans un abîme d’inutilité. Et croyez-moi, j’y ai été, fixant mon terminal à 3 heures du matin, me demandant pourquoi mon modèle avait décidé de se self-détruire.

Pour ceux qui ne sont pas familiers, “NaN” signifie “Not a Number.” Quand cela apparaît dans votre fonction de perte pendant l’entraînement, cela signifie que votre modèle essaie de calculer quelque chose qui est mathématiquement indéfini. Pensez à la division par zéro, à la prise du logarithme d’un nombre négatif, ou à un débordement qui casse tout. C’est particulièrement insidieux car cela commence souvent subtilement, peut-être après quelques époques, ou parfois, c’est là dès le premier lot. Et une fois que ça y est, ça a tendance à se propager, empoisonnant tous les calculs suivants.

J’ai récemment heurté ce mur avec un nouveau réseau antagoniste génératif (GAN) que je construisais pour un client dans le domaine de l’art numérique. L’objectif était de générer des œuvres d’art abstraites uniques basées sur une image de départ. Tout semblait bien en théorie – une architecture solide, des données propres, un GPU qui ronronne. Puis, boom. Époque 5, lot 12 : loss_discriminator: nan, loss_generator: nan. Mon cœur s’est enfoncé. Je savais que je devais chercher en profondeur, et ce qui a suivi a été un marathon de débogage de plusieurs jours. Alors, parlons de la façon dont j’ai abordé cela, et espérons que cela vous évitera un peu de douleur.

Le Travail de Détective NaN : Où Commencer sa Recherche

Quand vous voyez NaN dans votre perte, votre première pensée pourrait être de paniquer. Ne le faites pas. Prenez une respiration. C’est un problème courant, et il existe une méthode systématique pour y faire face. Pensez à vous comme à un détective, réunissant les indices.

1. Vérification de la Santé des Données : Le Fondement de Tout

Ma première étape est toujours les données. Ça semble évident, mais vous seriez surpris de voir à quelle fréquence un problème subtil dans vos données d’entrée peut se manifester sous forme de NaN. Pour mon projet GAN, je traitais des données d’image, ce qui signifiait vérifier les valeurs des pixels.

  • Valeurs Manquantes/Données Corrompues : Y a-t-il des NaNs ou des infinis déjà présents dans vos caractéristiques d’entrée ou vos étiquettes ? Même un seul NaN peut torpiller votre entraînement. J’ai une fois eu un CSV où quelques lignes avaient été corrompues lors d’un transfert, introduisant des NaNs qui n’apparaissaient qu’après quelques étapes de traitement.
  • Problèmes de Mise à l’Échelle/Normalisation : Vos caractéristiques sont-elles correctement mises à l’échelle ? Si vous avez des valeurs extrêmement grandes ou petites, ou une énorme plage dynamique, cela peut conduire à une instabilité numérique. Pour les images, je m’assure toujours que les valeurs des pixels sont dans une plage raisonnable, typiquement 0-1 ou -1 à 1. Si vous utilisez `torchvision.transforms.Normalize`, vérifiez bien vos valeurs de moyenne et d’écart-type. Des valeurs incorrectes ici peuvent déplacer vos données dans des plages problématiques.
  • One-Hot Encoding Mal Fait : Si vous utilisez l’encodage one-hot pour des données catégorielles, assurez-vous qu’il n’y a pas de vecteurs tout-zéro là où il ne devrait pas y en avoir, ou des valeurs en dehors de 0 et 1.

Pour mon GAN, j’ai inspecté manuellement quelques lots de données d’image traitées, imprimant les valeurs min/max et vérifiant les NaNs. Tout semblait propre, alors j’ai continué.


# Vérification simple des NaNs dans un tenseur PyTorch
if torch.isnan(my_tensor).any():
 print("NaN détecté dans le tenseur !")

# Vérification des infinis
if torch.isinf(my_tensor).any():
 print("Infini détecté dans le tenseur !")

# Imprimer min/max pour repérer rapidement des plages inhabituelles
print(f"Valeur min : {my_tensor.min().item()}, Valeur max : {my_tensor.max().item()}")

2. Chaos du Taux d’Apprentissage et de l’Optimiseur

C’est souvent le coupable, en particulier avec des modèles plus complexes. Un taux d’apprentissage excessivement élevé peut faire exploser les poids de votre modèle, entraînant des NaNs. Pensez-y comme essayer de faire de grands bonds en bas d’une colline – vous êtes plus susceptibles de trébucher et de tomber spectaculairement que d’atteindre le bas gracieusement.

  • Taux d’Apprentissage Trop Élevé : C’est probablement la raison la plus courante. Si votre taux d’apprentissage est trop agressif, votre optimiseur peut prendre des mesures trop grandes, dépassant les valeurs optimales et provoquant une croissance incontrôlable des poids. Pour les GANs, où vous avez deux réseaux concurrents, c’est encore plus critique. J’ai commencé mon GAN avec un taux d’apprentissage de 1e-4, ce que je pensais raisonnable, mais il s’est avéré trop élevé pour les étapes initiales du générateur.
  • Choix de l’Optimiseur : Certains optimisateurs sont plus sensibles à l’instabilité numérique que d’autres. Adam est généralement solide, mais si vous utilisez quelque chose comme le SGD standard avec un taux d’apprentissage élevé, vous pourriez rencontrer des problèmes plus rapidement.
  • Coupe de Gradient : C’est un sauveur. La coupe de gradient empêche les gradients de dépasser un certain seuil, maîtrisant efficacement les gradients explosifs avant qu’ils ne puissent provoquer des NaNs. Je recommande toujours d’ajouter cela comme une stratégie d’atténuation précoce, surtout pour les réseaux de neurones récurrents ou les GANs.

Dans mon scénario GAN, je soupçonnais le taux d’apprentissage. Mes expériences initiales n’avaient pas montré ce problème, mais la nouvelle architecture légèrement plus profonde pourrait avoir été plus sensible. J’ai réduit le taux d’apprentissage pour le discriminateur et le générateur d’un facteur de 10 (à 1e-5), et bien que les NaNs n’aient pas disparu immédiatement, ils ont commencé à apparaître plus tard dans l’entraînement, ce qui était un indice.


# Exemple de coupe de gradient dans PyTorch
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # Couper les gradients à une norme maximale de 1.0

3. Fonctions d’Activation et Fonctions de Perte

Ceci sont des suspects principaux pour l’instabilité mathématique. Certaines opérations peuvent produire des NaNs si leurs entrées ne sont pas dans une plage valide.

  • Logarithmes de Nombres Non-Positifs : C’est un classique. Si vous utilisez torch.log ou F.log_softmax, assurez-vous que l’entrée du logarithme est toujours strictement positive. Si elle atteint zéro ou devient négative, vous obtenez un NaN. J’ai vu cela arriver avec des probabilités qui devenaient accidentellement zéro en raison d’un sous-débordement numérique ou de prédictions très confiantes. Un correctif courant est d’ajouter un petit epsilon (par exemple, 1e-8) à l’entrée de la fonction log : torch.log(x + 1e-8).
  • Division par Zéro : Similaire aux logarithmes, la division par zéro produit immédiatement un NaN. Vérifiez toutes les fonctions de perte personnalisées ou les couches de normalisation où la division pourrait se produire.
  • Activations Explosives : Certaines fonctions d’activation, si alimentées avec des entrées extrêmement grandes, peuvent produire des nombres très grands qui entraînent ensuite des problèmes en aval. Bien que moins courant avec les ReLUs ou les Sigmoïdes standard, cela vaut la peine d’y penser si vous utilisez des activations personnalisées.
  • Inadéquation de la Fonction de Perte : Utilisez-vous la bonne fonction de perte pour votre tâche ? Par exemple, utiliser la Binary Cross Entropy Loss pour une classification multi-classe sans encodage one-hot approprié et activations sigmoïdes (au lieu de softmax) peut entraîner des problèmes numériques.

Pour mon GAN, la sortie du générateur passait par une activation tanh pour produire des images dans la plage de -1 à 1. La sortie du discriminateur, représentant les probabilités réelles/faux, passait par une sigmoïde, puis la Binary Cross Entropy Loss était appliquée. J’ai vérifié les entrées de la perte BCE, m’assurant qu’elles étaient des probabilités entre 0 et 1. Ici, j’ai trouvé un problème subtil : parfois, en raison de la nature agressive de l’entraînement du GAN, les sorties brutes (logits) du discriminateur pouvaient devenir des nombres extrêmement grands positifs ou négatifs avant la sigmoïde, entraînant des valeurs très proches de 0 ou 1. Bien que la sigmoïde gère cela, si la fonction de perte essaie ensuite de prendre log(0), vous êtes en difficulté.

La solution ici était d’utiliser torch.nn.BCEWithLogitsLoss. Cette fonction de perte est numériquement plus stable car elle combine l’activation sigmoïde et la perte BCE en une seule opération, travaillant directement sur les logits bruts plutôt que sur les probabilités. Cela évite d’éventuels problèmes de précision lorsque les probabilités se rapprochent extrêmement de 0 ou 1.


# Mauvais :
# prob = torch.sigmoid(logits)
# loss = F.binary_cross_entropy(prob, targets)

# Bon :
loss = F.binary_cross_entropy_with_logits(logits, targets)

4. Initialisation des Poids

Cela est souvent négligé, mais une mauvaise initialisation des poids peut préparer votre modèle à l’échec dès le départ. Si vos poids sont initialisés trop grands, ils peuvent provoquer des gradients explosifs dès le premier passage avant. S’ils sont trop petits, vous pourriez faire face à des gradients évanescents, ce qui peut aussi indirectement entraîner des NaNs si certaines parties de votre réseau deviennent complètement “mortes.”

Les schémas d’initialisation standard comme Kaiming (pour ReLU) ou Xavier (pour tanh/sigmoid) sont généralement de bons points de départ. Si vous construisez une couche ou un modèle personnalisé à partir de zéro, assurez-vous de ne pas initialiser avec des zéros ou des valeurs aléatoires extrêmement grandes.

5. Surveillance des Gradients : L’Ultime Outil de Diagnostic

C’est ici que j’ai finalement localisé le problème central dans mon GAN. Je ne regardais pas seulement la perte ; je jetais un coup d’œil sous le capot aux gradients eux-mêmes. Si vous soupçonnez des gradients explosifs, surveiller leurs magnitudes peut le confirmer.

J’ai ajouté des hooks à mon modèle pour imprimer les valeurs absolues moyennes et maximales des gradients pour chaque couche *avant* l’étape de l’optimiseur. Ce que j’ai observé était terrifiant : pour certaines couches du générateur, les gradients passaient soudainement de chiffres raisonnables (par exemple, 1e-3 à 1e-1) à des valeurs telles que 1e5 ou même 1e10 après seulement quelques batches, juste avant que le NaN n’apparaisse dans la perte.

Cela a confirmé que le générateur connaissait effectivement des gradients explosifs. Combiné à l’observation que réduire le taux d’apprentissage aidait à retarder les NaNs, cela pointait vers un problème d’instabilité numérique pendant l’étape de mise à jour du générateur.


# Exemple d'un hook simple de surveillance des gradients en PyTorch
def hook_fn(module, grad_input, grad_output):
 # grad_output est le gradient de la perte par rapport à la sortie du module
 if grad_output[0] is not None:
 print(f"Module : {module.__class__.__name__}, Grad Max : {grad_output[0].abs().max().item():.4f}")
 print(f"Module : {module.__class__.__name__}, Grad Mean : {grad_output[0].abs().mean().item():.4f}")

# Attacher des hooks à des couches spécifiques
for name, module in generator.named_modules():
 if isinstance(module, (torch.nn.Conv2d, torch.nn.Linear)): # Ou d'autres couches que vous souhaitez surveiller
 module.register_backward_hook(hook_fn)

# Effectuer un passage avant/arrière unique et observer la sortie

La Solution pour Mon GAN : Une Approche Multi-Facettes

Finalement, résoudre le problème de NaN dans mon GAN a nécessité une combinaison des stratégies ci-dessus :

  1. Changement vers BCEWithLogitsLoss : Cela a immédiatement stabilisé le calcul de la perte pour le discriminateur et le générateur en évitant les problèmes potentiels de log(0).
  2. Réduction des Taux d’Apprentissage : J’ai diminué progressivement les taux d’apprentissage pour le générateur et le discriminateur. J’ai trouvé qu’un taux d’apprentissage de 5e-6 pour le générateur et de 2e-5 pour le discriminateur fonctionnait le mieux pour mon architecture spécifique.
  3. Gradient Clipping (Générateur Seulement) : Même avec des taux d’apprentissage réduits, le générateur avait encore occasionnellement des pics de gradient. Appliquer torch.nn.utils.clip_grad_norm_ aux paramètres du générateur après son passage arrière, avec un max_norm=1.0, a finalement mis un terme aux gradients explosifs et, par conséquent, aux NaNs.
  4. Epsilon de Normalisation par Lots : J’ai également veillé à ce que le paramètre eps dans mes couches de Normalisation par Lots soit fixé à une valeur petite et raisonnable (la valeur par défaut de 1e-5 est généralement acceptable, mais il vaut la peine de vérifier). Bien que ce ne soit pas la cause directe des NaNs ici, un epsilon trop petit peut entraîner une division par zéro si la variance du lot devient nulle.

Après ces changements, mon GAN s’est entraîné merveilleusement bien. Les courbes de perte étaient lisses, les gradients restaient dans une plage saine, et les œuvres d’art générées commençaient à avoir une apparence étonnamment bonne. C’était un immense soulagement et un rappel que le débogage des modèles d’IA est souvent une question d’élimination systématique et de compréhension des subtilités numériques en jeu.

Points à Retenir

Si vous êtes confronté à une erreur de « NaN dans la Perte », voici votre liste de contrôle :

  • Vérifiez vos données minutieusement : NaNs, infinis, valeurs extrêmes.
  • Ajustez votre taux d’apprentissage : Commencez petit et augmentez progressivement.
  • Considérez le clipping des gradients : Surtout pour les modèles complexes ou ceux susceptibles d’instabilité.
  • Vérifiez votre fonction de perte : Utilisez des versions numériquement stables (par exemple, BCEWithLogitsLoss). Ajoutez un epsilon aux logarithmes.
  • Surveillez les gradients : C’est un outil de diagnostic puissant pour confirmer les gradients explosifs ou naufragés.
  • Inspectez les poids : Assurez-vous d’une initialisation sensée et vérifiez s’il y a de grands changements pendant l’entraînement.
  • Normalisation par Lots eps : Un petit mais important détail.
  • Reproductibilité : Essayez de restreindre le problème en vous entraînant sur un plus petit sous-ensemble de données, ou même un seul lot, pour voir si le NaN apparaît toujours.

Le débogage de l’IA n’est jamais glamour, mais c’est une compétence essentielle. L’erreur « NaN dans la Perte » est un rite de passage pour de nombreux développeurs d’IA. En travaillant systématiquement à travers ces étapes, vous ne résoudrez pas seulement votre problème actuel, mais vous obtiendrez également une compréhension plus profonde du fonctionnement interne de votre modèle. Bon débogage, et que vos pertes soient toujours des nombres !

🕒 Published:

✍️
Written by Jake Chen

AI technology writer and researcher.

Learn more →
Browse Topics: ci-cd | debugging | error-handling | qa | testing
Scroll to Top