Exemple de réseau convolutionnel pour la classification de chiffres manuscrits.
Auteur du code : Yunjey Choi, https://github.com/yunjey/pytorch-tutorial
Cet exemple utilise les fonctions avancées de PyTorch pour programmer un réseau de neurones : définition du réseau sous forme de classe, utilisation de fonctions dédiées pour le chargement des données. Mais on pourrait évidemment le réécrire avec la syntaxe de l'exemple "circlesquare"
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
# Device configuration
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
# Hyper parameters
num_epochs = 5
num_classes = 10
batch_size = 100
learning_rate = 0.001
A noter que ces commandes ne font que déclarer les jeu de données ainsi que les itérateurs pour charger ces données, mais ne charge rien en mémoire encore. Les données seront chargées depuis le disque dur au coup par coup lorsque ce sera nécessaire. Ceci évite de saturer la mémoire.
A noter également qu'on dispose de deux sous parties "train" et "test" pour l'entraînement et l'évaluation
# MNIST dataset
train_dataset = torchvision.datasets.MNIST(root='data/',
train=True,
transform=transforms.ToTensor(),
download=True)
test_dataset = torchvision.datasets.MNIST(root='data/',
train=False,
transform=transforms.ToTensor())
# Data loader
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=batch_size,
shuffle=False)
labels = train_dataset.targets
print(labels[0:5])
images = train_dataset.data
import numpy as np
import matplotlib.pyplot as plt
for k in range(5):
plt.subplot(1, 5, k+1)
plt.imshow(images[k,:,:])
plt.show()
tensor([5, 0, 4, 1, 9])
La première fonction init est le constructeur de la classe ; on y définit deux couches convolutionnelles similaires qui enchaînent une opération de convolution, une renormalisation, une opération ReLu et un pooling, ainsi qu'une couche finale "dense" de multiplication simple par une matrice. En interne à chaque définition de ces opérations, les paramètres internes des couches (filtres des convolutions et matrice) sont créés et initialisés. C'est donc l'équivalent des lignes "fa=... fb=... m=..." dans le code "circlesquare".
La deuxième fonction "forward" définit le calcul à effectuer pour appliquer le réseau à des données : on exécute les trois couches successivement, en intercalant un "reshape" avant la dernière opération pour être compatible avec la dernière multiplication matricielle simple.
# Convolutional neural network (two convolutional layers)
class ConvNet(nn.Module):
def __init__(self, num_classes=10):
super(ConvNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.fc = nn.Linear(7*7*32, num_classes)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
return out
model = ConvNet(num_classes).to(device)
# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
A noter que le chargement des données à chaque itération interne se fait via l'objet train_loader qui est un fait un itérateur : on peut créer une boucle permettant de charger chaque paquet de donnée successivement via la commande
for images, labels in train_loader:
... # dans ce bloc images et labels contiennent le paquet de données courant
... # (100 images et leurs labels correspondants)
Ici la syntaxe utilisée pour la boucle for ajoute "enumerate" qui fournit simplement un compteur i=0,1,2,... en plus.
Remarque : attention cette étape est évidemment la plus longue ; en l'absence de GPU, il faut attendre plusieurs minutes pour qu'elle se termine (le message "training done" doit apparaître à la fin)
# Train the model
print("beginning training...")
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
# Forward pass
outputs = model(images)
loss = criterion(outputs, labels)
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i+1) % 100 == 0:
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))
print("training done")
beginning training... Epoch [1/5], Step [100/600], Loss: 0.2157 Epoch [1/5], Step [200/600], Loss: 0.0678 Epoch [1/5], Step [300/600], Loss: 0.1204 Epoch [1/5], Step [400/600], Loss: 0.0394 Epoch [1/5], Step [500/600], Loss: 0.0518 Epoch [1/5], Step [600/600], Loss: 0.0546 Epoch [2/5], Step [100/600], Loss: 0.0642 Epoch [2/5], Step [200/600], Loss: 0.0473 Epoch [2/5], Step [300/600], Loss: 0.0166 Epoch [2/5], Step [400/600], Loss: 0.0304 Epoch [2/5], Step [500/600], Loss: 0.0189 Epoch [2/5], Step [600/600], Loss: 0.0512 Epoch [3/5], Step [100/600], Loss: 0.0728 Epoch [3/5], Step [200/600], Loss: 0.0290 Epoch [3/5], Step [300/600], Loss: 0.0163 Epoch [3/5], Step [400/600], Loss: 0.0192 Epoch [3/5], Step [500/600], Loss: 0.0211 Epoch [3/5], Step [600/600], Loss: 0.0376 Epoch [4/5], Step [100/600], Loss: 0.1209 Epoch [4/5], Step [200/600], Loss: 0.0611 Epoch [4/5], Step [300/600], Loss: 0.0071 Epoch [4/5], Step [400/600], Loss: 0.0324 Epoch [4/5], Step [500/600], Loss: 0.0413 Epoch [4/5], Step [600/600], Loss: 0.0040 Epoch [5/5], Step [100/600], Loss: 0.0082 Epoch [5/5], Step [200/600], Loss: 0.0666 Epoch [5/5], Step [300/600], Loss: 0.0070 Epoch [5/5], Step [400/600], Loss: 0.0619 Epoch [5/5], Step [500/600], Loss: 0.0048 Epoch [5/5], Step [600/600], Loss: 0.0156 training done
from torchviz import make_dot
make_dot(loss)
# Test the model
model.eval() # eval mode (batchnorm uses moving mean/variance instead of mini-batch mean/variance)
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Test Accuracy of the model on the 10000 test images: {} %'.format(100 * correct / total))
Test Accuracy of the model on the 10000 test images: 98.32 %
import numpy as np
import matplotlib.pyplot as plt
for k in range(5):
plt.subplot(1, 5, k+1)
plt.imshow(images[k,0,:,:])
print("vrais labels : ",labels[0:5])
outputs = model(images[0:5,:,:,:])
_, predicted = torch.max(outputs.data, 1)
print("labels prédits par le réseau : ",predicted)
vrais labels : tensor([8, 9, 0, 1, 2]) labels prédits par le réseau : tensor([8, 9, 0, 1, 8])
image80 = images[2,:,:,:]+images[3,:,:,:]
image80 = image80.unsqueeze(1)
outputs = model(image80)
_, predicted = torch.max(outputs.data, 1)
plt.imshow(image80[0,0,:,:])
predicted
tensor([0])
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
if (predicted == labels).sum().item()<100:
break
ind, = np.where((predicted!=labels).data.numpy())
print("vrais labels : ",labels[ind])
print("labels prédits : ",predicted[ind])
for k in range(len(ind)):
plt.subplot(1,len(ind),k+1)
plt.imshow(images[ind[k],0,:,:])
vrais labels : tensor([9, 9]) labels prédits : tensor([5, 4])