
Conv2d de PyTorch applique des filtres appris sur la hauteur et la largeur d’une image. Pour un lot, l’entrée a la forme (N, C, H, W) : images, canaux, hauteur, puis largeur. La sortie conserve la taille du lot, utilise le nombre de canaux choisi et possède des dimensions spatiales déterminées par le noyau, le pas, le padding et la dilatation.
Ce guide présente cinq petits exemples sur CPU avec des poids fixes. Nous avons exécuté 28 contrôles avec Python 3.12.14 et PyTorch 2.15.0+cpu, dont un calcul indépendant de chaque valeur de sortie des exemples de dimensions. Chaque bloc Python fonctionne indépendamment avec PyTorch installé, sans fichiers image, modèle préentraîné ni jeu de données.
La référence de Conv2d décrit les paramètres. Ici, une image à un seul canal mesure trois pixels de haut et quatre de large. Ses valeurs vont de 1 à 12. Le filtre mesure deux lignes sur trois colonnes :
import torch
x = torch.arange(1, 13, dtype=torch.float32).reshape(1, 1, 3, 4)
layer = torch.nn.Conv2d(1, 1, kernel_size=(2, 3), bias=False)
with torch.no_grad():
layer.weight.copy_(torch.tensor([[[[1., 0., -1.], [1., 0., -1.]]]]))
y = layer(x)
print('Input:', tuple(x.shape))
print('Output:', tuple(y.shape))
print('Values:', y[0, 0].detach().tolist())
Sortie observée :
Input: (1, 1, 3, 4)
Output: (1, 1, 2, 2)
Values: [[-4.0, -4.0], [-4.0, -4.0]]
La zone en haut à gauche contient les lignes [1, 2, 3] et [5, 6, 7]. La multiplication par le filtre donne (1 - 3) + (5 - 7) = -4. Les déplacements vers la droite, puis vers le bas, donnent les trois autres sorties. Sans padding, le filtre tient à deux positions verticales et deux positions horizontales.
Nous avons volontairement fixé les coefficients et désactivé le biais. Un entraînement apprendrait normalement ces coefficients. L’opération utilise le filtre dans l’ordre affiché, sans le retourner : il s’agit d’une corrélation croisée au sens mathématique. Ces valeurs montrent le calcul, sans constituer un détecteur d’images entraîné.
Un traitement d’images couleur peut fournir des tableaux de forme (N, H, W, C), avec trois valeurs de canal réunies à chaque pixel. Conv2d attend l’axe des canaux avant la hauteur et la largeur. Utilisez permute pour réorganiser ces axes.
Notre lot synthétique contient deux images de quatre pixels de haut et cinq de large, avec trois valeurs par pixel :
import torch
# Two images: height 4, width 5, three values at each pixel.
raw = torch.arange(120, dtype=torch.float32).reshape(2, 4, 5, 3)
x = raw.permute(0, 3, 1, 2)
layer = torch.nn.Conv2d(3, 2, kernel_size=(2, 3), bias=False)
with torch.no_grad():
layer.weight.fill_(1.)
y = layer(x)
print('Input:', tuple(x.shape))
print('First channel, first row:', x[0, 0, 0].tolist())
print('Weight:', tuple(layer.weight.shape))
print('Output:', tuple(y.shape))
print('First output row:', y[0, 0, 0].detach().tolist())
Sortie observée :
Input: (2, 3, 4, 5)
First channel, first row: [0.0, 3.0, 6.0, 9.0, 12.0]
Weight: (2, 3, 2, 3)
Output: (2, 2, 3, 3)
First output row: [207.0, 261.0, 315.0]
La première ligne du premier canal reste [0, 3, 6, 9, 12]. Lors du contrôle, un reshape vers les mêmes dimensions cibles produisait d’autres valeurs. Il ne remplace donc pas cette permutation.
Les poids du filtre ont la forme (2, 3, 2, 3) : deux canaux de sortie, trois canaux d’entrée par filtre, puis hauteur et largeur du noyau. La première sortie additionne 18 valeurs sur les trois plans d’entrée et la zone de deux lignes sur trois colonnes, soit 207. Les deux canaux de sortie sont identiques parce que tous leurs poids ont reçu la même valeur.
Les canaux de sortie sont des cartes de caractéristiques apprises. En choisir deux ne transforme pas une image RGB en image à deux couleurs. Vérifiez également ce que renvoie déjà votre chargeur : permuter un lot déjà organisé en NCHW changerait le sens de ses axes. Notre guide PyTorch DataLoader, en anglais traite de la constitution des lots.
Pour un lot d’images en niveaux de gris de forme (N, H, W), unsqueeze(1) ajoute un axe exprimant un canal par image. Ne supposez pas que tout tenseur à trois dimensions est un lot : notre contrôle distinct a confirmé que Conv2d accepte aussi une entrée sans lot de forme (C, H, W).
Pour un padding entier appliqué symétriquement sur chaque axe spatial, calculez :
H_out = floor((H_in + 2p_h - d_h(k_h - 1) - 1) / s_h) + 1
W_out = floor((W_in + 2p_w - d_w(k_w - 1) - 1) / s_w) + 1
Le premier élément d’une paire contrôle la hauteur, le second la largeur. Une valeur scalaire s’applique aux deux. Pour une entrée de 5 × 7, un noyau de 3 × 3, un pas (2, 3), aucun padding et une dilatation de 1, la sortie mesure 2 × 2.
L’exemple suivant modifie un réglage spatial à la fois, puis associe un noyau rectangulaire à des réglages différents sur les deux axes :
import torch
x = torch.arange(1, 36, dtype=torch.float32).reshape(1, 1, 5, 7)
cases = [
('valid', dict(kernel_size=3)),
('pad 1', dict(kernel_size=3, padding=1)),
('stride (2, 3)', dict(kernel_size=3, stride=(2, 3))),
('dilation (2, 1)', dict(kernel_size=3, dilation=(2, 1))),
('rectangle', dict(kernel_size=(2, 3), stride=(2, 1), padding=(0, 1))),
]
for label, options in cases:
layer = torch.nn.Conv2d(1, 1, bias=False, **options)
with torch.no_grad():
layer.weight.fill_(1.)
y = layer(x)
print(label, tuple(y.shape), 'top-left:', y[0, 0, 0, 0].item())
Sortie observée :
valid (1, 1, 3, 5) top-left: 81.0
pad 1 (1, 1, 5, 7) top-left: 20.0
stride (2, 3) (1, 1, 2, 2) top-left: 81.0
dilation (2, 1) (1, 1, 1, 5) top-left: 144.0
rectangle (1, 1, 2, 7) top-left: 20.0
Avec un padding de 1, la fenêtre en haut à gauche lit des zéros hors de l’image. Seules les valeurs 1, 2, 8 et 9 contribuent, soit 20. Avec une dilatation (2, 1), la première fenêtre lit les lignes 0, 2 et 4 en conservant des colonnes adjacentes ; sa somme vaut 144. Le noyau possède toujours neuf coefficients, mais ils couvrent une zone plus haute.
Dans le cas rectangulaire, padding=(0, 1) n’ajoute aucune ligne en haut ou en bas, et ajoute une colonne à chaque bord horizontal. C’est une paire hauteur/largeur, pas gauche/droite. La sortie compte deux lignes et sept colonnes. Nous avons vérifié tous les tableaux de sortie des cinq cas avec un calcul distinct à boucles imbriquées, et pas uniquement les valeurs du coin affichées.
Un autre contrôle a montré que padding='same' conserve hauteur et largeur avec un pas de 1, tandis que sa construction avec un pas de 2 provoque une erreur dans la version testée. Un test supplémentaire a rejeté un noyau plus large que l’image sans padding. Augmenter les canaux de sortie ne permet pas de faire tenir une fenêtre spatiale trop grande.
Ce lot contient une image à deux canaux. Les valeurs du second plan valent dix fois celles du premier. Comparez la connexion de tous les canaux à leur séparation en deux groupes :
import torch
plane = torch.arange(1, 10, dtype=torch.float32).reshape(1, 1, 3, 3)
x = torch.cat([plane, 10 * plane], dim=1)
for groups in (1, 2):
layer = torch.nn.Conv2d(2, 2, kernel_size=2, groups=groups, bias=False)
with torch.no_grad():
layer.weight.fill_(1.)
print('Groups:', groups, 'Weight:', tuple(layer.weight.shape))
print('Values:', layer(x)[0].detach().tolist())
Sortie observée :
Groups: 1 Weight: (2, 2, 2, 2)
Values: [[[132.0, 176.0], [264.0, 308.0]], [[132.0, 176.0], [264.0, 308.0]]]
Groups: 2 Weight: (2, 1, 2, 2)
Values: [[[12.0, 16.0], [24.0, 28.0]], [[120.0, 160.0], [240.0, 280.0]]]
La première zone de deux pixels sur deux vaut 12 dans le premier canal et 120 dans le second. Avec groups=1, les deux filtres de sortie additionnent ces contributions, soit 132. Avec groups=2, une sortie lit le premier canal et l’autre le second.
La couche groupée possède ici huit coefficients au lieu de seize. Nous avons également testé un multiplicateur depthwise de deux avec Conv2d(2, 4, 2, groups=2) : chaque plan d’entrée a produit deux sorties séparées. Les nombres de canaux non divisibles par le nombre de groupes ont été rejetés. Moins de coefficients ne prouve pas un gain de vitesse ; nous n’avons pas mesuré les performances de ces couches.
Le format mémoire channels_last change la disposition des éléments en mémoire. Il ne change ni le sens ni l’ordre des dimensions renvoyées par shape. Le tutoriel officiel sur le format mémoire explique cette distinction.
Ici, un filtre de un pixel sur un rend également la somme des canaux par pixel facile à vérifier :
import torch
x = torch.arange(120, dtype=torch.float32).reshape(2, 3, 4, 5)
x_cl = x.contiguous(memory_format=torch.channels_last)
layer = torch.nn.Conv2d(3, 2, kernel_size=1, bias=False)
with torch.no_grad():
layer.weight.fill_(1.)
print('Shapes:', tuple(x.shape), tuple(x_cl.shape))
print('Strides:', x.stride(), x_cl.stride())
print('Same values:', torch.equal(x, x_cl))
print('Same output:', torch.equal(layer(x), layer(x_cl)))
Sortie observée :
Shapes: (2, 3, 4, 5) (2, 3, 4, 5)
Strides: (60, 20, 5, 1) (60, 1, 15, 3)
Same values: True
Same output: True
Les deux tenseurs affichent toujours (2, 3, 4, 5). Leurs strides diffèrent, mais les valeurs indexées et les sorties de convolution sont identiques. Dans notre contrôle supplémentaire, la première ligne de sortie valait [60, 63, 66, 69, 72], la somme des trois canaux à chaque pixel. Un noyau de un sur un peut mélanger les canaux sans lire les pixels voisins.
Ne permutez pas un tenseur NCHW vers NHWC simplement pour obtenir un stockage channels-last. Choisissez d’abord l’ordre logique des axes. Examinez ensuite le format mémoire et mesurez son effet éventuel dans votre environnement. Cette démonstration sur CPU établit la conservation des valeurs, pas une accélération sur GPU.
Nous avons aussi testé une rétropropagation à travers un filtre de deux sur deux rempli de 1, sur une entrée de trois sur trois. Additionner les quatre sorties a produit les gradients d’entrée [[1, 2, 1], [2, 4, 2], [1, 2, 1]]. Le pixel central participe à quatre fenêtres. Ce résultat vérifie le passage des gradients dans cet exemple, sans établir de convergence sur une tâche d’image.
Une fois ces mécanismes corrects, choisissez le reste du modèle selon la tâche. Notre guide des architectures de réseaux de neurones, en anglais traite de ce choix. Les exemples utilisent des tenseurs float32 sur CPU et ne testent ni la qualité du prétraitement des images, ni la précision du modèle, ni la précision mixte, ni CUDA, ni le débit. L’environnement a émis un avertissement lié à l’absence de NumPy ; les contrôles utilisant uniquement des tenseurs ont abouti sans cette bibliothèque.
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.