Dans une première partie nous allons aborder succintement les notions mathématiques et informatiques nécessaires pour une bonne compréhension des explications données sur l'apprentissage supervisé qui fera l'objet de la deuxième partie de cet enseignement.
Exemple en physique:
$$v(x, t) = \dfrac{x}{t}$$
$x$ indique la distance parcourue par un objet et $t$ la durée de déplacement. $v$ est alors une fonction à deux variables réelles. Les deux variables sont $x$ et $t$. Il est important de noter que les deux variables sont indépendantes.
$v : \mathbb{R^+}\times\mathbb{R^+}\longrightarrow\mathbb{R^+}$
$v : (x, t)\mapsto v(x, t) = \dfrac{x}{t}$
Définition :
Soit $(x, y)\in E \subset \mathbb{R}^2$
On appelle fonction de deux variables définie sur $E$, le procédé qui consiste à associer à chaque couple $(x, y)$ de $E$ un réel unique. On note généralement : $f(x, y) = z$
On peut se représenter z comme une altitude définie en chaque point du plan de base.
Définition :
Soit $f$ , une fonction de deux variables définie sur un domaine $E$. L’ensemble des points de coordonnées $(x, y, z)$ avec $z = f (x, y)$, pour $(x, y)$ parcourant $E$ est appelé surface d’équation $z = f (x, y)$
Pour représenter une fonction de $\mathbb{R}^2$ dans $\mathbb{R}$, on représente les points de coordonnées $M(x, y, f (x, y))$.
Exemple : Représentation de la surface d'équation : $\quad z = x^2 + y^2$
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
# La fonction à représenter
def f(x, y):
return x ** 2 + y ** 2
def affiche_3DXY(f, ddx, ddy):
# L'intervalle de définition pour chaque variable
x = np.linspace(ddx[0], ddx[1], 50)
y = np.linspace(ddy[0], ddy[1], 50)
# Calcul des valeurs de z = f(x, y)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
# Paramètres de l'affichage 3D de la surface
plt.figure(figsize=(10,10))
ax = plt.axes(projection='3d')
ax.set_xlabel('$X$',fontsize=14);
ax.set_ylabel('$Y$',fontsize=14);
ax.set_zlabel('$Z$',fontsize=14);
surf = ax.plot_surface(X, Y, Z, cmap='jet',
linewidth=0, antialiased=False)
# Affichage de la surface
plt.show()
affiche_3DXY(f, [-6, 6], [-6, 6])
La dérivation d’une fonction d’une variable peut être généralisée. Les dérivées partielles d’une fonction de deux variables x et y se calculent de la façon suivante :
La dérivée partielle de $f$ par rapport à $x$ se note $\dfrac{\partial f}{\partial x}$, la dérivée partielle de $f$ par rapport à $y$ se note $\dfrac{\partial f}{\partial y}$
Exercices : Calculer les dérivées partielles $\dfrac{\partial f}{\partial x}(x, y)$ et $\dfrac{\partial f}{\partial y}(x, y)$ des fonctions suivantes
On rappelle la loi de Boyle Mariotte, valable pour une mole de gaz parfait : $P V = RT $, où $P$ désigne la pression du gaz, $V$ son volume, $R$ la constante des gaz parfaits et $T$ la température du milieu. La pression P est donc une fonction des deux variables T et V
$$ \left(P+\dfrac{a}{V^2}\right)(V-b) = RT$$
On appelle gradient d'une fonction $f$ en un point $M$, et on le note $\vec{\nabla}\,f$, le vecteur dont les composantes sont les dérivées partielles de $f$ calculées au point $M$
Pour une fonction à deux variables on écrit :
$\vec{\nabla} f : (x, y)\mapsto (f_x'(x, y), f_y'(x, y))$
$$\vec{\nabla}\,f (x, y)= \begin{pmatrix} \dfrac{\partial f}{\partial x}(x, y)\\ \dfrac{\partial f}{\partial y}(x, y)\end{pmatrix}$$
À faire : Quel est le gradient des fonctions suivantes au point $(2,5)$
$f: (x, y)\mapsto x^2 + y^2 + xy$
$f: (x, y)\mapsto xe^y - 2xy$
Pour une fonction de plusieurs variables, la direction du vecteur gradient correspond à la ligne de plus grande pente au point considéré, c’est la direction qu’il faut suivre en partant du point pour avoir le plus grand taux de variation de $f$.
La norme du gradient donne la pente de la surface (plus grande pente) au point considéré.
Les points critiques d’une fonction $f$ de plusieurs variables sont les points où son gradient s’annule
Définition :
Soit $f$ une fonction définie sur un ouvert $E$ de $\mathbb{R}^2$. On appelle courbe de niveau de $f$ associée au réel $k$ l'ensemble des points $(x,y)$ de $E$ vérifiant $f(x,y)=k$. Cet ensemble est éventuellement vide.
Exemples :
# La fonction à représenter
def f(x, y):
return x ** 3
def courbes_niveaux(f, ddx, ddy, dim):
# L'intervalle de définition pour chaque variable
x = np.linspace(ddx[0], ddx[1], 30)
y = np.linspace(ddy[0], ddy[1], 30)
# Calcul des valeurs de z = f(x, y)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
# Paramètres de l'affichage 3D de la surface
plt.figure(figsize=(dim[0],dim[1]))
#Les courbes de niveaux
cont = plt.contour(X,Y, Z, 10, colors='grey')
plt.clabel(cont, inline=1,fontsize=10,fmt='%3.1f')
# Affichage de la surface
plt.xlabel("X")
plt.ylabel("Y")
plt.show()
courbes_niveaux(f, [-6, 6], [-6, 6], (10, 5))
affiche_3DXY(f, [-6, 6], [-6, 6])
À faire
Des exemples plus compliqués
def f(x, y):
return x**2 - 6*y**2
courbes_niveaux(f, [-6, 6], [-6, 6], (10,10))
affiche_3DXY(f, [-6, 6], [-6, 6])
def f(x, y):
return ((x**2-1) + (y**2-4) + (x**2-1)*(y**2-4))/(x**2+y**2+1)**2
courbes_niveaux(f, [-6, 6], [-6, 6], (10,10))
affiche_3DXY(f, [-6, 6], [-6, 6])
La descente de gradient est une méthode d'analyse numérique permettant dans certaines conditions de déterminer le minimum d'une fonction. Prenons un exemple de fonction à une variable pour commencer.
Soit $\quad f : x\mapsto x^4 - 5x^3 - 3x^2 + 20x - 2$ avec $x\in\mathbb{R}$
# La fonction à représenter
def f(x):
return x**4 - 5*x**3 - 3*x**2 + 20*x - 2
# L'intervalle de définition
x = np.linspace(-2, 5, 100)
y = f(x)
#les points
plt.plot(x,y)
#affichage
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.grid()
plt.show()
L'idée est de construire une suite de valeur notée $(x_n)_{n\in\mathbb{N}}$ nous permettant d'approcher les points critiques de $f$ : $\nabla f(x) = 0$.
$$x_{n+1} = x_n - \eta\, \nabla f(x) = x_n - \eta \dfrac{\partial f}{\partial x}(x)$$ $$\boxed{x_{n+1} = x_n - \eta\, f'(x)}$$
Le critère d'arrêt est donné par $\Delta x = |x_{n+1} - x_n| <= \varepsilon$, ($\varepsilon\in\mathbb{R}$)
Exemple :
$f'(x) = 4x^3-15x^2-6x+20$
Soient : $\eta = 0.001$ et $x_0=5$
À faire :
def f(x):
return x**4 - 5*x**3 - 3*x**2 + 20*x - 2
def gradf(x):
return 4*x**3 - 15*x**2 - 6*x + 20
def descente(x, pas):
return x - pas*gradf(x)
# les données
x0 = 5
pas = 0.001
epsilon = 1e-12
# Initialisation du critère d'arrêt
prec = x0
deltaX = descente(prec, pas) - prec
# Nombre d'itérations pour atteindre un minimum de f
compt = 0
while abs(deltaX) > epsilon:
suiv = descente(prec, pas)
deltaX = suiv-prec
prec = suiv
compt += 1
# Affichage des résultats
print(compt, f(suiv))
À faire : Écrire une fonction $descente\_gradient$ dont vous choisirez les paramètres renvoyant un minimum d'une fonction donnée.
L'apprentissage automatique est la science qui permet de programmer un ordinateur pour qu'il apprenne à partir des données dans le but de faire des prédictions sur de nouvelles données.
Dans l'apprentissage supervisé ont peux distinguer deux étapes :
Remarque : Features en anglais ou variables explicatives en français sont les variables accessibles, que l'on peut mesurer.
Quelques exemples classiques d'apprentissage supervisé :
Soit $f$ la fonction de transfert suivante : $$ \forall x\in\mathbb{R},\ f_H: x\mapsto \left\{ \begin{array}{cccl} 0 & \text{si} & x < 0 \\ 1 & \text{si} & x \geq 0 \\ \end{array}\right. $$
Perceptron : L'objectif est d'implanter un perceptron à seuil (neurone simple à seuil) pour résoudre les problèmes de tri entre deux classes (ensembles d'élement) linéairements séparables. Un cas typique de classification.
Description
L'unique fonction de ce neurone est de fournir une sortie égale à $1$ ou $0$, on parle alors de neurone activé pour la valeur $1$ et de neurone non activé pour la valeur $0$.
Pour cela on effectue la somme $S = b + \sum\limits_{i=1}^n x_i\,\omega_i$, avec $b$ le poids associé à l'entrée de biais du neurone artificiel. La valeur de l'entrée de biais est fixée à 1. Connaissant cette somme on décide d'activer ou non notre neurone en fonction d'un seuil d'activation $\theta$, fixé au préalable.
Pour décider de l'activation de notre neurone on peut utiliser la fonction de transfert HeavySide notée $f_H$ de type seuil qui renvoie
heaviside :$\hphantom{000000000000000000000000000}$ | Graphique : |
---|---|
$$ y_H=f_H(S)=\left\{\begin{array}{cccl}0 & \text{si} & S < 0 \\1 & \text{si} & S \geq 0 \\ \end{array}\right.$$ |
$$y_H = f_H(S) = f_H(b + \sum\limits_{i=1}^n x_i\,\omega_i)$$
Remarques :
heaviside :$\hphantom{000000000000000000000000}$ | Graphique : |
---|---|
$$ y_H=\left\{\begin{array}{cccl}0 & \text{si} & S < \theta \\1 & \text{si} & S \geq \theta \\ \end{array}\right.$$ |
L'apprentissage supervisé ou comment permettre à l'ordinateur d'entrer dans un processus d'apprentissage ?
L'algorithme consiste à donner des valeurs d'apprentissage au perceptron et comparer la sortie de la fonction Heaviside notée $y_H$ à la sortie attendue que l'on peut lire dans les valeurs d'apprentissage notée $y_v$ (étiquette).
La différence $\Delta y = y_v - y_H$, introduit une notion d'erreur permettant par la suite de corriger (ou pas) les poids attribués à chaque valeur d'entrée du neurone
Afin de mesurer l’erreur commise sur l’ensemble d’un jeu de données, il est nécessaire de se fixer une fonction de perte qui mesure le coût d’une erreur lors de la prédiction d’un exemple.Pour cela nous allons nous intéresser à la fonction de perte liée à l'erreur quadratique
$$E(y_v, f_H(b + \sum\limits_{i=1}^n x_i\,\omega_i)) = \dfrac{1}{2}(y_v-f_H(b + \sum\limits_{i=1}^n x_i\,\omega_i))^2=\dfrac{1}{2}(y_v-y_s)^2$$
La mise à jour des poids revient à minimiser la valeur de sortie de la fonction d'erreur que nous pouvons approcher grâce à la méthode de descente de gradient. Cet ajustement des poids se fera pour chaque n-uplet de valeurs en entrée du perceptron.
$$\Delta \omega = -\eta\nabla E$$
$$\begin{pmatrix} \Delta\omega_1\\ \Delta\omega_2\\ \dots\\ \Delta\omega_n\end{pmatrix}= -\eta \begin{pmatrix} \dfrac{\partial E}{\partial \omega_1}\\ \dfrac{\partial E}{\partial \omega_2}\\ \dots\\ \dfrac{\partial E}{\partial \omega_n}\end{pmatrix}$$
Commençons par évaluer $\dfrac{\partial E}{\partial \omega_i}$ pour une fonction d'activation quelconque de classe $\mathcal{C}^1$ notée $f$.
$E = \dfrac{1}{2}(y_v-y_s)^2 = \dfrac{1}{2}\left(y_v-f\left(\sum\limits_{i=0}^n x_i\,\omega_i\right)\right)^2\quad\text{Pour simplifier les notations le biais est inclu dans la somme des }\omega_ix_i$
$\dfrac{\partial E}{\partial \omega_i} = 2\times\dfrac{1}{2}\left(y_v-f\left(\sum\limits_{i=0}^n x_i\,\omega_i\right)\right)\times\dfrac{\partial }{\partial \omega_i}\left(y_v-f\left(\sum\limits_{i=0}^n x_i\,\omega_i\right)\right)$
$\dfrac{\partial E}{\partial \omega_i} = (y_v-y_s)\times\dfrac{\partial }{\partial \omega_i}\left(f\left(\sum\limits_{i=0}^n x_i\,\omega_i\right)\right) \quad\text{avec}\quad (f\circ g)'(x) = f'(g(x))g'(x)$
$\dfrac{\partial E}{\partial \omega_i} = (y_v - y_s)\ f'\left(\sum\limits_{i=0}^n x_i\omega_i\right)\,x_i$
$\Delta \omega_i = -\eta\,(y_v - y_s)\,x_i\ f'\left(\sum\limits_{i=0}^n x_i\omega_i\right)$
$$\boxed{\Delta \omega_i = \eta\,(y_s - y_v)\,x_i\ f'\left(\sum\limits_{i=0}^n x_i\omega_i\right)}$$
Nous obtenons une relation de mise à jour des poids pour un n-uplet donné en entrée du perceptron. Pour le biais il suffit de fixer le $x_i$ correspondant à $1$ ou $-1$ suivant les cas.
Remarque : La fonction HeaviSide est constante par morceaux ($y=0$ ou $y=1$) donc le terme $f'\left(\sum\limits_{i=0}^n x_i\omega_i\right)$ peut être remplacé par la valeur 1 ou -1.
Des données purement numériques avec une zone d'activité occupée par les points rouges et une zone d'activité occupée par les points bleus. Une fois l'apprentissage terminé tout nouveau point placé sur la carte doit pouvoir faire l'objet d'une prédiction d'appartenance à la zone rouge ou bleue.
from random import randint
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
x1, y1 = [0.03, 0.14, 0.4], [0.08, 0.36, 0.17]
x2, y2 = [0.67, 0.53], [0.69, 0.67]
plt.grid()
plt.plot(x1, y1, '*')
plt.plot(x2, y2, 'ro')
plt.show()
Pour cet exemple l'idée est de réduire à son strict minimum le nombre de variables explicatives. Cela permet d'observer le fonctionnement du neurone de notre perceptron dans un plan.
La Table d'apprentissage correspondante aux variables explicatives qui serviront à l'entraînement de notre neurone.
\begin{array}{|c|c|c|}\hline x_1 & x_2 & y_v\\\hline 0.03 & 0.08 & 1\\\hline 0.14 & 0.36 & 1\\\hline 0.4 & 0.17 & 1\\\hline 0.53 & 0.67 & 0\\\hline 0.67 & 0.69 & 0\\\hline \end{array}
Soit $b = 0.5$, $\forall i\in\mathbb{N},\, \omega_i=0$ et $\eta = 0.2$
Méthode :
À faire : Compléter le tableau suivant :
\begin{array}{|c|c|c|c|c|c|c|c|}\hline \text{Itérations} & \text{n-uplet} & b & \omega_1 & \omega_2 & S = b +\sum\limits_{i=1}^n x_i\,\omega_i & y_H = f_H(S) & Y_v & \Delta y \\\hline 1 & (0.03, 0.08) & 0.5 & 0 & 0 & 0.5 & 1 & 1 & 0\\\hline 2 & & & & & & & & \\\hline 3 & & & & & & & & \\\hline 4 & & & & & & & & \\\hline 5 & & & & & & & & \\\hline \end{array}
Équation de la séparatrice : $b +\sum\limits_{i=1}^n \omega_i\, x_i = 0$
$$\omega_1x_1 + \omega_2x_2 + b = 0 \quad\text{ssi}\quad x_2 = -\frac{1}{\omega_2}(\omega_1x_1 + b)$$
Si on remplace par les valeurs que l'on a trouvé à l'itération 5 (on est pas sûr que ce soit fini) on peut écrire :
$$x_2 = \frac{1}{0.106}(-0.134\,x_1 + 0.3)$$
À faire : Compléter le code suivant pour obtenir une représentation graphique des valeurs d'apprentissage et de la séparatrice
# A compléter avec la fonction separatrice
x = np.linspace(0,0.6,100)
y = separatrice(x)
plt.grid()
plt.plot(x, y)
plt.plot(x1, y1, '*')
plt.plot(x2, y2, 'ro')
plt.show()
# La fonction de transfert
def heavyside(x):
if x < 0:
return 0
return 1
def perceptron(f, e, n, eta, b, data):
'''
f -> function : fonction de transfert
e -> int : nombre d'entrées du perceptron
n -> int : nombre d'itérations pour l'apprentissage
eta -> int : le pas d'apprentissage (arbitraire : 0 < eta <= 1)
b -> int : poids du biais
data -> list : les données que l'on cherche à prédire
'''
errors = [] # liste des erreurs à la prédiction
w = np.zeros(e) # Tous les poids à zéro
w[-1] = b # poids du biais
fin = len(data)
for i in range(n):
if i >= fin:
x, s = data[i%fin] # lecture circulaire de la table d'apprentissage
else:
x, s = data[i]
x = np.array(x)
result = np.dot(w, x) # somme pondérée
error = s - f(result) # différence entre prédiction et résultat
errors.append(error)
w += eta * error * x # mise à jour des poids (biais compris)
return w, errors
# Les données pour l'apprentissage du perceptron
data = [
([0.03, 0.08, 1], 1),
([0.14, 0.36, 1], 1),
([0.40, 0.17, 1], 1),
([0.53, 0.67, 1], 0),
([0.67, 0.69, 1], 0),
]
# Les tests de vérification
w, errors = perceptron(heavyside, 3, 20, 0.2, 0.5, data)
for x, _ in data:
result = np.dot(x, w)
print("{}: {:6.3f} -> {}".format(np.array(x), result, heavyside(result)))
Représentation graphique de la séparatrice :
Dans cette simulation je fixe plusieurs valeurs pour le nombre d'itération $n$ d'apprentissage. Cela permet d'observer comment se déplace la séparatrice dans le plan des variables d'apprentissage.
À faire :
x = np.linspace(min(x1), max(x2), 100)
def separator(x):
return -(w[0]/w[1]*x + w[2]/w[1])
plt.grid()
plt.plot(x1, y1, '*')
plt.plot(x2, y2, 'ro')
for n in [5, 10, 15]:
w, errors = perceptron(heavyside, 3, n, 0.2, 0.5, data)
y = separator(x)
plt.plot(x, y, label = str(n))
plt.legend(loc=4)
plt.show()
Soit une série de points dont on connait les coordonnées :
k = 100
X = [np.random.uniform(1e-3, 3e-2, 20), np.ones(20)]
Y = k*X[0]+np.random.uniform(-0.3, 0.3, 20)
plt.plot(X[0],Y, 'o')
plt.ylabel("$Y$")
plt.xlabel("$X$")
plt.show()
Objectif : Tracer une régression permettant de modéliser le nuage de points.
Pour cela on dispose d'un neurone avec :
On peut accéder aux données d'apprentissage avec l'instruction Python suivante :
print(X[0])
z = np.polyfit(X[0], Y, 1) # 1 pour le plus haut degré du polynôme
p = np.poly1d (z)
print(p) # affiche le polynôme
À l'aide du code ci-dessus comparer la droite resultant de l'apprentissage du neurone et celle calculée par numpy.
christophe.casseau@ensam.eu