Pourquoi choisir impair avec deux classes
Exercice de TD · niveau 3 (difficile) · NSI (première), chapitre 8 — Dichotomie, voisins et gloutons · Les plus proches voisins
Énoncé
Pourquoi choisir impair avec deux classes ? Construire un cas où pair produit une égalité, vérifier que la réponse dépend alors de l'ordre des exemples, puis écrire une version qui départage.
Corrigé
Le cas d'égalité parfaite.
PROCHES = [((0, 0), "A"), ((0, 2), "A"), ((2, 0), "B"), ((2, 2), "B")]
milieu = (1, 1)
for c, _ in PROCHES:
assert abs(distance(c, milieu) - 2 ** 0.5) < 1e-12 # les QUATRE a egale distance
assert k_plus_proches_voisins(PROCHES, milieu, 4) == "A"
inverse = [((2, 0), "B"), ((2, 2), "B"), ((0, 0), "A"), ((0, 2), "A")]
assert k_plus_proches_voisins(inverse, milieu, 4) == "B"
Mêmes données, même point, même — et deux réponses opposées. Seul l'ordre des lignes a changé. C'est la conséquence du if n > meilleur_score avec un strict : à égalité, la première classe rencontrée l'emporte, et « la première » dépend de l'ordre du dictionnaire des votes, lui-même hérité de l'ordre des exemples. Un algorithme dont le résultat dépend de l'ordre de saisie n'est pas déterministe au sens où l'utilisateur l'entend.
impair, à deux classes, rend l'égalité impossible : deux entiers de somme impaire ne peuvent pas être égaux. La parade est gratuite, et c'est celle du cours. Elle ne vaut cependant que pour deux classes : avec trois classes et , on peut avoir --.
Départager pour de bon.
def knn_departage(exemples, point, k):
"""Vote majoritaire ; en cas d'egalite, la classe dont les voisins sont
globalement les plus proches (plus petite somme des distances)."""
assert 1 <= k <= len(exemples)
mesures = sorted([(distance(c, point), cl) for c, cl in exemples],
key=lambda x: x[0])
voisins = mesures[:k]
votes, cumul = {}, {}
for d, classe in voisins:
votes[classe] = votes.get(classe, 0) + 1
cumul[classe] = cumul.get(classe, 0) + d
maxi = max(votes.values())
exaequo = [c for c, n in votes.items() if n == maxi]
if len(exaequo) == 1:
return exaequo[0]
return min(exaequo, key=lambda c: cumul[c])
Un cas où les deux règles diffèrent vraiment.
decale = [((1, 0), "A"), ((9, 9), "A"), ((2, 2), "B"), ((3, 3), "B")]
p = (1, 1)
assert k_plus_proches_voisins(decale, p, 4) == "A" # la classe du PLUS PROCHE
assert knn_departage(decale, p, 4) == "B" # la plus proche EN MOYENNE
possède le voisin le plus proche () mais aussi le plus lointain () ; a ses deux représentants à distance moyenne. Les deux règles sont défendables — ce qui ne l'est pas, c'est de ne pas dire laquelle on applique. Le choix appartient à la spécification, pas au hasard de l'ordre des données.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.