Tensors et Opérations en PyTorch : Maîtriser les Fondations
Dans l’article précédent, nous avons installé PyTorch et créé nos premiers tensors. Maintenant, nous allons plonger en profondeur dans les tensors et leurs opérations, car ce sont les briques fondamentales de tout ce que vous ferez avec PyTorch. Comprendre les tensors est essentiel pour construire des réseaux de neurones efficaces.
Qu’est-ce qu’un Tensor ?
Un tensor est une structure de données multidimensionnelle qui peut contenir des nombres. C’est la généralisation des scalaires (0D), vecteurs (1D), matrices (2D) à N dimensions.
import torch
# Scalaire (0D tensor)
scalar = torch.tensor(42)
print(f"Scalaire : {scalar}, shape : {scalar.shape}") # shape : torch.Size([])
# Vecteur (1D tensor)
vector = torch.tensor([1, 2, 3, 4])
print(f"Vecteur : {vector}, shape : {vector.shape}") # shape : torch.Size([4])
# Matrice (2D tensor)
matrix = torch.tensor([[1, 2], [3, 4], [5, 6]])
print(f"Matrice :\n{matrix}\nshape : {matrix.shape}") # shape : torch.Size([3, 2])
# Tensor 3D (par exemple : batch d'images)
tensor_3d = torch.randn(32, 3, 224, 224) # 32 images RGB de 224x224
print(f"Tensor 3D shape : {tensor_3d.shape}") # torch.Size([32, 3, 224, 224])

Création de Tensors
PyTorch offre de nombreuses méthodes pour créer des tensors selon vos besoins.
Méthodes de création directe
import torch
# À partir d'une liste Python
t1 = torch.tensor([1, 2, 3])
print(f"torch.tensor() : {t1}")
# Spécifier le dtype
t2 = torch.tensor([1, 2, 3], dtype=torch.float32)
print(f"Avec dtype float32 : {t2}")
# Tensor 2D
t3 = torch.tensor([[1, 2], [3, 4], [5, 6]])
print(f"Tensor 2D :\n{t3}")
Tensors remplis de valeurs spécifiques
# Tensor de zéros
zeros = torch.zeros(3, 4) # 3 lignes, 4 colonnes
print(f"Zeros shape {zeros.shape} :\n{zeros}")
# Tensor de uns
ones = torch.ones(2, 3)
print(f"Ones shape {ones.shape} :\n{ones}")
# Tensor rempli d'une valeur spécifique
sevens = torch.full((3, 3), 7)
print(f"Filled with 7 :\n{sevens}")
# Tensor identité
identity = torch.eye(4) # Matrice identité 4x4
print(f"Identity matrix :\n{identity}")
Tensors aléatoires
# Distribution normale (moyenne=0, std=1)
normal = torch.randn(3, 3)
print(f"Normal distribution :\n{normal}")
# Distribution uniforme [0, 1)
uniform = torch.rand(2, 4)
print(f"Uniform [0, 1) :\n{uniform}")
# Entiers aléatoires
random_ints = torch.randint(0, 10, (3, 3)) # Entre 0 et 10
print(f"Random integers [0, 10) :\n{random_ints}")
Séquences et ranges
# Range (comme Python range)
r1 = torch.arange(0, 10) # 0 à 9
print(f"arange(0, 10) : {r1}")
# Range avec step
r2 = torch.arange(0, 10, 2) # 0, 2, 4, 6, 8
print(f"arange(0, 10, 2) : {r2}")
# Linspace (N valeurs équidistantes)
lin = torch.linspace(0, 1, 5) # 5 valeurs de 0 à 1
print(f"linspace(0, 1, 5) : {lin}")
# Logspace (échelle logarithmique)
log = torch.logspace(0, 3, 4) # 10^0, 10^1, 10^2, 10^3
print(f"logspace(0, 3, 4) : {log}")
Créer à partir de tensors existants
x = torch.tensor([[1, 2], [3, 4]])
# Même shape, rempli de zéros
zeros_like = torch.zeros_like(x)
print(f"zeros_like :\n{zeros_like}")
# Même shape, rempli de uns
ones_like = torch.ones_like(x)
print(f"ones_like :\n{ones_like}")
# Même shape, valeurs aléatoires
rand_like = torch.rand_like(x, dtype=torch.float32)
print(f"rand_like :\n{rand_like}")
Conversion depuis NumPy
import numpy as np
# NumPy array → PyTorch tensor
np_array = np.array([[1, 2], [3, 4]])
torch_tensor = torch.from_numpy(np_array)
print(f"From NumPy :\n{torch_tensor}")
# PyTorch tensor → NumPy array
back_to_numpy = torch_tensor.numpy()
print(f"Back to NumPy :\n{back_to_numpy}")
Attention :
torch.from_numpy() partage la mémoire avec l’array NumPy d’origine. Modifier l’un modifie l’autre ! Utilisez .clone() pour créer une copie indépendante : torch.from_numpy(np_array).clone()Attributs des tensors
Chaque tensor PyTorch possède plusieurs attributs importants :
x = torch.randn(3, 4, 5)
# Shape (dimensions)
print(f"Shape : {x.shape}") # torch.Size([3, 4, 5])
print(f"Size : {x.size()}") # Identique à shape
print(f"Size dim 0 : {x.size(0)}") # Taille de la dimension 0 : 3
# Nombre total d'éléments
print(f"Nombre d'éléments : {x.numel()}") # 3 × 4 × 5 = 60
# Nombre de dimensions
print(f"Dimensions : {x.ndim}") # 3
# Type de données
print(f"Dtype : {x.dtype}") # torch.float32
# Device (CPU ou GPU)
print(f"Device : {x.device}") # cpu ou cuda:0
# Gradient activé ?
print(f"Requires grad : {x.requires_grad}") # False par défaut
Types de données (dtype)
PyTorch supporte plusieurs types de données :
# Entiers
int8 = torch.tensor([1, 2, 3], dtype=torch.int8) # 8-bit
int16 = torch.tensor([1, 2, 3], dtype=torch.int16) # 16-bit
int32 = torch.tensor([1, 2, 3], dtype=torch.int32) # 32-bit (default)
int64 = torch.tensor([1, 2, 3], dtype=torch.int64) # 64-bit (long)
# Flottants
float16 = torch.randn(3, dtype=torch.float16) # Half precision
float32 = torch.randn(3, dtype=torch.float32) # Single precision (default)
float64 = torch.randn(3, dtype=torch.float64) # Double precision
# Booléens
bool_tensor = torch.tensor([True, False, True], dtype=torch.bool)
# Conversion de type
x = torch.randn(3)
x_int = x.int() # Convertir en int32
x_long = x.long() # Convertir en int64
x_float = x.float() # Convertir en float32
x_double = x.double() # Convertir en float64
Performance : Par défaut, PyTorch utilise
float32 pour les calculs. Pour économiser mémoire GPU, vous pouvez utiliser float16 (half precision) avec une perte de précision minime. Pour plus de précision (calculs scientifiques), utilisez float64.Opérations mathématiques
PyTorch supporte toutes les opérations mathématiques classiques.
Opérations élément par élément
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
# Addition
c = a + b # ou torch.add(a, b)
print(f"Addition : {c}") # [5., 7., 9.]
# Soustraction
d = a - b # ou torch.sub(a, b)
print(f"Soustraction : {d}") # [-3., -3., -3.]
# Multiplication
e = a * b # ou torch.mul(a, b)
print(f"Multiplication : {e}") # [4., 10., 18.]
# Division
f = a / b # ou torch.div(a, b)
print(f"Division : {f}") # [0.25, 0.4, 0.5]
# Puissance
g = a ** 2 # ou torch.pow(a, 2)
print(f"Puissance : {g}") # [1., 4., 9.]
# Racine carrée
h = torch.sqrt(a)
print(f"Sqrt : {h}") # [1., 1.414, 1.732]
Opérations matricielles
# Matrices 2D
A = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
B = torch.tensor([[5.0, 6.0], [7.0, 8.0]])
# Multiplication matricielle (dot product)
C = A @ B # ou torch.matmul(A, B) ou torch.mm(A, B)
print(f"Matmul :\n{C}")
# [[19., 22.],
# [43., 50.]]
# Transpose
A_T = A.T # ou A.transpose(0, 1)
print(f"Transpose :\n{A_T}")
# Produit scalaire (dot product pour vecteurs 1D)
v1 = torch.tensor([1.0, 2.0, 3.0])
v2 = torch.tensor([4.0, 5.0, 6.0])
dot = torch.dot(v1, v2)
print(f"Dot product : {dot}") # 1*4 + 2*5 + 3*6 = 32
# Batch matrix multiplication (3D)
batch1 = torch.randn(10, 3, 4) # 10 matrices de 3x4
batch2 = torch.randn(10, 4, 5) # 10 matrices de 4x5
result = torch.bmm(batch1, batch2) # 10 matrices de 3x5
print(f"Batch matmul shape : {result.shape}") # torch.Size([10, 3, 5])
Opérations de réduction
x = torch.tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
# Somme de tous les éléments
total = x.sum()
print(f"Somme totale : {total}") # 21.0
# Somme par dimension
sum_dim0 = x.sum(dim=0) # Somme sur les lignes → [5., 7., 9.]
sum_dim1 = x.sum(dim=1) # Somme sur les colonnes → [6., 15.]
print(f"Sum dim=0 : {sum_dim0}")
print(f"Sum dim=1 : {sum_dim1}")
# Moyenne
mean_all = x.mean()
mean_dim0 = x.mean(dim=0)
print(f"Moyenne totale : {mean_all}") # 3.5
print(f"Moyenne dim=0 : {mean_dim0}") # [2.5, 3.5, 4.5]
# Maximum et minimum
max_val = x.max()
min_val = x.min()
print(f"Max : {max_val}, Min : {min_val}") # 6.0, 1.0
# Max/Min avec indices
max_val, max_idx = x.max(dim=1)
print(f"Max par ligne : {max_val}") # [3., 6.]
print(f"Indices : {max_idx}") # [2, 2]
# Produit de tous les éléments
prod = x.prod()
print(f"Produit : {prod}") # 720.0
# Norme (L2)
norm = torch.norm(x)
print(f"Norme L2 : {norm}") # √(1² + 2² + ... + 6²) = 9.539
Opérations in-place
Les opérations se terminant par _ modifient le tensor en place (économie mémoire) :
x = torch.tensor([1.0, 2.0, 3.0])
# Opération normale (crée un nouveau tensor)
y = x.add(5)
print(f"x après add : {x}") # [1., 2., 3.] (inchangé)
print(f"y : {y}") # [6., 7., 8.]
# Opération in-place (modifie x)
x.add_(5)
print(f"x après add_ : {x}") # [6., 7., 8.] (modifié)
# Autres exemples
x.mul_(2) # x = x * 2
x.div_(4) # x = x / 4
x.sqrt_() # x = sqrt(x)
Attention avec in-place : Les opérations in-place (
_) économisent de la mémoire mais peuvent causer des problèmes avec autograd. Évitez-les sur des tensors qui ont requires_grad=True durant la backpropagation !Broadcasting : Opérations entre shapes différentes
Le broadcasting permet d’effectuer des opérations entre tensors de shapes différentes en étendant automatiquement les dimensions.
Règles de broadcasting
PyTorch suit ces règles (similaires à NumPy) :
- Aligner les shapes à droite
- Les dimensions de taille 1 sont étendues pour correspondre à l’autre dimension
- Si une dimension est manquante, elle est considérée comme 1
# Exemple 1 : Vecteur + Scalaire
a = torch.tensor([1, 2, 3]) # shape: (3,)
b = torch.tensor(10) # shape: ()
c = a + b # Broadcasting: b devient [10, 10, 10]
print(c) # [11, 12, 13]
# Exemple 2 : Matrice + Vecteur
a = torch.randn(3, 4) # shape: (3, 4)
b = torch.randn(4) # shape: (4,)
c = a + b # Broadcasting: b devient (1, 4) puis (3, 4)
print(f"Matrice + Vecteur shape : {c.shape}") # (3, 4)
# Exemple 3 : Broadcasting complexe
a = torch.randn(8, 1, 6, 1) # shape: (8, 1, 6, 1)
b = torch.randn(7, 1, 5) # shape: (7, 1, 5)
c = a + b # Result shape: (8, 7, 6, 5)
print(f"Broadcasting complexe : {c.shape}")
Exemples pratiques de broadcasting
# Normaliser un batch d'images
batch_images = torch.randn(32, 3, 224, 224) # 32 images RGB
mean = torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)
normalized = (batch_images - mean) / std
print(f"Images normalisées shape : {normalized.shape}") # (32, 3, 224, 224)
# Ajouter un biais à chaque colonne
matrix = torch.randn(100, 10) # 100 exemples, 10 features
bias = torch.randn(10) # Un biais par feature
result = matrix + bias # Broadcasting automatique
print(f"Matrice + biais shape : {result.shape}") # (100, 10)
Indexing et slicing
PyTorch utilise une syntaxe d’indexing similaire à NumPy.
Indexing de base
x = torch.arange(0, 24).reshape(4, 6) # Matrice 4x6
print(f"Matrice :\n{x}")
# Accéder à un élément
print(f"x[0, 0] = {x[0, 0]}") # Premier élément : 0
print(f"x[2, 3] = {x[2, 3]}") # Ligne 2, colonne 3 : 15
# Accéder à une ligne entière
print(f"Première ligne : {x[0]}") # [0, 1, 2, 3, 4, 5]
print(f"Dernière ligne : {x[-1]}") # [18, 19, 20, 21, 22, 23]
# Accéder à une colonne
print(f"Première colonne : {x[:, 0]}") # [0, 6, 12, 18]
Slicing avancé
x = torch.arange(0, 24).reshape(4, 6)
# Sous-matrice
sub = x[1:3, 2:5] # Lignes 1-2, colonnes 2-4
print(f"Sous-matrice :\n{sub}")
# [[ 8, 9, 10],
# [14, 15, 16]]
# Avec step
every_other = x[::2, ::2] # Une ligne sur 2, une colonne sur 2
print(f"Every other :\n{every_other}")
# Slicing négatif
last_two_rows = x[-2:, :] # 2 dernières lignes
print(f"2 dernières lignes :\n{last_two_rows}")
Boolean masking
x = torch.randn(4, 4)
print(f"Matrice originale :\n{x}")
# Créer un masque booléen
mask = x > 0
print(f"Masque (x > 0) :\n{mask}")
# Sélectionner les éléments positifs
positives = x[mask]
print(f"Éléments positifs : {positives}")
# Modifier les éléments selon masque
x[x < 0] = 0 # Mettre tous les négatifs à zéro (ReLU!)
print(f"Après ReLU :\n{x}")
# Masque combiné
mask_combined = (x > 0.5) & (x < 1.0)
selected = x[mask_combined]
print(f"Éléments entre 0.5 et 1.0 : {selected}")
Indexing avancé
x = torch.arange(0, 24).reshape(4, 6)
# Sélectionner des lignes spécifiques
rows = torch.tensor([0, 2, 3])
selected_rows = x[rows]
print(f"Lignes 0, 2, 3 :\n{selected_rows}")
# Indexing multidimensionnel
row_indices = torch.tensor([0, 1, 2])
col_indices = torch.tensor([1, 2, 3])
elements = x[row_indices, col_indices] # (0,1), (1,2), (2,3)
print(f"Éléments sélectionnés : {elements}")
# where (comme np.where)
condition = x > 10
result = torch.where(condition, x, torch.tensor(0))
print(f"Where (>10) :\n{result}") # Valeurs >10, sinon 0
Reshaping et manipulation de dimensions
Reshape et view
x = torch.arange(0, 12)
print(f"Original : {x.shape}") # torch.Size([12])
# Reshape en matrice 3x4
reshaped = x.reshape(3, 4)
print(f"Reshaped 3x4 :\n{reshaped}")
# View (similaire mais avec contraintes mémoire)
viewed = x.view(2, 6)
print(f"Viewed 2x6 :\n{viewed}")
# -1 pour dimension automatique
auto = x.reshape(3, -1) # 3 lignes, colonnes automatique
print(f"Auto reshape (3, -1) : {auto.shape}") # (3, 4)
view() vs reshape() :
view() nécessite que le tensor soit contigü en mémoire et retourne une vue (pas de copie). reshape() fonctionne toujours mais peut faire une copie si nécessaire. Préférez reshape() sauf si vous voulez garantir aucune copie.Squeeze et unsqueeze
# Unsqueeze : Ajouter une dimension
x = torch.tensor([1, 2, 3]) # shape: (3,)
x_unsqueezed = x.unsqueeze(0) # shape: (1, 3)
print(f"Unsqueeze dim 0 : {x_unsqueezed.shape}")
x_unsqueezed = x.unsqueeze(1) # shape: (3, 1)
print(f"Unsqueeze dim 1 : {x_unsqueezed.shape}")
# Squeeze : Retirer les dimensions de taille 1
x = torch.randn(1, 3, 1, 5) # shape: (1, 3, 1, 5)
squeezed = x.squeeze() # Retire toutes les dims de taille 1
print(f"Squeezed : {squeezed.shape}") # (3, 5)
# Squeeze une dimension spécifique
squeezed_0 = x.squeeze(0) # Retire dim 0
print(f"Squeeze dim 0 : {squeezed_0.shape}") # (3, 1, 5)
Transpose et permute
# Transpose : Échanger 2 dimensions
x = torch.randn(3, 4, 5)
transposed = x.transpose(0, 1) # Échanger dims 0 et 1
print(f"Transpose (0, 1) : {transposed.shape}") # (4, 3, 5)
# T : Transpose pour 2D
matrix = torch.randn(3, 4)
matrix_T = matrix.T # ou matrix.transpose(0, 1)
print(f"Matrix transpose : {matrix_T.shape}") # (4, 3)
# Permute : Réorganiser toutes les dimensions
x = torch.randn(2, 3, 4, 5) # (batch, channels, height, width)
permuted = x.permute(0, 2, 3, 1) # (batch, height, width, channels)
print(f"Permuted : {permuted.shape}") # (2, 4, 5, 3)
Flatten et concatenation
# Flatten : Aplatir en 1D
x = torch.randn(3, 4, 5)
flat = x.flatten()
print(f"Flattened : {flat.shape}") # (60,) = 3*4*5
# Flatten à partir d'une dimension
flat_partial = x.flatten(start_dim=1)
print(f"Flatten from dim 1 : {flat_partial.shape}") # (3, 20)
# Concatenation
a = torch.randn(2, 3)
b = torch.randn(2, 3)
c_dim0 = torch.cat([a, b], dim=0) # Concat sur lignes
c_dim1 = torch.cat([a, b], dim=1) # Concat sur colonnes
print(f"Cat dim=0 : {c_dim0.shape}") # (4, 3)
print(f"Cat dim=1 : {c_dim1.shape}") # (2, 6)
# Stack : Ajoute une nouvelle dimension
stacked = torch.stack([a, b], dim=0)
print(f"Stacked : {stacked.shape}") # (2, 2, 3)
# Split : Diviser un tensor
x = torch.randn(10, 5)
chunks = torch.split(x, 3, dim=0) # Diviser en chunks de taille 3
print(f"Nombre de chunks : {len(chunks)}") # 4 chunks (3+3+3+1)
Autograd : Différentiation automatique en profondeur
L’autograd est le cœur de PyTorch. Il calcule automatiquement les gradients pour la backpropagation.
Activer le tracking des gradients
# Créer un tensor avec gradient tracking
x = torch.tensor([2.0, 3.0], requires_grad=True)
print(f"x : {x}")
print(f"requires_grad : {x.requires_grad}") # True
# Opérations sont trackées
y = x ** 2 + 2 * x + 1
print(f"y : {y}")
print(f"y.requires_grad : {y.requires_grad}") # True (hérité)
# Calculer les gradients
y.backward(torch.tensor([1.0, 1.0])) # Gradient de y par rapport à x
print(f"x.grad : {x.grad}") # dy/dx = 2x + 2 = [6., 8.]
Exemple complet : Régression linéaire manuelle
import torch
# Données : y = 3x + 2 + bruit
x_data = torch.randn(100, 1)
y_data = 3 * x_data + 2 + torch.randn(100, 1) * 0.1
# Paramètres à apprendre
w = torch.randn(1, 1, requires_grad=True)
b = torch.randn(1, requires_grad=True)
# Hyperparamètres
learning_rate = 0.01
epochs = 100
for epoch in range(epochs):
# Forward pass
y_pred = x_data @ w + b # Prédiction
# Loss (Mean Squared Error)
loss = ((y_pred - y_data) ** 2).mean()
# Backward pass
loss.backward() # Calcule les gradients
# Update des paramètres (gradient descent)
with torch.no_grad(): # Désactiver autograd temporairement
w -= learning_rate * w.grad
b -= learning_rate * b.grad
# Reset gradients
w.grad.zero_()
b.grad.zero_()
if (epoch + 1) % 10 == 0:
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}, w: {w.item():.2f}, b: {b.item():.2f}")
print(f"\nParamètres finaux : w = {w.item():.2f}, b = {b.item():.2f}")
print(f"Valeurs attendues : w = 3.00, b = 2.00")
Contrôler autograd
# Désactiver temporairement autograd
x = torch.randn(3, requires_grad=True)
with torch.no_grad():
y = x * 2 # Cette opération n'est pas trackée
print(f"y.requires_grad : {y.requires_grad}") # False
# Detach : Créer une copie sans gradient
x = torch.randn(3, requires_grad=True)
y = x.detach() # y est une copie sans gradient tracking
print(f"x.requires_grad : {x.requires_grad}") # True
print(f"y.requires_grad : {y.requires_grad}") # False
# Activer/désactiver globally (pour inférence)
torch.set_grad_enabled(False) # Désactiver
# ... inférence ...
torch.set_grad_enabled(True) # Réactiver
torch.no_grad() ou @torch.inference_mode() pendant l’inférence (prédiction). Cela désactive autograd, économise mémoire et accélère les calculs de 20-30%.Graphe de calcul
Autograd construit un graphe de calcul dynamique (DAG - Directed Acyclic Graph) :
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 # y = x²
z = 2 * y + 3 # z = 2x² + 3
# Le graphe : x → y → z
# Chaque opération est un nœud
z.backward() # Calcule dz/dx via la règle de la chaîne
print(f"dz/dx = {x.grad}") # dz/dx = dz/dy * dy/dx = 2 * 2x = 4x = 8.0
Accumuler les gradients
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
# Premier backward
y1 = (x ** 2).sum()
y1.backward()
print(f"Gradient après 1er backward : {x.grad}") # [2., 4., 6.]
# Deuxième backward (gradients s'accumulent!)
y2 = (x ** 3).sum()
y2.backward()
print(f"Gradient après 2ème backward : {x.grad}") # [2.+3., 4.+12., 6.+27.]
# Pour éviter l'accumulation, reset les gradients
x.grad.zero_()
print(f"Après zero_() : {x.grad}") # [0., 0., 0.]
Toujours reset les gradients : Entre chaque itération d’entraînement, appelez
optimizer.zero_grad() ou tensor.grad.zero_(). Sinon, les gradients s’accumulent et faussent l’entraînement !Exercices pratiques
Maintenant à vous de pratiquer ! Essayez ces exercices :
Normalisation min-max
Implémentez la normalisation min-max pour ramener un tensor dans [0, 1] :
def min_max_normalize(x):
"""
Normalise x dans [0, 1] : (x - min) / (max - min)
"""
# Votre code ici
pass
# Test
x = torch.tensor([1.0, 5.0, 3.0, 8.0, 2.0])
normalized = min_max_normalize(x)
print(normalized) # Devrait être [0.0000, 0.5714, 0.2857, 1.0000, 0.1429]
Softmax from scratch
Implémentez la fonction softmax avec broadcasting :
def softmax(x):
"""
Calcule softmax : exp(x) / sum(exp(x))
x: Tensor de shape (batch_size, num_classes)
Returns: Probabilities (même shape)
"""
# Votre code ici (attention à la stabilité numérique!)
pass
# Test
logits = torch.tensor([[1.0, 2.0, 3.0],
[1.0, 3.0, 2.0]])
probs = softmax(logits)
print(probs)
print(probs.sum(dim=1)) # Devrait être [1., 1.]
Matrice de covariance
Calculez la matrice de covariance d’un dataset :
def covariance_matrix(X):
"""
Calcule la matrice de covariance de X
X: Tensor de shape (n_samples, n_features)
Returns: Matrice de covariance (n_features, n_features)
"""
# Votre code ici
pass
# Test
X = torch.randn(100, 5) # 100 exemples, 5 features
cov = covariance_matrix(X)
print(f"Covariance shape : {cov.shape}") # (5, 5)
Conclusion
Dans cet article, vous avez maîtrisé les fondations de PyTorch :
- ✅ Création de tensors (zeros, ones, randn, from_numpy)
- ✅ Attributs des tensors (shape, dtype, device)
- ✅ Opérations mathématiques (élément par élément, matricielles, réductions)
- ✅ Broadcasting pour opérations entre shapes différentes
- ✅ Indexing et slicing avancés (masking, where)
- ✅ Reshaping (reshape, view, squeeze, unsqueeze, transpose)
- ✅ Autograd et différentiation automatique
- ✅ Graphe de calcul et accumulation de gradients
Prochaine étape : Dans le prochain article, nous allons utiliser ces connaissances pour construire notre premier réseau de neurones avec nn.Module. Vous apprendrez à empiler des layers, définir des architectures, et comprendre les fonctions d’activation.
Pour aller plus loin :
- Explorez la documentation PyTorch sur les tensors
- Consultez notre article sur les vecteurs et embeddings pour comprendre comment les tensors représentent des données
- Découvrez le hardware IA pour optimiser vos calculs tensoriels sur GPU
Exercice final : Créez un notebook et reproduisez tous les exemples. Modifiez les dimensions, essayez d’autres opérations, et expérimentez avec autograd sur différentes fonctions mathématiques. La pratique est la clé pour maîtriser les tensors !