Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 75

Tensor Operations in PyTorch

Recommended resources

Bonjour ! Dans la leçon précédente, vous avez découvert les tenseurs, les briques de construction fondamentales de PyTorch. Vous avez appris à les créer, à inspecter leurs attributs comme la forme et le type de données, et à les déplacer entre le CPU et le GPU.

Aujourd'hui, nous allons mettre ces tenseurs au travail. L'objectif de cette leçon est de vous apprendre à effectuer des opérations mathématiques sur les tenseurs PyTorch. Ces opérations sont au cœur de ce qui se passe à l'intérieur d'un réseau de neurones, depuis la propagation des données (forward pass) jusqu'à la mise à jour des poids du modèle. Nous aborderons les opérations de base, la multiplication matricielle, le concept puissant de broadcasting, et les opérations qui résument les données d'un tenseur.

1. Opérations arithmétiques élément par élément (Element-wise)

Les opérations les plus simples que vous pouvez effectuer sont les opérations arithmétiques de base : addition, soustraction, multiplication et division. Lorsque les tenseurs ont la même forme, ces opérations sont appliquées élément par élément. Cela signifie que chaque élément du premier tenseur est associé à l'élément à la même position dans le second tenseur.

La vidéo suivante vous montrera comment cela fonctionne de manière très pratique.

Tensor Math Operations - Deep Learning with PyTorch 4

Cette vidéo offre une introduction claire et directe aux opérations arithmétiques de base sur les tenseurs.

Regardez les sections suivantes pour voir les opérations en action : Création des tenseurs pour l'exemple. Addition : Vous verrez comment 1+5 et 2+6 sont calculés. Soustraction. Multiplication. Division. Notez qu'il existe deux façons de faire chaque opération : avec un opérateur standard (+, *) ou avec une fonction PyTorch (torch.add(), torch.mul()).

Comme vous l'avez vu, c'est assez intuitif. Voici un résumé en code :

import torch




# Créons deux tenseurs de même forme
a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[5, 6], [7, 8]])




# Addition
sum_tensor = a + b
print(f"Addition:\n{sum_tensor}\n")



# tensor([[ 6,  8],
#         [10, 12]])




# Multiplication élément par élément (Hadamard product)
elementwise_mul = a * b
print(f"Multiplication élément par élément:\n{elementwise_mul}\n")



# tensor([[ 5, 12],
#         [21, 32]])

Vous pouvez également effectuer ces opérations entre un tenseur et un scalaire (un seul nombre). PyTorch applique alors l'opération du scalaire à chaque élément du tenseur.




# Multiplication par un scalaire
scalar_mul = a * 10
print(f"Multiplication par un scalaire:\n{scalar_mul}\n")



# tensor([[10, 20],
#         [30, 40]])

2. Le point crucial : Multiplication Matricielle

C'est l'une des distinctions les plus importantes à maîtriser. L'opérateur * effectue une multiplication élément par élément. Cependant, en deep learning (et en algèbre linéaire), l'opération la plus courante est la multiplication matricielle. Votre expérience en mathématiques vous rappelle que pour multiplier deux matrices de formes et , le nombre de colonnes de la première doit être égal au nombre de lignes de la seconde, et le résultat aura une forme de .

Dans PyTorch, la multiplication matricielle se fait avec la fonction torch.matmul() ou, de manière plus idiomatique, avec l'opérateur @.




# Tenseurs de formes compatibles pour la multiplication matricielle (2,2) @ (2,2)
a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[5, 6], [7, 8]])




# Multiplication matricielle
matrix_mul = torch.matmul(a, b)



# Ou, de manière plus courante :
matrix_mul_at = a @ b

print(f"Résultat de la multiplication matricielle (a @ b):\n{matrix_mul_at}\n")



# tensor([[19, 22],
#         [43, 50]])




# Comparez cela à la multiplication élément par élément que nous avons vue plus tôt :
print(f"Rappel de la multiplication élément par élément (a * b):\n{a * b}")



# tensor([[ 5, 12],
#         [21, 32]])

Ne jamais confondre * et @ est une compétence fondamentale. Presque tous les calculs dans les couches d'un réseau de neurones impliquent des multiplications matricielles (@) pour transformer les données d'entrée à l'aide des poids du modèle.


3. Broadcasting : Opérations sur des formes compatibles

Que se passe-t-il si vous essayez d'effectuer une opération sur deux tenseurs dont les formes ne sont pas identiques ? Parfois, cela échoue, mais souvent, PyTorch peut utiliser une astuce puissante appelée broadcasting.

Le broadcasting permet à PyTorch d'étendre virtuellement le tenseur le plus petit pour qu'il corresponde à la forme du plus grand, rendant ainsi l'opération élément par élément possible. Un cas d'usage classique est l'ajout d'un vecteur de biais à une matrice.

La vidéo officielle de PyTorch explique très bien ce mécanisme.

Introduction to PyTorch Tensors

Cette section de la vidéo est consacrée au broadcasting.

Regardez attentivement la partie qui explique le broadcasting. Elle expose les règles qui le régissent et donne des exemples clairs de cas où il fonctionne et où il échoue.

Les règles du broadcasting, comme expliquées dans la vidéo, sont les suivantes (comparaison des dimensions de droite à gauche) :

  1. Les dimensions sont égales, OU
  2. L'une des dimensions est 1, OU
  3. Une dimension n'existe pas dans l'un des tenseurs.

Voici l'exemple classique de l'ajout d'un vecteur de biais :




# Supposons que `x` est une batch de 3 "samples", chacun ayant 4 features
x = torch.rand(3, 4)




# `bias` est un vecteur qui doit être ajouté à chaque sample
bias = torch.tensor([0.1, 0.2, 0.3, 0.4])




# Formes : x.shape = (3, 4), bias.shape = (4,) ou (1, 4)
# Règle : En comparant de droite à gauche :
# - La dernière dimension est 4 pour les deux. C'est compatible.
# - La dimension suivante de x est 3, mais elle n'existe pas dans `bias`. `bias` est "broadcasté" (copié) sur cette dimension.

y = x + bias
print(f"x:\n{x}\n")
print(f"bias:\n{bias}\n")
print(f"Résultat après broadcasting (y):\n{y}")

Le broadcasting est extrêmement efficace car il évite de créer manuellement des copies des données en mémoire, tout en rendant le code plus concis.


4. Opérations de Réduction

Souvent, vous voudrez agréger les informations contenues dans un tenseur. Les opérations de réduction réduisent le nombre d'éléments d'un tenseur, généralement pour en calculer une valeur statistique. Les plus courantes sont sum(), mean(), max(), et min().

L'article suivant explique cela de manière très claire.

PyTorch Basic Tensor Operations

Cet article fournit des exemples concis pour les opérations de réduction.

Lisez la section Reduction Operations. Portez une attention particulière à l'argument dim, qui est essentiel.

Comme vous l'avez lu, vous pouvez soit réduire le tenseur entier à une seule valeur, soit le réduire le long d'une dimension spécifique.

t = torch.tensor([[1., 2., 3.], [4., 5., 6.]])
print(f"Tenseur original:\n{t}\n")




# Réduction sur l'ensemble du tenseur
total_sum = t.sum()
print(f"Somme totale: {total_sum}\n") # tensor(21.)




# Réduction le long d'une dimension
# dim=0 réduit les lignes (calcule la somme de chaque colonne)
sum_dim0 = t.sum(dim=0)
print(f"Somme le long de dim=0:\n{sum_dim0}\n") # tensor([5., 7., 9.])




# dim=1 réduit les colonnes (calcule la somme de chaque ligne)
sum_dim1 = t.sum(dim=1)
print(f"Somme le long de dim=1:\n{sum_dim1}")   # tensor([ 6., 15.])

L'argument dim est crucial. Comprendre comment l'utiliser est fondamental pour des opérations comme le calcul de la perte moyenne sur un batch de données.


5. Une note sur les opérations "in-place"

La plupart des opérations que nous avons vues créent un nouveau tenseur en mémoire pour stocker le résultat. PyTorch propose également des versions "in-place" de nombreuses opérations, qui modifient le tenseur directement. On les reconnaît à leur suffixe _.

a = torch.ones(2, 2)
b = torch.ones(2, 2)




# Opération standard : `a` n'est pas modifié
c = a.add(b)
print(f"a après a.add(b) : \n{a}") # a est toujours rempli de 1




# Opération in-place : `a` est modifié
a.add_(b) # Notez le `_`
print(f"a après a.add_(b) : \n{a}") # a est maintenant rempli de 2

Bien qu'elles puissent économiser de la mémoire, les opérations in-place doivent être utilisées avec prudence. Elles peuvent poser des problèmes avec le calcul automatique des gradients (que nous verrons bientôt) si la valeur originale du tenseur était nécessaire ailleurs. Pour commencer, il est souvent plus sûr d'utiliser les opérations standards qui retournent de nouveaux tenseurs.


Conclusion

Vous maîtrisez maintenant les outils pour effectuer les calculs numériques qui animent les réseaux de neurones. C'est un grand pas en avant !

Points clés à retenir :

  • Les opérateurs arithmétiques comme * effectuent des opérations élément par élément.
  • La multiplication matricielle, cruciale en deep learning, se fait avec @ ou torch.matmul().
  • Le broadcasting est un mécanisme puissant qui permet des opérations sur des tenseurs de formes différentes mais compatibles.
  • Les opérations de réduction (sum, mean, etc.) agrègent les données d'un tenseur, et l'argument dim permet de contrôler l'axe de réduction.
  • Les opérations in-place (avec un _) modifient les tenseurs directement, ce qui peut être efficace mais nécessite de la prudence.

Dans la prochaine leçon, nous allons découvrir le deuxième super-pouvoir des tenseurs PyTorch : autograd. Vous verrez comment PyTorch utilise la chaîne d'opérations que vous venez d'apprendre pour calculer automatiquement les gradients, le moteur de l'apprentissage des modèles.