Inference

Augustin Chevallier

1 Réseaux de neurones: théorie

1.1 Objectif

  • variables explicatives: \(x = (x_1,...,x_p) \in \mathbb{R}^p\)
  • variable(s) à expliquer: \(y \in \mathbb{R}^q\)
  • On dispose de \(n\) données \(X\) et \(Y\)

But:

On cherche une fonction \(f: \mathbb{R}^p \rightarrow \mathbb{R}^q\) t.q. \(f(x) \approx y\)

1.2 Modèle

Un modèle est un ensemble de fonctions \(\mathcal{F}\) de \(\mathbb{R}^p \rightarrow \mathbb{R}^q\)

Pour les réseaux de neurones:

  • \(\mathcal{F}\) est paramétré par \(\theta\): \(\mathcal{F} = \{f(.,\theta) | \theta \text{ dans l'ensemble des paramètres}\}\)
  • On regarde \(f(x;\theta)\)
  • \(\theta \in \mathbb{R}^k\)

Régime surparamétré

  • pour entraîner un réseau de neurones, il faut \(k >> n\)
  • c’est contraire aux stats classiques où il faut plutôt \(k << n\)
  • les réseaux de neurones sont robustes au sur-apprentissage

1.3 Perceptron: 1 neurone

  • Entrées \(X = (x_1, x_2, \dots, x_n)\)
  • Sortie \[y = h\left( \sum w_i x_i + b \right)\]
  • Poids \(w_i\)
  • Biais \(b\), parfois noté \(w_0\)
  • Fonction d’activation : \(h\)

1.4 Fonctions d’activations

Fonctions les plus courantes :

  1. Fonction Seuil (Heaviside) :
    \[ h(x) = \begin{cases} 1, & x \geq 0 \\ 0, & x < 0 \end{cases} \]

  2. Sigmoïde :
    \[ h(x) = \frac{1}{1 + e^{-x}} \]

  3. Tanh (Tangente hyperbolique) :
    \[ h(x) = \tanh(x) \]

  4. ReLU (Rectified Linear Unit) :
    \[ h(x) = \max(0, x) \]

1.5 Classification avec perceptron

  • On souhaite séparer 2 classes
  • On utilise la sortie d’un perceptron \(y\) avec \[y = h\left( \sum w_i x_i + b \right)\]
  • Prédiction : si \(y> 0\), classe 1, si \(y<0\) classe \(2\)
  • La frontière entre les classes est forcément affine!

1.6 Perceptron selon chatgpt

1.7 Réseau de Neurones Feedforward: Une Couche

  • Couche d’Entrée : \(X = [x_1, x_2, \ldots, x_n]\)
  • Neurones : \(N = \{ \text{Neurone}_1, \text{Neurone}_2, \ldots, \text{Neurone}_m \}\)

Calcul pour chaque neurone \(j\):

\[ z_j = \sum_{i=1}^{n} w_{ij} x_i + b_j \]

  • \(w_{ij}\) = poids de la connexion entre l’entrée \(x_i\) et le neurone \(j\)
  • \(b_j\) = biais du neurone \(j\)

Sortie: \([h(z_1), \ldots, h(z_m)]\)

1.8 Notes

Note 1:

Chaque neurone reçoit les mêmes entrées mais a des poids et biais différents, permettant d’apprendre des représentations complexes des données.

Note 2:

  • On note \(W\) la matrice des poids et \(b\) le vecteur des biais.
  • On note \(z\) le vecteur \(z = (z_1,...,z_m)\)
  • On note \(x\) le vecteur \(x = (x_1,...,x_n)\)
  • Alors: \(z = x W + b\)
  • Le produit matriciel est une opération essentielle !
  • On peut vectoriser le code.

1.9 Réseau de neurone feedforward

  • Extension du perceptron avec plusieurs couches de neurones.
  • Les sorties de chaque couche sont les entrées de la suivante
  • Composés de :
    1. Couche d’entrée
    2. Couches cachées
    3. Couche de sortie, généralement sans fonction d’activation
  • Chaque neurone dans une couche est connecté à tous les neurones de la couche suivante (connexions entièrement connectées).
  • Information circule dans une seule direction (avant) : Pas de boucles ou de rétroactions.

1.10 Réseau de neurone feedforward: version ChatGPT

1.11 Exemple - quizz

On note \(W^{(i)}\),\(b^{(i)}\) les poids et biais de la couche \(i\).

  • \(W^{(1)} = [1,2,3]\), \(W^{(2)} = \begin{bmatrix} 3 & 4 \\ -2 & 0 \\ 1 & 2 \end{bmatrix}\), \(W^{(3)} = \begin{bmatrix} 1 \\ 2 \end{bmatrix}\)

  • \(b^{(1)} = (0,0,0)\), \(b^{(2)} = (1,1)\), \(b^{(3)} = (-1)\)

  • La fonction d’activation est \(tanh\)

  1. Quelle est la dimension de l’espace de départ ?
  2. Quelle est la dimension de l’espace d’arrivée ?
  3. Calculer l’image de \(x = 3\)

1.12 Inference vs Entrainement

Entrainement:

  • on cherche \(\theta\) tel que \(f(x,\theta)\) corresponde le mieux possible à nos données

Inference:

  • \(\theta\) est fixe
  • On fait une nouvelle prédiction de \(y\) pour un nouveau \(x\)

Dans ce chapitre: on ne s’intéresse pas à l’entraînement.

2 En pratique: pytorch

2.1 Pytorch

3 librairies populaires pour les réseaux de neurones:

  • Tensorflow
  • Pytorch
  • JAX

On utilisera ici Pytorch, la plus populaire. Les 3 libraries sont sous Python.

2.2 Installer PyTorch

https://pytorch.org/get-started/locally/

2.3 Importer PyTorch

import torch
import torch.nn as nn
import torch.nn.functional as F

torch.nn.functional contient:

  • les fonctions d’activations
  • les fonctions de couts (loss)

2.4 Tensors

Tensors:

  • équivalent PyTorch des ndarrays de numpy
  • syntaxe similaire
  • utile pour calculer des gradients (cf prochain cours)
tensor_ex = torch.tensor([[1.,2,3],[4,5,6]])
print(tensor_ex[0,:]) #première ligne
print(tensor_ex[:,0]) #première colonne
print(tensor_ex.shape) #dimensions
print(tensor_ex[1,1]) #acceder à un élement
tensor([1., 2., 3.])
tensor([1., 4.])
torch.Size([2, 3])
tensor(5.)

2.5 Tensors de dimension 0

Cas particulier: les scalaires (i.e. un seul nombre) sont dits des tenseurs de dimension 0

  • pour accéder à l’unique élément, il faut utiliser .item() plutôt que [0]
dim0_tensor = torch.tensor(1.0)
#dim0_tensor[0] #Ne fonctionne pas!
print(dim0_tensor.item())
1.0

2.6 Un exemple: FashionMNIST

Fashion MNIST:

  • images en niveau de gris 28x28 pixels
  • labels: type de vêtement, numérotés de 0 à 9

2.7 Un modèle: entrée/sortie

Classification:

  • on cherche à apprendre le type de vêtement en fonction de l’image
  • espace de départ: \(\mathbb{R}^{28*28}\)
  • espace d’arrivée: \(\mathbb{R}^{10}\)
  • entrée: une image \(x\) dans l’espace de départ \(\mathbb{R}^{28*28}\)
  • sortie: un vecteur \(y\) dans l’espace d’arrivée \(\mathbb{R}^{10}\), le i-ème élément correspond à la “probabilité” d’avoir le i-ème type de vêtement
  • pour déterminer le vêtement, il faut faire l’argmax de la sortie \(y\)

2.8 Un modèle: détails

Le modèle:

  • une première couche cachée de 512 paramètres
  • une deuxième couche cachée de 512 paramètres
  • on utilise la fonction d’activation RELU

Les paramètres (\(\theta\)):

  • couche 1:
    • les poids \(w_1\): matrice \((28 * 28) \times 512\)
    • les biais \(b_1\): vecteur de taille \(512\)
  • couche 2
    • les poids \(w_2\): matrice \(512 \times 512\)
    • les biais \(b_2\): vecteur de taille \(512\)
  • couche de sortie
    • les poids \(w_3\): matrice \(512 \times 10\)
    • les biais \(b_3\): vecteur de taille \(10\)

2.9 Créer un modèle:

class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        #initialize parameters here!

    def forward(self, x):
        #compute the result of your model here!
        return something

# Instancier le modèle
model = SimpleNN()
  • On initialise/définit les paramètres \(\theta\) du modèle dans la fonction __init__
  • On évalue le modèle en \(x\) dans la fonction forward
  • On instancie le modèle

2.10 Créer le modèle: modèle complet

class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.flatten = nn.Flatten()
        # Définir les poids et biais pour trois couches
        self.w1 = nn.Parameter(torch.randn(28*28,512))
        self.b1 = nn.Parameter(torch.randn(512))
        self.w2 = nn.Parameter(torch.randn(512, 512))
        self.b2 = nn.Parameter(torch.randn(512))
        self.w3 = nn.Parameter(torch.randn(512, 10))
        self.b3 = nn.Parameter(torch.randn(10))
    
    def forward(self, x):
        x = self.flatten(x)
        # Appliquer les transformations et les fonctions d'activation
        x = F.relu(x.matmul(self.w1) + self.b1)
        x = F.relu(x.matmul(self.w2) + self.b2)
        x = x.matmul(self.w3) + self.b3
        return x

# Instancier le modèle
model = SimpleNN()
2

Initialisation:

  • nn.Parameters: indique un paramètre du modèle (i.e. une partie de \(\theta\))

Evaluation:

  • nn.Flatten() permet de transformer un tenseur d-dimensionnel en un tenseur 1-dimensionnel
  • F.relu: fonction d’activation
  • matmul: multiplication matricielle

2.11 Charger les paramètres depuis un fichier

Les paramètres du modèle sont initialisés aléatoirement :

  • l’évaluation du modèle ne sera donc pas pertinente !
  • il faut charger des “bons” paramètres
  • trouver des “bons” paramètres sera l’objet du cours suivant

Charger un modèle avec PyTorch:

  • PyTorch connaît les paramètres et leur structure car ceux-ci sont déclarés avec nn.Parameter
  • Il est alors possible de les enregistrer/charger dans un fichier
model.load_state_dict(torch.load('model_weights.pth'))
<All keys matched successfully>

2.12 Inference: donnée d’entrée

On utilise ici une donnée du jeux de test:

  • x: notre donnée
  • y: le (vrai) label
  • On utilise la fonction squeeze() pour supprimer les dimensions inutiles de nos données (i.e. s’il y a une seul colonne dans un tableau 2d, on peut le transformer en un tableau 1d)
(x,y) = test_data[0]
print(x.shape)
plt.imshow(x.squeeze())
print("vrais label",y,labels_map[y])
torch.Size([1, 28, 28])
vrais label 9 Ankle Boot

2.13 Inference: prédiction

model.eval()

with torch.no_grad():
    prediction = model(x)
    label_idx = prediction.argmax().item()
    
    print("prediction",label_idx,labels_map[label_idx])
prediction 9 Ankle Boot
  • Il faut d’abord passer le modèle en mode “evaluation” (par opposition à apprentissage):
  • On désactive aussi le calcul du gradient pour gagner des performances (cf cours suivant):