Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.
Application
Habitats des renards et classification kNN
Le sujet cherche à prédire la présence de renards dans une zone en comparant végétation, proximité de l’eau, densité urbaine et disponibilité des proies. Les zones connues portent aussi le booléen presence_renard. Le texte annonce une échelle de 1 à 10, mais les données réelles contiennent également 0 : on conserve ces observations fournies sans les modifier. Le nouvel habitat vaut (5,2,4,6), dans cet ordre. L’enjeu informatique est de calculer une distance euclidienne, sélectionner les voisins puis lire correctement leur étiquette ; la prédiction du modèle ne constitue pas une observation de terrain.
| Zone dans le fichier | Végétation | Eau | Urbain | Proies | Renard |
|---|---|---|---|---|---|
| 0 | 9 | 6 | 0 | 4 | True |
| 1 | 10 | 5 | 9 | 10 | False |
| 2 | 8 | 5 | 1 | 6 | False |
Le dossier comporte le sujet et les fichiers listés dans les téléchargements. Les programmes de départ et un aperçu des grands jeux de données sont présentés ci-dessous ; les fichiers complets sont téléchargeables. Travaillez sur une copie. Les appels professeur signalés dans les questions servent à présenter votre démarche ; vous pouvez aussi demander de l’aide en cas de difficulté.
Données fournies : donnees_habitats.py
Le fichier donnees_habitats.py contient la liste zones_connues et ses 1 001 enregistrements. Voici les trois premiers, dans l’ordre du fichier.
vegetation | proximite_eau | densite_urbaine | disponibilite_proies | presence_renard |
|---|---|---|---|---|
| 9 | 6 | 0 | 4 | True |
| 10 | 5 | 9 | 10 | False |
| 8 | 5 | 1 | 6 | False |
Pour exécuter le programme sur toutes les données et retrouver les résultats du sujet, utilisez le fichier intégral fourni. Il est aussi inclus dans l’archive ZIP de cette page.
Code fourni : prediction_habitat.py
from math import sqrt
from donnees_habitats import zones_connues
nouveau = {'vegetation': 5, 'proximite_eau': 2,
'densite_urbaine': 4, 'disponibilite_proies': 6}
def distance(habitat_1, habitat_2):
'''
Calcule la distance euclidienne entre deux habitats.
entrée :
- habitat_1 : dictionnaire représentant un habitat.
- habitat_2 : dictionnaire représentant un autre habitat.
sortie :
- float : distance euclidienne entre habitat_1 et habitat_2.
'''
pass # à remplacer par votre code
def distance_d_un_habitat(habitat, habitats):
'''
Calcule la distance entre un habitat et chaque habitat de la liste.
entrée :
- habitat : dictionnaire représentant un habitat.
- habitats : liste de dictionnaires représentant des habitats.
sortie :
- list[tuple] : liste de tuples (distance, habitat) où distance est la distance entre habitat et chaque habitat de la liste.
'''
pass # à remplacer par votre code
def premiere_composante(c):
'''Fonction utilitaire renvoyant la première composante d'un tuple'''
return c[0]
def k_plus_proches(k, habitat, habitats):
'''
Calcule les k habitats les plus proches de l'habitat donné.
entrée :
- k : entier représentant le nombre d'habitats à retourner.
- habitat : dictionnaire représentant un habitat.
- habitats : liste de dictionnaires représentant des habitats.
sortie :
- list[tuple] : liste de tuples (distance, habitat) l'élément à l'indice 0 est la distance euclidienne entre habitat
et chaque habitat de la liste et l'élément à l'indice 1 est le dictionnaire correspondant à l'habitat correspondant.
'''
# On calcule les distances
distances = distance_d_un_habitat(habitat, habitats)
# On cherche à trier les distances en fonction de la distance euclidienne.
distances.sort(key=premiere_composante)
# renvoie les distances jusque la borne k non comprise
return distances[:k]
def presence_renard(k, habitat, habitats):
'''
Vérifie si l'habitat donné a plus de k/2 voisins avec des renards.
entrée :
- k : entier représentant le nombre d'habitats à considérer.
- habitat : dictionnaire représentant un habitat.
- habitats : liste de dictionnaires représentant des habitats.
sortie :
- bool : True si l'habitat a plus de k/2 voisins avec des renards, False sinon.
'''
habitats = k_plus_proches(k, habitat, habitats)
n_renards = 0
for habitat in habitats:
distance = habitat[0]
caracteristiques = habitat[1]
if distance['presence_renard']:
n_renards += 1
return n_renards > k/2
Question 1
#Écrire distance(habitat_1,habitat_2) selon la racine carrée de la somme des carrés des différences sur les quatre caractéristiques. Les dictionnaires possèdent au moins les quatre clés nécessaires.
Indice
Ne pas inclure presence_renard dans les coordonnées.
Comprendre la correction
def distance(habitat_1, habitat_2):
somme = 0
for cle in ['vegetation','proximite_eau','densite_urbaine','disponibilite_proies']:
somme += (habitat_1[cle] - habitat_2[cle]) ** 2
return sqrt(somme)On sélectionne explicitement les quatre clés numériques, car le booléen de présence sert d’étiquette à prédire et ne doit pas influencer la distance. Des clés supplémentaires ne doivent donc pas être parcourues automatiquement. Une distance nulle correspond à des caractéristiques identiques.
assert distance(nouveau,nouveau) == 0
assert distance(zones_connues[0],nouveau) == distance(nouveau,zones_connues[0])Question 2
#Écrire distance_d_un_habitat : recevoir un habitat et une liste d’habitats ; renvoyer une liste de tuples (distance, dictionnaire de l’habitat).
Comprendre la correction
def distance_d_un_habitat(habitat, habitats):
return [(distance(habitat, autre), autre) for autre in habitats]La liste garde l’ordre de l’entrée. Elle n’est pas encore triée : cette responsabilité appartient à k_plus_proches. Chaque tuple conserve à la fois une clé de classement et les caractéristiques nécessaires pour le vote.
Question 3
#Tester avec nouveau et zones_connues en affichant les trois premiers tuples. Les distances attendues sont 7.211102550927978,8.660254037844387 et 5.196152422706632.
Indice
La question demande les premiers du fichier, pas les plus proches.
Comprendre la correction
resultats = distance_d_un_habitat(nouveau,zones_connues)
for resultat in resultats[:3]:
print(resultat)Les carrés des distances valent 52,75 et 27. Par exemple pour la première zone : (5-9)²+(2-6)²+(4-0)²+(6-4)²=52. Les tuples doivent contenir exactement les trois dictionnaires reproduits dans le contexte. Ces trois premiers éléments ne sont pas les trois plus proches : ne pas ajouter un tri à cette étape.
Question 4
#Corriger presence_renard, qui doit renvoyer True si plus de la moitié des k habitats traités contiennent un renard. La fonction contient une erreur de traitement des tuples.
PythonFaire voter le dictionnaire, pas sa distanceÉcrivez votre solution et mettez-la à l’épreuve
Corrigez presence_renard(k, habitat, habitats). k_plus_proches, fourni, renvoie des tuples (distance, dictionnaire). Renvoyez True si strictement plus de la moitié des k voisins portent presence_renard=True. Respectez 1 ≤ k ≤ len(habitats). Les petits jeux h(position, presence) ne font varier que la végétation pour rendre les distances vérifiables mentalement.
def presence_renard(k, habitat, habitats):
# À vous de jouer
pass
Les cas de test proposés :
- Majorité de deux sur trois : Le booléen est dans la seconde composante de chaque tuple.
- Une égalité ne gagne pas : Deux voix sur quatre ne dépassent pas la moitié.
- Ne faire voter que les voisins retenus : Le classement sélectionne la zone proche à position 1, pas la majorité du fichier.
- Voisin unique positif : Une seule voix positive suffit lorsque k vaut 1.
- Ordre stable à distance égale : Le tri fourni conserve l’ordre d’origine pour les distances égales.
- Refuser un voisinage impossible : Le seuil doit correspondre à un nombre de voisins réellement disponibles.
Indice
Quel est le type de chaque composante du tuple ?
Comprendre la correction
Le code fourni lit distance["presence_renard"] alors que distance est le nombre situé à l’indice 0 du tuple. Le dictionnaire est la seconde composante. Le déballage explicite donne des noms aux deux rôles et évite cette confusion.
def presence_renard(k, habitat, habitats):
assert 1 <= k <= len(habitats)
voisins = k_plus_proches(k, habitat, habitats)
n_renards = 0
for distance_voisin, caracteristiques in voisins:
if caracteristiques['presence_renard']:
n_renards += 1
return n_renards > k / 2Le vote est strict : une égalité donne False. La précondition 1≤k≤len(habitats) garantit que l’on traite réellement k voisins. Sans elle, une tranche trop grande contiendrait moins de k éléments, mais le seuil utiliserait encore k/2.
Le tuple enrichit une observation sans la transformer : sa première composante permet le classement, sa seconde permet l’interprétation. Après le tri, la distance n’a plus besoin d’être recalculée pour voter. À chaque voisin, le compteur augmente de 1 seulement si son dictionnaire porte True. Avec trois voisins d’étiquettes False, True, True, on obtient 2 > 1,5 et donc True. Avec quatre voisins dont deux positifs, 2 > 2 est faux. Le test du seuil strict est donc différent d’un test de simple égalité de classes.
Lire le tuple avant de voterUn atelier pour expérimenter
Choisissez une réponse puis confrontez votre intuition à l’explication. Vous pouvez modifier vos choix.
Lire les réponses et leurs explications
- Dans (1.732, {presence_renard:
True}), où est l’étiquette ?Dans le dictionnaire de la seconde composante.. La seconde composante contient les caractéristiques.
- Avec k=4 et deux présences, le résultat est…
False. 2 n’est pas strictement supérieur à 4/2.
Appuyez votre explication sur les valeurs et les conditions exactes du programme.
Question 5
#L’habitat nouveau est-il susceptible de contenir des renards ? Expliquer en utilisant plusieurs valeurs de k.
Comprendre la correction
for k in [1,3,5,7,9,11,15]:
print(k, presence_renard(k,nouveau,zones_connues))| k | Voisins avec renard | Prédiction |
|---|---|---|
| 1 | 0 | False |
| 3 | 2 | True |
| 5 | 3 | True |
| 7 | 5 | True |
| 9 | 6 | True |
| 11 | 8 | True |
| 15 | 9 | True |
Les 1001 habitats réellement fournis donnent les résultats ci-dessus. Pour les valeurs 3 à 15 testées, la présence est majoritaire ; avec k=1, le premier voisin indique l’absence. Les trois plus proches ont tous la même distance √3, et le tri Python stable garde leur ordre dans le fichier. Le résultat k=1 est donc sensible à cet ordre. Avec k=1001,491 présences donnent False : un très grand voisinage peut perdre le caractère local de la prédiction. On ne doit pas choisir k uniquement pour obtenir la réponse désirée.
Le choix de k règle le compromis du modèle : un petit voisinage dépend fortement de quelques observations locales ; un grand voisinage inclut des zones moins proches et peut diluer une particularité locale. Les résultats de cette question rendent ce phénomène concret, mais ne suffisent pas à choisir le meilleur k. Pour cela, il faudrait évaluer le modèle sur des observations connues tenues à l’écart du vote. Au bac, l’attendu est ici de comparer plusieurs sorties et de les expliquer, sans annoncer une qualité prédictive qui n’a pas été mesurée.
Le vote change-t-il quand k augmente ?Un atelier pour expérimenter
Les quinze voisins ci-dessous sont ceux du fichier fourni, déjà classés par distance croissante. Faites varier k pour voir exactement quelles étiquettes participent au vote.
Lire le résultat de l’expérience initiale
2 présences sur 3 voisins : True.
Le vote demande une majorité stricte. Les égalités de distance gardent l’ordre initial du fichier, comme le tri Python fourni.
| Rang | Distance | Présence |
|---|---|---|
| 1 | √3 | False |
| 2 | √3 | True |
| 3 | √3 | True |
Une prédiction doit être expliquée par ses voisins, son vote et la convention sur les égalités.
Revoir les notions de cet exercice
Du sujet à la méthode
Votre prochaine séance de révision
- Garder séparés distance, tri et vote.
- Évaluer la sensibilité à k au lieu de présenter une prédiction comme une certitude.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26_BCG_NSI_11 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
