Entraîner un réseau de neurones artificiels (partie 1)

CSI 4106 - Automne 2025

Marcel Turcotte

Version: oct. 21, 2025 14h36

Préambule

Message du Jour

Résultats d’apprentissage

  • Expliquer l’architecture et le fonctionnement des réseaux de neurones à propagation avant (FNN).
  • Identifier les fonctions d’activation courantes et comprendre leur impact sur la performance du réseau.
  • Introduire une implémentation simple mais fonctionnelle d’un réseau de neurones à propagation avant.

Résumé

3Blue1Brown (1/2)

3Blue1Brown (2/2)

Résumé - DL

  • Apprentissage profond (DL) est une technique d’apprentissage automatique qui peut être appliquée à l’apprentissage supervisé (y compris la régression et la classification), l’apprentissage non supervisé, et l’apprentissage par renforcement.

  • Inspiré par la structure et la fonction des réseaux neuronaux biologiques trouvés chez les animaux.

  • Composé de neurones interconnectés (ou unités) organisés en couches.

Résumé - FNN

Résumé - FNN

Résumé - unités

Fonctions d’activation courantes

Afficher le code
# Attribution: https://github.com/ageron/handson-ml3/blob/main/10_neural_nets_with_keras.ipynb

import numpy as np
import matplotlib.pyplot as plt

from scipy.special import expit as sigmoid

def relu(z):
    return np.maximum(0, z)

def derivative(f, z, eps=0.000001):
    return (f(z + eps) - f(z - eps))/(2 * eps)

max_z = 4.5
z = np.linspace(-max_z, max_z, 200)

plt.figure(figsize=(11, 3.1))

plt.subplot(121)
plt.plot(z, relu(z), "m-.", linewidth=2, label="ReLU")
plt.plot(z, sigmoid(z), "g--", linewidth=2, label="Sigmoïde")
plt.plot(z, np.tanh(z), "b-", linewidth=1, label="Tanh")
plt.grid(True)
plt.title("Fonctions d'activation")
plt.axis([-max_z, max_z, -1.65, 2.4])
plt.gca().set_yticks([-1, 0, 1, 2])
plt.legend(loc="lower right", fontsize=13)

plt.subplot(122)
plt.plot(z, derivative(sigmoid, z), "g--", linewidth=2, label="Sigmoïde")
plt.plot(z, derivative(np.tanh, z), "b-", linewidth=1, label="Tanh")
plt.plot([-max_z, 0], [0, 0], "m-.", linewidth=2)
plt.plot([0, max_z], [1, 1], "m-.", linewidth=2)
plt.plot([0, 0], [0, 1], "m-.", linewidth=1.2)
plt.plot(0, 1, "mo", markersize=5)
plt.plot(0, 1, "mx", markersize=10)
plt.grid(True)
plt.title("Dérivées")
plt.axis([-max_z, max_z, -0.2, 1.2])

plt.show()

Approximation Universelle

Le théorème d’approximation universelle affirme qu’un réseau de neurones feed-forward avec une seule couche cachée contenant un nombre fini de neurones peut approximer n’importe quelle fonction continue sur un sous-ensemble compact de \(\mathbb{R}^n\), moyennant des poids et des fonctions d’activation appropriés.

MLP Naïf

Données

Afficher le code
# Générer et tracer le jeu de données "cercles"
import matplotlib.pyplot as plt
from sklearn.datasets import make_circles

# Générer des données synthétiques
X, y = make_circles(n_samples=1200, factor=0.35, noise=0.06, random_state=42)

# Séparer les coordonnées pour le tracé
x1, x2 = X[:, 0], X[:, 1]

# Tracer les deux classes
plt.figure(figsize=(5, 5))
plt.scatter(x1[y==0], x2[y==0], color="C0", label="classe 0 (anneau extérieur)")
plt.scatter(x1[y==1], x2[y==1], color="C1", label="classe 1 (cercle intérieur)")
plt.xlabel("x₁")
plt.ylabel("x₂")
plt.title("Jeu de données généré avec make_circles")
plt.axis("equal") # assure que les cercles ont l'air ronds
plt.legend()
plt.show()

Architecture

Utilitaires

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

def bce_loss(y_true, y_prob, eps=1e-9):

    """Perte d'entropie croisée binaire (moyenne sur les données)."""

    y_prob = np.clip(y_prob, eps, 1 - eps)

    return -np.mean(y_true * np.log(y_prob) + (1 - y_true) * np.log(1 - y_prob))

NaïveMLP

L’implémentation complète est présentée ci-dessous et sera examinée dans les écrans suivants.

Afficher le code
class NaiveMLP:

    """
    Un perceptron multicouche minimal (MLP) utilisant un algorithme d'entraînement
    par force brute qui ne nécessite pas de calculs de dérivées.

    Veuillez noter que l'algorithme d'entraînement suggéré est destiné uniquement
    à des fins didactiques et ne doit pas être confondu avec un véritable algorithme d'entraînement.
    """

    def __init__(self, layer_sizes, step=0.1, seed=None):
        
        self.sizes = list(layer_sizes)
        self.step = float(step)
        rng = np.random.default_rng(seed)

        # Initialiser les poids et les biais

        self.W = [rng.standard_normal(size=(in_d, out_d)) * 0.5
                  for in_d, out_d in zip(layer_sizes[:-1], layer_sizes[1:])]

        self.b = [np.zeros(out_d) for out_d in layer_sizes[1:]]

    def forward(self, X):

        """
        Passage avant simple : ne calcule que les activations de sortie.

        X: forme (N, input_dim)

        Retourne : probabilités de sortie, forme (N,)
        """

        a = X
        for W, b in zip(self.W, self.b):
            a = sigmoid(a @ W + b)

        return a.ravel()

    def predict(self, X, threshold=0.5):

        return (self.forward(X) >= threshold).astype(int)

    def loss(self, X, y):

        return bce_loss(y, self.forward(X))

    def _all_param_tags(self):

        """
        Génère des étiquettes référant à chaque paramètre scalaire :
        ('W', layer_idx, i, j) ou ('b', layer_idx, j)
        """

        for l, W in enumerate(self.W):
            for i in range(W.shape[0]):
                for j in range(W.shape[1]):
                    yield ('W', l, i, j)
            for j in range(self.b[l].shape[0]):
                yield ('b', l, j)

    def _get_param(self, tag):
        kind = tag[0]
        if kind == 'W':
            _, l, i, j = tag
            return self.W[l][i, j]
        else:
            _, l, j = tag
            return self.b[l][j]

    def _set_param(self, tag, val):
        kind = tag[0]
        if kind == 'W':
            _, l, i, j = tag
            self.W[l][i, j] = val
        else:
            _, l, j = tag
            self.b[l][j] = val

    def train(self, X, y, epochs=10, verbose=True):

        """
        Mise à jour simultanée :
        - Pour chaque paramètre scalaire θ, essayez θ + δ pour δ dans {−step, 0, +step},
          choisissez le δ qui donne la perte minimale.
        - Collecter tous les δ choisis, puis appliquer toutes les mises à jour ensemble.
        """

        for ep in range(1, epochs + 1):

            base_loss = self.loss(X, y)
            updates = {}

            # Tester tous les paramètres
            for tag in self._all_param_tags():

                theta = self._get_param(tag)
                best_delta = 0.0
                best_loss = base_loss

                for delta in (-self.step, 0.0, +self.step):
                    self._set_param(tag, theta + delta)
                    trial_loss = self.loss(X, y)
                    if trial_loss < best_loss:
                        best_loss = trial_loss
                        best_delta = delta

                # restaurer l'original
                self._set_param(tag, theta)
                updates[tag] = best_delta

            # Appliquer tous les deltas ensemble
            for tag, d in updates.items():
                if d != 0.0:
                    self._set_param(tag, self._get_param(tag) + d)

            new_loss = self.loss(X, y)

            if verbose:
                print(f"Époque {ep:3d} : perte {base_loss:.5f}{new_loss:.5f}")

            # arrêt précoce optionnel
            if abs(new_loss - base_loss) < 1e-12:
                break

Définition de la Classe

class NaiveMLP:

    """
    Un perceptron multicouche (MLP) minimal utilisant un algorithme 
    d'entraînement par force brute qui ne nécessite pas de calcul 
    des dérivées.

    Veuillez noter que l'algorithme d'entraînement proposé est destiné 
    uniquement à des fins didactiques et ne doit pas être confondu avec 
    un véritable algorithme d'entraînement.
    """

Constructeur

    def __init__(self, layer_sizes, step=0.1, seed=None):
        
        self.sizes = list(layer_sizes)
        self.step = float(step)
        rng = np.random.default_rng(seed)

        # Initialiser les poids et les biais

        self.W = [rng.standard_normal(size=(in_d, out_d)) * 0.5
                  for in_d, out_d in zip(layer_sizes[:-1], layer_sizes[1:])]

        self.b = [np.zeros(out_d) for out_d in layer_sizes[1:]]

Python

seed = 0

rng = np.random.default_rng(seed)

layer_sizes = [2, 4, 4, 1]

[(in_d, out_d) for in_d, out_d in zip(layer_sizes[:-1], layer_sizes[1:])]
[(2, 4), (4, 4), (4, 1)]
[rng.standard_normal(size=(in_d, out_d)) * 0.5 for in_d, out_d in zip(layer_sizes[:-1], layer_sizes[1:])]
[array([[ 0.06286511, -0.06605243,  0.32021133,  0.05245006],
        [-0.26783469,  0.18079753,  0.65200002,  0.47354048]]),
 array([[-0.35186762, -0.63271074, -0.31163723,  0.02066299],
        [-1.16251539, -0.10939583, -0.62295547, -0.36613368],
        [-0.27212949, -0.15815008,  0.20581527,  0.52125668],
        [-0.06426733,  0.68323174, -0.33259734,  0.17575504]]),
 array([[ 0.45173509],
        [ 0.04700615],
        [-0.37174962],
        [-0.46086269]])]

Python

[out_d for out_d in layer_sizes[1:]]
[4, 4, 1]
[np.zeros(out_d) for out_d in layer_sizes[1:]]
[array([0., 0., 0., 0.]), array([0., 0., 0., 0.]), array([0.])]

Passage Avant

    def forward(self, X):

        """
        Passage avant simple : calculer les activations de sortie.
        X : forme (N, input_dim)
        Retourne : probabilités de sortie, forme (N,)
        """

        a = X
        for W, b in zip(self.W, self.b):
            a = sigmoid(a @ W + b)

        return a.ravel()

Faire des prédictions

    def predict(self, X, threshold=0.5):

        return (self.forward(X) >= threshold).astype(int)

Calcul du coût

    def loss(self, X, y):

        return bce_loss(y, self.forward(X))

Discussion

À l’exception de l’algorithme d’entraînement, notre implémentation de réseau de neurones est maintenant complète.

Pour ceux qui ne sont pas familiers avec l’algorithme de rétropropagation, comment proposez-vous d’apprendre les paramètres du modèle ?

Changer les poids → calculer la perte → conserver si meilleur → répéter.

Pseudocode

pour chaque époque :
    pour chaque paramètre w dans le réseau :
        meilleur_delta = 0
        meilleure_perte = perte_actuelle
        pour delta dans [-0.01, 0, +0.01] :
            w_temp = w + delta
            perte_temp = calculer_perte(w_temp, données)
            si perte_temp < meilleure_perte :
                meilleure_perte = perte_temp
                meilleur_delta = delta
        w += meilleur_delta

Python

    def _all_param_tags(self):

        """
        Génère des tags référenciant chaque paramètre scalaire :
        ('W', layer_idx, i, j) ou ('b', layer_idx, j)
        """

        for l, W in enumerate(self.W):
            for i in range(W.shape[0]):
                for j in range(W.shape[1]):
                    yield ('W', l, i, j)
            for j in range(self.b[l].shape[0]):
                yield ('b', l, j)

Python

Afficher le code
class Demo:

    def __init__(self, layer_sizes):
        self.sizes = list(layer_sizes)
        rng = np.random.default_rng(0)
        self.W = [rng.standard_normal(size=(in_d, out_d)) * 0.5
                  for in_d, out_d in zip(layer_sizes[:-1], layer_sizes[1:])]
        self.b = [np.zeros(out_d) for out_d in layer_sizes[1:]]

    def _all_param_tags(self):

        """
        Génère des étiquettes référenciant chaque paramètre scalaire :
        ('W', layer_idx, i, j) ou ('b', layer_idx, j)
        """

        for l, W in enumerate(self.W):
            for i in range(W.shape[0]):
                for j in range(W.shape[1]):
                    yield ('W', l, i, j)
            for j in range(self.b[l].shape[0]):
                yield ('b', l, j)

    def show(self):

      for tag in self._all_param_tags():
        print(tag)
d = Demo([2,4,4,1])
d.show()

Python

('W', 0, 0, 0)
('W', 0, 0, 1)
('W', 0, 0, 2)
('W', 0, 0, 3)
('W', 0, 1, 0)
('W', 0, 1, 1)
('W', 0, 1, 2)
('W', 0, 1, 3)
('b', 0, 0)
('b', 0, 1)
('b', 0, 2)
('b', 0, 3)
('W', 1, 0, 0)
('W', 1, 0, 1)
('W', 1, 0, 2)
('W', 1, 0, 3)
('W', 1, 1, 0)
('W', 1, 1, 1)
('W', 1, 1, 2)
('W', 1, 1, 3)
('W', 1, 2, 0)
('W', 1, 2, 1)
('W', 1, 2, 2)
('W', 1, 2, 3)
('W', 1, 3, 0)
('W', 1, 3, 1)
('W', 1, 3, 2)
('W', 1, 3, 3)
('b', 1, 0)
('b', 1, 1)
('b', 1, 2)
('b', 1, 3)
('W', 2, 0, 0)
('W', 2, 1, 0)
('W', 2, 2, 0)
('W', 2, 3, 0)
('b', 2, 0)

Python

    def _get_param(self, tag):
        kind = tag[0]
        if kind == 'W':
            _, l, i, j = tag
            return self.W[l][i, j]
        else:
            _, l, j = tag
            return self.b[l][j]

Python

    def _set_param(self, tag, val):
        kind = tag[0]
        if kind == 'W':
            _, l, i, j = tag
            self.W[l][i, j] = val
        else:
            _, l, j = tag
            self.b[l][j] = val

Entraînement (apprentissage)

    def train(self, X, y, epochs=10, verbose=True):

        for ep in range(1, epochs + 1):

            base_loss = self.loss(X, y)
            updates = {}

            # Examiner tous les paramètres
            for tag in self._all_param_tags():

                theta = self._get_param(tag)
                best_delta = 0.0
                best_loss = base_loss

                for delta in (-self.step, 0.0, +self.step):
                    self._set_param(tag, theta + delta)
                    trial_loss = self.loss(X, y)
                    if trial_loss < best_loss:
                        best_loss = trial_loss
                        best_delta = delta

                # restaurer l'original
                self._set_param(tag, theta)
                updates[tag] = best_delta

            # Appliquer tous les deltas ensemble
            for tag, d in updates.items():
                if d != 0.0:
                    self._set_param(tag, self._get_param(tag) + d)

            new_loss = self.loss(X, y)

            if verbose:
                print(f"Époque {ep:3d}: perte {base_loss:.5f}{new_loss:.5f}")

            # arrêt anticipé optionnel
            if abs(new_loss - base_loss) < 1e-12:
                break

Ouf!

Est-ce que ça fonctionne ?

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

model = NaiveMLP([2, 4, 4, 1], step=0.06, seed=0)

print("Loss initiale :", model.loss(X_train, y_train))

model.train(X_train, y_train, epochs=100)

print("Exactitude sur l'entraînement :", accuracy_score(y_train, model.predict(X_train)))
print("Exactitude sur le test :", accuracy_score(y_test, model.predict(X_test)))
Loss initiale : 0.7001969055705487
Époque   1 : perte 0.70020 → 0.69315
Époque   2 : perte 0.69315 → 0.69547
Époque   3 : perte 0.69547 → 0.69387
Époque   4 : perte 0.69387 → 0.69542
Époque   5 : perte 0.69542 → 0.69384
Époque   6 : perte 0.69384 → 0.69537
Époque   7 : perte 0.69537 → 0.69382
Époque   8 : perte 0.69382 → 0.69531
Époque   9 : perte 0.69531 → 0.69379
Époque  10 : perte 0.69379 → 0.69525
Époque  11 : perte 0.69525 → 0.69361
Époque  12 : perte 0.69361 → 0.69517
Époque  13 : perte 0.69517 → 0.69331
Époque  14 : perte 0.69331 → 0.69503
Époque  15 : perte 0.69503 → 0.69298
Époque  16 : perte 0.69298 → 0.69468
Époque  17 : perte 0.69468 → 0.69257
Époque  18 : perte 0.69257 → 0.69364
Époque  19 : perte 0.69364 → 0.69200
Époque  20 : perte 0.69200 → 0.69173
Époque  21 : perte 0.69173 → 0.69118
Époque  22 : perte 0.69118 → 0.68965
Époque  23 : perte 0.68965 → 0.68970
Époque  24 : perte 0.68970 → 0.68716
Époque  25 : perte 0.68716 → 0.68672
Époque  26 : perte 0.68672 → 0.68417
Époque  27 : perte 0.68417 → 0.68170
Époque  28 : perte 0.68170 → 0.67855
Époque  29 : perte 0.67855 → 0.67406
Époque  30 : perte 0.67406 → 0.66867
Époque  31 : perte 0.66867 → 0.66203
Époque  32 : perte 0.66203 → 0.65492
Époque  33 : perte 0.65492 → 0.64672
Époque  34 : perte 0.64672 → 0.63852
Époque  35 : perte 0.63852 → 0.62948
Époque  36 : perte 0.62948 → 0.61977
Époque  37 : perte 0.61977 → 0.60918
Époque  38 : perte 0.60918 → 0.59844
Époque  39 : perte 0.59844 → 0.58893
Époque  40 : perte 0.58893 → 0.57598
Époque  41 : perte 0.57598 → 0.56310
Époque  42 : perte 0.56310 → 0.55035
Époque  43 : perte 0.55035 → 0.53809
Époque  44 : perte 0.53809 → 0.52214
Époque  45 : perte 0.52214 → 0.50660
Époque  46 : perte 0.50660 → 0.49073
Époque  47 : perte 0.49073 → 0.47591
Époque  48 : perte 0.47591 → 0.45758
Époque  49 : perte 0.45758 → 0.44074
Époque  50 : perte 0.44074 → 0.42251
Époque  51 : perte 0.42251 → 0.41069
Époque  52 : perte 0.41069 → 0.38858
Époque  53 : perte 0.38858 → 0.36998
Époque  54 : perte 0.36998 → 0.35227
Époque  55 : perte 0.35227 → 0.34356
Époque  56 : perte 0.34356 → 0.32577
Époque  57 : perte 0.32577 → 0.31462
Époque  58 : perte 0.31462 → 0.29240
Époque  59 : perte 0.29240 → 0.27704
Époque  60 : perte 0.27704 → 0.25851
Époque  61 : perte 0.25851 → 0.25409
Époque  62 : perte 0.25409 → 0.23884
Époque  63 : perte 0.23884 → 0.23260
Époque  64 : perte 0.23260 → 0.21815
Époque  65 : perte 0.21815 → 0.21238
Époque  66 : perte 0.21238 → 0.19964
Époque  67 : perte 0.19964 → 0.19350
Époque  68 : perte 0.19350 → 0.17787
Époque  69 : perte 0.17787 → 0.16963
Époque  70 : perte 0.16963 → 0.15270
Époque  71 : perte 0.15270 → 0.14804
Époque  72 : perte 0.14804 → 0.13478
Époque  73 : perte 0.13478 → 0.13357
Époque  74 : perte 0.13357 → 0.12381
Époque  75 : perte 0.12381 → 0.12041
Époque  76 : perte 0.12041 → 0.10789
Époque  77 : perte 0.10789 → 0.10512
Époque  78 : perte 0.10512 → 0.09204
Époque  79 : perte 0.09204 → 0.08493
Époque  80 : perte 0.08493 → 0.07447
Époque  81 : perte 0.07447 → 0.07243
Époque  82 : perte 0.07243 → 0.06423
Époque  83 : perte 0.06423 → 0.06479
Époque  84 : perte 0.06479 → 0.06053
Époque  85 : perte 0.06053 → 0.05940
Époque  86 : perte 0.05940 → 0.05501
Époque  87 : perte 0.05501 → 0.05465
Époque  88 : perte 0.05465 → 0.05370
Époque  89 : perte 0.05370 → 0.05036
Époque  90 : perte 0.05036 → 0.04376
Époque  91 : perte 0.04376 → 0.04596
Époque  92 : perte 0.04596 → 0.04283
Époque  93 : perte 0.04283 → 0.04213
Époque  94 : perte 0.04213 → 0.04031
Époque  95 : perte 0.04031 → 0.03879
Époque  96 : perte 0.03879 → 0.03629
Époque  97 : perte 0.03629 → 0.03574
Époque  98 : perte 0.03574 → 0.03499
Époque  99 : perte 0.03499 → 0.03293
Époque 100 : perte 0.03293 → 0.03075
Exactitude sur l'entraînement : 1.0
Exactitude sur le test : 1.0

Visualisation

Show code
# Plot helper: decision boundary in the original (x1, x2) plane

def plot_decision_boundary(model, X, y, title="Frontière de décision du MLP naïf"):

    # grille sur le plan d'entrée
    pad = 0.3
    x1_min, x1_max = X[:,0].min()-pad, X[:,0].max()+pad
    x2_min, x2_max = X[:,1].min()-pad, X[:,1].max()+pad

    xx, yy = np.meshgrid(
        np.linspace(x1_min, x1_max, 400),
        np.linspace(x2_min, x2_max, 400)
    )
    grid = np.c_[xx.ravel(), yy.ravel()]

    # prédire les probabilités sur la grille
    p = model.forward(grid).reshape(xx.shape)

    # probabilités remplies + contour p=0.5 + points de données
    plt.figure(figsize=(3.75, 3.75), dpi=140)
    plt.contourf(xx, yy, p, levels=50, alpha=0.7)
    cs = plt.contour(xx, yy, p, levels=[0.5], linewidths=2)
    plt.scatter(X[:,0], X[:,1], c=y, s=18, edgecolor="k", linewidth=0.2)
    plt.clabel(cs, fmt={0.5: "p=0.5"})
    plt.title(title)
    plt.xlabel("x₁")
    plt.ylabel("x₂")
    plt.tight_layout()
    plt.show()

plot_decision_boundary(model, X, y)

Données de type XOR

Afficher le code
n_samples = 800
rng = np.random.default_rng(42)

X = rng.uniform(-6, 6, size=(n_samples, 2))
x1, x2 = X[:, 0], X[:, 1]

y = ((x1 * x2) > 0).astype(int)

plt.figure(figsize=(4.5, 4.5))
plt.scatter(X[y == 0, 0], X[y == 0, 1],
            color="C0", label="classe 0", edgecolor="k", linewidth=0.3)
plt.scatter(X[y == 1, 0], X[y == 1, 1],
            color="C1", label="classe 1", edgecolor="k", linewidth=0.3)

plt.axhline(0, color="gray", linestyle="--", linewidth=1)
plt.axvline(0, color="gray", linestyle="--", linewidth=1)

plt.xlabel("x₁")
plt.ylabel("x₂")
plt.title("Données de type XOR")
plt.xlim(-6, 6)
plt.ylim(-6, 6)
plt.axis("equal")
plt.legend()
plt.tight_layout()
plt.show()

Données de type XOR (suite)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

model = NaiveMLP([2, 4, 4, 1], step=0.06, seed=0)

print("Perte initiale :", model.loss(X_train, y_train))

model.train(X_train, y_train, epochs=1000, verbose=False)

print("Perte finale :", model.loss(X_train, y_train))

print("Exactitude sur l'ensemble d'entraînement :", accuracy_score(y_train, model.predict(X_train)))
print("Exactitude sur l'ensemble de test :", accuracy_score(y_test, model.predict(X_test)))
Perte initiale : 0.7029764953722529
Perte finale : 3.4494531195516116e-07
Exactitude sur l'ensemble d'entraînement : 1.0
Exactitude sur l'ensemble de test : 0.99

Données de type XOR (suite)

plot_decision_boundary(model, X, y)

Inconvénients

  • Inefficacité computationnelle.
  • Limitations de scalabilité.
  • Taille de pas fixe (±η) manque d’adaptabilité.
  • Mauvaise coordination des paramètres.
  • Absence d’informations sur la direction ou la magnitude.
  • Absence de fonctionnalités sophistiquées de l’optimiseur.
  • Risque de sur-apprentissage ou de mauvaise généralisation.

Notation

Notation

Un perceptron à deux couches calcule :

\[ \hat{y} = \phi_2(\phi_1(X)) \]

\[ \phi_l(Z) = \phi(W_lZ_l + b_l) \]

Notation

Un perceptron à 3 couches calcule :

\[ \hat{y} = \phi_3(\phi_2(\phi_1(X))) \]

\[ \phi_l(Z) = \phi(W_lZ_l + b_l) \]

Notation

Un perceptron à \(k\) couches calcule :

\[ \hat{y} = \phi_k( \ldots \phi_2(\phi_1(X)) \ldots ) \]

\[ \phi_l(Z) = \phi(W_lZ_l + b_l) \]

Prologue

Résumé

  • Présentation de l’apprentissage profond comme une approximation de fonction en couches à travers différentes tâches.
  • Description des réseaux de neurones feedforward (FNNs) : entrées → couches cachées → sorties ; l’information ne circule que vers l’avant.
  • Mention des unités utilisant le biais et les activations ; explication de l’importance de la non-linéarité.
  • Revue des plages et du comportement dérivé des fonctions sigmoid/tanh/ReLU.
  • Énoncé du Théorème d’Approximation Universelle et de ses limites pratiques.
  • Construction d’un petit MLP et calcul des prédictions et de la perte BCE sur des données jouets.
  • Démonstration d’un algorithme d’entraînement naïf, non basé sur le gradient ; il fonctionnait mais était peu évolutif et fragile.
  • Établissement d’une notation compacte pour les couches, \(\hat{y} = \phi_k( \ldots \phi_2(\phi_1(X)) \ldots )\)\(\phi_l(Z) = \phi(W_lZ_l + b_l)\), en vue de préparer le backpropagation.

Prochain cours

  • Nous introduirons le backpropagation, et discuterons du gradient évanescent, du softmax et de la régularisation.

Références

Cybenko, George V. 1989. « Approximation by superpositions of a sigmoidal function ». Mathematics of Control, Signals and Systems 2: 303‑14. https://api.semanticscholar.org/CorpusID:3958369.
Géron, Aurélien. 2022. Hands-on Machine Learning with Scikit-Learn, Keras, and TensorFlow. 3ᵉ éd. O’Reilly Media, Inc.
He, Kaiming, Xiangyu Zhang, Shaoqing Ren, et Jian Sun. 2016. « Deep Residual Learning for Image Recognition ». In 2016 IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 770‑78. https://doi.org/10.1109/CVPR.2016.90.
Hornik, Kurt, Maxwell Stinchcombe, et Halbert White. 1989. « Multilayer feedforward networks are universal approximators ». Neural Networks 2 (5): 359‑66. https://doi.org/https://doi.org/10.1016/0893-6080(89)90020-8.
Russell, Stuart, et Peter Norvig. 2020. Artificial Intelligence: A Modern Approach. 4ᵉ éd. Pearson. http://aima.cs.berkeley.edu/.

Marcel Turcotte

Marcel.Turcotte@uOttawa.ca

École de science informatique et de génie électrique (SIGE)

Université d’Ottawa