IAC / TP 1 - Perceptron

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:

import matplotlib.pyplot as plt

plt.figure(figsize=(20, 12, 'cm'))

plt.show()

 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:

import matplotlib.pyplot as plt

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)

plt.show()

 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:

import matplotlib.pyplot as plt

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 drawFigures():
    """Dessin de differentes figures (lignes et points)"""
    plt.plot(1.5, 1.5, marker='o', color='red')
    plt.plot(2.5, 1.5, marker='+', color='green')
    plt.plot(3.5, 1.5, marker='x', color='blue')
    
    plt.scatter(1.5, 2.5, marker='o', color='magenta')
    plt.scatter(2.5, 2.5, marker='+', color='cyan')
    plt.scatter(3.5, 2.5, marker='x', color='purple')
    
    plt.plot([1 , 2],[ 2, 2], color='red', linestyle='solid')
    plt.plot([3 , 4],[ 2, 2], color='green', linestyle='dashed')
    plt.plot([1 , 2],[ 3, 3], color='magenta', linestyle='dashdot')
    plt.plot([3 , 4],[ 3, 3], color='cyan', linestyle='dotted')

    plt.text(1.6, 1.4, 'Label 1', fontsize=9, color='black', rotation=0)
    plt.text(3.6, 1.0, 'Label 2', fontsize=9, color='black', rotation=-30)

def main():
    """Point d'entree du programme"""
    initPLT()     # Initialisation de MatPlotLib
    drawFigures() # Dessin des figures
    plt.show()    # Affichage du canevas MatPlotLib

if __name__ == "__main__":
    main()

 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:

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 main():
    """Point d'entree du programme"""
    initPLT()     # Initialisation de MatPlotLib

    plt.show()    # Affichage du canevas MatPlotLib


if __name__ == "__main__":
    main()

 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
  Dimension: <nombre de colonnes du CSV>
  Size     : <nombre n de samples>
  Samples  :
      <1: t1 - (x1, x2)>
      ...
      <n: tn - (x1n, x2n)>

Par exemple, pour le fichier datasetA.csv, la sortie doit être:

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      1: 1 - (0.5, 2.0)
      2: 1 - (1.1, 2.1)
      3: 1 - (0.7, 2.6)
      4: 1 - (0.5, 0.9)
      5: 1 - (1.0, 1.4)
      6: 0 - (2.0, 1.0)
      7: 0 - (2.5, 0.7)
      8: 0 - (2.2, 3.3)
      9: 0 - (1.5, 0.8)
     10: 0 - (2.3, 1.7)

 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:

  • L'axe horizontal est l'axe des \( x_{1} \)
  • L'axe vertical est l'axe des \( x_{2} \)
  • Chaque sample est représenté par un point de forme ronde et de coordonnées \( (x_{1},\ x_{2}) \)
  • Chaque sample est de couleur rouge si \( t\ =\ 1 \) et verte si \( t\ =\ 0 \)

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
  w1: -0.500000
  w2:  1.200000
   b: -1.350000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      1: 1 - (0.5, 2.0)
      2: 1 - (1.1, 2.1)
      3: 1 - (0.7, 2.6)
      4: 1 - (0.5, 0.9)
      5: 1 - (1.0, 1.4)
      6: 0 - (2.0, 1.0)
      7: 0 - (2.5, 0.7)
      8: 0 - (2.2, 3.3)
      9: 0 - (1.5, 0.8)
     10: 0 - (2.3, 1.7)

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
  w1: -0.500000
  w2:  1.200000
   b: -1.350000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      1: 1 - (0.5, 2.0) - z= 0.800000
      2: 1 - (1.1, 2.1) - z= 0.620000
      3: 1 - (0.7, 2.6) - z= 1.420000
      4: 1 - (0.5, 0.9) - z=-0.520000
      5: 1 - (1.0, 1.4) - z=-0.170000
      6: 0 - (2.0, 1.0) - z=-1.150000
      7: 0 - (2.5, 0.7) - z=-1.760000
      8: 0 - (2.2, 3.3) - z= 1.510000
      9: 0 - (1.5, 0.8) - z=-1.140000
     10: 0 - (2.3, 1.7) - z=-0.460000

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}$$

 Exercice 6.

Dans le programme perceptron.py, ajouter une fonction phi(z) qui calcule la valeur de la fonction d'activation \( \phi \) pour le nombre z.

Modifier le programme perceptron.py pour afficher la valeur de \( \phi \) pour chaque sample du dataset. sur la console. Après la modification le programme doit afficher:

Model
  w1: -0.500000
  w2:  1.200000
   b: -1.350000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      1: 1 - (0.5, 2.0) - z= 0.800000 - φ=1
      2: 1 - (1.1, 2.1) - z= 0.620000 - φ=1
      3: 1 - (0.7, 2.6) - z= 1.420000 - φ=1
      4: 1 - (0.5, 0.9) - z=-0.520000 - φ=0
      5: 1 - (1.0, 1.4) - z=-0.170000 - φ=0
      6: 0 - (2.0, 1.0) - z=-1.150000 - φ=0
      7: 0 - (2.5, 0.7) - z=-1.760000 - φ=0
      8: 0 - (2.2, 3.3) - z= 1.510000 - φ=1
      9: 0 - (1.5, 0.8) - z=-1.140000 - φ=0
     10: 0 - (2.3, 1.7) - z=-0.460000 - φ=0

Indice: Le caractère phi a pour code Python \u03c6

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}) $$

 Exercice 7.

Dans le programme perceptron.py, ajouter une fonction y(model, features) qui calcule la valeur de la prédiction \( y \) pour le vecteur de caractéristiques features et le modèle model.

Modifier le programme perceptron.py et afficher pour chaque sample du dataset sa valeur de prédiction sous la forme y=<prédiction>. Dans notre exemple l'affichage doit donner:

Model
  w1: -0.500000
  w2:  1.200000
   b: -1.350000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      0: 1 - (0.5, 2.0) - z= 0.800000 - φ=1 - y=1
      1: 1 - (1.1, 2.1) - z= 0.620000 - φ=1 - y=1
      2: 1 - (0.7, 2.6) - z= 1.420000 - φ=1 - y=1
      3: 1 - (0.5, 0.9) - z=-0.520000 - φ=0 - y=0
      4: 1 - (1.0, 1.4) - z=-0.170000 - φ=0 - y=0
      5: 0 - (2.0, 1.0) - z=-1.150000 - φ=0 - y=0
      6: 0 - (2.5, 0.7) - z=-1.760000 - φ=0 - y=0
      7: 0 - (2.2, 3.3) - z= 1.510000 - φ=1 - y=1
      8: 0 - (1.5, 0.8) - z=-1.140000 - φ=0 - y=0
      9: 0 - (2.3, 1.7) - z=-0.460000 - φ=0 - y=0

Que remarque t'on en comparant les valeurs \( t \) des classe des samples et les prédictions \( y \) associées ? 

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.

 Exercice 8.

Dans le programme perceptron.py, ajouter une fonction displayFrontier(model) qui permet de tracer sur le graphique MatPlotLib la frontière de décision du perceptron de modèle model sous la forme d'une droite de couleur orange.

Modifier ensuite la fonction main du programme pour afficher la frontière de décision du perceptron utilisé. L'affichage attendu est:

Indice: Pour dessiner le segment de droite correspondant à la frontière de décision il suffit de calculer ses valeurs aux bornes horizontales xmin et xmax du graphique. Pour récupérer les bornes des graphiques avec MatPlotLib il est possible d'utiliser xmin, xmax = plt.xlim() pour les bornes horizontales et ymin, ymax = plt.ylim()pour les bornes verticales.

Attention au cas ou \( w_{2}=0 \) !

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.

 Exercice 9.

Dans le programme perceptron.py, modifier la fonction displayDataset(dataset) en displayDataset(dataset, model) afin qu'il soit maintenant possible d'afficher la classe des individus mais aussi la prédiction du perceptron si celle-ci est incorrecte. Pour cela un individu devra être affiché:

  • en vert si sa classe est 0 et la prédiction assosiée est 0
  • en cyan si sa classe est 0 mais que la prédiction associée est 1
  • en rouge si sa classe est 1 et la prédiction associée est 1
  • en magenta si sa classe est 1 et sa prédiction associée est 0
  • en gris si quelque soit la classe il n'y a pas de prédiction associée

Tester le programme avec les valeurs le modèle actuelles. L'affichage attendu est:

 

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{}$$

 Exercice 10.

Dans le programme perceptron.py, ajouter une fonction error(dataset, model) qui calcule et retourne l'erreur du modèle model pour le jeux de données dataset. Faire ensuite afficher l'erreur dans la console après l'affichage du dataset.

Ajouter ensuite une fonction displayError(e) qui affiche la valeur d'erreur e en haut à gauche du graphique MatPlotLib. 

Une fois les deux fonction implantées, la sortie attendue du programme devient:

Model
  w1: -0.500000
  w2:  1.200000
   b: -1.350000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      0: 1 - (0.5, 2.0) - z= 0.800000 - φ=1 - y=1
      1: 1 - (1.1, 2.1) - z= 0.620000 - φ=1 - y=1
      2: 1 - (0.7, 2.6) - z= 1.420000 - φ=1 - y=1
      3: 1 - (0.5, 0.9) - z=-0.520000 - φ=0 - y=0
      4: 1 - (1.0, 1.4) - z=-0.170000 - φ=0 - y=0
      5: 0 - (2.0, 1.0) - z=-1.150000 - φ=0 - y=0
      6: 0 - (2.5, 0.7) - z=-1.760000 - φ=0 - y=0
      7: 0 - (2.2, 3.3) - z= 1.510000 - φ=1 - y=1
      8: 0 - (1.5, 0.8) - z=-1.140000 - φ=0 - y=0
      9: 0 - (2.3, 1.7) - z=-0.460000 - φ=0 - y=0
  Error: 3.0

et le graphique affiché attendu est:

 

 

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 \)

 Exercice 11.

Dans le programme perceptron.py, modifier les valeurs du modèle (variable model) et tenter de trouver manuellement une configuration qui permet une classification sans erreur. Vous avez 10 essais maximum. 

 

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:

  1. Un modèle est créé avec des paramètres empiriques (par convention tous égaux à \( 0 \)).
  2. 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.
  3. 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.

 Exercice 12.

Dans le programme perceptron.py, ajouter une fonction train(model, dataset) qui permettra d'entrainer le modèle model à partir du jeu de données dataset. Cette fonction ne doit pour l'instant que retourner la valeur 0 et sera définie dans les exercices suivants. Pour l'instant la fonction train est de la forme:

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;

Ajouter au programme un appel à train(model, dataset) en lui passant en paramètres le modèle actuel et le jeux de données chargé depuis le fichier CSV (placer l'appel à train(model, dataset) avant les affichages sur la console et les traçages MatPlotLib).

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.

 Exercice 13.

Dans le programme perceptron.py, ajouter une fonction init(model) qui permettra d'initialiser le modèle model en mettant tous ses paramètres à \( 0 \). 

Ajouter à la fonction train(model, dataset) un appel à init(model):

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
    """
   
    init(model)
    
    return 0;

Si les appels à train(model, dataset) sont correctement réalisés, la sortie console du programme attendue est:

Model
  w1:  0.000000
  w2:  0.000000
   b:  0.000000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      0: 1 - (0.5, 2.0) - z= 0.000000 - φ=1 - y=1
      1: 1 - (1.1, 2.1) - z= 0.000000 - φ=1 - y=1
      2: 1 - (0.7, 2.6) - z= 0.000000 - φ=1 - y=1
      3: 1 - (0.5, 0.9) - z= 0.000000 - φ=1 - y=1
      4: 1 - (1.0, 1.4) - z= 0.000000 - φ=1 - y=1
      5: 0 - (2.0, 1.0) - z= 0.000000 - φ=1 - y=1
      6: 0 - (2.5, 0.7) - z= 0.000000 - φ=1 - y=1
      7: 0 - (2.2, 3.3) - z= 0.000000 - φ=1 - y=1
      8: 0 - (1.5, 0.8) - z= 0.000000 - φ=1 - y=1
      9: 0 - (2.3, 1.7) - z= 0.000000 - φ=1 - y=1
  Error: 5.0

L'affichage graphique MatPlotLib attendu est quant à lui:

Sur le graphique, les individus de la classe 1 sont affichés en rouge (et non en magenta) car avec tous les paramètres du modèle à \( 0 \), la fonction d'agrégation vaut toujours \( 0 \) et la fonction d'activation \( \phi \) vaut alors toujours \( 1 \) par définition. Les individus de la classe 1 sont donc toujours considérés comme bien classés.

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.

 Exercice 14.

Dans le programme perceptron.py, ajouter une fonction epoch(model, dataset, alpha) qui permet de calculer une époque (epoch) pour le modèle model à partir du jeux de données dataset et du taux d'apprentissage alpha. Cette fonction modifie model avec les nouveaux paramètres.

Modifier ensuite la fonction train afin de pouvoir lui faire appeler epoch:

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)
    
    epoch(model, dataset, alpha)
    
    return 0;

Modifier finalement l'appel à la fonction train dans le programme pour l'appeler avec un taux d'apprentissage égal à 1.

Si l'implantation de la fonction epoch est correctement réalisés, la sortie console du programme attendue est:

Model
  w1: -2.000000
  w2: -1.000000
   b: -1.000000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      0: 1 - (0.5, 2.0) - z=-4.000000 - φ=0 - y=0
      1: 1 - (1.1, 2.1) - z=-5.300000 - φ=0 - y=0
      2: 1 - (0.7, 2.6) - z=-5.000000 - φ=0 - y=0
      3: 1 - (0.5, 0.9) - z=-2.900000 - φ=0 - y=0
      4: 1 - (1.0, 1.4) - z=-4.400000 - φ=0 - y=0
      5: 0 - (2.0, 1.0) - z=-6.000000 - φ=0 - y=0
      6: 0 - (2.5, 0.7) - z=-6.700000 - φ=0 - y=0
      7: 0 - (2.2, 3.3) - z=-8.700000 - φ=0 - y=0
      8: 0 - (1.5, 0.8) - z=-4.800000 - φ=0 - y=0
      9: 0 - (2.3, 1.7) - z=-7.300000 - φ=0 - y=0
  Error: 5.0

L'affichage graphique MatPlotLib attendu est quant à lui:

 

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

 Exercice 15.

Dans le programme perceptron.py, modifier la fonction train(model, dataset, alpha) afin qu'elle implante la version simplifiée de l'algorithme d'apprentissage de Rosenblatt.

Relancer le programme pour tester la fonction train. La sortie console du programme attendue est:

Model
  w1: -5.100000
  w2:  3.000000
   b:  1.000000

Dataset
  Dimension: 3
  Size     : 10
  Samples  :
      0: 1 - (0.5, 2.0) - z= 4.450000 - φ=1 - y=1
      1: 1 - (1.1, 2.1) - z= 1.690000 - φ=1 - y=1
      2: 1 - (0.7, 2.6) - z= 5.230000 - φ=1 - y=1
      3: 1 - (0.5, 0.9) - z= 1.150000 - φ=1 - y=1
      4: 1 - (1.0, 1.4) - z= 0.100000 - φ=1 - y=1
      5: 0 - (2.0, 1.0) - z=-6.200000 - φ=0 - y=0
      6: 0 - (2.5, 0.7) - z=-9.650000 - φ=0 - y=0
      7: 0 - (2.2, 3.3) - z=-0.320000 - φ=0 - y=0
      8: 0 - (1.5, 0.8) - z=-4.250000 - φ=0 - y=0
      9: 0 - (2.3, 1.7) - z=-5.630000 - φ=0 - y=0
  Error: 0.0

 

 

...