PyTorch

Débuter avec PyTorch

PyTorch est un Framework Python de calcul scientifique développé par Facebook. Il a deux objectifs principaux :

  • Un remplacement de NumPy pour utiliser la puissance des GPU et d’autres accélérateurs.
  • Une bibliothèque de différenciation automatique Autograd qui est utile pour entraîner des réseaux de neurones.

Dans cette introduction à PyTorch, je présente brièvement les tenseurs qui sont au cœur du fonctionnement du Framework PyTorch. Ensuite, j’introduis la bibliothèque Autograd de PyTorch. Enfin, je vous aiderai à construire votre premier modèle de régression logistique sur PyTorch !

Connaissances requises : Python et NumPy, bases des réseaux de neurones.

Outils pour le code : Jupyter Notebook avec PyTorch installé ou Google Colab.

Les tenseurs

Un tenseur est une structure de données importante dans PyTorch et également dans d’autres frameworks d’apprentissage profond tels que Tensorflow. C’est une généralisation d’un vecteur ou d’une matrice à un nombre arbitraire de dimensions.  L’avantage d’un tenseur par rapport à un tableau NumPy (NumPy array) est qu’il sert à effectuer des opérations très rapides sur des GPU, à distribuer le calcul sur plusieurs machines et à garder trace des opérations qui l’ont créé.

Vous pouvez commencer par vous-même à manipuler des tenseurs. Voici des références qui vous seront utiles : 

Je vous montre également des exemples d’opérations en quelques lignes de code :

## importer la bibliothèque PyTorch
import torch 

## Définir un tenseur 1D rempli d'entiers de 1 à 6 
tens = torch.arange(1,7)
# ==> retourne tensor([1, 2, 3, 4, 5, 6])

## Créer un tableau NumPy à partir du tenseur
import numpy as np
tens_np = np.array(tens)
# ==> retourne array([1, 2, 3, 4, 5, 6])

## Transformer le tenseur en dimension (2,3)
tens.view(2,3)
# ==> retourne tensor([[1, 2, 3],
        #[4, 5, 6]])

## Effectuer une somme par ligne et une somme par colonne du tenseur de dimension (2,3)
transformed = tens.view(2,3)
transformed.sum(dim = 0) ## par colonne 
# ==> retourne tensor([5, 7, 9])

transformed.sum(dim = 1) ## par ligne
# ==> retourne tensor([ 6, 15])

## Modifier l'élément qui se trouve à la position 1,1
transformed[1,1] = 10
# ==> retourne tensor([[ 1,  2,  3],
        #[ 4, 10,  6]])

PyTorch Autograd 

La bibliothèque Autograd permet le calcul automatique de gradients d’une fonction par rapport à ses paramètres. Elle sert ainsi à effectuer facilement l’étape de backpropagation lors de l’entraînement d’un réseau de neurones. En effet, lorsqu’un tenseur est créé avec l’option require_grad = True, ceci signale à Autograd que chaque opération effectuée sur ce tenseur doit être gardée en mémoire. Le code suivant montre un exemple de calcul de gradients avec Autograd.

## définir un tenseur aléatoire
x = torch.randn(2, 2, requires_grad=True)

## effectuer une opération sur le tenseur : somme (xi**2)
print(x, x.dtype, x.shape)

## Attention l’opération doit retourner un scalaire pour que la fonction backward fonctionne
out = x.pow(2).sum() ## scalaire
print(out, out.dtype, out.shape)

## étape de rétropropagation ou backward pour calculer les gradients de out par rapport à la variable x
out.backward() 

## Le gradient est stocké dans x.grad ; pour vérifier le résultat, on devrait avoir 2*x
print(x.grad)
print(2*x)

Voici un tutoriel du site de PyToch qui vous permettra d’approfondir vos connaissances sur le fonctionnement d’Autograd : https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py

Construction d’un simple modèle de régression logistique sur PyTorch

Pour faire simple, je construis un jeu de données pour de la classification binaire. Les données doivent être transformées en tenseurs parce que les modules de PyTorch sont compatibles avec les tenseurs.

## Deux classes d'étudiants 7 dans chacune et 2 attributs par individu
ST1 = np.array([[17.0 ,12 ,13 ,15 ,15 ,20 ,20],[ 10 ,12 ,14 ,15 ,20 ,15 ,20]]) # classe 1 
ST2 = np.array([4, 7.5, 10 ,11, 5 ,5 ,6, 8, 5, 0, 5, 0, 10, 6]).reshape(2,7) # classe 2 

## Concaténer les deux tableaux NumPy 
Xstudents = np.concatenate((ST1,ST2),axis=1)

## Construire les labels 1 pour la classe 1 et 0 pour la classe 2
Ystudents = np.ones(14)
Ystudents[7:] = 0

## Transformer les données en Tenseurs 
X = torch.FloatTensor(Xstudents.T)
Y = torch.FloatTensor(Ystudents)

print("Dimension de Y",Y.shape) ## 14

print("Dimension de X", X.shape) ## 14,2


Le jeu de données étant préparé, voici les étapes pour définir un modèle de régression logistique et l’entraîner : 

  • Créer un modèle de régression (une couche linéaire avec une seule neurone et la fonction d’activation logistique).
  • Définir l’optimiseur (optimizer) ; on peut prendre par exemple la méthode du gradient stochastique (SGD).
  • Définir la fonction objectif à optimiser.
  • Écrire la boucle d’entraînement et l’exécuter jusqu’à convergence. Il peut être utile de jouer avec le pas d’apprentissage. Exécuter la descente du gradient exemple par exemple.
  • Explorer le modèle appris.

Pour la définition du modèle et de ses paramètres, on peut exécuter le code suivant :

# Le modèle
D_in = 2  # dimension de l'input : 2 
D_out = 1 # dimension de l'output : 1

### Le module "Sequential" est un module "container" qui permet de définir un réseau feed forward
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, D_out), ## couche linéaire z = Wx
    torch.nn.Sigmoid()    ## une activation sigmoid
)
print(model) 
### >> retourne 
###Sequential(
 ## (0): Linear(in_features=2, out_features=1, bias=True)
 #"" (1): Sigmoid()
##)


loss_fn = torch.nn.BCELoss() # La fonction de coût binary cross entropy
learning_rate = 1e-2   ## le pas d'apprentissage
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # l'optimiseur

On peut tester notre modèle pour voir si les dimensions des tenseurs d’Input sont compatibles avec notre définition du modèle.

# Test Avec un seul Input
prediction = model(X[0]) # ou prediction = model.forward(X[0]) les deux sont équivalents

print("Pour le premier Input: ",prediction)

# Test avec les trois premiers Inputs
prediction = model(X[0:3])
print("Pour les trois premiers Inputs: ",prediction)

# Test avec tout le dataset
prediction = model(X)
print("For all: ",prediction)

## Attention, pour calculer la fonction de coût entre la prédiction et le vrai label, il faut adapter les dimensions : 
prediction = model(X[0])
Ymodified = Y.view(-1,1) 
print("La première prédiction: ",prediction, prediction.shape)
print("Le label: ",Ymodified[0], Ymodified[0].shape)

print("La valeur de la loss : ",loss_fn(prediction,Ymodified[0])

Le modèle est compatible avec les données. Il reste à définir la boucle d’entraînement ce qui est assez direct et simple avec PyTorch.

Nepochs = 100 # Nombre d'époques (Nombre de passes sur toutes les données) 
Nprint  = Nepochs/10  # fréquence de print
for epoch in range(Nepochs):
    total=0.
    for i in range(14):
        optimizer.zero_grad()   ## réinitialiser les gradients
        prediction = model(X[i]).squeeze()   ## forward pass 
        loss = loss_fn(prediction, Y[i]) ## calcul de la fonction de coût courante
        loss.backward()  ## backpropagation pass à travers le réseau       
        optimizer.step() ## mise à jour des paramètres du réseau ( w = w -lr * w.grad) équivalent à une itération du SGD
        total+=loss ## calcul de la loss sur tous les exemples à chaque époque
    if epoch%Nprint==0:
        print(epoch,total)

Nous avons vu la notion de tenseurs sur PyTorch, nous avons compris le rôle d’Autograd et implémenté un modèle simple de régression logistique sur un jeu de données. PyTorch propose des fonctionnalités encore plus riches et on peut construire des modèles beaucoup plus avancés ! 

On peut générer par exemple des mini-batchs de données à chaque pas de la boucle d’entraînement pour accélérer ce dernier et utiliser des optimiseurs encore plus performants que SGD tels que ADAM.

Il est possible de calculer une métrique comme l’accuracy sur le jeu de données. On n’a pas réalisé un partage des données en train/test dans notre exemple mais c’est une pratique tout à fait courante en apprentissage automatique.. et encore bien d’autres choses peuvent être explorées avec PyTorch !

Pour terminer, il existe une méthode alternative que le module Sequential pour créer des modèles plus complexes de réseaux de neurones. Cette méthode utilise le nn.Module qui est une classe qui contient des couches et une méthode forward(input) pour effectuer la forward pass

Définition alternative du modèle précédent :

class LogisticRegression(torch.nn.Module):## notre modèle est une classe qui hérite de nn.Module
  def __init__(self,D_in): ## constructeur pour définir les couches du modèle
    super(LogisticRegression, self).__init__()
    self.lin = torch.nn.Linear(D_in, 1) ## couche linéaire
    self.out = torch.nn.Sigmoid() ## couche sigmoid
  def forward(self, x): ## forward pass en tant que méthode
    a = self.lin(x) ## appliquer les couches une après l'autre
    return self.out(a) 
mod = LogisticRegression(D_in=2)  ## instancier un modèle

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.