AI & GPU
Hoe ResNet gemakkelijk te begrijpen in PyTorch

Hoe ResNet gemakkelijk te begrijpen in PyTorch

Inleiding tot ResNet

Wat is ResNet?

ResNet, afkorting voor Residual Neural Network, is een architecture voor deep learning die in 2015 werd geïntroduceerd door onderzoekers bij Microsoft. Het is ontworpen om het probleem van het verdwijnen/ontploffen van gradiënt aan te pakken, een veelvoorkomend probleem bij het trainen van zeer diepe neurale netwerken.

  1. Residual Neural Network: ResNet is een type neurale netwerk dat "skip connections" of "residual connections" gebruikt om het trainen van veel diepere modellen mogelijk te maken. Deze skip connections laten het netwerk bepaalde lagen overslaan, waardoor er effectief een "shortcut" wordt gecreëerd die helpt om het probleem van het verdwijnen van gradiënt te verminderen.

  2. Het probleem van het verdwijnen/ontploffen van gradiënt aanpakken: In zeer diepe neurale netwerken kunnen de gradiënten die worden gebruikt voor backpropagatie ofwel verdwijnen (extreem klein worden) ofwel exploderen (extreem groot worden) naarmate ze door het netwerk worden doorgegeven. Dit kan het moeilijk maken voor het netwerk om effectief te leren, vooral in de diepere lagen. De skip connections van ResNet helpen dit probleem op te lossen door de gradiënten gemakkelijker door het netwerk te laten stromen.

Voordelen van ResNet

  1. Verbeterde prestaties bij diepe neurale netwerken: De skip connections van ResNet maken het mogelijk om veel diepere neurale netwerken te trainen, wat kan leiden tot aanzienlijk verbeterde prestaties bij een verscheidenheid aan taken, zoals beeldclassificatie, objectdetectie en semantische segmentatie.

  2. Snellere convergentie tijdens het trainingsproces: De skip connections in ResNet kunnen ook helpen bij de snellere convergentie van het netwerk tijdens het trainingsproces, omdat ze de gradiënten gemakkelijker door het netwerk laten stromen.

Implementatie van ResNet in PyTorch

Opzetten van de omgeving

  1. Installeren van PyTorch: Om te kunnen beginnen met het implementeren van ResNet in PyTorch, moet je eerst de PyTorch-bibliotheek installeren. Je kunt PyTorch downloaden en installeren vanaf de officiële website (https://pytorch.org/ (opens in a new tab)) op basis van je besturingssysteem en Python-versie.

  2. Nodige bibliotheken importeren: Nadat je PyTorch hebt geïnstalleerd, moet je de nodige bibliotheken importeren voor je project. Dit omvat meestal PyTorch, NumPy en andere bibliotheken die je mogelijk nodig hebt voor gegevensvoorbewerking, visualisatie of andere taken.

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt

Definiëren van de ResNet-architectuur

Begrip van de basisbouwstenen

  1. Convolutionele lagen: ResNet maakt, net als veel andere deep learning-modellen, gebruik van convolutionele lagen als primaire bouwstenen voor het extraheren van kenmerken.

  2. Batchnormalisatie: ResNet maakt ook gebruik van batchnormalisatielagen om het trainingsproces te stabiliseren en de prestaties van het model te verbeteren.

  3. Activeringsfuncties: De ResNet-architectuur gebruikt meestal ReLU (Rectified Linear Unit) als activatiefunctie, die helpt om niet-lineariteit in het model te introduceren.

  4. Poolinglagen: ResNet kan ook poolinglagen bevatten, zoals max-pooling of average-pooling, om de ruimtelijke dimensies van de featuremaps te verkleinen en translatie-invariantie te introduceren.

Implementatie van het ResNet-blok

  1. Residuale verbinding: De belangrijkste innovatie van ResNet is de residuale verbinding, waarmee het netwerk bepaalde lagen kan omzeilen door de input van een laag bij de output ervan op te tellen. Dit helpt het probleem van het verdwijnen van gradiënt te verminderen.
class ResNetBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super(ResNetBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)
        
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )
 
    def forward(self, x):
        residual = self.shortcut(x)
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out += residual
        out = self.relu(out)
        return out
  1. Shortcut-verbinding: Naast de residuale verbinding maakt ResNet ook gebruik van een "shortcut-verbinding" om de dimensies van de input en output van het ResNet-blok aan elkaar aan te passen, indien nodig.

Opstellen van het volledige ResNet-model

  1. Stapelen van de ResNet-blokken: Om het volledige ResNet-model te creëren, moet je meerdere ResNet-blokken op elkaar stapelen, waarbij je het aantal lagen en het aantal filters in elk blok aanpast.

  2. Aanpassen van het aantal lagen: ResNet-modellen worden geleverd in verschillende varianten, zoals ResNet-18, ResNet-34, ResNet-50, ResNet-101 en ResNet-152, die verschillende aantallen lagen hebben. Het aantal lagen heeft invloed op de complexiteit en prestaties van het model.

Implementatie van ResNet-18 in PyTorch

Definiëren van het ResNet-18-model

  1. Inputlaag: De inputlaag van het ResNet-18-model accepteert meestal een afbeelding van een specifieke grootte, zoals 224x224 pixels.

  2. Convolutionele lagen: De initiële convolutionele lagen van het ResNet-18-model extraheren basale kenmerken uit de inputafbeelding.

  3. ResNet-blokken: De kern van het ResNet-18-model is de stapeling van meerdere ResNet-blokken, die gebruik maken van residuale verbindingen om het trainen van een dieper netwerk mogelijk te maken.

  4. Volledig verbonden laag: Na de convolutionele en ResNet-blokken heeft het model een volledig verbonden laag om de uiteindelijke classificatie- of voorspellingstaak uit te voeren.

  5. Outputlaag: De outputlaag van het ResNet-18-model heeft een aantal eenheden dat overeenkomt met het aantal klassen in het op te lossen probleem.

class ResNet18(nn.Module):
    def __init__(self, num_classes=10):
        super(ResNet18, self).__init__()
        self.in_channels = 64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
 
        self.layer1 = self._make_layer(64, 64, 2, stride=1)
        self.layer2 = self._make_layer(64, 128, 2, stride=2)
        self.layer3 = self._make_layer(128, 256, 2, stride=2)
        self.layer4 = self._make_layer(256, 512, 2, stride=2)
 
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512, num_classes)
 
    def _make_layer(self, in_channels, out_channels, num_blocks, stride):
        layers = []
        layers.append(ResNetBlock(in_channels, out_channels, stride))
        self.in_channels = out_channels
        for i in range(1, num_blocks):
            layers.append(ResNetBlock(out_channels, out_channels))
        return nn.Sequential(*layers)
 
    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
 
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
 
        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

Initialiseren van het model

Om een instantie van het ResNet-18-model te maken, kun je eenvoudig de ResNet18-klasse instantiëren:

model = ResNet18(num_classes=10)

Het modeloverzicht afdrukken

Je kunt een overzicht van de ResNet-18-modelarchitectuur afdrukken met behulp van de summary()-functie uit de torchsummary-bibliotheek:

from torchsummary import summary
summary(model, input_size=(3, 224, 224))

Hiermee krijg je een gedetailleerd overzicht van de lagen van het model, inclusief het aantal parameters en de outputvorm van elke laag.

Het ResNet-18-model trainen

Voorbereiding van de dataset

Het downloaden en laden van de dataset

Voor dit voorbeeld zullen we de CIFAR-10-dataset gebruiken, die een veelgebruikte benchmark is voor beeldclassificatietaken. Je kunt de dataset downloaden met behulp van de torchvision.datasets.CIFAR10-module:

# De CIFAR-10-dataset downloaden en laden
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transforms.ToTensor())
test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms.ToTensor())

Het voorbewerken van de gegevens

Voordat we het model kunnen trainen, moeten we de gegevens voorbewerken, zoals het normaliseren van de pixelwaarden en het toepassen van technieken voor gegevensaugmentatie:

# Definieer de gegevenstransformaties
transform_train = transforms.Compose([
    transforms.RandomCrop(32, padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
 
transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
 
# Maak de dataloaders aan
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=100, shuffle=False, num_workers=2)

Definiëren van de trainingslus

Instellen van het apparaat (CPU of GPU)

Om gebruik te maken van GPU-versnelling, kun je het model en de gegevens naar de GPU verplaatsen:

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)

Definiëren van de verliesfunctie en optimizer

Vervolgens moet je de verliesfunctie en de optimizer definiëren die tijdens het trainingsproces zal worden gebruikt:

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)

Implementatie van de trainingslus

De trainingslus omvat de volgende stappen:

  1. Voorwaartse doorvoer door het model
  2. Berekenen van de verlieswaarde
  3. Terugpropageren van de gradiënten
  4. Bijwerken van de modelparameters
  5. Bijhouden van het trainingsverlies en de nauwkeurigheid
num_epochs = 100
train_losses = []
train_accuracies = []
val_losses = []
val_accuracies = []
 
for epoch in range(num_epochs):
    # Trainingsfase
    model.train()
    running_loss = 0.0
    correct = 0
    total = 0
    for i, (inputs, labels) in enumerate(train_loader):
        inputs, labels = inputs.to(device), labels.to(device)
        
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
 
## Optimalisatie van het model
 
### Regulering
 
Regulering is een techniek die wordt gebruikt om overpassing in diepe leermodellen te voorkomen. Overpassing treedt op wanneer een model goed presteert op de trainingsdata, maar niet generaliseert naar nieuwe, ongeziene data. Reguleringsmethoden helpen het model beter te generaliseren door een straf voor complexiteit in te voeren of ruis toe te voegen aan het trainingsproces.
 
Een populaire reguleringsmethode is L2-regulering, ook wel bekend als gewichtsverval. Deze methode voegt een straftoeterm aan de verliesfunctie toe die evenredig is met het gekwadrateerde gewicht van het model. De verliesfunctie met L2-regulering kan worden geschreven als:
 

verlies = originele_verlies + lambda * som(w^2)


waarbij `lambda` de sterkte van de regulering is en `w` de gewichten van het model zijn.

Een andere populaire reguleringstechniek is Dropout. Dropout zet tijdens de training een deel van de activaties in een laag willekeurig op nul, waardoor de capaciteit van het model wordt verlaagd en het gedwongen wordt robuustere kenmerken te leren. Dit helpt overpassing te voorkomen en kan de generalisatieprestaties van het model verbeteren.

Hier is een voorbeeld van hoe Dropout kan worden geïmplementeerd in een PyTorch-model:

```python
import torch.nn as nn

class MijnModel(nn.Module):
    def __init__(self):
        super(MijnModel, self).__init__()
        self.fc1 = nn.Linear(64, 128)
        self.dropout = nn.Dropout(p=0.5)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = self.fc1(x)
        x = self.dropout(x)
        x = self.fc2(x)
        return x

In dit voorbeeld wordt de Dropout-laag toegepast na de eerste volledig verbonden laag, met een dropout-tarief van 0,5, wat betekent dat 50% van de activaties tijdens de training willekeurig op nul wordt gezet.

Optimalisatie-algoritmen

De keuze van het optimalisatie-algoritme kan een aanzienlijke invloed hebben op de prestaties en convergentie van een diep leermodel. Hier zijn enkele populaire optimalisatie-algoritmen die worden gebruikt in diep leren:

Stochastische gradiëntafdaling (SGD)

SGD is het meest basale optimalisatie-algoritme, waarbij de gradiënten worden berekend op basis van een enkel trainingsvoorbeeld of een kleine batch voorbeelden, en de gewichten dienovereenkomstig worden bijgewerkt. SGD kan traag convergeren, maar het is eenvoudig en effectief.

import torch.optim as optim
 
model = MijnModel()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

Adam

Adam (Adaptief Moment Schatting) is een geavanceerder optimalisatie-algoritme dat adaptieve leersnelheden berekent voor elke parameter. Het combineert de voordelen van momentum en RMSProp, waardoor het een populaire keuze is voor veel taken op het gebied van diep leren.

optimizer = optim.Adam(model.parameters(), lr=0.001)

AdaGrad

AdaGrad (Adaptieve Gradiënt) is een optimalisatie-algoritme dat de leersnelheid voor elke parameter aanpast op basis van de historische gradiënten. Het is effectief voor spaarzame data, maar kan last hebben van een agressieve vermindering van de leersnelheid na verloop van tijd.

optimizer = optim.Adagrad(model.parameters(), lr=0.01)

RMSProp

RMSProp (Root Mean Square Propagation) is een ander adaptief optimalisatie-algoritme voor leersnelheden dat een voortschrijdend gemiddelde van de gekwadrateerde gradiënten bijhoudt. Het is met name geschikt voor niet-stationaire doelfuncties, zoals die in recurrente neurale netwerken.

optimizer = optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99)

De keuze van het optimalisatie-algoritme hangt af van het specifieke probleem, de structuur van het model en de kenmerken van de data. Het is vaak een goed idee om te experimenteren met verschillende algoritmen en hun prestaties op uw taak te vergelijken.

Overdrachtsleren

Overdrachtsleren is een techniek waarbij een model dat is getraind op een groot dataset als uitgangspunt wordt gebruikt voor een model op een andere maar gerelateerde taak. Dit kan vooral nuttig zijn wanneer de doeldataset klein is, omdat het model hiermee kan profiteren van de geleerde kenmerken op de grotere dataset.

Een veelgebruikte aanpak voor overdrachtsleren in diep leren is het gebruik van een voorgetraind model, zoals degene die beschikbaar zijn voor populaire beeldherkennings- of natuurlijke taalverwerkingstaken, en het fijnafstemmen van het model op de doeldataset. Hierbij worden de onderste lagen van het voorgetrainde model bevroren en alleen de bovenste lagen opnieuw getraind met de nieuwe data.

Hier is een voorbeeld van hoe u een voorgetraind ResNet-model kunt fijnafstemmen voor een beeldclassificatietoepassing in PyTorch:

import torchvision.models as models
import torch.nn as nn
 
# Laad het voorgetrainde ResNet-model
resnet = models.resnet18(pretrained=True)
 
# Bevries de parameters van het voorgetrainde model
for param in resnet.parameters():
    param.requires_grad = False
 
# Vervang de laatste laag door een nieuwe volledig verbonden laag
num_features = resnet.fc.in_features
resnet.fc = nn.Linear(num_features, 10)  # Neem aan dat er 10 klassen zijn
 
# Train het model op de nieuwe dataset
optimizer = optim.Adam(resnet.fc.parameters(), lr=0.001)

In dit voorbeeld laden we eerst het voorgetrainde ResNet18-model en bevriezen we de parameters van de onderste lagen. Vervolgens vervangen we de laatste volledig verbonden laag door een nieuwe laag met het juiste aantal uitvoeren voor onze doeltaak (in dit geval 10 klassen). Ten slotte trainen we het model met behulp van de Adam-optimalisator, waarbij alleen de parameters van de nieuwe volledig verbonden laag worden bijgewerkt.

Overdrachtsleren kan de prestaties van diep leermodellen aanzienlijk verbeteren, vooral wanneer de doeldataset klein is. Het is een krachtige techniek die tijd en middelen kan besparen tijdens de ontwikkeling van het model.

Modelinterpreteerbaarheid

Naarmate diep leermodellen complexer en wijdverbreider worden, neemt de behoefte aan interpreteerbare modellen toe. Interpreteerbaarheid verwijst naar het vermogen om het interne besluitvormingsproces van een model te begrijpen en uit te leggen.

Een populaire techniek om de interpreteerbaarheid van modellen te verbeteren, is het gebruik van aandachtsmechanismen. Aandacht stelt het model in staat zich te concentreren op de meest relevante delen van de invoer bij het maken van een voorspelling, en het kan worden gevisualiseerd om te begrijpen welke functies het model gebruikt.

Hier is een voorbeeld van hoe u een aandachtsmechanisme kunt implementeren in een PyTorch-model voor een natuurlijke taalverwerkingstaak:

import torch.nn as nn
import torch.nn.functional as F
 
class AandachtModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        super(AandachtModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, bidirectional=True, batch_first=True)
        self.aandacht = nn.Linear(hidden_dim * 2, 1)
 
    def forward(self, input_ids):
        # Embed de invoer
        embedded = self.embedding(input_ids)
 
        # Voer de ingebedde invoer door de LSTM
        lstm_output, _ = self.lstm(embedded)
 
        # Bereken de aandachtsgewichten
        aandachtsgewichten = F.softmax(self.aandacht(lstm_output), dim=1)
 
        # Bereken de gewogen som van de uitvoer van de LSTM
        context = torch.sum(aandachtsgewichten * lstm_output, dim=1)
 
        return context

In dit voorbeeld wordt het aandachtsmechanisme geïmplementeerd als een lineaire laag die de uitvoer van de LSTM als invoer gebruikt en een set aandachtsgewichten produceert. Deze gewichten worden vervolgens gebruikt om een gewogen som van de uitvoer van de LSTM te berekenen, die de uiteindelijke uitvoer van het model is.

Door de aandachtsgewichten te visualiseren, kunt u inzicht krijgen in welke delen van de invoer het model zich concentreert bij het maken van een voorspelling. Dit kan u helpen het besluitvormingsproces van het model beter te begrijpen en potentiële vooroordelen of verbeterpunten te identificeren.

Een andere techniek voor het verbeteren van de interpreteerbaarheid van modellen is het gebruik van analyse van de belangrijkheid van functies. Hierbij wordt geïdentificeerd welke functies het model het meest belangrijk vindt bij het maken van voorspellingen. Een populaire methode hiervoor zijn Shapley-waarden, die een manier bieden om de bijdrage van elke functie aan de uitvoer van het model te kwantificeren.

Het verbeteren van de interpreteerbaarheid van modellen is een belangrijk onderzoeksgebied in diep leren, omdat dit kan helpen om meer vertrouwen op te bouwen in deze krachtige modellen en ervoor te zorgen dat ze verantwoord worden gebruikt.

Conclusie

In deze tutorial hebben we een scala aan onderwerpen behandeld die verband houden met diep leren, waaronder modeloptimalisatie, overdrachtsleren en modelinterpreteerbaarheid. We hebben technieken besproken zoals regulering, optimalisatie-algoritmen en aandachtsmechanismen, en voorbeelden gegeven van hoe u deze concepten kunt implementeren in PyTorch.

Naarmate diep leren zich blijft ontwikkelen en breder wordt toegepast, is het belangrijk om deze geavanceerde onderwerpen te begrijpen en ze toe te passen in uw eigen projecten. Door deze technieken onder de knie te krijgen, bent u beter in staat om krachtige, robuuste en interpreteerbare diep leermodellen te bouwen die een breed scala aan problemen kunnen oplossen.

Bedenk dat diep leren een snel veranderend vakgebied is en dat het belangrijk is om op de hoogte te blijven van het nieuwste onderzoek en de beste werkwijzen. Blijf verkennen, experimenteren en leren, en u bent goed op weg om een expert op het gebied van diep leren te worden.