{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fe5a8fb9",
   "metadata": {},
   "source": [
    "# Compétition de ML\n",
    "\n",
    "**Objectif** : Ce TP a pour but de vous mettre en conditions réelles de modélisation d'un problème d'apprentissage automatique. Donné un jeu de données correspondant à une tâche de classification, vous devez apprendre le meilleur modèle possible avec les outils que vous avez appris dans cette EC. Votre modèle sera ensuite évalué sur un jeu de test caché, ce qui révèlera les différentes performances de vos modèles.\n",
    "\n",
    "**Règles** : par binome, optimisez un modèle de ML que vous soumettrez sur la competition Kaggle : https://www.kaggle.com/competitions/competition-insa-i-4-2026/overview\n",
    "\n",
    "**Invitation**: https://www.kaggle.com/t/2012386b884248ee855408c842dcc553"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cc92ec6",
   "metadata": {},
   "source": [
    "## Le jeu de données\n",
    "\n",
    "Le jeu de données MNIST est un jeu de données historique du domaine de l'apprentissage automatique et qui a permis de révéler la puissance des réseaux de neurones convolutionnels, ce qui a initié la révolution du Deep Learning depuis 2012.\n",
    "\n",
    "Ce jeu de données est initialement constitué d'un jeu d'apprentissage de 60 000 données, et d'un jeu de test de 10 000 images. Chaque image représente un chiffre manuscrit issu du système postal américain encodé par une image de 28 par 28 pixels en niveaux de gris. Chaque pixel a donc une valeur entre 0 (noir) et 255 (blanc).\n",
    "\n",
    "Le but de la tâche associée à ce jeu de donnée est de retrouver le chiffre (parfois mal) écrit (entre 0 et 9). \n",
    "\n",
    "Références :\n",
    "* http://yann.lecun.com/exdb/mnist/\n",
    "* https://en.wikipedia.org/wiki/MNIST_database"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31bb2d02",
   "metadata": {},
   "source": [
    "## 1. Chargement du jeu de données\n",
    "\n",
    "Le jeu de données étant assez conséquent, nous avons réduit le jeu de données à 10000 images, soit environ 1000 données par classe. Chaque image est représentée par un vecteur de taille $28^2 = 784$ dimensions. Ce vecteur est obtenu en concaténant les colonnes de l'image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e08f48e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from joblib import load, dump\n",
    "\n",
    "X, y  = load(\"data_train.joblib\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b946cd3",
   "metadata": {
    "metadata": {}
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def draw_image(image,y='unknown'):\n",
    "    plt.figure()\n",
    "    plt.imshow(image.reshape((28,28)),cmap=\"gray\",vmin=0,vmax=255)\n",
    "    plt.title(y)\n",
    "\n",
    "# affichage de quelques images\n",
    "indexes = np.random.randint(0,10000,10)\n",
    "for i in indexes:\n",
    "    draw_image(X[i,:],y[i])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1371a69d",
   "metadata": {},
   "source": [
    "## 2. Conception du modèle\n",
    "\n",
    "Afin de répondre à la tâche donnée, nous devons mettre en oeuvre un algorithme de classification multi classe (pour chaque chiffre). Pour cette compétition, nous allons nous resteindre aux modèles disponibles dans `scikit-learn`. Comme vu en TP, l'avantage de ces modèles est de fournir une interface similaire pour l'ensemble des méthodes :\n",
    "* `fit` : pour apprendre le modèle et regler les paramètres internes\n",
    "* `predict` : pour prédire des données selon les paramètres appris\n",
    "\n",
    "### Modèles pour la classification\n",
    "Dans `scikit-learn`, plusieurs méthodes permettent d'effectuer des taches de classification multiclasse. Voici une liste non exhaustive que vous pourrez enrichir. Pour chaque méthode est indiqué les hyperparamètres les plus intéressants à optimiser.\n",
    "\n",
    "### K-plus proches voisins\n",
    "[lien vers la doc](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.KNeighborsClassifier)\n",
    "\n",
    "Un modèle intuitif et simple mais éprouvé : décider de la classe en regardant la classe des données les plus proches. Vient alors la question de comment définir la notion de distance entre les données ?\n",
    "\n",
    "Hyperparamètres à optimiser : `n_neighbors`(le nombre de voisins à regarder), `weights` (est ce que chaque voisin a la meme influence, ou alors cette influence est pondérée par la distance ?), `metric` (la distance à utiliser. Vous pouvez même fournir votre propre fonction de distance).\n",
    "\n",
    "\n",
    "#### Régression logistique\n",
    "[lien vers la doc](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html)\n",
    "\n",
    "Modèle linéaire permettant d'effectuer une classification, et non une régression comme son nom semble l'indiquer.  Méthode simple et efficace, mais reste assez basique.\n",
    "\n",
    "Hyperparamètres à optimiser : `C`, `penalty`\n",
    "\n",
    "#### SVM\n",
    "[lien vers la doc](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html?highlight=svc#sklearn.svm.SVC)\n",
    "\n",
    "Modèle initialement linéaire profitant de \"l'astuce du noyau\" pour calculer une fonction de décision non linéaire. Ce modèle applique une projection des données dans un espace, ou une frontière de décision linéaire est déterminée. Ces méthodes étaient l'état de l'art avant l'avènement du deep.\n",
    "\n",
    "Hyperparamètres à optimiser : `C`, `kernel` (manière de comparer deux images, vous pouvez meme implémenter le votre !), `degree` (pour les kernels polynomiaux), `gamma`\n",
    "\n",
    "Le SVM dans sa version linéaire (peut être plus rapide) : https://scikit-learn.org/stable/modules/generated/sklearn.svm.LinearSVC.html#sklearn.svm.LinearSVC\n",
    "\n",
    "#### Arbre de décision\n",
    "[lien vers la doc](https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html?highlight=decision%20tree#sklearn.tree.DecisionTreeClassifier)\n",
    "\n",
    "Les arbres de décision permettent de découper l'espace des données afin d'y associer une décision (la classe) à chaque case finale. Les arbres de décision peuvent être assembler en \"foret\" pour concevoir des modèles plus complexes (https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html?highlight=forest#sklearn.ensemble.RandomForestClassifier). Les arbres et leurs dérivés sont des méthodes plutot anciennes mais remises au gout du jour par de très bonnes performances et une explicabilité appréciable.\n",
    "\n",
    "Hyperparamètres à optimiser : `max_depth` : profondeur maximum de l'arbre\n",
    "\n",
    "#### Multi Layer Perceptron\n",
    "[lien vers la doc](https://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPClassifier.html?highlight=mlp#sklearn.neural_network.MLPClassifier)\n",
    "\n",
    "Un réseau de neurones classique. Dans la forme initiale, ce réseau comporte une seule couche cachée de 100 neurones, on ne peut donc pas parler de \"deep\". Aussi, ce n'est pas un CNN et par conséquent, n'est pas idéal pour des données en entrée sous forme d'image. Toutefois, cela reste un bon candidat pour la victoire, surtout avec une bonne architecture voir un prétraitement sur les données.\n",
    "\n",
    "Hyperparamètres à optimiser : \n",
    "* `hidden_layer_sizes` : la définition de votre architecture par une liste d'entiers. Chaque élément de la liste code le nombre de neurones de la couche correspondante. Clairement l'hyperparamètre le plus important du modèle.\n",
    "* `activation` : la fonction d'activation assurant la non linéarité. Depuis quelques années, `relu` fait consensus mais cela peut valoir le coup de tenter autre chose.\n",
    "* `alpha` : l'importance de la régularisation\n",
    "\n",
    "D'autres méthodes de classification sont disponibles dans `scikit-learn`, n'hésitez pas à fouiller mais attention à ne pas perdre trop de temps !\n",
    "\n",
    "### Stratégies\n",
    "Afin de vous aiguiller, voici quelques conseils stratégiques.\n",
    "#### Réglages des hyparamètres\n",
    "Régler vos hyperparamètres en pensant au sur apprentissage et en appliquant une bonne stratégie (vue en cours) pour éviter les biais le plus possible.\n",
    "\n",
    "#### Prétraitement \n",
    "Rien de vous empêche d'appliquer une transformation quelconque de vos données. Attention à ce que votre modèle intègre bien se préprocessing pour les données de test.\n",
    "\n",
    "#### Projection et réduction de dimension\n",
    "Une PCA peut être une bonne stratégie pour réduire la dimension et ainsi accélérer les calculs tout en supprimant du bruit dans vos données.\n",
    "\n",
    "#### Réduire le dataset\n",
    "Avec 10 000 images, les temps de calculs peuvent être longs, selon la méthode et les hyperparamètres. Une stratégie peut être de se limiter à un sous ensemble des données, ou alors de calculer un faible nombre de représentants via un K-means par exemple.\n",
    "\n",
    "#### Conseils généraux\n",
    "* N'hésitez pas à utiliser 2 Pc en //, ou colab pour tester plusieurs choses.\n",
    "* Vous ne pourrez pas tout tester, il faudra choisir\n",
    "* N'hésitez pas a soumettre régulièrement des modèles intermédiaires.\n",
    "* Les données de test existent sur le web. Mais je vous déconseille de passer du temps à les récupérer, vous allez passer tout le TP à essayer de les décoder correctement."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c16aa995",
   "metadata": {},
   "source": [
    "## 3. À vous de jouer.\n",
    "Entrainez un modèle sur le jeu de données d'entrainement disponible. Faites de votre mieux pour bien choisir le modèle, ainsi que les hyperparamètres. Vous pouvez vous aider de la fonction `accuracy_score` pour évaluer vos modèles.\n",
    "\n",
    "Voici un exemple basique d'un apprentissage de modèle, suivi de la génération de la soumission des prédictions pour Kaggle.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67038495",
   "metadata": {
    "metadata": {}
   },
   "outputs": [],
   "source": [
    "# MLPClassifier\n",
    "from sklearn.neural_network import MLPClassifier\n",
    "from sklearn.model_selection import train_test_split\n",
    "import warnings\n",
    "from sklearn.exceptions import ConvergenceWarning\n",
    "from sklearn.metrics import accuracy_score\n",
    " \n",
    "\n",
    "\n",
    "X_train, X_val, y_train, y_val = train_test_split(X, y, random_state=0, train_size=0.3)\n",
    "\n",
    "mlp = MLPClassifier(\n",
    "    hidden_layer_sizes=(90,),\n",
    "    max_iter=300,\n",
    "    alpha=1e-4,\n",
    "    solver=\"sgd\",\n",
    "    verbose=10,\n",
    "    random_state=1,\n",
    "    learning_rate_init=0.002,\n",
    ")\n",
    "\n",
    "\n",
    "with warnings.catch_warnings():\n",
    "    warnings.filterwarnings(\"ignore\", category=ConvergenceWarning, module=\"sklearn\")\n",
    "    mlp.fit(X_train, y_train)\n",
    "\n",
    "print(\"Training set score: %f\" % accuracy_score(mlp.predict(X_train), y_train))\n",
    "print(\"Validation set score: %f\" % accuracy_score(mlp.predict(X_val), y_val))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "918bef49",
   "metadata": {},
   "source": [
    "### Soumission pour Évaluation\n",
    "\n",
    "Une fois votre modèle entrainé, vous allez être départagé sur sa performance sur un jeu de test caché.\n",
    "\n",
    "Pour cela, il va falloir créer une soumission sur la compétition Kaggle correspondante. \n",
    "\n",
    "Afin d'évaluer vos prédictions, Kaggle vous demande de créer un fichier CSV contenant les prédictions de votre modèle sur le jeu de test. Ce fichier doit contenir deux colonnes : `Id` et `Label`. La première colonne contient l'identifiant de l'image (de 0 à ?), et la seconde colonne contient la classe prédite (un chiffre entre 0 et 9). Le fichier doit être au format CSV, sans index. La fonction `generate_submission` vous permet de générer ce fichier à partir de votre modèle. \n",
    "\n",
    "Vous pouvez ensuite soumettre ce fichier sur la compétition Kaggle pour évaluer votre modèle : https://www.kaggle.com/competitions/competition-insa-i-4\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ada6d977",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_submission_file(model, images, filename=\"submission.csv\"):\n",
    "    \"\"\"\n",
    "    Generates a submission file for the Kaggle competition.\n",
    "    \"\"\"\n",
    "    predictions = model.predict(images)\n",
    "    with open(filename, \"w\") as f:\n",
    "        f.write(\"Id,Label\\n\")\n",
    "        for i, pred in enumerate(predictions):\n",
    "            f.write(f\"{i},{pred}\\n\")\n",
    "    print(f\"Submission file generated: {filename}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b52f0cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from joblib import load \n",
    "X_test = load(\"images_test.joblib\")\n",
    "generate_submission_file(mlp, X_test, \"my_submission.csv\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0d04054",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "i4",
   "language": "python",
   "name": "i4"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
