Épreuve écrite · 2026 · Jour 1

Bac NSI 2026 Asie jour 1

Ce sujet met en scène une compagnie de danse, un perceptron et un réseau de routeurs. Il demande de relier des données correctement, de lire les effets d’un algorithme et de simuler des échanges. Les 34 corrections expliquent aussi les limites des modèles : un chargement glouton n’est pas forcément optimal et une simulation simplifiée de RIP ne gère pas toutes les pannes réelles. Durée : 3 h 30 sans calculatrice.

Dans ce sujet

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é.

Exercice 1 · 6 points

Compagnie de danse : relier les spectacles et charger le matériel

La compagnie L’air de l’art se déplace avec ses danseurs, costumes et accessoires. Jessica, sa secrétaire, utilise une base relationnelle pour relier les personnes, chorégraphies et représentations. Les adresses sont simplifiées à des villes et les codes postaux contiennent cinq chiffres. Le schéma est :

TableAttributsClé primaireClés étrangères
personneid, nom, adresse, codepid-
spectacleid, jour, lieu, codep, titreid-
choregraphienomchore, annee, choregraphenomchorechoregraphe → personne.id
participeidpersonne, idspectacle, idchoregraphie(idpersonne, idspectacle, idchoregraphie)idpersonne → personne.id ; idspectacle → spectacle.id ; idchoregraphie → choregraphie.nomchore

Le jour est au format AAAA-MM-JJ. Le nom de chorégraphie est unique ; participe associe une personne qui danse, une représentation et une chorégraphie. Le schéma nomme la ville du spectacle lieu, même si la phrase explicative parle d’adresse. Mots SQL autorisés : SELECT, DISTINCT, FROM, WHERE, JOIN ... ON, UPDATE ... SET, DELETE, INSERT INTO ... VALUES. COUNT(*) compte les lignes.

Question 1

#

Ajouter la chorégraphie Tout autour, créée en 2015 par Rachid Ouramdane d’identifiant 9.

Indice

La dernière colonne attend une référence à personne.id.

Comprendre la correction
INSERT INTO choregraphie (nomchore, annee, choregraphe)
VALUES ('Tout autour', 2015, 9);

Le chorégraphe est référencé par son identifiant, pas par son nom. La personne 9 doit exister pour respecter la clé étrangère. Le nom de chorégraphie doit être encore disponible puisqu’il constitue sa clé primaire.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 2

#

Afficher le nombre de chorégraphies créées en 1983.

Indice

La question porte sur la table des œuvres, pas sur les représentations.

Comprendre la correction
SELECT COUNT(*)
FROM choregraphie
WHERE annee = 1983;

On filtre les lignes sur l’année puis les compte. Il ne faut pas compter les participations à ces chorégraphies, qui pourraient se répéter pour plusieurs danseurs et spectacles.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)
SELECT choregraphie.nomchore, personne.nom
FROM choregraphie
JOIN personne ON choregraphie.choregraphe = personne.id;

Question 3

#

Décrire le résultat de la requête donnée.

Indice

Le rôle de la personne dépend de la clé utilisée pour la joindre.

Comprendre la correction

Elle renvoie le nom de chaque chorégraphie et le nom de la personne qui l’a chorégraphiée. La jointure suit choregraphe = personne.id. Elle ne renvoie pas tous les danseurs de cette œuvre : ce lien passerait par participe.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 4

#

Afficher les noms des danseurs ayant dansé dans un spectacle de titre Tout autour.

SQLRetrouver les danseurs du spectacle Tout autourÉcrivez votre solution et mettez-la à l’épreuve

Renvoyez les noms des personnes ayant dansé dans une représentation dont le titre est Tout autour, sans répéter une personne pour chacune de ses chorégraphies. Le titre du spectacle ne se confond pas avec le nom d’une chorégraphie.

SELECT ...
FROM ...
WHERE ...;

Les cas de test proposés :

  • Jeu pédagogique sur le schéma de la compagnie : Le nom du spectacle, la chorégraphie Tout autour et son chorégraphe 9 sont issus du sujet. Les représentations, danseurs et participations sont des compléments pédagogiques explicitement ajoutés.
  • Cas complémentaire : plusieurs représentations de même titre : Jeu complémentaire pédagogique, distinct des données officielles. Deux représentations portent le même titre. La recherche doit les couvrir toutes et éviter une répétition de Danseur C.
Indice

Suivez personne → participe → spectacle.

Comprendre la correction
SELECT DISTINCT p.nom
FROM personne AS p
JOIN participe AS pa ON pa.idpersonne = p.id
JOIN spectacle AS s ON s.id = pa.idspectacle
WHERE s.titre = 'Tout autour';

Le titre demandé est celui du spectacle, pas le nom de la chorégraphie. Une personne peut participer à plusieurs chorégraphies dans la même représentation ; DISTINCT évite de répéter son nom dans le résultat. Si plusieurs spectacles ont ce titre, toutes leurs participations sont concernées. Une projection sur le seul nom peut confondre des homonymes ; afficher aussi l’identifiant serait utile pour les distinguer, mais la question demande les noms.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 5

#

Quelles précautions prendre avant de supprimer un spectacle ? Sans écrire la requête.

Indice

Une suppression de parent ne doit pas laisser de participations orphelines.

Comprendre la correction

Vérifier les lignes de participe qui référencent ce spectacle et les supprimer ou les archiver selon le besoin avant d’effacer la ligne de spectacle, sauf cascade prévue. Sinon une clé étrangère pointerait vers un spectacle absent et le SGBD refuserait l’opération. Il faut cibler l’identifiant exact et préserver les autres personnes et chorégraphies, qui peuvent servir à d’autres représentations.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Partie B : chargement du véhicule

Chaque sous-dictionnaire contient nb, volume unitaire et poids unitaire :

dmateriel = {
  "costume_A": {
    "nb": 3,
    "volume": 2,
    "poids": 1
  },
  "costume_B": {
    "nb": 1,
    "volume": 6,
    "poids": 2
  },
  "chaise_bois": {
    "nb": 40,
    "volume": 50,
    "poids": 4
  },
  "chapeau_A": {
    "nb": 2,
    "volume": 3,
    "poids": 1
  },
  "chapeau_B": {
    "nb": 4,
    "volume": 7,
    "poids": 1
  }
}
n = 0
for clef in dmateriel:
    n += dmateriel[...][...]

Question 6

#

Compléter le total du nombre d’éléments nécessaires.

Indice

Le dictionnaire externe et les sous-dictionnaires ont des rôles différents.

Comprendre la correction
n += dmateriel[clef]['nb']

La première clé sélectionne le type de matériel ; la seconde sélectionne sa quantité. Le total vaut 3+1+40+2+4 = 50 objets. len(dmateriel) vaudrait seulement 5, nombre de types distincts, et ne répondrait pas à la question.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)
def tab_clefs_tab_volumes(dmatos):
    tab_clefs = []
    tab_volumes = []
    for clef, dico in dmatos.items():
        tab_clefs.append(...)
        tab_volumes.append(...)
    return tab_clefs, tab_volumes

Résultat attendu : ([costume_A,costume_B,chaise_bois,chapeau_A,chapeau_B], [2,6,50,3,7]), avec les noms représentés comme chaînes.

Question 7

#

Compléter tab_clefs_tab_volumes pour obtenir les clés et leurs volumes unitaires dans deux listes correspondantes.

Indice

Les deux listes doivent conserver la même correspondance d’indices.

Comprendre la correction
tab_clefs.append(clef)
tab_volumes.append(dico["volume"])

items() fournit à chaque tour une clé et le sous-dictionnaire associé. Les deux append sont effectués dans le même tour : les indices restent alignés entre les listes. Le volume demandé est unitaire, pas nb × volume. Sur l’exemple, les volumes sont [2,6,50,3,7].

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 8

#

Charger les objets les plus volumineux d’abord puis les petits relève de quel type d’algorithme ?

Indice

Le choix se fait localement sans explorer toutes les combinaisons.

Comprendre la correction

Une stratégie gloutonne : à chaque étape, on fait un choix local immédiat, ici le plus gros objet qui peut encore entrer. Elle ne revient pas sur les objets déjà chargés pour réorganiser le contenu. Une telle stratégie peut être rapide et intuitive sans garantir le minimum global de véhicules.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

clefs_triees_selon_volume(dmatos) est supposée disponible et trie les clés par volumes unitaires croissants. Pour dmateriel : [costume_A,chapeau_A,costume_B,chapeau_B,chaise_bois].

Question 9

#

Citer un tri vu en cours et un ordre de grandeur de son coût en fonction de n.

Indice

Un nom de tri doit être accompagné du bon ordre de grandeur.

Comprendre la correction

Par exemple le tri par insertion, O(n²) dans le pire cas. Autre réponse valable au programme de Terminale : le tri fusion, O(n log n) dans le pire cas. Il faut préciser à quel cas correspond le coût : le tri par insertion est linéaire sur une liste déjà triée, mais ce n’est pas son pire cas.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)
def remplissage(dmatos, vmax):
    vol = 0
    clefs_triees = clefs_triees_selon_volume(dmatos)
    i = len(clefs_triees) - 1
    elements = []
    while i >= ... and vol < ...:
        clef = clefs_triees[i]
        if dmatos[clef]["nb"] == 0:
            i -= 1
        elif vol + dmatos[clef]["volume"] <= vmax:
            vol += ...
            elements.append(...)
            dmatos[clef]["nb"] -= 1
        else:
            ...
    return elements

Exemple attendu : remplissage(dmateriel,160) renvoie [chaise_bois,chaise_bois,chaise_bois,chapeau_B,chapeau_A].

Question 10

#

Compléter remplissage, qui charge sans dépasser vmax et met à jour les quantités restantes.

PythonCharger le décor et mettre à jour le stockÉcrivez votre solution et mettez-la à l’épreuve

Complétez remplissage(dmatos, vmax). Chaque type de matériel possède nb et volume. Le helper fournit les clés par volume croissant ; chargez donc les types en commençant par la fin de cette liste. Prenez autant d’exemplaires que possible avant de passer au type suivant, sans dépasser vmax. Renvoyez la liste des noms chargés et décrémentez les stocks dans dmatos. Le poids n’intervient pas dans cette question.

def remplissage(dmatos, vmax):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Le chargement de 160 du sujet : 150 de chaises, puis 7 et 3 de chapeaux remplissent la voiture. Seuls les stocks chargés diminuent.
  • Un objet trop gros ne bloque pas les petits : Refuser le plus gros doit faire avancer vers un type plus petit.
  • Une égalité exacte avec la capacité : Le dernier objet peut atteindre exactement vmax.
  • Un type épuisé est sauté : Une quantité nulle ne doit pas devenir négative.
  • Ce glouton n’optimise pas toutes les combinaisons : La règle imposée retient 8, même si 6 + 4 remplirait mieux. Il faut appliquer le sujet, sans inventer une autre stratégie.
  • Aucune capacité et aucun stock : Une boucle sans candidat ou sans place termine sans modifier le matériel.
Indice

Après un ajout réussi, ne diminuez pas i : il peut rester plusieurs exemplaires du même type.

Comprendre la correction
def remplissage(dmatos, vmax):
    vol = 0
    clefs_triees = clefs_triees_selon_volume(dmatos)
    i = len(clefs_triees) - 1
    elements = []
    while i >= 0 and vol < vmax:
        clef = clefs_triees[i]
        if dmatos[clef]['nb'] == 0:
            i -= 1
        elif vol + dmatos[clef]['volume'] <= vmax:
            vol += dmatos[clef]['volume']
            elements.append(clef)
            dmatos[clef]['nb'] -= 1
        else:
            i -= 1
    return elements

Le tri est croissant mais l’indice commence à la fin : les plus gros objets sont donc examinés d’abord. Si un objet entre, on l’ajoute, réduit sa quantité et garde le même indice pour tenter un autre exemplaire. Si son stock est nul ou s’il ne tient plus, on passe au type suivant, plus petit. Chaque itération réduit une quantité ou diminue i, donc la boucle progresse. L’invariant de volume est 0 ≤ vol ≤ vmax.

Pour vmax = 160, trois chaises occupent 150 ; un chapeau_B ajoute 7 et un chapeau_A ajoute 3, soit exactement 160. Les quantités restantes deviennent 37 chaises, 3 chapeaux_B et 1 chapeau_A, les costumes restant inchangés. La fonction ne vérifie pas une capacité en poids : le champ poids est fourni mais la consigne de cette partie ne porte que sur le volume.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)

Question 11

#

Écrire nb_voitures(dmatos,vmax), nombre de véhicules nécessaires selon remplissage.

Indice

Il faut une condition garantissant qu’une voiture vide signifie réellement un stock épuisé.

Comprendre la correction
def nb_voitures(dmatos, vmax):
    for clef in dmatos:
        if dmatos[clef]["nb"] > 0 and dmatos[clef]["volume"] > vmax:
            raise ValueError("Un objet dépasse la capacité du véhicule")
    nombre = 0
    while remplissage(dmatos, vmax) != []:
        nombre += 1
    return nombre

Chaque appel remplit un nouveau véhicule et retire les objets chargés du stock. La première liste vide indique que plus rien ne reste à charger, sous la condition que chaque objet puisse entrer seul. La vérification initiale explicite cette condition ; sans elle, un objet trop volumineux pourrait rester alors que la fonction renvoie []. On suppose aussi des volumes strictement positifs, des quantités non négatives et une capacité positive.

Le nombre obtenu est celui de cette stratégie gloutonne, pas une garantie de minimum absolu. La fonction consomme le stock en place : pour conserver l’inventaire initial, il faudrait travailler sur une copie des sous-dictionnaires, pas seulement sur une copie superficielle du dictionnaire externe.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)
Remplir la camionnette et observer ce qui resteUn atelier pour expérimenter

Réglez la capacité d’un véhicule. Le modèle recommence toujours avec le stock original, applique exactement le chargement glouton du sujet et affiche les objets retenus.

Lire le résultat de l’expérience initiale

160 / 160 unités chargées

La stratégie prend autant de gros objets que possible puis passe aux plus petits. Les objets de volume supérieur à la capacité resteront impossibles à transporter dans ce type de véhicule.

MatérielQuantité restanteVolume unitaire
costume_A32
costume_B16
chaise_bois3750
chapeau_A13
chapeau_B37

Un algorithme glouton fournit une solution selon sa règle locale. Il faut vérifier séparément la faisabilité de tous les objets et l’optimalité globale.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Perceptron : de la somme pondérée à l’apprentissage supervisé

Dans le scénario du concours Jeunes Talents IA 2025, une équipe de Terminale construit un perceptron pour classer des emails. L’apprentissage est supervisé : chaque exemple vient avec une réponse cible. Un perceptron reçoit n entrées xᵢ, possède n poids pᵢ et un biais b, puis calcule somme = Σ xᵢpᵢ + b. La fonction seuil vaut 1 si cette somme est positive ou nulle, 0 sinon.

Entrées x1, x2 → multiplication par p1, p2
                       ↓
                somme x1*p1 + x2*p2 + b
                       ↓
                  seuil à zéro
                       ↓
                 sortie 0 ou 1

Le schéma original illustre deux entrées et un biais ajouté avant le seuil. On considère d’abord p1=0,7, p2=-0,2 et b=0,1.

Question 1

#

Calculer la somme puis la sortie pour x1=1 et x2=3.

Indice

Le produit associé à x2 vaut -0,6.

Comprendre la correction

Somme = 1×0,7 + 3×(-0,2) + 0,1 = 0,2. Elle est positive, donc la sortie vaut 1. Le biais est ajouté une fois, après la somme des produits. Les entrées ne sont pas les poids : x2=3 multiplie bien le poids négatif -0,2.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
class DetecteurSpam:
    def __init__(self, nb_entrees):
        self.biais = 0.0
        self.poids = [0.0 for i in range(nb_entrees)]
    def get_biais(self):
        return self.biais
    def set_biais(self, nouveau_biais):
        self.biais = nouveau_biais
    def get_poids(self):
        return self.poids
    def set_poids(self, nouveaux_poids):
        assert len(nouveaux_poids) == len(self.poids)
        self.poids = nouveaux_poids

Question 2

#

Donner un nom de méthode et un nom d’attribut de DetecteurSpam.

Indice

Repérez les affectations self.nom et les définitions def.

Comprendre la correction

set_biais est une méthode ; poids est un attribut. biais est également un attribut ; get_biais, get_poids et set_poids sont d’autres méthodes. Le paramètre nb_entrees n’est pas conservé comme attribut portant ce nom.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Question 3

#

Créer detecteur à deux entrées et fixer ses poids à [0.7,-0.2] et son biais à 0.1 en utilisant les méthodes.

Indice

Initialisez la bonne dimension avant de remplacer le vecteur de poids.

Comprendre la correction
detecteur = DetecteurSpam(2)
detecteur.set_poids([0.7, -0.2])
detecteur.set_biais(0.1)

Le constructeur initialise deux poids nuls ; les méthodes les remplacent ensuite. Le nombre 2 indique la dimension du modèle, pas le nombre d’exemples de son entraînement.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Question 4

#

Expliquer le rôle de l’assertion de longueur dans set_poids.

Indice

Une somme pondérée demande une correspondance un-à-un entre entrées et poids.

Comprendre la correction

Elle vérifie que le nouveau vecteur possède autant de poids que le modèle a d’entrées. Si les longueurs diffèrent, une AssertionError interrompt l’affectation. Elle ne vérifie ni le type numérique des poids ni leur pertinence pour classer des emails. Le corps de la méthode utilise le nom nouveaux_poids ; la variante singulière dans la phrase du sujet est une coquille.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Question 5

#

Écrire fonction_activation(self,valeur), seuil renvoyant 1 pour valeur≥0, 0 sinon.

Indice

L’égalité à zéro doit être incluse.

Comprendre la correction
def fonction_activation(self, valeur):
    if valeur >= 0:
        return 1
    return 0

Le cas zéro appartient à la classe 1. Ce choix influence les premiers exemples puisque le constructeur crée des poids et un biais nuls : au départ, toutes les sommes valent 0 et toutes les prédictions valent donc 1.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)
def prediction(self, entrees):
    somme = 0
    for i in range(len(entrees)):
        somme += ...
    somme += ...
    sortie = self.fonction_activation(...)
    return ...

Exemple fourni à trois entrées : biais 0.2, poids [0.5,-0.1,0.2], prediction([0.1,0.3,0.2]) vaut 1.

Question 6

#

Compléter prediction(entrees).

Indice

Ne confondez pas la somme réelle et la sortie binaire.

Comprendre la correction
def prediction(self, entrees):
    somme = 0
    for i in range(len(entrees)):
        somme += entrees[i] * self.poids[i]
    somme += self.biais
    sortie = self.fonction_activation(somme)
    return sortie

Chaque entrée est multipliée par son poids au même indice. Le biais est ajouté hors de la boucle, exactement une fois. L’activation est ensuite appelée sur la somme complète. Pour les poids [0.5,-0.1,0.2], le biais 0.2 et les entrées [0.1,0.3,0.2], la somme vaut 0.05-0.03+0.04+0.2 = 0.26, donc la sortie est 1. Le sujet garantit ici l’égalité des dimensions.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 7

#

Donner le coût temporel de prediction en fonction de n entrées.

Indice

La méthode parcourt une fois la liste des entrées.

Comprendre la correction

O(n), linéaire : n produits et n accumulations dans la boucle, suivis d’un ajout et d’un test de coût constant. Le nombre d’exemples d’entraînement n’intervient pas dans le coût d’une seule prédiction.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
echantillon = [[0.5,0.3], [0.2,0.1], [0.4,0.8], [0.7,0.9]]
cibles = [0,1,1,1]

Question 8

#

Donner echantillon[1] et echantillon[2][1].

Indice

Les deux niveaux d’indices commencent à zéro.

Comprendre la correction

echantillon[1] = [0.2,0.1] et echantillon[2][1] = 0.8. Le premier accès choisit le deuxième exemple, le second la deuxième caractéristique du troisième exemple.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Une époque parcourt tous les exemples. En cas d’erreur : nouveau poids = ancien poids + taux×erreur×entrée ; nouveau biais = ancien biais + taux×erreur.

def entrainement(self, echantillon, cibles, taux=0.1):
    nb_erreurs = 0
    for i in range(len(echantillon)):
        entrees = echantillon[i]
        cible = ...
        predict = ...
        if ...:
            nb_erreurs += 1
            erreur = cible - predict
            for j in range(len(self.poids)):
                self.poids[j] += ... * ... * entrees[j]
            self.biais += taux * erreur
    return nb_erreurs

Question 9

#

Compléter les lignes 5,7,8,13 de entrainement.

PythonApprendre en corrigeant une erreur à la foisÉcrivez votre solution et mettez-la à l’épreuve

Complétez entrainement dans DetecteurSpam. Une époque parcourt les vecteurs echantillon et leurs cibles 0 ou 1 de même indice. Le helper prediction utilise les poids courants et un seuil qui renvoie 1 dès que la somme vaut zéro ou davantage. À chaque erreur, ajoutez taux × (cible - prédiction) × entrée à chaque poids et taux × (cible - prédiction) au biais. Renvoyez le nombre d’erreurs rencontrées pendant ce passage, pas un nouveau score calculé après toutes les mises à jour.

class DetecteurSpam(DetecteurSupport):
    def entrainement(self, echantillon, cibles, taux=0.1):
        # Une époque d’apprentissage
        pass

Les cas de test proposés :

  • Une prédiction déjà correcte : Les paramètres nuls prédisent 1, selon le seuil du sujet. Un exemple correct ne modifie rien.
  • Une erreur vers la classe zéro : L’erreur cible-prédiction vaut -1 ; chaque poids reçoit une correction adaptée à son entrée.
  • Une erreur vers la classe un : Une entrée nulle ne change pas son poids, mais le biais reçoit tout de même la correction.
  • Le deuxième exemple voit les nouveaux paramètres : Le premier exemple fait passer le score à -2. Le second devient alors une erreur lui aussi ; les prédictions ne doivent pas être pré-calculées.
  • Utiliser le taux par défaut : Le taux par défaut vaut 0.1. Les comparaisons de flottants tolèrent ici uniquement l’arrondi machine.
  • Une époque sans exemple : Sans exemple, aucune erreur ni mise à jour : la fonction doit quand même retourner un entier.
Indice

Utilisez la prédiction avant mise à jour pour calculer cible - predict.

Comprendre la correction
def entrainement(self, echantillon, cibles, taux=0.1):
    nb_erreurs = 0
    for i in range(len(echantillon)):
        entrees = echantillon[i]
        cible = cibles[i]
        predict = self.prediction(entrees)
        if predict != cible:
            nb_erreurs += 1
            erreur = cible - predict
            for j in range(len(self.poids)):
                self.poids[j] += taux * erreur * entrees[j]
            self.biais += taux * erreur
    return nb_erreurs

La cible et l’exemple partagent le même indice i. L’erreur signée vaut +1 si l’on aurait dû répondre 1, -1 si l’on aurait dû répondre 0. Elle oriente la mise à jour du biais et de chaque poids. Un exemple correctement classé ne change rien. Les poids modifiés sont immédiatement utilisés pour l’exemple suivant : l’ordre des exemples influence donc la trajectoire de l’apprentissage.

Le compteur indique les erreurs rencontrées pendant cette époque, avant chaque correction locale, pas forcément le nombre d’exemples mal classés par le modèle final de l’époque. En revanche, une époque sans erreur ne modifie aucun poids et prouve que le modèle courant classe correctement tous les exemples parcourus.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
URGENTGRATUITSPAM attendu
000
010
100
111

Question 10

#

Créer un détecteur apprenant URGENT ET GRATUIT en au plus 1000 époques ; afficher le nombre nécessaire pour une époque sans erreur, ou -1.

Indice

Arrêtez-vous sur une époque entière sans mise à jour, pas après un seul exemple correct.

Comprendre la correction
emails = [[0,0], [0,1], [1,0], [1,1]]
spams = [0,0,0,1]
detecteur = DetecteurSpam(2)
for epoque in range(1, 1001):
    erreurs = detecteur.entrainement(emails, spams)
    if erreurs == 0:
        print(epoque)
        break
else:
    print(-1)

range(1,1001) autorise exactement mille époques, comptées à partir de 1. Le else de la boucle ne s’exécute que si aucun break n’a été rencontré : -1 signifie alors « pas d’époque sans erreur dans la limite fixée ». Il ne prouve pas une impossibilité mathématique d’apprentissage. On peut aussi écrire la même logique avec while et un compteur.

La cible correspond à une porte ET, qui est linéairement séparable. Par exemple poids [1,1], biais -1.5 distinguent le seul point (1,1) des trois autres. Ce petit modèle n’est pas un détecteur fiable du spam réel : il apprend uniquement la règle et les exemples choisis. Près du seuil zéro, les arrondis flottants peuvent changer le nombre exact d’époques observé ; on évite donc de confondre ce nombre dépendant de l’implémentation avec une propriété universelle de ET.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
Faire apprendre la porte ET, une époque après l’autreUn atelier pour expérimenter

Faites varier le nombre d’époques. Pour lire les mises à jour exactement sans arrondi décimal, cet atelier utilise un taux de 1. Les règles de mise à jour et le seuil sont ceux du sujet.

Lire le résultat de l’expérience initiale

Poids [1, 1], biais 0

Le seuil renvoie 1 pour une somme égale à zéro. Une époque avec zéro erreur confirme que tous les exemples sont corrects et qu’aucun paramètre n’a changé.

ÉpoqueErreurs rencontréesPoids URGENTPoids GRATUITBiais
12110

Une règle d’apprentissage ajuste des paramètres sur des exemples étiquetés. La qualité se mesure sur la règle et les données, pas sur le nom « IA ».

Revoir les notions de cet exercice

Exercice 3 · 8 points

Simuler RIP : masques, files et échanges de vecteurs

On simule la construction des tables de routage RIP. Le réseau de la figure 1 forme une chaîne R1-R2-R3 :

SegmentInterfaces et machines
LAN 192.168.20.0/24PC1 192.168.20.1, switch, R1 192.168.20.254
Lien 172.24.0.0/30R1 172.24.0.1 ; R2 172.24.0.2
Lien 172.24.1.0/30R2 172.24.1.2 ; R3 172.24.1.1
LAN 192.168.40.0/24R3 192.168.40.254, switch, PC2 192.168.40.1
172.24.0.0/30172.24.1.0/30R1R2R3
Chaîne des trois routeurs de la figure 1
Lire les connexions du schéma
  • R1 relié à R2 : 172.24.0.0/30
  • R2 relié à R3 : 172.24.1.0/30

Les adresses IP sont des suites de 32 bits. Le suffixe S de IP/S fixe les S premiers bits du réseau ; deux machines d’un même sous-réseau partagent ces bits. Deux adresses sont réservées au réseau et à la diffusion.

Question 1

#

Identifier la partie fixe dans 192.168.20.0/24.

Indice

Un octet contient huit bits.

Comprendre la correction

Les trois premiers octets : 192.168.20, soit 24 bits. Le dernier octet est la partie machine et ne fait pas partie du préfixe fixe.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

Question 2

#

Quel nombre maximal de machines peut-on brancher sur un /30 ?

Indice

Calculez 2² puis retirez réseau et diffusion.

Comprendre la correction

2 : 32-30 = 2 bits machine, donc 4 adresses dont deux réservées. Un /30 est ainsi adapté au lien point à point entre deux interfaces.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

RIP minimise les routeurs traversés. Table fournie pour R1 :

DestinationInterfacePasserelleDistance
192.168.20.0/24192.168.20.254-0
172.24.0.0/30172.24.0.1-0
172.24.1.0/30172.24.0.1172.24.0.21
192.168.40.0/24172.24.0.1172.24.0.22

Question 3

#

Donner la table RIP de R2.

Indice

Interface désigne la sortie locale ; passerelle désigne le prochain routeur.

Comprendre la correction
DestinationInterface de R2PasserelleDistance
172.24.0.0/30172.24.0.2- 0
172.24.1.0/30172.24.1.2- 0
192.168.20.0/24172.24.0.2172.24.0.11
192.168.40.0/24172.24.1.2172.24.1.11

Les deux réseaux de liaison sont directement connectés à R2, donc sans passerelle intermédiaire et à distance 0 dans la convention du sujet. Pour les LAN distants, l’interface est celle de R2, tandis que la passerelle est l’adresse du voisin sur le lien partagé. Ces deux colonnes ne doivent pas être échangées.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Partie B : comparer des préfixes avec un masque

Les adresses et masques sont des tuples de quatre entiers. Le masque place à 1 les bits réseau et à 0 les bits machine.

Question 4

#

Donner le tuple du masque de suffixe 30.

Indice

Le dernier octet contient six 1 suivis de deux 0.

Comprendre la correction
(255, 255, 255, 252)

Les 24 premiers bits sont trois octets à 255. Le dernier octet a six bits réseau à 1 puis deux bits machine à 0 : 11111100₂ = 252.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
  01110110  (118)
& 11010010  (210)
= 01010010   (82)

L’opérateur Python & applique ce ET directement aux entiers : 118 & 210 vaut 82.

Question 5

#

Pourquoi 255 & A = A pour un entier A entre 0 et 255 ?

Indice

Un bit ET 1 conserve sa valeur.

Comprendre la correction

255 s’écrit 11111111 sur huit bits. Le ET bit à bit conserve chaque bit de A, car 0 ET 1 = 0 et 1 ET 1 = 1. La restriction 0≤A≤255 garantit que A tient dans ces huit positions.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Question 6

#

(172,20,189,8) et (172,20,191,203) sont-elles dans le même réseau avec (255,255,252,0) ? Justifier.

Indice

Le masque peut fixer seulement une partie du troisième octet.

Comprendre la correction

Oui. Les deux premiers octets sont conservés et identiques ; le dernier est annulé par &0. Pour le troisième : 189 = 10111101, 191 = 10111111 et 252 = 11111100. Dans les deux cas, le ET donne 10111100 = 188. Les deux adresses masquées sont donc (172,20,188,0), réseau /22.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
def meme_reseau(adresse1, adresse2, masque):
    for i in range(4):
        octet1 = adresse1[...] & masque[...]
        octet2 = ...
        if ...:
            return ...
    return ...

Question 7

#

Compléter meme_reseau pour comparer les deux adresses masquées.

Indice

Comparer l’adresse brute serait trop strict : les bits machine peuvent différer.

Comprendre la correction
def meme_reseau(adresse1, adresse2, masque):
    for i in range(4):
        octet1 = adresse1[i] & masque[i]
        octet2 = adresse2[i] & masque[i]
        if octet1 != octet2:
            return False
    return True

Une seule différence de préfixe suffit à conclure que les réseaux diffèrent. True est renvoyé après les quatre octets, sinon on ne contrôlerait que le premier. Les entrées sont supposées des tuples d’octets valides et le masque un masque réseau cohérent.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

Partie C : simulation objet

File fournit enfiler, defiler et est_vide. Reseau.connexion(file) attribue une adresse et associe cette adresse à la file reçue. Un paquet est le couple (orig,donnees). Tout paquet du réseau est distribué à toutes ses machines sauf l’origine.

class Reseau:
    def __init__(self, adresse, masque):
        self.adresse = adresse
        self.masque = masque
        self.machines = {}
        self.file = File()

    def diffuser(self):
        while not ...:
            paquet = ...
            orig, donnees = paquet
            for m in self.machines:
                if ...:
                    self.machines[m].enfiler(...)

Question 8

#

Compléter Reseau.diffuser pour distribuer chaque paquet à toutes les machines sauf son émettrice.

Indice

Les routeurs doivent connaître l’émetteur du vecteur reçu.

Comprendre la correction
def diffuser(self):
    while not self.file.est_vide():
        paquet = self.file.defiler()
        orig, donnees = paquet
        for m in self.machines:
            if m != orig:
                self.machines[m].enfiler(paquet)

On vide progressivement la file du réseau. machines associe une adresse à la file de réception de cette machine. Le paquet complet doit être envoyé, en conservant l’adresse orig dont le routeur aura besoin comme passerelle dans sa mise à jour. Envoyer seulement donnees ferait perdre cette origine. Le réseau diffusant à toutes les machines est une simplification explicite du simulateur, pas une description générale de toute communication Ethernet.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
class Interface:
    def __init__(self, reseau):
        self.sortie = reseau.file
        self.file = File()
        self.adresse = reseau.connexion(self.file)
        self.masque = reseau.masque

class Routeur:
    def __init__(self, nom):
        self.nom = nom
        self.interfaces = []
        self.table = {}

    def branchement(self, reseau):
        k = len(self.interfaces)
        self.table[reseau.adresse] = (k, None, 0)
        self.interfaces.append(Interface(reseau))

Les vecteurs sont des couples (adresse réseau,distance). Initialement seules les destinations connectées sont présentes à distance 0. Une information voisine ajoute un réseau inconnu ou remplace une route strictement plus longue, selon Bellman-Ford. La table associe chaque destination à (indice interface,passerelle,distance). Une passerelle None indique une connexion directe. Si la table change, envoyer_vecteurs est appelé sur les autres interfaces.

routeur_m.table = {
    (172,64,0,0): (1, None, 0),
    (172,60,0,0): (0, (172,58,0,2), 1),
    (192,168,47,0): (0, (172,58,0,2), 2),
    (172,62,0,0): (0, (172,58,0,2), 2)
}
vecteurs = [((192,168,72,0),0), ((172,62,0,0),0),
            ((172,64,0,0),0), ((172,60,0,0),1),
            ((192,168,47,0),1)]

Question 9

#

Donner routeur_m.table après m_a_j((172,64,0,2),1,vecteurs).

Indice

Toute distance annoncée par le voisin doit augmenter de 1 avant comparaison.

Comprendre la correction
{
    (172,64,0,0): (1, None, 0),
    (172,60,0,0): (0, (172,58,0,2), 1),
    (192,168,47,0): (0, (172,58,0,2), 2),
    (172,62,0,0): (1, (172,64,0,2), 1),
    (192,168,72,0): (1, (172,64,0,2), 1)
}

Le réseau 192.168.72.0 est nouveau : on l’ajoute avec la distance annoncée 0 plus un saut vers le voisin. Pour 172.62.0.0, la nouvelle distance 1 améliore l’ancienne 2 : on remplace interface et passerelle. Le réseau directement connecté 172.64.0.0 garde sa distance 0. Les propositions vers 172.60.0.0 (coût 2 contre 1) et 192.168.47.0 (2 contre 2) n’améliorent rien, donc sont ignorées. Une égalité ne modifie pas le choix existant dans ce code.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)
def m_a_j(self, passerelle, i_interf, vecteurs):
    a_ete_modif = False
    for adresse, d in vecteurs:
        if ... or ...:
            self.table[adresse] = (i_interf, passerelle, d + 1)
            a_ete_modif = ...
    if a_ete_modif:
        self.envoyer_vecteurs(i_interf)

Question 10

#

Compléter m_a_j aux lignes 4 et 6.

Indice

Comparez la distance candidate d+1 à la troisième composante de la route existante.

Comprendre la correction
if adresse not in self.table or d + 1 < self.table[adresse][2]:
    self.table[adresse] = (i_interf, passerelle, d + 1)
    a_ete_modif = True

L’ordre des deux termes de OR protège l’accès à une clé absente : si l’adresse est nouvelle, le second terme n’est pas évalué. La composante d’indice 2 contient la distance. Le drapeau conserve True dès qu’une modification intervient, afin de n’envoyer qu’une annonce après avoir traité tous les vecteurs. Le modèle n’accepte que les améliorations ; il ne suffit pas à gérer le retrait ou la dégradation d’une route après panne.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
def vecteurs(self, i):
    res = []
    for adresse in self.table:
        i_interf, passerelle, dist = self.table[adresse]
        if ...:
            res.append(...)
    return res

Question 11

#

Compléter vecteurs(i), en excluant les routes passant par l’interface de sortie i.

Indice

L’interface stockée dans la route est à comparer à l’interface d’envoi.

Comprendre la correction
if i_interf != i:
    res.append((adresse, dist))

La méthode ne renvoie que destination et distance, pas la passerelle ni l’indice local, qui n’auraient pas le même sens chez le voisin. L’exclusion correspond au principe de split horizon dans ce modèle : ne pas annoncer sur une interface une route que l’on emprunte par cette même interface. Cela réduit les retours d’informations inutiles, sans prouver à lui seul l’absence de toute boucle dans toutes les variantes de RIP.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
def envoyer_vecteurs(self, i_interf):
    for i in range(len(self.interfaces)):
        if ...:
            adresse = self.interfaces[i].adresse
            v = self.vecteurs(...)
            paquet = (adresse, v)
            ... .enfiler(paquet)

Question 12

#

Compléter envoyer_vecteurs(i_interf), qui envoie par toutes les autres interfaces.

Indice

L’annonce doit être déposée sur le réseau pour qu’il la diffuse à ses machines.

Comprendre la correction
def envoyer_vecteurs(self, i_interf):
    for i in range(len(self.interfaces)):
        if i != i_interf:
            adresse = self.interfaces[i].adresse
            v = self.vecteurs(i)
            paquet = (adresse, v)
            self.interfaces[i].sortie.enfiler(paquet)

Le paramètre i_interf indique l’interface par laquelle l’information vient d’arriver ; cette sortie est ignorée pour l’annonce déclenchée. Pour chaque autre interface, vecteurs(i) applique son propre filtrage. L’origine du paquet est l’adresse de l’interface émettrice. On enfile dans sortie, la file du réseau, et non dans file, la file de réception de l’interface. Lorsque i_interf vaut -1, aucun indice valide n’est exclu.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
def traitement(self):
    for i in range(len(self.interfaces)):
        file = self.interfaces[i].file
        while not file.est_vide():
            orig, donnees = file.defiler()
            self.m_a_j(orig, i, donnees)

Algorithme demandé : tous les routeurs annoncent avec -1 ; diffuser toutes les files réseau ; traiter tous les routeurs ; recommencer tant qu’un réseau contient encore un paquet. Les collections sont routeurs et reseaux.

Question 13

#

Compléter le script simulant la construction des tables jusqu’à absence de paquets dans les réseaux.

Indice

Regardez les files réseau après le traitement, car de nouvelles annonces peuvent juste y être ajoutées.

Comprendre la correction
for routeur in routeurs:
    routeur.envoyer_vecteurs(-1)

continuer = True
while continuer:
    for reseau in reseaux:
        reseau.diffuser()
    for routeur in routeurs:
        routeur.traitement()
    continuer = False
    for reseau in reseaux:
        if not reseau.file.est_vide():
            continuer = True

Un tour distribue d’abord les annonces en attente des réseaux vers les interfaces, puis chaque routeur traite ses réceptions et peut produire de nouvelles annonces. La condition de poursuite est examinée après ces deux phases. Si tous les réseaux sont vides à ce moment, toutes les réceptions ont été traitées et plus aucune amélioration ne produit de paquet. Mettre continuer=False à la fin de chaque réseau sans conserver un True précédent pourrait arrêter à tort alors qu’un autre réseau contient encore des paquets.

Le modèle suppose une topologie fixe et ne traite que des distances nouvellement découvertes ou strictement améliorées, ce qui conduit à un état stable sur cet exemple fini. Un protocole réel comporte aussi annonces périodiques, temporisations et gestion des routes devenues inaccessibles ; ces mécanismes ne doivent pas être inventés dans ce script simplifié.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)
Une annonce du voisin mérite-t-elle une mise à jour ?Un atelier pour expérimenter

Choisissez la distance actuelle d’une route et celle annoncée par le voisin. Le simulateur applique le +1 et la règle d’amélioration stricte ; le mode réseau inconnu force une première insertion.

Lire le résultat de l’expérience initiale

Table modifiée, annonce à préparer

Le voisin est à un saut : sa distance annoncée augmente de 1. Une égalité ne remplace pas la route actuelle dans l’algorithme fourni.

Réseau connuAncienne distanceAnnonce voisineCandidateDécision
Oui201Insérer ou remplacer

Une route est améliorée en comparant son coût complet via le voisin, pas la distance du voisin seule.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Pour une structure imbriquée, donnez un sens à chaque niveau de clé ou d’indice.
  • Expliquez les effets de bord : remplissage consomme le stock, entrainement change les poids, diffuser vide une file.
  • Dans un simulateur réseau, distinguez file locale de réception, file du réseau et interface de sortie.

Retrouver ces notions dans d’autres sujets

Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027

Énoncé : sujet 26-NSIJ1JA1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.