Ce TP est destiné à l'étude d'un perceptron pour la classification binaire linéaire.
Prérequis
Ce travail repose sur l'utilisation des librairies MatPlotLib et Numpy. Si ces librairies sont installées et fonctionnelles au sein de votre environnement Python et si vous en connaissez l'usage, vous pouvez ignorer ces prérequis et commencer à implanter un perceptron.
Librairie MatPlotLib
Les affichages visuels de ce travail sont réalisés avec la librairie Python MatPlotLib. Son bon fonctionnement est nécessaire pour la suite.
Installation
La librairie MatPlotLib sera utilisée pour l'affichage visuel. Celle-ci peut être installée avec les commandes:
pip install matplotlib
ou pour un environnement conda / anaconda:
conda install -c conda-forge matplotlib
Pour le reste de ce travail, la version 3.11.0 ou supérieure de MatPlotLib est requise.
Utilisation
L'utilisation de la librairie MatPlotLib dans un programme Python requiert d'y ajouter l'import:
import matplotlib.pyplot as plt
Une fois l'import réalisé, les fonctions liées à MatPlotLib seront disponibles dans le reste du programme par l'intermédiaire de l'objet plt.
Configuration d'environnement
La librairie MatPlotLib affiche par défaut un rendu géométrique dans un fenêtre interactive indépendante. Selon l'environnement Python utilisé, le rendu peut être limité à une simple image figée, empêchant les interactions utilisateurs ou les effets d'animation. Pour corriger ce problème, il existe plusieurs solutions différentes selon l'environnement.
Jupyter Notebook
Exécuter le code suivant dans le Jupyter Notebook:
%matplotlib qt
PyCharm
Aller dans Settings / Tool / Python Plot et décocher l'option Show plots in tool windows.
Spyder
Aller dans Tools / Preferences / IPython console / Graphics / Backend:Inline et changer "Inline" en "Automatic". Cliquer ensuite sur le bouton OK et redémarrer Spyder.
Création du canevas
La librairie MatPlotLib affiche son contenu dans un canevas créé grâce à l'instruction:
plt.figure(figsize=(w, h, u))
où
- w est la largeur du canevas
- h est la hauteur du canevas
- u est l'unité de w et h ('cm', 'px' ou 'inch')
Si une version antérieure à la 3.11.0 est utilisée, w et h sont automatiquement en pouces (inch), le paramètre u ne doit alors pas être utilisé.
Affichage du rendu
Une fois un canevas créé, l'affichage de celui-ci est obtenu grâce à l'appel à l'instruction:
plt.show()
Cette instruction est généralement la dernière du programme et provoque l'affichage. Tant qu'elle n'est pas utilisée, la fenêtre contenant le canevas MatPlotLib n'est pas visible.
| Prérequis / Exercice A. |
|
Créer un fichier prerequisite.py et y insérer le code suivant:
Exécuter le programme python et s'assurer que la fenêtre MatPlotLib s'affiche correctement (fenêtre complètement vide). |
Configuration des axes
Par défaut un canevas MatPlotLib est prévu pour afficher un axe horizontal (x) et un axe vertical (y). Chacun des axes peut être personnalisé et en particulier il est possible de nommer les axes avec les instructions:
plt.xlabel(nom)
plt.ylabel(nom)
où nom est une chaine de caractères représentant le nom de l'axe.
Il est également possible de donner les plages de valeurs minimum et maximum des axes grâce aux instructions:
plt.xlim(min, max)
plt.ylim(min, max)
où min et max sont des nombres indiquant la valeur minimum de l'axe (par défaut 0) et sa valeur maximum.
Afin d'améliorer la visualisation, une grille peut être affichée sur le graphique grâce à l'instruction:
plt.grid(alpha=a, linewidth=w)
où:
- a est la transparence de la grille sous la forme d'un nombre entre 0 (invisible) et 1 (opaque)
- w est un nombre indiquant l'épaisseur des lignes de la grille (en pixels)
Afin que les axes deviennent visibles, il est nécessaire de les nommer et de leur spécifier des plages de valeurs.
| Prérequis / Exercice B. |
|
Remplacer le contenu du fichier prerequisite.py avec le code suivant:
Exécuter le programme python et s'assurer que la fenêtre MatPlotLib s'affiche correctement avec des axes x et y dont les plages vont de 0 à 5 et une grille grise. |
Fonctions d'affichage MatPlotLib
MatPlotLib permet l'affichage de différentes formes et primitives graphiques.
Point
L'affichage d'un point de coordonnées $(x,\ y)$ est réalisé par l'instruction:
plt.plot(x, y, marker='m', color='c')
ou encore l'instruction:
plt.scatter(x, y, marker='m', color='c')
où:
- m est la forme (marker) utilisée pour représenter le point ('o' pour rond, '+' pour une croix droite, 'x' pour une croix oblique)
- c est la couleur du point ('red', 'greed', 'blue', …)
Ligne
L'affichage d'une ligne entre deux points de coordonnées $(x_{1}, y_{1}, z_{1})$ et $(x_{2}, y_{2}, z_{2})$ est réalisé par l'instruction:
plt.plot([x1 , x2],[ y1, y2],[ z1, z2], color='c', linestyle='s')
où:
- c est la couleur de la ligne ('red', 'greed', 'blue', ...)
- s est le style de la ligne ('solid', 'dashed', 'dashdot' or 'dotted')
Texte
dfqsqsdL'affichage d'un texte sur une graphique est obtenu par l'instruction:
plt.text(x, y, text, fontsize=f, color=c, rotation=r)
où:
- x et y sont les coordonnées ou le texte doit être affiché
- text est une chaine de caractères contenant le texte à afficher
- f est un nombre représentant la taille de la police à utiliser
- c est la couleur du texte à afficher ('red', 'green', 'blue', ...)
- r est la rotation du texte (en degrés)
| Prérequis / Exercice C. |
|
Remplacer le contenu du fichier prerequisite.py avec le code suivant:
Exécuter le programme python et s'assurer que la fenêtre MatPlotLib s'affiche correctement avec tous les rendus. |
Librairie Numpy
Voir https://numpy.org/doc/stable/user/absolute_beginners.html au besoin.
1. Mise en place
La première partie de ce travail consiste à créer un programme Python perceptron.py qui contiendra les fonctions développées.
| Exercice 1. |
|
Créer un fichier perceptron.py et y insérer le code suivant:
Exécuter le programme python et s'assurer que la fenêtre MatPlotLib s'affiche correctement. |
2. Classification par apprentissage
Avant d'aller plus loin, il est nécessaire de définir les concepts et le vocabulaire sur lesquels se basent les approches de classification par apprentissage.
2.1. Vecteur de caractéristiques (Features vector)
Pour classer un ensemble de \( m \) individus en utilisant un perceptron il faut tout d'abord définir un ensemble de \( n \) caractéristiques observables sur lesquelles la classification se base. Un individu \( k \) peut alors être représenté par un vecteur de caractéristiques (features vector) \( X^{(k)} \) défini par: $$ X^{(k)}=(x^{(k)}_{1},...,x^{(k)}_{n}) $$ où chaque \( x^{(k)}_{i} \) est la valeur de la caractéristique \( i \) pour l'individu \( k \).
Dans le cas étudié ici, les individus à classer sont des plantes et les caractéristiques observables sont la longueur de leur feuille, notée \( x_{1} \), et la largeur de leur feuille, notée \( x_{2} \). Ces caractéristiques ont été observées et quantifiées pour un groupe de 10 individus:
individu |
\(X^{k}\) | |
| Longueur de feuille | Largeur de feuille | |
| (\( k \)) |
(\( x_{1} \)) |
(\( x_{2} \)) |
| 1 | 0.50 | 2.00 |
| 2 | 1.10 | 2.10 |
| 3 | 0.70 | 2.60 |
| 4 | 0.5 | 0.90 |
| 5 | 1.00 | 1.40 |
| 6 | 2.00 | 1.00 |
| 7 | 2.50 | 0.70 |
| 8 | 2.20 | 3.30 |
| 9 | 1.50 | 0.80 |
| 10 | 2.30 | 1.70 |
2.2. Exemple (Sample)
La classification est l'affectation d'individus à une classe en fonction de leurs caractéristiques. Cette affectation peut être représentée numériquement pour un individu \( k \) en associant son vecteur de caractéristiques (feature vector) \( X^{(k)} \) à une valeur cible (target value), notée \( t^{(k)} \), représentant sa classe.
Pour un individu \( k \), on appelle exemple (sample), noté \( S^{(k)} \), l'association de sa valeur cible (target value) à son vecteur de caractéristiques (features vector). Plus formellement, l'exemple (sample) \( S^{(k)} \) est un vecteur défini par: $$S^{(k)}\ =\ t.X^{k}\ =\ (t,\ x^{(k)}_{1},\ ...,\ x^{(k)}_{n})$$
Dans le cadre de classification binaire, la valeur cible (target value) \( t^{(k)} \) prend les valeurs 0 ou 1 pour représenter les deux classes. Dans ce travail, les classes de plantes peuvent être représentées par les valeurs cibles (target values) \( 0 \) pour non toxique et \( 1 \) pour toxique. Le groupe de 10 individus pour lequel les caractéristiques et les classes sont connues est alors:
| individu | Classe | \( S^{(k)} \) | ||
| Target value | \(X^{k}\) | |||
| Longueur de feuille | Largeur de feuille | |||
| (\( k \)) |
(\( t \)) |
(\( x_{1} \)) |
(\( x_{2} \)) |
|
| 1 | Toxique | 1 | 0.50 | 2.00 |
| 2 | Toxique | 1 | 1.10 | 2.10 |
| 3 | Toxique | 1 | 0.70 | 2.60 |
| 4 | Toxique | 1 | 0.5 | 0.90 |
| 5 | Toxique | 1 | 1.00 | 1.40 |
| 6 | Non toxique | 0 | 2.00 | 1.00 |
| 7 | Non toxique | 0 | 2.50 | 0.70 |
| 8 | Non toxique | 0 | 2.20 | 3.30 |
| 9 | Non toxique | 0 | 1.50 | 0.80 |
| 10 | Non toxique | 0 | 2.30 | 1.70 |
2.3. Jeu de données (Dataset)
Un jeu de données (dataset) est l'ensemble des données disponibles et utilisables par le processus d'apprentissage. Dans le cadre de la classification à l'aide d'un perceptron, les données nécessaires sont les caractéristiques des individus et la classe à laquelle ils appartiennent. D'après les définitions précédentes, un exemple (sample) regroupe déjà ces données pour un individu. Un jeu de données (dataset) est alors simplement l'ensemble des exemples (samples) disponibles. Plus formellement:
$$\mathcal{D}\ =\ \{S^{(k)}\}$$
Dans ce travail, le jeu de données (dataset) est stocké dans un fichier au format CSV ou chaque ligne k est de la forme t,x1,x2 et représente les valeurs de l'exemple (sample) pour la plante \( k \). Par exemple, le fichier CSV suivant:
#t, X1, X2
1,0.50,2.00
1,1.10,2.10
1,0.70,2.60
1,0.50,0.90
1,1.00,1.40
0,2.00,1.00
0,2.50,0.70
0,2.20,3.30
0,1.50,0.80
0,2.30,1.70
représente un dataset de 10 samples avec une valeur cible (target value) t représentant une classe et deux valeurs x1 et x2 composant le vecteur de caractéristiques (features vector).
2.1. Lecture du dataset
La librairie Numpy permet de lire un fichier CSV et de stocker les données qu'il contient dans un tableau grâce à l'instruction:
dataset = np.loadtxt(filename, delimiter='d', comments='c')
où:
- dataset est un tableau de tuples numpy contenant autant de valeurs qu'il y a de lignes dans le fichier
- filename est une chaine de caractères indiquant le fichier à lire
- d est un caractère représentant le délimiteur utilisé pour séparer les valeurs (',', ';', '\t', ...)
- c est un caractère indiquant le début d'un commentaire ('#', '//', ...)
| Exercice 2. |
|
Télécharger le fichier datasetA.csv se trouvant à l'adresse https://www.seinturier.fr/teaching/ai/connectionist/pw/01/datasetA.csv et le placer dans le même répertoire que le programme perceptron.py. Dans le fichier perceptron.py , ajouter une fonction readDataset(filename) qui permet de lire le fichier CSV donné par filename et qui stocke les données lues dans un tableau de tuples numpy. Modifier le programme perceptron.py pour qu'il affiche le contenu du CSV chargé sur la console de la façon suivante: Dataset Par exemple, pour le fichier datasetA.csv, la sortie doit être: Dataset Exécuter le programme et s'assurer de la conformité de sa sortie. |
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
# Lecture du dataset
dataset = readDataset('datasetA.csv')
# Affichage du dataset
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for id, (t, x1, x2) in enumerate(dataset):
print(f" {id+1:3d}: {int(t)} - ({x1}, {x2})")
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
2.2. Visualisation du dataset
Le dataset utilisé peut être visualisé géométriquement dans un graphique MatPlotLib. En considérant l'axe horizontal comme l'axe des \( x_{1} \) et l'axe vertical comme l'axe des \( x_{2} \), chaque sample du dataset peut être visualisé comme un point de coordonnées \( (x_{1},\ x_{2}) \).
| Exercice 3. |
|
Dans le programme perceptron.py , ajouter une fonction displayDataset(dataset) qui affiche le contenu du dataset de la forme \( [t,\ x_{1},\ x_{2}] \) passé en paramètre dans un graphique MatPlotLib. Le graphique devra avoir les propriétés suivantes:
Tester le programme sur le dataset lu depuis le fichier datasetA.csv. L'affichage doit être le suivant:
|
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
dataset = readDataset('datasetA.csv')
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for id, (t, x1, x2) in enumerate(dataset):
print(f" {id+1:3d}: {int(t)} - ({x1}, {x2})")
displayDataset(dataset) # Affichage du dataset
plt.savefig('ex03-datasetA.svg')
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
3. Perceptron
Un perceptron est un Neurone Formel permettant entre autre de répondre à un problème de classification binaire. Il est défini formellement par:
- Un vecteur de \( n \) entrées \( X\ =\ (x_{1},\ ...,\ x_{n}) \)
- Un vecteur de \( n \) poids \( (w_{1},\ ...,\ w_{n}) \)
- Un scalaire \( b \) de biais
- Une fonction d'agrégation $$ z(X)\ =\ \sum_{i=1}^{n}x_{i}w_{i} $$
- Une fonction d'activation $$ \phi(z)\ =\ \begin{cases}
0 & \text{si } z < 0 \\
1 & \text{si } z \ge 0
\end{cases} $$
Un perceptron peut être représenté schématiquement:
3.1. Modèle
On appelle modèle de perceptron le vecteur \( \mathcal{W} \) composé de ses poids synaptiques et de son biais. Plus formellement:
$$\mathcal{W}=(w_{1},\ ...,\ w_{n},\ b)$$
| Exercice 4. |
|
Dans le programme perceptron.py, ajouter une variable model définie comme un tableau numpy de 3 élements représentant un modèle de perceptron à 2 entrées \( (w_{1},\ w_{2},\ b)\). Ce modèle sera initialisé avec les valeurs \( w_{1}=-0.5 \), \( w_{2}=1.2 \) et \( b=-1.35 \). Modifier le programme perceptron.py pour afficher les caractéristiques du modèle sur la console. La sortie avec le modèle créé précédemment devra afficher: Model Dataset |
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
# Exercice 4. 1/2: Ajout du modèle
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
# Exercice 4. 2/2: Affichage du modèle
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for id, (t, x1, x2) in enumerate(dataset):
print(f" {id+1:3d}: {int(t)} - ({x1}, {x2})")
displayDataset(dataset) # Affichage du dataset
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
3.2. Fonction d'agrégation et fonction d'activation
Un perceptron est caractérisé sa fonction d'agrégation et sa fonction d'activation, toutes deux étant paramétrées par son modèle.
La fonctions d'agrégation, notée \( z \) est définie comme la somme pondérée d'un vecteur de caractéristiques (features vector) et des poids du modèle. Plus formellement:
$$z(X^{(k)})=\sum_{i=1}^{n}x_{i}^{(k)}w_{i}\ +\ b$$
| Exercice 5. |
|
Dans le programme perceptron.py, ajouter une fonction z(model, features) qui calcule la valeur de la fonction d'agrégation \( z \) pour le modèle model et le vecteur de caractéristiques features. Rappel: Pour un sample \( (t,\ x_{1},\ ...,\ x_{n}) \) le vecteur de caractéristiques correspondant est \( (x_{1},\ ...,\ x_{n}) \). Modifier le programme perceptron.py pour afficher la valeur de z pour chaque sample du dataset. sur la console. Après la modification le programme doit afficher: Model Dataset |
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
# Exercice 5. 1/2: ajout de la fonction z
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
# Exercice 5. 2/2: Affichage de z
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for id, (t, x1, x2) in enumerate(dataset):
print(f" {id+1:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f}")
displayDataset(dataset) # Affichage du dataset
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
La fonction d'activation d'un perceptron est définie comme la fonction de Heaviside appliquée au résultat de la fonction d'agrégation. Plus formellement:
$$\phi(z)\ =\ \begin{cases}
0 & \text{si } z < 0 \\
1 & \text{si } z \ge 0
\end{cases}$$
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
# Exercice 6. 1/2: ajout de la fonction phi
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
# Exercice 6. 1/2: Ajout de l'affichage de phi
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for id, (t, x1, x2) in enumerate(dataset):
print(f" {id+1:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))}")
displayDataset(dataset) # Affichage du dataset
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
3.3. Prédiction
A partir d'un vecteur de caractéristiques (features) \( X^{(k)}\ =\ (x^{(k)}_{1},\ ...,\ x^{(k)}_{n}) \) représentant un individu \( k \), le perceptron calcule une prédiction \( y^{(k)} \) représentant la classe (\( 0 \) ou \( 1 \)) à laquelle l'individu \( k \) est censé appartenir. Dans le cas d'un perceptron de Rosenblatt, la prédiction est définie par:
$$ y^{(k)}\ =\ \phi(z(X^{(k)}))\ =\ \phi(\sum_{i=1}^{n}x^{(k)}_{i}w_{i}) $$
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
# Exercice 7. 1/2: ajout de la fonction z
def y(model, features):
"""Calcule la valeur prediction pour le modele et les features passes en parametre"""
return phi(z(model, features))
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
# Exercice 7. 2/2: Ajout de l'affichage de la prediction
for k in range(0,dataset.shape[0]):
t = dataset[k][0]
x1 = dataset[k][1]
x2 = dataset[k][2]
print(f" {k:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))} - y={y(model, dataset[k][1:])}")
displayDataset(dataset) # Affichage du dataset
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
En comparant les classes explicites ( \( t \)) et les prédictions, on remarque de nombreuses différences. La prédiction du perceptron avec le modèle actuel est incorrecte.
3.2. Frontière de décision
Un perceptron permet de faire de la classification binaire linéaire. Il peut être vu comme la partition d'un espace à \( n \) dimensions par un hyperplan appelé frontière de décision. Dans le cas d'un espace à 2 dimensions, un perceptron sépare l'espace en deux demi plans et sa frontière de décision est une droite d'équation:
$$x_{1}w_{1}\ +\ x_{2}w_{2}\ +\ b\ =\ 0$$
soit encore:
$$x_{2}\ =\ \frac{-w_{1}x_{1}\ -\ b}{w_{2}}$$
Afin de visualiser les erreurs de prédiction, il est intéressant de visualiser la frontière de décision.
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
# Exercice 8 1/2 - Implantation de la fonction
def displayFrontier(model):
"""Affiche la frontière de décision du perceptron sous forme d'une droite.
La frontière est l'ensemble des points où z = 0, soit : w1*x1 + w2*x2 + b = 0
"""
w1, w2, b = model
# On récupère les bornes actuelles de l'axe des abscisses
x1_min, x1_max = plt.xlim()
x2_min, x2_max = plt.ylim()
if w2 != 0:
# Cas général : x2 = -(w1*x1 + b) / w2
plt.plot([x1_min, x1_max], [-(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2], color='orange', linewidth=2)
else:
# Cas particulier : droite verticale x1 = -b / w1
plt.plot([-b/w1, -b/w1],[x2_min, x2_max], color='orange', linewidth=2)
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
def y(model, features):
"""Calcule la valeur prediction pour le modele et les features passes en parametre"""
return phi(z(model, features))
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for k in range(0,dataset.shape[0]):
t = dataset[k][0]
x1 = dataset[k][1]
x2 = dataset[k][2]
print(f" {k:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))} - y={y(model, dataset[k][1:])}")
displayDataset(dataset) # Affichage du dataset
# Exercice 8 2/2 - Rendu de la frontière de decision
displayFrontier(model)
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
Afin d'optimiser la visualisation du comportement du perceptron, il est intéressant de modifier l'affichage du dataset en ajoutant la visualisation des prédictions.
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
# Exercice 9 - 1/2 - modification de l'affichage
def displayDataset(dataset, model=None):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
if (model is None):
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
else:
for t, x1, x2 in dataset:
color = 'gray'
# Calcul de la prédiction
p = y(model, (x1, x2))
if t == 1:
if p == 1:
color = 'red'
elif p == 0:
color = 'magenta'
else:
color = 'black'
elif t == 0:
if p == 0:
color = 'green'
elif p == 1:
color = 'cyan'
else:
color = 'black'
plt.scatter(x1, x2, marker='o', color=color)
def displayFrontier(model):
"""Affiche la frontière de décision du perceptron sous forme d'une droite.
La frontière est l'ensemble des points où z = 0, soit : w1*x1 + w2*x2 + b = 0
"""
w1, w2, b = model
# On récupère les bornes actuelles de l'axe des abscisses
x1_min, x1_max = plt.xlim()
x2_min, x2_max = plt.ylim()
if w2 != 0:
# Cas général : x2 = -(w1*x1 + b) / w2
plt.plot([x1_min, x1_max], [-(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2], color='orange', linewidth=2)
else:
# Cas particulier : droite verticale x1 = -b / w1
plt.plot([-b/w1, -b/w1],[x2_min, x2_max], color='orange', linewidth=2)
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
def y(model, features):
"""Calcule la valeur prediction pour le modele et les features passes en parametre"""
return phi(z(model, features))
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for k in range(0,dataset.shape[0]):
t = dataset[k][0]
x1 = dataset[k][1]
x2 = dataset[k][2]
print(f" {k:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))} - y={y(model, dataset[k][1:])}")
# Exercice 9 - 2/2 - appel de l'affichage modifie
displayDataset(dataset, model) # Affichage du dataset
displayFrontier(model)
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
4. Apprentissage
La capacité de classification d'un perceptron est entièrement fonction de ses paramètres \( w_{1}\ ,...,\ w_{n},\ b \). Dans le cadre de travail, le perceptron utilisé possède 3 paramètres \( w_{1},\ w_{2},\ b \) qui définissent sa frontière de décision.
4.1. Erreur
Dans un processus d'apprentissage, la première nécessité est de pouvoir quantifier l'erreur de classification. Pour un perceptron, l'erreur de classification pour un individu \( k \) peut être évaluée simplement en calculant la distance \( \lvert{}t^{(k)}-y^{(k)}\rvert{} \) entre la valeur cible \( t^{(k)} \) représentant sa classe et la valeur de prédiction \( y^{(k)} \). L'erreur globale de classification peut alors être définie par:
$$e\ =\ \sum_{k=1}^{m}\lvert{}t^{(k)}-y^{(k)}\rvert{}\ =\ \sum_{k=1}^{m}\lvert{}t^{(k)}-\phi(z(X^{(k)}))\rvert{}$$
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset, model=None):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
if (model is None):
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
else:
for t, x1, x2 in dataset:
color = 'gray'
# Calcul de la prédiction
p = y(model, (x1, x2))
if t == 1:
if p == 1:
color = 'red'
elif p == 0:
color = 'magenta'
else:
color = 'black'
elif t == 0:
if p == 0:
color = 'green'
elif p == 1:
color = 'cyan'
else:
color = 'black'
plt.scatter(x1, x2, marker='o', color=color)
def displayFrontier(model):
"""Affiche la frontière de décision du perceptron sous forme d'une droite.
La frontière est l'ensemble des points où z = 0, soit : w1*x1 + w2*x2 + b = 0
"""
w1, w2, b = model
# On récupère les bornes actuelles de l'axe des abscisses
x1_min, x1_max = plt.xlim()
x2_min, x2_max = plt.ylim()
if w2 != 0:
# Cas général : x2 = -(w1*x1 + b) / w2
plt.plot([x1_min, x1_max], [-(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2], color='orange', linewidth=2)
else:
# Cas particulier : droite verticale x1 = -b / w1
plt.plot([-b/w1, -b/w1],[x2_min, x2_max], color='orange', linewidth=2)
# Exercice 10 - 2/2: Affichage de l'erreur
def displayError(e):
"""
Affiche la valeur d'erreur dans le coin superieur gauche du graphique
:param e: la valeur d'erreur a afficher
"""
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
plt.text(x_min+0.05, y_max-0.3, f"Error: {e:5.2f}", fontsize=9, color='black')
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
def y(model, features):
"""Calcule la valeur prediction pour le modele et les features passes en parametre"""
return phi(z(model, features))
# Exercice 10 - 1/2: Implantation de la fonctione error
def error(model, dataset):
"""
Calcul l'erreur d'un modele d'apprentissage par rapport a un jeu de donnees
:param model: le modele pour lequel calculer l'erreur
:param dataset: le jeux de donnees sur lequel l'erreur est calculee
:return: l'erreur du modele par rapport au jeux de donnees
"""
e = 0
for k in range(0,dataset.shape[0]):
e = e + np.absolute(dataset[k][0] - y(model, dataset[k][1:]))
return e
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
e = error(model, dataset)
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for k in range(0,dataset.shape[0]):
t = dataset[k][0]
x1 = dataset[k][1]
x2 = dataset[k][2]
print(f" {k:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))} - y={y(model, dataset[k][1:])}")
print(f" Error: {e}")
displayDataset(dataset, model)
displayFrontier(model)
displayError(e)
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
Afin que le perceptron puisse classer correctement des individus, il faut trouver les paramètres du modèle qui font tendre l'erreur vers \( 0 \)
4.2. Algorithme d'apprentissage du perceptron
Franck Rosenblatt a proposé un algorithme permettant de déterminer les paramètres du modèle pour faire en sorte que les prédictions du perceptron soient correcte, et donc que l'erreur devienne nulle pour un dataset donné:
| Algorithme du perceptron | |
| 1. | données |
| 2. | \( \mathcal{X}=\{X^{(k)}\} \) un ensemble de \( m \) vecteurs de caractéristiques |
| 3. | \( \mathcal{T}=\{t^{(k)}\} \) un ensemble de \( m \) valeurs cibles |
| 4. | \( \mathcal{W}=\{w_{i}\} \) un ensemble de \( n \) poids |
| 5. | \( b \) une valeur de biais |
| 6. | \( \alpha \) le taux d'apprentissage |
| 7. | variables |
| 8. | \( e \) une valeur d'erreur |
| 9. | \( y \) une valeur de prédiction |
| 10. | début |
| 11. | pour \( i \) allant de \(1\) à \( n \) faire |
| 12. | \( w_{i}\leftarrow{}0 \) |
| 13. | finpour |
| 14. | \(b\leftarrow{}0\) |
| 15. | \(e\leftarrow{}1\) |
| 16. | tanque \( e>0 \) faire |
| 17. | pour \( k \) allant de \(1\) à \( m \) faire |
| 18. | \( y\ \leftarrow{}\ \phi(z(X^{k})) \) |
| 19. | si \( t^{(k)}\neq{} y\) alors |
| 20. | pour \( i \) allant de \(1\) à \( n \) faire |
| 21. | \( w_{i}=w_{i}+\alpha{}(t^{(k)}-y)x^{(k)}_{i} \) |
| 22. | finpour |
| 23. | \( b=b+\alpha{}(t^{(k)}-y) \) |
| 24. | finsi |
| 25. | finpour |
| 26. | \( e\leftarrow{}0 \) |
| 27. | pour \( k \) allant de \(1\) à \( m \) faire |
| 28. | \( e\leftarrow{}e+ \lvert{}t^{(k)}-\phi(z(X^{k}))\rvert{}\) |
| 29. | finpour |
| 30. | fintq |
| 31. | fin |
Cet algorithme est simple à implanter tel quel mais il est intéressant de s'intéresser un peu plus en détail à sa structure pour proposer une implantation modulaire et réutilisable.
5. Implantation de l'algorithme du perceptron
L'algorithme du perceptron peut être vu comme un processus en 3 étapes:
- Un modèle est créé avec des paramètres empiriques (par convention tous égaux à \( 0 \)).
- Les paramètres du modèle sont ajustés en fonction des erreurs entre les valeurs cibles et les prédictions pour chaque exemple du jeu de données.
- L'erreur globale du modèle avec ses nouveaux paramètres est calculée.
Les étapes 1 à 3 sont répétées tant que l'erreur n'est pas nulle. L'étape 2 consistant à parcourir tout le jeu de données pour ajuster les paramètres du modèle s'appelle une époque (epoch).
| Algorithme du perceptron | ||
| 1. | données | |
| 2. | \( \mathcal{X}=\{X^{(k)}\} \) un ensemble de \( m \) vecteurs de caractéristiques | |
| 3. | \( \mathcal{T}=\{t^{(k)}\} \) un ensemble de \( m \) valeurs cibles | |
| 4. | \( \mathcal{W}=\{w_{i}\} \) un ensemble de \( n \) poids | |
| 5. | \( b \) une valeur de biais | |
| 6. | \( \alpha \) le taux d'apprentissage | |
| 7. | variables | |
| 8. | \( e \) une valeur d'erreur | |
| 9. | \( y \) une valeur de prédiction | |
| 10. | début | |
| 11. | pour \( i \) allant de \(1\) à \( n \) faire | Initialisation du modèle |
| 12. | \( w_{i}\leftarrow{}0 \) | |
| 13. | finpour | |
| 14. | \(b\leftarrow{}0\) | |
| 15. | \(e\leftarrow{}1\) | |
| 16. | tanque \( e>0 \) faire | |
| 17. | pour \( k \) allant de \(1\) à \( m \) faire | Epoque (Epoch): Ajustement des paramètres du modèle en itérant sur tout les samples du dataset. |
| 18. | \( y\ \leftarrow{}\ \phi(z(X^{k})) \) | |
| 19. | si \( t^{(k)}\neq{} y\) alors | |
| 20. | pour \( i \) allant de \(1\) à \( n \) faire | |
| 21. | \( w_{i}=w_{i}+\alpha{}(t^{(k)}-y)x^{(k)}_{i} \) | |
| 22. | finpour | |
| 23. | \( b=b+\alpha{}(t^{(k)}-y) \) | |
| 24. | finsi | |
| 25. | finpour | |
| 26. | \( e\leftarrow{}0 \) | Calcul de l'erreur globale |
| 27. | pour \( k \) allant de \(1\) à \( m \) faire | |
| 28. | \( e\leftarrow{}e+ \lvert{}t^{(k)}-\phi(z(X^{k}))\rvert{}\) | |
| 29. | finpour | |
| 30. | fintq | |
| 31. | fin | |
Il est possible d'implanter l'algorithme du perceptron en codant les 3 étapes précédentes sous forme de fonctions.
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset, model=None):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
if (model is None):
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
else:
for t, x1, x2 in dataset:
color = 'gray'
# Calcul de la prédiction
p = y(model, (x1, x2))
if t == 1:
if p == 1:
color = 'red'
elif p == 0:
color = 'magenta'
else:
color = 'black'
elif t == 0:
if p == 0:
color = 'green'
elif p == 1:
color = 'cyan'
else:
color = 'black'
plt.scatter(x1, x2, marker='o', color=color)
def displayFrontier(model):
"""Affiche la frontière de décision du perceptron sous forme d'une droite.
La frontière est l'ensemble des points où z = 0, soit : w1*x1 + w2*x2 + b = 0
"""
w1, w2, b = model
# On récupère les bornes actuelles de l'axe des abscisses
x1_min, x1_max = plt.xlim()
x2_min, x2_max = plt.ylim()
if w2 != 0:
# Cas général : x2 = -(w1*x1 + b) / w2
plt.plot([x1_min, x1_max], [-(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2], color='orange', linewidth=2)
else:
# Cas particulier : droite verticale x1 = -b / w1
plt.plot([-b/w1, -b/w1],[x2_min, x2_max], color='orange', linewidth=2)
def displayError(e):
"""
Affiche la valeur d'erreur dans le coin superieur gauche du graphique
:param e: la valeur d'erreur a afficher
"""
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
plt.text(x_min+0.05, y_max-0.3, f"Error: {e:5.2f}", fontsize=9, color='black')
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
def y(model, features):
"""Calcule la valeur prediction pour le modele et les features passes en parametre"""
return phi(z(model, features))
def error(model, dataset):
"""
Calcul l'erreur d'un modele d'apprentissage par rapport a un jeu de donnees
:param model: le modele pour lequel calculer l'erreur
:param dataset: le jeux de donnees sur lequel l'erreur est calculee
:return: l'erreur du modele par rapport au jeux de donnees
"""
e = 0
for k in range(0,dataset.shape[0]):
e = e + np.absolute(dataset[k][0] - y(model, dataset[k][1:]))
return e
# Exercice 12 - 1/2 - Implantation de la fonction train
def train(model, dataset):
"""
Entraine le modele donne en parametre sur le jeu de donnees fourni.
:param model: Le modele a entrainer
:param dataset: LE jeux de donnees d'entrainement
"""
return 0;
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
# Exercice 12 - 2/2 - Appel a la fonction train
train(model, dataset)
e = error(model, dataset)
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for k in range(0,dataset.shape[0]):
t = dataset[k][0]
x1 = dataset[k][1]
x2 = dataset[k][2]
print(f" {k:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))} - y={y(model, dataset[k][1:])}")
print(f" Error: {e}")
displayDataset(dataset, model)
displayFrontier(model)
displayError(e)
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
5.1. Initialisation
L'initialisation consiste à créer un modèle dont tous les paramètres sont égaux à 0. Ceci est réalisable par une fonction.
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset, model=None):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
if (model is None):
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
else:
for t, x1, x2 in dataset:
color = 'gray'
# Calcul de la prédiction
p = y(model, (x1, x2))
if t == 1:
if p == 1:
color = 'red'
elif p == 0:
color = 'magenta'
else:
color = 'black'
elif t == 0:
if p == 0:
color = 'green'
elif p == 1:
color = 'cyan'
else:
color = 'black'
plt.scatter(x1, x2, marker='o', color=color)
def displayFrontier(model):
"""Affiche la frontière de décision du perceptron sous forme d'une droite.
La frontière est l'ensemble des points où z = 0, soit : w1*x1 + w2*x2 + b = 0
"""
w1, w2, b = model
# On récupère les bornes actuelles de l'axe des abscisses
x1_min, x1_max = plt.xlim()
x2_min, x2_max = plt.ylim()
if w2 != 0:
# Cas général : x2 = -(w1*x1 + b) / w2
plt.plot([x1_min, x1_max], [-(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2], color='orange', linewidth=2)
else:
if w1 != 0:
# Cas particulier : droite verticale x1 = -b / w1
plt.plot([-b/w1, -b/w1],[x2_min, x2_max], color='orange', linewidth=2)
def displayError(e):
"""
Affiche la valeur d'erreur dans le coin superieur gauche du graphique
:param e: la valeur d'erreur a afficher
"""
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
plt.text(x_min+0.05, y_max-0.3, f"Error: {e:5.2f}", fontsize=9, color='black')
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
def y(model, features):
"""Calcule la valeur prediction pour le modele et les features passes en parametre"""
return phi(z(model, features))
def error(model, dataset):
"""
Calcul l'erreur d'un modele d'apprentissage par rapport a un jeu de donnees
:param model: le modele pour lequel calculer l'erreur
:param dataset: le jeux de donnees sur lequel l'erreur est calculee
:return: l'erreur du modele par rapport au jeux de donnees
"""
e = 0
for k in range(0,dataset.shape[0]):
e = e + np.absolute(dataset[k][0] - y(model, dataset[k][1:]))
return e
# Exercice 13 - 1/2 - Implantation de la fonction init
def init(model):
if (model is None):
return
else:
for i in range(0,model.shape[0]):
model[i] = 0
def train(model, dataset):
"""
Entraine le modele donne en parametre sur le jeu de donnees fourni.
:param model: Le modele a entrainer
:param dataset: LE jeux de donnees d'entrainement
"""
# Exercice 13 - 2/2 - Appel a init
init(model)
return 0;
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
# Exercice 12 - 2/2 - Appel a la fonction train
train(model, dataset)
e = error(model, dataset)
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for k in range(0,dataset.shape[0]):
t = dataset[k][0]
x1 = dataset[k][1]
x2 = dataset[k][2]
print(f" {k:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))} - y={y(model, dataset[k][1:])}")
print(f" Error: {e}")
displayDataset(dataset, model)
displayFrontier(model)
displayError(e)
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
5.2. Epoque (Epoch)
L'étape centrale de l'algorithme d'apprentissage du perceptron est l'époque (epoch). Il s'agit de l'ajustement des paramètres du modèle (poids et biais) en fonction des examples du jeux de données. Dans le cas de l'algorithme de Rosenblatt, pour chaque exemple, si la valeur cible n'est pas égale à la prédiction, les paramètres du modèle sont modifiés de la différence entre la valeur cible et la prédiction erronée, le tout multiplié par un facteur \( \alpha \) (taux d'apprentissage):
| 17. | pour \( k \) allant de \(1\) à \( m \) faire | Epoque (Epoch): Ajustement des paramètres du modèle en itérant sur tout les samples du dataset. |
| 18. | \( y\ \leftarrow{}\ \phi(z(X^{k})) \) | |
| 19. | si \( t^{(k)}\neq{} y\) alors | |
| 20. | pour \( i \) allant de \(1\) à \( n \) faire | |
| 21. | \( w_{i}=w_{i}+\alpha{}(t^{(k)}-y)x^{(k)}_{i} \) | |
| 22. | finpour | |
| 23. | \( b=b+\alpha{}(t^{(k)}-y) \) | |
| 24. | finsi | |
| 25. | finpour |
Cette étape peut être codée dans une fonction indépendante.
Contenu du fichier perceptron.py:
import matplotlib.pyplot as plt
import numpy as np
def initPLT():
"""Initialisation des composants de MatPlotLib"""
plt.figure(figsize=(20, 12, 'cm'))
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(0, 5.0)
plt.ylim(0, 5.0)
plt.grid(alpha=0.5, linewidth = 1)
def readDataset(filename):
"""Lit un dataset CSV dont chaque ligne est [t, x1, x2]
et le renvoie sous forme de tableau NumPy (n, 3)."""
return np.loadtxt(filename, delimiter=',', comments='#')
def displayDataset(dataset, model=None):
"""Affiche le dataset passé en paramètre dans le graphique MatPlotLib actuel"""
if (model is None):
for t, x1, x2 in dataset:
color = 'red' if int(t) == 1 else 'green'
plt.scatter(x1, x2, marker='o', color=color)
else:
for t, x1, x2 in dataset:
color = 'gray'
# Calcul de la prédiction
p = y(model, (x1, x2))
if t == 1:
if p == 1:
color = 'red'
elif p == 0:
color = 'magenta'
else:
color = 'black'
elif t == 0:
if p == 0:
color = 'green'
elif p == 1:
color = 'cyan'
else:
color = 'black'
plt.scatter(x1, x2, marker='o', color=color)
def displayFrontier(model):
"""Affiche la frontière de décision du perceptron sous forme d'une droite.
La frontière est l'ensemble des points où z = 0, soit : w1*x1 + w2*x2 + b = 0
"""
w1, w2, b = model
# On récupère les bornes actuelles de l'axe des abscisses
x1_min, x1_max = plt.xlim()
x2_min, x2_max = plt.ylim()
if w2 != 0:
# Cas général : x2 = -(w1*x1 + b) / w2
plt.plot([x1_min, x1_max], [-(w1 * x1_min + b) / w2, -(w1 * x1_max + b) / w2], color='orange', linewidth=2)
else:
if w1 != 0:
# Cas particulier : droite verticale x1 = -b / w1
plt.plot([-b/w1, -b/w1],[x2_min, x2_max], color='orange', linewidth=2)
def displayError(e):
"""
Affiche la valeur d'erreur dans le coin superieur gauche du graphique
:param e: la valeur d'erreur a afficher
"""
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
plt.text(x_min+0.05, y_max-0.3, f"Error: {e:5.2f}", fontsize=9, color='black')
def z(model, features):
"""Calcule la valeur de la fonction d'agregation pour le modele
et le vecteur de caracteristiques (features) donne"""
w = model[:-1] # les n poids
b = model[-1] # le biais
return np.dot(w, features) + b
def phi(z):
"""Calcule la valeur de la fonction d'activation heaviside pour la valeur agregee z"""
return 1 if z >= 0 else 0
def y(model, features):
"""Calcule la valeur prediction pour le modele et les features passes en parametre"""
return phi(z(model, features))
def error(model, dataset):
"""
Calcul l'erreur d'un modele d'apprentissage par rapport a un jeu de donnees
:param model: le modele pour lequel calculer l'erreur
:param dataset: le jeux de donnees sur lequel l'erreur est calculee
:return: l'erreur du modele par rapport au jeux de donnees
"""
e = 0
for k in range(0,dataset.shape[0]):
e = e + np.absolute(dataset[k][0] - y(model, dataset[k][1:]))
return e
def init(model):
if (model is None):
return
else:
for i in range(0,model.shape[0]):
model[i] = 0
# Exercice 14 - 1/4 - Implantation de la fonction epoch
def epoch(model, dataset, alpha):
for k in range(0,dataset.shape[0]):
pred = y(model, dataset[k][1:])
if dataset[k][0] != pred:
# Mise a jour des wi (n premiers elements du vecteur de parametres)
for i in range(0,model.shape[0]-1):
model[i] = model[i] + alpha*(dataset[k][0]-pred)*dataset[k][i+1]
# Mise a jour de b (dernier element du vecteur de parametres)
model[model.shape[0]-1] = model[model.shape[0]-1] + alpha*(dataset[k][0]-pred)
# Exercice 13 - 2/4 - Appel a init
def train(model, dataset, alpha=1.0):
"""
Entraine le modele donne en parametre sur le jeu de donnees fourni.
:param model: Le modele a entrainer
:param dataset: LE jeux de donnees d'entrainement
"""
init(model)
# Exercice 13 - 3/4 - Appel a init
epoch(model, dataset, alpha)
return 0;
def main():
"""Point d'entree du programme"""
initPLT() # Initialisation de MatPlotLib
model = np.array([-0.5, 1.2, -1.35], dtype=float)
dataset = readDataset('datasetA.csv')
# Exercice 13 - 4/4 - Modification de l'appel a train
train(model, dataset, 1.0)
e = error(model, dataset)
print("Model")
print(f" w1: {model[0]:9.6f}")
print(f" w2: {model[1]:9.6f}")
print(f" b: {model[2]:9.6f}")
print("")
print("Dataset")
print(" Dimension: "+str(dataset.shape[1]))
print(" Size : "+str(dataset.shape[0]))
print(" Samples : ")
for k in range(0,dataset.shape[0]):
t = dataset[k][0]
x1 = dataset[k][1]
x2 = dataset[k][2]
print(f" {k:3d}: {int(t)} - ({x1}, {x2}) - z={z(model, (x1, x2)):9.6f} - \u03c6={phi(z(model, (x1, x2)))} - y={y(model, dataset[k][1:])}")
print(f" Error: {e}")
displayDataset(dataset, model)
displayFrontier(model)
displayError(e)
plt.show() # Affichage du canevas MatPlotLib
if __name__ == "__main__":
main()
5.2. Erreur globale
La troisième et dernière étape de l'algorithme d'apprentissage est le calcul de l'erreur globale du modèle à partir des paramètres calculés durant l'époque:
| 26. | \( e\leftarrow{}0 \) | Calcul de l'erreur globale |
| 27. | pour \( k \) allant de \(1\) à \( m \) faire | |
| 28. | \( e\leftarrow{}e+ \lvert{}t^{(k)}-\phi(z(X^{k}))\rvert{}\) | |
| 29. | finpour |
Ce calcul est déjà codé dans le programme actuel sous la forme de la fonction error(model, dataset) implantée lors de l'exercice 10.
5.3. Entrainement
A partir de la fonction init définie dans l'exercice 13, de la fonction epoch définie dans l'exercice 14 et de la fonction error définie dans l'exercice 10, il est possible d'écrire l'algorithme de Rosenblatt sous une forme plus simple:
donnees
model un modèle de perceptron
dataset un jeu de données
alpha le taux d'apprentissage
début
init(model)
\( e\leftarrow{} \) error(model, dataset)
tanque \( e>0 \) faire
epoch(model, dataset, alpha)
\(e\ \leftarrow \)error(model, dataset)
fintq
fin
...