← Blog
Trois tiges en céramique verte élargies en palettes dans un cadre ivoire, sur fond lavande.
Publié le
2026-11-11

PyTorch nn.Linear : dimensions, poids et exemples

Dans PyTorch, nn.Linear(in_features, out_features) transforme la dernière dimension d’un tenseur vers un nouveau nombre de caractéristiques. La couche apprend une matrice de poids et, par défaut, un biais. Un lot de forme (32, 64) transmis à Linear(64, 10) produit (32, 10) : le constructeur ne reçoit pas la taille du lot.

La difficulté consiste souvent à identifier ce que représente le dernier axe. Un appel peut réussir tout en projetant la largeur d’une image au lieu de ses canaux, ou en conservant des positions de séquence que vous souhaitiez réunir. Ce guide explicite ces choix avec de petites entrées dont les résultats se vérifient à la main.

Les six exemples indépendants ont été testés avec Python 3.12.14 et PyTorch 2.15.0+cpu. Les vérifications associées couvrent les calculs, les dimensions, les paramètres, les erreurs et les gradients. Elles ne mesurent ni les performances GPU ni la qualité d’un modèle. La référence officielle Linear décrit le constructeur et les formes attendues.

Lire la matrice de poids dans le bon sens

Pour des vecteurs d’entrée disposés en lignes, l’opération s’écrit y = x @ weight.T + bias. Ici, Linear(3, 2) possède deux lignes de trois poids. Chaque ligne produit une caractéristique de sortie à partir des trois caractéristiques d’entrée.

import torch

x = torch.tensor([[1., 2., 3.], [-1., 0., 2.]], dtype=torch.float64)
layer = torch.nn.Linear(3, 2, dtype=torch.float64)
with torch.no_grad():
    layer.weight.copy_(torch.tensor([[1., 2., -1.], [0., 0.5, 3.]]))
    layer.bias.copy_(torch.tensor([0.5, -1.]))
y = layer(x)
print('Weight shape:', tuple(layer.weight.shape))
print('Output:', y.tolist())
print('Matrix formula matches:', torch.equal(y, x @ layer.weight.T + layer.bias))
Weight shape: (2, 3)
Output: [[2.5, 9.0], [-2.5, 5.0]]
Matrix formula matches: True

Pour [1, 2, 3], la première sortie vaut 1×1 + 2×2 + 3×(-1) + 0.5 = 2.5. La seconde vaut 1×0 + 2×0.5 + 3×3 - 1 = 9. Les mêmes poids appliqués à [-1, 0, 2] donnent [-2.5, 5].

La matrice stockée a pour forme (out_features, in_features) : la transposition compte dans cette expression avec des vecteurs-lignes. Les résultats montrent aussi que Linear ne convertit pas les scores en probabilités. Une valeur peut être négative ou dépasser un. Choisissez l’activation ou la fonction de perte selon la tâche, sans ajouter automatiquement softmax.

Nous affectons des paramètres connus sous no_grad() pour rendre le calcul reproductible. Cette affectation ne les fige pas : le module conserve des paramètres entraînables. Un test distinct compare sa sortie à functional.linear avec les mêmes entrées, poids et biais.

Séparer les dimensions de lot et de séquence

La couche transforme la dernière dimension et conserve les précédentes. Les poids égaux à un dans cet exemple font de chaque caractéristique de sortie la somme des trois caractéristiques d’entrée :

import torch

layer = torch.nn.Linear(3, 2, dtype=torch.float64)
with torch.no_grad():
    layer.weight.fill_(1.)
    layer.bias.zero_()
vector = torch.tensor([1., 2., 3.], dtype=torch.float64)
batch = torch.arange(12, dtype=torch.float64).reshape(4, 3)
sequence = torch.arange(24, dtype=torch.float64).reshape(2, 4, 3)
print('Vector:', tuple(layer(vector).shape))
print('Batch:', tuple(layer(batch).shape))
print('Sequence:', tuple(layer(sequence).shape))
print('First sequence:', layer(sequence)[0].tolist())
Vector: (2,)
Batch: (4, 2)
Sequence: (2, 4, 2)
First sequence: [[3.0, 3.0], [12.0, 12.0], [21.0, 21.0], [30.0, 30.0]]

Le vecteur sans dimension de lot produit [6, 6]. Le lot de quatre lignes produit quatre vecteurs à deux caractéristiques. Pour la séquence (2, 4, 3), le résultat (2, 4, 2) conserve les deux éléments du lot et les quatre positions. La couche ne moyenne pas les positions, n’aplatit pas la séquence et ne mélange pas les éléments du lot.

À la première position, [0, 1, 2] donne une somme de 3. À la suivante, [3, 4, 5] donne 12. Les deux caractéristiques de sortie coïncident parce que leurs lignes de poids sont volontairement identiques. Dans un modèle entraîné, elles peuvent apprendre des projections différentes.

Nos tests modifient aussi uniquement le deuxième élément du lot et vérifient que la première sortie reste inchangée. Une entrée non contiguë obtenue en échangeant les axes initiaux produit le résultat réordonné correspondant. Ces contrôles vérifient le rôle des axes au-delà de leur seule taille.

Compter les paramètres et comprendre bias=False

Dans cette couche à trois entrées et deux sorties, six poids et deux valeurs de biais donnent huit scalaires entraînables. Sans biais, il en reste six :

import torch

with_bias = torch.nn.Linear(3, 2)
without_bias = torch.nn.Linear(3, 2, bias=False)
print('With bias:', sum(p.numel() for p in with_bias.parameters()))
print('Without bias:', sum(p.numel() for p in without_bias.parameters()))
print('Absent bias:', without_bias.bias)
print('Saved parameter names:', list(with_bias.state_dict()))
print('No-bias parameter names:', list(without_bias.state_dict()))
With bias: 8
Without bias: 6
Absent bias: None
Saved parameter names: ['weight', 'bias']
No-bias parameter names: ['weight']

Le nombre général est in_features × out_features, auquel s’ajoute out_features si le biais est activé. Augmenter la taille du lot ou la longueur de séquence n’ajoute aucun paramètre : la même couche s’applique à chaque position des axes précédents.

Sans biais, une entrée nulle produit une sortie nulle. Avec biais, elle renvoie le vecteur de biais. Nous vérifions les deux cas. Les paramètres sont initialisés automatiquement : une nouvelle couche ne reproduira pas le premier exemple sans affectation de ces valeurs ou chargement de paramètres correspondants.

Les entrées du state_dict montrent les paramètres sauvegardés pour cette couche. Leur chargement exige aussi une définition de modèle compatible. Notre guide de sauvegarde et de chargement PyTorch, en anglais, traite des checkpoints et de la reprise de l’entraînement. Conservez les réglages d’architecture, notamment les nombres de caractéristiques, avec le checkpoint.

Choisir ce que la couche projette dans une image

Considérons des images organisées en (N, C, H, W), de forme (2, 3, 2, 4). Linear(4, 5) projette la largeur, puisqu’elle occupe le dernier axe. Pour obtenir un vecteur par image, aplatissez d’abord les axes autres que le lot. Pour projeter les canaux à chaque pixel, placez-les en dernier avant d’appliquer la couche.

import torch

images = torch.arange(48, dtype=torch.float64).reshape(2, 3, 2, 4)
width_layer = torch.nn.Linear(4, 5, dtype=torch.float64)
image_layer = torch.nn.Linear(24, 5, dtype=torch.float64)
channel_layer = torch.nn.Linear(3, 5, dtype=torch.float64)
print('Width projection:', tuple(width_layer(images).shape))
flat = torch.flatten(images, start_dim=1)
print('Flattened input:', tuple(flat.shape))
print('Whole-image projection:', tuple(image_layer(flat).shape))
channels_last = images.permute(0, 2, 3, 1)
projected = channel_layer(channels_last).permute(0, 3, 1, 2)
print('Per-pixel channel projection:', tuple(projected.shape))
Width projection: (2, 3, 2, 5)
Flattened input: (2, 24)
Whole-image projection: (2, 5)
Per-pixel channel projection: (2, 5, 2, 4)

Il s’agit de trois opérations différentes. La projection de largeur garde les canaux et la hauteur séparés. La projection de l’image entière réunit 3×2×4 = 24 entrées en un vecteur par image. La projection des canaux combine les trois valeurs de canal indépendamment aux huit positions spatiales, puis rétablit les canaux en deuxième position.

Notre test calcule un pixel projeté par multiplications et additions scalaires, puis le compare au résultat tensoriel. Il confirme aussi que l’aplatissement conserve dans l’ordre les 24 valeurs de la première image.

La référence flatten explique start_dim. Ici, start_dim=1 conserve l’axe du lot. Partir de zéro réunirait plusieurs images dans le même vecteur. Un reshape ne remplace pas non plus le déplacement explicite de l’axe des canaux.

Diagnostiquer les erreurs courantes de Linear

Examinez la forme, le dtype et le périphérique de l’entrée avec ceux des paramètres de la couche. Ces cas enfreignent volontairement la taille finale attendue, le type flottant ou le nombre minimal d’axes :

import torch

layer = torch.nn.Linear(3, 2, dtype=torch.float64)
cases = {
    'Wrong final dimension': torch.zeros(2, 4, dtype=torch.float64),
    'Wrong dtype': torch.zeros(2, 3, dtype=torch.float32),
    'Scalar input': torch.tensor(1., dtype=torch.float64),
}
for name, value in cases.items():
    try:
        layer(value)
    except RuntimeError:
        print(name + ': RuntimeError')
Wrong final dimension: RuntimeError
Wrong dtype: RuntimeError
Scalar input: RuntimeError

La première entrée finit par quatre valeurs alors que la couche en attend trois. La deuxième utilise float32 face à des paramètres float64, dans un appel ordinaire sans autocast. La troisième n’a aucun axe de caractéristiques. Les trois déclenchent RuntimeError dans la version testée. L’exemple affiche la classe de l’exception, car le message détaillé peut changer entre versions.

Une erreur de dimensions dans la multiplication matricielle doit conduire à examiner le sens des axes, pas à modifier in_features jusqu’à ce que le programme fonctionne. Déduisez le nombre de caractéristiques de l’opération précédente. Pour les images, choisissez entre les trois projections ci-dessus avant de fixer la taille de la couche.

En cas de dtype incompatible, convertissez les entrées vers le type flottant voulu ou construisez le modèle de façon cohérente. Sur GPU, les entrées et les paramètres doivent également utiliser des périphériques compatibles. Ces exemples s’exécutent sur CPU et ne testent ni CUDA ni la précision mixte.

Vérifier les gradients séparément du mode d’évaluation

Le dernier exemple additionne les deux sorties et effectue une rétropropagation. Ses poids fixes permettent de prévoir chaque dérivée :

import torch

layer = torch.nn.Linear(3, 2, dtype=torch.float64)
with torch.no_grad():
    layer.weight.copy_(torch.tensor([[1., 2., -1.], [0., 0.5, 3.]]))
    layer.bias.copy_(torch.tensor([0.5, -1.]))
x = torch.tensor([1., 2., 3.], dtype=torch.float64, requires_grad=True)
train_output = layer(x)
layer.eval()
y = layer(x)
y.sum().backward()
print('Train/eval outputs equal:', torch.equal(train_output, y))
print('Input gradient:', x.grad.tolist())
print('Weight gradient:', layer.weight.grad.tolist())
print('Bias gradient:', layer.bias.grad.tolist())
with torch.no_grad():
    inference_output = layer(x)
print('no_grad output requires_grad:', inference_output.requires_grad)
Train/eval outputs equal: True
Input gradient: [1.0, 2.5, 2.0]
Weight gradient: [[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]]
Bias gradient: [1.0, 1.0]
no_grad output requires_grad: False

Le gradient d’entrée [1, 2.5, 2] est la somme des deux lignes de poids. Chaque ligne de poids reçoit [1, 2, 3], le vecteur d’entrée, et chaque biais reçoit un gradient de un. Le code de vérification compare aussi le gradient d’entrée à une différence finie centrée, calculée indépendamment avec des opérations scalaires Python.

La couche Linear isolée produit les mêmes sorties en modes train et eval lorsque ses paramètres restent identiques. L’appel eval() ne désactive pas le suivi des gradients, comme le montre la rétropropagation. L’appel ultérieur sous no_grad() produit une sortie qui ne demande pas de gradients. D’autres modules d’un modèle complet peuvent changer de comportement entre entraînement et évaluation ; consultez notre guide de eval, no_grad et inference_mode, en anglais.

Avant d’intégrer Linear à un modèle plus grand, nommez les axes d’entrée, vérifiez la forme de sortie attendue et contrôlez un petit exemple numérique. Ces étapes permettent de repérer une projection accidentelle avant de mener un entraînement complet sur des données mal interprétées.

Votre prochaine charge de travail sur Hivenet.

Choisissez une charge de travail d’IA, de calcul ou de stockage à tester sur Hivenet, ou échangez avec notre équipe sur sa mise en production.