Pytorch tutorial : Introduction

Quand j’ai écris ce billet de blog à propos du pytorch tutorial, je me suis souvenu du challenge que je me suis fixé en début d’année d’apprendre le deep learning, je ne connaissais alors même pas Python. Ce qui rends les choses difficiles ce n’est pas forcément la complexité des concepts, mais cela commence par des questions du type : Quel framework utiliser pour le Deep learning ? Quel fonction d’activation est ce que je dois choisir ? Quel est la fonction de côut la mieux adaptée pour mon problème ?
Mon expérience personnelle a été d’étudier le framework PyTorch et surtout pour la théorie le cours en ligne mis à disposition gratuitement par Yan LeCun que je remercie (lien ici https://atcold.github.io/pytorch-Deep-Learning/). Il me reste encore à apprendre et travailler sur le domaine mais par ce billet de blog, j’aimerais partager et vous donner un panorama de ce que j’ai appris sur le deep learning cette année.

Deep Learning : Quelle fonction d’activation et de coût choisir ?
L’objectif de cet article est de balayer ce sujet central en DeepLearning du choix la fonction d’activation et de coût (loss) en fonction du problème que vous cherchez à résoudre au moyen d’un algorithme de DeepLearning.
On parle ici de la fonction d’activation de la dernière couche de votre modèle c’est à dire celle qui vous donne le résultat.
Ce résultat est utilisé dans l’algorithme qui vérifie à chaque apprentissage la différence entre le résultat prédit par le réseau de neurones et le résultat réel (algorithme de descente du gradient), pour cela on applique au résultat une fonction de coût (loss function) qui représente cette différence et que vous cherchez à minimiser au fil des entrainement afin de réduire l’erreur. (Voir cet article ici pour en savoir plus : https://machinelearnia.com/descente-de-gradient/)

L’algorithe de descente de Gradient permet de trouver le minimum de n’importe quelle fonction convexe pas à pas.
Pour rappel la machine apprends en minimisant la fonction de coût, itérativement par étapes d’entrainement successif, le résultat de la fonction de coût et pris en compte pour l’ajustement des paramètres des neurones (poids et biais par exemple pour les couches linéaires).
Ce choix de fonction d’activation sur la dernière couche et la fonction de coût à minimiser (loss function) est donc crucial puisque ces deux éléments combinés vont déterminer comment vous allez résoudre un problème au moyen de votre réseau de neurones.

Cet article présente également des exemples simples qui utilisent le framework Pytorch d’après moi un très bon outil pour le machine learning.
La question ici à se poser est la suivante :
- Est ce que je cherche à créer un modèle qui effectue une classification binaire ? (Vous cherchez dans ce cas à prédire une probabilité que le résultat d’une entrée soit 0 ou 1)
- Est ce que je cherche à calculer/prédire une valeur numérique avec mon réseau de neurones ? (Vous cherchez dans ce cas à prédire une valeur par exemple décimale en sortie qui corresponds à votre entrée)
- Est ce que je cherche à créer un modèle qui effectue une classification d’un label simple ou multiple pour un ensemble de classes ? (Vous cherchez dans ce cas à prédire pour chaque classe en sortie la probabilité qu’une entrée corresponde à cette classe)
- Est ce que je cherche à créer un modèle qui effectue la recherche de plusieurs classes dans un ensemble de classes possibles ? (Vous cherchez dans ce cas à prédire pour chaque classe en sortie son taux de présence dans l’entrée).
Problème de classification binaire :
Vous cherchez à prédire au moyen de votre algorithme de DeepLearning qu’un résultat est vrai ou faux, et plus précisément c’est une probabilité qu’un résultat soit 1 ou qu’un résultat soit 0 que vous allez obtenir.

La taille de sortie de votre réseau de neurone est 1 (couche finale) et vous cherchez à obtenir un résultat entre 0 et 1 qui sera assimilé à la probabilité que le résultat soit 1 (exemple en sortie du réseau si vous obtenez 0.65 cela correspondra à 65% de chance que le résultat soit vrai).
Exemple d’application : Prédiction de la probabilité que l’équipe à domicile d’un match de football gagne. Plus la valeur est proche de 1 plus l’équipe à domicile a des chances de gagner, et inversement plus le résultat se rapproche de 0 plus l’équipe à domicile a des chance de perdre.
Il est possible de qualifier ce résultat en utilisant un seuil, par exemple en admettant que si la sortie est > 0.5 alors le résultat est vrai sinon faux.
Pytorch tutorial – Fonction d’activation finale (cas classification binaire) :
La fonction d’activation finale doit retourner un résultat entre 0 et 1, le bon choix dans ce cas peut être la fonction sigmoïde. La fonction sigmoïde permettra de traduire facilement un résultat entre 0 et 1 et donc est idéal pour traduire la probabilité qu’on cherche.à prédire.
Si vous souhaitez tracer la fonction sigmoïde en Python voici un code qui devrait vous aider (voir également https://squall0032.tumblr.com/post/77300791096/plotting-a-sigmoid-function-using-python-matplotlib) :
import math import numpy as np import matplotlib.pyplot as plt def sigmoid(x): a = [] for item in x: a.append(1/(1+math.exp(-item))) return a x = np.arange(-10., 10., 0.2) sig = sigmoid(x) plt.plot(x,sig) plt.show()

La fonction de coût – Loss function (cas classification binaire) :
Il faut déterminer lors de l’entrainement la différence entre la probabilité que le modèle prédit (traduite via la fonction finale sigmoid) et la réponse vraie et connue (0 ou 1). La fonction à utiliser est Binary Cross Entrropy car cette fonction permet de calculer la différence entre 2 probabilité.
L’optimiseur va ensuite utiliser ce résultat pour ajuster les poids et biais dans votre modèle (ou les autres paramètres selon l’architecture de votre modèle).
Exemple :
Dans cet exemple je vais créer un réseau de neurones avec 1 couche linéaire et une fonction d’activation finale sigmoïde.
Dans un premier temps, au travers de ce pytorch tutorial, j’effectue tous les imports que nous allons utiliser dans ce billet :
import numpy as np import pandas as pd import matplotlib.pyplot as plt from collections import OrderedDict import math from random import randrange import torch from torch.autograd import Variable import torch.nn as nn from torch.autograd import Function from torch.nn.parameter import Parameter from torch import optim import torch.nn.functional as F from torchvision import datasets, transforms from echoAI.Activation.Torch.bent_id import BentID from sklearn.model_selection import train_test_split import sklearn.datasets from sklearn.metrics import accuracy_score import hiddenlayer as hl import warnings warnings.filterwarnings("ignore") from torchviz import make_dot, make_dot_from_trace
Les lignes suivantes permettent de ne pas afficher les warning en python :
import warnings warnings.filterwarnings("ignore")
Je vais générer 1000 échantillons générés par la librairie sklearn me permettant d’avoir un jeu de tests pour un problème de classification binaire. Le jeu de tests porte sur 2 entrées décimales et une sortie binaire (0 ou 1). J’affiche également le résultat sous forme d’un nuage de point :
inputData,outputData = sklearn.datasets.make_moons(1000,noise=0.3) plt.scatter(inputData[:,0],inputData[:,1],s=40,c=outputData,cmap = plt.cm.get_cmap("Spectral"))

Le jeu de test ici généré correspond à un jeu de données avec 2 classes (classe 0 et 1).
L’objectif de mon réseau de neurone est donc une classification binaire de l’input.
Je vais prendre 20% de ce jeu de test comme donnée de test et les 80% restant comme données d’entrainement. Je split à nouveau le jeu de test pour créer un jeu de validation sur le jeu de données d’entrainement.
X_train, X_test, y_train, y_test = train_test_split(inputData, outputData, test_size=0.20, random_state=42)


Dans mon exemple je vais créer un réseau avec une architecture quelconque ce qui nous intéresse c’est de montrer le fonctionnement de la dernière couche :
- couche d’entrée (input layer) : fonction d’activation linéaire
- couches cachées (hidden layers) : fonction d’activation : fonction d’activation bent identity que j’importe du du package EchoAI qui implémente des fonctions d’activation supplémentaire pour Pytorch (https://echo-ai.readthedocs.io/en/latest/#implemented-activation-functions et https://echo-ai.readthedocs.io/en/latest/#torch-bent-id).
- fonction d’activation linéaire : couche de sortie avec une fonction d’activation choisie sigmoid comme expliqué ci-dessus, l’objectif est de ramener le résultat à une probabilité que l’input soit de la classe 0 ou 1. (https://pytorch.org/docs/stable/generated/torch.nn.Sigmoid.html#torch.nn.Sigmoid)
fc1 = nn.Linear(2, 2) fc2 = nn.Linear(2, 1) model = nn.Sequential(OrderedDict([ ('lin1', fc1), ('BentID1', BentID()), ('lin2', fc2), ('sigmoid', nn.Sigmoid()) ])) model = model.double()
J’ai ajouté une ligne permettant de transformer le modèle pytorch pour qu’il utilise le type double. Cela évite une erreur du type :
Expected object of scalar type Double but got scalar type Float for argument
Également il faudra utiliser dtype=torch.double lors de la création des torch.tensor à chaque entrainement.
Comme indiqué dans mon documents ci-desssus la fonction de coût est la fonction Binary Cross Entropy dans ce cas (BCELoss : https://pytorch.org/docs/stable/generated/torch.nn.BCELoss.html#torch.nn.BCELoss) :
loss = nn.BCELoss()
Dans le cadre de ce pytorch tutorial, J’utilise l’optomizer Adam avec un learning rate de 0.01 :
Les optimiseurs en deep learning sont des algorithmes ou des méthodes utilisés pour modifier les attributs de votre réseau neuronal tels que les poids et le biais de tel manière que la fonction de cout (loss function) soit minimisée.
Le taux d’apprentissage est un hyperparamètre qui contrôle dans quelle mesure le modèle doit être modifié en réponse à l’erreur estimée chaque fois que les poids du modèle sont mis à jour.
Ici j’utilise l’algorithme d’optimisation Adam. En Deep Learning Adam est une méthode de descente de gradient stochastique qui calcule les taux d’apprentissage adaptatif individuels pour différents paramètres à partir d’estimations des moments de premier et de second ordre des gradients.
Plus d’information sur l’optimiser Adam : https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/
Implémentation PyTorch de l’algorithme d’optimisation Adam : https://pytorch.org/docs/stable/optim.html?highlight=adam#torch.optim.Adam
optimizer = optim.Adam(model.parameters(), lr=0.01)
J’ai préalablement splits une seconde fois les données d’entrainement pour prendre 20% des données comme des données de validations. Elle nous permettent à chaque entrainement de valider que nous ne faisons pas de surraprentissage (sur-ajustement, ou encore surinterprétation, ou simplement en anglais overfitting) .
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.20, random_state=42)
Dans cet exemple j’ai choisi d’implémenter le EarlyStopping algorithme avec une patience de 5. Cela signifie que si la fonction de cout des données de validation augmente durant 15 entrainements (soit la distance entre la prédiction et les données vraies). Après 15 entrainements avec une distance qui augmente on recharge les données précédentes qui représente la configuration du réseau de neurone produisant une distance de la fonction de cout minimale (dans le cas de notre exemple cela consiste à recharger le poids et le biais pour les 2 couches linéaires). Tant que la fonction diminue on sauvegarde la configuration du réseau de neurones (poids et biais des couches linéaire dans notre exemple).
Ici j’ai choisi d’afficher les poids et le biais des 2 couches linéaire tous les 10 entrainements, pour vous donner une idée de comment fonctionne l’algorithme d’optimisation Adam :
J’ai utilisé https://github.com/waleedka/hiddenlayer pour afficher divers graphique autour des métriques de training et validation. Les graphiques sont ainsi rafraîchi en cours d’utilisation.
history1 = hl.History() canvas1 = hl.Canvas()
Ici le code principal de la boucle d’apprentissage :
bestvalidloss = np.inf saveWeight1 = fc1.weight saveWeight2 = fc2.weight saveBias1 = fc1.bias saveBias2 = fc2.bias wait = 0 for epoch in range(1000): model.train() optimizer.zero_grad() result = model(torch.tensor(X_train,dtype=torch.double)) lossoutput = loss(result, torch.tensor(y_train,dtype=torch.double)) lossoutput.backward() optimizer.step() print("EPOCH " + str(epoch) + "- train loss = " + str(lossoutput.item())) if((epoch+1)%10==0): #AFFICHE LES PARAMETRES DU RESEAU TOUT LES 10 entrainements print("*************** PARAMETERS WEIGHT & BIAS *********************") print("weight linear1= " + str(fc1.weight)) print('Bias linear1=' + str(fc1.bias)) print("weight linear2= " + str(fc2.weight)) print('bias linear2=' + str(fc2.bias)) print("**************************************************************") model.eval() validpred = model(torch.tensor(X_valid,dtype=torch.double)) validloss = loss(validpred, torch.tensor(y_valid,dtype=torch.double)) print("EPOCH " + str(epoch) + "- valid loss = " + str(validloss.item())) # Store and plot train and valid loss. history1.log(epoch, trainloss=lossoutput.item(),validloss=validloss.item()) canvas1.draw_plot([history1["trainloss"], history1["validloss"]]) if(validloss.item() < bestvalidloss): bestvalidloss = validloss.item() # saveWeight1 = fc1.weight saveWeight2 = fc2.weight saveBias1 = fc1.bias saveBias2 = fc2.bias wait = 0 else: wait += 1 if(wait > 15): #Restauration des mailleurs parametre et early stopping fc1.weight = saveWeight1 fc2.weight = saveWeight2 fc1.bias = saveBias1 fc2.bias = saveBias2 print("##############################################################") print("stop valid because loss is increasing (EARLY STOPPING) afte EPOCH=" + str(epoch)) print("BEST VALID LOSS = " + str(bestvalidloss)) print("BEST PARAMETERS WHEIGHT AND BIAS = ") print("FIRST LINEAR : WEIGHT=" + str(saveWeight1) + " BIAS = " + str(saveBias1)) print("SECOND LINEAR : WEIGHT=" + str(saveWeight2) + " BIAS = " + str(saveBias2)) print("##############################################################") break else: continue
Voici un aperçu du résultat, l’entraînement s’arrête au bout d’environ 200 epoch (une « epoch » est un terme utilisé en machine learning pour désigner un passage du jeu de donnée d’entrainement complet). Dans mon exemple je n’ai pas utilisé de batch pour charger les données le nombre d’epoch est donc le nombre d’itérations.

Evolution de la fonction de cout sur le test d’entrainement et le test de validation (pytorch tutorial)
Voici un aperçu des résultats de la prédiction :
result = model(torch.tensor(X_test,dtype=torch.double)) plt.scatter(X_test[:,0],X_test[:,1],s=40,c=y_test,cmap = plt.cm.get_cmap("Spectral")) plt.title("True") plt.colorbar() plt.show() plt.scatter(X_test[:,0],X_test[:,1],s=40,c=result.data.numpy(),cmap = plt.cm.get_cmap("Spectral")) plt.title("predicted") plt.colorbar() plt.show()

Pour afficher l’architecture du réseau de neurone j’ai utilisé la librairie hidden layer qui nécessite l’installation de graphviz pour éviter l’erreur suivante :
RuntimeError: Make sure the Graphviz executables are on your system's path
Pour résoudre cette erreur voir https://graphviz.org/download/
Sur Mac OS X j’ai par exemple installé via Homebrew :
brew install graphviz
Voici un aperçu du graphe de la hidden layer :
# HiddenLayer graph hl.build_graph(model, torch.zeros(2,dtype=torch.double))
J’ai également utilisé PyTorchViz https://github.com/szagoruyko/pytorchviz/blob/master/examples.ipynb pour afficher un graphe des opérations de Pytorch pendant le forward d’une entrée et on peut voir ainsi clairement ce qui va se passer lors du backward (c’est à dire l’application le calcul de la différentiel dols/dx pour tout les paramètres x du réseau qui a requires_grad=True).
make_dot(model(torch.ones(2,dtype=torch.double)), params=dict(model.named_parameters()))
Problème de régression (calcul de valeur numérique/décimale) :
Vous cherchez dans ce cas à prédire au moyen de votre algorithme de DeepLearning une grandeur numérique continue.
Dans ce cas l’algorithme de descente du gradient consiste a comparer la différence entre la valeur numérique prédite par le réseau et la valeur vraie.
La taille de sortie du réseau sera 1 puisqu’il y a une seule valeur à prédire.

Fonction d’activation finale (cas valeur décimale ou numérique) :
La fonction d’activation à utiliser dans ce cas dépend de la plage dans laquelle votre donnée est située.
Pour une donnée entre -infinie et + infinie alors vous pouvez utiliser une fonction linéaire en sortie de votre réseau.
Fonction graphe de fonction Linear. https://pytorch.org/docs/stable/generated/torch.nn.Linear.html. La particularité est que cette fonction linéaire (du type y = ax + b)contient des paramètres apprenable (weight and bias qui sont modifiés par l’optimizer au fil des entrainements soit y = weight * x + bias).

Vous pouvez également utiliser la fonction Relu si votre valeur à prédire est strictement positive
la sortie de ReLu est la valeur maximale entre zéro et la valeur d’entrée. Une sortie est égale à zéro lorsque la valeur d’entrée est négative et la valeur d’entrée lorsque l’entrée est positive.
A noter que contrairement à la fonction d’activation linéaire rectifiée Relu ne possède pas de paramètre ajustable (learnable).
https://pytorch.org/docs/stable/generated/torch.nn.ReLU.html
Une autre fonction possible est PReLu (unité de rectification linéaire paramétrique) :
Source https://upload.wikimedia.org/wikipedia/commons/a/ae/Activation_prelu.svg
Egalement une autre fonction d’activation finale possible est Bent identity (identité courbée).
Enfin je recommande l’utilisation possible de Exponentielle douce paramétrique (Soft exponential), j’utilise l’implément de echoAi car elle n’est pas nativement dans PyTorch
https://echo-ai.readthedocs.io/en/latest/#torch-soft-exponential
Source https://upload.wikimedia.org/wikipedia/commons/b/b5/Activation_soft_exponential.svg
La fonction de coût (cas régression, calcul de valeur numérique) :
La fonction de cout qui permet de déterminer la distance entre la valeur prédite et la valeur réelle est la moyenne de la distance au carré entre les 2 prédictions. La fonction à utiliser est mean squared error (MSE):https://pytorch.org/docs/stable/generated/torch.nn.MSELoss.html
Voici un exemple simple qui illustre l’utilisation de chacune des fonctions finales :
Je suis parti au départ de l’exemple du prix des maisons que j’ai ensuite adapté pour faire le test avec chacune des fonctions.
Exemple : Chargement des données de tests
Dans un premier temps j’importe les librairies nécessaires :
import numpy as np import pandas as pd import matplotlib.pyplot as plt from collections import OrderedDict import math from random import randrange import torch from torch.autograd import Variable import torch.nn as nn from torch.autograd import Function from torch.nn.parameter import Parameter from torch import optim import torch.nn.functional as F from torchvision import datasets, transforms from echoAI.Activation.Torch.bent_id import BentID from echoAI.Activation.Torch.soft_exponential import SoftExponential from sklearn.model_selection import train_test_split import sklearn.datasets from sklearn.metrics import accuracy_score import hiddenlayer as hl import warnings warnings.filterwarnings("ignore") from torchviz import make_dot, make_dot_from_trace from sklearn.datasets import make_regression from torch.autograd import Variable
Je crée le jeu de données qui est assez simple et je l’affiche :
house_prices_array = [30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140] house_price_np = np.array(house_prices_array, dtype=np.float32) house_price_np = house_price_np.reshape(-1,1) house_price_tensor = Variable(torch.from_numpy(house_price_np)) house_size = [ 7.5, 7, 6.5, 6.0, 5.5, 5.0, 4.5,3.5,3.2,2.8,3.0,2.5] house_size_np = np.array(house_size, dtype=np.float32) house_size_np = house_size_np.reshape(-1, 1) house_size_tensor = Variable(torch.from_numpy(house_size_np)) import matplotlib.pyplot as plt plt.scatter(house_prices_array, house_size_np) plt.xlabel("House Price $") plt.ylabel("House Sizes") plt.title("House Price $ VS House Size") plt.show()

Dans l’exemple, on voit que que la fonction à trouver est proche de
f(x) = – 0.05 * x + 9
Exemple : – 0.05 * 40 + 9 = 7 et -0.05 * 30 + 9 = 7.5
J’initialise le biais et le poids à -0.01 et 8 pour limiter le temps d’entrainement.
Fonction d’activation linéaire (Résolution problème de régression):
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
Je déclare MSELoss et un optimizer Adam réglé avec un taux d’apprentissage 0.01
loss = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.01)
Voici la boucle d’entrainement :
history1 = hl.History() canvas1 = hl.Canvas() for epoch in range(100): model.train() optimizer.zero_grad() result = model(house_price_tensor) lossoutput = loss(result, house_size_tensor) lossoutput.backward() optimizer.step() print("EPOCH " + str(epoch) + "- train loss = " + str(lossoutput.item())) history1.log(epoch, trainloss=lossoutput.item()) canvas1.draw_plot([history1["trainloss"]])
Au bout d’une centaine d’entrainement on obtient un MSELoss = 0.13

Si j’affiche les poids et biais trouvé par le modèle j’obtiens dans mon exemple :
print("weight" + str(fc1.weight)) print("bias" + str(fc1.bias))

Le réseau éxecute donc ici la fonction f(x) = -0.0408 * x + 8.1321
J’affiche ensuite le résultat prédit pour le comparer avec le résultat réel :
result = model(house_price_tensor) plt.scatter(house_prices_array, house_size_np) plt.title("True") plt.show() plt.scatter(house_prices_array,result.detach().numpy()) plt.title("predicted") plt.show()

Voici un aprerçu du diagramme d’architecture pour un simple réseau de neurone Pytorch avec fonction Linéaire, on voit l’opérateur de multiplication et l’opérateur d’addition pour executer y = ax + b.
hl.build_graph(model, torch.ones(1,dtype=torch.float))
Fonction d’activation ReLu (Résolution problème de régression):
ReLu n’étant pas une fonction d’activation avec des paramètres « apprenables » (modifié par l’optimiseur) j’ajoute donc une couche linéaire en amont pour le test :
Le résultat est identique, ce qui est logique puisque dans mon cas reLu ne fait que passer le résultat qui est strictement positif
Dans l’exemple précédent il faut ajouter une couche ReLu en sortie après la couche linéaire :
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('relu',nn.ReLU()) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
De même pour PrRelu dans mon cas:
Ces Deux fonctions font simplement un passe plat entre la fonction linéaire et la sortie. ReLu reste intéressant si vous souhaitez mettre à 0 tout les sortie concernant une entrée négative.
Fonction Bent Identity (Résolution problème de régression) :
Bent identity il n’y a pas de paramètre apprenante mais la fonction ne fait pas que forwarder l’entrée elle y applique la fonction identité courbée qui modifie légèrement le résultat :
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('BentID',BentID()) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
Dans notre cas après 100 entrainements le réseau a trouvé les poids suivant pour la fonction linéaire en amont de bent identity le loss final est de 0.78 donc moin bon qu’avec la fonction linéaire (le réseau converge moins vite) :

Dans notre cas la fonction appliquée par le réseau sera donc :
-0.0481 ((math.sqt(x**2 + 1) -1)/2 + x) + 7.7386
Résultat obtenu avec Bent Identity :

hl.build_graph(model, torch.ones(1,dtype=torch.float))
J’affiche l’architécture du réseau Pytorch qui fait maintenant couche linéaire + couche bent identity :
Egalement :
make_dot(model(torch.ones(1,dtype=torch.float)), params=dict(model.named_parameters()))
Fonction exponentielle douce (Soft Exponential) – (Résolution de problème de régression)
Soft Exponential possède un paramètre entrainable alpha nous allons placer une fonction linéaire en amont et softExponential en sortie et vérifier l’approximation effectuée dans ce cas :
fc1 = nn.Linear(1, 1) fse = SoftExponential(1,torch.tensor([-0.9],dtype=torch.float)) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('SoftExponential',fse) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
Après 100 entrainements le résultats sur les paramètre de la couche linéaire et les paramètre de la couche soft exponential sont les suivants :
print("weight" + str(fc1.weight)) print("bias" + str(fc1.bias)) print("fse alpha" + str(fse.alpha))

Le résultat n’est pas vraiment bon puisque l’approximation ne se fait pas dans le bon sens,

On remarque donc qu’on pourrait inverser la fonction dans notre cas définir une fonction d’activation customize de Soft Exponential avec un critère de biais et c’est ce que nous allons faire ici.
Fonction Custom – (Résolution de problème de régression)
On déclare une fonction d’activation custom à PyTorch par rapport au soft exponential d’origine j’ai ajouté le critère de biais beta ainsi que l’inversion torch.div(1,…)
class SoftExponential2(nn.Module): def __init__(self, in_features, alpha=None,beta=None): super(SoftExponential2, self).__init__() self.in_features = in_features # initialize alpha if alpha is None: self.alpha = Parameter(torch.tensor(0.0)) # create a tensor out of alpha else: self.alpha = Parameter(torch.tensor(alpha)) # create a tensor out of alpha if beta is None: self.beta = Parameter(torch.tensor(0.0)) # create a tensor out of alpha else: self.beta = Parameter(torch.tensor(beta)) # create a tensor out of alpha self.alpha.requiresGrad = True # set requiresGrad to true! self.beta.requiresGrad = True # set requiresGrad to true! def forward(self, x): if self.alpha == 0.0: return x if self.alpha < 0.0: return torch.add(torch.div(1,(-torch.log(1 - self.alpha * (x + self.alpha)) / self.alpha)),self.beta) if self.alpha > 0.0: return torch.add(torch.div(1,((torch.exp(self.alpha * x) - 1) / self.alpha + self.alpha)),self.beta)
On a maintenant 2 paramètres entrainable dans cette fonction custom à Pytorch.
En abaissant également le taux d’apprentissage learning rate à 0.01 au bout de 100 entrainement et en initialisant alpha = 0 .1 et beta = 0.7 j’arrive à un loss < 5
fse = SoftExponential2(1,torch.tensor([0.1],dtype=torch.float),torch.tensor([7.0],dtype=torch.float)) model = nn.Sequential(OrderedDict([ ('SoftExponential',fse) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
print("fse alpha" + str(fse.alpha)) print("fse beta" + str(fse.beta))


Problème de catégorisation (prédire une classe parmi plusieurs classes possible) – single-label classifier avec pytorch
Vous cherchez à prédire la probabilité que votre entrée corresponde à une classe unique en sortie paris plusieurs classes possible.
Par exemple vous souhaitez prédire le type de véhicule sur une image permis les classes : voiture, camion, train

Le dimensionnement de votre réseau en sortie correspond à n neurones pour n classes. Et pour chaque neurones de sortie correspondant à une classe possible à prédire on obtiendra une probabilité entre 0 et 1 qui représentera la probabilité que l’entrée correspondent à cette classe en sortie. Ainsi pour chaque classe cela revient à une résolution d’un problème de classification binaire dans la partie 1 mais en plus duquel il faut considérer qu’une entrée ne peut correspondre qu’à une seule classe en sortie.
Fonction d’activation Problème de catégorisation (prédire une classe parmi plusieurs classes possibles) :
La fonction d’activation à utiliser sur la couche finale est une fonction Softfmax avec n dimensions correspondant au nombre de classe à prédire.
Softmax est une fonction mathématique qui convertit un vecteur de nombres (tensor) en un vecteur de probabilités, où les probabilités de chaque valeur sont proportionnelles à l’échelle relative de chaque valeur dans le vecteur.
(Cf https://machinelearningmastery.com/softmax-activation-function-with-python/)

En d’autres terme pour un tensor de sortie il va retourner une probabilité pour chaque classe en échelonnant chacune d’elle pour que leur somme soit égale à un.
https://pytorch.org/docs/stable/generated/torch.nn.Softmax.html
Fonction de cout – Problème de catégorisation (prédire une classe parmi plusieurs classes possibles) :
La fonction de cout à utiliser est proche de celle utilisée dans le cas de la classification binaire mais avec cette notion de vecteur de probabilité.
Cross Entropy va evéluer la différence entre 2 distributions de probabilité. On va donc l’utiliser pour comparer la valeur prédite et la valeur vraie.
Voir https://en.wikipedia.org/wiki/Cross_entropy

En me basant sur le tutorial et le jeu de donnée présent sur cette page on a :https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html
Pour info si vous obtenez l’erreur suivante :
ImportError: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
il suffit d’executer l’installation de Iprogress sur jupyter lab
pip install ipywidgets jupyter nbextension enable --py widgetsnbextension
Dans notre exemple on va dans un premier temps récupérer le jeu de données en entrée
Le dataset utilisé est CIFAR-10, plus d’information ici : https://www.cs.toronto.edu/~kriz/cifar.html
Celui-ci est déjà intégré à Pytorch pour nous permettre d’effectuer certain test.
Exemple (problème de catégorisation) :
Préparation du dataset :
import torch import torchvision import torchvision.transforms as transforms transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

On définit alors une fonction qui permet de visualiser l’image imshow et on affiche pour chaque image l’unique étiquette qui lui est associé :
import matplotlib.pyplot as plt import numpy as np # functions to show an image def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() # get some random training images dataiter = iter(trainloader) images, labels = dataiter.next() # show images imshow(torchvision.utils.make_grid(images)) # print labels print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

Créer un réseau de neurones de convolution (plus d’information ici : https://towardsdatascience.com/a-comprehensive-guide-to-convolutional-neural-networks-the-eli5-way-3bd2b1164a53)
Un réseau de neurones convolutifs (ConvNet ou CNN) est un algorithme de DeepLearning qui peut prendre une image en entrée, il fixe un score (poids et biais qui sont des paramètres apprenables) à divers aspects / objets de l’image et être capable de différencier l’un de l’autre.

L’architecture d’un réseau de neurone convolutif est proche de celle du modèle de connectivité des neurones dans le cerveau humain et a été inspirée par l’organisation du cortex visuel.
Les neurones individuels ne répondent aux stimuli que dans une région restreinte du champ visuel connue sous le nom de champ récepteur. Une collection de ces champs se chevauchent pour couvrir toute la zone visuelle.
Dans notre cas nous allons utiliser avec Pytorch une couche Conv2d et une couche de pooling.
https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html : La couche Conv2d est la couche de convolution 2D.
https://www.freecodecamp.org/news/an-intuitive-guide-to-convolutional-neural-networks-260c2de0a050/
Un filtre dans une couche conv2D a une hauteur et une largeur. Ils sont souvent plus petits que l’image d’entrée. Ce filtre se déplace donc sur toute l’image au fil de l’entrainement (cette zone est appelée champ réceptif).
La couche de Max Pooling est un processus d’échantillonage. L’objectif est de sous-échantillonner une représentation d’entrée (image par exemple), en réduisant sa dimension et en faisant des hypothèses sur les caractéristiques contenues dans les sous-régions regroupées.
Dans mon exemple avec PyTorch la déclaration se fait :
import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x net = Net()
Définir le critère de la fonction de cout avec CrossEntropyLoss, ici on utilise l’opimizer SGD plutot que adam (plus d’info ici sur les comparaison entre optimizer https://ruder.io/optimizing-gradient-descent/)
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
La boucle d’entrainement :
for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 2000 == 1999: # print every 2000 mini-batches print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss = 0.0 print('Finished Training')

Le réseau de neurone Pytorch est sauvegardé
PATH = './cifar_net.pth' torch.save(net.state_dict(), PATH)
On charge un test et on utilise le réseau pour prédire le résultat.
dataiter = iter(testloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

Dans cet exemple les labels « vrai » sont « Cat, ship, ship, plane », on lance alors le réseau pour faire une prédiction :
net = Net() net.load_state_dict(torch.load(PATH)) outputs = net(images) _, predicted = torch.max(outputs, 1) print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))

Voici un aperçu de l’architecture de ce réseau de convolution.
import hiddenlayer as hl from torchviz import make_dot, make_dot_from_trace make_dot(net(images), params=dict(net.named_parameters()))
Problème de catégorisation (prédire plusieurs classe parmi plusieurs classes possible) – multiple-label classifier avec pytorch
Globalement il s’agit de prédire plusieurs probabilités pour chacune des classes pour indiquer leurs probabilités de présence dans l’entrée. Une utilisation possible est d’indiquer la présence d’un objet dans une image.

Le problème revient alors à un problème de classification binaire pour n classes.
La fonction d’activation finale est sigmoid et la fonction loss est Binary cross entropy
Cette exemple sur internet illustre parfaitement l’utilisation de BCELoss dans le cas de la prédiction de plusieurs classes parmi plusieurs classe possible.
https://medium.com/@thevatsalsaglani/training-and-deploying-a-multi-label-image-classifier-using-pytorch-flask-reactjs-and-firebase-c39c96f9c427
Dans cet exemple : L’ensemble de données d’image utilisé est l’ensemble de données d’attributs CelebFaces à grande échelle (CelebA).
http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html
Dans ce dataset de données, il y a 200K images avec 40 étiquettes de classe différentes et chaque image a un encombrement d’arrière-plan différent et il y a beaucoup de variations différentes, ce qui rend difficile pour un modèle de classer efficacement chaque étiquette de classe.
Je vous propose de suivre le tutorial issu de l’article https://medium.com/@thevatsalsaglani/training-and-deploying-a-multi-label-image-classifier-using-pytorch-flask-reactjs-and-firebase-c39c96f9c427:
- Step1 : Téléchargez le fichier img_align_celeba.zip dur site http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html (il est dans un google drive)
- Step2 : Téléchargez le fichier list_attr_celeba.txt qui contient les annotations pour chaque image sur le meme site http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html
- Step3 : En ouvrant le fichier des annotations vous pouvez consultez les 40 étiquettes : 5_o_Clock_Shadow Arched_Eyebrows Attractive Bags_Under_Eyes Bald Bangs Big_Lips Big_Nose Black_Hair Blond_Hair Blurry Brown_Hair Bushy_Eyebrows Chubby Double_Chin Eyeglasses Goatee Gray_Hair Heavy_Makeup High_Cheekbones Male Mouth_Slightly_Open Mustache Narrow_Eyes No_Beard Oval_Face Pale_Skin Pointy_Nose Receding_Hairline Rosy_Cheeks Sideburns Smiling Straight_Hair Wavy_Hair Wearing_Earrings Wearing_Hat Wearing_Lipstick Wearing_Necklace Wearing_Necktie Young
Pour chacune des image présente dans le jeu de test il y a une valuer 1 ou -1 précisant pour image si la classe est présente dans l’image.
L’objectif ici sera de prédire pour chacune des classe la probabilité de sa présence dans l’image. - Step4 : vous pouvez charger le notebook qui est présent sur https://github.com/vatsalsaglani/MultiLabelClassifier/blob/master/prediction_celebA.ipynb
Voici le résultat que j’obtiens :
Chargement du dataset :

Utilisation de la fonction imshow (cf exemple précédent pour la prédiction single label)

Architecture du réseau de neurone convolutif :
import torch.nn.functional as F class MultiClassifier(nn.Module): def __init__(self): super(MultiClassifier, self).__init__() self.ConvLayer1 = nn.Sequential( nn.Conv2d(3, 64, 3), # 3, 256, 256 nn.MaxPool2d(2), # op: 16, 127, 127 nn.ReLU(), # op: 64, 127, 127 ) self.ConvLayer2 = nn.Sequential( nn.Conv2d(64, 128, 3), # 64, 127, 127 nn.MaxPool2d(2), #op: 128, 63, 63 nn.ReLU() # op: 128, 63, 63 ) self.ConvLayer3 = nn.Sequential( nn.Conv2d(128, 256, 3), # 128, 63, 63 nn.MaxPool2d(2), #op: 256, 30, 30 nn.ReLU() #op: 256, 30, 30 ) self.ConvLayer4 = nn.Sequential( nn.Conv2d(256, 512, 3), # 256, 30, 30 nn.MaxPool2d(2), #op: 512, 14, 14 nn.ReLU(), #op: 512, 14, 14 nn.Dropout(0.2) ) self.Linear1 = nn.Linear(512 * 14 * 14, 1024) self.Linear2 = nn.Linear(1024, 256) self.Linear3 = nn.Linear(256, 40) def forward(self, x): x = self.ConvLayer1(x) x = self.ConvLayer2(x) x = self.ConvLayer3(x) x = self.ConvLayer4(x) x = x.view(x.size(0), -1) x = self.Linear1(x) x = self.Linear2(x) x = self.Linear3(x) return F.sigmoid(x)
Un aperçu de l’architecture du réseau (pytorch tutorial) :
SYNTHESE :
Voici une fiche de synthèse que j’ai créé pour vous permettre de plus rapidement choisir la bonne fonction d’activation et la bonne fonction de coût en fonction de votre problème à résoudre.

Pytorch tutorial – LES 5 MEILLEURS LIENS SUR LE DEEP LEARNING
https://atcold.github.io/pytorch-Deep-Learning/ : Cours gratuit de Yann LeCun
https://pytorch.org/ : Le site officiel pour PyToch
https://machinelearningmastery.com : Un site qui traite de sujet avancé sur le machine learning et le deep learning
https://www.fun-mooc.fr/courses/course-v1:CNAM+01031+session03/about : Un MOOC gratuit sur le deep learning
https://dataanalyticspost.com/ : Un site de veille intéressant autour du machine learning.
Pytorch tutorial : liens internes
https://128mots.com/index.php/en/category/non-classe-en/
https://128mots.com/index.php/category/python/