Computer Vision met PyTorch in 2026: CNNs, Transfer Learning en Sollicitatievragen

Computer vision met PyTorch: bouw CNNs vanaf nul, pas transfer learning toe met voorgetrainde modellen en bereid je voor op technische sollicitatiegesprekken.

Computer Vision met PyTorch in 2026: CNNs, Transfer Learning en Sollicitatievragen

Computer vision met PyTorch is in 2026 de dominante aanpak geworden voor beeldclassificatie, objectdetectie en visuele herkenningstaken. Deze tutorial behandelt het bouwen van CNNs vanaf nul, het toepassen van transfer learning met voorgetrainde modellen en de voorbereiding op technische sollicitatievragen.

PyTorch 2.4 Prestaties

PyTorch 2.4 introduceert torch.compile() standaard voor CNN-operaties, wat 30-50% snelheidsverbeteringen oplevert op moderne GPUs zonder codewijzigingen. Alle voorbeelden in dit artikel zijn compatibel met PyTorch 2.4+.

Convolutionele Neurale Netwerken voor Beeldclassificatie Begrijpen

Convolutionele Neurale Netwerken extraheren hiërarchische kenmerken uit afbeeldingen door middel van leerbare filters. In tegenstelling tot volledig verbonden netwerken behouden CNNs de ruimtelijke relaties tussen pixels, waardoor ze ideaal zijn voor visuele taken. De architectuur bestaat uit convolutielagen die randen en patronen detecteren, pooling-lagen die de dimensionaliteit verminderen en volledig verbonden lagen die de classificatie uitvoeren.

Een convolutie-operatie schuift een kleine filter (typisch 3x3 of 5x5) over de invoerafbeelding en berekent inproducten op elke positie. Dit produceert feature maps die specifieke patronen zoals randen, texturen of vormen benadrukken. Diepere lagen combineren deze laag-niveau kenmerken tot complexe representaties—gezichten, objecten of scènes.

python
# cnn_architecture.py
import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    """Basic CNN for CIFAR-10 classification (32x32 RGB images, 10 classes)"""
    
    def __init__(self, num_classes: int = 10):
        super().__init__()
        # First conv block: 3 input channels (RGB) -> 32 feature maps
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, padding=1),  # Output: 32x32x32
            nn.BatchNorm2d(32),  # Normalize activations for stable training
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 16x16x32
        )
        # Second conv block: increase depth for richer features
        self.conv2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, padding=1),  # Output: 16x16x64
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 8x8x64
        )
        # Third conv block: capture high-level patterns
        self.conv3 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),  # Output: 8x8x128
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 4x4x128
        )
        # Classifier head
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(inplace=True),
            nn.Dropout(0.5),  # Prevent overfitting
            nn.Linear(256, num_classes)
        )
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        return self.classifier(x)

Deze architectuur verhoogt progressief het aantal filters (32 → 64 → 128) terwijl de ruimtelijke dimensies worden verminderd door pooling. BatchNorm stabiliseert de training door laaguitvoer te normaliseren, terwijl Dropout voorkomt dat de classificator trainingsvoorbeelden uit het hoofd leert.

Een CNN Trainen met PyTorch DataLoaders

Efficiënt laden van data is cruciaal voor GPU-benutting tijdens training. PyTorch's DataLoader verzorgt automatisch batching, shuffling en parallel laden van data. Goede data-augmentatie—willekeurige uitsnedes, spiegelingen en kleurvariaties—verbetert de generalisatie op ongeziene afbeeldingen aanzienlijk.

python
# train_cnn.py
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# Data augmentation for training (reduces overfitting)
train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomCrop(32, padding=4),  # Random crop with padding
    transforms.ColorJitter(brightness=0.2, contrast=0.2),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465),  # CIFAR-10 mean
                         (0.2470, 0.2435, 0.2616))  # CIFAR-10 std
])

# No augmentation for validation (deterministic evaluation)
val_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465),
                         (0.2470, 0.2435, 0.2616))
])

def train_model(model: nn.Module, epochs: int = 20) -> dict:
    """Train CNN with standard best practices"""
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = model.to(device)
    
    # Load CIFAR-10 dataset
    train_data = datasets.CIFAR10(root="./data", train=True, 
                                   download=True, transform=train_transform)
    val_data = datasets.CIFAR10(root="./data", train=False,
                                 transform=val_transform)
    
    # DataLoaders with num_workers for parallel loading
    train_loader = DataLoader(train_data, batch_size=128, shuffle=True,
                              num_workers=4, pin_memory=True)
    val_loader = DataLoader(val_data, batch_size=256, shuffle=False,
                            num_workers=4, pin_memory=True)
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
    
    best_acc = 0.0
    for epoch in range(epochs):
        model.train()
        for images, labels in train_loader:
            images, labels = images.to(device), labels.to(device)
            optimizer.zero_grad()
            outputs = model(images)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
        
        scheduler.step()
        
        # Validation
        model.eval()
        correct, total = 0, 0
        with torch.no_grad():
            for images, labels in val_loader:
                images, labels = images.to(device), labels.to(device)
                outputs = model(images)
                _, predicted = outputs.max(1)
                total += labels.size(0)
                correct += predicted.eq(labels).sum().item()
        
        acc = 100. * correct / total
        best_acc = max(best_acc, acc)
        print(f"Epoch {epoch+1}/{epochs} - Val Acc: {acc:.2f}%")
    
    return {"best_accuracy": best_acc}

Belangrijke trainingsoptimalisaties omvatten: pin_memory=True voor snellere CPU-naar-GPU-overdrachten, num_workers voor parallel laden van data, AdamW-optimizer met weight decay voor regularisatie en cosine annealing learning rate scheduling voor geleidelijke convergentie.

Transfer Learning met Voorgetrainde ResNet en EfficientNet

Transfer learning maakt gebruik van modellen die voorgetraind zijn op ImageNet (1,2 miljoen afbeeldingen, 1000 klassen) en past deze aan voor aangepaste datasets. Deze aanpak bereikt hogere nauwkeurigheid met minder trainingsdata en rekentijd. PyTorch's torchvision.models biedt state-of-the-art architecturen met voorgetrainde gewichten.

De standaardaanpak bevriest vroege convolutielagen (die universele kenmerken zoals randen detecteren) en verfijnt latere lagen plus een nieuwe classificatiekop voor de doeltaak.

python
# transfer_learning.py
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 = "resnet50",
    num_classes: int = 10,
    freeze_backbone: bool = True
) -> nn.Module:
    """Create a pretrained model with custom classification head"""
    
    if model_name == "resnet50":
        # Load ResNet50 with ImageNet weights
        model = models.resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)
        # Replace final fully connected layer
        in_features = model.fc.in_features  # 2048 for ResNet50
        model.fc = nn.Sequential(
            nn.Dropout(0.3),
            nn.Linear(in_features, num_classes)
        )
        
    elif model_name == "efficientnet_b0":
        # EfficientNet: better accuracy/compute tradeoff
        model = models.efficientnet_b0(weights=EfficientNet_B0_Weights.IMAGENET1K_V1)
        in_features = model.classifier[1].in_features  # 1280 for B0
        model.classifier = nn.Sequential(
            nn.Dropout(0.2),
            nn.Linear(in_features, num_classes)
        )
    
    if freeze_backbone:
        # Freeze all layers except classifier
        for name, param in model.named_parameters():
            if "fc" not in name and "classifier" not in name:
                param.requires_grad = False
    
    return model

def count_trainable_params(model: nn.Module) -> int:
    """Count parameters that will be updated during training"""
    return sum(p.numel() for p in model.parameters() if p.requires_grad)

# Example usage
model = create_transfer_model("resnet50", num_classes=5, freeze_backbone=True)
print(f"Trainable parameters: {count_trainable_params(model):,}")
# Output: ~10,245 (only classifier) vs ~25 million (full ResNet50)

Het bevriezen van de backbone reduceert de trainbare parameters van 25 miljoen naar ongeveer 10.000, waardoor training op kleine datasets mogelijk is zonder overfitting. Voor grotere datasets (10.000+ afbeeldingen) kunnen latere lagen geleidelijk worden ontdooid met differentiële learning rates—lagere rates voor voorgetrainde lagen, hogere rates voor de nieuwe classificator.

Klaar om je Data Science & ML gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Data-augmentatiestrategieën voor Computer Vision

Data-augmentatie breidt de trainingsset kunstmatig uit door transformaties toe te passen die de semantische inhoud behouden. Moderne augmentatie gaat verder dan eenvoudige spiegelingen en rotaties—technieken zoals MixUp en CutOut creëren synthetische trainingsvoorbeelden die de robuustheid van het model verbeteren.

python
# augmentation_strategies.py
import torch
import torchvision.transforms.v2 as T
from torchvision.transforms.v2 import functional as F

# Modern augmentation pipeline using torchvision v2 transforms
advanced_augmentation = T.Compose([
    T.RandomResizedCrop(224, scale=(0.8, 1.0)),  # Random crop and resize
    T.RandomHorizontalFlip(p=0.5),
    T.RandomVerticalFlip(p=0.1),  # Less common but useful for some domains
    T.RandomRotation(degrees=15),
    T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.1),
    T.RandomAffine(degrees=0, translate=(0.1, 0.1)),  # Small translations
    T.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),
    T.ToImage(),
    T.ToDtype(torch.float32, scale=True),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

class CutOut:
    """Randomly mask out square regions of the image"""
    def __init__(self, n_holes: int = 1, length: int = 16):
        self.n_holes = n_holes
        self.length = length
    
    def __call__(self, img: torch.Tensor) -> torch.Tensor:
        h, w = img.shape[1], img.shape[2]
        mask = torch.ones_like(img)
        
        for _ in range(self.n_holes):
            y = torch.randint(0, h, (1,)).item()
            x = torch.randint(0, w, (1,)).item()
            y1 = max(0, y - self.length // 2)
            y2 = min(h, y + self.length // 2)
            x1 = max(0, x - self.length // 2)
            x2 = min(w, x + self.length // 2)
            mask[:, y1:y2, x1:x2] = 0
        
        return img * mask

def mixup_data(
    x: torch.Tensor,
    y: torch.Tensor,
    alpha: float = 0.4
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, float]:
    """MixUp: blend two images and their labels"""
    lam = torch.distributions.Beta(alpha, alpha).sample().item()
    batch_size = x.size(0)
    index = torch.randperm(batch_size)
    
    mixed_x = lam * x + (1 - lam) * x[index]
    y_a, y_b = y, y[index]
    
    return mixed_x, y_a, y_b, lam

CutOut dwingt het model om te vertrouwen op meerdere visuele aanwijzingen in plaats van een enkele discriminerende regio. MixUp creëert convexe combinaties van trainingsparen, wat resulteert in gladdere beslissingsgrenzen. Deze technieken verbeteren de nauwkeurigheid consistent met 1-3% op standaard benchmarks.

Sollicitatievragen over CNNs en Computer Vision

Technische sollicitatiegesprekken voor computer vision-functies testen zowel theoretisch begrip als praktische implementatievaardigheden. Voorbereiding moet vragen omvatten over architectuurontwerp, optimalisatie-uitdagingen en overwegingen voor deployment in de praktijk.

Veelvoorkomende Sollicitatieonderwerpen

Interviewers vragen vaak naar berekeningen van het receptieve veld, het doel van batch normalisatie, waarom convoluties werken voor afbeeldingen en hoe overfitting in vision-modellen te diagnosticeren.

V: Wat is het receptieve veld en waarom is het belangrijk?

Het receptieve veld is het gebied van de invoerafbeelding dat een bepaalde feature map-locatie beïnvloedt. Diepere lagen hebben grotere receptieve velden, waardoor ze bredere context kunnen vastleggen. Voor een netwerk met n convolutielagen met 3x3 kernels groeit het receptieve veld als (2n + 1) × (2n + 1). Architectuurontwerp vereist het balanceren van de grootte van het receptieve veld tegen rekenkosten—gedilateerde convoluties vergroten receptieve velden zonder parameters te verhogen.

V: Waarom wordt batch normalisatie gebruikt in CNNs?

Batch normalisatie normaliseert laaginvoer naar nul gemiddelde en eenheidsvariantie, waarmee interne covariate shift wordt aangepakt. Voordelen omvatten: snellere convergentie (hogere learning rates worden stabiel), regularisatie-effect (vermindert behoefte aan dropout) en verbeterde gradiëntstroom (voorkomt verdwijnende gradiënten in diepe netwerken). Tijdens inferentie vervangen running statistics de batch-statistieken voor deterministische uitvoer.

V: Leg het verschil uit tussen same en valid padding.

Same padding voegt nullen toe rond de invoer om ruimtelijke dimensies te behouden na convolutie—een 32×32 invoer met een 3×3 kernel produceert een 32×32 uitvoer. Valid padding past geen padding toe en vermindert dimensies—dezelfde operatie produceert een 30×30 uitvoer. Same padding heeft de voorkeur in diepe netwerken om agressieve ruimtelijke reductie te voorkomen.

V: Hoe ga je om met klasse-onbalans bij beeldclassificatie?

Strategieën omvatten: gewogen cross-entropy loss (hogere gewichten voor minderheidsklassen), oversampling van minderheidsklassen tijdens training, data-augmentatie gericht op ondervertegenwoordigde klassen en focal loss die makkelijke voorbeelden naar beneden weegt. Bij ernstige onbalans (>100:1) is het raadzaam meerdere technieken te combineren en metrieken buiten nauwkeurigheid te overwegen—F1-score, precision-recall AUC of confusion matrix-analyse.

Voor verdere voorbereiding op deep learning-sollicitatiegesprekken kunnen de modules CNN & Beeldclassificatie en Deep Learning Fundamenten worden verkend.

PyTorch Vision-modellen Deployen met TorchScript

Productie-deployment vereist het converteren van PyTorch-modellen naar geoptimaliseerde formaten. TorchScript compileert modellen naar een draagbare representatie die zonder Python draait, waardoor deployment in C++-applicaties, mobiele apparaten of serverless omgevingen mogelijk wordt.

python
# deployment_export.py
import torch
from torchvision import models
from torchvision.models import ResNet18_Weights

def export_for_production(model: torch.nn.Module, save_path: str) -> None:
    """Export model to TorchScript for production deployment"""
    model.eval()  # Set to evaluation mode (disables dropout, uses running stats)
    
    # Create example input matching expected dimensions
    example_input = torch.randn(1, 3, 224, 224)
    
    # Method 1: Tracing (for models without control flow)
    traced_model = torch.jit.trace(model, example_input)
    traced_model.save(save_path.replace(".pt", "_traced.pt"))
    
    # Method 2: Scripting (handles if/else, loops)
    scripted_model = torch.jit.script(model)
    scripted_model.save(save_path.replace(".pt", "_scripted.pt"))
    
    # Verify outputs match
    with torch.no_grad():
        original_out = model(example_input)
        traced_out = traced_model(example_input)
        scripted_out = scripted_model(example_input)
    
    assert torch.allclose(original_out, traced_out, atol=1e-5)
    assert torch.allclose(original_out, scripted_out, atol=1e-5)
    print(f"Model exported successfully to {save_path}")

def optimize_for_inference(model_path: str) -> torch.jit.ScriptModule:
    """Load and optimize TorchScript model for inference"""
    model = torch.jit.load(model_path)
    
    # Optimize for inference (fuses operations, removes dropout)
    optimized = torch.jit.optimize_for_inference(model)
    
    return optimized

# Export ResNet18
model = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
export_for_production(model, "resnet18_production.pt")

Voor maximale inferentiesnelheid kan ONNX-export worden overwogen voor cross-framework compatibiliteit of TensorRT voor NVIDIA GPU-deployment. De vergelijking PyTorch vs TensorFlow behandelt deployment-afwegingen tussen frameworks.

Conclusie

Computer vision met PyTorch combineert krachtige abstracties met productie-klare tools:

  • CNN-architecturen extraheren hiërarchische kenmerken door convolutie-, pooling- en normalisatielagen
  • Transfer learning met voorgetrainde modellen (ResNet, EfficientNet) bereikt hoge nauwkeurigheid met beperkte data
  • Data-augmentatie (MixUp, CutOut, geometrische transformaties) verbetert generalisatie en voorkomt overfitting
  • Technische sollicitatiegesprekken testen receptieve velden, batch normalisatie, padding-strategieën en het omgaan met klasse-onbalans
  • TorchScript maakt productie-deployment mogelijk zonder Python-afhankelijkheden

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Fullstack-ontwikkelaar, oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 14 augustus 2026

Delen

Gerelateerde artikelen