Vision par Ordinateur avec PyTorch en 2026 : CNN, Transfer Learning et Questions d'Entretien

Guide complet sur la vision par ordinateur avec PyTorch : réseaux de neurones convolutifs, transfer learning et préparation aux entretiens techniques en deep learning.

Vision par Ordinateur avec PyTorch en 2026 : CNN, Transfer Learning et Questions d'Entretien

La vision par ordinateur représente l'un des domaines les plus dynamiques de l'intelligence artificielle. Avec PyTorch comme framework de prédilection, les développeurs et data scientists peuvent construire des modèles sophistiqués de classification d'images, de détection d'objets et de segmentation sémantique. Ce guide explore les concepts fondamentaux des CNN, les techniques de transfer learning et les questions fréquentes en entretien technique.

La maîtrise des CNN et du transfer learning constitue un atout majeur pour les postes en machine learning. Les recruteurs évaluent souvent la compréhension pratique de ces concepts lors des entretiens techniques.

Fondamentaux des Réseaux de Neurones Convolutifs

Les réseaux de neurones convolutifs (CNN) forment l'architecture de base pour le traitement d'images. Contrairement aux réseaux fully-connected, les CNN exploitent la structure spatiale des données visuelles grâce à des opérations de convolution.

L'architecture typique d'un CNN comprend plusieurs types de couches : les couches de convolution extraient des caractéristiques locales, les couches de pooling réduisent la dimensionnalité, et les couches fully-connected effectuent la classification finale.

python
import torch
import torch.nn as nn
import torch.nn.functional as F

class SimpleCNN(nn.Module):
    def __init__(self, num_classes: int = 10):
        super(SimpleCNN, self).__init__()
        # Convolutional layers
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        
        # Batch normalization
        self.bn1 = nn.BatchNorm2d(32)
        self.bn2 = nn.BatchNorm2d(64)
        self.bn3 = nn.BatchNorm2d(128)
        
        # Pooling and dropout
        self.pool = nn.MaxPool2d(2, 2)
        self.dropout = nn.Dropout(0.5)
        
        # Fully connected layers
        self.fc1 = nn.Linear(128 * 4 * 4, 512)
        self.fc2 = nn.Linear(512, num_classes)
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # Block 1: 32x32 -> 16x16
        x = self.pool(F.relu(self.bn1(self.conv1(x))))
        
        # Block 2: 16x16 -> 8x8
        x = self.pool(F.relu(self.bn2(self.conv2(x))))
        
        # Block 3: 8x8 -> 4x4
        x = self.pool(F.relu(self.bn3(self.conv3(x))))
        
        # Flatten and classify
        x = x.view(-1, 128 * 4 * 4)
        x = self.dropout(F.relu(self.fc1(x)))
        x = self.fc2(x)
        
        return x

Ce modèle illustre les composants essentiels : la batch normalization stabilise l'entraînement, le dropout prévient le surapprentissage, et le max pooling réduit progressivement les dimensions spatiales.

Configuration de l'Environnement et Préparation des Données

Avant d'entraîner un modèle, la préparation des données constitue une étape critique. PyTorch fournit des outils puissants pour le chargement et l'augmentation des données.

python
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from typing import Tuple

def create_data_loaders(
    data_dir: str,
    batch_size: int = 32,
    num_workers: int = 4
) -> Tuple[DataLoader, DataLoader]:
    """Create training and validation data loaders with augmentation."""
    
    # Training transforms with augmentation
    train_transform = transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.RandomRotation(15),
        transforms.ColorJitter(
            brightness=0.2, 
            contrast=0.2, 
            saturation=0.2
        ),
        transforms.ToTensor(),
        transforms.Normalize(
            mean=[0.485, 0.456, 0.406],
            std=[0.229, 0.224, 0.225]
        )
    ])
    
    # Validation transforms (no augmentation)
    val_transform = transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize(
            mean=[0.485, 0.456, 0.406],
            std=[0.229, 0.224, 0.225]
        )
    ])
    
    # Create datasets
    train_dataset = datasets.ImageFolder(
        root=f"{data_dir}/train",
        transform=train_transform
    )
    
    val_dataset = datasets.ImageFolder(
        root=f"{data_dir}/val",
        transform=val_transform
    )
    
    # Create data loaders
    train_loader = DataLoader(
        train_dataset,
        batch_size=batch_size,
        shuffle=True,
        num_workers=num_workers,
        pin_memory=True
    )
    
    val_loader = DataLoader(
        val_dataset,
        batch_size=batch_size,
        shuffle=False,
        num_workers=num_workers,
        pin_memory=True
    )
    
    return train_loader, val_loader

L'augmentation de données améliore la robustesse du modèle en créant des variations artificielles des images d'entraînement. Les transformations géométriques et colorimétriques augmentent la diversité du dataset sans nécessiter de nouvelles données.

Transfer Learning avec des Modèles Pré-entraînés

Le transfer learning permet d'exploiter des modèles entraînés sur de vastes datasets comme ImageNet. Cette approche réduit considérablement le temps d'entraînement et améliore les performances, particulièrement avec des datasets limités.

python
import torch
import torch.nn as nn
from torchvision import models
from torchvision.models import ResNet50_Weights, EfficientNet_B0_Weights

def create_transfer_model(
    model_name: str,
    num_classes: int,
    freeze_backbone: bool = True
) -> nn.Module:
    """Create a transfer learning model with a custom classifier."""
    
    if model_name == "resnet50":
        # Load pre-trained ResNet50
        weights = ResNet50_Weights.IMAGENET1K_V2
        model = models.resnet50(weights=weights)
        
        # Freeze backbone if specified
        if freeze_backbone:
            for param in model.parameters():
                param.requires_grad = False
        
        # Replace classifier
        num_features = model.fc.in_features
        model.fc = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(num_features, 512),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(512, num_classes)
        )
        
    elif model_name == "efficientnet_b0":
        # Load pre-trained EfficientNet
        weights = EfficientNet_B0_Weights.IMAGENET1K_V1
        model = models.efficientnet_b0(weights=weights)
        
        if freeze_backbone:
            for param in model.parameters():
                param.requires_grad = False
        
        # Replace classifier
        num_features = model.classifier[1].in_features
        model.classifier = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(num_features, num_classes)
        )
    
    else:
        raise ValueError(f"Unknown model: {model_name}")
    
    return model


def unfreeze_layers(model: nn.Module, num_layers: int) -> None:
    """Gradually unfreeze layers for fine-tuning."""
    # Get all parameters as a list
    params = list(model.parameters())
    
    # Unfreeze the last n layers
    for param in params[-num_layers:]:
        param.requires_grad = True

La stratégie de gel progressif des couches optimise le processus d'entraînement. Les premières couches, qui détectent des caractéristiques génériques comme les bords et les textures, restent gelées. Les couches supérieures, spécifiques à la tâche, sont entraînées sur le nouveau dataset.

Boucle d'Entraînement Complète

Une boucle d'entraînement robuste intègre la validation, le suivi des métriques et la sauvegarde des meilleurs modèles.

python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from typing import Dict, List
import copy

class Trainer:
    def __init__(
        self,
        model: nn.Module,
        train_loader: DataLoader,
        val_loader: DataLoader,
        learning_rate: float = 1e-4,
        device: str = "cuda"
    ):
        self.model = model.to(device)
        self.train_loader = train_loader
        self.val_loader = val_loader
        self.device = device
        
        self.criterion = nn.CrossEntropyLoss()
        self.optimizer = optim.AdamW(
            filter(lambda p: p.requires_grad, model.parameters()),
            lr=learning_rate,
            weight_decay=0.01
        )
        self.scheduler = optim.lr_scheduler.CosineAnnealingLR(
            self.optimizer, 
            T_max=50
        )
        
        self.best_model_weights = None
        self.best_accuracy = 0.0
        self.history: Dict[str, List[float]] = {
            "train_loss": [],
            "val_loss": [],
            "val_accuracy": []
        }
    
    def train_epoch(self) -> float:
        """Train for one epoch."""
        self.model.train()
        running_loss = 0.0
        
        for images, labels in self.train_loader:
            images = images.to(self.device)
            labels = labels.to(self.device)
            
            self.optimizer.zero_grad()
            outputs = self.model(images)
            loss = self.criterion(outputs, labels)
            loss.backward()
            self.optimizer.step()
            
            running_loss += loss.item()
        
        return running_loss / len(self.train_loader)
    
    @torch.no_grad()
    def validate(self) -> tuple[float, float]:
        """Validate the model."""
        self.model.eval()
        running_loss = 0.0
        correct = 0
        total = 0
        
        for images, labels in self.val_loader:
            images = images.to(self.device)
            labels = labels.to(self.device)
            
            outputs = self.model(images)
            loss = self.criterion(outputs, labels)
            
            running_loss += loss.item()
            _, predicted = outputs.max(1)
            total += labels.size(0)
            correct += predicted.eq(labels).sum().item()
        
        accuracy = correct / total
        avg_loss = running_loss / len(self.val_loader)
        
        return avg_loss, accuracy
    
    def fit(self, epochs: int) -> Dict[str, List[float]]:
        """Full training loop."""
        for epoch in range(epochs):
            train_loss = self.train_epoch()
            val_loss, val_accuracy = self.validate()
            self.scheduler.step()
            
            # Save history
            self.history["train_loss"].append(train_loss)
            self.history["val_loss"].append(val_loss)
            self.history["val_accuracy"].append(val_accuracy)
            
            # Save best model
            if val_accuracy > self.best_accuracy:
                self.best_accuracy = val_accuracy
                self.best_model_weights = copy.deepcopy(
                    self.model.state_dict()
                )
            
            print(
                f"Epoch {epoch+1}/{epochs} - "
                f"Train Loss: {train_loss:.4f} - "
                f"Val Loss: {val_loss:.4f} - "
                f"Val Acc: {val_accuracy:.4f}"
            )
        
        # Load best weights
        self.model.load_state_dict(self.best_model_weights)
        return self.history

Cette implémentation utilise l'optimiseur AdamW avec weight decay pour la régularisation et un scheduler cosine annealing pour ajuster dynamiquement le learning rate.

Techniques Avancées pour la Vision par Ordinateur

Les architectures modernes intègrent des mécanismes d'attention qui améliorent la capacité du modèle à se concentrer sur les régions pertinentes de l'image.

python
import torch
import torch.nn as nn
import torch.nn.functional as F

class ChannelAttention(nn.Module):
    """Squeeze-and-Excitation channel attention."""
    
    def __init__(self, channels: int, reduction: int = 16):
        super().__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Sequential(
            nn.Linear(channels, channels // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(channels // reduction, channels, bias=False),
            nn.Sigmoid()
        )
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c)
        y = self.fc(y).view(b, c, 1, 1)
        return x * y.expand_as(x)


class SpatialAttention(nn.Module):
    """CBAM spatial attention module."""
    
    def __init__(self, kernel_size: int = 7):
        super().__init__()
        self.conv = nn.Conv2d(
            2, 1, 
            kernel_size=kernel_size, 
            padding=kernel_size // 2,
            bias=False
        )
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        attention = torch.cat([avg_out, max_out], dim=1)
        attention = self.sigmoid(self.conv(attention))
        return x * attention

Les mécanismes d'attention channel et spatial permettent au réseau d'apprendre quelles caractéristiques et quelles régions sont les plus importantes pour la tâche de classification.

Prêt à réussir tes entretiens Data Science & ML ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Questions d'Entretien Fréquentes en Vision par Ordinateur

Les entretiens techniques en deep learning couvrent généralement des concepts théoriques et pratiques. Voici les questions les plus courantes avec leurs réponses.

Question : Expliquez la différence entre valid et same padding dans les convolutions.

Le padding "valid" n'ajoute aucun pixel autour de l'image, ce qui réduit les dimensions de sortie. Le padding "same" ajoute des pixels pour maintenir les dimensions d'entrée identiques aux dimensions de sortie. Le choix dépend de l'architecture souhaitée et de la préservation de l'information spatiale aux bords.

Question : Comment le batch normalization améliore-t-il l'entraînement des CNN ?

Le batch normalization normalise les activations à chaque mini-batch, réduisant le covariate shift interne. Cette technique accélère la convergence, permet d'utiliser des learning rates plus élevés et agit comme régularisateur. Durant l'inférence, les statistiques de moyenne et variance sont calculées sur l'ensemble du dataset d'entraînement.

Question : Quand utiliser le transfer learning versus entraîner from scratch ?

Le transfer learning est préférable avec des datasets limités, des tâches similaires au dataset source, ou des contraintes de ressources computationnelles. L'entraînement from scratch convient aux datasets massifs, aux domaines très différents d'ImageNet (images médicales, satellites), ou lorsque les caractéristiques de bas niveau diffèrent significativement.

Question : Expliquez le concept de receptive field dans les CNN.

Le receptive field représente la région de l'image d'entrée qui influence un neurone particulier. Il augmente avec la profondeur du réseau grâce aux convolutions et poolings successifs. Un receptive field plus large permet au modèle de capturer des dépendances spatiales à plus grande échelle, essentiel pour la compréhension contextuelle.

Question : Comment gérer le déséquilibre de classes en classification d'images ?

Plusieurs stratégies existent : pondération des classes dans la loss function, suréchantillonnage des classes minoritaires, sous-échantillonnage des classes majoritaires, ou génération de données synthétiques via augmentation ciblée. Le choix dépend de la sévérité du déséquilibre et de la taille du dataset.

Métriques d'Évaluation pour la Vision par Ordinateur

L'évaluation rigoureuse d'un modèle nécessite des métriques adaptées à la tâche et au contexte métier.

python
import torch
import numpy as np
from sklearn.metrics import (
    confusion_matrix, 
    classification_report,
    precision_recall_fscore_support
)
from typing import Dict, List

@torch.no_grad()
def evaluate_model(
    model: torch.nn.Module,
    test_loader: torch.utils.data.DataLoader,
    class_names: List[str],
    device: str = "cuda"
) -> Dict:
    """Comprehensive model evaluation."""
    model.eval()
    all_predictions = []
    all_labels = []
    all_probabilities = []
    
    for images, labels in test_loader:
        images = images.to(device)
        outputs = model(images)
        probabilities = torch.softmax(outputs, dim=1)
        _, predicted = outputs.max(1)
        
        all_predictions.extend(predicted.cpu().numpy())
        all_labels.extend(labels.numpy())
        all_probabilities.extend(probabilities.cpu().numpy())
    
    # Calculate metrics
    precision, recall, f1, support = precision_recall_fscore_support(
        all_labels, 
        all_predictions, 
        average=None
    )
    
    # Per-class metrics
    per_class_metrics = {
        class_names[i]: {
            "precision": float(precision[i]),
            "recall": float(recall[i]),
            "f1_score": float(f1[i]),
            "support": int(support[i])
        }
        for i in range(len(class_names))
    }
    
    # Overall metrics
    accuracy = np.mean(np.array(all_predictions) == np.array(all_labels))
    macro_f1 = np.mean(f1)
    
    return {
        "accuracy": float(accuracy),
        "macro_f1": float(macro_f1),
        "per_class": per_class_metrics,
        "confusion_matrix": confusion_matrix(
            all_labels, 
            all_predictions
        ).tolist()
    }

L'accuracy seule peut être trompeuse avec des classes déséquilibrées. Le F1-score macro fournit une vue plus équilibrée des performances sur toutes les classes.

Conclusion

La vision par ordinateur avec PyTorch offre des possibilités considérables pour résoudre des problèmes complexes de traitement d'images. La maîtrise des CNN, du transfer learning et des techniques d'attention constitue une base solide pour les praticiens du deep learning. La préparation aux entretiens techniques nécessite une compréhension approfondie de ces concepts, combinée à une expérience pratique de leur implémentation. L'écosystème PyTorch continue d'évoluer avec de nouvelles architectures et optimisations, rendant essentielle une veille technologique constante dans ce domaine en rapide évolution.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Développeur fullstack, fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 14 août 2026

Partager

Articles similaires