{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Le rôle de l’aléatoire dans l’apprentissage automatique\n",
        "\n",
        "CSI 4506 — Introduction à l’intelligence artificielle\n",
        "\n",
        "Marcel Turcotte  \n",
        "2026-09-20\n",
        "\n",
        "# Introduction\n",
        "\n",
        "L’aléatoire joue plusieurs rôles en apprentissage automatique. Il sert à\n",
        "initialiser les paramètres des modèles, à mélanger les données, à créer\n",
        "des mini-lots, à construire les ensembles d’entraînement et de test et à\n",
        "mettre en œuvre des algorithmes randomisés. Ces rôles sont apparentés,\n",
        "mais distincts. Par exemple, une initialisation aléatoire rompt la\n",
        "symétrie entre les neurones d’une même couche d’un réseau de neurones ;\n",
        "si leurs poids étaient initialisés de façon identique, ils pourraient\n",
        "apprendre les mêmes représentations. Le mélange et l’échantillonnage\n",
        "aléatoires exposent un algorithme à différentes sélections et à\n",
        "différents ordres des données disponibles.\n",
        "\n",
        "# Nombres pseudo-aléatoires\n",
        "\n",
        "La plupart des bibliothèques informatiques qui produisent des nombres\n",
        "aléatoires utilisent en fait des générateurs de nombres\n",
        "*pseudo-aléatoires*. Elles produisent des séquences possédant des\n",
        "propriétés statistiques utiles, mais ces séquences sont générées de\n",
        "manière déterministe.\n",
        "\n",
        "Un générateur de nombres pseudo-aléatoires maintient un **état**\n",
        "interne. Chaque tirage produit une valeur et met cet état à jour. Un\n",
        "**germe** initialise l’état ; le même générateur, le même germe et la\n",
        "même suite d’appels reproduisent les mêmes valeurs. La valeur `42` est\n",
        "couramment utilisée dans les exemples, mais elle ne possède aucune\n",
        "propriété statistique particulière.\n",
        "\n",
        "Dans l’exemple suivant, qui utilise le module\n",
        "[`random`](https://docs.python.org/3/library/random.html) de Python,\n",
        "nous produisons deux séquences. Puisque nous ne réinitialisons pas le\n",
        "germe entre les deux, la seconde séquence continue à partir de l’état\n",
        "laissé par la première. Les valeurs changeront généralement si cette\n",
        "cellule est exécutée dans une nouvelle session Python."
      ],
      "id": "d9721bc7-7802-490f-a37f-cbef43c8dc04"
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Séquence 1 : [52, 47, 41, 12, 18]\n",
            "Séquence 2 : [81, 98, 44, 31, 100]"
          ]
        }
      ],
      "source": [
        "import random\n",
        "\n",
        "sequence_1 = [random.randint(1, 100) for _ in range(5)]\n",
        "sequence_2 = [random.randint(1, 100) for _ in range(5)]\n",
        "\n",
        "print(f\"Séquence 1 : {sequence_1}\")\n",
        "print(f\"Séquence 2 : {sequence_2}\")"
      ],
      "id": "sans_germe"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "En revanche, l’exemple suivant réinitialise le générateur au même état\n",
        "avant de produire chacune des deux premières séquences. Un autre germe\n",
        "sélectionne normalement une autre séquence."
      ],
      "id": "47746efe-b88e-4d3c-bf78-7da30a1a2e1c"
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Séquence 1 : [82, 15, 4, 95, 36]\n",
            "Séquence 2 : [82, 15, 4, 95, 36]\n",
            "Séquence 3 : [7, 35, 12, 99, 53]"
          ]
        }
      ],
      "source": [
        "random.seed(42)\n",
        "sequence_1 = [random.randint(1, 100) for _ in range(5)]\n",
        "\n",
        "random.seed(42)\n",
        "sequence_2 = [random.randint(1, 100) for _ in range(5)]\n",
        "\n",
        "random.seed(123)\n",
        "sequence_3 = [random.randint(1, 100) for _ in range(5)]\n",
        "\n",
        "print(f\"Séquence 1 : {sequence_1}\")\n",
        "print(f\"Séquence 2 : {sequence_2}\")\n",
        "print(f\"Séquence 3 : {sequence_3}\")"
      ],
      "id": "fixer_germe"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "La capacité de reproduire des choix aléatoires est essentielle en calcul\n",
        "scientifique et en apprentissage automatique. Elle nous aide à déboguer\n",
        "le code, à comparer des méthodes dans les mêmes conditions et à décrire\n",
        "précisément nos expériences.\n",
        "\n",
        "Le module `random` de Python et scikit-learn ne partagent pas un\n",
        "générateur unique. L’appel `random.seed(42)` contrôle les fonctions du\n",
        "module `random`. Dans scikit-learn, un entier fourni à un paramètre\n",
        "comme `random_state=42` contrôle les choix aléatoires effectués par cet\n",
        "objet ou cette fonction.\n",
        "\n",
        "Imaginez qu’un modèle se comporte de manière inattendue et que vous\n",
        "demandiez à une collègue ou un collègue d’examiner le problème. Si\n",
        "chaque exécution crée une partition différente des données, cette\n",
        "personne pourrait ne pas observer le même comportement. Fixer le germe\n",
        "vous permet de reproduire les mêmes choix aléatoires et d’examiner la\n",
        "même expérience. Cela n’améliore pas la partition et ne garantit pas que\n",
        "le modèle obtenu sera performant. Une reproductibilité complète peut\n",
        "aussi dépendre des versions logicielles, des données, du matériel et\n",
        "d’autres sources de variabilité aléatoire.\n",
        "\n",
        "> **Important**\n",
        ">\n",
        "> Un germe est une **variable de contrôle expérimentale**, et non un\n",
        "> réglage de performance. Il rend reproductible une séquence\n",
        "> particulière de choix aléatoires. Il ne doit pas être sélectionné\n",
        "> parce qu’il produit le résultat de test le plus favorable.\n",
        "\n",
        "# La nécessité de partitionner les données\n",
        "\n",
        "La performance sur les données d’entraînement indique dans quelle mesure\n",
        "un modèle s’ajuste aux exemples qu’il a déjà vus. Elle fournit\n",
        "généralement une estimation optimiste de la performance sur de nouveaux\n",
        "exemples ; nous utilisons donc un ensemble de test distinct pour estimer\n",
        "la capacité de généralisation. L’ensemble de test ne doit influencer ni\n",
        "l’entraînement ni les choix effectués lors de la construction du modèle.\n",
        "Nous étudierons plus tard des procédures d’évaluation plus complètes.\n",
        "\n",
        "# Partitions reproductibles\n",
        "\n",
        "Lorsque les exemples peuvent raisonnablement être considérés comme des\n",
        "observations indépendantes provenant de la même distribution, une\n",
        "partition aléatoire réduit l’effet de leur ordre initial et aide à\n",
        "produire des sous-ensembles comparables. Elle ne garantit pas que chaque\n",
        "sous-ensemble sera représentatif, particulièrement lorsque le jeu de\n",
        "données est petit. Les séries chronologiques et les données groupées ou\n",
        "autrement dépendantes nécessitent d’autres stratégies de\n",
        "partitionnement.\n",
        "\n",
        "Nous créons d’abord un jeu de données jouet contenant 10 exemples. Nous\n",
        "utilisons des noms de couleurs pour voir facilement dans quel\n",
        "sous-ensemble chaque exemple se retrouve. `train_test_split` peut\n",
        "partitionner directement ces chaînes de caractères, bien que la plupart\n",
        "des estimateurs exigent une représentation numérique des attributs avant\n",
        "l’entraînement."
      ],
      "id": "265923e4-d479-48ec-b985-f766fd733e3f"
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Exemples originaux (attribut, cible) :\n",
            "[('rouge', 0), ('bleu', 1), ('vert', 0), ('jaune', 1), ('violet', 0), ('orange', 1), ('rose', 0), ('brun', 1), ('noir', 0), ('blanc', 1)]"
          ]
        }
      ],
      "source": [
        "X = [['rouge'], ['bleu'], ['vert'], ['jaune'], ['violet'],\n",
        "     ['orange'], ['rose'], ['brun'], ['noir'], ['blanc']]\n",
        "\n",
        "# y contient des cibles binaires (0 ou 1)\n",
        "\n",
        "y = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]\n",
        "\n",
        "print(\"Exemples originaux (attribut, cible) :\")\n",
        "print([(item[0], cible) for item, cible in zip(X, y)])"
      ],
      "id": "creer_jeu_de_donnees"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Nous utilisons ensuite la fonction\n",
        "[`train_test_split`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html)\n",
        "de scikit-learn pour partitionner `X` et `y` ensemble. Les attributs et\n",
        "les cibles correspondantes demeurent donc appariés. Nous produisons cinq\n",
        "partitions en fournissant cinq germes à `random_state`. L’argument\n",
        "`stratify=y` demande à la fonction de préserver les proportions des\n",
        "classes dans la mesure permise par la taille des sous-ensembles."
      ],
      "id": "10e873cd-a852-4327-aa16-6c64a27a8a7a"
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {},
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Germe (random_state) = 1   \n",
            "  Ensemble d'entraînement : [('noir', 0), ('bleu', 1), ('orange', 1), ('vert', 0), ('blanc', 1), ('violet', 0)]\n",
            "  Ensemble de test :        [('jaune', 1), ('brun', 1), ('rouge', 0), ('rose', 0)]\n",
            "\n",
            "Germe (random_state) = 42  \n",
            "  Ensemble d'entraînement : [('brun', 1), ('noir', 0), ('vert', 0), ('jaune', 1), ('orange', 1), ('violet', 0)]\n",
            "  Ensemble de test :        [('rouge', 0), ('rose', 0), ('bleu', 1), ('blanc', 1)]\n",
            "\n",
            "Germe (random_state) = 100 \n",
            "  Ensemble d'entraînement : [('brun', 1), ('bleu', 1), ('vert', 0), ('violet', 0), ('jaune', 1), ('rose', 0)]\n",
            "  Ensemble de test :        [('orange', 1), ('rouge', 0), ('noir', 0), ('blanc', 1)]\n",
            "\n",
            "Germe (random_state) = 2024\n",
            "  Ensemble d'entraînement : [('jaune', 1), ('blanc', 1), ('noir', 0), ('vert', 0), ('orange', 1), ('rose', 0)]\n",
            "  Ensemble de test :        [('bleu', 1), ('brun', 1), ('rouge', 0), ('violet', 0)]\n",
            "\n",
            "Germe (random_state) = 9999\n",
            "  Ensemble d'entraînement : [('rouge', 0), ('brun', 1), ('noir', 0), ('bleu', 1), ('violet', 0), ('orange', 1)]\n",
            "  Ensemble de test :        [('jaune', 1), ('blanc', 1), ('vert', 0), ('rose', 0)]\n"
          ]
        }
      ],
      "source": [
        "from sklearn.model_selection import train_test_split\n",
        "\n",
        "germes = [1, 42, 100, 2024, 9999]\n",
        "\n",
        "for germe in germes:\n",
        "\n",
        "    # random_state contrôle le mélange des données avant la partition\n",
        "\n",
        "    X_train, X_test, y_train, y_test = train_test_split(\n",
        "        X, y, test_size=0.4, random_state=germe, stratify=y\n",
        "    )\n",
        "    \n",
        "    # Conserver chaque couleur avec sa cible pour faciliter l'examen\n",
        "\n",
        "    exemples_entrainement = [\n",
        "        (item[0], cible) for item, cible in zip(X_train, y_train)\n",
        "    ]\n",
        "    exemples_test = [(item[0], cible) for item, cible in zip(X_test, y_test)]\n",
        "    \n",
        "    print(f\"Germe (random_state) = {germe:<4}\")\n",
        "    print(f\"  Ensemble d'entraînement : {exemples_entrainement}\")\n",
        "    print(f\"  Ensemble de test :        {exemples_test}\\n\")"
      ],
      "id": "train_test_split_germes"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Dans ces exemples, le changement du germe modifie les exemples qui\n",
        "appartiennent à chaque sous-ensemble. Le même entier fourni à\n",
        "`random_state` reproduit la même partition, pourvu que les données et\n",
        "l’environnement logiciel demeurent inchangés.\n",
        "\n",
        "# Conclusion : reproductibilité et variabilité\n",
        "\n",
        "Il importe de distinguer la reproduction d’une expérience de la mesure\n",
        "de sa sensibilité aux choix aléatoires.\n",
        "\n",
        "Fixer un germe, par exemple avec `random_state=42`, permet à d’autres\n",
        "personnes de reproduire une partition précise. Toutefois, une partition\n",
        "peut être exceptionnellement favorable ou défavorable ; un résultat\n",
        "reproductible ne constitue donc pas nécessairement un résumé fiable de\n",
        "la performance attendue.\n",
        "\n",
        "Plus tard dans le cours, nous utiliserons des procédures d’évaluation\n",
        "répétées pour mesurer cette variabilité. Ces expériences doivent suivre\n",
        "une procédure déterminée à l’avance et présenter à la fois la\n",
        "performance typique et sa dispersion, plutôt que d’essayer de nombreux\n",
        "germes et de ne retenir que le meilleur résultat. Le [cours](slides.qmd)\n",
        "illustre le phénomène sous-jacent : différentes partitions peuvent\n",
        "produire différents arbres de décision et différentes mesures de\n",
        "performance.\n",
        "\n",
        "# Documentation\n",
        "\n",
        "- [Module `random` de\n",
        "  Python](https://docs.python.org/3/library/random.html)\n",
        "- [`scikit-learn`\n",
        "  `train_test_split`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html)\n",
        "- [Contrôle de l’aléatoire dans\n",
        "  scikit-learn](https://scikit-learn.org/stable/common_pitfalls.html#controlling-randomness)\n",
        "\n",
        "# Références\n",
        "\n",
        "- Bethard, S. (2022). “We need to talk about random seeds”. arXiv\n",
        "  preprint [arXiv:2210.13393](https://arxiv.org/abs/2210.13393).\n",
        "- Henderson, P., Islam, R., Bachman, P., Pineau, J., Precup, D., &\n",
        "  Meger, D. (2018). “Deep reinforcement learning that matters”.\n",
        "  *Proceedings of the AAAI Conference on Artificial Intelligence*,\n",
        "  32(1).\n",
        "- Bouthillier, X., Delaunay, P., Bronzi, M., et al. (2021). “Accounting\n",
        "  for variance in machine learning benchmarks”. *Proceedings of Machine\n",
        "  Learning and Systems*, 3, 747-769."
      ],
      "id": "bd242851-fc57-4f5f-9b2f-1c920e16fa3b"
    }
  ],
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "path": "/Users/turcotte/.virtualenvs/csi4106/share/jupyter/kernels/python3"
    },
    "language_info": {
      "name": "python",
      "codemirror_mode": {
        "name": "ipython",
        "version": "3"
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.12"
    }
  }
}