import numpy as np
import torch
from torch.utils import data
import plotly.io as pio
pio.renderers.default = 'notebook'
from matplotlib import pyplot as plt
from d2l import torch as d2l #pip install d2l ou pip install d2l==0.17.0
## à consulter ici pour les fonctions disponibles
## https://github.com/d2l-ai/d2l-en/blob/master/d2l/torch.py
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
print(features.shape)
print(labels.shape)
print(features[1:100])
print(labels[1:100])
Une fonction pour gérer les minibatch de données¶
def load_array(data_arrays, batch_size, is_train=True): #@save
"""Construct a PyTorch data iterator."""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size, is_train=True)
my_iter = iter(data_iter)
next(my_iter)
Un réseau de neurones avec une seule couche¶
Dans PyTorch, une couche entièrement connectée est définie dans la classe Linear. Notez que nous avons passé deux arguments dans nn.Linear. Le premier spécifie la dimension de la donnée d'entrée qui est $2$, et le second est la dimension de la donnée de sortie, qui est un scalaire unique et donc $1$.
# `nn` désigne neural networks
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
#help(nn.Linear)
Initialisation des paramètres du modèle¶
Avant d'utiliser l'objet net, nous devons initialiser les paramètres du modèle, comme les poids et le biais dans le modèle de régression linéaire. Ici, nous spécifions que chaque paramètre de poids doit être échantillonné de manière aléatoire à partir d'une distribution normale avec une moyenne de $0$ et un écart type de $0.01$. Le paramètre de biais sera initialisé à zéro.
Comme nous avons spécifié les dimensions d'entrée et de sortie lors de la construction de nn.Linear, nous pouvons maintenant accéder directement aux paramètres pour spécifier leurs valeurs initiales. Nous localisons d'abord la couche par net[0], qui est la première couche du réseau, puis nous utilisons les méthodes weight.data et bias.data pour accéder aux paramètres. Ensuite, nous utilisons les méthodes de remplacement normal_ et fill_ pour écraser les valeurs des paramètres.
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
Déclaration de la fonction de perte¶
loss = nn.MSELoss()
Déclaration de l'algorithme d'optimisation¶
La descente de gradient stochastique par minibatch est un outil standard pour l'optimisation des réseaux de neurones et PyTorch le prend donc en charge, ainsi qu'un certain nombre de variations de cet algorithme dans le module optim. Lorsque nous instancions une instance SGD, nous spécifions les paramètres à optimiser (que nous pouvons obtenir de notre réseau via net.parameters()), avec un dictionnaire d'hyperparamètres requis par notre algorithme d'optimisation. La descente de gradient stochastique par minibatch nécessite simplement que nous définissions la valeur lr, qui est fixée à $0.03$ ici.
trainer = torch.optim.SGD(net.parameters(), lr=0.001)
Entrainement¶
Pendant un certain nombre d'époques (epoch), nous ferons un passage complet sur l'ensemble de données (train_data), en saisissant itérativement un minibatch d'entrées et les vraies valeurs de la variable réponse (labels). Pour chaque minibatch, nous suivons le étapes suivantes :
Générer des prédictions en appelant net(X) et calculer la perte $l$ (la propagation en forward).
Calculer les gradients en exécutant la rétropropagation (propagation en backward).
Mettre à jour les paramètres du modèle via un appel à l'optimiseur.
Nous calculons et affichons la perte après chaque epoch pour suivre la progression.
num_epochs = 10
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
Ci-dessous, nous comparons les paramètres du modèle appris par l'entraînement sur les données et les paramètres réels qui ont généré les données.
w = net[0].weight.data
print('error in estimating w:', true_w - w.reshape(true_w.shape))
print('estimated w: ', w.reshape(true_w.shape))
print('true w: ', true_w)
b = net[0].bias.data
print('error in estimating b:', true_b - b)
print('estimated b: ', b)
print('true b:', true_b)
Jeu de données de classification d'images¶
L'un des jeux de données les plus utilisés pour la classification d'images est le jeu de données MNIST [LeCun et al., 1998]. Bien qu'il ait eu un bon parcours en tant que jeu de données de référence, même les modèles simples selon les normes actuelles atteignent une précision de classification supérieure à 95 %, ce qui le rend impropre à distinguer les modèles les plus forts des plus faibles. Aujourd'hui, MNIST sert davantage de vérification que de référence. Nous allons nous concentrer sur le jeu de données Fashion-MNIST [Xiao et al., 2017], qualitativement similaire mais comparativement complexe, qui a été publié en 2017.
%matplotlib widget
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display()
Lecture du jeu de données¶
Nous pouvons télécharger et lire le jeu de données Fashion-MNIST en mémoire grâce aux fonctions intégrées.
# `ToTensor` va convertir les images du format de stockage PIL au type float 32-bit
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True)
Fashion-MNIST se compose d'images de 10 catégories, chacune représentée par 6000 images dans l'ensemble de données d'entraînement et par 1000 dans l'ensemble de données de test. Un ensemble de données de test (ou ensemble de test) est utilisé pour évaluer les performances du modèle et non pour l'apprentissage. Par conséquent, l'ensemble d'entraînement et l'ensemble de test contiennent respectivement 60000 et 10000 images.
len(mnist_train), len(mnist_test)
La hauteur et la largeur de chaque image d'entrée sont toutes les deux de $28$ pixels. Notez que le jeu de données est constitué d'images en niveaux de gris, dont le nombre de canaux est de $1$. Une image est de hauteur $h$, de largeur $w$ pixels sous la forme $h \times w$ ou $(h, w)$.
mnist_train[0][0].shape
Les images de Fashion-MNIST sont associées aux catégories suivantes : t-shirt, pantalon, pull-over, robe, manteau, sandale, chemise, basket, sac et bottine. La fonction suivante permet de convertir les indices numériques des étiquettes en leurs noms dans le texte.
def get_fashion_mnist_labels(labels): #@save
"""Affichage les labels des images Fashion-MNIST"""
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): #@save
""" Affichage d'une liste d'images"""
figsize = (num_cols * scale, num_rows * scale)
_, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
# Tensor Image
ax.imshow(img.numpy())
else:
# PIL Image
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes
Voici les images et leurs étiquettes correspondantes (en texte) pour les premiers exemples de l'ensemble de données d'entraînement.
myiter = iter(data.DataLoader(mnist_train, batch_size=18))
X, y = next(myiter)
show_images(X.reshape(18, 28, 28), 3, 6, titles=get_fashion_mnist_labels(y));
Lecture des minibatch¶
Pour nous faciliter la vie lors de la lecture des ensembles d'apprentissage et de test, nous utilisons l'itérateur de données intégré plutôt que d'en créer un de toutes pièces. Rappelons qu'à chaque itération, un itérateur de données lit un mini-batch de données de taille batch_size à chaque fois. Nous mélangeons également de manière aléatoire les exemples pour l'itérateur de données d'apprentissage.
## une fonction où on peut déclarer le nombre de cpu à utiliser
import os
os.cpu_count()
def get_dataloader_workers(): #@save
"""Une fonction pour spécifier le nombre de processeurs à utiliser, ici 12 - 1"""
return os.cpu_count() - 1
#return 1
batch_size = 256
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
timer = d2l.Timer()
for X, y in train_iter:
continue
f'{timer.stop():.2f} sec'