Apprentissage Automatique Numerique

M1 AFD

TP Perceptron, Perceptron multi-couches

Objectif du TP

L’objectif de ce TP est d’implémenter l’algorithme du perceptron pour résoudre un problème de classifi- cation binaire. Le corpus utilisé est constitué de votes des membres du congrès américains sur des sujets sensibles ainsi que l’appartenance politique du membre (républicain ou démocrate).

Quelques exemples de données du corpus :
republican,n,y,n,y,y,y,n,n,n,y,?,y,y,y,n,y
republican,n,y,n,y,y,y,n,n,n,n,n,y,y,y,n,?
democrat,?,y,y,?,y,y,n,n,n,n,y,n,y,y,n,n
democrat,n,y,y,n,?,y,n,n,n,n,y,n,y,n,n,y
democrat,y,y,y,n,y,y,n,n,n,n,y,?,y,y,y,y

Les données et leur description sont disponibles suivantes :

https://archive.ics.uci.edu/ml/datasets/Congressional+Voting+Records
Au total, 435 exemples constituent ce corpus. 100 exemples seront utilisés en tant que corpus de validation et le reste sera utilisé pour estimer les paramètres du modèle (corpus d’apprentissage). Il est important de mélanger les données avant de créer les corpus. Pensez à utiliser une ”graine“ (seed) pour le générateur de nombres pseudo-aléatoires afin de pouvoir reproduire les résultats.

Travail à effectuer

  1. Préparer des données : coder les données textuelles en valeurs numériques
  2. Ecrire une fonction ´ classify qui permet de donner l’étiquette d’une observation à partir d’un vecteur de poids
  3. Ecrire une fonction ´ test qui prend en paramètre un corpus et un vecteur de poids et qui renvoie le taux d’erreur
  4. Implémenter l’algorithme du perceptron dans une fonction train
    • Limiter l’apprentissage à un nombre maximum d’itérations
    • Définir et utiliser un autre critère d’arrˆet de l’apprentissage
  5. Tracer la courbe d’évolution du taux d’erreur en fonction de l’avancement de l’apprentissage (numéro de l’itération).
  6. Sommes nous sˆurs d’atteindre une erreur nulle sur le corpus d’apprentissage ?
  7. Intégrer un learning rate lors de la phase d’apprentissage : Wt+1 = Wt + λ x̄
  8. Déterminer un schéma permettant de faire évoluer le learning rate pendant l’apprentissage.
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
In [2]:
df = pd.read_csv('house-votes-84.data')

np.random.seed(987654321) # also work with panda

df = df.sample(frac=1).replace(['y', 'n', '?', 'republican', 'democrat'], [1, -1, 0, 1, -1])

df.head()
Out[2]:
Class Name handicapped-infants water-project-cost-sharing adoption-of-the-budget-resolution physician-fee-freeze el-salvador-aid religious-groups-in-schools anti-satellite-test-ban aid-to-nicaraguan-contras mx-missile immigration synfuels-corporation-cutback education-spending superfund-right-to-sue crime duty-free-exports export-administration-act-south-africa
126 1 -1 0 -1 1 1 1 -1 -1 -1 -1 -1 1 1 1 -1 -1
259 -1 1 -1 1 -1 -1 -1 1 1 1 -1 -1 -1 -1 -1 -1 1
30 1 -1 1 -1 1 1 1 -1 -1 -1 -1 -1 1 1 1 -1 -1
265 -1 1 -1 1 -1 -1 -1 1 1 1 1 -1 -1 -1 -1 -1 1
4 -1 1 1 1 -1 1 1 -1 -1 -1 -1 1 0 1 1 1 1
In [4]:
class Perceptron(object):
    
    MAX_ITER = 200
    
    def __init__(self, lr = 0.7):
        self.learning_rate = lr

    def _classify(self, x):
        """
        Give a class label to the x sample
        """
        return np.dot(x, self.weights)

    def fit(self, data_set):
        """
        Dress the learning sample
        
        class -1 -> invert the feature
        """
        
        self.data_set = data_set.copy() # used to calculate error through the training
        
        # invert class -1
        dataSet = data_set.copy()
        dataTarget = dataSet[:, 0].copy()
        for i in range(len(dataTarget)):
            if (dataTarget[i] == -1):
                dataSet[i] *= -1
                dataSet[i][0] = -1
            
        # init the weights
        
        self.weights =np.random.uniform(0.0, 10.0, size=len(data_set[0])) 
#         self.weights = np.empty(len(data_set[0]))
#         self.weights.fill(0.001)

#         self.weights = np.append( np.array(1) , np.zeros(len(data_set[0])-1) )
                
        return self._train(dataSet)
    
    def _train(self, train_data):
        """
        Perceptron algo
        """
        self.index_iter = 0
        i = 0
        
        best_score = 0
        best_weights = []
        self.all_score = []
        
        test_data = self.data_set.copy()
        test_data[:,0] = 1
        test_target = self.data_set[:, 0]
        
        while self.index_iter < self.MAX_ITER:
            
            if (len(train_data) == i): #linéairement séparable YaY
                score = per.score(test_data, test_target) # calculate the current Score
                self.all_score.append(score)
                return True
            
            
            if( self._classify(train_data[i]) < 0):
                self.weights += train_data[i] * self.learning_rate
                self.index_iter += 1
                i = 0
                
                # score
                score = per.score(test_data, test_target) # calculate the current Score
                self.all_score.append(score)
                if(best_score < score): # store the best score and his related weights
                    best_score = score
                    best_weights = self.weights.copy()
                    
                
            else:
                i+=1
                
        self.weights = best_weights # if not lineaire get the best weights found
        return False
        
    def score(self, test_data, test_target):
        """
        Take a corpus and calculate the score (percentage of success)
        """
        res = [(self._classify( test_data[i] ) * test_target[i]) >= 0
               for i in range(len(test_target)) ]
        return sum(res) / len(test_data)
    
    def plot_error_fit(self):
        """
        Plot the errors through the learning
        """
        plt.ylim([0,1]) 
        plt.title("Courbe d’evolution du taux d’erreur\n en fonction de l’avancement de l’apprentissage")
        plt.xlabel('iteration')
        plt.ylabel('% error')
        plt.plot(1-np.array(self.all_score))
        
        
separator = 335

train = df.values[:separator]
    
per = Perceptron()
print("Linéairement séparable?", per.fit(train))
print(per.weights)

per.plot_error_fit()

test_data = df.values[separator:].copy()
test_data[:,0] = 1
test_target = df.values[separator:][:, 0]
print("Score du corpus de test:", per.score(test_data, test_target))
Linéairement séparable? False
[  0.37461296   8.54185664  -2.87595522  -6.36845765  17.54378009
   6.2290078   -0.04070177   6.26980004  -0.78038495  -1.04080078
   0.9899839   -3.96497922   4.07641378   3.81790468  -4.50736466
   0.28865852  -0.78813429]
Score du corpus de test: 0.94

Sur ce corpus d’apprentissage (335) il n'est pas possible d’atteindre une erreur nulle, en 200 iterations.
Il faut mettre un corpus de taille plus petite (seprator = 160) pour que le perceptron ai une chance de trouver une separation lineraire en 200 iteration (updates de weights)

Pour faire évoluer le learning rate pendant l’apprentissage, on peut le faire démarrer a 1 au debut puis le faire baisser en fonction du score et du nombre d'itération