Paillasse/portail/public/js/contenus-data-4.js

172 lines
39 KiB
JavaScript
Raw Normal View History

// Cours complets · Data & IA lot 4 : pandas, apprentissage automatique, réseaux de neurones
(function () {
var reg = function (c) {
(window.CONTENUS_PRO = window.CONTENUS_PRO || []).unshift(c.cours);
(window.CONTENUS_PLUS = window.CONTENUS_PLUS || {})[c.cours.id] = c.plus;
(window.CONTENUS_EXEMPLES = window.CONTENUS_EXEMPLES || {})[c.cours.id] = c.exemples;
(window.CONTENUS_SCHEMAS = window.CONTENUS_SCHEMAS || {})[c.cours.id] = { termes: c.termes, figs: [] };
};
reg({
cours: { id: 'pandas', match: /pandas|analyse de données|dataframe|traitement de données|données tabulaires|CSV/i,
titre: 'Analyse de données avec pandas : charger, nettoyer, agréger', duree: '',
niveau: 'NSI Terminale · BTS SIO SLAM · BUT Informatique / SD · Master data',
prerequis: ['Python : listes, dictionnaires, fonctions', 'SQL : SELECT, GROUP BY'],
competences: ['NSI · Traitement de données en tables', 'BTS SIO SLAM · Exploiter des données', 'BUT SD · Traiter des données'],
intro: 'Les données réelles arrivent sales : exports Pronote en point-virgule avec des virgules décimales, cellules vides, doublons, dates au format texte, noms mal orthographiés. Avant toute statistique ou tout modèle d’IA, un data engineer passe l’essentiel de son temps à les charger, les nettoyer et les restructurer. pandas est la bibliothèque Python de référence pour ce travail : elle manipule des tableaux de millions de lignes avec une syntaxe proche de SQL.',
cm: [
{ t: 'Series et DataFrame', d: '', html: 'Une <b>Series</b> est une colonne typée avec un <b>index</b> ; un <b>DataFrame</b> est un tableau de Series partageant le même index. Chaque colonne a un <b>dtype</b> (int64, float64, object pour le texte, datetime64, category). Les opérations sont <b>vectorisées</b> : <code>df["note"] * 2</code> s’applique à toute la colonne en une instruction, bien plus vite qu’une boucle Python.' },
{ t: 'Charger des données réelles', d: '', html: '<code>pd.read_csv</code> accepte de nombreux paramètres indispensables avec les exports français : <code>sep=";"</code>, <code>decimal=","</code>, <code>encoding="utf-8-sig"</code> ou <code>"latin-1"</code>, <code>parse_dates</code>, <code>dtype</code>. On inspecte toujours avant de calculer : <code>df.head()</code>, <code>df.info()</code> (types et valeurs manquantes), <code>df.describe()</code> (statistiques).', exemple: 'import pandas as pd\ndf = pd.read_csv("notes.csv", sep=";", decimal=",", encoding="utf-8-sig", parse_dates=["date"], dayfirst=True)\ndf.info()' },
{ t: 'Sélectionner et filtrer', d: '', html: '<code>df["col"]</code> sélectionne une colonne, <code>df[["a","b"]]</code> plusieurs. <code>df.loc[lignes, colonnes]</code> sélectionne par étiquette, <code>df.iloc</code> par position. Le filtrage utilise des masques booléens combinés par <code>&amp;</code>, <code>|</code> et <code>~</code>, chaque condition entre parenthèses.', exemple: 'df.loc[(df["classe"] == "SIO1") & (df["note"] < 8), ["nom", "matiere", "note"]]', attention: 'Modifier le résultat d’un filtre (df[df.note < 8]["note"] = 8) ne modifie pas df et produit un avertissement SettingWithCopy. On écrit df.loc[df.note < 8, "note"] = 8.' },
{ t: 'Nettoyer', d: '', html: 'Les valeurs manquantes sont <code>NaN</code>. <code>isna().sum()</code> les compte, <code>dropna</code> retire les lignes, <code>fillna</code> les remplace. Le choix est métier : une note absente n’est pas un zéro. <code>drop_duplicates</code> retire les doublons, <code>str.strip().str.upper()</code> normalise le texte, <code>astype</code> et <code>pd.to_numeric(errors="coerce")</code> convertissent les types en transformant les valeurs invalides en NaN.' },
{ t: 'Agréger et joindre', d: '', html: '<code>groupby</code> découpe, applique, combine, exactement comme GROUP BY en SQL : <code>df.groupby("classe")["note"].agg(["mean","median","count"])</code>. <code>pivot_table</code> produit un tableau croisé. <code>merge</code> joint deux DataFrame comme un JOIN, avec <code>how="left"</code> pour garder toutes les lignes de gauche et <code>validate="m:1"</code> pour vérifier qu’on ne multiplie pas les lignes par erreur.' },
{ t: 'Performances et export', d: '', html: 'Sur de gros volumes : lire seulement les colonnes utiles (<code>usecols</code>), typer le texte répétitif en <code>category</code>, éviter <code>apply</code> ligne à ligne au profit des opérations vectorisées, et stocker les résultats intermédiaires en <b>Parquet</b>, format colonne compressé et typé, bien plus rapide que le CSV. L’export final se fait avec <code>to_csv</code>, <code>to_excel</code> ou directement dans une base avec <code>to_sql</code>.' }
],
synthese: ['DataFrame = colonnes typées partageant un index ; opérations vectorisées', 'read_csv avec sep, decimal, encoding, parse_dates', 'Inspecter : head, info, describe, isna().sum()', 'loc pour sélectionner et modifier ; masques entre parenthèses', 'Valeurs manquantes : décision métier', 'groupby, pivot_table, merge avec validate', 'Parquet et category pour les gros volumes'],
qcm: [
['Quel paramètre pour lire « 12,5 » comme un nombre ?', ['sep=","', 'decimal=","', 'dtype=float', 'encoding'], 1, 'Virgule décimale française.'],
['Que montre df.info() ?', ['Les 5 premières lignes', 'Types et nombre de valeurs non nulles par colonne', 'La moyenne', 'Le graphique'], 1, 'Premier réflexe.'],
['Comment combiner deux conditions ?', ['and', '(c1) & (c2)', 'c1 && c2', 'c1 + c2'], 1, 'Opérateurs bit à bit sur des masques.'],
['Une note absente doit-elle devenir 0 ?', ['Toujours', 'Non, c’est une décision métier (absence ≠ zéro)', 'Oui avec fillna', 'Peu importe'], 1, 'Elle fausserait la moyenne.'],
['Équivalent pandas de GROUP BY ?', ['merge', 'groupby', 'pivot', 'concat'], 1, 'Découper, appliquer, combiner.'],
['Pourquoi préférer Parquet au CSV pour les fichiers intermédiaires ?', ['Plus lisible', 'Typé, compressé, lecture par colonne rapide', 'Obligatoire', 'Compatible Excel'], 1, 'Format colonne.']
],
td: [
{ n: 'Application', c: 'Charger l’export notes.csv (point-virgule, virgule décimale), afficher le nombre de notes manquantes par matière, puis la moyenne par classe et par matière en tableau croisé.', r: 'df = pd.read_csv("notes.csv", sep=";", decimal=",")\ndf.groupby("matiere")["note"].apply(lambda s: s.isna().sum())\ndf.pivot_table(index="classe", columns="matiere", values="note", aggfunc="mean").round(1)' },
{ n: 'Application', c: 'Joindre notes.csv et eleves.csv sur l’identifiant, en vérifiant qu’aucun élève n’est dupliqué, et lister les élèves sans aucune note.', r: 'm = eleves.merge(notes, on="id", how="left", validate="1:m")\nm[m["note"].isna()].drop_duplicates("id")[["nom","prenom"]]' },
{ n: 'Approfondissement', c: 'Repérer les élèves dont la moyenne a baissé de plus de 3 points entre le 1er et le 2e trimestre.', r: 'p = df.pivot_table(index="id", columns="trimestre", values="note", aggfunc="mean")\np[(p[1] - p[2]) > 3]' }
],
tp: { type: 'IDE Python', url: 'apercu-ide.html', desc: 'Jeu de données anonymisé d’un établissement, tests automatiques.', etapes: ['Charger et inspecter', 'Nettoyer types et doublons', 'Agrégats par classe', 'Jointure avec les effectifs', 'Exporter un bilan en CSV'] },
plusLoin: [['Documentation pandas', 'https://pandas.pydata.org/docs/'], ['DataCamp · Data Manipulation with pandas', 'https://www.datacamp.com/courses/data-manipulation-with-pandas'], ['data.gouv.fr · jeux de données ouverts', 'https://www.data.gouv.fr/']] },
plus: { cas: { titre: '', texte: '' },
resolu: { enonce: 'Le tableau de bord du conseil de classe affiche une moyenne générale de 4,2 pour la SIO1, alors que les enseignants constatent des moyennes autour de 12. Trouver et corriger l’erreur du script d’analyse.', etapes: [
['Inspecter les types', 'df.info()\n note 1840 non-null object\n→ La colonne note est du texte : read_csv n’a pas reconnu la virgule décimale.'],
['Voir ce qui a été calculé', 'df["note"].head(3)\n0 12,5\n1 14\n2 abs\n→ Des valeurs « abs » et des virgules. Le script faisait pd.to_numeric(errors="coerce") : « 12,5 » et « abs » devenaient NaN puis étaient remplacés par 0 avec fillna(0).'],
['Corriger le chargement', 'df = pd.read_csv("notes.csv", sep=";", decimal=",", na_values=["abs", "ABS", "disp"])\ndf["note"].dtype\nfloat64\n→ Les absences deviennent des valeurs manquantes, pas des zéros.'],
['Recalculer', 'df.groupby("classe")["note"].mean().round(2)\nSIO1 11.87\n→ Moyenne cohérente avec le terrain.'],
['Prévenir', '→ Ajout de contrôles automatiques dans le script : assert df["note"].between(0, 20).all(), et journalisation du nombre d’absences traitées. Aucun fillna(0) sur des notes.']
] },
erreurs: ['Oublier decimal="," sur un export français', 'Remplacer les absences par zéro', 'Modifier un filtre au lieu d’utiliser loc', 'Boucler ligne par ligne avec iterrows', 'Joindre sans vérifier les doublons', 'Calculer sans avoir regardé info()'],
memo: [['Instruction', 'Usage'], ['pd.read_csv(f, sep=";", decimal=",")', 'lire un export français'], ['df.head() · df.info() · df.describe()', 'inspecter'], ['df.isna().sum()', 'valeurs manquantes par colonne'], ['df.loc[masque, cols]', 'filtrer et sélectionner'], ['df.groupby(k)[c].agg([...])', 'agréger'], ['df.pivot_table(index, columns, values)', 'tableau croisé'], ['a.merge(b, on, how, validate)', 'joindre'], ['df.drop_duplicates()', 'retirer les doublons'], ['pd.to_numeric(s, errors="coerce")', 'convertir en nombre'], ['df.to_parquet() · df.to_csv()', 'exporter']],
examen: { titre: 'Sujet type · 20 points', q: ['Écrire le chargement d’un export fourni. (3 pts)', 'Nettoyer les valeurs manquantes en justifiant. (4 pts)', 'Produire trois agrégats demandés. (6 pts)', 'Joindre deux tables et détecter les incohérences. (4 pts)', 'Expliquer vectorisation et Parquet. (3 pts)'], r: ['read_csv paramétré', 'na_values, pas de 0', 'groupby / pivot_table', 'merge validate, how', 'voir cours'] },
prof: { deroule: [['Ouverture', 'Un vrai export raté projeté'], ['Cours', 'DataFrame, chargement, inspection'], ['Pratique', 'Nettoyage dans l’IDE'], ['Pratique', 'Agrégats et jointures'], ['Clôture', 'Quiz flash']], diff: 'Élèves en difficulté : cellules à trous. Élèves avancés : Parquet, polars, DataCamp.', eval: 'Tests IDE, sujet type.' } },
exemples: [
['Inspecter un export', 'Premier chargement.', 'df.info()', 'RangeIndex: 1840 entries\n # Column Non-Null Count Dtype\n 0 nom 1840 non-null object\n 1 classe 1840 non-null object\n 2 note 1712 non-null float64\n 3 date 1840 non-null datetime64[ns]', '128 notes manquantes (absences), types corrects : on peut calculer.'],
['Compter par groupe', 'Effectifs par classe.', 'df.groupby("classe")["nom"].nunique()', 'classe\nSIO1 31\nSIO2 27', 'nunique compte les élèves distincts, pas le nombre de lignes de notes.'],
['Détecter des doublons', 'Un élève apparaît deux fois.', 'eleves[eleves.duplicated("ine", keep=False)]', ' ine nom prenom\n12 070123456AB MARTIN Léa\n88 070123456AB Martin Lea', 'Même identifiant INE, casse et accents différents : on normalise avant de dédoublonner.'],
['Jointure qui multiplie les lignes', 'Le nombre de lignes explose après merge.', 'notes.merge(eleves, on="nom", validate="m:1")', 'MergeError: Merge keys are not unique in right dataset; not a many-to-one merge', 'Deux élèves portent le même nom : on joint sur l’identifiant, jamais sur le nom.'],
['Tableau croisé', 'Bilan pour le conseil.', 'df.pivot_table(index="classe", columns="matiere", values="note", aggfunc="mean").round(1)', 'matiere Maths Réseaux Dev\nclasse\nSIO1 11.2 12.8 10.4\nSIO2 12.0 13.1 12.7', 'Le Dev de SIO1 est le point faible, piste pour le conseil.'],
['Mesurer un gain de performance', 'Script lent sur 2 millions de lignes.', '%timeit df["total"] = df.apply(lambda r: r.note * r.coef, axis=1)\n%timeit df["total"] = df["note"] * df["coef"]', '8.41 s ± 120 ms\n6.2 ms ± 0.3 ms', 'La version vectorisée est plus de mille fois plus rapide.']
],
termes: ['pandas', 'DataFrame', 'Series', 'index', 'dtype', 'read_csv', 'loc', 'iloc', 'NaN', 'isna', 'fillna', 'dropna', 'groupby', 'agg', 'pivot_table', 'merge', 'validate', 'drop_duplicates', 'Parquet', 'category', 'vectorisé']
});
reg({
cours: { id: 'ml', match: /apprentissage automatique|machine learning|scikit|régression|classification|intelligence artificielle|\bIA\b/i,
titre: 'Apprentissage automatique : du jeu de données au modèle évalué', duree: '',
niveau: 'NSI Terminale (k plus proches voisins) · BUT SD / Informatique · École d’ingénieurs · Master IA',
prerequis: ['Analyse de données avec pandas', 'Statistiques descriptives'],
competences: ['NSI · Algorithme des k plus proches voisins', 'BUT SD · Modéliser', 'Master · Concevoir un système d’apprentissage'],
intro: 'Un modèle d’apprentissage automatique apprend une règle à partir d’exemples plutôt qu’on ne la lui programme. Il détecte un courriel frauduleux, prédit le risque de décrochage d’un élève ou reconnaît un caractère manuscrit. Mais un modèle mal évalué donne des chiffres flatteurs et des décisions fausses. Ce module suit la démarche complète avec scikit-learn : préparer les données, séparer apprentissage et test, choisir un modèle, mesurer correctement, et comprendre les limites éthiques et réglementaires.',
cm: [
{ t: 'Apprendre à partir d’exemples', d: '', html: 'En <b>apprentissage supervisé</b>, chaque exemple a des <b>variables explicatives</b> (X) et une <b>cible</b> (y) connue : on apprend à prédire y à partir de X. C’est une <b>classification</b> si y est une catégorie (décroche / ne décroche pas), une <b>régression</b> si y est un nombre (note finale). En <b>apprentissage non supervisé</b>, il n’y a pas de cible : on cherche une structure, par exemple des groupes d’élèves aux profils proches (clustering).' },
{ t: 'Séparer apprentissage et test', d: '', html: 'Un modèle évalué sur les données qui ont servi à l’entraîner paraît toujours excellent : il les a apprises par cœur. On sépare donc un <b>jeu d’apprentissage</b> et un <b>jeu de test</b> jamais vu (<code>train_test_split</code>, souvent 80/20, stratifié pour garder la proportion des classes). Pour régler les hyperparamètres sans toucher au test, on utilise la <b>validation croisée</b> : l’apprentissage est découpé en k parts, chacune servant tour à tour de validation.<br><br>Le <b>surapprentissage</b> (overfitting) se voit quand le score d’apprentissage est très bon et celui de test nettement moins bon ; le <b>sous-apprentissage</b> quand les deux sont mauvais.', attention: 'La fuite de données (data leakage) fausse tout : normaliser toutes les données avant la séparation, ou inclure une variable connue seulement après coup (la note finale pour prédire le décrochage), donne un modèle parfait en test et inutile en réalité.' },
{ t: 'Préparer les variables', d: '', html: 'Les modèles attendent des nombres. On <b>encode</b> les catégories (OneHotEncoder), on <b>normalise</b> les échelles (StandardScaler) pour les modèles sensibles aux distances comme les k plus proches voisins, on traite les valeurs manquantes (SimpleImputer). Un <b>Pipeline</b> enchaîne ces étapes et le modèle : il apprend les paramètres de préparation sur l’apprentissage uniquement et les applique au test, ce qui évite la fuite de données.', exemple: 'from sklearn.pipeline import make_pipeline\nfrom sklearn.compose import make_column_transformer\nfrom sklearn.preprocessing import StandardScaler, OneHotEncoder\nfrom sklearn.ensemble import RandomForestClassifier\n\nprep = make_column_transformer(\n (StandardScaler(), ["absences", "moyenne_t1", "retards"]),\n (OneHotEncoder(handle_unknown="ignore"), ["filiere"]))\nmodele = make_pipeline(prep, RandomForestClassifier(n_estimators=300, random_state=0))\nmodele.fit(X_train, y_train)' },
{ t: 'Quelques modèles de référence', d: '', html: 'Les <b>k plus proches voisins</b> (au programme de NSI) classent un exemple selon la classe majoritaire de ses k voisins les plus proches. La <b>régression linéaire</b> et la <b>régression logistique</b> sont simples, rapides et interprétables. Les <b>arbres de décision</b> produisent des règles lisibles ; les <b>forêts aléatoires</b> et le <b>gradient boosting</b> en combinent des centaines et sont souvent les plus performants sur des données tabulaires. On commence toujours par une <b>référence simple</b> (prédire la classe majoritaire) pour savoir si le modèle apporte vraiment quelque chose.' },
{ t: 'Mesurer correctement', d: '', html: 'L’<b>exactitude</b> (accuracy) trompe quand les classes sont déséquilibrées : si 5 % des élèves décrochent, prédire « personne ne décroche » donne 95 %. La <b>matrice de confusion</b> distingue vrais et faux positifs et négatifs. La <b>précision</b> mesure la part de vrais positifs parmi les alertes, le <b>rappel</b> la part des vrais cas détectés, le <b>F1</b> leur moyenne harmonique. Le choix dépend du coût des erreurs : manquer un élève en difficulté coûte plus cher qu’un entretien inutile, on privilégie donc le rappel. En régression, on utilise l’erreur absolue moyenne (MAE) ou la racine de l’erreur quadratique (RMSE).' },
{ t: 'Éthique et cadre légal', d: '', html: 'Un modèle reproduit les biais de ses données : entraîné sur des décisions passées discriminantes, il les automatise. Pour des décisions qui touchent des personnes, et a fortiori des mineurs, on vérifie les performances par sous-groupe, on garde un humain dans la décision et on explique les facteurs du modèle. Le <b>RGPD</b> encadre les décisions individuelles automatisées (article 22) et impose la minimisation des données ; le <b>règlement européen sur l’IA</b> (AI Act) classe l’éducation parmi les usages à haut risque, soumis à des obligations de documentation, de qualité des données et de supervision humaine.' }
],
synthese: ['Supervisé (classification, régression) ou non supervisé', 'Toujours évaluer sur un jeu de test jamais vu ; validation croisée pour régler', 'Pipeline pour préparer sans fuite de données', 'Partir d’une référence simple', 'Accuracy trompeuse si déséquilibre : matrice de confusion, précision, rappel, F1', 'Biais, supervision humaine, RGPD article 22, AI Act'],
qcm: [
['Prédire la note finale d’un élève est une :', ['classification', 'régression', 'clustering', 'réduction de dimension'], 1, 'La cible est un nombre.'],
['Score d’apprentissage 0,99, score de test 0,71 : c’est', ['du sous-apprentissage', 'du surapprentissage', 'normal', 'une fuite de données'], 1, 'Le modèle a appris par cœur.'],
['5 % de décrocheurs : un modèle qui prédit « aucun » a une accuracy de :', ['5 %', '50 %', '95 %', '0 %'], 2, 'D’où les autres métriques.'],
['Quelle métrique privilégier pour ne manquer aucun élève en difficulté ?', ['Précision', 'Rappel', 'Accuracy', 'MAE'], 1, 'Part des vrais cas détectés.'],
['Pourquoi utiliser un Pipeline ?', ['Esthétique', 'Pour appliquer la préparation apprise sur l’apprentissage seulement, sans fuite', 'Plus rapide', 'Obligatoire'], 1, 'Évite la fuite de données.'],
['Selon l’AI Act, l’usage de l’IA dans l’éducation est classé :', ['interdit', 'à haut risque', 'sans risque', 'non concerné'], 1, 'Obligations renforcées.']
],
td: [
{ n: 'Échauffement', c: 'Implémenter les k plus proches voisins en Python pur pour classer un point à partir d’une liste de points étiquetés.', r: 'def knn(pts, x, k):\n d = sorted(pts, key=lambda p: (p[0]-x[0])**2 + (p[1]-x[1])**2)[:k]\n etiq = [p[2] for p in d]\n return max(set(etiq), key=etiq.count)' },
{ n: 'Application', c: 'Entraîner une régression logistique et une forêt aléatoire sur le jeu fourni, comparer leur F1 en validation croisée à 5 plis, puis évaluer la meilleure sur le test.', r: 'from sklearn.model_selection import cross_val_score\nfor m in [logit, foret]:\n print(cross_val_score(m, X_train, y_train, cv=5, scoring="f1").mean())\nmeilleur.fit(X_train, y_train); f1_score(y_test, meilleur.predict(X_test))' },
{ n: 'Approfondissement', c: 'Vérifier si le modèle de détection du décrochage a un rappel différent pour les filles et les garçons, et proposer une conduite à tenir.', r: 'for g in ["F","M"]: m = X_test["sexe"] == g; recall_score(y_test[m], pred[m])\nSi écart important : examiner les données, rééquilibrer, ajuster le seuil par sous-groupe avec prudence, documenter, et garder la décision humaine.' }
],
tp: { type: 'IDE Python', url: 'apercu-ide.html', desc: 'scikit-learn dans le navigateur (Pyodide), jeu de données synthétique.', etapes: ['Séparer apprentissage et test', 'Construire un Pipeline', 'Comparer trois modèles en validation croisée', 'Lire la matrice de confusion', 'Analyser les performances par sous-groupe'] },
plusLoin: [['scikit-learn · guide utilisateur', 'https://scikit-learn.org/stable/user_guide.html'], ['CNIL · IA : dossiers et recommandations', 'https://www.cnil.fr/fr/intelligence-artificielle'], ['DataCamp · Supervised Learning with scikit-learn', 'https://www.datacamp.com/courses/supervised-learning-with-scikit-learn'], ['Kaggle Learn · Intro to Machine Learning', 'https://www.kaggle.com/learn/intro-to-machine-learning']] },
plus: { cas: { titre: '', texte: '' },
resolu: { enonce: 'Un prototype de détection du décrochage affiche 98 % d’exactitude et 0,97 de F1 en test. Mis en service sur la nouvelle année, il ne détecte presque aucun élève. Trouver pourquoi.', etapes: [
['Regarder les variables', 'modele[-1].feature_importances_ triées\nnb_absences_annee 0.71\nmoyenne_t1 0.08\n→ Le nombre d’absences sur l’année entière domine. En septembre, cette variable n’est pas encore connue.'],
['Identifier la fuite', '→ La variable résume toute l’année, y compris la période où l’élève a déjà décroché : le modèle « prédit » à partir d’un effet du décrochage. C’est une fuite de données temporelle.'],
['Reconstruire le jeu', 'Variables limitées à ce qui est connu au 15 octobre : absences et retards de septembre, moyenne des premières évaluations, filière.\nSéparation temporelle : apprentissage sur 2022-2024, test sur 2025.\n→ On évalue comme on utilisera le modèle.'],
['Réévaluer', 'F1 test : 0.58, rappel 0.71, précision 0.49\n→ Scores plus modestes mais honnêtes : le modèle repère 71 % des futurs décrocheurs, avec une alerte sur deux justifiée.'],
['Décider de l’usage', '→ Usage limité à une aide au repérage pour l’équipe de vie scolaire, jamais une décision automatique ; analyse d’impact (AIPD) et information des familles. Documentation du modèle et de ses limites.']
] },
erreurs: ['Évaluer sur les données d’apprentissage', 'Normaliser avant de séparer', 'Utiliser une variable connue après coup', 'Se fier à l’accuracy sur des classes déséquilibrées', 'Pas de référence simple', 'Décision automatique sur des élèves sans supervision humaine'],
memo: [['Instruction', 'Usage'], ['train_test_split(X, y, test_size=0.2, stratify=y)', 'séparer'], ['make_pipeline(prep, modele)', 'préparer sans fuite'], ['cross_val_score(m, X, y, cv=5, scoring="f1")', 'validation croisée'], ['GridSearchCV', 'régler les hyperparamètres'], ['DummyClassifier', 'référence simple'], ['confusion_matrix · classification_report', 'mesurer'], ['precision_score · recall_score · f1_score', 'métriques'], ['mean_absolute_error', 'régression'], ['feature_importances_ · permutation_importance', 'comprendre le modèle']],
examen: { titre: 'Sujet type · 20 points', q: ['Distinguer classification, régression, clustering. (3 pts)', 'Construire et évaluer un pipeline sur un jeu fourni. (8 pts)', 'Interpréter une matrice de confusion. (4 pts)', 'Identifier une fuite de données dans un cas donné. (3 pts)', 'Citer deux obligations RGPD ou AI Act. (2 pts)'], r: ['voir cours', 'split, pipeline, cv, test', 'précision, rappel', 'variable postérieure', 'art. 22, haut risque'] },
prof: { deroule: [['Ouverture', 'Un modèle à 95 % qui ne sert à rien'], ['Cours', 'Supervisé, séparation, métriques'], ['Pratique', 'kNN à la main'], ['Pratique', 'Pipeline scikit-learn'], ['Débat', 'IA et décisions sur les élèves'], ['Clôture', 'Quiz flash']], diff: 'Élèves en difficulté : notebook guidé. Élèves avancés : gradient boosting, interprétabilité (SHAP), Kaggle.', eval: 'Notebook rendu, sujet type.' } },
exemples: [
['Séparer proprement', 'Préparer l’évaluation.', 'X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)\nprint(y_tr.mean(), y_te.mean())', '0.0512 0.0508', 'stratify garde la même proportion de cas positifs dans les deux jeux.'],
['Référence simple', 'Savoir si le modèle apporte quelque chose.', 'DummyClassifier(strategy="most_frequent").fit(X_tr, y_tr).score(X_te, y_te)', '0.949', 'Toute accuracy inférieure à 95 % est pire que ne rien faire : il faut regarder rappel et F1.'],
['Matrice de confusion', 'Évaluer le modèle.', 'confusion_matrix(y_te, pred)', '[[1782 31]\n [ 28 69]]', '69 décrocheurs repérés sur 97 (rappel 0,71), 31 fausses alertes sur 100 alertes (précision 0,69).'],
['Rapport complet', 'Présenter les résultats.', 'print(classification_report(y_te, pred, target_names=["reste", "décroche"]))', ' precision recall f1-score support\n reste 0.98 0.98 0.98 1813\n décroche 0.69 0.71 0.70 97', 'La classe minoritaire est celle qui compte ; ses scores sont bien plus faibles que ceux de la majorité.'],
['Détecter le surapprentissage', 'Arbre de décision très profond.', 'arbre.score(X_tr, y_tr), arbre.score(X_te, y_te)', '(1.0, 0.89)', 'Score parfait en apprentissage, bien moindre en test : on limite max_depth ou on passe à une forêt aléatoire.'],
['Importance par permutation', 'Expliquer le modèle à l’équipe.', 'r = permutation_importance(modele, X_te, y_te, scoring="f1", n_repeats=10)\nsorted(zip(r.importances_mean, X_te.columns), reverse=True)[:3]', '[(0.21, "absences_sept"), (0.09, "moyenne_eval1"), (0.04, "retards_sept")]', 'Les absences de septembre sont le premier signal, cohérent avec l’expérience des CPE.']
],
termes: ['apprentissage supervisé', 'classification', 'régression', 'clustering', 'jeu d’apprentissage', 'jeu de test', 'validation croisée', 'surapprentissage', 'fuite de données', 'Pipeline', 'StandardScaler', 'OneHotEncoder', 'k plus proches voisins', 'forêt aléatoire', 'matrice de confusion', 'précision', 'rappel', 'F1', 'accuracy', 'scikit-learn', 'AI Act', 'RGPD']
});
reg({
cours: { id: 'neurones', match: /réseaux? de neurones|deep learning|apprentissage profond|PyTorch|TensorFlow|OCR|reconnaissance vocale/i,
titre: 'Réseaux de neurones : principe, entraînement et applications (OCR, voix)', duree: '',
niveau: 'BUT Informatique / SD · École d’ingénieurs · Master IA',
prerequis: ['Apprentissage automatique', 'Dérivées et matrices'],
competences: ['Master · Concevoir et entraîner un modèle d’apprentissage profond', 'École d’ingénieurs · Intégrer un modèle en production'],
intro: 'Reconnaître un caractère manuscrit, transcrire une voix, décrire une image : ces tâches, longtemps hors de portée des programmes classiques, sont aujourd’hui résolues par des réseaux de neurones. Ce module explique ce qu’est réellement un réseau, comment il apprend par descente de gradient et rétropropagation, quelles architectures conviennent aux images et au son, et comment on met un tel modèle en production.',
cm: [
{ t: 'Le neurone et la couche', d: '', html: 'Un neurone artificiel calcule une somme pondérée de ses entrées, ajoute un biais et applique une <b>fonction d’activation</b> non linéaire : ReLU (max(0, x)) dans les couches cachées, sigmoïde ou softmax en sortie pour obtenir des probabilités. Une <b>couche dense</b> est un produit matriciel suivi d’une activation. Empiler des couches permet de représenter des fonctions très complexes ; sans non-linéarité, plusieurs couches se réduiraient à une seule transformation linéaire.' },
{ t: 'Apprendre : perte, gradient, rétropropagation', d: '', html: 'L’entraînement minimise une <b>fonction de perte</b> (entropie croisée pour la classification, erreur quadratique pour la régression). La <b>descente de gradient</b> ajuste chaque poids dans la direction qui fait baisser la perte, d’un pas fixé par le <b>taux d’apprentissage</b>. La <b>rétropropagation</b> calcule efficacement ces gradients couche par couche grâce à la règle de dérivation des fonctions composées. On traite les données par <b>lots</b> (mini-batches) et on parcourt plusieurs fois le jeu (<b>époques</b>). Un taux trop grand fait diverger, trop petit rend l’apprentissage interminable.' },
{ t: 'Images : réseaux convolutifs', d: '', html: 'Une image de 28×28 pixels a 784 entrées ; une photo en a des millions. Les <b>réseaux convolutifs</b> (CNN) appliquent de petits filtres qui glissent sur l’image et détectent des motifs locaux (bords, courbes), puis des motifs de plus en plus abstraits d’une couche à l’autre. Les poids d’un filtre sont partagés sur toute l’image, ce qui réduit énormément le nombre de paramètres et rend le réseau insensible à la position du motif. C’est le cœur des systèmes d’<b>OCR</b> : détection des zones de texte, puis reconnaissance des caractères.' },
{ t: 'Séquences : son, texte et transformeurs', d: '', html: 'Le son est une séquence : on le convertit d’abord en <b>spectrogramme</b> (énergie par fréquence au cours du temps, souvent en échelle de Mel), que l’on traite comme une image ou comme une séquence. Les réseaux récurrents ont longtemps servi à la reconnaissance vocale ; ils sont aujourd’hui largement remplacés par les <b>transformeurs</b>, fondés sur le mécanisme d’<b>attention</b> qui relie chaque élément de la séquence à tous les autres. Whisper (transcription) et les grands modèles de langage reposent sur cette architecture.' },
{ t: 'Entraîner en pratique', d: '', html: 'On entraîne rarement de zéro : l’<b>apprentissage par transfert</b> part d’un modèle pré-entraîné sur un grand corpus et l’ajuste (fine-tuning) sur ses propres données. Contre le surapprentissage : <b>augmentation de données</b> (rotations, bruit, variations de vitesse pour la voix), <b>dropout</b>, <b>arrêt précoce</b> quand la perte de validation remonte. Les calculs se font sur GPU ; un suivi des courbes de perte d’apprentissage et de validation est indispensable.' },
{ t: 'Mettre en production', d: '', html: 'Un modèle en production doit être rapide et léger : export en format standard (ONNX), <b>quantification</b> (poids en 8 bits au lieu de 32), exécution sur CPU si possible. On surveille la <b>dérive</b> : les données réelles s’éloignent de celles de l’entraînement (nouvelle police de caractères, micro différent), et les performances baissent sans erreur visible. Côté réglementaire, la voix est une donnée biométrique potentielle au sens du RGPD : traitement local de préférence, finalité limitée, conservation minimale.' }
],
synthese: ['Neurone = somme pondérée + biais + activation non linéaire', 'Perte, descente de gradient, rétropropagation, lots et époques', 'CNN pour les images et l’OCR ; spectrogrammes et transformeurs pour la voix', 'Transfert, augmentation, dropout, arrêt précoce', 'Production : ONNX, quantification, surveillance de la dérive, RGPD'],
qcm: [
['Pourquoi une activation non linéaire ?', ['Pour accélérer', 'Sinon les couches se réduisent à une seule transformation linéaire', 'Pour normaliser', 'Obligatoire en Python'], 1, 'La non-linéarité donne la puissance.'],
['Que calcule la rétropropagation ?', ['Les prédictions', 'Les gradients de la perte par rapport aux poids', 'Le taux d’apprentissage', 'Les lots'], 1, 'Règle de dérivation en chaîne.'],
['Avantage principal d’une convolution sur une image ?', ['Couleurs', 'Partage des poids et détection de motifs locaux', 'Plus de paramètres', 'Aucun'], 1, 'Moins de paramètres, invariance à la position.'],
['Comment représente-t-on souvent le son pour un réseau ?', ['En texte', 'En spectrogramme', 'En binaire brut', 'En CSV'], 1, 'Temps × fréquence.'],
['La perte de validation remonte alors que celle d’apprentissage baisse :', ['tout va bien', 'surapprentissage, arrêt précoce', 'taux trop faible', 'données manquantes'], 1, 'On arrête ou on régularise.'],
['Qu’est-ce que la quantification ?', ['Compter les données', 'Réduire la précision des poids pour alléger le modèle', 'Ajouter des couches', 'Chiffrer le modèle'], 1, 'Souvent 8 bits.']
],
td: [
{ n: 'Application', c: 'Calculer à la main la sortie d’un neurone : entrées (1, 2), poids (0,5 ; −1), biais 0,2, activation ReLU.', r: '0,5 × 1 + (−1) × 2 + 0,2 = −1,3 → ReLU = 0' },
{ n: 'Application', c: 'Écrire en PyTorch un réseau à deux couches pour MNIST (784 → 128 → 10) et sa boucle d’entraînement.', r: 'model = nn.Sequential(nn.Flatten(), nn.Linear(784,128), nn.ReLU(), nn.Linear(128,10))\nopt = torch.optim.Adam(model.parameters(), 1e-3); loss = nn.CrossEntropyLoss()\nfor x, y in loader: opt.zero_grad(); l = loss(model(x), y); l.backward(); opt.step()' },
{ n: 'Approfondissement', c: 'Un OCR entraîné sur des copies scannées échoue sur des photos prises au téléphone. Proposer trois actions.', r: 'Augmentation (perspective, flou, éclairage) · fine-tuning sur un échantillon de photos annotées · prétraitement (redressement, binarisation) et surveillance de la dérive.' }
],
tp: { type: 'IDE Python', url: 'apercu-ide.html', desc: 'Réseau dense sur des chiffres manuscrits, dans le navigateur.', etapes: ['Visualiser le jeu de données', 'Construire le réseau', 'Tracer les courbes de perte', 'Mesurer l’exactitude par chiffre', 'Tester l’augmentation de données'] },
plusLoin: [['3Blue1Brown · Neural networks (vidéos)', 'https://www.3blue1brown.com/topics/neural-networks'], ['Tutoriels PyTorch', 'https://pytorch.org/tutorials/'], ['Fidle (CNRS) · formation deep learning en français', 'https://fidle.cnrs.fr/'], ['CNIL · la voix, une donnée à protéger', 'https://www.cnil.fr/']] },
plus: { cas: { titre: '', texte: '' },
resolu: { enonce: 'Le module de transcription vocale des consignes, déployé en salle de TP, fonctionnait bien en tests mais produit des transcriptions incohérentes depuis l’installation des nouveaux casques.', etapes: [
['Comparer les entrées', 'soxi test_ancien.wav nouveau.wav\nSample Rate : 16000 | Sample Rate : 48000\n→ Les nouveaux casques enregistrent en 48 kHz ; le modèle attend du 16 kHz.'],
['Vérifier le prétraitement', '→ Le code passait l’audio brut au modèle sans rééchantillonnage : le spectrogramme calculé était « compressé » en fréquence, comme une voix accélérée trois fois.'],
['Corriger', 'audio = torchaudio.functional.resample(audio, orig_freq=sr, new_freq=16000)\n→ Rééchantillonnage systématique à l’entrée, avec contrôle du format.'],
['Valider', 'WER sur 50 consignes de test : 38 % → 7 %\n→ Le taux d’erreur de mots revient au niveau des tests.'],
['Prévenir', '→ Assertion sur la fréquence et le nombre de canaux, surveillance du WER sur un échantillon hebdomadaire, et traitement local sans conservation des enregistrements.']
] },
erreurs: ['Pas de jeu de validation', 'Taux d’apprentissage choisi au hasard', 'Ignorer les courbes de perte', 'Prétraitement différent entre entraînement et production', 'Entraîner de zéro sans nécessité', 'Envoyer des voix d’élèves à un service tiers sans analyse RGPD'],
memo: [['Élément', 'À retenir'], ['nn.Linear · nn.Conv2d', 'couche dense · convolution'], ['nn.ReLU · softmax', 'activations'], ['nn.CrossEntropyLoss', 'perte de classification'], ['torch.optim.Adam(lr=1e-3)', 'optimiseur courant'], ['loss.backward() · opt.step()', 'rétropropagation et mise à jour'], ['model.eval() · torch.no_grad()', 'mode inférence'], ['Dropout · EarlyStopping', 'contre le surapprentissage'], ['torch.onnx.export', 'export pour la production'], ['WER · CER', 'erreurs de mots et de caractères (voix, OCR)']],
examen: { titre: 'Sujet type · 20 points', q: ['Calculer la sortie d’un petit réseau. (4 pts)', 'Expliquer descente de gradient et rétropropagation. (5 pts)', 'Justifier le choix d’un CNN pour l’OCR. (4 pts)', 'Analyser des courbes de perte fournies. (4 pts)', 'Citer deux précautions de mise en production. (3 pts)'], r: ['produit matriciel + activation', 'voir cours', 'motifs locaux, partage des poids', 'surapprentissage ou non', 'dérive, quantification, RGPD'] },
prof: { deroule: [['Ouverture', 'Démonstration OCR sur une copie'], ['Cours', 'Neurone, perte, gradient'], ['Pratique', 'Réseau dense MNIST'], ['Cours', 'CNN et spectrogrammes'], ['Clôture', 'Quiz flash']], diff: 'Élèves en difficulté : calculs à la main guidés. Élèves avancés : fine-tuning, Fidle.', eval: 'Notebook, sujet type.' } },
exemples: [
['Vérifier les dimensions', 'Erreur à la première passe.', 'x, y = next(iter(loader)); print(x.shape, model(x).shape)', 'torch.Size([64, 1, 28, 28]) torch.Size([64, 10])', 'Un lot de 64 images en entrée, 10 scores en sortie : les dimensions sont cohérentes.'],
['Suivre l’apprentissage', 'Entraînement en cours.', 'for e in range(5): … print(e, perte_train, perte_val, acc_val)', '0 0.412 0.228 0.934\n1 0.191 0.160 0.953\n2 0.139 0.131 0.961\n3 0.108 0.129 0.962\n4 0.085 0.134 0.961', 'La perte de validation remonte à l’époque 4 : on garde le modèle de l’époque 3 (arrêt précoce).'],
['Taux d’apprentissage trop grand', 'La perte explose.', 'opt = torch.optim.SGD(model.parameters(), lr=5.0)', 'époque 0 perte 2.30 → époque 1 perte nan', 'La perte devient NaN : les poids divergent. On revient à lr=1e-2 pour SGD ou 1e-3 pour Adam.'],
['Mesurer l’OCR', 'Évaluer sur un jeu annoté.', 'jiwer.cer(references, predictions)', '0.043', '4,3 % de caractères erronés : acceptable pour du texte imprimé, à surveiller pour l’écriture manuscrite.'],
['Alléger le modèle', 'Déploiement sur un mini-PC de salle.', 'q = torch.quantization.quantize_dynamic(model, {nn.Linear}, dtype=torch.qint8)\nprint(taille(model), taille(q))', '1.62 Mo 0.43 Mo', 'Le modèle quantifié est près de quatre fois plus léger, pour une perte d’exactitude généralement faible, à mesurer.'],
['Transcrire localement', 'Consigne vocale en salle.', 'whisper consigne.wav --model small --language fr', '[00:00.000 --> 00:04.200] Ouvrez le terminal et tapez ip addr.', 'Le modèle tourne sur la machine locale : aucune voix d’élève ne sort de l’établissement.']
],
termes: ['réseau de neurones', 'neurone', 'couche', 'ReLU', 'softmax', 'fonction de perte', 'descente de gradient', 'rétropropagation', 'taux d’apprentissage', 'époque', 'lot', 'CNN', 'convolution', 'spectrogramme', 'transformeur', 'attention', 'apprentissage par transfert', 'dropout', 'arrêt précoce', 'ONNX', 'quantification', 'dérive', 'OCR', 'WER', 'PyTorch']
});
})();