NSI Première à Montréal • Algorithmique

Exercices corrigés de NSI Première : l'algorithme des k plus proches voisins

Voici une série d'exercices corrigés sur l'algorithme des k plus proches voisins, pour la spécialité NSI de première, programme français. Elle s'adresse aux élèves des lycées français, dont le Lycée Marie de France et le Collège Stanislas à Montréal, et à tout élève de première qui rencontre son premier algorithme d'apprentissage.

Le fil de la série tient en une phrase : le vote ne décide rien, c'est la distance qui choisit les votants. Changer de distance, oublier de normaliser un attribut, trier des chaînes au lieu de nombres ou mal choisir k, c'est changer d'électeurs, et donc parfois de prédiction, sans qu'aucune erreur ne s'affiche.

Tout se fait sans calculatrice : les distances euclidiennes tombent juste ou se comparent par leur carré, ce que l'exercice 2 justifie. Les codes à compléter sont en Python, et chaque réponse chiffrée se vérifie directement sur la page.

Faites chaque exercice au complet avant d'ouvrir la correction : c'est en cherchant qu'on apprend, pas en lisant la solution.

Série autocorrigée Tape tes réponses sous chaque question : la page te dit juste ou faux avant d'ouvrir la correction. Avec un compte, chaque bonne réponse du premier coup rapporte des points.

Ce chapitre fait partie de NSI en Première
Avant de commencer Fiche de révision : les pièges et la méthode de ce chapitre

Avant ce chapitre

Ces notions sont supposées acquises ici. Si le premier exercice résiste, le blocage vient presque toujours de l'une d'elles, pas du chapitre lui-même.

Remonter plus loin : la chaîne complète (5 chapitres) ↓

Le chemin de remédiation, du plus ancien au plus proche. Un élève qui reprend ce chapitre de zéro le reprend dans cet ordre.

  1. 1Algorithmique et ScratchTroisième, Mathématiques
  2. 2Algorithmique et PythonSeconde, Mathématiques
  3. 3Python : types, contrôle, fonctions et tableaux
  4. 4Algorithmique : preuve, terminaison et coût
  5. 5Traitement de données en tables et types construits

Rappel de cours

  • • PRINCIPE : pour prédire la classe d'un élément, on calcule sa distance à tous les exemples de la table, on garde les kk plus proches, et on prédit la classe la plus représentée parmi eux.
  • • DISTANCE EUCLIDIENNE : (x−x′)2+(y−y′)2\sqrt{(x-x')^{2}+(y-y')^{2}}, à vol d'oiseau. DISTANCE DE MANHATTAN : ∣x−x′∣+∣y−y′∣|x-x'|+|y-y'|, le long du quadrillage. Elle n'est jamais plus petite que l'euclidienne.
  • • La racine carrée est croissante : classer par CARRÉ de distance donne les mêmes voisins.
  • • Trier une table : sorted(table, key=f) renvoie une nouvelle liste ; table.sort(key=f) trie sur place et renvoie None. On passe la fonction f, sans l'appeler.
  • • VOTE : un dictionnaire de compteurs, puis la clé de plus grand compteur. max(compteurs) renvoie la plus grande CLÉ, pas l'étiquette la plus fréquente.
  • • CHOIX DE kk : k=1k=1 suit chaque individu atypique (sur-ajustement) ; k=nk=n répond toujours la classe la plus nombreuse (sous-ajustement). Avec deux classes, kk impair évite les égalités.
  • • NORMALISATION : x′=x−min⁡max⁡−min⁡x'=\dfrac{x-\min}{\max-\min}, avec le minimum et le maximum de la table, dès que les attributs n'ont ni la même unité ni la même étendue.
  • • CSV : csv.DictReader lit toutes les valeurs comme des CHAÎNES. On convertit par float une seule fois, au chargement.
  • • On choisit kk sur des exemples TEST hors de la table : sur la table elle-même, k=1k=1 réussit toujours.
  • • COÛT : aucune phase d'apprentissage ; chaque prédiction calcule nn distances. Garder les kk meilleurs en un parcours coûte bien moins que trier toute la table.

Partie A : les bases (/50)

Exercice 1 : Deux distances, deux plus proches voisins : des pommes et des poires

Un producteur veut trier automatiquement des fruits. Chaque fruit est décrit par un couple (largeur, hauteur) mesuré en millimètres. La table d'exemples contient cinq fruits dont on connaît la nature : trois pommes A, D, E et deux poires B, C. Un nouveau fruit N, de largeur 7070 mm et de hauteur 7575 mm, arrive sur le tapis.

Coordonnées : A (76 ;75)(76\,;75), B (67 ;79)(67\,;79), C (64 ;83)(64\,;83), D (82 ;70)(82\,;70), E (70 ;67)(70\,;67). On compare deux distances entre deux points (x ;y)(x\,;y) et (x′ ;y′)(x'\,;y') : la distance euclidienne (x−x′)2+(y−y′)2\sqrt{(x-x')^{2}+(y-y')^{2}}, « à vol d'oiseau », et la distance de Manhattan ∣x−x′∣+∣y−y′∣|x-x'|+|y-y'|, celle d'un trajet qui suit le quadrillage.

6065707580856570758085ABCDEN● pomme○ poirelargeur (mm)hauteur (mm)
  • a) Calculez la distance euclidienne de N à chacun des cinq fruits. Tous les résultats sont entiers.
  • b) Calculez la distance de Manhattan de N à chacun des cinq fruits.
  • c) Quel est le plus proche voisin de N pour chacune des deux distances ? Quelle nature l'algorithme prédit-il pour k=1k=1 dans chaque cas ?
  • d) Donnez les trois plus proches voisins et la prédiction pour k=3k=3, avec chacune des deux distances. Expliquez pourquoi la distance de Manhattan n'est jamais plus petite que la distance euclidienne.

Tape tes réponses, la page te dit juste ou faux 0/11

a)
b)
c)
d)
Voir la correction

Réponses

  • a) d(N,A)=6d(N,A)=6, d(N,B)=5d(N,B)=5, d(N,C)=10d(N,C)=10, d(N,D)=13d(N,D)=13, d(N,E)=8d(N,E)=8
  • b) A : 66, B : 77, C : 1414, D : 1717, E : 88
  • c) Euclidienne : B, donc poire ; Manhattan : A, donc pomme
  • d) Euclidienne : B, A, E ; Manhattan : A, B, E ; les deux prédisent pomme. ∣a∣+∣b∣≥a2+b2|a|+|b|\ge\sqrt{a^{2}+b^{2}} car (∣a∣+∣b∣)2=a2+b2+2∣a∣∣b∣(|a|+|b|)^{2}=a^{2}+b^{2}+2|a||b|

a) On calcule les écarts de coordonnées, puis on applique la formule. Pour A : écarts 66 et 00, donc 36+0=6\sqrt{36+0}=6. Pour B : écarts −3-3 et 44, donc 9+16=25=5\sqrt{9+16}=\sqrt{25}=5. Pour C : −6-6 et 88, donc 36+64=10\sqrt{36+64}=10. Pour D : 1212 et −5-5, donc 144+25=169=13\sqrt{144+25}=\sqrt{169}=13. Pour E : 00 et −8-8, donc 88. Le signe d'un écart ne compte pas, puisqu'on l'élève au carré ; en revanche il ne faut surtout pas additionner les écarts AVANT de les élever au carré : (−3+4)2=1(-3+4)^{2}=1 n'a rien à voir avec 9+16=259+16=25. Écrire le détail des écarts sur la copie est ce qui rend l'erreur visible au correcteur, et à vous.

b) La distance de Manhattan additionne les valeurs absolues des écarts : A 6+0=66+0=6, B 3+4=73+4=7, C 6+8=146+8=14, D 12+5=1712+5=17, E 0+8=80+8=8. Le nom vient des rues à angle droit d'une ville quadrillée : pour aller d'un carrefour à un autre, on ne traverse pas les pâtés de maisons en diagonale, on suit les rues. La valeur absolue est indispensable ; sans elle, les écarts de B, −3-3 et +4+4, se compenseraient et donneraient 11, une distance absurdement petite. Ce piège revient en Python à l'exercice 2.

c) Avec la distance euclidienne, le plus proche est B, à 55 : l'algorithme prédit une POIRE. Avec la distance de Manhattan, le plus proche est A, à 66, devant B à 77 : il prédit une POMME. Même table, même fruit, même kk, et deux réponses opposées. C'est le fil de toute la série : le vote ne décide rien, c'est la distance qui choisit les votants. B est proche en diagonale, A est proche en ligne droite le long d'un axe, et les deux distances ne récompensent pas la même chose. Le choix de la distance fait donc partie de l'algorithme, au même titre que kk, et un énoncé qui ne la précise pas est incomplet.

d) Euclidienne, par distances croissantes : B 55, A 66, E 88, C 1010, D 1313. Les trois premiers sont B, A, E : deux pommes contre une poire, prédiction pomme. Manhattan : A 66, B 77, E 88, C 1414, D 1717 ; les trois premiers sont A, B, E, et la prédiction est encore pomme. Avec trois votants, le désaccord de k=1k=1 disparaît : un seul voisin ne pèse plus tout le vote. Enfin, pour deux écarts aa et bb, (∣a∣+∣b∣)2=a2+b2+2∣a∣∣b∣≥a2+b2(|a|+|b|)^{2}=a^{2}+b^{2}+2|a||b|\ge a^{2}+b^{2}, et les deux membres étant positifs, ∣a∣+∣b∣≥a2+b2|a|+|b|\ge\sqrt{a^{2}+b^{2}}. Géométriquement, l'escalier qui suit le quadrillage est plus long que la diagonale, sauf quand l'un des écarts est nul, comme pour A et E où les deux distances coïncident.

6065707580856570758085ABCDEN34N vers B : 5 à vol d'oiseau,7 en suivant le quadrillagelargeur (mm)hauteur (mm)

Coche ici les exercices faits ou à revoir : un compte gratuit, sans mot de passe, retient tes coches d'une visite à l'autre et te dit quel chapitre attaquer ensuite. Crée ton espace, un courriel suffit.

Exercice 2 : Programmer une distance : la racine, la valeur absolue et la longueur

Dans l'algorithme des k plus proches voisins, un élément est décrit par un p-uplet de nombres, un tuple Python, et tout repose sur une fonction qui mesure l'écart entre deux p-uplets de même longueur. On propose les deux fonctions ci-dessous ; la seconde est à compléter.

Python
from math import sqrt

def distance_euclidienne(p, q):
    s = 0
    for i in range(len(p)):
        s = s + (p[i] - q[i]) ** 2
    return sqrt(s)

def distance_manhattan(p, q):
    s = 0
    for i in range(len(p)):
        s = s + ...          # à compléter
    return s
  • a) Que renvoie distance_euclidienne((1, 5, 2), (3, 1, 6)) ? Donnez la valeur de s à la fin de chacun des trois tours de boucle.
  • b) Complétez distance_manhattan. Un camarade écrit s = s + (p[i] - q[i]) : que renvoie sa version sur les mêmes p-uplets, et que renvoie la version correcte ?
  • c) Pour aller plus vite, un autre camarade supprime la racine carrée et renvoie s. Trois éléments u, v, w sont à des distances euclidiennes de la cible dont les carrés valent 3636, 2525 et 4949. Les plus proches voisins changent-ils ? Quel est le plus proche des trois ?
  • d) Que renvoie distance_euclidienne((3, 4), (0, 0, 12)) ? Et distance_euclidienne((0, 0, 12), (3, 4)) ? Quelle ligne ajouter en tête de fonction pour que l'erreur ne passe plus inaperçue ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
Voir la correction

Réponses

  • a) s vaut 44, 2020 puis 3636 ; la fonction renvoie 6,06{,}0
  • b) s = s + abs(p[i] - q[i]) ; la version fautive renvoie −2-2, la bonne 1010
  • c) Non : la racine est croissante, elle ne change pas l'ordre ; le plus proche est v
  • d) 5,05{,}0 sans aucune erreur ; puis IndexError ; ajouter assert len(p) == len(q)

a) Les écarts sont 1−3=−21-3=-2, 5−1=45-1=4 et 2−6=−42-6=-4. Au premier tour, s passe de 00 à (−2)2=4(-2)^{2}=4 ; au deuxième, à 4+16=204+16=20 ; au troisième, à 20+16=3620+16=36. La fonction renvoie 36=6\sqrt{36}=6, écrit 6.0 par Python puisque sqrt renvoie toujours un flottant. La boucle parcourt les INDICES, et non les valeurs, parce qu'il faut lire la même coordonnée dans p et dans q au même tour. C'est aussi ce qui rend la fonction valable en dimension quelconque : deux, trois ou vingt attributs, le code ne change pas. En évaluation, une trace à trois lignes, une par tour, est la réponse attendue à « que renvoie cette fonction ».

b) La ligne manquante est s = s + abs(p[i] - q[i]), et la version correcte renvoie 2+4+4=102+4+4=10. Sans abs, les écarts s'ajoutent avec leur signe : −2+4−4=−2-2+4-4=-2. La fonction renvoie alors une « distance » NÉGATIVE, et surtout, elle peut déclarer très proches deux points très éloignés dont les écarts se compensent : (0,0)(0, 0) et (5,−5)(5, -5) seraient à distance 00. Aucune erreur n'est levée, le programme tourne et prédit n'importe quoi. Le contrôle qui attrape ce défaut tient en une ligne : une distance est toujours positive ou nulle, et elle est nulle seulement entre deux points identiques. Un seul résultat négatif dans une trace suffit à condamner la fonction.

c) Les voisins ne changent pas. La racine carrée est une fonction STRICTEMENT CROISSANTE sur les nombres positifs : si a<ba<b alors a<b\sqrt{a}<\sqrt{b}, et réciproquement. Classer les éléments par carré de distance ou par distance donne donc exactement le même ordre : v (2525, distance 55), puis u (3636, distance 66), puis w (4949, distance 77). Le plus proche est v. Supprimer la racine est une optimisation courante et légitime, qui économise un calcul par élément de la table. Elle devient fausse dès qu'on utilise la VALEUR de la distance et plus seulement son rang, par exemple pour ne garder que les voisins à moins de 66 : il faudrait alors comparer le carré à 3636, pas à 66.

d) Le premier appel renvoie 5,05{,}0. La boucle tourne sur range(len(p)), donc sur deux indices seulement : 32+42=253^{2}+4^{2}=25, et la troisième coordonnée de q, 1212, est ignorée sans un mot. Le second appel fait trois tours, et au troisième il lit q[2], qui n'existe pas : IndexError. Le même oubli donne donc, selon l'ordre des arguments, une erreur ou un résultat faux, et le résultat faux est le plus dangereux des deux, puisqu'on ne le voit pas. La distance n'a de sens qu'entre deux p-uplets de même longueur : c'est une PRÉCONDITION, qui s'écrit assert len(p) == len(q) en première ligne. Dans une table chargée depuis un fichier, une ligne à qui il manque une colonne produit exactement ce défaut.

Python
def distance_manhattan(p, q):
    assert len(p) == len(q), 'p et q doivent avoir la même longueur'
    s = 0
    for i in range(len(p)):
        s = s + abs(p[i] - q[i])
    return s

assert distance_manhattan((1, 5, 2), (3, 1, 6)) == 10
assert distance_manhattan((0, 0), (5, -5)) == 10

Exercice 3 : Trier une table par distance : sort, sorted et la clé

Un torréfacteur reçoit des sacs de grains verts et veut vérifier l'espèce annoncée. Chaque grain de référence est un dictionnaire ; la table grains est la liste de ces dictionnaires. Un grain inconnu mesure 10,510{,}5 mm de long et 7,57{,}5 mm de large.

Pour trouver ses voisins, on trie la table par distance croissante au grain inconnu. On compare les CARRÉS des distances euclidiennes, ce qui ne change pas l'ordre.

nomlongueur (mm)largeur (mm)espèce
G111,58,0arabica
G29,57,5robusta
G311,07,0arabica
G49,08,0robusta
G59,56,5robusta
G612,08,5arabica
Python
cible = {'longueur': 10.5, 'largeur': 7.5}

def d2(g):
    return ((g['longueur'] - cible['longueur']) ** 2
            + (g['largeur'] - cible['largeur']) ** 2)

voisins = sorted(grains, key=d2)          # (1)
voisins = grains.sort(key=d2)             # (2)
voisins = sorted(grains, key=d2(grains))  # (3)
  • a) Calculez d2 pour chacun des six grains.
  • b) Trois instructions de tri sont proposées. Que vaut voisins après l'instruction (2) ? Que se passe-t-il avec l'instruction (3) ? Laquelle faut-il garder ?
  • c) Donnez les trois premiers grains de voisins et l'espèce prédite pour k=3k=3, puis pour k=5k=5.
  • d) Après l'instruction (1), quel est le premier élément de grains ? Pourquoi est-ce préférable ici au tri sur place ?

Tape tes réponses, la page te dit juste ou faux 0/9

a)
b)
c)
d)
Voir la correction

Réponses

  • a) G1 1,251{,}25 ; G2 11 ; G3 0,50{,}5 ; G4 2,52{,}5 ; G5 22 ; G6 3,253{,}25
  • b) (2) : voisins vaut None ; (3) : d2 est appelée sur la liste entière, TypeError ; garder (1)
  • c) G3, G2, G1 : arabica pour k=3k=3 ; pour k=5k=5, G5 et G4 s'ajoutent : robusta, 33 voix contre 22
  • d) Toujours G1 : sorted renvoie une NOUVELLE liste et laisse la table intacte

a) Écarts à (10,5 ;7,5)(10{,}5\,;7{,}5) : G1 (1 ;0,5)(1\,;0{,}5), d2 =1+0,25=1,25=1+0{,}25=1{,}25 ; G2 (−1 ;0)(-1\,;0), d2 =1=1 ; G3 (0,5 ;−0,5)(0{,}5\,;-0{,}5), d2 =0,25+0,25=0,5=0{,}25+0{,}25=0{,}5 ; G4 (−1,5 ;0,5)(-1{,}5\,;0{,}5), d2 =2,25+0,25=2,5=2{,}25+0{,}25=2{,}5 ; G5 (−1 ;−1)(-1\,;-1), d2 =2=2 ; G6 (1,5 ;1)(1{,}5\,;1), d2 =2,25+1=3,25=2{,}25+1=3{,}25. Aucune racine à calculer : l'exercice 2 a montré que le carré suffit pour CLASSER. Piège de calcul fréquent : 0,52=0,250{,}5^{2}=0{,}25 et non 0,50{,}5, un carré de nombre inférieur à 11 est plus petit que lui.

b) L'instruction (2) trie grains SUR PLACE, et la méthode sort renvoie None : voisins vaut donc None, et la ligne suivante, voisins[0], lève une erreur. L'instruction (3) appelle d2 tout de suite, avec la liste entière comme argument ; g['longueur'] sur une liste lève une TypeError, les indices d'une liste étant des entiers. Le paramètre key attend une FONCTION, que sorted appellera elle-même sur chaque élément : on écrit key=d2, le nom seul, sans parenthèses. On garde donc l'instruction (1). Retenez la paire : liste.sort() modifie la liste et ne renvoie rien ; sorted(liste) ne modifie rien et renvoie une nouvelle liste.

c) Rangés par d2 croissant : G3 0,50{,}5, G2 11, G1 1,251{,}25, G5 22, G4 2,52{,}5, G6 3,253{,}25. Pour k=3k=3, les votants sont G3 arabica, G2 robusta, G1 arabica : deux voix contre une, prédiction arabica. Pour k=5k=5, G5 et G4, deux robusta, rejoignent le vote : trois robusta contre deux arabica, prédiction robusta. La prédiction bascule entre k=3k=3 et k=5k=5 sans que la table ni le grain aient changé. Les voisins s'obtiennent par une tranche, voisins[:k], et la tranche garde l'ordre du tri : c'est pourquoi on trie D'ABORD et on coupe ENSUITE.

d) Le premier élément de grains est toujours G1, l'ordre de la table n'a pas bougé. C'est préférable parce que la table sert à TOUTES les prédictions : la suivante trie par rapport à une autre cible, et un tri sur place laisserait la table dans l'ordre d'une cible passée, ce qui ne fausse pas le résultat mais rend illisible tout affichage fondé sur la position, « le troisième grain de la table ». La fonction qui prédit ne doit pas modifier les données qu'on lui confie : c'est un effet de bord. Le prix de sorted est une copie de la liste des références, négligeable ici.

Exercice 4 : Le vote majoritaire : un dictionnaire de compteurs et le piège de max

Une application d'ornithologie identifie une mésange à partir de trois mesures prises sur une photo. Pour un oiseau photographié, elle a déjà trié sa table d'exemples et obtenu les espèces de ses sept plus proches voisins, rangées par distance croissante dans la liste voisins. Il reste à faire voter ces voisins.

La ligne compteurs[e] = compteurs.get(e, 0) + 1 ajoute 11 au compteur de l'espèce e, en partant de 00 si e n'est pas encore une clé. La seconde boucle est à compléter.

Python
voisins = ['charbonnière', 'bleue', 'nonnette', 'charbonnière',
           'bleue', 'charbonnière', 'nonnette']

def vote(etiquettes):
    compteurs = {}
    for e in etiquettes:
        compteurs[e] = compteurs.get(e, 0) + 1
    gagnante = None
    for e in compteurs:
        if gagnante is None or ...:     # à compléter
            gagnante = e
    return gagnante
  • a) Donnez le contenu de compteurs après la première boucle, pour l'appel vote(voisins).
  • b) Complétez la condition. Quelle espèce vote(voisins) renvoie-t-il, et quelle proportion des voix a-t-elle obtenue ? S'agit-il d'une majorité absolue ?
  • c) Un camarade remplace toute la seconde boucle par return max(compteurs). Que renvoie sa version ? Pourquoi ?
  • d) On appelle vote(voisins[:5]), c'est-à-dire k=5k=5. Que renvoie la fonction complétée en b) ? Et si l'on avait écrit la condition avec >= au lieu de > ?

Tape tes réponses, la page te dit juste ou faux 0/9

a)
b)
c)
d)
Voir la correction

Réponses

  • a) {'charbonnière': 3, 'bleue': 2, 'nonnette': 2}
  • b) compteurs[e] > compteurs[gagnante] ; charbonnière, avec 37≈0,43\frac{3}{7}\approx 0{,}43 des voix : majorité relative seulement
  • c) 'nonnette' : max compare les CLÉS, dans l'ordre alphabétique, pas les compteurs
  • d) Égalité 22 contre 22 : avec >, charbonnière ; avec >=, bleue

a) La boucle lit les sept étiquettes dans l'ordre. Charbonnière apparaît aux rangs 11, 44 et 66, bleue aux rangs 22 et 55, nonnette aux rangs 33 et 77. D'où compteurs = {'charbonnière': 3, 'bleue': 2, 'nonnette': 2}. Vérification immédiate : la somme des compteurs vaut 77, le nombre de votants. Le dictionnaire garde l'ORDRE D'INSERTION des clés, celui de leur première apparition : charbonnière, puis bleue, puis nonnette. Ce détail décide de la question d).

b) La condition est compteurs[e] > compteurs[gagnante] : on garde e si elle a strictement plus de voix que la meilleure vue jusqu'ici. C'est le schéma du maximum, appliqué aux VALEURS du dictionnaire, en retenant la CLÉ qui les porte. Le test gagnante is None traite le premier tour, où il n'existe encore aucune gagnante à battre. La fonction renvoie 'charbonnière', avec 33 voix sur 77, soit 37≈0,43\frac{3}{7}\approx 0{,}43. Ce n'est PAS une majorité absolue : avec trois classes, la classe gagnante peut rassembler moins de la moitié des voix. « Classe majoritaire » veut dire ici la plus représentée, la majorité relative, et un énoncé qui demande « plus de la moitié des voisins » pose une autre question.

c) max(compteurs) parcourt le dictionnaire, donc ses CLÉS, et renvoie la plus grande dans l'ordre alphabétique : 'bleue' < 'charbonnière' < 'nonnette', d'où 'nonnette', une espèce qui n'a que deux voix. Le code tourne, renvoie une espèce plausible, et se trompe sans aucun message. Si l'on tient à une fonction toute faite, c'est max(compteurs, key=compteurs.get) : la clé de comparaison devient le compteur. Mais la boucle écrite en b) a l'avantage de rendre visible la règle appliquée en cas d'égalité, qui est l'objet de la question suivante.

d) Les cinq premiers voisins donnent charbonnière 22, bleue 22, nonnette 11 : il y a égalité. Avec >, la boucle retient charbonnière au premier tour, puis bleue ne la bat pas, 2>22>2 étant faux : la fonction renvoie 'charbonnière'. Avec >=, bleue remplace charbonnière à égalité, et nonnette ne l'égale pas : la fonction renvoie 'bleue'. Le choix n'est pas anodin. Comme la liste est rangée par distance croissante, l'ordre d'insertion est celui de la proximité : avec >, l'égalité est tranchée en faveur de l'espèce dont le représentant est le PLUS PROCHE, ce qui est une règle défendable. Avec >=, elle l'est en faveur de la plus lointaine. Une égalité se prévoit : on la tranche par une règle écrite, ou on l'évite en choisissant kk, comme le montre l'exercice 5.

Python
def vote(etiquettes):
    compteurs = {}
    for e in etiquettes:
        compteurs[e] = compteurs.get(e, 0) + 1
    gagnante = None
    for e in compteurs:
        if gagnante is None or compteurs[e] > compteurs[gagnante]:
            gagnante = e
    return gagnante

assert vote(voisins) == 'charbonnière'
assert vote(voisins[:5]) == 'charbonnière'   # égalité : la plus proche

Exercice 5 : L'influence de k : l'abeille atypique et la syrphe déguisée

Les syrphes sont des mouches qui imitent les abeilles, rayures comprises. Un laboratoire les distingue par deux mesures en millimètres : la longueur des antennes, courtes chez la syrphe, et la longueur du corps. La table contient 77 abeilles et 55 syrphes, représentées ci-dessous ; toutes les coordonnées sont entières.

Un insecte X mesure 22 mm d'antennes et 1010 mm de corps. L'abeille la plus proche de lui, en (3 ;10)(3\,;10), est une petite abeille solitaire, très différente des autres abeilles de la table. On compare les carrés des distances euclidiennes.

-112345676789101112131415X● abeille○ syrpheantennes (mm)corps (mm)
  • a) Rangez les insectes par distance croissante à X, jusqu'au septième. Donnez le carré de la distance de X à l'abeille (3 ;10)(3\,;10), à la syrphe (3 ;8)(3\,;8) et à l'abeille (4 ;12)(4\,;12).
  • b) Quelle est la prédiction pour k=1k=1, k=3k=3, k=5k=5 et k=7k=7 ?
  • c) Que se passe-t-il pour k=2k=2 ? Pourquoi choisit-on un kk impair quand il y a deux classes ?
  • d) Quelle est la prédiction pour k=12k=12 ? Montrez qu'elle ne dépend plus de X. Lequel des choix k=1k=1 ou k=12k=12 est sur-ajusté aux particularités de la table, et que conseillez-vous ?

Tape tes réponses, la page te dit juste ou faux 0/10

a)
b)
c)
d)
Voir la correction

Réponses

  • a) 11 (abeille (3 ;10)(3\,;10)), 22 et 22 (syrphes (1 ;9)(1\,;9), (1 ;11)(1\,;11)), 44, 55, 88 (abeille (4 ;12)(4\,;12)), 99
  • b) k=1k=1 : abeille ; k=3k=3, 55 et 77 : syrphe
  • c) Une voix partout, égalité ; avec deux classes et kk impair, l'égalité est impossible
  • d) k=12k=12 : abeille (77 contre 55), quel que soit X ; k=1k=1 est sur-ajusté ; un kk impair intermédiaire, 33 ou 55

a) Carrés des distances à X (2 ;10)(2\,;10) : abeille (3 ;10)(3\,;10) : 1+0=11+0=1 ; syrphes (1 ;9)(1\,;9) et (1 ;11)(1\,;11) : 1+1=21+1=2 chacune ; syrphe (2 ;12)(2\,;12) : 0+4=40+4=4 ; syrphe (3 ;8)(3\,;8) : 1+4=51+4=5 ; abeille (4 ;12)(4\,;12) : 4+4=84+4=8 ; syrphe (2 ;7)(2\,;7) : 0+9=90+9=9. Les suivants sont des abeilles, (5 ;11)(5\,;11) à 1010, (6 ;10)(6\,;10) à 1616, puis (5 ;13)(5\,;13) à 1818, (4 ;14)(4\,;14) et (6 ;12)(6\,;12) à 2020. L'égalité entre les deux syrphes de carré 22 est sans conséquence : elles sont de la même classe et entrent ensemble dans le vote dès k=3k=3. Une égalité de distance ne compte que si elle tombe sur la FRONTIÈRE du kk-ième voisin avec deux classes différentes.

b) k=1k=1 : le seul votant est l'abeille atypique, prédiction abeille. k=3k=3 : abeille, syrphe, syrphe, prédiction syrphe. k=5k=5 : une abeille et quatre syrphes, syrphe. k=7k=7 : l'abeille (4 ;12)(4\,;12) entre au sixième rang et la syrphe (2 ;7)(2\,;7) au septième, soit deux abeilles contre cinq syrphes, syrphe. Sur la figure, X est entouré de syrphes, et la seule abeille proche est un individu qui ne ressemble pas aux autres. Avec k=1k=1, cet individu décide seul, et l'algorithme reproduit une particularité de la table au lieu de la tendance qu'elle décrit.

c) Pour k=2k=2, les deux votants sont l'abeille à 11 et une syrphe à 22 : une voix chacune, égalité, et l'algorithme ne sait pas conclure. Encore faut-il choisir laquelle des deux syrphes à 22 entre dans le vote, ce qui ajoute une seconde ambiguïté. Avec DEUX classes, un kk impair rend l'égalité impossible, puisqu'un nombre impair de voix ne se partage pas en deux moitiés égales. Avec trois classes ou plus, l'imparité ne suffit plus, comme l'exercice 4 l'a montré avec 22, 22 et 11 voix pour k=5k=5 : il faut alors une règle de départage écrite.

d) Pour k=12k=12, tous les insectes de la table votent, quel que soit X : 77 abeilles contre 55 syrphes, prédiction abeille. La distance ne sert plus à rien, l'algorithme répond toujours la classe la plus nombreuse de la table : c'est le défaut inverse, le sous-ajustement, où la prédiction ignore l'élément à classer. Le choix k=1k=1 est le SUR-AJUSTÉ : il suit chaque individu de la table, y compris les atypiques et les erreurs d'étiquetage. On retient donc un kk impair intermédiaire, ici 33 ou 55, assez grand pour qu'un individu isolé ne décide pas seul, assez petit pour que le vote reste local. L'exercice 9 montre comment choisir kk sur des données, plutôt qu'au jugé.

-112345676789101112131415X● abeille○ syrphecercles : 1, 3, 5 voisinsantennes (mm)corps (mm)

Partie B : problèmes et raisonnement (/50)

Exercice 6 : Normaliser les attributs : quand le prix écrase l'autonomie

Un banc d'essai a classé six téléphones en « recommandé » ou « déconseillé ». Chaque téléphone est décrit par son autonomie en heures et son prix en dollars. On veut prédire l'avis du banc d'essai pour un nouveau modèle X : 2626 h d'autonomie, 300300 dollars.

On utilise la distance de Manhattan et k=3k=3.

téléphoneautonomie (h)prix (dollars)avis
P110290déconseillé
P212320déconseillé
P328360recommandé
P4301 000recommandé
P524380recommandé
P618200déconseillé
  • a) Calculez la distance de Manhattan de X à chacun des six téléphones, sans rien transformer. Quelle est la prédiction pour k=3k=3 ?
  • b) Dans ce calcul, un écart d'une heure pèse autant qu'un écart d'un dollar. Quelle est l'étendue de chaque attribut dans la table, et combien de fois l'étendue du prix dépasse-t-elle celle de l'autonomie ? Pourquoi la prédiction de a) est-elle suspecte ?
  • c) On normalise chaque attribut par x′=x−min⁡max⁡−min⁡x'=\dfrac{x-\min}{\max-\min}, le minimum et le maximum étant lus dans la table. Donnez les coordonnées normalisées de X, puis ses distances de Manhattan normalisées à P3 et à P5.
  • d) Calculez les autres distances normalisées et donnez la nouvelle prédiction pour k=3k=3. Faut-il recalculer le minimum et le maximum en incluant X ?

Tape tes réponses, la page te dit juste ou faux 0/14

a)
b)
c)
d)
Voir la correction

Réponses

  • a) P1 2626, P2 3434, P3 6262, P4 704704, P5 8282, P6 108108 ; voisins P1, P2, P3 : déconseillé
  • b) Étendues 2020 h et 800800 dollars, rapport 4040 : le prix décide seul
  • c) X' =(0,8 ;0,125)=(0{,}8\,;0{,}125) ; P3 : 0,1750{,}175 ; P5 : 0,20{,}2
  • d) P1 0,81250{,}8125, P2 0,7250{,}725, P4 1,0751{,}075, P6 0,5250{,}525 ; voisins P3, P5, P6 : recommandé ; non, on garde le minimum et le maximum de la table

a) Distance brute = écart d'autonomie + écart de prix. P1 : 16+10=2616+10=26 ; P2 : 14+20=3414+20=34 ; P3 : 2+60=622+60=62 ; P4 : 4+700=7044+700=704 ; P5 : 2+80=822+80=82 ; P6 : 8+100=1088+100=108. Les trois plus proches sont P1, P2 et P3 : deux déconseillés contre un recommandé, prédiction déconseillé. Or P1 et P2 ont une autonomie de 1010 et 1212 heures, moins de la moitié de celle de X, et ce sont eux qui votent parce qu'ils coûtent à peu près le même prix. Le calcul additionne des heures et des dollars : le nombre obtenu n'a pas d'unité, et il n'a pas de sens.

b) Autonomie : de 1010 à 3030 h, étendue 2020. Prix : de 200200 à 1 0001\,000 dollars, étendue 800800, soit 4040 fois plus. Un écart de 2020 dollars, insignifiant pour un téléphone, pèse autant dans la distance que tout l'écart d'autonomie de la table, de 1010 à 3030 heures. Autrement dit, la distance brute ne regarde presque que le prix, et l'autonomie ne sert qu'à départager des téléphones de même prix. Ce n'est pas un choix, c'est un accident d'unités : exprimer le prix en milliers de dollars ferait au contraire disparaître le prix du calcul. Dès que les attributs n'ont ni la même unité ni la même étendue, les voisins dépendent des unités choisies, ce qui est absurde.

c) La normalisation min-max ramène chaque attribut entre 00 et 11 : 00 pour la plus petite valeur de la table, 11 pour la plus grande. Autonomie : a′=a−1020a'=\dfrac{a-10}{20} ; prix : p′=p−200800p'=\dfrac{p-200}{800}. Pour X : a′=1620=0,8a'=\dfrac{16}{20}=0{,}8 et p′=100800=0,125p'=\dfrac{100}{800}=0{,}125. P3 (28 ;360)(28\,;360) devient (0,9 ;0,2)(0{,}9\,;0{,}2), distance 0,1+0,075=0,1750{,}1+0{,}075=0{,}175. P5 (24 ;380)(24\,;380) devient (0,7 ;0,225)(0{,}7\,;0{,}225), distance 0,1+0,1=0,20{,}1+0{,}1=0{,}2. Après normalisation, un écart de 0,10{,}1 représente un dixième de l'étendue, que ce soit en heures ou en dollars : les deux attributs pèsent enfin le même poids.

d) P1 (0 ;0,1125)(0\,;0{,}1125) : 0,8+0,0125=0,81250{,}8+0{,}0125=0{,}8125. P2 (0,1 ;0,15)(0{,}1\,;0{,}15) : 0,7+0,025=0,7250{,}7+0{,}025=0{,}725. P4 (1 ;1)(1\,;1) : 0,2+0,875=1,0750{,}2+0{,}875=1{,}075. P6 (0,4 ;0)(0{,}4\,;0) : 0,4+0,125=0,5250{,}4+0{,}125=0{,}525. Rangement : P3, P5, P6, P2, P1, P4. Les trois votants sont deux recommandés et un déconseillé : la prédiction devient recommandé. La même table, le même kk et la même distance donnent l'avis contraire : on a seulement changé les échelles, donc les votants. On ne recalcule PAS le minimum et le maximum avec X : la transformation est fixée une fois pour toutes sur la table, et chaque nouvel élément la subit telle quelle, sinon deux prédictions successives ne seraient pas faites dans le même repère. Un élément hors de l'étendue donne une coordonnée hors de [0 ;1][0\,;1], ce qui ne pose aucun problème.

Exercice 7 : Charger la table depuis un fichier CSV : des nombres qui sont des chaînes

Un club de botanique a mesuré des feuilles d'érable et de chêne, en centimètres, et enregistré ses mesures dans le fichier feuilles.csv. Le module csv de Python lit ce fichier en une liste de dictionnaires, un par ligne, dont les clés sont les noms de la première ligne.

Contenu du fichier feuilles.csv
longueur,largeur,arbre
12.5,10.0,érable
9.0,4.5,chêne
10.5,8.5,érable
14.0,7.0,chêne
8.5,7.5,érable
11.5,5.5,chêne
Python
import csv

def charger(nom_fichier):
    with open(nom_fichier, encoding='utf-8') as f:
        return list(csv.DictReader(f))

feuilles = charger('feuilles.csv')

def convertir(table):
    for ligne in table:
        ...                    # à compléter
  • a) Que vaut feuilles[0] ? Quel est le type de feuilles[0]['longueur'] ?
  • b) Que produit feuilles[0]['longueur'] - 11.0 ? Que renvoie min([f['longueur'] for f in feuilles]) ? Pourquoi aucun des deux n'est-il le résultat voulu ?
  • c) Complétez convertir pour que longueur et largeur deviennent des flottants. Que renvoie alors la même expression min ? À quel moment faut-il appeler convertir ?
  • d) Une nouvelle feuille mesure 11,011{,}0 cm sur 7,57{,}5 cm. Avec les carrés des distances euclidiennes et k=3k=3, quel arbre l'algorithme prédit-il ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
Voir la correction

Réponses

  • a) {'longueur': '12.5', 'largeur': '10.0', 'arbre': 'érable'} ; le type est str
  • b) TypeError ; min renvoie '10.5', le plus petit dans l'ordre ALPHABÉTIQUE
  • c) ligne['longueur'] = float(ligne['longueur']) et de même pour largeur ; min vaut 8,58{,}5 ; une seule fois, juste après le chargement
  • d) Voisins F3 (1,251{,}25), F6 (4,254{,}25), F5 (6,256{,}25) : érable

a) feuilles[0] est le dictionnaire {'longueur': '12.5', 'largeur': '10.0', 'arbre': 'érable'}. Les guillemets autour de 12.5 ne sont pas une coquetterie d'affichage : un fichier CSV est un fichier TEXTE, et le module csv ne devine aucun type. Toutes les valeurs lues sont des chaînes, de type str, y compris celles qui ressemblent à des nombres. La première ligne du fichier n'apparaît pas dans la liste : DictReader s'en sert pour nommer les clés, ce qui évite l'erreur classique de traiter l'en-tête comme une donnée.

b) '12.5' - 11.0 lève une TypeError : Python refuse de soustraire un flottant à une chaîne. C'est l'erreur heureuse, car elle se voit dès le premier calcul de distance. L'expression min, elle, ne lève rien et renvoie '10.5'. Les chaînes se comparent caractère par caractère, dans l'ordre alphabétique : '10.5' commence par '1', qui précède '8' et '9', donc '10.5' est la plus « petite » chaîne alors que la plus petite longueur est 8,58{,}5. De même, un tri de la colonne rangerait '14.0' avant '8.5'. Un algorithme des k plus proches voisins qui trierait des chaînes produirait des voisins faux sans aucun message : c'est l'erreur malheureuse.

c) Dans la boucle : ligne['longueur'] = float(ligne['longueur']), puis la même ligne pour largeur. On ne convertit pas la colonne arbre, qui est une étiquette et doit rester une chaîne. Après conversion, min renvoie 8,58{,}5, un flottant, la vraie plus petite longueur. On appelle convertir UNE SEULE FOIS, juste après charger, et non dans la fonction de distance : une conversion placée dans la distance serait refaite à chaque comparaison, c'est-à-dire des milliers de fois par prédiction sur une grande table, pour un résultat toujours identique. Le fichier se nettoie au chargement ; l'algorithme travaille ensuite sur des nombres.

d) On numérote les feuilles F1 à F6 dans l'ordre du fichier. Écarts à (11 ;7,5)(11\,;7{,}5) et carrés : F1 (1,5 ;2,5)(1{,}5\,;2{,}5), 2,25+6,25=8,52{,}25+6{,}25=8{,}5 ; F2 (−2 ;−3)(-2\,;-3), 4+9=134+9=13 ; F3 (−0,5 ;1)(-0{,}5\,;1), 0,25+1=1,250{,}25+1=1{,}25 ; F4 (3 ;−0,5)(3\,;-0{,}5), 9+0,25=9,259+0{,}25=9{,}25 ; F5 (−2,5 ;0)(-2{,}5\,;0), 6,256{,}25 ; F6 (0,5 ;−2)(0{,}5\,;-2), 0,25+4=4,250{,}25+4=4{,}25. Les trois plus proches sont F3 érable, F6 chêne et F5 érable : deux voix contre une, prédiction érable. Les deux attributs sont ici dans la même unité et d'étendues voisines, 5,55{,}5 et 5,55{,}5 cm, donc la normalisation de l'exercice 6 n'est pas indispensable : elle se décide en regardant les étendues, pas par habitude.

Python
def convertir(table):
    for ligne in table:
        ligne['longueur'] = float(ligne['longueur'])
        ligne['largeur'] = float(ligne['largeur'])

feuilles = charger('feuilles.csv')
convertir(feuilles)          # une seule fois, au chargement
assert min([f['longueur'] for f in feuilles]) == 8.5

Exercice 8 : Cinq affirmations à corriger

Chacune des cinq affirmations ci-dessous est FAUSSE. Pour chacune, donnez l'argument ou le contre-exemple qui la réfute, en vous appuyant si possible sur un exercice de la série, puis écrivez l'énoncé correct.

  • a) « Plus kk est grand, meilleure est la prédiction, puisque davantage de voisins votent. »
  • b) « Supprimer la racine carrée de la distance euclidienne change les plus proches voisins. »
  • c) « Avec k=1k=1, l'algorithme ne se trompe jamais sur les exemples de sa propre table : c'est donc le meilleur choix de kk. »
  • d) « L'algorithme est rapide parce qu'il apprend une fois pour toutes ; ensuite il répond sans parcourir la table. »
  • e) « Avec deux classes, k=4k=4 évite les égalités aussi bien que k=3k=3. »

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Faux : pour k=nk=n l'algorithme renvoie toujours la classe la plus nombreuse de la table
  • b) Faux : la racine est croissante, l'ordre des distances ne change pas, 00 voisin modifié
  • c) Faux : chaque exemple est son propre voisin, à distance 00 ; il faut évaluer sur des éléments hors de la table
  • d) Faux : il n'y a pas d'apprentissage, chaque prédiction calcule nn distances
  • e) Faux : 44 voix peuvent se partager 22 contre 22 ; seul un kk impair l'interdit

a) Faux. Quand kk atteint le nombre nn d'exemples, toute la table vote, et la prédiction est la classe la plus nombreuse de la table, QUEL QUE SOIT l'élément à classer : à l'exercice 5, k=12k=12 répond abeille même pour un insecte entouré de syrphes. La distance ne joue plus aucun rôle. Énoncé correct : un kk trop petit suit les individus atypiques, un kk trop grand efface la notion de voisinage ; on cherche une valeur intermédiaire, et on la choisit en mesurant les erreurs sur des exemples de test, comme à l'exercice 9.

b) Faux. La racine carrée est strictement croissante sur les nombres positifs : si d12<d22d_{1}^{2}<d_{2}^{2} alors d1<d2d_{1}<d_{2}. Classer par carré de distance donne exactement le même rang à chaque élément, donc les mêmes kk voisins et la même prédiction ; aucun voisin ne change. C'est précisément ce qui a permis de travailler sans calculatrice aux exercices 3, 5 et 7. Énoncé correct : supprimer la racine ne change pas les voisins, mais change la VALEUR de la distance, ce qui compte dès qu'on la compare à un seuil fixé.

c) Faux. Pour un exemple de la table, le plus proche voisin est lui-même, à distance 00, et il porte évidemment sa propre classe : le taux de réussite de k=1k=1 sur la table vaut toujours 100 %100\ \%, même si le choix est mauvais. Mesurer ainsi, c'est corriger une copie en lui donnant son propre corrigé. À l'exercice 9, k=1k=1 ne réussit que deux oiseaux test sur quatre. Énoncé correct : la qualité d'un choix de kk se mesure sur des éléments de classe connue qui ne sont PAS dans la table.

d) Faux sur les deux moitiés. Les k plus proches voisins n'ont aucune phase d'apprentissage : ils ne construisent aucun résumé de la table, ils la gardent entière en mémoire. Tout le travail se fait au moment de la prédiction : pour une table de 1 0001\,000 lignes, chaque prédiction calcule 1 0001\,000 distances puis trie ou sélectionne. Énoncé correct : l'algorithme ne coûte rien avant la première question, et coûte au moins un parcours complet de la table à chaque question. C'est l'objet de l'exercice 10.

e) Faux. Avec k=4k=4 et deux classes, les voix peuvent se répartir 22 contre 22, et l'algorithme ne sait pas conclure ; à l'exercice 5, k=2k=2 donnait déjà une voix chacune. Un nombre impair de voix, en revanche, ne se coupe jamais en deux moitiés égales. Énoncé correct : avec deux classes, un kk impair garantit une majorité ; avec trois classes ou plus, aucun kk ne la garantit, et il faut écrire une règle de départage, comme à l'exercice 4.

Exercice 9 : Problème : choisir k sur des oiseaux test

Le pouillot véloce et le pouillot fitis sont deux petits oiseaux presque impossibles à distinguer à l'œil. Les bagueurs, qui les capturent pour les baguer puis les relâchent, mesurent la longueur de l'aile pliée, en millimètres : celle du fitis est en général plus longue. On utilise un seul attribut, donc la distance entre deux oiseaux est l'écart ∣a−b∣|a-b| entre leurs ailes.

Table d'exemples, 1111 oiseaux : véloces 5555, 5757, 5858, 6060, 6363 ; fitis 6161, 6464, 6565, 6767, 6868, 7070. Quatre autres oiseaux, dont l'espèce a été confirmée par le chant, servent d'oiseaux TEST : T1 62,562{,}5 fitis, T2 59,559{,}5 véloce, T3 61,561{,}5 véloce, T4 66,566{,}5 fitis.

545658606264666870T1T2T3T4● véloce○ fitis| oiseau testaile (mm)
  • a) Pour k=1k=1 : donnez le plus proche voisin de chaque oiseau test et la prédiction. Quel est le taux de réussite sur les quatre oiseaux test ?
  • b) Même travail pour k=3k=3.
  • c) Même travail pour k=11k=11.
  • d) Quel est le taux de réussite de k=1k=1 si l'on teste les 1111 oiseaux de la table eux-mêmes ? Pourquoi ce nombre ne prouve-t-il rien ? Quelle valeur de kk retenez-vous ?

Tape tes réponses, la page te dit juste ou faux 0/10

a)
b)
c)
d)
Voir la correction

Réponses

  • a) T1 : 6363, véloce (faux) ; T2 : 6060, véloce ; T3 : 6161, fitis (faux) ; T4 : 6767, fitis ; 50 %50\ \%
  • b) T1 fitis, T2 véloce, T3 véloce, T4 fitis : 100 %100\ \%
  • c) Toujours fitis (66 contre 55) : 50 %50\ \%
  • d) 100 %100\ \%, car chaque oiseau est son propre voisin à distance 00 ; on retient k=3k=3

a) T1 62,562{,}5 : le plus proche est le véloce 6363, à 0,50{,}5 ; prédiction véloce, FAUX. T2 59,559{,}5 : véloce 6060, à 0,50{,}5 ; véloce, juste. T3 61,561{,}5 : fitis 6161, à 0,50{,}5 ; fitis, FAUX. T4 66,566{,}5 : fitis 6767, à 0,50{,}5 ; fitis, juste. Deux réussites sur quatre, soit 50 %50\ \%, autant qu'en tirant à pile ou face. La figure montre pourquoi : les deux espèces se CHEVAUCHENT entre 6060 et 6363 mm, avec un véloce à grande aile (6363) et un fitis à petite aile (6161). Avec un seul votant, chaque oiseau test proche de la zone de chevauchement est classé d'après l'individu le plus atypique de la table.

b) T1 62,562{,}5 : 6363 véloce à 0,50{,}5, puis 6161 et 6464 fitis à 1,51{,}5 ; deux fitis contre un véloce, fitis, juste. T2 59,559{,}5 : 6060 véloce à 0,50{,}5, 6161 fitis et 5858 véloce à 1,51{,}5 ; véloce, juste. T3 61,561{,}5 : 6161 fitis à 0,50{,}5, 6060 et 6363 véloces à 1,51{,}5 ; véloce, juste. T4 66,566{,}5 : 6767, 6565 et 6868, trois fitis ; fitis, juste. Quatre sur quatre, 100 %100\ \%. On vérifie à chaque fois que le quatrième voisin est strictement plus loin que le troisième, à 2,52{,}5 : sinon le choix des trois votants serait ambigu. Avec trois votants, l'individu atypique est mis en minorité par ses deux voisins suivants.

c) Pour k=11k=11, toute la table vote pour chaque oiseau test : 66 fitis contre 55 véloces, la prédiction est TOUJOURS fitis. T1 et T4 sont justes, T2 et T3 faux : 50 %50\ \%. Le taux est le même qu'avec k=1k=1, pour la raison opposée : k=1k=1 écoute un seul individu, k=11k=11 n'écoute plus l'aile de l'oiseau du tout. Ce résultat dépend d'ailleurs uniquement de la composition de la table : il suffirait d'y ajouter deux véloces pour que k=11k=11 réponde toujours véloce.

d) Sur les 1111 oiseaux de la table, k=1k=1 réussit 1111 fois sur 1111, soit 100 %100\ \% : chaque oiseau a pour plus proche voisin lui-même, à distance 00, qui porte sa propre espèce. Ce nombre ne mesure que la mémoire de l'algorithme, pas sa capacité à classer un oiseau nouveau, qui est la seule chose qu'on lui demande. C'est pourquoi on garde des oiseaux TEST hors de la table. On retient k=3k=3, le seul des trois essais qui réussit tous les tests. Prudence toutefois : quatre oiseaux test, c'est 25 %25\ \% par oiseau, et un vrai choix de kk se ferait sur des dizaines d'oiseaux ; la méthode, elle, est exactement celle-ci.

Exercice 10 : Problème : l'algorithme complet et son coût, pour conseiller une taille

Un site de vente de vêtements conseille une taille, S, M ou L, à partir de la taille en centimètres et de la masse en kilogrammes du client. Sa table rassemble les clients qui ont gardé leur article. La fonction distance est celle de l'exercice 2 appliquée aux deux attributs, vote celle de l'exercice 4 ; il reste une ligne à écrire.

Extrait de la table, pour un nouveau client de 175175 cm et 7272 kg : C1 (165 ;60)(165\,;60) S, C2 (172 ;70)(172\,;70) M, C3 (178 ;75)(178\,;75) L, C4 (185 ;85)(185\,;85) L, C5 (180 ;80)(180\,;80) L, C6 (168 ;62)(168\,;62) S, C7 (176 ;68)(176\,;68) M.

Python
def k_plus_proches(table, cible, k):
    couples = []
    for client in table:
        couples.append((distance(client, cible), client['vetement']))
    ...                                   # à compléter : trier couples
    return [couples[i][1] for i in range(k)]

def predire(table, cible, k):
    return vote(k_plus_proches(table, cible, k))
  • a) Quelle instruction complète la ligne manquante : couples.sort(), couples = couples.sort() ou sorted(couples) ? Comment deux couples de même distance sont-ils rangés ? Quelle précondition sur k faut-il ajouter ?
  • b) Sur l'extrait, calculez les carrés des distances du nouveau client à C2, C7 et C3, puis donnez la taille conseillée pour k=3k=3 et pour k=5k=5.
  • c) La table complète compte n=40 000n=40\,000 clients et le site fait 500500 prédictions par jour. Combien de distances une prédiction calcule-t-elle ? Et une journée ?
  • d) Si le tri est un tri par insertion, combien de comparaisons fait-il au pire pour 40 00040\,000 couples ? Une autre version ne garde, pendant le parcours, que les k=5k=5 meilleurs couples dans une petite liste triée, en y insérant chaque nouveau couple à sa place : en comptant au plus kk comparaisons par client, combien en fait-elle ?

Tape tes réponses, la page te dit juste ou faux 0/10

a)
b)
c)
d)
Voir la correction

Réponses

  • a) couples.sort() ; à distance égale, par l'étiquette dans l'ordre alphabétique ; assert k <= len(table)
  • b) C2 : 1313 ; C7 : 1717 ; C3 : 1818 ; k=3k=3 : M ; k=5k=5 : égalité M, L à 22 voix, la règle de l'exercice 4 donne M
  • c) 40 00040\,000 distances par prédiction, 20 000 00020\,000\,000 par jour
  • d) 40 000×39 9992=799 980 000\frac{40\,000\times 39\,999}{2}=799\,980\,000 comparaisons au pire, contre au plus 40 000×5=200 00040\,000\times 5=200\,000

a) couples.sort() trie la liste sur place, et c'est ce qu'on veut puisque couples est une liste de travail créée pour cette prédiction. couples = couples.sort() remplace la liste par None, et la ligne suivante échoue ; sorted(couples) seul calcule une liste triée et la jette. Les couples sont des tuples, comparés d'abord sur la distance, puis, à distance égale, sur l'étiquette : 'L' avant 'M' avant 'S', un départage alphabétique arbitraire qu'il vaut mieux savoir. Enfin la tranche range(k) suppose k <= len(table), sinon couples[i] lève une IndexError : la précondition s'écrit assert 0 < k <= len(table).

b) Écarts au client (175 ;72)(175\,;72) : C2 (−3 ;−2)(-3\,;-2), 9+4=139+4=13 ; C7 (1 ;−4)(1\,;-4), 1+16=171+16=17 ; C3 (3 ;3)(3\,;3), 9+9=189+9=18. Les autres sont bien plus loin : C5 25+64=8925+64=89, C6 49+100=14949+100=149, C1 100+144=244100+144=244, C4 100+169=269100+169=269. Pour k=3k=3 : C2 M, C7 M, C3 L, taille conseillée M. Pour k=5k=5 : C5 L et C6 S s'ajoutent, et les voix se répartissent M 22, L 22, S 11. C'est une égalité, que la règle de l'exercice 4, avec >, tranche en faveur de M, l'étiquette du plus proche client. Sans règle écrite, le conseil dépendrait d'un détail d'implémentation. Les centimètres et les kilogrammes ont ici des étendues comparables, 2020 et 2525 environ, ce qui rend la normalisation moins urgente qu'à l'exercice 6.

c) Chaque prédiction parcourt TOUTE la table et calcule 40 00040\,000 distances : c'est le prix de l'absence de phase d'apprentissage, l'algorithme ne résume rien et recommence tout à chaque question. Sur une journée, 500×40 000=20 000 000500\times 40\,000=20\,000\,000 distances. Le calcul des distances est LINÉAIRE en nn : doubler la table double le travail de chaque prédiction. C'est acceptable ici, quelques dixièmes de seconde par prédiction, mais le coût ne diminue jamais avec l'usage, contrairement à un modèle qu'on aurait calculé une fois pour toutes.

d) Le tri par insertion fait au pire 1+2+⋯+(n−1)=n(n−1)21+2+\cdots+(n-1)=\dfrac{n(n-1)}{2} comparaisons, soit 40 000×39 9992=799 980 000\dfrac{40\,000\times 39\,999}{2}=799\,980\,000, près de huit cents millions pour UNE prédiction : le tri coûte alors bien plus que les distances. Or on n'a besoin que des cinq premiers. En gardant une liste triée d'au plus 55 couples et en y insérant chaque nouveau couple à sa place, par le geste du tri par insertion, chaque client coûte de l'ordre de k=5k=5 comparaisons : 40 000×5=200 00040\,000\times 5=200\,000 au total, environ 4 0004\,000 fois moins. Un couple plus éloigné que le cinquième de la liste est même écarté en une seule comparaison, ce qui arrive pour presque tous les clients. Trier toute la table pour n'en lire que le début est la dépense inutile type de cet algorithme.

Python
def k_plus_proches(table, cible, k):
    assert 0 < k <= len(table)
    meilleurs = []                 # au plus k couples, triés
    for client in table:
        c = (distance(client, cible), client['vetement'])
        if len(meilleurs) < k or c < meilleurs[-1]:
            meilleurs.append(c)
            i = len(meilleurs) - 1
            while i > 0 and meilleurs[i - 1] > meilleurs[i]:
                meilleurs[i - 1], meilleurs[i] = meilleurs[i], meilleurs[i - 1]
                i = i - 1
            if len(meilleurs) > k:
                meilleurs.pop()
    return [c[1] for c in meilleurs]
Chapitre précédent Traitement de données en tables et types construits Chapitre suivant Interactions homme-machine sur le Web

© Ahmed Squalli Houssaini. Série publiée sur www.letuteurscientifique.ca/exercices/nsi-k-plus-proches-voisins. Libre pour l'usage personnel et en classe ; sa republication ailleurs demande une autorisation écrite (mentions légales).

Voir aussi

Vous cherchez un tuteur en NSI à Montréal ?

Contactez-moi pour une première séance. Bachelier en informatique de McGill et maîtrise en informatique appliquée de Concordia, je travaille l'algorithmique sur des données réelles, jusqu'aux pièges qui ne lèvent aucune erreur.

Site par Studio Squalli