Améliorer les k plus proches voisins en pondérant chaque vote par…
Exercice supplémentaire · niveau 3 (difficile) · NSI (première), chapitre 8 — Dichotomie, voisins et gloutons · Les outils, et ce qu'ils coûtent
Énoncé
Améliorer les plus proches voisins en pondérant chaque vote par l'inverse de la distance. Construire un cas où cette version et la version simple ne donnent pas la même réponse.
Corrigé
def knn_pondere(exemples, point, k):
"""Chaque voisin vote avec un poids 1/(distance + eps)."""
assert 1 <= k <= len(exemples)
eps = 1e-9 # evite la division par zero sur un point connu
mesures = sorted([(distance(c, point), cl) for c, cl in exemples],
key=lambda x: x[0])
poids = {}
for d, classe in mesures[:k]:
poids[classe] = poids.get(classe, 0) + 1 / (d + eps)
return max(poids, key=lambda c: poids[c])
LOIN = [((0, 0), "A"), ((10, 10), "B"), ((11, 11), "B")]
assert knn(LOIN, (0.5, 0.5), 3) == "B" # 2 voix contre 1
assert knn_pondere(LOIN, (0.5, 0.5), 3) == "A" # le tres proche l'emporte
Le cas est volontairement caricatural, et il le fallait. Le point est à du seul , et à plus de des deux . Le vote simple, qui compte les têtes, choisit : deux voix contre une. Le vote pondéré donne au un poids de contre à chaque , et choisit . Une majorité de voisins lointains l'emporte sur un voisin manifestement pertinent — c'est ce défaut que la pondération corrige.
Le eps n'est pas une coquetterie. Si le point à classer figure exactement dans les exemples, sa distance est nulle et lèverait ZeroDivisionError. L'ajouter donne à ce voisin un poids énorme mais fini — ce qui est le comportement voulu : un point connu doit être classé comme lui-même.
Le prix à payer. La pondération introduit un choix arbitraire de plus — pourquoi et non ? Chaque réglage supplémentaire est une décision à documenter, et une occasion de tromperie si on l'ajuste jusqu'à obtenir le résultat qui arrange.
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.