Régression linéaire et descente de gradient

CSI 4506 - automne 2025

Marcel Turcotte

Version: sept. 15, 2025 08h43

Préamble

Message du jour

Résultats d’apprentissage

  • Différencier les tâches de régression des tâches de classification.
  • Expliquer la méthodologie pour l’entraînement des modèles de régression linéaire.
  • Interpréter la fonction des algorithmes d’optimisation dans le traitement de la régression linéaire.
  • Détailler l’importance des dérivées partielles dans l’algorithme de descente de gradient.
  • Comparer les méthodes de descente de gradient par lot, stochastique et mini-lot.

Régression linéaire

Justification

La régression linéaire est introduite pour présenter de manière pratique un algorithme d’apprentissage bien connu, la descente de gradient. De plus, elle sert de base pour introduire la régression logistique – un algorithme de classification – qui facilite davantage les discussions sur les réseaux de neurones artificiels.

  • Régression linéaire
    • Descente de gradient
    • Régression logistique
      • Réseaux de neurones

Apprentissage supervisé - régression

  • Les données d’entraînement sont une collection d’exemples étiquetés.
    • \(\{(x_i,y_i)\}_{i=1}^N\)
      • Chaque \(x_i\) est un vecteur d’attributs avec \(D\) dimensions.
      • \(x_i^{(j)}\) est la valeur de l’attribut \(j\) de l’exemple \(i\), pour \(j \in 1 \ldots D\) et \(i \in 1 \ldots N\).
    • L’étiquette \(y_i\) est un nombre réel.
  • Problème : Étant donné l’ensemble de données en entrée, créer un modèle pouvant être utilisé pour prédire la valeur de \(y\) pour un \(x\) non vu.

Éruptions du Old Faithful

import pandas as pd

WOLFRAM_CSV = "https://raw.githubusercontent.com/turcotte/csi4106-f25/refs/heads/main/datasets/old_faithful_eruptions/Sample-Data-Old-Faithful-Eruptions.csv"
df = pd.read_csv(WOLFRAM_CSV)

# Renommer les colonnes
df = df.rename(columns={"Duration": "eruptions", "WaitingTime": "waiting"})
print(df.shape)
df.head(6)

Éruptions du Old Faithful

(272, 2)
eruptions waiting
0 3.600 79
1 1.800 54
2 3.333 74
3 2.283 62
4 4.533 85
5 2.883 55

Geyser Old Faithful

Visualisation Rapide

Code
import matplotlib.pyplot as plt

plt.figure(figsize=(6,4))
plt.scatter(df["eruptions"], df["waiting"], s=20)
plt.xlabel("Durée de l'éruption (min)")
plt.ylabel("Temps d'attente jusqu'à la prochaine éruption (min)")
plt.title("Old Faithful : éruptions vs attente")
plt.tight_layout()
plt.show()

Problème

  • Prédire le temps d’attente jusqu’à la prochaine éruption, \(y\), en fonction de la durée de l’éruption actuelle, \(x\).

Régression linéaire

Un modèle linéaire suppose que la valeur de l’étiquette, \(\hat{y_i}\), peut être exprimée comme une combinaison linéaire des valeurs des attributs, \(x_i^{(j)}\) : \[ \hat{y_i} = \theta_0 + \theta_1 x_i^{(1)} + \theta_2 x_i^{(2)} + \ldots + \theta_D x_i^{(D)} \]

Ici, \(\theta_{j}\) est le \(j\)-ième paramètre du modèle (linéaire), avec \(\theta_0\) étant le terme/paramètre de biais, et \(\theta_1 \ldots \theta_D\) étant les poids des attributs.

Définition

Problème : trouver les valeurs pour tous les paramètres du modèle de sorte que le modèle “s’ajuste au mieux” aux données d’entraînement.

  • L’Erreur Quadratique Moyenne Racine (Root Mean Squared Error [RMSE]) est une mesure de performance courante pour les problèmes de régression.

\[ \sqrt{\frac{1}{N}\sum_1^N [h(x_i) - y_i]^2} \]

Minimisation de l’EQM (RMSE)

Apprentissage

Code
from sklearn.linear_model import SGDRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# Préparer les données
X = df[["eruptions"]].values  # shape (n_samples, 1)
y = df["waiting"].values      # shape (n_samples,)

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

# Ajuster via SGDRegressor — modèle linéaire via descente de gradient
sgd = SGDRegressor(
    loss="squared_error",
    penalty=None,
    learning_rate="constant",
    eta0=0.01,
    max_iter=2000,
    tol=None,
    random_state=42
)

sgd.fit(X_train, y_train)

print("Paramètres appris :")
print(f"  intercept = {sgd.intercept_[0]:.3f}")
print(f"  pente     = {sgd.coef_[0]:.3f}")

y_pred = sgd.predict(X_test)
print(f"MSE test = {mean_squared_error(y_test, y_pred):.2f}")
print(f"R² test  = {r2_score(y_test, y_pred):.3f}")
Paramètres appris :
  intercept = 32.910
  pente     = 10.503
MSE test = 43.02
R² test  = 0.671

Visualisation

Code
import numpy as np

# Disperser les données
plt.figure(figsize=(6,4))
plt.scatter(X, y, color="steelblue", s=30, alpha=0.7, label="données")

# Tracer la ligne ajustée
x_line = np.linspace(0, X.max(), 100).reshape(-1, 1)
y_line = sgd.predict(x_line)
plt.plot(x_line, y_line, color="red", linewidth=2, label="ligne ajustée")

plt.xlabel("Durée de l'éruption (min)")
plt.ylabel("Temps d'attente jusqu'à la prochaine éruption (min)")
plt.title("Old Faithful : Régression linéaire via SGD")
plt.legend()
plt.tight_layout()
plt.show()

Caractéristiques

Un algorithme d’apprentissage typique comprend les composants suivants :

  1. Un modèle, souvent composé d’un ensemble de paramètres dont les valeurs seront “apprises”.
  2. Une fonction objectif.
    • Dans le cas de la régression, il s’agit souvent d’une fonction de perte (loss function), une fonction qui quantifie la mauvaise classification. L’Erreur Quadratique Moyenne Racine est une fonction de perte courante pour les problèmes de régression. \(\sqrt{\frac{1}{N}\sum_1^N [h(x_i) - y_i]^2}\)
  3. Un algorithme d’optimisation

Optimisation

Jusqu’à ce que certains critères d’arrêt soient atteints1 :

  • Évaluer la fonction de perte, en comparant \(h(x_i)\) à \(y_i\).
  • Apporter de petites modifications aux poids, de manière à réduire la valeur de la fonction de perte.

Remarques

  • Il est important de séparer l’algorithme d’optimisation du problème qu’il traite.
  • Pour la régression linéaire, une solution analytique exacte existe, mais elle présente certaines limitations.
  • La descente de gradient sert d’algorithme général applicable non seulement à la régression linéaire, mais aussi à la régression logistique, l’apprentissage profond, t-SNE (t-distributed Stochastic Neighbor Embedding), parmi divers autres problèmes.
  • Il existe une gamme diversifiée d’algorithmes d’optimisation qui ne reposent pas sur des méthodes basées sur le gradient.

Optimisation — un attribut

  • Modèle (hypothèse) :
    \[ h(x_i; \theta) = \theta_0 + \theta_1 x_i^{(1)} \]

  • Fonction de perte/coût :
    \[ J(\theta_0, \theta_1) = \frac{1}{N}\sum_{i=1}^N [h(x_i;\theta) - y_i]^2 \]

Hypothèses vs paramètres

Hypothèses vs paramètres

Dérivée

Dérivée

  • Nous commencerons par une fonction à une variable.
  • Considérez cela comme notre fonction de perte, que nous visons à minimiser; pour réduire la disparité moyenne entre les valeurs attendues et les valeurs prédites.
  • J’utilise le symbol \(t\) pour la variable afin d’éviter toute confusion avec les attributs des exemples de notre jeu de données.

Code source

from sympy import *

t = symbols('t')

f = t**2 + 4*t + 7

plot(f)

Dérivée

  • Le graphe de la dérivée, \(f^{'}(t)\), est représenté en rouge.

  • La dérivée indique comment les changements dans l’entrée affectent la sortie, \(f(t)\).

  • La magnitude de la dérivée en \(t = -2\) est \(0\).

  • Ce point correspond au minimum de notre fonction.

Dérivée

  • Lorsqu’elle est évaluée en un point spécifique, la dérivée indique la pente de la ligne tangente au graphe de la fonction à ce point.

  • À \(t= -2\), la pente de la ligne tangente est de 0.

Dérivée

  • Une dérivée positive indique qu’augmenter la variable d’entrée entraînera une augmentation de la valeur de sortie.

  • De plus, la magnitude de la dérivée quantifie la rapidité du changement de la sortie.

Dérivée

  • Une dérivée négative indique qu’augmenter la variable d’entrée entraînera une diminution de la valeur de sortie.

  • De plus, la magnitude de la dérivée quantifie la rapidité du changement de la sortie.

Code source

import sympy as sp
import numpy as np
import matplotlib.pyplot as plt

# Define the variable and function
t = sp.symbols('t')
f = t**2 + 4*t + 7

# Compute the derivative
f_prime = sp.diff(f, t)

# Lambdify the functions for numerical plotting
f_func = sp.lambdify(t, f, "numpy")
f_prime_func = sp.lambdify(t, f_prime, "numpy")

# Generate t values for plotting
t_vals = np.linspace(-5, 2, 400)

# Get y values for the function and its derivative
f_vals = f_func(t_vals)
f_prime_vals = f_prime_func(t_vals)

# Plot the function and its derivative
plt.plot(t_vals, f_vals, label=r'$f(t) = t^2 + 4t + 7$', color='blue')
plt.plot(t_vals, f_prime_vals, label=r"$f'(t) = 2t + 4$", color='red')

# Fill the area below the derivative where it's negative
plt.fill_between(t_vals, f_prime_vals, where=(f_prime_vals > 0), color='red', alpha=0.3)

# Add labels and legend
plt.axhline(0, color='black',linewidth=1)
plt.axvline(0, color='black',linewidth=1)
plt.title('Function and Derivative')
plt.tlabel('t')
plt.ylabel('y')
plt.legend()

# Show the plot
plt.grid(True)
plt.show()

Descente de gradient

Descente de gradient — un attribut

  • Modèle (hypothèse) :
    \[ h(x_i; \theta) = \theta_0 + \theta_1 x_i^{(1)} \]

  • Fonction de perte/coût :
    \[ J(\theta_0, \theta_1) = \frac{1}{N}\sum_{i=1}^N [h(x_i;\theta) - y_i]^2 \]

Descente de gradient - intuition

Descente de gradient - étape par étape

Descente de gradient - une variable

  • Initialisation : \(\theta_0\) et \(\theta_1\) - soit avec des valeurs aléatoires, soit avec des zéros.
  • Boucle :
  • répéter jusqu’à convergence : \[ \theta_j := \theta_j - \alpha \frac {\partial}{\partial \theta_j}J(\theta_0, \theta_1) , \text{pour } j=0 \text{ et } j=1 \]
  • \(\alpha\) est appelé le taux d’apprentissage - c’est la taille de chaque pas.
  • \(\frac {\partial}{\partial \theta_j}J(\theta_0, \theta_1)\) est la dérivée partielle par rapport à \(\theta_j\).

Descente de gradient - une variable

Code
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt

# Définir la variable et la fonction
t = sp.symbols('t')
f = t**2 + 4*t + 7

# Calculer la dérivée
f_prime = sp.diff(f, t)

# Lambdifier les fonctions pour le traçage numérique
f_func = sp.lambdify(t, f, "numpy")
f_prime_func = sp.lambdify(t, f_prime, "numpy")

# Générer des valeurs de t pour le traçage
t_vals = np.linspace(-5, 2, 400)

# Obtenir des valeurs y pour la fonction et sa dérivée
f_vals = f_func(t_vals)
f_prime_vals = f_prime_func(t_vals)

# Tracer la fonction et sa dérivée
plt.plot(t_vals, f_vals, label=r'$J$', color='blue')
plt.plot(t_vals, f_prime_vals, label=r"$\frac {\partial}{\partial \theta_j}J(\theta)$", color='red')

# Ajouter des étiquettes et une légende
plt.axhline(0, color='black',linewidth=1)
plt.axvline(0, color='black',linewidth=1)
plt.title('Fonction et Dérivée')
plt.xlabel(r'$\theta_j$')
plt.ylabel(r'$J$')
plt.legend()

# Afficher le graphique
plt.grid(True)
plt.show()

  • Lorsque la valeur de \(\theta_j\) est dans l’intervalle \([- \inf, -2)\), \(\frac {\partial}{\partial \theta_j}J(\theta)\) a une valeur négative.

  • Par conséquent, \(- \alpha \frac {\partial}{\partial \theta_j}J(\theta)\) est positif.

  • En conséquence, la valeur de \(\theta_j\) est augmentée.

Descente de gradient - une variable

Code
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt

# Définir la variable et la fonction

t = sp.symbols('t')
f = t**2 + 4*t + 7

# Calculer la dérivée

f_prime = sp.diff(f, t)

# Lambdifier les fonctions pour le tracé numérique

f_func = sp.lambdify(t, f, "numpy")
f_prime_func = sp.lambdify(t, f_prime, "numpy")

# Générer les valeurs de t pour le traçage

t_vals = np.linspace(-5, 2, 400)

# Obtenir les valeurs y pour la fonction et sa dérivée

f_vals = f_func(t_vals)
f_prime_vals = f_prime_func(t_vals)

# Tracer la fonction et sa dérivée

plt.plot(t_vals, f_vals, label=r'$J$', color='blue')
plt.plot(t_vals, f_prime_vals, label=r"$\frac {\partial}{\partial \theta_j}J(\theta)$", color='red')

# Ajouter des étiquettes et une légende

plt.axhline(0, color='black',linewidth=1)
plt.axvline(0, color='black',linewidth=1)
plt.title('Fonction et Dérivée')
plt.xlabel(r'$\theta_j$')
plt.ylabel(r'$J$')
plt.legend()

# Afficher le graphique

plt.grid(True)
plt.show()

  • Lorsque la valeur de \(\theta_j\) est dans l’intervalle \((-2, \infty]\), \(\frac {\partial}{\partial \theta_j}J(\theta)\) a une valeur positive.

  • Par conséquent, \(- \alpha \frac {\partial}{\partial \theta_j}J(\theta)\) est négatif.

  • En conséquence, la valeur de \(\theta_j\) est diminuée.

Dérivées partielles

Étant donnée

\[ J(\theta_0, \theta_1) = \frac{1}{N}\sum_1^N [h(x_i) - y_i]^2 = \frac{1}{N}\sum_1^N [\theta_0 + \theta_1 x_i - y_i]^2 \]

Nous avons

\[ \frac {\partial}{\partial \theta_0}J(\theta_0, \theta_1) = \frac{2}{N} \sum\limits_{i=1}^{N} [\theta_0 - \theta_1 x_i - y_{i}] \]

et

\[ \frac {\partial}{\partial \theta_1}J(\theta_0, \theta_1) = \frac{2}{N} \sum\limits_{i=1}^{N} x_{i} [\theta_0 + \theta_1 x_i - y_{i}] \]

Dérivée partielle (SymPy)

from IPython.display import Math, display
from sympy import *

# Define the symbols

theta_0, theta_1, x_i, y_i = symbols('theta_0 theta_1 x_i y_i')

# Define the hypothesis function:

h = theta_0 + theta_1 * x_i

print("Hypothesis function:")

display(Math('h(x) = ' + latex(h)))
Hypothesis function:

\(\displaystyle h(x) = \theta_{0} + \theta_{1} x_{i}\)

Dérivée partielle (SymPy)

N = Symbol('N', integer=True)

# Define the loss function (mean square error)

J = (1/N) * Sum((h - y_i)**2, (x_i, 1, N))

print("Loss function:")

display(Math('J = ' + latex(J)))
Loss function:

\(\displaystyle J = \frac{\sum_{x_{i}=1}^{N} \left(\theta_{0} + \theta_{1} x_{i} - y_{i}\right)^{2}}{N}\)

Dérivée partielle (SymPy)

# Calculate the partial derivative with respect to theta_0

partial_derivative_theta_0 = diff(J, theta_0)

print("Partial derivative with respect to theta_0:")

display(Math(latex(partial_derivative_theta_0)))
Partial derivative with respect to theta_0:

\(\displaystyle \frac{\sum_{x_{i}=1}^{N} \left(2 \theta_{0} + 2 \theta_{1} x_{i} - 2 y_{i}\right)}{N}\)

Dérivée partielle (SymPy)

# Calculate the partial derivative with respect to theta_1

partial_derivative_theta_1 = diff(J, theta_1)

print("Partial derivative with respect to theta_1:")

display(Math(latex(partial_derivative_theta_1)))
Partial derivative with respect to theta_1:

\(\displaystyle \frac{\sum_{x_{i}=1}^{N} 2 x_{i} \left(\theta_{0} + \theta_{1} x_{i} - y_{i}\right)}{N}\)

Régression linéaire multivariée

\[ h(x_i) = \theta_0 + \theta_1 x_i^{(1)} + \theta_2 x_i^{(2)} + \theta_3 x_i^{(3)} + \cdots + \theta_D x_i^{(D)} \]

\[ \begin{align*} x_i^{(j)} &= \text{valeur de l'attribut } j \text{ dans le } i \text{ème exemple} \\ D &= \text{le nombre d'attributs'} \end{align*} \]

Descente de gradient - multivariée

La nouvelle fonction de perte est

\[ J(\theta_0, \theta_1, \ldots, \theta_D) = \dfrac {1}{N} \displaystyle \sum _{i=1}^N [ h(x_{i}) - y_i ]^2 \]

Sa dérivée partielle :

\[ \frac {\partial}{\partial \theta_j}J(\theta) = \frac{2}{N} \sum\limits_{i=1}^N x_i^{(j)} [ \theta x_i - y_i ] \]

\(\theta\), \(x_i\) et \(y_i\) sont des vecteurs, et \(\theta x_i\) est une opération vectorielle !

Vecteur gradient

Le vecteur contenant la dérivée partielle de \(J\) (par rapport à \(\theta_j\), pour \(j \in \{0, 1 \ldots D\}\)) est appelé le vecteur gradient.

\[ \nabla_\theta J(\theta) = \begin{pmatrix} \frac {\partial}{\partial \theta_0}J(\theta) \\ \frac {\partial}{\partial \theta_1}J(\theta) \\ \vdots \\ \frac {\partial}{\partial \theta_D}J(\theta)\\ \end{pmatrix} \]

  • Ce vecteur donne la direction de la plus forte pente ascendante.
  • Il donne son nom à l’algorithme de descente de gradient :

\[ \theta' = \theta - \alpha \nabla_\theta J(\theta) \]

Descente de gradient - multivariée

L’algorithme de descente de gradient devient :

Répétez jusqu’à convergence :

\[ \begin{aligned} \{ & \\ \theta_j := & \theta_j - \alpha \frac {\partial}{\partial \theta_j}J(\theta_0, \theta_1, \ldots, \theta_D) \\ & \text{pour } j \in [0, \ldots, D] \textbf{ (mettre à jour simultanément)} \\ \} & \end{aligned} \]

Descente de gradient - multivariée

Répétez jusqu’à convergence :

\[ \begin{aligned} \; \{ & \\ \; & \theta_0 := \theta_0 - \alpha \frac{2}{N} \sum\limits_{i=1}^{N} x^{0}_i[h(x_i) - y_i] \\ \; & \theta_1 := \theta_1 - \alpha \frac{2}{N} \sum\limits_{i=1}^{N} x^{1}_i[h(x_i) - y_i] \\ \; & \theta_2 := \theta_2 - \alpha \frac{2}{N} \sum\limits_{i=1}^{N} x^{2}_i[h(x_i) - y_i] \\ & \cdots \\ \} & \end{aligned} \]

Hypothèses

Quelles étaient nos hypothèses ?

  • La fonction (objectif/de perte) est différentiable.

Local vs global

  • Une fonction est convexe si pour toute paire de points sur le graphe de la fonction, la ligne reliant ces deux points se trouve au-dessus ou sur le graphe.

  • Une fonction convexe possède un unique minimum.

  • La fonction de perte pour la régression linéaire (MSE) est convexe.

  • Pour les fonctions qui ne sont pas convexes, l’algorithme de descente de gradient converge vers un minimum local.

  • Les fonctions de perte généralement utilisées avec les régressions linéaires ou logistiques, et les Support Vector Machines (SVM) sont convexes, mais pas celles pour les réseaux de neurones artificiels.

Local vs global

Convergence

Code
# 1. Définir la variable symbolique et la fonction
x = sp.Symbol('x', real=True)
f_expr = 2*x**3 + 4*x**2 - 5*x + 1

# 2. Calculer la dérivée de f
f_prime_expr = sp.diff(f_expr, x)

# 3. Convertir les expressions symboliques en fonctions Python
f = sp.lambdify(x, f_expr, 'numpy')
f_prime = sp.lambdify(x, f_prime_expr, 'numpy')

# 4. Générer une plage de valeurs x
x_vals = np.linspace(-4, 2, 1000)

# 5. Calculer f et f' sur cette plage
y_vals = f(x_vals)
y_prime_vals = f_prime(x_vals)

# 6. Préparer les chaînes LaTeX pour la légende
f_label = rf'$f(x) = {sp.latex(f_expr)}$'
f_prime_label = rf'$f^\prime(x) = {sp.latex(f_prime_expr)}$'

# 7. Tracer f et f', avec les équations dans la légende
plt.figure(figsize=(8, 4))
plt.plot(x_vals, y_vals, label=f_label)
plt.plot(x_vals, y_prime_vals, label=f_prime_label)

# 8. Colorier la région entre l'axe x et f'(x) pour tout le domaine
plt.fill_between(x_vals, y_prime_vals, 0, color='gray', alpha=0.2, interpolate=True,
                 label='Région entre 0 et f\'(x)')

# 9. Ajouter une ligne de référence, des étiquettes, une légende, etc.
plt.axhline(0, color='black', linewidth=0.5)
plt.title(rf'Fonction et sa dérivée avec ombrage pour $f^\prime(x)$')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.show()

Taux d’apprentissage

  • Petits pas, des valeurs faibles pour \(\alpha\), feront que l’algorithme convergera lentement.
  • Grands pas peuvent amener l’algorithme à diverger.
  • Remarquez comment l’algorithme ralentit naturellement en approchant d’un minimum.

Taux d’apprentissage

Code
import numpy as np
import matplotlib.pyplot as plt

def f(x):
    return x**2

def grad_f(x):
    return 2*x

# Estimation initiale, taux d'apprentissage et nombre d'étapes de descente de gradient
x_current = 2.0
learning_rate = 1.1  # Trop grand => divergence
num_iterations = 5   # Nous ferons cinq mises à jour

# Stocker chaque valeur de x dans une liste (trajectoire) pour l'affichage
trajectory = [x_current]

# Effectuer la descente de gradient
for _ in range(num_iterations):
    g = grad_f(x_current)
    x_current = x_current - learning_rate * g
    trajectory.append(x_current)

# Préparer les données pour l'affichage
x_vals = np.linspace(-5, 5, 1000)
y_vals = f(x_vals)

# Tracer la fonction f(x)
plt.figure(figsize=(6, 5))
plt.plot(x_vals, y_vals, label=r"$f(x) = x^2$")
plt.axhline(0, color='black', linewidth=0.5)

# Tracer la trajectoire, en étiquetant chaque itération
for i, x_t in enumerate(trajectory):
    y_t = f(x_t)
    # Tracer le point
    plt.plot(x_t, y_t, 'ro')
    # Étiqueter le numéro d'itération
    plt.text(x_t, y_t, f"  {i}", color='red')
    # Connecter les points consécutifs
    if i > 0:
        x_prev = trajectory[i - 1]
        y_prev = f(x_prev)
        plt.plot([x_prev, x_t], [y_prev, y_t], 'r--')

# Finitions
plt.title("Divergence de la descente de gradient avec un grand taux d'apprentissage")
plt.xlabel("x")
plt.ylabel("f(x)")
plt.legend()
plt.grid(True)
plt.show()

Descente de gradient par lot (batch)

  • Pour être plus précis, cet algorithme est connu sous le nom de descente de gradient par lot car à chaque itération, il traite le “lot entier” d’exemples d’apprentissage.
  • La littérature suggère que l’algorithme pourrait mettre plus de temps à converger si les attributs sont sur des échelles différentes.

Descente de gradient par lot - inconvénient

  • L’algorithme de descente de gradient par lot devient très lent à mesure que le nombre d’exemples d’apprentissage augmente.
  • Cela est dû au fait que toutes les données d’apprentissage sont vues à chaque itération. L’algorithme est généralement exécuté pour un nombre fixe d’itérations, disons 1000.

Descente de gradient stochastique

L’algorithme de descente de gradient stochastique sélectionne aléatoirement un exemple d’apprentissage pour calculer son gradient.

epochs = 10
for epoch in range(epochs):
    for i in range(N):
        selection = np.random.randint(N)
        # Calcul du gradient à l'aide de la sélection
        # Mise à jour des paramètres
  • Cela lui permet de fonctionner avec de grands ensembles d’apprentissage.
  • Sa trajectoire n’est pas aussi régulière que l’algorithme par lot.
  • En raison de sa trajectoire accidentée, il est souvent meilleur pour trouver les minima globaux par rapport à l’algorithme par lot.
  • Sa trajectoire accidentée le fait rebondir autour des minima locaux.

Descente de gradient par mini-lots

  • À chaque étape, plutôt que de sélectionner un exemple d’apprentissage comme le fait la SGD, la descente de gradient par mini-lots (mini-batch) sélectionne aléatoirement un petit nombre d’exemples d’apprentissage pour calculer les gradients.
  • Sa trajectoire est plus régulière comparée à la SGD.
  • À mesure que la taille des mini-lots augmente, l’algorithme devient de plus en plus similaire à la descente de gradient par lot, qui utilise tous les exemples à chaque étape.
  • Il peut profiter de l’accélération matérielle des opérations matricielles, en particulier avec les GPU.

Visualisation rapide

Code
import matplotlib.pyplot as plt

plt.figure(figsize=(6,4))
plt.scatter(df["eruptions"], df["waiting"], s=20)
plt.xlabel("Durée de l'éruption (min)")
plt.ylabel("Temps d'attente jusqu'à la prochaine éruption (min)")
plt.title("Old Faithful : éruptions vs attente")
plt.tight_layout()
plt.show()

Stochastique, mini-lots, lot

Résumé

  • La descente de gradient par lot est intrinsèquement lente et impraticable pour de grands ensembles de données nécessitant une prise en charge hors mémoire, bien qu’elle soit capable de gérer un nombre substantiel d’attributs.

  • La descente de gradient stochastique est rapide et bien adaptée pour traiter un grand volume d’exemples de manière efficace.

  • La descente de gradient par mini-lots combine les avantages des méthodes par lot et stochastiques ; elle est rapide, capable de gérer de grands ensembles de données, et tire parti de l’accélération matérielle, en particulier avec les GPU.

Optimisation et réseaux profonds

Nous allons brièvement revisiter le sujet en discutant des réseaux de neurones artificiels profonds, pour lesquels il existe des algorithmes d’optimisation spécialisés.

  • Optimisation par Momentum
  • Gradient Acceleré de Nesterov
  • AdaGrad
  • RMSProp
  • Adam et Nadam

Dernier mot

  • L’optimisation est un sujet vaste. D’autres algorithmes existent et sont utilisés dans d’autres contextes.
  • Parmi eux :
    • L’optimisation par essaims particulaires (PSO), les algorithmes génétiques (GA), et les algorithmes de colonie d’abeilles artificielles (ABC).

Prologue

Régression linéaire - résumé

  • Un modèle linéaire suppose que la valeur de l’étiquette, \(\hat{y_i}\), peut être exprimée comme une combinaison linéaire des valeurs des attributs, \(x_i^{(j)}\) : \[ \hat{y_i} = h(x_i) = \theta_0 + \theta_1 x_i^{(1)} + \theta_2 x_i^{(2)} + \ldots + \theta_D x_i^{(D)} \]

  • L’écart moyen quadratique (MSE) est : \[ \frac{1}{N}\sum_1^N [h(x_i) - y_i]^2 \]

  • La descente de gradient par lot, stochastique, ou par mini-lots peut être utilisée pour trouver les valeurs “optimales” des poids, \(\theta_j\) pour \(j \in 0, 1, \ldots, D\).

  • Le résultat est un régresseur, une fonction qui peut être utilisée pour prédire la valeur \(y\) (l’étiquette) pour un exemple non vu \(x\).

Andrew Ng

Herman Kamper

Mathématiques

3Blue1Brown

  • Essence de l’algèbre linéaire
    • Une série de 16 vidéos (10 à 15 minutes par vidéo) offrant “une compréhension géométrique des matrices, déterminants, valeurs propres et plus encore.”
      • 6 662 732 vues au 30 septembre 2019.
  • Essence du calcul
    • Une série de 12 vidéos (15 à 20 minutes par vidéo) : “L’objectif ici est de faire en sorte que le calcul apparaisse comme quelque chose que vous auriez pu découvrir vous-même.”
      • 2 309 726 vues au 30 septembre 2019.

Prochain cours

  • Régression logistique

Annexe

Régression linéaire

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X, y)

Code source

import numpy as np

X = 6 * np.random.rand(100, 1) - 4
y = X ** 2 - 4 * X + 5 + np.random.randn(100, 1)

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X, y)

X_new = np.array([[-4], [2]])
y_pred = lin_reg.predict(X_new)

Interactions GPT 4o (2024-09-14)

Utilisateur : Expliquez le code sur l’écran précédent ligne par ligne.

IA : Certainement ! Voici une explication ligne par ligne du code fourni :

import numpy as np
  • Explication : Cette ligne importe la bibliothèque NumPy, qui est un paquet fondamental pour les calculs numériques en Python. Elle est couramment utilisée pour travailler avec des tableaux et des matrices, ainsi que pour effectuer diverses opérations mathématiques.
X = 6 * np.random.rand(100, 1) - 4
  • Explication : Cette ligne génère un tableau NumPy X contenant 100 lignes et 1 colonne de nombres aléatoires. La fonction np.random.rand(100, 1) génère des nombres aléatoires uniformément distribués entre 0 et 1. La multiplication par 6 ajuste ces nombres à la plage [0, 6), et la soustraction de 4 déplace la plage à [-4, 2).
y = X ** 2 - 4 * X + 5 + np.random.randn(100, 1)
  • Explication : Cette ligne génère la variable cible y basée sur une équation quadratique avec du bruit ajouté. Plus précisément :

    • X ** 2 calcule le carré de chaque élément de X.
    • - 4 * X soustrait quatre fois chaque élément de X.
    • + 5 ajoute une constante de 5.
    • + np.random.randn(100, 1) ajoute du bruit gaussien avec une moyenne de 0 et un écart-type de 1 à chaque élément, rendant les données plus réalistes en introduisant une certaine variabilité.
from sklearn.linear_model import LinearRegression
  • Explication : Cette ligne importe la classe LinearRegression du module sklearn.linear_model, qui fait partie de la bibliothèque Scikit-Learn. Scikit-Learn est largement utilisée pour l’apprentissage automatique en Python.
lin_reg = LinearRegression()
  • Explication : Cette ligne crée une instance de la classe LinearRegression et l’assigne à la variable lin_reg. Cet objet sera utilisé pour ajuster le modèle de régression linéaire aux données.
lin_reg.fit(X, y)
  • Explication : Cette ligne ajuste le modèle de régression linéaire aux données en trouvant la meilleure ligne passant par les points (X, y). La méthode fit entraîne le modèle en utilisant les attributs fournis X et la variable cible y.
X_new = np.array([[-4], [2]])
  • Explication : Cette ligne crée un nouveau tableau NumPy X_new avec deux valeurs : -4 et 2. Ces valeurs représentent de nouveaux points de données pour lesquels nous voulons prédire la variable cible en utilisant le modèle entraîné.
y_pred = lin_reg.predict(X_new)
  • Explication : Cette ligne utilise le modèle de régression linéaire entraîné pour prédire la variable cible y_pred pour les nouveaux points de données dans X_new. La méthode predict renvoie les valeurs prédites basées sur le modèle ajusté.

En résumé, ce script génère des données synthétiques, ajuste un modèle de régression linéaire à ces données, puis utilise le modèle pour faire des prédictions sur de nouveaux points de données.

Source code (continued)

import os
import matplotlib as mpl
import matplotlib.pyplot as plt

def save_fig(fig_id, tight_layout=True, fig_extension="pdf", resolution=300):
    path = os.path.join(fig_id + "." + fig_extension)
    print("Saving figure", fig_id)
    if tight_layout:
        plt.tight_layout()
    plt.savefig(path, format=fig_extension, dpi=resolution)

Source code (continued)

plt.plot(X, y, "b.")
plt.plot(X_new, y_pred, "r-")
plt.xlabel("$x$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([-4, 2, -1, 35])
save_fig("regression_linear-01")
plt.show()

References

Azzalini, A., et A. W. Bowman. 1990. « A Look at Some Data on the Old Faithful Geyser ». Journal of the Royal Statistical Society Series C: Applied Statistics 39 (3): 357‑65. https://doi.org/10.2307/2347385.
Géron, Aurélien. 2022. Hands-on Machine Learning with Scikit-Learn, Keras, and TensorFlow. 3ᵉ éd. O’Reilly Media, Inc.
Russell, Stuart, et Peter Norvig. 2020. Artificial Intelligence: A Modern Approach. 4ᵉ éd. Pearson. http://aima.cs.berkeley.edu/.
Stanton, Jeffrey M. 2001. « Galton, Pearson, and the Peas: A Brief History of Linear Regression for Statistics Instructors ». Journal of Statistics Education 9 (3). https://doi.org/10.1080/10691898.2001.11910537.

Marcel Turcotte

Marcel.Turcotte@uOttawa.ca

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

Université d’Ottawa