0% ont trouvé ce document utile (0 vote)
136 vues417 pages

Introduction au Machine Learning avec Python

Ce document décrit l'utilisation d'un jeu de données classique en apprentissage automatique, les iris, pour construire un modèle de classification des espèces de fleurs à l'aide de l'algorithme du plus proche voisin sous Python. Le document explique le chargement et la visualisation des données, puis la division des données en ensembles d'apprentissage et de test avant la construction du modèle.

Transféré par

roland
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
136 vues417 pages

Introduction au Machine Learning avec Python

Ce document décrit l'utilisation d'un jeu de données classique en apprentissage automatique, les iris, pour construire un modèle de classification des espèces de fleurs à l'aide de l'algorithme du plus proche voisin sous Python. Le document explique le chargement et la visualisation des données, puis la division des données en ensembles d'apprentissage et de test avant la construction du modèle.

Transféré par

roland
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Le Machine Learning

avec Python
Salim Lardjane
Université Bretagne Sud
Un premier exemple
Un premier exemple

• On va commencer par une application simple du machine


learning et créer notre premier modèle sous Python.

• On suppose qu’une botaniste amatrice souhaite


distinguer les espèces de certaines fleurs d’iris qu’elle a
récoltées.

Un premier exemple

• Elle a effectué des mesures sur chaque iris récolté :


longueur et largeur des pétales, longueur et largeur des
sépales, toutes mesurées en centimètres.

Un premier exemple
• Elle dispose des même mesures sur des fleurs d’iris qui
avaient été classées par une botaniste experte comme
appartenant aux espèces setosa, versicolor ou virginica.

• Pour ces fleurs-là, elle est certaine de l’appartenance de


chaque fleur.

• Notre objectif est de construire un modèle de Machine


Learning qui puisse apprendre à partir des mesures faites
sur les iris dont l’espèce est connue, de façon à ce qu’on
puisse prévoir l’espèce pour une nouvelle fleur d’iris.
Un premier exemple

• Il s’agit d’un problème d’apprentissage supervisé.

• C’est un exemple de reconnaissance de formes (ang.


Pattern Recognition, Classification).

• On souhaite obtenir en sortie, pour un nouveau point de


données (une nouvelle iris), l’espèce correspondante.
Dans ce contexte, on appelle celle-ci label.
Un premier exemple
• Les données qu’on va utiliser sont une jeu de données
classique en Machine Learning et en Statistique.

• Dans scikit-learn, il est inclus dans le module datasets.

• On peut le charger à l’aide de la fonction load_iris.

from [Link] import load_iris

iris_dataset = load_iris()
Un premier exemple

• L’objet iris_dataset renvoyé par load_iris est un objet de


type Bunch, qui est très semblable à un dictionnaire.

• Il contient des clefs et des valeurs associées.

print("Keys of iris_dataset :
\n{}".format(iris_dataset.keys()))
Un premier exemple

• Résultat obtenu :

Keys of iris_dataset :

dict_keys(['data', 'target', 'target_names',


'DESCR', 'feature_names'])
Un premier exemple

• La valeur de la clef DESCR est une courte description du


jeu de données. On peut en examiner le début de la façon
suivante :

print(iris_dataset[‘DESCR'][:193]+"\n...")

• On obtient le résultat suivant :

Un premier exemple
Iris Plants Database

====================

Notes

-----

Data Set Characteristics:

:Number of Instances: 150 (50 in each of three classes)

:Number of Attributes: 4 numeric, predictive att

...
Un premier exemple

• La valeur de la clef target_names est un tableau de


chaînes de caractères, contenant les espèces de fleurs
que l’on souhaite prévoir :

print("Target names:
{}".format(iris_dataset['target_names']))

• Résultat :
Un premier exemple
Target names: ['setosa' 'versicolor'
‘virginica']

• La valeur de feature_names est une liste de chaînes de


caractères, donnant la description de chaque variable :

print("Feature names:
{}".format(iris_dataset['feature_names']))

• Résultat :
Un premier exemple
Feature names: ['sepal length (cm)', 'sepal
width (cm)', 'petal length (cm)', 'petal width
(cm)’]

• Les données à proprement parler sont contenues dans les


champs target et data.

• data contient les mesures de longueur de sépale, de largeur


de sépale, de longueur de pétale et de largeur de pétale,
sous la forme d’un tableau NumPy :

print("Type of data:
{}".format(type(iris_dataset['data'])))
Un premier exemple
• Résultat :

Type of data: <class ‘[Link]'>

• Les lignes de data correspondent aux fleurs et les


colonnes représentent les quatre mesures faites sur
chaque fleur :

print("Shape of data:
{}".format(iris_dataset['data'].shape))

• Résultat :
Un premier exemple
Shape of data: (150, 4)

• On voit que le tableau contient des mesures pour 150


fleurs.

• On va afficher les 5 premières lignes du tableau de


données :

print("First five lines of data:


\n{}".format(iris_dataset['data'][:5]))
Un premier exemple
• Résultat :

First five lines of data:

[[ 5.1 3.5 1.4 0.2]

[ 4.9 3. 1.4 0.2]

[ 4.7 3.2 1.3 0.2]

[ 4.6 3.1 1.5 0.2]

[ 5. 3.6 1.4 0.2]]


Un premier exemple
• Le tableau target contient l’espèce de chacune des fleurs
sur lesquelles on a effectué des mesures; c’est un tableau
NumPy.

print("Type of target:
{}".format(type(iris_dataset[‘target'])))

• Résultat :

Type of target: <class '[Link]'>


Un premier exemple

• target est un tableau uni-dimensionnel avec une donnée


par fleur :

print("Shape of target:
{}".format(iris_dataset['target'].shape))

• Résultat :

Shape of target: (150,)


Un premier exemple

• Les espèces sont codées par les entiers 0, 1, 2 :

print("Target:
\n{}".format(iris_dataset['target']))

• Résultat :
Un premier exemple

Target:

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2

2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2

2 2]
Un premier exemple

• La signification des codes est données par le tableau


iris[‘target_names’].

• Setosa est codé par 0, versicolor par 1 et virginica par 2.


Un premier exemple
• On souhaite construire un modèle de Machine Learning à partir
de ces données, qui puisse prédire l’espèce à partir de nouvelles
mesures.

• Mais avant d’appliquer notre modèle à de nouvelles mesures, on


doit s’assurer qu’il fonctionne bien, c’est-à-dire qu’on peut faire
confiance à ses prédictions.

• On ne peut pas utiliser les données utilisées pour construire le


modèle pour l’évaluer.

• En effet, le modèle peut simplement garder en mémoire les


données sur lesquelles il a été construit; mais cela ne dit rien sur
sa capacité de généralisation.
Un premier exemple
• Afin d’évaluer les performance du modèle, on lui soumet
de nouvelles données pour lesquelles on dispose de
labels.

• C’est généralement fait en divisant les données


labellisées dont on dispose (150 mesures ici) en deux
parties : la première partie, utilisée pour construire le
modèle, est appelée ensemble d’apprentissage. La
deuxième partie, composée des données restantes, est
utilisée pour évaluer les performances du modèle; elle est
appelée ensemble de test.
Un premier exemple
• scikit-learn contient une fonction qui réordonne
aléatoirement les données et effectue la séparation en
deux parties : il s’agit de la fonction train_test_split.

• Cette fonction extrait 75% des lignes du tableau de


données comme ensemble d’apprentissage et les 25%
restantes comme ensemble de test.

• Les pourcentages sont en fait arbitraires mais la pratique


recommande la règle des 75%-25%.
Un premier exemple
• Sous scikit-learn, les données sont généralement notées
par un X majuscule et les labels par un y minuscule.

• Appliquons train_test_split à nos données :

from sklearn.model_selection import


train_test_split

X_train, X_test, y_train, y_test =


train_test_split(iris_dataset['data'],
iris_dataset[‘target'], random_state=0)
Un premier exemple

• Avant d’effectuer la division, la fonction train_test_split


réordonne les données à l’aide d’un générateur de
nombres pseudo-aléatoires. Ceci permet d’éviter que
toutes les données d’une classe soient exclues de
l’ensemble d’apprentissage par exemple.

• Afin d’obtenir le même résultat en relançant la fonction


plusieurs fois, on fournit un germe au générateur de
nombres pseudo-aléatoires. C’est fait à l’aide de
l’argument random_state de la fonction.
Un premier exemple

• Le résultat de la fonction train_test_split est X_train,


X_test, y_train et y_test.

• Ce sont tous des tableaux NumPy.

• X_train contient 75% des lignes du tableau de données et


X_test 25%.
Un premier exemple

print("X_train shape: {}".format(X_train.shape))

print("X_test shape: {}".format(X_test.shape))

• Résultat :

X_train shape: (112, 4)

X_test shape: (38, 4)


Un premier exemple
• Avant de construire notre modèle de machine learning,
commençons par visualiser nos données.

• A cet effet, on peut utiliser les nuages de points (ang.


scatter plots) des variables prises deux à deux.

• On commence par convertir les données en un


DataFrame pandas.

• On utilise ensuite la fonction scatter_matrix de pandas


pour obtenir le graphique.
Un premier exemple

import pandas as pd

import [Link] as pdpl

iris_dataframe = [Link](X_train, columns =


iris_dataset.feature_names)

grr = pdpl.scatter_matrix(iris_dataframe,
c=y_train, figsize=(15,15), marker=‘o',
hist_kwds={'bins':20}, s=60, alpha = 0.8)
Un premier exemple
Un premier exemple

• Le graphique obtenu permet de voir que les trois classes


peuvent être relativement bien séparées en utilisant les
mesures de sépales et de pétales.

• Ceci signifie qu’un algorithme de Machine Learning


devrait être à même d’apprendre à les séparer.

• Le premier algorithme qu’on va considérer est


l’algorithme du plus proche voisin.
Un premier exemple
• Pour faire une prévision sur de nouvelles données,
l’algorithme identifie le point de l’ensemble
d’apprentissage le plus proche de ces données puis il
affecte le label de ce point au nouvelles données.

• Tous les algorithmes de machine learning présents dans


scikit-learn sont implémentés dans des classes
spécifiques, qui sont appelées classes Estimator.

• L’algorithme des k-plus proches voisins est implémenté


dans la classe KNeighborsClassifier du module
Neighbors.
Un premier exemple
• Le paramètre le plus important de KNeighborsClassifier est
le nombre de voisins, qu’on va fixer à 1 :

from [Link] import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=1)

• L’objet knn encapsule l’algorithme utilisé pour construire le modèle à


partir des données d’apprentissage, ainsi que l’algorithme utilisé pour
faire des prévisions sur de nouveaux points.

• Il contiendra également l’information extraite par l’algorithme des


données d’apprentissage. Dans le cas de KNeighborsClassifier, il
s’agira simplement d’une copie de l’ensemble d’apprentissage.
Un premier exemple
• Pour construire notre modèle à partir de l’ensemble
d’apprentissage, on appelle la méthode fit de l’objet knn, qui
prend comme arguments les tableaux NumPy X_train et
y_train :

[Link](X_train, y_train)

• Résultat :

KNeighborsClassifier(algorithm='auto',
leaf_size=30, metric=‘minkowski',
metric_params=None, n_jobs=1, n_neighbors=1,
p=2, weights='uniform')
Un premier exemple
• La méthode fit renvoie l’objet knn lui-même (et le modifie par endroits);
on obtient ainsi une représentation de notre modèle sous la forme
d’une chaîne de caractères.

• Cette représentation nous fournit les paramètres utilisés dans la


création du modèle. Ici, tous ont leurs valeurs par défaut, sauf
n_neighbors qu’on a fixé à 1.

• La plupart des modèles disponibles sous scikit-learn ont de nombreux


paramètres mais la plupart d’entre eux servent soit à optimiser la
rapidité d’exécution, soit concernent des cas d’utilisation particuliers.

• Ainsi, afficher un modèle scikit-learn peut donner des chaînes de


caractères très longues, mais il n’est nécessaire de connaître qu’un
petit nombre de paramètres, qu’on verra dans la suite du cours.
Un premier exemple
• On peut à présent effectuer des prévisions sur de nouvelles données.

• Supposons par exemple qu’on ait trouvé dans la nature un iris de


longueur de sépale 5cm, de largeur de sépale 2.9cm, de longueur de
pétale 1cm et de largeur de pétale 0.2cm. A quelle espèce
appartient-il ?

• On commence par ranger les données dans un tableau NumPy :

import numpy as np

X_new = [Link]([[5, 2.9, 1, 0.2]])

print("X_new shape: {}".format(X_new.shape))


Un premier exemple
• Résultat :

X_new shape: (1, 4)

• Notons qu’on a rangé les données dans une ligne d’un


tableau bi-dimensionnel, car scikit-learn attend les
données sous cette dernière forme.

• Afin d’effectuer une prévision, on appelle la méthode


predict de l’objet knn :
Un premier exemple
prediction = [Link](X_new)

print("Prediction: {}".format(prediction))

print("Predicted target name:


{}".format(iris_dataset['target_names'][prediction]))

• Résultat :

Prediction: [0]

Predicted target name: [‘setosa']


Un premier exemple

• Notre modèle prédit que le nouvel iris appartient à la


classe 0, c’est-à-dire que son espèce est setosa.

• Mais comment savoir si on peut avoir confiance en notre


modèle et donc en cette conclusion ?

• C’est ici qu’intervient l’ensemble de test.


Un premier exemple
• Les données de test n’ont pas été utilisées pour
construire le modèle et on dispose du label correct pour
chacune de ces données.

• Par conséquent, on peut faire une prévision pour chaque


iris de l’ensemble test et la comparer au label correct.

• On peut évaluer la qualité du modèle en calculant son


exactitude (ang. accuracy), c’est-à-dire la proportion de
fleurs de l’ensemble test pour lesquelles la prévision est
correcte.
Un premier exemple
y_pred = [Link](X_test)

print("Test set predictions:\n


{}".format(y_pred))

print("Test set score: {:.


2f}".format([Link](y_pred==y_test)))

• Résultat :

[2 1 0 ..., 1 0 2]

Test set score: 0.97


Un premier exemple

• On peut également utiliser la méthode score de l’objet


knn, qui calcule directement l’exactitude :

print("Test set score: {:.


2f}".format([Link](X_test,y_test)))

• Résultat :

Test set score: 0.97


Un premier exemple
• L’exactitude de notre modèle est d’environ 0.97, ce qui
signifie qu’on a pris la bonne décision pour 97% des iris
de l’ensemble test.

• Ainsi, on peut s’attendre à ce que notre modèle fournisse


une prévision correcte dans 97% des cas pour de
nouveaux iris.

• Pour notre botaniste cela justifie l’utilisation du modèle


pour faire des prévisions.
L’apprentissage
supervisé
Apprentissage supervisé
• L’apprentissage supervisé est le type de machine learning le
plus utilisé en pratique.

• On est dans le cadre de l’apprentissage supervisé lorsqu’on


veut prédire une réponse (output) associée à un certain
stimulus (input) et qu’on dispose d’exemples de couples
stimulus/réponse (input/output) pour ce faire.

• On construit un modèle de machine learning à partir de ces


exemples, qui constituent notre ensemble d’apprentissage.

• Notre but est de faire les prévisions les plus exactes


possibles pour de nouvelles données.
Apprentissage supervisé

• L’apprentissage supervisé requiert souvent une


intervention humaine lors de la construction de
l’ensemble d’apprentissage, mais automatise et accélère
grandement la tâche de prévision par la suite.
Apprentissage supervisé

• Il existe deux types principaux de problèmes


d’apprentissage supervisé : la classification ou
reconnaissance de formes (ang. classification, pattern
recognition) - également appelée analyse discriminante -
et la régression (ang. regression).

• En reconnaissance de formes, l’objectif est de prévoir un


label (identifiant) de classe en le choisissant parmi un
nombre prédéfini de possibilités. L’exemple des iris en est
une illustration.
Apprentissage supervisé

• On distingue parfois classification binaire et classification


multi-classes.

• La reconnaissance de spams est un exemple de


classification binaire. Elle répond à la question «  ce
nouveau mail est-il un spam ? ».

• En classification binaire, on désigne souvent une classe


comme positive et l’autre comme négative, selon l’objet
de l’étude.
Apprentissage supervisé

• L’exemple des iris est un exemple de classification multi-


classes.

• Un autre exemple est la reconnaissance de la langue d’un


site web à partir du texte présent sur le site. Ici, les
classes sont données par une liste de langues
prédéfinies.
Apprentissage supervisé
• En régression, le but est de prévoir la valeur prise par une
variable continue (un nombre réel en mathématiques, un flottant
en informatique).

• En régression, on prévoit une quantité, alors qu’en classification


on prévoit une qualité.

• Un exemple de régression est la prévision du revenu annuel de


quelqu’un à partir de son niveau d’éducation, de son âge, et de
son lieu de résidence.

• Un autre exemple de régression est la prévision du rendement


d’une ferme de blé à partir des rendements des années
précédentes, de la météo et du nombre d’employés de la ferme.
Généralisation, sur-apprentissage,
sous-apprentissage
• En apprentissage supervisé, on construit un modèle sur
les données d’apprentissage puis on l’utilise pour faire
des prévisions sur de nouvelles données qui ont les
mêmes caractéristiques globales que les données
d’apprentissage.

• Si le modèle est apte à faire des prévisions exactes sur de


nouvelles données, on dit qu’il a la capacité de
généraliser.

• On souhaite construire un modèle qui généralise de la


façon la plus exacte possible.
Généralisation, sur-apprentissage,
sous-apprentissage
• Usuellement, on construit le modèle de façon à ce qu’il fasse des
prévisions d’exactitude élevée sur l’ensemble d’apprentissage.

• Si l’ensemble test et l’ensemble d’apprentissage ont les mêmes


caractéristiques en termes de distribution, on s’attend à ce que le
modèle soit également d’exactitude élevée sur l’ensemble test.

• Toutefois, il y a des cas où cela ne fonctionne pas, notamment


lorsque le modèle construit est très complexe.

• Dans ce cas, on peut être aussi exact que l’on veut sur
l’ensemble d’apprentissage sans pour autant être d’exactitude
élevée sur l’ensemble test.
Généralisation, sur-apprentissage,
sous-apprentissage

• Intuitivement, on s’attend à ce que les modèles les plus


simples généralisent le mieux sur l’ensemble test, même
s’ils s’ajustent moins bien à l’ensemble d’apprentissage.

• Lorsque le modèle construit est trop complexe par


rapport à l’information contenue dans l’ensemble
d’apprentissage, on dit qu’il y a sur-apprentissage.

• Dans ce cas, le modèle construit colle trop aux


caractéristiques uniques de l’ensemble d’apprentissage
et généralise mal.
Généralisation, sur-apprentissage,
sous-apprentissage

• D’autre part, si le modèle est trop simple, on peut


échouer à capturer tous les aspects des données et leur
variabilité. On dit dans ce cas qu’il y a sous-
apprentissage.

• Il y a un arbitrage à faire entre qualité de l’ajustement sur


l’ensemble d’apprentissage et qualité de la prévision sur
l’ensemble test.
Complexité et taille des
données
• La complexité du modèle doit être reliée à la variation des
données en entrée (inputs).

• Plus celles-ci sont variées, plus la complexité du modèle


construit peut être élevée tout en évitant le sur-apprentissage.

• Usuellement, plus on a de données, plus celles-ci sont variées.

• Ainsi, collecter plus de données permet usuellement d’ajuster


des modèles plus complexes.

• Toutefois, dupliquer les mêmes données ou recueillir des


données très similaires ne le permettent pas.
Les algorithmes
d’apprentissage
supervisé
Algorithmes
• On va présenter dans la suite divers algorithmes d’apprentissage
supervisé et expliquer comment ils apprennent à partir des données et
comment ils font des prévisions.

• On discutera la notion de complexité pour les modèles qu’ils


permettent d’obtenir.

• On examinera leurs avantages et leurs inconvénients et le type de


données sur lesquels ils sont les plus efficaces.

• On présentera la signification de leurs paramètres les plus importants.

• Enfin, la plupart de ces algorithmes ayant une variante classification et


une variante régression, on présentera celles-ci.
Données
• On va utiliser deux jeux de données réelles pour illustrer
les différents algorithmes.

• Tous deux sont disponibles sous scikit-learn.

• Le premier, qu’on désignera sous le nom de cancer,


regroupe des mesures cliniques faites sur des tumeurs du
sein. Chaque tumeur est labellisée comme « bénigne » ou
« maligne ».

• L’objectif est de prévoir si une tumeur est maligne à partir


des mesures effectuées.
Données

• Les données peuvent être lues à l’aide de la fonction


load_breast_cancer de scikit-learn.

from [Link] import load_breast_cancer

cancer = load_breast_cancer()

print("[Link]():
\n{}".format([Link]()))
Données
• Résultat :

[Link]():

dict_keys(['feature_names', 'target',
'DESCR', 'target_names', ‘data'])

• Les jeux de données contenus dans scikit-learn sont


généralement stockés comme des objets de type Bunch.
On en a déjà vu un exemple avec iris.
Données
• Le jeu de données est composé de 569 lignes et de 30
variables :

print("Shape of cancer data :


{}".format([Link]))

• Résultat :

Shape of cancer data : (569, 30)


Données
• Parmi les 569 tumeurs, 212 sont malignes et 357 sont
bénignes :

print("Sample count per class:\n{}".format({n:


v for n, v in zip(cancer.target_names,
[Link]([Link]))}))

• Résultat :

Sample count per class:

{'malignant': 212, 'benign': 357}


Données
• Afin de connaître la signification des variables, on peut
consulter l’attribut feature_names :

print("Feature names:
\n{}".format(cancer.feature_names))

• Résultat :

Feature names:

['mean radius' 'mean texture' 'mean


perimeter' ..., 'worst concave points''worst
symmetry' 'worst fractal dimension']
Données
• D’avantage d’informations sur les données sont fournies
dans [Link].

• Le deuxième jeu de données concerne la régression.

• L’objectif est de prévoir la valeur médiane des maisons


dans divers quartiers de la ville de Boston (Etats-Unis) à
partir d’informations sur le taux de criminalité, la proximité
à la rivière Charles, l’accessibilité de l’autoroute, etc.

• Le jeu de données contient 506 individus et 13 variables.


Données
from [Link] import load_boston

boston = load_boston()

print("Data shape:
{}".format([Link]))

• Résultat :

Data shape: (506, 13)


Données

• On peut accéder à plus d’informations sur le jeu de


données à l’aide de l’attribut DESCR de l’objet boston.

• On va augmenter le jeu de données boston des


interactions des 13 variables prises deux à deux.

• Pour cela, on définit la fonction suivante :


Données

from [Link] import MinMaxScaler, PolynomialFeatures

def load_extended_boston():

boston = load_boston()

X = [Link]

X = MinMaxScaler().fit_transform([Link])

X = PolynomialFeatures(degree=2, include_bias=False).fit_transform(X)

return X, [Link]
Données

• On peut alors accéder aux nouvelles données en faisant :

X, y = load_extended_boston()

print("[Link]: {}".format([Link]))

• Résultat :

[Link]: (506, 104)


Les k-plus proches
voisins
k-NN
• L’algorithme k-NN (k-plus proches voisins, ang. k-nearest
neighbors) est sans doute l’algorithme de machine
learning le plus simple.

• La construction du modèle consiste simplement à stocker


l’ensemble d’apprentissage.

• Afin de faire une prévision sur une nouvelle donnée,


l’algorithme considère les k plus proches voisins de cette
donnée dans l’ensemble d’apprentissage.
k-NN

• Dans le cas du problème de classification, la décision est


prise à l’aide d’un vote à la majorité : on affecte à la
nouvelle donnée la classe majoritaire parmi ses k plus
proches voisins.

• Pour choisir le nombre de voisins k, on peut se baser sur


l’exactitude des prévisions sur l’échantillon test.
k-NN
import [Link] as plt

from [Link] import load_breast_cancer

from sklearn.model_selection import train_test_split

from [Link] import KNeighborsClassifier

cancer = load_breast_cancer()

X_train, X_test, y_train, y_test = train_test_split([Link],

[Link],

stratify=[Link],

random_state=66)

training_accuracy = []

test_accuracy = []
k-NN
neighbors_settings = range(1,11)

for n_neighbors in neighbors_settings:

clf = KNeighborsClassifier(n_neighbors=n_neighbors)

[Link](X_train,y_train)

training_accuracy.append([Link](X_train,y_train))

test_accuracy.append([Link](X_test,y_test))
k-NN

[Link](neighbors_settings, training_accuracy, label = "training accuracy")

[Link](neighbors_settings, test_accuracy, label= "test accuracy")

[Link]("Accuracy")

[Link]("n_neighbors")

[Link]()
k-NN
k-NN
• Dans l’interprétation de ce graphique, il faut faire attention au fait
que les valeurs plus faibles de k correspondent à des modèles
plus complexes.

• Avec un seul voisin, la prévision sur l’ensemble d’apprentissage


est parfaite et lorsqu’on prend plus de voisins, le modèle devient
plus simple et l’exactitude baisse.

• L’exactitude sur l’échantillon test pour un seul voisin est


inférieure à celle obtenue avec plus de voisins, ce qui indique que
le modèle à un seul voisin est trop complexe.

• D’autres part, avec 10 voisins, on a un modèle trop simple et la


performance est pire.
k-NN

• La meilleure performance est obtenue entre les deux, aux


alentours de 6 voisins.

• Ceci dit, il est bon de garder l’échelle du graphique


présente à l’esprit : au pire on a une exactitude de 88%,
ce qui peut être largement suffisant pour certaines
applications.
k-NN

• Afin d’illustrer graphiquement l’effet du nombre de voisins


n, considérons la classification par n-plus proches voisins
d’un échantillon de taille 50, extrait de l’ensemble
d’apprentissage, en tumeurs malignes et bénignes à partir
des deux premières variables : mean radius et mean
texture.

• Le programme correspondant est disponible sur le forum


sous le nom [Link].
k-NN
k-NN
k-NN
k-NN
k-NN
k-NN
k-NN
k-NN
k-NN
k-NN
k-NN
• Il existe une variante régression de l’algorithme des k-plus
proches voisins.

• Pour une nouvelle donnée, la prévision correspondante est la


moyenne de la variable cible sur les k-plus proches voisins
de la donnée dans l’ensemble d’apprentissage.

• Examinons l’effet du nombre de voisins à partir d’un


échantillon de taille 40 issu du jeu de données boston.

• On veut prévoir la variable cible (prix médian des maisons


dans un quartier) à partir de la variable LSTAT (proportion de
ménages défavorisés dans le quartier).
k-NN
• On obtient le graphique suivant :

k-NN

• Les modèles sont évalués à l’aide de la méthode score,


qui pour les régresseurs calcule le R2, ou coefficient de
détermination.

• Le R2 est compris entre 0 et 1 si la régression obtenue


est au moins aussi bonne que l’ajustement d’une
constante, mais il peut être négatif dans le cas contraire.
k-NN

• Les graphiques précédents ont été obtenus à l’aide d’un


programme Python disponible sur le Forum sous le nom
[Link].

• Il fait intervenir les méthodes fit, pour ajuster le modèle,


predict pour faire des prévisions et score pour calculer le
R2, associées à un objet de type KNeighborsRegressor,
instanciant le modèle.
k-NN
• L’algorithmes des k-plus proches voisins pour la
classification est contrôlé par deux paramètres
essentiels : le nombre de voisins et la distance utilisée
pour mesurer la proximité.

• En pratique, un nombre de voisins compris entre 3 et 5


fonctionne souvent bien, mais il faut tout de même ajuster
ce paramètre.

• Le choix de la distance est plus compliqué. Par défaut, on


utilise la distance euclidienne, qui fonctionne bien dans
de nombreux contextes.
k-NN
• Un des avantages de k-NN est que le modèle est très
simple à comprendre et a souvent de bonne
performances sans nécessiter trop d’ajustements.

• L’algorithme k-NN est la méthode de base à utiliser avant


des méthodes plus avancées et un bon point de
comparaison.

• Construire le modèle est généralement rapide, mais


lorsque la base d’apprentissage est de taille importante,
la prévision peut être lente.
k-NN
• L’algorithme k-NN ne fonctionne généralement pas bien
sur des jeux de données ayant un grand nombre de
variables (plus d’une centaine) et il fonctionne
particulièrement mal pour les jeux de données où les
variables valent 0 la plupart du temps (données sparse).

• Ainsi, bien que l’algorithme k-NN soit facile à


comprendre, il n’est pas souvent utilisé dans les
applications réelles, en raison de la lenteur de la prévision
et de son inaptitude à prendre en compte un grand
nombre de variables.
Modèles linéaires de
régression
Modèles linéaires

• Les modèles linéaires sont une classe de modèles très


utilisés en pratique et ont été étudiés dans le détail au
cours des dernières décennies, bien qu’ils remontent à
plus d’un siècle.

• Les modèles linéaires font leur prévision à l’aide d’une


fonction linéaire des variables en entrée (variables
explicatives).
Modèles linéaires
• Pour la régression, la formule de prévision d’un modèle
linéaire a la forme suivante :

[Link] = w[0]*x[0]+w[1]*x[1]+…+w[p]*x[p]+b

• [Link] désigne la prévision, x[i] la (i+1)-ème variable


explicative et w et b sont les paramètres appris sur
l’ensemble d’apprentissage.

• Si on n’a qu’une seule variable en entrée, on obtient un


modèle linéaire simple :

[Link] = w[0]*x[0]+b
MCO
MCO
• La régression linéaire par moindres carrés ordinaires est
la méthode linéaire classique et la plus simple de
régression.

• L’algorithme des MCO (Moindres Carrés Ordinaires)


détermine les paramètres w et b qui minimisent l’erreur
quadratique moyenne (EQM) entre les prévisions et les
valeurs cibles sur l’ensemble d’apprentissage.

• L’EQM est la somme des carrés des différences entre les


prévisions et les vraies valeurs.
MCO

• L’algorithme des MCO n’a pas de paramètres de contrôle,


ce qui est pratique, mais du coup on ne peut contrôler la
complexité du modèle.

• Le programme [Link] effectue une régression par MCO


de la variable cible sur la variable LSTAT pour un
échantillon de taille 200 issu des données boston.

• On obtient le graphique suivant :


MCO
MCO

• Le code permettant d’ajuster le modèle est le suivant :

from sklearn.linear_model import LinearRegression

lr = LinearRegression().fit(X_train, y_train)

• La qualité de l’ajustement peut être évaluée à l’aide de la


méthode score :
MCO

print("Training set score: {:.2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f} ».format([Link](X_test,y_test)))

• Résultat :

Training set score: 0.56

Test set score: 0.46


MCO

• Le R2, égal à 0.56 sur les données d’apprentissage, n’est


pas bon et il est tout aussi mauvais sur les données de
test.

• Cela signifie que le modèle n’est pas adapté à la structure


des données ; il est sans doute trop rigide. Il y a sous-
apprentissage.
MCO
• Considérons à présent l’ensemble des données boston,
avec toutes les interactions des variables prises deux à
deux.

• On soumet le code suivant, extrait du programme python


[Link] :

lr = LinearRegression().fit(X_train, y_train)

print("Training set score: {:.2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f}".format([Link](X_test,y_test)))


MCO
• Résultat :

Training set score: 0.95

Test set score: 0.61

• L’écart entre la performance sur l’ensemble


d’apprentissage et l’ensemble de test est un signe de sur-
apprentissage. On doit donc chercher un modèle qui
permette de contrôler la complexité.
Régression Ridge
Régression Ridge
• La régression ridge est également un modèle linéaire de
régression.

• Par conséquent, la formule de prévision est la même que


celle des mco.

• La différence est que les coefficients sont estimées de


façon à ce que la fonction de prévision s’ajuste au mieux
à l’ensemble d’apprentissage sous une contrainte
additionnelle.
Régression Ridge
• Celle-ci spécifie que les coefficients w doivent être aussi
petits, c’est-à-dire proches de zéro, que possible.

• Intuitivement, cela signifie que chaque variable explicative


doit avoir aussi peu d’effet sur la réponse que possible,
tout an ayant globalement un modèle avec un bon
pouvoir prédictif.

• Cette contrainte est un exemple de la technique de


régularisation, très utilisée en machine learning.
Régression Ridge
• Régulariser un problème signifie lui imposer des
contraintes pour éviter le sur-apprentissage.

• Le type de contrainte imposée par la régression ridge est


appelée régularisation L2.

• Sous Python, la régression ridge est implémentée par la


classe

linear_model.Ridge
Régression Ridge
• Examinons ce que la régression ridge donne sur les
données boston (extrait du programme [Link]) :

from sklearn.linear_model import Ridge

ridge = Ridge().fit(X_train,y_train)

print("Training set score: {:.2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f}".format([Link](X_test,y_test)))

• Résultat :
Régression Ridge
Training set score: 0.89

Test set score: 0.75

• Le score d’apprentissage de Ridge est inférieur à celui de


LinearRegression (0.95) sur nos données, mais le score de test est
supérieur (0.75 > 0.61).

• C’est cohérent avec nos attentes.

• Avec la régression linéaire, il y a sur-apprentissage.

• Ridge étant un modèle plus contraint, on a un risque de sur-


apprentissage inférieur.
Régression Ridge

• Prendre un modèle moins complexe dégrade la


performance sur l’ensemble d’apprentissage mais
améliore la capacité à généraliser.

• Comme on s’intéresse uniquement à la prévision, on doit


préférer le modèle de régression ridge au modèle de
régression par mco pour nos données.
Régression Ridge
• Le modèle Ridge effectue un arbitrage entre simplicité du
modèle (coefficients proches de zéro) et performance sur
l’ensemble d’apprentissage.

• L’importance relative de la contrainte de simplicité par


rapport à la qualité de l’ajustement sur l’ensemble
d’apprentissage est contrôlée par le paramètre alpha.

• Dans l’exemple précédent, on a utilisé la valeur par défaut


alpha = 1.0.
Régression Ridge
• Ce choix était toutefois arbitraire; il n’est pas certain qu’il
fournisse le meilleur compromis entre simplicité du
modèle et performance sur l’ensemble d’apprentissage.

• Le choix optimal de alpha dépend du jeu de données


qu’on modélise.

• Faire croître alpha rapproche les coefficients de zéro, ce


qui dégrade la performance du modèle sur l’ensemble
d’apprentissage mais peut l’améliorer en termes de
généralisation.
Régression Ridge
• Exemple :

ridge10 = Ridge(alpha=10).fit(X_train,y_train)

print("Training set score: {:.2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f} ».format([Link](X_test,y_test)))

• Résultat :

Training set score: 0.79

Test set score: 0.64


Régression Ridge

• Prendre alpha = 10 dégrade les performance du modèle


sur l’ensemble d’apprentissage et l’ensemble test.

• Examinons le cas alpha = 0.1 :

ridge01 = Ridge(alpha=0.1).fit(X_train,y_train)

print("Training set score: {:.2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f}".format([Link](X_test,y_test)))


Régression Ridge
• Résultat :

Training set score: 0.93

Test set score: 0.77

• La valeur alpha = 0.1 semble bien fonctionner et on pourrait d’ailleurs


faire décroître alpha un peu plus pour améliorer la capacité de
généralisation du modèle.

• On verra plus précisément comment sélectionner le paramètre alpha


dans la suite du cours.
Régression Ridge

• On peut avoir une vision qualitative de la façon dont le


paramètre alpha impacte le modèle en sauvegardant la
valeur de l’attribut coef_ des modèles obtenus pour
différentes valeurs de alpha.

• Plus alpha est grand, plus le modèle est contraint et plus


on s’attend à avoir de faibles valeurs des coefficients.

• C’est confirmé par le graphique suivant (code [Link]) :


Régression Ridge
Régression Ridge
• Une autre façon de comprendre l’influence de la
régularisation consiste à fixer une valeur de alpha et à faire
varier la quantité de données d’apprentissage.

• Pour obtenir le graphique suivant, on a échantillonné le jeu


de données boston et évalué LinearRegression et
Ridge(alpha=1) sur des sous-ensembles de taille croissante.

• Le graphique obtenu est un exemple de courbe


d’apprentissage (ang. learning curve).

• Le programme correspondant sur le Forum est


ridge_learning_curve.py.
Régression Ridge
Régression Ridge
• Comme on pouvait s’y attendre, le score d’apprentissage est
plus élevé que le score de test pour toutes les tailles de
données, dans le cas de la régression ridge comme dans le cas
de la régression linéaire par mco.

• Toutefois, le score de test de la régression ridge est meilleur,


notamment pour les petites tailles de données.

• Pour des tailles d’échantillon inférieures à 400, la régression


linéaire par mco échoue à apprendre quoi que ce soit.

• Plus la taille de l’échantillon d’apprentissage devient importante,


plus la performance de la régression linéaire par mco se
rapproche de celle de la régression ridge.
Régression Ridge

• Ainsi, plus on a de données d’apprentissage, moins l’effet


de la régularisation est important et lorsqu’on a
suffisamment de données, régression linéaire par mco et
ridge donnent les mêmes résultats.

• Un autre point intéressant est la décroissance du score


d’apprentissage pour la régression linéaire par mco : plus
il y a de données, plus il est difficile pour un modèle de
les sur-ajuster ou de les mémoriser.
Lasso
Lasso

• La régression Lasso est une alternative à la régression


ridge pour régulariser la régression linéaire.

• Le Lasso impose également la contrainte d’avoir des


coefficients proches de zéro mais d’une façon légèrement
différente, appelée régularisation L1.

• Celle-ci a pour effet que certains coefficient sont


exactement égaux à zéro.
Lasso
• Cela signifie que certaines variables dont entièrement
ignorées par le modèle. La régression Lasso inclut donc
une forme de sélection de variables.

• Du coup, les modèles obtenus sont plus faciles à


interpréter et révèlent les structures les plus importantes
des données.

• Appliquons la régression Lasso aux données boston


(extraits du programme lasso_boston.py) :
Lasso

from sklearn.linear_model import Lasso

lasso = Lasso().fit(X_train,y_train)

print("Training set score: {:.2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f}".format([Link](X_test,y_test)))

print("Number of features used: {}".format([Link](lasso.coef_ != 0)))


Lasso
• Résultat :

Training set score: 0.29

Test set score: 0.21

Number of features used: 4

• La performance de Lasso est mauvaise pour les données boston, à


la fois sur les données d’apprentissage et de test.

• Cela signifie qu’il y a sous-apprentissage.

• Il s’avère que seules 4 variables sont utilisées par le modèle sur les
105 disponibles.
Lasso
• De même que la régression ridge, Lasso admet un
paramètre de régularisation alpha, qui contrôle l’intensité
avec laquelle les coefficients sont poussés vers zéro.

• Dans l’exemple précédent, on a utilisé la valeur par défaut


de alpha, qui vaut 1.

• Pour réduire le sous-apprentissage, faisons décroître


alpha. Lorsqu’on fait cela, on doit faire croître la valeur du
paramètre max_iter, qui contrôle le nombre maximum
d’itérations lors de l’ajustement du modèle.
Lasso

• Extrait du programme lasso_boston.py :

lasso001 = Lasso(alpha=0.01,max_iter=100000).fit(X_train,y_train)

print("Training set score: {:.


2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f}".format([Link](X_test,y_test)))

print("Number of features used: {}".format([Link](lasso001.coef_ != 0)))


Lasso
• Résultat :

Training set score: 0.90

Test set score: 0.77

Number of features used: 33

• Un alpha plus petit correspond à un modèle plus complexe, qui a


de meilleurs performance sur l’échantillon d’apprentissage et
l’échantillon test.

• La performance est légèrement meilleure qu’avec Ridge et on


n’utilise que 33 des 105 variables disponibles, ce qui rend le
modèle plus facile à comprendre.
Lasso
• Toutefois, si alpha est trop petit, on supprime l’effet de la
régularisation et il y a sur-apprentissage, avec un résultat
similaire à la régression linéaire par mco (extrait de
lasso_boston.py) :

lasso00001 = Lasso(alpha=0.0001,max_iter=100000).fit(X_train,y_train)

print("Training set score: {:.


2f}".format([Link](X_train,y_train)))

print("Test set score: {:.2f}".format([Link](X_test,y_test)))

print("Number of features used: {}".format([Link](lasso00001.coef_ != 0)))


Lasso

• Résultat :

Training set score: 0.95

Test set score: 0.64

Number of features used: 94

• Nous pouvons représenter graphiquement les coefficients


des différents modèles linéaires obtenus jusqu’ici :
Lasso
Lasso
• En pratique, on préfère souvent Ridge à Lasso à
performances similaires.

• L’avantage de Lasso réside dans l’interprétabilité du


modèle obtenu.

• Scikit-learn met aussi à disposition la classe ElasticNet,


qui combine les régularisations Lasso et Ridge.

• En pratique, ElasticNet est ce qui marche le mieux, mais


on a deux paramètres à ajuster, un pour la régularisation
L1 et un pour la régularisation L2.
Modèles linéaires de
classification binaire
Modèles linéaires

• Les modèles linéaires sont également très utilisés en


classification (reconnaissance de formes, analyse
discriminante).

• Considérons d’abord le cas de la classification binaire.

• Dans ce cas, une prévision est faite à l’aide de la formule :

[Link] = w[0]*x[0]+…+w[p]*x[p]+b > 0


Modèles linéaires
• La formule est très similaire à celle utilisée pour la
régression, si ce n’est qu’au lieu de renvoyer la somme
pondérée des variables, on compare cette valeur à un
seuil égal à zéro.

• Si la fonction prend une valeur inférieure à zéro, on prédit


la classe -1; si elle prend une valeur supérieure à zéro, on
prédit la classe +1.

• Cette règle est commune à tous les modèles linéaires de


classification.
Modèles linéaires
• Dans le cas de la régression linéaire, la réponse [Link], est
une fonction linéaire des variables : une droite, un plan ou
un hyperplan (en dimensions supérieures).

• Dans le cas des modèles linéaires de classification, c’est


la frontière de décision qui est une fonction linéaire des
variables.

• En d’autres termes, une règle de décision linéaire binaire


est une règle de décision qui sépare deux classes à l’aide
d’une droite, d’un plan ou d’un hyperplan.
Modèles linéaires
• Il y a divers algorithmes d’apprentissage des modèles
linéaires de classification.

• Ceux diffèrent de deux façons :

A. La façon dont ils mesurent la qualité de l’ajustement du


modèle aux données d’apprentissage.

B. Le fait qu’il y ait régularisation ou pas et le type de


régularisation utilisée s’il y en a.
Modèles linéaires

• Les deux algorithmes de classification linéaire d’usage le


plus fréquent sont la régression logistique, implémentée
sous Python par linear_model.LogisticRegression, et les
machines à vecteurs supports linéaires (SVM linéaires),
implémentées par [Link] (SVC : support vector
classification).

• Nous allons appliquer ces deux algorithmes à un


échantillon de taille 50 issu des données cancer (extrait
du programme linear_classif.py).
Modèles linéaires
from sklearn.linear_model import LogisticRegression

from [Link] import LinearSVC

fig, axes = [Link](1,2,figsize=(10,3))

for model, ax in zip([LinearSVC(), LogisticRegression()], axes):

clf = [Link](X,y)

plot_2d_separator(clf, X, fill=False, eps=0.5, ax=ax, alpha=.7)

discrete_scatter(X[:,0], X[:,1], y, ax=ax)

ax.set_title("{}".format(clf.__class__.__name__))

ax.set_xlabel("Variable 0")

ax.set_ylabel("Variable 1")

axes[0].legend()
Modèles linéaires
Modèles linéaires
• Les deux modèles obtenus ont des frontières de décision
similaires, bien qu’elles ne soient pas confondues.

• Par défaut, les deux modèles utilisent une régularisation


L2, comme le fait Ridge pour la régression.

• Pour LogisticRegression et LinearSVC, le paramètre


contrôlant l’intensité de la régularisation est noté C.

• Des valeurs plus élevées de C correspondent à moins de


régularisation.
Modèles linéaires

• En d’autres termes, quand on utilise une valeur élevée de


C, LogisticRegression et LinearSVC essayent de s’ajuster
le mieux possible aux données d’apprentissage alors que
pour des valeurs faibles de C, les modèles mettent
l’accent sur l’obtention d’un vecteur de coefficients w
proche de zéro.

• Un autre aspect important de l’influence de C est illustré


par les graphiques suivants :
Modèles linéaires
Modèles linéaires
• Sur le graphique de gauche, on a un C très faible,
correspondant à une régularisation importante.

• Celle-ci domine et les classes sont mal séparées.

• Sur le graphique du milieu, les classes sont mieux séparées


et il n’y a que 3 tumeurs mal classées.

• Finalement, sur le graphique à droite, la frontière de décision


sépare encore mieux les deux classes.

• Toutefois, ce dernier modèle est peut-être du coup en sur-


apprentissage.
Modèles linéaires
• Comme pour la régression, les modèles linéaires de
classification peuvent sembler très restrictifs dans les
espaces de faible dimension, n’autorisant que des
frontières de décision droites ou planes.

• Toutefois, en dimensions supérieures, ces modèles


deviennent très puissants et il faut faire attention à un sur-
apprentissage éventuel.

• Utilisons LogisticRegression sur les données cancer


complètes (extrait de linear_classif.py) :
Modèles linéaires
X_train, X_test, y_train, y_test = train_test_split([Link],

[Link],

stratify=[Link],

random_state=42)

logreg = LogisticRegression().fit(X_train,y_train)

print("Training set score: {:.3f}".format([Link](X_train,y_train)))

print("Test set score: {:.3f}".format([Link](X_test,y_test)))


Modèles linéaires

• Résultat :

Training set score: 0.953

Test set score: 0.958

• La valeur par défaut C = 1 donne de bons résultats, avec


95% d’exactitude sur les données d’apprentissage et de
test. Toutefois, comme ces performances sont proches, il
y a vraisemblablement sous-apprentissage.
Modèles linéaires
• Augmentons C pour avoir un modèle plus flexible :

logreg100 = LogisticRegression(C=100).fit(X_train,y_train)

print("Training set score: {:.3f}".format([Link](X_train,y_train)))

print("Test set score: {:.3f}".format([Link](X_test,y_test)))

• Résultat :

Training set score: 0.972

Test set score: 0.965


Modèles linéaires
• En fixant C = 100, on obtient une exactitude supérieure
sur les données d’apprentissage et une exactitude
légèrement supérieure sur les données de test, ce qui
confirme notre intuition qu’un modèle plus complexe
donnerait de meilleurs résultats.

• Examinons à présent ce qui se passe si on régularise


davantage que le choix par défaut de C = 1.

• Fixons C = 0.01.
Modèles linéaires
logreg001 = LogisticRegression(C=0.01).fit(X_train,y_train)

print("Training set score: {:.3f}".format([Link](X_train,y_train)))

print("Test set score: {:.3f}".format([Link](X_test,y_test)))

• Résultat :

Training set score: 0.934

Test set score: 0.930


Modèles linéaires

• Comme on pouvait s’y attendre, il y a encore plus sous-


apprentissage et la performance se dégrade sur les
données test comme sur les données d’apprentissage.

• Finalement, examinons les coefficients du modèle obtenu


pour les trois valeurs du coefficient de régularisation
(programme linear_classif.py) :
Modèles linéaires
Modèles linéaires

• Si on veut un modèle plus interprétable, on peut utiliser la


régularisation L1 car elle limite le modèle à quelques
variables.

• Le graphique des coefficients est le suivant (programme


linear_classif.py) :
Modèles linéaires
Modèles linéaires
• Les performances des différents modèle sont les
suivantes :

Training accuracy of l1 logreg with C=0.001: 0.91

Test accuracy of l1 logreg with C=0.001: 0.92

Training accuracy of l1 logreg with C=1.000: 0.96

Test accuracy of l1 logreg with C=1.000: 0.96

Training accuracy of l1 logreg with C=100.000: 0.99

Test accuracy of l1 logreg with C=100.000: 0.98


Modèles linéaires

• Comme pour la régression linéaire, on constate que la


régularisation L1 effectue une sélection de variables.

• Relevons également la très bonne performance en


prévision pour C= 100.
Modèles linéaires de
classification multi-
classes
Modèles linéaires

• De nombreux modèles linéaires de classification sont


spécifiques à la classification binaire et ne se généralisent
pas naturellement à la classification multi-classes, à
l’exception de la régression logistique.

• Une technique standard pour généraliser un algorithme


de classification binaire à la classification multi-classes
est l’approche one-vs.-rest.
Modèles linéaires
• Selon l’approche one-vs.-rest, un modèle binaire est appris
pour chaque classe, en essayant de la séparer de toutes les
autres classes.

• On obtient ainsi autant de modèles binaires qu’il y a de


classes.

• Pour faire une prévision, tous les classificateurs binaires sont


lancés sur une même donnée de test.

• Le classificateur qui a le plus grand score sur sa classe est


retenu et le label correspondant est renvoyé comme
prévision.
Modèles linéaires
• Comme on a un classificateur binaire par classe, on obtient
un vecteur de coefficients w et un terme constant b pour
chaque classe. La classe prédite est celle pour laquelle la
formule suivant prend sa valeur la plus élevée :

w[0]*x[0]+…+w[p]*x[p]+b

• Les mathématiques de la classification multi-classes par


régression logistique sont quelque peu différentes de celle
de l’approche one-vs.-rest mais elles aboutissent également
à un vecteur de coefficients et un terme constant par classe,
et la méthode de prévision est la même.
Modèles linéaires

• Appliquons la méthode one-vs.-rest à une problème de


classification à trois classes.

• On utilise un jeu de données bi-dimensionnel où les


données de chaque classe suivent une loi gaussienne
bivariée.
Modèles linéaires
Modèles linéaires
• On lance un classificateur LinearSVC sur les données :

linear_svm = LinearSVC().fit(X,y)

print("Coefficient shape: ",linear_svm.coef_.shape)

print("Intercept shape: ",linear_svm.intercept_.shape)

• Résultat :

Coefficient shape: (3, 2)

Intercept shape: (3,)


Modèles linéaires

• On voit que coef_ est de dimensions (3,2), ce qui signifie


que chaque ligne de coef_ contient le vecteur de
coefficients pour l’une des trois classes. Intercept_, quant
à lui, est un vecteur de dimension 3, une pour chaque
classe.

• A présent, visualisons les frontières de décision pour


chacun des trois classificateurs (programme
linear_classif.py):
Modèles linéaires
Modèles linéaires
• Qu’en est-il du triangle au milieu du graphique ?

• Les trois classificateurs classent la zone correspondante


en « rest ». A quelle classe sont assignés d’éventuels
points dans cette zone ? Réponse : à celle pour laquelle
la formule de classification donne la valeur la plus élevée,
c’est-à-dire à la classe de la droite la plus proche.

• Visualisons les prévisions pour toutes les zones du plan


(programme linear_classif.py) :
Modèles linéaires
Modèles linéaires
• Le principal paramètre des modèles linéaires est le
paramètre de régularisation, appelé alpha dans les modèles
de régression et C pour LinearSVC et LogisticRegression.

• Des valeurs élevées de alpha, ou des valeurs faibles de C,


signifient que le modèle est plus simple.

• Pour les modèles de régression, en particulier, l’ajustement


de ces paramètres est très important.

• Usuellement, on cherche C et alpha sur une échelle


logarithmique.
Modèles linéaires
• L’autre décision à prendre est de choisir entre une
régularisation L1 ou une régularisation L2.

• Si l’on suppose que seules quelques variables sont


pertinentes pour la prévision, on choisit la régularisation
L1. Sinon, on choisit la régularisation L2.

• La régularisation L1 peut également être précieuse si


l’interprétabilité du modèle est importante puisqu’elle
sélectionne un faible nombre de variables pertinentes.
Modèles linéaires
• Les modèles linéaires apprennent rapidement et prévoient
rapidement.

• Il passent à l’échelle pour des jeux de données très


volumineux et fonctionnent bien avec les données sparse.

• Si les données sont composées de centaines de milliers ou


de millions de lignes, on peut utiliser l’option solver = sag
dans LogisticRegression et Ridge, ce qui peut permettre
d’obtenir les résultats plus rapidement.

• Une autre solution pour le passage à l’échelle consiste à


utiliser les classes SGDClassifier ou SGDRegressor.
Modèles linéaires
• Un autre avantage des modèles linéaires est qu’il est
relativement facile de comprendre la façon dont une
prévision est obtenue.

• Toutefois, la signification des coefficients n’est pas


toujours claire.

• En particulier, si les variables explicatives sont très


corrélées, les coefficients peuvent être difficiles à
interpréter.
Modèles linéaires
• Les modèles linéaires se comportent généralement bien
lorsque le nombre de variables est important par rapport
au nombre d’échantillons (d’individus).

• Ils sont également utilisés sur des données très


volumineuses, pour la simple raison qu’il n’est pas
possible d’utiliser d’autres modèles.

• Toutefois, en faible dimension, d’autres modèles peuvent


être plus performants en termes de généralisation.
Classificateur
Bayésien Naïf
Naive Bayes

• Les classificateurs bayésiens naïfs sont assez semblables


aux classificateurs linéaires, mais ils ont tendance à être
plus rapides en termes d’apprentissage.

• Par contre, ils ont en général des performances en


généralisation inférieures à celles des classificateurs
linéaires comme LogisticRegression et LinearSVC.
Naive Bayes

• La raison pour laquelle les modèles bayésiens naïfs sont


si efficaces en termes d’apprentissage est qu’ils
apprennent en considérant chaque variable séparément
et en calculant des statistique simples par classe pour
chaque variable.

• Il y a trois sortes de classificateurs bayésiens naïfs


implémentés dans scikit-learn : GaussianNB, BernoulliNB
et MultinomialNB.
Naive Bayes

• GaussianNB peut être appliqué à des données continues,


BernoulliNB à des données binaires et MultinomialNB à
des données de comptage (i.e. chaque variable prend des
valeurs entières représentant un nombre de choses, par
exemple, le nombre de fois qu’un mot apparaît dans une
phrase).

• BernoulliNB et MultinomialNB sont surtout utilisées en


classification de données textuelles.
Naive Bayes
• Le classificateur BernoulliNB compte le nombre de fois où
les variables sont différentes de zéro sur chaque classe. Afin
d’illustrer son fonctionnement, considérons un exemple :

X = [Link]([[0,1,0,1],

[1,0,1,1],

[0,0,0,1],

[1,0,1,0]])

y = [Link]([0,1,0,1])
Naive Bayes
• Ici, nous avons quatre points de données, chacun
composé de quatre valeurs binaires.

• Il y a deux classes : 0 et 1.

• Pour la classe 0 (1er et 3ème points), le première variable


vaut 0 deux fois et est différente de 0 zéro fois; la
deuxième variable vaut zéro une fois et est différente de
zéro une fois, etc. On calcule ensuite les mêmes
statistiques sur la classe 1. En termes de programme cela
donne (programme naive_bayes.py) :
Naive Bayes
counts={}

for label in [Link](y):

counts[label]=X[y==label].sum(axis=0)

print("Feature counts:\n{}".format(counts))

• Résultat :

Feature counts:

{0: array([0, 1, 0, 2]), 1: array([2, 0, 2, 1])}


Naive Bayes

• Les deux autres modèles bayésiens naïfs, MultinomialNB


et GaussianNB, sont légèrement différents en termes de
statistiques calculées.

• MultinomialNB prend en compte la valeur moyenne de


chaque variable sur chaque classe, alors qua
GaussianNB calcule la valeur moyenne et l’écart-type de
chaque variable sur chaque classe.
Naive Bayes
• Afin de faire une prévision, un nouveau point de données
est comparé aux statistiques calculées pour chaque
classe et la classe retenue en prévision est celle qui lui
correspond le mieux.

• Pour GaussianNB comme pour MultinomialNB, cela


conduit à une formule de prévision linéaire.

• Toutefois, l’attribut coef_ du modèle bayésien naïf a un


sens différent de celui qu’il a pour un modèle linéaire :
coef_ est différent de w.
Naive Bayes

• MultinomialNB et BernoulliNB admettent un paramètre


unique alpha, qui contrôle la complexité du modèle.

• Un alpha plus élevé correspond à modèle moins


complexe.

• La performance de l’algorithme est relativement peu


sensible au choix de alpha, mais un bon ajustement de
celui-ci peu améliorer un peu les performances.
Naive Bayes
• GaussianNB est surtout utilisé pour des données en très
grande dimension, alors que les deux autres variantes sont
souvent utilisées pour des données textuelles.

• MultinomialNB a souvent de meilleurs performances que


BinaryNB sur de grands documents.

• Le modèle bayésien naïf partage de nombreux avantages et


inconvénients avec les modèles linéaires. Il apprend et
prévoit très rapidement et la procédure d’apprentissage est
facile à comprendre. Il est particulièrement adapté aux
données en très grande dimension et aux très grands
ensembles de données.
Autres méthodes
• D’autres méthodes d’apprentissage supervisé sont : les
arbres de décision, les forêts aléatoires, les gradient
boosting machines, les kernelized support vector
machines, les réseaux de neurones artificiels (deep
learning).

• Ces méthodes sont enseignées en Master 1 et Master 2


Data Science & Modélisation Statistique à l’UBS.

• Sous Python, elles s’utilisent comme les méthodes qu’on


a vues précédemment.
Introduction à
l’Apprentissage par
renforcement
Renforcement
• L’apprentissage par renforcement se situe entre
l’apprentissage supervisé, où on fournit à l’algorithme les
réponses correctes pour une base d’exemples, et
l’apprentissage non supervisé, où l’algorithme doit
extraire des connaissances des données sans avoir de
réponses correctes.

• Dans l’apprentissage par renforcement, on indique


simplement à l’algorithme la qualité de ses réponses, sans
lui dire comment les améliorer.
Renforcement

• L’algorithme doit essayer différentes stratégies et


déterminer celle qui fonctionne le mieux.

• L’algorithme effectue une recherche dans l’espace d’états


des entrées et sorties possibles afin de maximiser une
gratification (une récompense).
Renforcement
• L’apprentissage par renforcement est généralement décrit
au moyen d’une interaction entre un agent et sont
environnement.

• L’agent est la chose qui apprend et l’environnement est


composé des conditions dans lesquelles a lieu
l’apprentissage et de ce qui est appris.

• L’environnement fournit également de l’information sur la


qualité d’une réponse via la fonction de gratification.
Renforcement
• Comme exemple, considérons un enfant qui apprend à se
tenir debout et à marcher.

• Il essaye différentes stratégies pour se tenir debout et il


reçoit un feedback sur celles qui marche en tombant ou
en ne tombant pas.

• Les méthodes qui semblent fonctionner sont essayées


encore et encore, jusqu’à qu’elles soient perfectionnées
ou qu’il trouve de meilleures solutions, et celles qui ne
fonctionnent pas sont oubliées.
Formalisation

• L’apprentissage par renforcement associe des actions à


des états ou des situations en vue de maximiser une
gratification numérique.

• L’algorithme connait son input courant (l’état) et les


choses qu’il peut faire (les actions) et son but est de
maximiser la gratification.
Formalisation
• La façon la plus fréquente de penser à l’apprentissage
par renforcement consiste à faire intervenir un robot.

• L’état est défini par les données des capteurs du robot ou


une version transformée de celles-ci. Celles-ci constituent
une sorte de représentation de l’environnement du robot.

• Les capteurs ne fournissent pas nécessairement toute


l’information utile (par exemple, il ne fournisse pas la
localisation du robot) et il peut y avoir des inexactitudes et
du bruit sur les données des capteurs.
Formalisation
• Les diverses façons possibles dont le robot peut
actionner ses moteurs constituent les actions, qui
déplacent le robot dans son environnement, et la
gratification peut consister en une mesure de qualité de
la façon dont il exécute sa tâche sans percuter d’objets
de l’environnement.

• En apprentissage par renforcement, la fonction de


gratification évalue la solution courante mais ne suggère
pas de façon de l’améliorer, contrairement à ce qui se
passe en apprentissage supervisé.
Formalisation

• Une autre difficulté est qu’il peut y avoir un délai dans la


gratification, ce qui signifie que celle-ci n’est accordée
que bien après qu’une action ait été effectuée.

• Ainsi, on doit parfois distinguer la gratification immédiate


et la gratification espérée dans le futur.

• Une fois que l’algorithme sait comment évaluer les


actions, il doit choisir celle à effectuer dans l’état courant.
Formalisation

• C’est fait par une combinaison d’exploration (essayer


d’obtenir une nouvelle solution) et d’exploitation (essayer
d’améliorer une solution existante).

• Concrètement, cela peut signifier : reproduire l’action qui


a fourni la plus grande gratification la dernière fois que le
robot a été dans le même état ou essayer une action
différente dans l’espoir d’améliorer la gratification.
Applications

• L’apprentissage par renforcement est utilisé notamment


en robotique car il permet aux robots d’apprendre à
effectuer des tâche sans intervention humaine.

• Par exemple, il a été utilisé pour faire en sorte que des


robots libèrent de l’espace dans une pièce en poussant
des boîtes sur les côtés, qu’ils se suivent, qu’ils se
déplacent vers des repères lumineux et qu’ils naviguent.
Applications
• Un inconvénient de l’apprentissage par renforcement est
qu’il est en général assez lent car il doit recueillir de
l’information via l’exploration et l’exploitation des
solutions pour améliorer celles-ci.

• Il dépend aussi fortement de la fonction de gratification


choisie. Si ce choix est mal fait, l’algorithme peut faire
n’importe quoi.

• Dans la suite, on va voir une famille d’algorithmes


permettant de faire de l’apprentissage par renforcement :
les algorithmes génétiques.
Algorithmes
génétiques
Algorithmes génétiques

• Le mécanisme de l'évolution agit sur une population


animale par l'intermédiaire d'une hypothétique fonction
d'aptitude possédant un biais positif vis-à-vis des
animaux les plus aptes, c'est-à-dire ceux qui vivent
suffisamment longtemps pour se reproduire et qui sont
plus attractifs; et qui on donc plus de partenaires et une
descendance plus nombreuse.

• Ce mécanisme est notamment décrit dans les deux


ouvrages suivants :
Algorithmes génétiques
Algorithmes génétiques
• Les algorithmes génétiques modélisent le mécanisme
génétique de l'évolution. En particulier, ils modélisent la
reproduction sexuée, dans laquelle les deux parents
transmettent de l'information génétique à leurs
descendants.

• Ainsi, les enfants ont des similarités avec leurs parents et


il ya beaucoup d'héritage génétique. Toutefois, il y a
également des mutations aléatoires, dues à des erreurs
de copie lorsque les chromosomes sont reproduits, ce qui
signifient que les choses évoluent au cours du temps.
Algorithmes génétiques
• Les algorithmes génétiques sont souvent, mais pas
toujours, très efficaces.

• Il dépendent d'un certain nombre de paramètres critiques


qu'il est difficile d'ajuster.

• On ne peut garantir que la solution trouvée soit un tant


soit peu pertinente.

• En pratique, il donnent souvent satisfaction et sont


notamment utilisés lorsqu'on n'a pas d'alternative
classique pour mener à bien une optimisation.
Algorithmes génétiques
• En se basant sur les principes de l'évolution, on on a besoin, pour
modéliser des mécanismes génétiques simples sur ordinateur et
les utiliser pour résoudre un problème d'optimisation, de
déterminer :

• Une méthode pour représenter les solutions comme des


chromosomes

• Une méthode pour calculer la qualité d'une solution

• Une méthode de sélection pour choisir les parents

• Une méthode pour générer une descendance en croisant les


parents
Algorithmes génétiques
• La démarche proposée sera illustrée sur une exemple
classique, qui est difficile à résoudre (NP-complet) : le
problème du sac-à-dos. Celui-ci est le suivant :

• Vous devez partir en vacances. Vous avez acheté le plus


grand et le meilleur sac à dos en vente, mais il ne suffit
toujours pas à contenir tout ce que vous devez emporter.
Vous décidez donc d'associer une valeur à chaque objet
et de mesurer l'espace qu'il occupe. Vous souhaitez
maximiser la valeur totale des objets que vous emporterez
sous la contrainte qu'ils rentrent tous dans le sac.
Algorithmes génétiques
• Ce problème, ou des variations de celui-ci, apparaît en
cryptographie, combinatoire, logistique et management;
c'est donc un problème important du point de vue pratique.
Malheureusement, il est NP-complet ce qui signifie que la
détermination d'une solution optimale pour des cas
intéressants est impossible du point de vue calculatoire.

• Pour simplifier le problème, on supposera dans la suite que


la valeur est égale au volume.

• On va voir comment déterminer une approximation de la


solution optimale à l'aide d'un algorithme génétique.
Représentations
symboliques
• La première chose dont nous ayons besoin est une façon de
représenter les solutions possibles par l'analogue de
chromosomes.

• Les AG (Algorithmes génétiques) utilisent pour ce faire des


chaînes de caractères, chaque élément des ces chaînes
(équivalent à un gène) étant choisis dans un alphabet.

• Les différents caractères de l'alphabet sont l'analogue des


allèles et sont souvent binaires (0/1).

• On génère alors aléatoirement un ensemble de chaînes


aléatoires, qui constitue notre population initiale.
Représentations
symboliques

• Pour le problème du sac-à-dos, l'alphabet est très


simple : on peut le prendre binaire. On adopte une
longueur de chaîne égale à L, où L est le nombre total
d'objet qu'on souhaite pouvoir emmener, chaque élément
de la chaîne pouvant être égal à 0 ou 1. On code alors 0
pour les objets qu'on emmène pas et 1 pour ceux qu'on
emmène. Ainsi, la chaîne obtenue correspond à une
solution possible au problème.
Représentations
symboliques

• Cependant, cela ne nous dit pas si cette solution est


réalisable (c’est-à-dire si elle rentre dans le sac) ni si c'est
une bonne solution (si elle maximise le volume total des
objets emmenés).

• Pour ce faire, on a besoin d'une méthode permettant de


déterminer dans quelle mesure chaque chaîne remplit les
critères du problème, c'est-à-dire d’évaluer la qualité de
la solution.
Qualité des solutions
• La fonction de qualité, d'aptitude ou de pertinence peut être vue
comme un oracle qui prend une chaîne pour argument et lui
associe une valeur.

• C'est la seule partie de l'algorithme spécifique au problème.

• Il faut bien réfléchir à ce qu'on exige de la fonction de qualité; la


meilleure chaîne doit être de qualité la plus élevée et la qualité
doit décroître pour des chaînes qui font moins bien sur le
problème posé.

• En général, la qualité doit être une fonction positive : même les


chaînes les moins pertinente doivent avoir une qualité supérieure
ou égale à zéro.
Qualité des solutions
• Dans l'évolution réelle, la fonction de qualité n'est pas
statique : il y a compétition entre les différentes espèces
(prédateurs et proies par exemple) et des traitement
peuvent être découverts pour certaines maladies; la
mesure de qualité change donc au cours du temps. Dans
le cadre des algorithmes génétiques, on fait abstraction de
ces aspects.

• Pour le problème du sac-à-dos, on peut souhaiter que le


sac soit de valeur la plus élevée possible. On associe dans
ce cas à chaque chaîne la valeur totale correspondante.
Cela correspond à une fonction de qualité possible.
Qualité des solutions

• Toutefois, cela ne nous dit pas si la solution rentre dans le


sac : avec cette fonction de qualité, la solution optimale
est de tout prendre.

• On doit donc vérifier qu’elle rentre dans le sac et, si ce


n'est pas le cas, réduire la qualité de la solution.
Qualité des solutions

• Une approche consiste à fixer la qualité d'une solution à 0


si elle ne rentre pas dans le sac. Toutefois, supposons
qu'un solution soit presque parfaite, en cela qu'il y a juste
un objet en trop.

• En fixant la qualité à 0, on réduit les chances que cette


solution soit autorisée à évoluer et donc à s'améliorer au
cours des itérations suivantes.
Qualité des solutions

• On va donc, au lieu de cela, adopter comme fonction de


qualité la somme des volumes des objets s'ils rentrent
dans le sac mais s'il n'y rentrent pas, on la définira
comme la taille du sac moins deux fois le volume
excédentaire.

• Ceci autorise les solutions qui sont légèrement


excédentaires à être prises en considération pour
amélioration, tout en traduisant le fait que ce ne sont pas
les plus adaptées.
Qualité des solutions

• Il existe un algorithme évident pour déterminer des


solutions au problème du sac-à-dos. Il consiste à prendre
à chaque étape l'objet le plus volumineux qui n'ait pas
encore été sélectionné et qui rentre encore dans le sac, et
à itérer. Il ne fournit pas nécessairement la solution
optimale, mais il est très simple et très rapide.

• On attend donc d'un AG qu'il fournisse une bien meilleure


solution que cet algorithme pour justifier l'effort de
programmation et d’exécution associé.
Population
• On est à présent en mesure d'évaluer la qualité de n'importe quelle
chaîne.

• L'Algorithme Génétique agit sur une population de chaînes qu'il


fait évoluer au cours du temps.

• La première génération est en général générée aléatoirement.

• On évalue ensuite la qualité de chaque chaîne et les élément de la


première génération sont croisés (on verra comment dans la suite)
de façon à obtenir une deuxième génération, et ainsi de suite.

• L'évolution a lieu de façon à ce que la qualité des individus


composant la population s'améliore au fil du temps.
Population
• Pour le problème du sac-à-dos, on commence par créer
un ensemble de chaînes aléatoires binaires de longueur L
en utilisant un générateur de nombres aléatoires.

• On peut par exemple générer 100 chaînes. On choisit


alors des parents dans cette population et on les croise.

• La taille de la population reste constante au cours des


itérations, ce qui diffère de l'évolution animale telle qu'elle
a lieu en réalité.
Sélection des parents

• L'idée de base est que la qualité s'améliorera si on croise


des chaînes qui sont déjà de bonne qualité comparées
aux autres (on imite la sélection naturelle).

• Ce faisant, on dit qu'on effectue une exploitation de la


population courante. En général, on s'autorise toutefois
également à faire une exploration de celle-ci en n'écartant
pas la possibilité de sélectionner des chaînes de qualité
moindre.
Sélection des parents

• In fine, on sélectionne donc les chaînes en fonction de


leur qualité, de façon à ce que les chaînes les plus aptes
aient plus de chances d'être sélectionnées pour la
reproduction.

• Pour ce faire, on utilise en général l'une des deux


méthodes suivantes :
Sélection par troncation

• C'est une méthode simple consistant à sélectionner une


proportion f des meilleurs chaînes et à ignorer les autres.
Par exemple, on peut fixer f=0.5, ce qui signifie que les
50% de chaînes les meilleures sont sélectionnées pour
reproduction et choisies avec une probabilité égale.

• Cette méthode est facile à implémenter mais limite le


degré d'exploration effectué, ce qui a tendance à biaiser
l'AG en faveur d'une exploitation de la population.
Sélection proportionnelle
• Elle est considérée comme la meilleure approche en général.

• Elle consiste à sélectionner les chaînes de façon probabiliste,


la probabilité qu'une chaîne soit sélectionnée étant
proportionnelle à sa qualité.

• Pour une chaîne alpha, on retient en général la formule:

p_alpha=F_alpha/SOMME(F_beta:beta)

• où p_alpha désigne la probabilité que la chaîne alpha soit


sélectionnée et F_alpha sa qualité.
Sélection proportionnelle

• On comprend donc la condition de positivité imposée à la


fonction de qualité.

• Si on souhaite autoriser celle-ci à prendre des valeurs


négatives, on utilise plutôt la formule de Boltzmann :

p_alpha=exp(sF_alpha)/SOMME(exp(sF_beta):beta)

• où s est un paramètre appelé intensité de sélection.


Sélection proportionnelle
• Se pose toutefois un problème algorithmique : comment
échantillonner la population avec des probabilités inégales ?

• En effet, on dispose en général de fonctions permettant


d'échantillonner avec des probabilités égales (avec ou sans
remise); par example choice sous Python.

• L'astuce consiste à construire une population virtuelle où


chaque chaîne est répétée un nombre de fois proportionnel à
p_alpha (donc à sa qualité si la première formule est utilisée).

• On effectue alors un échantillonnage simple dans cette


population virtuelle.
Sélection proportionnelle

• Quelle que soit la façon dont on sélectionne les chaînes,


l'étape suivante consiste à les mettre en paires.

• Comme l'ordre dans lequel elles sont échantillonnées est


aléatoire, on peut simplement associer à chaque chaîne
d'indice pair la chaîne suivante d’indice impair pour
reproduction.
Opérateurs génétiques
• Ayant sélectionné les paires d'individus pour
reproduction, il reste à déterminer comment croiser les
chaînes de façon à générer leur descendance, ce qui
constitue la partie génétique de l'algorithme.

• On utilise en général deux opérateurs génétiques que l'on


va présenter dans la suite.

• Ce sont les opérateurs initialement proposés lors de


l'invention des algorithmes génétiques et ce sont encore
aujourd'hui, de loin, les plus utilisés.
Opérateur de croisement
• En biologie, les organismes ont deux chromosomes et
chaque parent transmet l’un d'entre eux.

• Dans le cadre des AG, les membres de la population ont


seulement l’équivalent d'un chromosome : la chaîne.

• On génère donc une nouvelle chaîne en prenant une partie


de la première et une partie de la deuxième.

• La façon la plus usuelle de le faire consiste à choisir au


hasard un point dans la chaîne et à utiliser la parent 1 pour la
première partie de la chaîne et le parent 2 pour la deuxième.
Opérateur de croisement
• On génère ensuite un deuxième descendant, composé de
la première partie du parent 2 et de la deuxième partie du
parent 1.

• Cette méthode est appelée méthode de croisement


basée sur un point; la généralisation à plusieurs points est
immédiate.

• Une version plus extrême est appelée croisement


uniforme et consiste à sélectionner aléatoirement
l'élément courant de la chaîne du descendant parmi les
éléments correspondants des deux parents.
Opérateur de croisement

• L'opérateur de croisement sert à effectuer une exploration


globale puisque les chaînes produites sont radicalement
différentes de leurs parents.

• On espère sélectionner les bonnes parties de chacune


des solutions de façon à obtenir une solution encore
meilleure.
Opérateur de mutation
• L'exploitation des chaînes courantes les plus aptes est
effectuée par l’opérateur de mutation qui implémente une
recherche locale aléatoire.

• La valeur de chaque élément de la chaîne est modifiée avec


une probabilité (généralement faible) p.

• Sur l'exemple du sac-à-dos, la mutation consiste à inverser


un bit.

• Souvent, on prend p=1/L où L désigne la longueur de la


chaîne, de façon à avoir en moyenne une mutation par
chaîne. Ce choix est validé par la pratique.
Elitisme, tournois et niches

• Un problème de l'Algorithme Génétique standard est que


la meilleure qualité sur la population peut croître comme
décroître à la génération suivante.

• C'est dû au fait que les meilleurs chaînes de la génération


courante ne sont pas recopiées à la génération suivante
et que parfois aucun de leurs descendants n'est aussi
apte.
Elitisme, tournois et niches

• Une façon d'éviter cela est appelée élitisme : cela


consiste à copier les meilleurs chaînes dans la population
suivante sans modification.

• Une autre solution est d'implémenter un tournoi ou les


deux parents et leurs deux descendants entrent en
compétition, les deux meilleurs parmi les quatre étant
intégrés à la nouvelle population.
Elitisme, tournois et niches
• Bien qu'élitisme et tournois permettent de garantir que les bonnes
solutions ne soient pas perdues, ils peuvent être à la source d'un
problème de convergence prématurée, l'algorithme se stabilisant sur
une population ne contenant pas l'optimum.

• Cela est dû au fait que, dans ce cas, l'AG favorise les membres les
plus aptes de la population, ce qui signifie que les optima locaux sont
favorisés et exploités.

• L'exploration n'est alors pas suffisante pour échapper à ces maxima


locaux.

• En fait, élitisme et tournois on pour effet de réduire la diversité de la


population en autorisant les mêmes individus à subsister sur plusieurs
générations.
Elitisme, tournois et niches

• Une solution au problème est la création de niches


(également appelées populations insulaires) : la
population est séparée en plusieurs sous-populations qui
évoluent indépendamment pendant un certain temps et
qui convergent donc vraisemblablement vers des optima
locaux différents.

• Au cours du temps, quelques éléments d'une sous-


population sont occasionnellement injectés dans une
autre sous-population.
Elitisme, tournois et niches

• Il existe d'autres approches permettant d'améliorer la


convergence et les résultats finaux des algorithmes
génétiques, mais il s'agit déjà de notions plus avancées.
Récapitulation
• L'Algorithme Génétique le plus simple est obtenu en combinant les
différents éléments que nous avons détaillés plus haut.

• Initialisation : Générer N chaînes aléatoires de longueur L dans


l'alphabet choisi

• Apprentissage : Répéter

• Créer une nouvelle population (initialement vide)

• Répéter :

• Sélectionner deux chaînes dans la population courante en se


basant sur la qualité
Récapitulation
• Les croiser pour produire deux nouvelles chaînes

• Faire muter les deux nouvelles chaînes

• Ajouter les deux nouvelles chaînes à la population où


utiliser élitisme ou tournois

• Garder trace de la meilleure chaîne dans la population …


jusqu'à génération de N nouvelles chaînes

• Remplacer la population courante par la nouvelle population

• … jusqu'à ce qu'un critère d'arrêt soit vérifié.


Implémentation sous
Python

• Un algorithme génétique est implémenté sous Python


pour le problème du sac à dos dans les programmes
[Link], [Link] et run_ga.py, disponibles sur le forum
et dûs à Stephen Marsland.

• L’implémentation retenue fait intervenir de la


programmation objet sous Python.
Apprentissage non
supervisé
Apprentissage non
supervisé

• L’apprentissage non supervisé inclut tous les types de


machine learning où il n’y a pas de réponse.

• En apprentissage non supervisé, on montre les inputs à


l’algorithme d’apprentissage qui en extrait des
connaissances.
Apprentissage non
supervisé
• On abordera dans la suite deux types d’apprentissage
non supervisé : les transformations des données et le
clustering.

• Les transformations non supervisées des données sont


des algorithmes qui créent une nouvelle représentation
des données, plus facile à comprendre pour les êtres
humains ou pour d’autres algorithmes de machine
learning.

• Une application usuelle des transformations non


supervisées est la réduction de dimension.
Apprentissage non
supervisé

• La réduction de dimension consiste à partir d’une


représentation des données en grande dimension, avec
un grand nombre de variables et à déterminer une
représentation qui résume les principales caractéristiques
des données avec moins de variables.

• Une application standard de la réduction de dimension


est la réduction à deux dimensions en vue de la
visualisation.
Apprentissage non
supervisé
• Une autre application des transformations non
supervisées consiste à déterminer des parties ou
composantes en lesquelles se décomposent les données.

• L’exemple typique est l’identification de thèmes sur des


collections de documents textes. Le but est d’identifier
les thèmes sur lesquels portent les différents textes et
ceux qui interviennent dans chaque texte. C’est utile pour
suivre l’évolution de l’opinion sur des réseaux sociaux par
exemple.
Apprentissage non
supervisé
• Le deuxième type d’algorithmes d’apprentissage non
supervisé qui sera abordé est celui des algorithmes de
clustering.

• Ici, le but est de partitionner les données dans des


groupes distincts d’items/d’individus similaires.

• Exemple : on charge des photos sur un réseau social. Afin


d’organiser les photos, le site peut essayer de regrouper
les photos qui montrent la même personne. Ceci peut
être fait à l’aide d’un algorithme de clustering.
Apprentissage non
supervisé
• Un des principaux défis de l’apprentissage non supervisé est
d’évaluer si l’algorithme a appris quelque chose d’utile.

• Les algorithmes d’apprentissage non supervisé sont usuellement


appliqués à des données non labellisées, donc on ne sait pas
quelle doit être la sortie correcte.

• Par conséquent, il est très difficile de savoir si un modèle a été


performant.

• Sur notre exemple des photos, l’algorithme utilisé peut par


exemple regrouper toutes les photos montrant quelqu’un de
profil et toutes les photos montrant quelqu’un de face. Cela peut
être intéressant mais ce n’est pas ce que l’on souhaite obtenir.
Apprentissage non
supervisé
• En fait, il n’y pas moyen de « dire » à l’algorithme ce que
l’on souhaite obtenir et, souvent, la seule façon d’évaluer
sa performance est d’examiner les résultats
manuellement.

• Ainsi, les algorithmes non supervisés sont souvent utilisés


dans le cadre d’une démarche exploratoire, c’est-à-dire
lorsqu’un data scientist cherche à mieux comprendre les
données, plutôt que comme partie de systèmes
automatiques d’inférence plus généraux.
Apprentissage non
supervisé

• Une autre application usuelle des algorithmes non


supervisés est le pré-traitement des données avant
d’utiliser un algorithme supervisé.

• Apprendre une nouvelle représentation des données peut


parfois améliorer la performance des algorithmes
supervisés ou réduire la quantité de mémoire et/ou le
temps de traitement requis pour obtenir le résultat.
Pré-traitement et mise à
l’échelle

• Certains algorithmes d’apprentissage (comme par


exemple, les réseaux de neurones et les SVM) sont très
sensibles à l’échelle des données.

• Par conséquent, il est standard d’ajuster les variables de


façon à obtenir une représentation des données plus
adaptée à ces algorithmes.

• Considérons un exemple (programme [Link]).


Pré-traitement et mise à
l’échelle
Pré-traitement et mise à
l’échelle

• Le premier graphique montre un jeu de données de


classification binaire bi-dimensionnel.

• La première variable prend ses valeurs entre 10 et 15, la


deuxième entre 1 et 9.

• Les quatre graphiques suivants montrent quatre manières


différentes de mettre les données à l’échelle de façon à
obtenir des intervalles de variation plus standards.
Pré-traitement et mise à
l’échelle

• Le StandardScaler de scikit-learn correspond à une


standardisation des données (la moyenne de chaque
variable transformée est égale à 0 et son écart-type à 1)
et ramène donc les variables à une même échelle.

• Toutefois, cette mise à l’échelle n’impose rien quant à la


valeur minimale ou maximale des variables.
Pré-traitement et mise à
l’échelle
• Le RobustScaler fonctionne de façon similaire au
StandardScaler, en ce qu’il utilise les propriétés
statistiques des données pour les ramener à une même
échelle.

• Toutefois, le RobustScaler utilise la médiane et les


quartiles au lieu de la moyenne et de l’écart-type.

• Cela a pour effet de lui faire ignorer les valeurs aberrantes


(ang. outliers), qui peuvent poser problème aux autres
méthodes de mise à l’échelle.
Pré-traitement et mise à
l’échelle

• Le MinMaxScaler, quant à lui, met les variables à l’échelle


de façon ce qu’elles varient entre 0 et 1.

• Dans le cas bi-dimensionnel, cela implique que toutes les


données mises à l’échelle sont concentrées dans le carré
[0,1]x[0,1].
Pré-traitement et mise à
l’échelle
• Enfin, le Normalizer effectue, quant à lui, une mise à
l’échelle très différente.

• En effet, il projette chaque vecteur de données sur le cercle


unité (ou l’hyper-sphère unité en grandes dimensions).

• Cela implique que chaque vecteur de donnée est mis à


l’échelle différemment (en le divisant par sa norme).

• Cette mise à l’échelle n’est utilisée que lorsque ce sont


seulement la direction ou les angles entre les vecteurs de
données qui intéressent le Data Scientist.
Exemple

• Ayant vu les différentes stratégies possibles de mise à


l’échelle, nous allons à présent les appliquer avec scikit-
learn.

• Nous allons utiliser le jeux de données cancer et mettre


les variables à l’échelles, avec une même mise à l’échelle
pour l’ensemble d’apprentissage et l’ensemble test.

• Programme : echelle_cancer.py.
Exemple
from [Link] import load_breast_cancer

from sklearn.model_selection import train_test_split

from [Link] import MinMaxScaler

cancer = load_breast_cancer()

X_train, X_test, y_train, y_test = train_test_split([Link],


[Link], random_state=1)
Exemple

scaler = MinMaxScaler()

[Link](X_train)

X_train_scaled = [Link](X_train)

X_test_scaled = [Link](X_test)
Exemple

• Pour le MinMaxScaler, la méthode fit calcule le minimum


et le maximum de chaque variable du tableau de données
passé en argument.

• La méthode transform applique la mise à l’échelle


correspondante au tableau de données passé en
argument.
Effet de la mise à l’échelle

• Afin d’évaluer l’effet que peut avoir une mise à l’échelle


sur la performance d’un algorithme, reprenons les
données cancer et effectuons la classification avec
l’algorithme SVC (Support Vector Classification).

• Programme : echelle_cancer.py.
Effet de la mise à l’échelle

from [Link] import SVC

svm = SVC(C=100)

[Link](X_train,y_train)

print("Test set accuracy: {:.2f}".format([Link](X_test,y_test)))


Effet de la mise à l’échelle

• Résultat:

Test set accuracy: 0.62

• A présent, lançons l’algorithme sur les données mises à


l’échelle :
Effet de la mise à l’échelle

[Link](X_train_scaled, y_train)

print("Scaled test set accuracy: {:.


2f}".format([Link](X_test_scaled,y_test)))

• Résultat :

Scaled test set accuracy: 0.97


Effet de la mise à l’échelle

• On voit ainsi que l’effet de la mise à l’échelle peut être


très important.

• Il ne faut donc pas hésiter à essayer une mise à l’échelle


des données avant d’utiliser un ou des algorithmes
d’apprentissage.

• On privilégiera à cette fin les méthodes mises à


disposition par scikit-learn.
Analyse en Composantes
Principales
ACP

• L’analyse en composantes principales est une méthode


qui effectue une rotation des données de façon à ce que
les variables résultantes soient statistiquement non
corrélées.

• Cette rotation est souvent suivie de la sélection d’un


sous-ensemble des nouvelles variables, selon leur
pertinence pour expliquer les données.
ACP

• Le graphique suivant illustre le fonctionnement de l’ACP


sur des données simulées (programme
pca_illustration.py).
ACP
ACP
• Le premier graphique montre le nuage de point initial. Le
point sont colorés de façon à pouvoir être distingués.

• L’algorithme commence par déterminer la direction de


variance maximale (appelée composante 1).

• C’est la direction qui contient le plus d’information, i.e.


celle où les variables initiales sont le plus corrélées les
unes avec les autres.

• Ensuite, l’algorithme détermine la direction, orthogonale à


la première, qui contient le plus d’information.
ACP

• En deux dimensions, il n’y a qu’une seule direction


possible à angle droit, mais en dimensions supérieures il y
en a une infinité.

• Les directions ainsi déterminées sont appelées


composantes principales.

• En général, il y en autant que de variables initiales.


ACP

• Le deuxième graphique représente les mêmes données


mais après une rotation, de façon à ce que la première
composante coïncide avec l’axe des abscisses et la
deuxième avec l’axe des ordonnées.

• Avant d’effectuer la rotation, la moyenne est soustraite


aux données, de façon à ce que les données
transformées soient centrées sur zéro.
ACP
• On peut utiliser l’ACP pour réduire la dimension en ne
retenant que certaines composantes principales.

• Dans l’exemple précédent, on pourrait ne retenir que la


première composante, ce qui réduit la dimension à 1.

• Finalement, on peut inverser la rotation et rajouter la


moyenne aux données. Les points obtenus sont dans
l’espace des variables initiales mais on n’a conservé que
l’information contenue dans la première composante.

• Cette transformation est notamment utilisée pour débruiter


des données.
ACP
• Nous allons a présent appliquer l’ACP aux données
cancer en vue de les visualiser.

• C’est une des utilisations les plus fréquentes de l’ACP.

• Pour les données iris, nous avions pu obtenir une


visualisation en représentant les nuages de points croisés
des variables prises deux à deux, mais pour les données
cancer, il y a 30 variables, ce qui ferait 30*14 = 420
nuages de points croisés ! il n’est pas possible
d’examiner ces nuages de points simultanément, encore
moins de les comprendre.
ACP

• Nous pouvons toutefois utiliser une visualisation plus


simple : représenter les histogrammes de chaque variable
pour les deux classes, tumeurs bénignes et malignes.

• C’est fait à l’aide du programme cancer_histo.py.

• Le résultat est le suivant :


ACP
ACP
• Le graphique précédent peut nous donner une idée des
variables les plus pertinentes pour distinguer tumeurs
bénignes et malignes (une variable est pertinente si les
histogrammes représentés sont suffisamment disjoints).

• Toutefois, ce graphique ne nous dit rien sur les


interactions entre variables et leurs liens avec les deux
classes.

• L’ACP permet de rendre compte des interactions et


fournit une représentation graphique plus complète.
ACP

• La représentation graphique des données dans le plan


formé des deux première composantes principales est la
suivante (programme cancer_PCA.py) :
ACP
ACP
• Il est important de noter que l’ACP est une méthode non
supervisée et qu’elle n’utilise pas l’information sur les
classes lorsqu’elle détermine la rotation recherchée.

• Elle prend en compte simplement les corrélations entre


les variables.

• On voit toutefois que les deux classes sont bien séparées


dans l’espace obtenu. En fait, un simple classificateur
linéaire dans cet espace pourrait les distinguer de façon
satisfaisante.
ACP

• Un inconvénient de l’ACP est que les axes obtenus ne


sont pas toujours faciles à interpréter.

• Les composantes principales sont des combinaisons


linéaires des variables initiales. Toutefois, ces
combinaisons sont généralement très complexes.

• Pour plus de détails, on pourra consulter le cours de Data


Mining et Analyse des Données.
Clustering par k-
moyennes
k-means

• Le clustering consiste à partitionner les données en


groupes, appelés clusters ou classes, tels que les
individus appartenant à une même classe soient similaires
et ceux appartenants à des classes différentes soient
différents.

• De façon analogue aux algorithmes de classification, les


algorithmes de clustering associent un nombre à chaque
individu indiquant le cluster auquel il appartient.
k-means
• Le clustering par k-moyennes est un des algorithmes de
clustering les plus simples et les plus utilisés.

• Il procède en déterminant des centres de classes représentatifs


de certaines régions de l’espace des données.

• L’algorithme alterne entre deux étapes : 1) affecter chaque


individu à la classe du centre le plus proche, puis 2) recalculer le
centre comme la moyenne des points qui lui sont associés.
L’algorithme s’arrête lorsque l’affectation des individus aux
classes se stabilise.

• Illustration sur des données simulées (programme


kmeans_exemple.py) :
k-means
k-means
• Les centres de classes sont représentés par des triangles
alors que les points de données sont représentés par des
cercles.

• Les couleurs indiquent l’appartenance aux classes.

• On a spécifié qu’on voulait trois classes. L’algorithme est


donc initialisé en choisissant trois points au hasard
comme entres de classes. Puis les étapes itératives sont
lancées. En trois itérations, les classes se stabilisent et
l’algorithme s’arrête.
k-means

• Etant donné un nouveau point, l’algorithme k-means


l’affecte à la classe de centre le plus proche.

• Le graphique suivant représente les frontières des classes


apprises précédemment (programme
kmeans_exemple.py) :
k-means
k-means

• Utiliser k-means avec scikit-learn est assez immédiat.

• On instancie la classe KMeans en fixant le nombre de


classes souhaité, puis on lance la méthode fit avec les
données (programme [Link]) :
k-means
from [Link] import make_blobs

from [Link] import KMeans

X, y = make_blobs(random_state=1)

kmeans = KMeans(n_clusters=3)

[Link](X)

print("Cluster memberships:\n{}".format(kmeans.labels_))

print([Link](X))
k-means

• kmeans.labels_ contient les affectations des individus aux


différentes classes.

• La méthode predict permet de déterminer l’affectation


d’un nouvel individu à une classe déterminée par
l’algorithme.

• Pour plus de détails sur les k-means, voir le cours de Data


Mining et Analyse des Données.
Clustering agglomératif
(CAH)
CAH
• Le clustering agglomératif ou classification ascendante
hiérarchique est une famille d’algorithmes conçus selon le
même principe : on commence par considérer que
chaque point constitue une classe à part entière puis on
fusionne les deux classes les plus similaires, jusqu’à ce
qu’un critère d’arrêt soit vérifié.

• Le critère d’arrêt spécifié dans scikit-learn est le nombre


de clusters; ainsi les classes similaires sont fusionnées
jusqu’à ce que le nombre spécifié de classes soit atteint.
CAH
• Il y a divers critères de « linkage » qui définissent la notion
de similarité entre classes retenue par l’algorithme.

• Les trois possibilités suivantes sont offertes par scikit-


learn :

• ward : c’est le choix par défaut. Ce choix de linkage


conduit souvent à des classes d’effectif comparable.

• average

• complete (ou maximum)


CAH

• On utilisera dans la suite la méthode de ward, qui est la


plus utilisée en pratique.

• Le graphique suivant illustre la progression de la CAH sur


un jeu de données bi-dimensionnel, pour lequel on
cherche un clustering en 3 classes (programme
cah_exemple.py) :
CAH
CAH
• Initialement, chaque point constitue une classe. Puis, à
chaque étape, les deux classes les plus proches sont
fusionnées. A l’étape 9, on n’a plus que 3 classes. Comme
c’est le nombre de classes spécifié, l’algorithme s’arrête.

• Examinons la façon dont la CAH fonctionne sur des


données simulées réparties en 3 classes.

• En raison de la structure de l’algorithme, la CAH ne dispose


pas d’une méthode predict pour affecter de nouveaux
points aux classes. Si on veut obtenir l’affectation des
individus aux classes, il faut utiliser la méthode fit_predict
(programme [Link]).
CAH
CAH
• La CAH retrouve les classes de façon parfaite.

• Alors que l’implémentation sous scikit-learn de la CAH


requiert de spécifier le nombre de clusters a priori, nous
allons voir à présent comment choisir ce nombre sous
Python.

• Pour cela, nous allons montrer comment représenter une


CAH à l’aide d’un dendrogramme sous Python.

• C’est fait à l’aide de l’implémentation de la CAH sous


SciPy (programme cah_scipy.py).
CAH
from [Link] import make_blobs

from [Link] import dendrogram, ward

import [Link] as plt

X, y = make_blobs(random_state=0, n_samples=12)

linkage_array=ward(X)

dendrogram(linkage_array)
CAH
ax = [Link]()

bounds = ax.get_xbound()

[Link](bounds,[7.25,7.25], '--', c='k')

[Link](bounds,[4,4],'--',c='k')

[Link](bounds[1], 7.25, ' two clusters', va='center', fontdict={'size':


15})

[Link](bounds[1], 4, ' three clusters', va='center', fontdict={'size': 15})

[Link]("Sample index")

[Link]("Cluster distance")
CAH
CAH

• Pour davantage d’information sur la CAH, voir le cours de


Data Mining et Analyse des Données.

• Une autre méthode de clustering très utilisée en pratique


est la méthode DBSCAN, disponible sous scikit-learn.

• Elle n’a pas les inconvénients des k-means et de la CAH,


qui peuvent échouer à identifier les classes lorsque
celles-ci ont des formes complexes.
Introduction à la
Programmation Orientée
Objet sous Python
Python objet

• Les classes sont les principaux outils de la


programmation orientée objet.

• Ce type de programmation permet de structurer des


logiciels complexes en les organisant comme des
ensembles d’objets qui interagissent, entre eux et avec le
monde extérieur.
Python objet
• Le premier bénéfice de cette approche de la
programmation réside dans le fait que les différents objets
utilisés peuvent être construits indépendamment les uns
des autres sans qu’il n’y ait de risque d’interférence.

• Ce résultat est obtenu grâce au concept d’encapsulation :


la fonctionnalité interne de l’objet et les variables qu’il
utilise pour effectuer son travail sont en quelque sorte
enfermées dans l’objet. Les autres objets et le monde
extérieur ne peuvent y avoir accès qu’à travers des
procédures bien définies : l’interface de l’objet.
Python objet

• En particulier, l’utilisation de classes dans les


programmes permet - entre autres avantages - d’éviter au
maximum l’emploi de variables globales.

• En effet, l’utilisation de variables globales comporte des


risques, d’autant plus importants que les programmes
sont volumineux, parce qu’il est toujours possible que de
telles variables soient modifiées ou redéfinies n’importe
où dans le corps du programme.
Python objet

• Un deuxième bénéfice résultant de l’utilisation des


classes est la possibilité qu’elles offrent de construire de
nouveaux objets à partir d’objets préexistants, et donc de
réutiliser des pans entiers d’une programmation déjà
écrite pour en tirer une fonctionnalité nouvelle.

• C’est rendu possible grâce aux concepts de dérivation et


de polymorphisme.
Python objet

• La dérivation est le mécanisme qui permet de construire


une classe « enfant » à partir d’une classe « parente ».

• L’enfant ainsi obtenu hérite de toutes les propriétés et de


toute les fonctionnalités de son ancêtre, auxquelles on
peut ajouter ce que l’on veut.
Python objet
• Le polymorphisme permet d’attribuer des comportements
différents à des objets dérivant les uns des autres, ou au
même objet en fonction d’un certain contexte.

• Signalons que la programmation orientée objet (POO) est


optionnelle sous Python.

• On peut mener à bien de nombreux projets sans l’utiliser,


en se limitant par exemple à des fonctions. Toutefois, la
maîtriser permet de traiter des problèmes plus
complexes.
Définition d’une classe
élémentaire
• Pour créer une nouvelle classe d’objets Python, on utilise
l’instruction class.

• Pour l’illustrer nous allons commencer par définir un type


d’objet très rudimentaire, qui sera simplement un
nouveau type de donnée : le type point.

• Ce type correspond au concept de point en géométrie


plane. Dans le plan, un point est caractérisé par deux
nombres (son abscisse et son ordonnée).
Définition d’une classe
élémentaire
• En notation mathématique, on représente un point par
ses deux coordonnées x et y mises entre parenthèses.

• On parlera par exemple du point (25, 17).

• Une manière naturelle de représenter un point sous


Python serait d’utiliser pour les coordonnées deux valeurs
de type float. On veut cependant combiner ces deux
valeurs dans une seule entité ou un seul objet. Pour y
arriver on va définir une classe Point( ).
Définition d’une classe
élémentaire
class Point(object):

"Définition d'un point géométrique"

• Les définitions de classes peuvent être situées n’importe où dans


un programme, mais on les place généralement au début.

• Le double point est obligatoire à la fin de la ligne après


l’instruction class, ainsi que l’indentation du bloc d’instructions
qui suit.

• Ce bloc doit contenir au moins une ligne : par exemple, une


chaîne de caractère décrivant la classe, qui sera
automatiquement incorporée dans la documentation de Python.
Définition d’une classe
élémentaire
• Les parenthèses sont destinées à contenir la référence
d’une classe préexistante. C’est requis pour permettre le
mécanisme d’héritage.

• Lorsqu’on souhaite créer une classe fondamentale -


c’est-à-dire ne dérivant d’aucune autre - la référence à
indiquer est par convention le nom spécial object, qui
désigne l’ancêtre de toutes les classes.

• Une convention très répandue consiste à donner aux


classes des noms commençant par une majuscule.
Définition d’une classe
élémentaire

• On vient de définir une classe Point( ). On peut à présent


s’en servir pour créer des objets de cette classe, que l’on
appellera également des instances de cette classe.

• L’opération s’appelle pour cette raison une instanciation.

• Exemple :

p9 = Point( )
Définition d’une classe
élémentaire

• Après cette instruction, la variable p9 contient la


référence d’un nouvel objet Point( ). On dit également que
p9 est une nouvelle instance de la classe Point( ).

• Mais, à ce stade que peut-on faire avec l’objet p9 ?

• Essayons l’instruction suivante :

print(p9)
Définition d’une classe
élémentaire

• Résultat :

<__main__.Point object at 0x1a1a549748>

• Le message renvoyé par Python indique que p9 est une


instance de la classe Point( ), laquelle est définie au
niveau principal (main) du programme. Elle est situé dans
un emplacement déterminé de la mémoire vive, dont
l’adresse est fournie en notation hexadécimale.
Définition d’une classe
élémentaire

• Essayons à présent l’instruction suivante :

print(p9.__doc__)

• Résultat :

Définition d'un point géométrique


Définition d’une classe
élémentaire

• Les chaîne de documentation de divers objets sous


Python sont associées à l’attribut prédéfini __doc__.

• Il est donc toujours possible de retrouver la


documentation associée à un objet Python quelconque en
invoquant cet attribut.
Attributs (ou variables)
d’instance

• L’objet qu’on vient de créer est une coquille vide.

• On va lui ajouter des composants, par simple


assignation :

p9.x = 3.0

p9.y = 4.0
Attributs (ou variables)
d’instance

• Les variables x et y qu’on a définies en les liant d’emblée


à p9, sont désormais des attributs de l’objet p9.

• On les appelle également des variables d’instance.

• Elles sont en effet encapsulées dans cette instance (ou


objet).
Attributs (ou variables)
d’instance

• On peut utiliser les attributs d’un objet dans n’importe


quelle expression exactement comme des variables
ordinaires :

print(p9.x)

print(p9.x**2+p9.y**2)
Attributs (ou variables)
d’instance
• Résultat :

3.0

25.0

• Du fait de leur encapsulation dans l’objet, les attributs sont


des variables distinctes d’autres variables qui pourraient
porter le même nom.

• Par exemple, l’instruction :

x = p9.x
Attributs (ou variables)
d’instance

• signifie : extraire de l’objet référence par p9 la valeur de


son attribut x et assigner cette valeur à la variable x.

• Il n’y a pas de conflit entre la variable indépendante x et


l’attribut x de p9.

• L’objet p9 contient son propre espace de noms,


indépendant de l’espace de noms principal où se trouve
la variable x.
Attributs (ou variables)
d’instance

• Notons que, s’il et très aisé d’ajouter un attribut à un objet


en utilisant une simple instruction d’assignation, ce n’est
pas la façon recommandée de procéder. Cette dernière
sera développée dans la suite.
Passage d’objets comme
arguments d’une fonction

• Les fonctions peuvent utiliser des objets comme


paramètres et elles peuvent fournir un objet comme valeur
de retour.

• Exemple :

def affiche_point(p):

print("Abscisse = ", p.x, "Ordonnée = ", p.y)


Passage d’objets comme
arguments d’une fonction
• Le paramètre p utilisé par cette fonction doit être un objet de type
Point( ), dont l’instruction qui suit utilise les variables d’instance
p.x et p.y.

• Lorsqu’on appelle cette fonction, on doit lui fournir un objet de


classe Point( ) comme argument.

• Exemple :

affiche_point(p9)

• Résultat :

Abscisse = 3.0 Ordonnée = 4.0


Similitude et unicité
• Dans la langue parlée, les mêmes mots peuvent avoir des
signification différentes selon le contexte dans lequel on
les utilise.

• La conséquence en est que certaines expressions


utilisant ces mots peuvent être comprises de plusieurs
façon différentes (expressions ambiguës).

• Exemple : le mot « même » a des significations différentes


dans les phrases « Charles et moi avons la même
voiture » et « Charles et moi avons la même mère ».
Similitude et unicité
• Lorsqu’on traite d’objets logiciels, on peut rencontrer la
même ambiguïté.

• Si nous parlons de l’égalité de deux objets Point( ), cela


signifie-t-il que ces deux objets contiennent les mêmes
données (leurs attributs), ou bien cela signifie-t-il que
nous parlons de deux références à un même et unique
objet ?

• Exemple :
Similitude et unicité
p1 = Point()

p1.x = 3

p1.y = 4

p2 = Point()

p2.x = 3

p2.y = 4

print(p1 == p2)
Similitude et unicité

• Résultat :

False

• Les instruction précédentes créent deux objets p1 et p2


qui restent distincts, même s’ils font partie d’une même
classe et on des contenus similaires.

• On peut confirmer cela d’une autre manière :


Similitude et unicité
print(p1)

• Résultat :

<__main__.Point object at 0x1810424a58>

print(p2)

• Résultat :

<__main__.Point object at 0x1810424a20>


Similitude et unicité
• Les deux variables p1 et p2 référencent bien des objets
différents, mémorisés à des emplacements différents dans la
mémoire de l’ordinateur.

• Essayons autre chose à présent :

p2 = p1

print(p1 == p2)

• Résultat :

True
Similitude et unicité
• Par l’instruction p2 = p1, nous assignons le contenu de
p1 à p2.

• Cela signifie que désormais ces deux variables


référencent le même objet.

• On dit que les variables p1 et p2 sont des alias l’une de


l’autre.

• Lorsqu’on modifie un attribut de p1, l’attribut


correspondant de p2 change lui aussi :
Similitude et unicité
p1.x = 7

print(p2.x)

• Résultat :

• Les deux références p1 et p2 pointent vers le même


emplacement dans la mémoire :
Similitude et unicité
print(p1)

• Résultat :

<__main__.Point object at 0x1810424a58>

print(p2)

• Résultat :

<__main__.Point object at 0x1810424a58>


Objets composés d’objets

• Supposons qu’on veuille définir une classe qui serve à


représenter les rectangles.

• Pour simplifier, on suppose que ces rectangles sont


toujours orientés horizontalement ou verticalement et
jamais en oblique.

• De quelles informations avons-nous besoin pour définir


de tels rectangles ?
Objets composés d’objets
• Il existe plusieurs possibilités.

• On peut par exemple spécifier la position du centre du


rectangle (deux coordonnées) et préciser sa taille (largeur
et hauteur).

• On peut aussi spécifier la position du coin supérieur


gauche et du coin inférieur droit, ou la position du coin
supérieur gauche et la taille.

• On retient cette dernière possibilité.


Objets composés d’objets
• Définissons notre nouvelle classe :

class Rectangle(object):

"définition d'une classe de rectangles"

• On crée une instance de la façon suivante :

boite = Rectangle()

[Link] = 50.0

[Link] = 35.0
Objets composés d’objets

• On crée ainsi un nouvel objet Rectangle( ) et on lui donne


deux attributs.

• Pour specifier le coin supérieur gauche, on utilise à


présent une instance de la classe Point( ) que nous avons
définie précédemment.

• Ainsi, on définit un objet à l’intérieur d’un autre objet :


Objets composés d’objets

[Link] = Point()

[Link].x = 12.0

[Link].y = 27.0

• La première instruction permet de créer un nouvel attribut


coin pour l’objet boite.
Objets composés d’objets

• Ensuite, pour accéder à cet objet, qui se trouve lui-même


à l’intérieur d’un autre objet, on utilise la qualification des
noms hiérarchisée à l’aide de points.

• Ainsi, l’expression [Link].y signifie « aller à l’objet


référencé dans la variable boite. Dans cet objet, repérer
l’attribut coin, puis aller à l’objet référencé dans cet
attribut. Une fois cet objet trouvé, sélectionner son
attribut y. »
Objets composés d’objets

• Le nom boite se trouve dans l’espace de noms principal.

• Il référence un autre espace de noms réservé à l’objet


correspondant, dans lequel sont mémorisés les noms
largeur, hauteur et coin.

• Ceux-ci référencent à leur tour soit d’autres espaces de


noms (c’est le cas de coin), soit des valeurs bien
déterminées, lesquelles sont mémorisées ailleurs.
Objets composés d’objets

• Python réserve des espaces de noms différents pour


chaque module, chaque classe, chaque instance, chaque
fonction.

• On peut tirer parti de tous ces espaces de noms bien


compartimentés afin de réaliser des programmes
robustes, c’est-à-dire dont les différentes composantes
n’interfèrent pas facilement.
Objets comme valeurs de
retour d’une fonction

• Les fonctions peuvent transmettre une instance comme


valeur de retour.

• Par exemple, la fonction trouveCentre( ) ci-après doit être


appelée avec un argument de type Rectangle( ) et elle
renvoie un objet de type Point( ), contenant les
coordonnées du centre du rectangle :
Objets comme valeurs de
retour d’une fonction

def trouveCentre(box):

p = Point()

p.x = [Link].x + [Link]/2.0

p.y = [Link].y - [Link]/2.0

return p
Objets comme valeurs de
retour d’une fonction
• On peut par exemple appeler cette fonction en utilisant
comme argument l’objet boite défini précédemment :

centre = trouveCentre(boite)

print(centre.x, centre.y)

• Résultat :

37.0 44.5
Modification des objets
• On peut modifier les propriétés d’un objet en assignant de
nouvelles valeurs à ses attributs.

• Par exemple, on peut modifier la taille d’un rectangle


(sans modifier sa position) en réassignant ses attributs
hauteur et largeur :

[Link] = [Link] + 20

[Link] = [Link] - 5
Modification des objets
• On peut faire cela sous Python car dans ce langage les
propriétés des objets sont toujours publiques.

• D’autres langages établissent une distinction nette entre


attributs publics (accessibles de l’extérieur de l’objet) et
attributs privés (accessibles seulement aux algorithmes
inclus dans l’objet lui-même).

• Cependant, comme on l’a déjà signalé, modifier les


attributs d’un objet par assignation simple, depuis
l’extérieur de l’objet, n’est pas une pratique
recommandable.
Modification des objets

• En effet, elle contredit un des objectifs fondamentaux de


la programmation objet, qui vise à établir une séparation
stricte entre la fonctionnalité d’un objet (telle que déclarée
au monde extérieur) et la manière dont cette
fonctionnalité est réellement implémentée dans l’objet (et
que le monde extérieur n’a pas à connaître).

• Concrètement, cela signifie qu’on va maintenant étudier


comment faire fonctionner les objets à l’aide d’outils
vraiment appropriés, qu’on appellera des méthodes.
Modification des objets

• Lorsqu’on aura bien compris le maniement de celles-ci,


on se fixera pour règle de ne plus modifier les attributs
d’un objet par assignation directe depuis l’extérieur,
comme on l’a fait jusqu’à présent.

• On veillera au contraire à utiliser pour cela des méthodes


mises en place spécifiquement dans ce but.

• L’ensemble de ces méthodes constituera ce que nous


appellerons l’interface de l’objet.
Classes, méthodes,
héritage
POO

• L’idée de base de la programmation orientée objet


consiste à regrouper dans un même ensemble à la fois un
certain nombre de données (les attributs d’instance) et les
algorithmes destinés à effectuer divers traitements sur
ces données (les méthodes, i.e. des fonctions
particulières encapsulées dans l’objet).

• Objet = (attributs + méthodes)


POO

• Cette façon d’associer dans une même capsule les


propriétés d’un objet et les fonctions qui permettent
d’agir sur elles correspond chez les programmeurs à une
volonté de construire des entités informatiques dont le
comportement se rapproche du comportement des objets
du monde qui nous entoure.

• Considérons par exemple un widget « bouton » dans une


application graphique.
POO

• Il nous paraît raisonnable de souhaiter que l’objet


informatique que nous appelons « bouton » ait un
comportement qui ressemble à celui d’un bouton
d’appareil quelconque dans le monde réel.

• Or la fonctionnalité d’un bouton réel (sa capacité de


fermer ou d’ouvrir un circuit électrique) est intégrée dans
l’objet lui-même, au même titre que des propriétés telles
que sa taille sa couleur, etc.
POO

• De la même manière, on souhaite que les différentes


caractéristiques du bouton logiciel (sa taille son
emplacement, sa couleur, le texte associé) mais aussi la
définition de ce qui se passe lorsqu’on effectue diverses
actions de la souris sur ce bouton, soient regroupés dans
une entité bien précise à l’intérieur du programme, pour
qu’il n’y ait pas de confusion entre ce bouton et un autre,
ou entre ce bouton et d’autres entités.
Méthodes

• Pour illustrer notre propos nous allons définir une nouvelle


classe Time( ) qui doit nous permettre d’effectuer diverses
opération sur des instants, des durées, etc.

class Time(object):

"Définition d'objets temporels"


Méthodes
• Créons à présent un objet de ce type et ajoutons-lui des
variables d’instance pour mémoriser les heures, minutes
et secondes :

instant = Time()

[Link] = 11

[Link] = 34

[Link] = 25
Méthodes

• Définissons une fonction affiche_heure( ) permettant de


visualiser le contenu d’un objet de classe Time( ) sous la
forme conventionnelle « heures:minutes:secondes » et
appliquons-là à l’objet instant créé précédemment :

def affiche_heure(t):

print(str([Link])+":"+str([Link])+":"+str([Link]))

affiche_heure(instant)
Méthodes
• Résultat :

11:34:25

• Si on doit utiliser fréquemment des objets de la classe


Time( ), la fonction affiche_heure devient très utile.

• Il est donc souhaitable d’encapsuler cette fonction dans


la classe Time( ), de manière à s’assurer qu’elle est
toujours disponible dès lors qu’on manipule des objets de
cette classe.
Méthodes

• Une fonction encapsulée dans une classe s’appelle une


méthode.

• On définit une méthode comme on définit une fonction,


c’est-à-dire en écrivant un bloc d’instructions à la suite du
mot réservé def, mais avec deux différences :
Méthodes

• La définition d’une méthode est toujours placée à


l’intérieur de la définition d’une classe, de manière à ce
que la relation qui lie méthode et classe soit clairement
établie.

• La définition d’une méthode doit toujours comporter au


moins un paramètre, qui doit être une référence
d’instance et ce paramètre particulier doit toujours être
listé en premier.
Méthodes
• On peut en principe utiliser un nom de variable
quelconque pour ce premier paramètre mais il est
vivement conseillé de respecter la convention qui
consiste à lui donner le nom : self.

• Ce paramètre self est nécessaire parce qu’il faut pouvoir


désigner l’instance à laquelle la méthode est associée
dans les instructions faisant partie de la définition.

• Exemple :
Méthodes

class Time(object):

"Nouvelle classe temporelle"

def affiche_heure(self):

print("{0}:{1}:{2}".format([Link],[Link],[Link]))
Méthodes
• On dispose donc à présent d’une classe Time( ), dotée
d’une méthode affiche_heure( ).

• Exemple :

maintenant = Time()

maintenant.affiche_heure()

• Résultat :

AttributeError: 'Time' object has no attribute 'heure'


Méthodes
• L’erreur est normale : on a pas créé les attributs
d’instance.

[Link] = 23

[Link] = 34

[Link] = 21

maintenant.affiche_heure()
Méthodes

• Résultat :

23:34:21

• L’erreur que nous avons rencontrée peut-elle être évitée ?


Constructeur

• Elle ne se serait pas produite si on s’était arrangé pour


que la méthode affiche_heure( ) puisse toujours afficher
quelque chose, sans qu’il soit nécessaire d’effectuer au
préalable une manipulation sur l’objet nouvellement créé.

• En d’autres termes, il faudrait que les variables


d’instances soient prédéfinies elles aussi à l’intérieur de la
classe, avec pour chacune d’elles une valeur par défaut.
Constructeur

• Pour obtenir cela, on fait appel à une méthode


particulière, qu’on appelle constructeur.

• La méthode constructeur a ceci de particulier qu’elle est


exécutée automatiquement lorsqu’on instancie un nouvel
objet à partir de la classe.

• On peut donc y placer tout ce qui semble nécessaire pour


initialiser automatiquement l’objet que l’on crée.
Constructeur

• Afin qu’elle soit reconnue comme telle par Python, la


méthode constructeur doit obligatoirement s’appeler
__init__ .

• Exemple :
Constructeur
class Time(object):

"Encore une nouvelle classe temporelle"

def __init__(self):

[Link] = 12

[Link] = 0

[Link] = 0

def affiche_heure(self):

print("{0}:{1}:{2}".format([Link],[Link],[Link]))

tstart = Time()

tstart.affiche_heure()
Constructeur
• Résultat :

12:0:0

• On n’a pas d’erreur cette fois.

• Comme toute méthode, la méthode __init__ peut être dotée de


paramètres.

• Dans le cas de cette méthode particulière qu’est le


constructeur, les paramètres peuvent jouer un rôle très
intéressant car ils permettent d’initialiser certaines des variables
d’instances au moment même de la création de l’objet.
Constructeur
• Exemple :

class Time(object):

"Encore une nouvelle classe temporelle"

def __init__(self, hh = 12, mm = 0, ss = 0):

[Link] = hh

[Link] = mm

[Link] = ss

def affiche_heure(self):

print("{0}:{1}:{2}".format([Link],[Link],[Link]))
Constructeur
• La nouvelle méthode __init__ comporte trois paramètres,
ayant chacun une valeur par défaut.

• On obtient ainsi une classe encore plus perfectionnée.

• Lorsqu’on instancie un objet de cette classe, on peut


initialiser ses principaux attributs à l’aide d’arguments, au
sein même de l’instruction d’instanciation.

• Si on omet tout ou partie d’entre eux, les attributs


reçoivent de toute façon des valeurs par défaut.
Constructeur

• Exemple :

recreation = Time(10,15,18)

recreation.affiche_heure()

• Résultat :

10:15:18
Constructeur

• Exemple :

rentree = Time(10,30)

rentree.affiche_heure()

• Résultat :

10:30:0
Constructeur

• Exemple :

rdv = Time(hh = 18)

rdv.affiche_heure()

• Résultat :

18:0:0
Espaces de noms
• Les variables définies à l’intérieur d’une fonction sont des
variables locales, inaccessibles aux instructions qui se
trouvent à l’extérieur de la fonction.

• Cela permet d’utiliser les mêmes noms de variables dans


différentes parties d’un programme, sans risque
d’interférence.

• En d’autres termes, chaque fonction possède son propre


espace de noms, indépendant de l’espace de noms
principal.
Espaces de noms
• Les instructions se trouvant à l’intérieur d’une fonction
peuvent accéder aux variables définies au niveau
principal, mais en consultation seulement : elles peuvent
utiliser les valeurs de ces variables, mais pas les modifier,
à moins de faire appel à l’instruction global.

• Il existe donc une sorte de hiérarchie entre les espaces de


noms.

• Il en va de même à propos des classes et des objets.


Espaces de noms

• Chaque classe possède son propre espace de noms. Les


variables qui en font partie sont appelées variables de
classe ou attributs de classe.

• Chaque objet instance (créé à partir d’une classe) obtient


son propre espace de noms. Les variables qui en font
partie sont appelées variables d’instance ou attributs
d’instance.
Espaces de noms

• Les classes peuvent utiliser, mais pas modifier, les


variables définies au niveau principal.

• Les instances peuvent utiliser, mais pas modifier, les


variables définies au niveau de la classe et les variables
définies au niveau principal.
Espaces de noms
• Exemple :

class Espaces(object):

aa = 33

def affiche(self):

print(aa, [Link], [Link])

aa = 12

essai = Espaces()

[Link] = 67

[Link]()

print(aa, [Link], [Link])


Espaces de noms
• Résultat :

12 33 67

12 33 67

• Dans cet exemple, le même nom aa est utilisé pour définir


trois variables différentes : une dans l’espace de noms de
la classe, une autre dans l’espace de noms principal, et
enfin une dernière dans l’espace de noms de l’instance.
Héritage
• Les classes constituent le principal outil de la
programmation orientée objet, qui est considérée
aujourd’hui comme la technique de programmation la plus
performante.

• L’un des principaux atouts de ce type de programmation est


que l’on peut se servir d’une classe préexistante pour en
créer une nouvelle, qui hérite de toutes ses propriétés mais
peut modifier certaines d’entre elles et/ou y ajouter les
siennes propres.

• Le procédé s’appelle dérivation. Il permet de créer une


hiérarchie de classes, allant du général au particulier.
Héritage

• On peut par exemple définir une classe Mammifere( ) qui


contient un ensemble de caractéristiques propres à ce
type d’animal.

• A partir de cette classe parente, nous pouvons dériver


une ou plusieurs classes filles, comme : une classe
Primate( ), une classe Rongeur( ), une classe Carnivore( ),
etc, qui héritent de toutes les caractéristiques de la classe
Mammifere( ), en y ajoutant leurs spécificités.
Héritage

• A partir de la classe Carnivore( ), nous pouvons ensuite


dériver une classe Belette( ), une classe Loup( ), une
classe Chien( ), etc, qui héritent encore une fois de toutes
les caractéristiques de la classe parente avant d’y ajouter
les leurs.

• Exemple :
Héritage
class Mammifere(object):

caract1 = "il allaite ses petits ;"

class Carnivore(Mammifere):

caract2 = "il se nourrit de la chair de ses proies ;"

class Chien(Carnivore):

caract3 = "son cri s'appelle aboiement ;"

mirza = Chien()

print(mirza.caract1,mirza.caract2,mirza.caract3)
Héritage

• Résultat :

il allaite ses petits ; il se nourrit de la


chair de ses proies ; son cri s'appelle
aboiement ;

• Dans cet exemple, on voit que l’objet mirza, qui est une
instance de la classe Chien( ), hérite non seulement de
l’attribut défini pour cette classe, mais également des
attributs définis pour les classes parentes.
Héritage

• Pour dériver une classe à partir d’une classe parente, on


utilise l’instruction class, suivie du nom que l’on veut
attribuer à la classe et on place entre parenthèses le nom
de la classe parente.

• Les classes les plus fondamentales dérivent de l’objet


« ancêtre » object.
Héritage
• Les attributs utilisés dans l’exemple précédent sont des attributs
de classes (et non des attributs d’instance).

• L’instance mirza peut accéder à ces attributs mais pas les


modifier.

mirza.caract2 = " son corps est couvert de poils ;"

print(mirza.caract2)

• Résultat :

son corps est couvert de poils ;


Héritage

fido = Chien()

print(fido.caract2)

• Résultat :

il se nourrit de la chair de ses proies ;


Héritage

• L’instruction

mirza.caract2 = " son corps est couvert de poils ; «

• crée une nouvelle variables d’instance associée seulement


à l’objet mirza.

• Il existe dès lors deux variables avec le même nom


caract2 : l’une dans l’espace de noms de l’objet mirza et
l’autre dans l’espace de noms de la classe Carnivore( ).
Héritage
• Mais s’il existe des variables identiques dans plusieurs
espaces de noms, laquelle est sélectionnée lors de
l’exécution d’une instruction ?

• Pour résoudre ce conflit, Python respecte une règle de


priorité très simple : lorsqu’on lui demande d’utiliser la
valeur d’une variable nommée alpha, il commence par
chercher ce nom dans l’espace local (le plus « interne » en
quelque sorte). Si une variable alpha est trouvée dans
l’espace local, c’est elle qui est utilisée et la recherche
s’arrête. Sinon, Python examine l’espace de noms de la
structure parente, puis celui de la structure grand-parente,
etc, jusqu’au niveau principal du programme.
Héritage et polymorphisme

• On va a présent examiner un script plus détaillé.

• Rappelons d’abord quelque notions de Chimie.

• Les atomes sont des entités constituées d’un certain


nombre de protons (particules chargées d’électricité
positive), d’électrons (chargés négativement) et de
neutrons (neutres).

• Le type d’atome (élément) est déterminé par le nombre de


protons, qu’on appelle numéro atomique.
Héritage et polymorphisme

• Dans son état fondamental, un atome contient autant


d’électrons que de protons et, par conséquent, il est
électriquement neutre.

• Il possède également un nombre variable de neutrons


mais ceux-ci n’influencent pas la charge électrique
globale.

• Dans certaines circonstances, un atome peut gagner ou


perdre des électrons.
Héritage et polymorphisme

• Il acquiert de ce fait une charge électrique globale et


devient alors un ion (ion négatif s’il a gagné des électrons,
ion positif s’il en a perdu).

• La charge électrique d’un ion est égale à la différence


entre le nombre de protons et le nombre d’électrons qu’il
contient.
Héritage et polymorphisme

• Le script suivant génère des objets Atome( ) et Ion( ).

• Un ion étant un atome modifié, la classe qui définit les


objets Ion( ) sera une classe dérivée de la classe
Atome( ) : elle héritera d’elle tous ses attributs et toutes
ses méthodes en y ajoutant les siennes propres.
Héritage et polymorphisme
• L’une de ces méthodes ajoutées (la méthode affiche( ))
remplace une méthode de même nom héritée de la classe
Atome( ).

• Les classes Atome( ) et Ion( ) possèdent donc chacune


une méthode de même nom, mais qui effectue un travail
différent.

• On parle dans ce cas de polymorphisme.

• On dit également que la méthode affiche( ) de la classe


Atome( ) est surchargée.
Héritage et polymorphisme
• Il est possible d’instancier un nombre quelconque d’atomes et
d’ions à partir de ces deux classes.

• Or, l’une d’entre elles, la classe Atome( ), doit contenir une version
simplifiée du tableau périodique des éléments (tableau de
Mendeleev), de façon à pouvoir attribuer un nom d’élément
chimique, ainsi qu’un nombre de neutrons, à chaque objet généré.

• Comme il n’est pas souhaitable de recopier ce tableau dans


chacune des instances, on le placera dans un attribut de classe.

• Ainsi, ce tableau n’existera qu’en un seul endroit en mémoire, tout


en restant accessible à tous les objets produits à partir de cette
classe.
Héritage et polymorphisme
class Atome:

"""Atomes simplifiés choisis parmi les 10 premiers éléments du TPE"""

table = [None, ('hydrogène',0), ('hélium',2), ('lithium',4), ('béryllium',5),

('bore',6), ('carbone',6), ('azote',7), ('oxygène',8), ('fluor',10),

('néon',10)]

def __init__(self, nat):

"le n° atomique détermine le nombre de protons, d'électrons et de neutrons"

[Link], [Link] = nat, nat

[Link] = [Link][nat][1]

def affiche(self):

print()

print("Nom de l'élément : ", [Link][[Link]][0])

print("{0} protons, {1} électrons, {2} neutrons".format([Link],[Link],[Link]))


Héritage et polymorphisme
class Ion(Atome):

"""Les ion sont des atomes qui ont perdu ou gagné des électrons"""

def __init__(self, nat, charge):

"le n° atomique et le charge électrique déterminent l'ion"

Atome.__init__(self,nat)

[Link] = [Link] - charge

[Link] = charge

def affiche(self):

[Link](self)

print("Particule électrisée. Charge = ", [Link])


Héritage et polymorphisme
a1 = Atome(5)

a2 = Ion(3,1)

a3 = Ion(8,-2)

[Link]()

[Link]()

[Link]()
Héritage et polymorphisme
• Résultat :

Nom de l'élément : bore

5 protons, 5 électrons, 6 neutrons

Nom de l'élément : lithium

3 protons, 2 électrons, 4 neutrons

Particule électrisée. Charge = 1

Nom de l'élément : oxygène

8 protons, 10 électrons, 8 neutrons

Particule électrisée. Charge = -2


Héritage et polymorphisme

• On instancie les objets Atome( ) en fournissant leur


numéro atomique (qui doit être compris entre 1 et 10).

• Pour instancier les objets Ion( ), on doit fournir un numéro


atomique et une charge électrique globale (positive ou
négative).

• La même méthode affiche( ) fait apparaître les propriétés


de ces objets, qu’il s’agisse d’atomes ou d’ions, avec
dans le cas de l’ion une ligne supplémentaire
(polymorphisme).
Héritage et polymorphisme
• La définition de la classe Atome( ) commence par
l’assignation de la variable table.

• Une variable définie à cet endroit fait partie de l’espace


de noms de la classe. C’est donc un attribut de classe
dans lequel nous plaçons des informations concernant
les dix premiers éléments du TPE.

• Comme il n’existe pas d’élément de numéro atomique


zéro, on a placé à l’indice zéro dans la liste l’objet spécial
None. On aurait pu placer à cet endroit n’importe quelle
autre valeur, puisque cet indice ne sera pas utilisé.
Héritage et polymorphisme
• Notons que la méthode constructeur de la classe Ion( ) fait appel
à la méthode constructeur de sa classe parente, dans :

Atome.__init__(self,nat)

• Cet appel est nécessaire afin que les objets de la classe Ion( )
soient initialisés de la même manière que les objets de la classe
Atome( ).

• Si nous n’effectuons pas cet appel, les objets-ions n’hériterons


pas automatiquement des attributs np, ne et nn, car ceux-ci sont
des attributs d’instance créés par la méthode constructeur de la
classe Atome( ), et celle-ci n’est pas invoquée automatiquement
lorsqu’on instancie des objets d’une classe dérivée.
Héritage et polymorphisme
• Insistons sur le fait que l’héritage ne concerne que les
classes et non les instances de ces classes.

• Lorsqu’on dit qu’une classe dérivée hérite de toutes les


propriétés de sa classe parente, cela ne signifie pas que
les propriétés des instances de la classe parente sont
automatiquement transmises aux instances de la classe
fille. Par conséquent :

• Dans la méthode constructeur d’une classe dérivée, il faut


le plus souvent prévoir un appel à la méthode
constructeur de sa classe parente.
Modules contenant des
bibliothèques de classes

• Les modules Python servent à regrouper des


bibliothèques de classes et de fonctions.

• On va créer un module de classes, en encodant les lignes


d’instruction ci-après dans un fichier-module nommé
[Link].
Modules contenant des bibliothèques de
classes
class Rectangle(object):

"Classe de rectangles"

def __init__(self, longueur = 0, largeur = 0):

self.L = longueur

self.l = largeur

[Link] = "rectangle"

def perimetre(self):

return "({0:d}+{1:d})*2 = {2:d}".format(self.L, self.l, (self.L+self.l)*2)

def surface(self):

return "{0:d}*{1:d} = {2:d}".format(self.L, self.l, self.L*self.l)

def mesures(self):

print("Un {0} de {1:d} sur {2:d}".format([Link],self.L,self.l))

print("a une surface de {0}".format([Link]()))

print("et un périmètre de {0}\n".format([Link]()))


Modules contenant des
bibliothèques de classes
class Carre(Rectangle):

"Classe de carrés"

def __init__(self,cote):

Rectangle.__init__(self, cote, cote)

[Link] = "carré"

if __name__ == "__main__":

r1 = Rectangle(15,30)

[Link]()

c1 = Carre(13)

[Link]()
Modules contenant des
bibliothèques de classes

• Une fois ce module enregistré, on peut l’utiliser de deux


manières : soit on en lance l’exécution comme celle d’un
programme ordinaire, soit on l’importe dans un script
quelconque ou depuis la ligne de commande pour en
utiliser les classes.

• Exemple :
Modules contenant des
bibliothèques de classes
import formes

f1 = [Link](27,12)

[Link]()

• Résultat :

Un rectangle de 27 sur 12

a une surface de 27*12 = 324

et un périmètre de (27+12)*2 = 78
Modules contenant des
bibliothèques de classes
f2 = [Link](13)

[Link]()

• Résultat :

Un carré de 13 sur 13

a une surface de 13*13 = 169

et un périmètre de (13+13)*2 = 52
Modules contenant des
bibliothèques de classes
• La classe Carre( ) est construite par dérivation à partir de la
classe Rectangle( ) dont elle hérite de toutes les
caractéristiques. En d’autres termes, la classe Carre( ) est une
classe fille de la classe Rectangle( ).

• L’instruction

if __name__ == "__main__":

placée à la fin du module, sert à déterminer si le module est lancé


en tant que programme autonome, auquel cas les instructions qui
suivent sont exécutées, ou utilisé comme une bibliothèque de
classes importées ailleurs, auquel cas cette partie du code est
sans effet.
Bibliographie
• An Introduction to Statistics with Python, T. Halwanter, Springer 2016

• Introduction to Machine Learning with Python, A. C. Müller & S. Guido, O’Reilly


2017

• Machine Learning : An Algorithmic Perspective, 2nd ed., S. Marsland, CRC


Press 2015.

• Programmation en Python pour les Mathématiques, 2ème éd., A. Casamayou-


Boucau, P. Chauvin & G. Connan, Dunod 2016

• Apprendre à programmer avec Python 3, 3ème éd., G. Swinnen, Eyrolles 2016.

• Informatique avec Python, S. Bays, Ellipses 2017

• Introduction à l’Informatique, S. Bays, Ellipses 2017

Vous aimerez peut-être aussi