Pytorch tutorial : Deep learning en python

Pytorch tutorial : Introduction

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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.

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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/)

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Source https://upload.wikimedia.org/wikipedia/commons/6/68/Gradient_descent.jpg :
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.

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
https://upload.wikimedia.org/wikipedia/commons/6/60/ArtificialNeuronModel_english.png

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 :

  1. 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)
  2. 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)
  3. 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)
  4. 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.

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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()
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Tracé de la fonction sigmoid, fonction utilisée comme fonction d’activation finale dans le cas d’un algorithme de Deep Learning
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"))
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Création de 1000 échantillons avec un résultat 0 ou 1 via sklearn

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)
Données en entrée
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Données en sortie

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 :

  1. couche d’entrée (input layer) : fonction d’activation linéaire
  2. 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).
  3. 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.

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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()
Résultats : Le premier graphique montre la réponse « vraie » la seconde montre la réponse prédite (la couleur varie en fonction de la probabilité prédite) – pytorch tutorial

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))
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Hidden Layer architecture pour le classifier binaire

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()))
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Diagramme d’architecture du réseau de neurone créé avec PyTorch tuorial

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).

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Source https://upload.wikimedia.org/wikipedia/commons/9/9c/Linear_function_kx.png : Fonctions linéaires – pytorch tutorial

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

Fonction d’activation ReLu Source = https://upload.wikimedia.org/wikipedia/commons/f/fe/Activation_rectified_linear.svg

Une autre fonction possible est PReLu (unité de rectification linéaire paramétrique) :

Fonction d’activation PReLu
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).

Fonction d’activation bent identity (identité courbée) Source https://upload.wikimedia.org/wikipedia/commons/c/c3/Activation_bent_identity.svg


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

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
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()
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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))
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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()
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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))
Diagramme du réseau de neurone
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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)
Architecture du modèle avec couche linéaire + couche ReLu – pytorch tutorial
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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()))
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
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,

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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))

Architecture du réseau appliqué pour ma fonction custom inversion de soft exponentialt avec ajout d’un critère de biais.
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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/)

pytorch tutorial - Tutorial pour apprendre le deep learning avec 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)))
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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.

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python
Source https://upload.wikimedia.org/wikipedia/commons/6/63/Typical_cnn.png – pytorch tutorial

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.

Source : https://cdn-media-1.freecodecamp.org/images/gb08-2i83P5wPzs3SL-vosNb6Iur5kb5ZH43

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()))
pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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.

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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:

  1. 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)
  2. 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
  3. 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.
  4. 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)

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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) :

pytorch tutorial - Tutorial pour apprendre le deep learning avec Python

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 - Tutorial pour apprendre le deep learning avec Python

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/