{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "71f0c610",
   "metadata": {},
   "source": [
    "# V.2 - Travaux pratiques\n",
    "\n",
    "L'objectif de cette séance de travaux pratiques est d'illustration l'utilisation des machines à vecteurs de support (*Support Vector Machines*, SVM) pour les problèmes de classification linéaires en python avec Scikit-learn.\n",
    "\n",
    "Références externes utiles :\n",
    "\n",
    "- [Documentation NumPy](https://docs.scipy.org/doc/numpy/user/index.html)\n",
    "- [Documentation SciPy](https://docs.scipy.org/doc/scipy/reference/)\n",
    "- [Documentation MatPlotLib](http://matplotlib.org/)\n",
    "- [Site scikit-learn](http://scikit-learn.org/stable/index.html)\n",
    "- [Site langage python](https://www.python.org)\n",
    "- [Site LibSVM](https://www.csie.ntu.edu.tw/~cjlin/libsvm/)\n",
    "- [Site LibLinear](https://www.csie.ntu.edu.tw/~cjlin/liblinear/)\n",
    "\n",
    "## Machines à vecteurs de support linéaires\n",
    "\n",
    "Les machines à vecteurs de support (SVM : *Support Vector Machines*) sont une classe de méthodes d'apprentissage statistique basées sur le principe de la maximisation de la marge (séparation des classes). Il existe plusieurs formulations (linéaires, versions à noyaux) qui peuvent s'appliquer sur des données séparables (linéairement) mais aussi sur des données non séparables.\n",
    "\n",
    "Les avantages des SVM :\n",
    "\n",
    "- Très efficaces en dimension élevée.\n",
    "- Ils sont aussi efficaces dans le cas où la dimension de l'espace est plus grande que le nombre d'échantillons d'apprentissage.\n",
    "- Pour la décision, n'utilisent pas tous les échantillons d'apprentissage, mais seulement une partie (les vecteurs de support). En conséquence, ces algorithmes demandent moins de mémoire.\n",
    "\n",
    "Désavantages :\n",
    "\n",
    "- Si le nombre d'attributs est beaucoup plus grand que le nombre d'échantillons, les performances sont moins bonnes.\n",
    "- Comme il s'agit de méthodes de discrimination entre les classes, elles ne fournissent pas d'estimations de probabilités.\n",
    "\n",
    "## Jeu de données Iris\n",
    "\n",
    "Dans Scikit-learn, les SVM sont implémentées dans le module `sklearn.svm`. Dans cette partie nous allons nous intéresser à la version linéaire (Scikit-learn utilise les bibliothèques libLinear et libSVM déjà discutées). Nous allons utiliser le jeu de données Iris déjà rencontré dans les séances précédentes. Pour pouvoir afficher les résultats, on va utiliser seulement les premiers deux attributs (longueur et largeur des sépales)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "61fda27c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn import svm, datasets\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# Chargement des données\n",
    "iris = datasets.load_iris()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11ccbc02",
   "metadata": {},
   "source": [
    "Pour commencer, nous ne conservons que les deux premiers attributs du jeu de données :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42414b8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "X, y = iris.data[:, :2], iris.target\n",
    "# On conserve 50% du jeu de données pour l'évaluation\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d99bc5e",
   "metadata": {},
   "source": [
    "Nous pouvons maintenant entraîner une machine à vecteur de support linéaire :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2591e41f",
   "metadata": {},
   "outputs": [],
   "source": [
    "C = 1.0 # paramètre de régularisation\n",
    "lin_svc = svm.LinearSVC(C=C)\n",
    "lin_svc.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfb1c03a",
   "metadata": {},
   "source": [
    "<div class=\"admonition-question admonition\">\n",
    "\n",
    "**Question**\n",
    "\n",
    "Calculez le score d'échantillons bien classifiés sur le jeu de données de test.\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c585868",
   "metadata": {},
   "source": [
    "Visualisons maintenant la surface de décision apprise par notre modèle :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fdfa8e12",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Créer la surface de décision discretisée\n",
    "x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1\n",
    "y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1\n",
    "# Pour afficher la surface de décision on va discrétiser l'espace avec un pas h\n",
    "h = max((x_max - x_min) / 100, (y_max - y_min) / 100)\n",
    "xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))\n",
    "\n",
    "# Surface de décision\n",
    "Z = lin_svc.predict(np.c_[xx.ravel(), yy.ravel()])\n",
    "Z = Z.reshape(xx.shape)\n",
    "\n",
    "plt.contourf(xx, yy, Z, cmap=plt.cm.coolwarm, alpha=0.8)\n",
    "# Afficher aussi les points d'apprentissage\n",
    "plt.scatter(X_train[:, 0], X_train[:, 1], label=\"train\", edgecolors='k', c=y_train, cmap=plt.cm.coolwarm)\n",
    "plt.scatter(X_test[:, 0], X_test[:, 1], label=\"test\", marker='*', c=y_test, cmap=plt.cm.coolwarm)\n",
    "plt.xlabel('Sepal length')\n",
    "plt.ylabel('Sepal width')\n",
    "plt.title(\"LinearSVC\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e233da27",
   "metadata": {},
   "source": [
    "<div class=\"admonition-question admonition\">\n",
    "\n",
    "**Question**\n",
    "\n",
    "Testez differentes valeurs pour le paramètre C. Comment la frontière de décision évolue en fonction de C ?\n",
    "\n",
    "</div>\n",
    "\n",
    "<div class=\"admonition-question admonition\">\n",
    "\n",
    "**Question**\n",
    "\n",
    "D'après la visualisation ci-dessus, ce modèle vous paraît-il adapté au problème ? Si non, que peut-on faire pour l'améliorer ?\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98e8874d",
   "metadata": {},
   "source": [
    "Nous verrons dans le prochain TP que scikit-learn permet de manipuler des machines à vecteurs de support avec des noyaux non-linéaires dans la classe `SVC`.\n",
    "\n",
    "Les modèles linéaires `LinearSVC()` et `SVC(kernel='linear')`, comme nous l'avons déjà dit, produisent des résultats légèrement différents à cause du fait qu'ils optimisent des fonctions de coût différentes mais aussi à cause du fait qu'ils gèrent les problèmes multi-classe de manière différente (linearSVC utilise *One-vs-All* et SVC utilise *One-vs-One*)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "08a845ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "lin_svc = svm.LinearSVC(C=C).fit(X_train, y_train)\n",
    "svc = svm.SVC(kernel='linear', C=C).fit(X_train, y_train)\n",
    "\n",
    "titles = ['SVC with linear kernel', 'LinearSVC (linear kernel)']\n",
    "\n",
    "fig = plt.figure(figsize=(12, 4.5))\n",
    "\n",
    "for i, clf in enumerate((svc, lin_svc)):\n",
    "    plt.subplot(1, 2, i + 1)\n",
    "    plt.subplots_adjust(wspace=0.4, hspace=0.4)\n",
    "    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])\n",
    "    # Utiliser une palette de couleurs\n",
    "    Z = Z.reshape(xx.shape)\n",
    "    plt.contourf(xx, yy, Z, cmap=plt.cm.coolwarm, alpha=0.8)\n",
    "    # Afficher aussi les points d'apprentissage\n",
    "    plt.scatter(X_train[:, 0], X_train[:, 1], label=\"train\", edgecolors='k', c=y_train, cmap=plt.cm.coolwarm)\n",
    "    plt.scatter(X_test[:, 0], X_test[:, 1], label=\"test\", marker='*', c=y_test, cmap=plt.cm.coolwarm)\n",
    "    plt.xlabel('Sepal length')\n",
    "    plt.ylabel('Sepal width')\n",
    "    plt.title(titles[i])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13588530",
   "metadata": {},
   "source": [
    "Pour l'instant, nous n'avons exploité que deux variables explicatives. Néanmoins, l'intérêt des machines à vecteur de support linéaires est qu'il est souvent plus facile de trouver des hyperplans séparateurs dans des espaces de grande dimension.\n",
    "\n",
    "<div class=\"admonition-question admonition\">\n",
    "\n",
    "**Question**\n",
    "\n",
    "Réalisez l'optimisation d'une nouvelle machine à vecteur de support linéaire mais en utilisant les quatre attributs du jeu de données Iris. Le score de classification en test a-t-il augmenté ? Pourquoi ?\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64fe1bda",
   "metadata": {},
   "source": [
    "## Jeu de données Digits\n",
    "\n",
    "Le jeu de données Digits est une collection d'images de chiffres manuscrits (nous l'avons déjà utilisé dans le {ref}`TP sur les forêts aléatoires <chap-tpForetsAleatoires>`). Elles peuvent se charger directement depuis scikit-learn :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ce5520b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.datasets import load_digits\n",
    "digits = load_digits()\n",
    "X, y = digits.data, digits.target"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc85d0a0",
   "metadata": {},
   "source": [
    "<div class=\"admonition-question admonition\">\n",
    "\n",
    "**Question**\n",
    "\n",
    "Utilisez les données Digits pour construire un classifieur LinearSVC et évaluez-le. Si le temps d'apprentissage est trop long, sélectionnez une partie plus petite de la base d'apprentissage (par exemple 10000 échantillons). Pour quelle valeur de C on obtient le meilleurs résultats de généralisation ?\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09d160be",
   "metadata": {},
   "source": [
    "## Approfondissement : LibLinear et libSVM\n",
    "\n",
    "<div class=\"admonition-note admonition\">\n",
    "\n",
    "**Note**\n",
    "\n",
    "Cette partie est facultative. Elle vous permet de manipuler directement les bibiothèques LibLinear et LibSVM sur lesquelles s'appuie scikit-learn pour les machines à vecteur de supports. Ces implémentations sont très rapides et sont écrites en langage C.\n",
    "\n",
    "Il est nécessaire d'avoir un compilateur C fonctionnel pour cette partie.\n",
    "\n",
    "</div>\n",
    "\n",
    "Dans cette partie nous allons tester [LibLinear](https://www.csie.ntu.edu.tw/~cjlin/liblinear/), une implémentation libre de la formulation SVC sur un problème de classification. Ouvrez un terminal lignes de commandes et créez un répertoire `tpsvm` :\n",
    "\n",
    "```bash\n",
    "cd ~\n",
    "mkdir tpsvm\n",
    "cd tpsvm\n",
    "```\n",
    "\n",
    "Commençons par récupérer et compiler le code de la bibliothèque :\n",
    "\n",
    "```bash\n",
    "wget http://cedric.cnam.fr/~ferecatu/RCP209/liblinear-2.2.tar.gz\n",
    "tar xzvf liblinear-2.2.tar.gz\n",
    "cd liblinear-2.2/\n",
    "make\n",
    "```\n",
    "\n",
    "Nous allons utiliser la base de chiffres en en écriture manuscrite MNIST : 10 classes, 60000 échantillons d'apprentissage, 10000 échantillon de test, 784 attributs.\n",
    "\n",
    "```bash\n",
    "mkdir databases\n",
    "cd databases\n",
    "wget http://cedric.cnam.fr/~ferecatu/RCP209/mnist.bz2\n",
    "wget http://cedric.cnam.fr/~ferecatu/RCP209/mnist.t.bz2\n",
    "bzip2 -d mnist.bz2\n",
    "bzip2 -d mnist.t.bz2\n",
    "```\n",
    "\n",
    "Entrainer un modèle SVMC linéaire avec les paramètres par défaut :\n",
    "\n",
    "```bash\n",
    "time ../train mnist\n",
    ">>> real    2m22.716s\n",
    "time ../predict mnist.t mnist.model mnist.t.output\n",
    ">>> Accuracy = 80.26% (8026/10000)\n",
    ">>> real    0m0.283s\n",
    "```\n",
    "\n",
    "<div class=\"admonition-question admonition\">\n",
    "\n",
    "**Question**\n",
    "\n",
    "Les SVM sont par leur conception des classifieurs binaires. Comment LibLinear gère la classification multi-classe (10 classes sur la base MNIST) ?\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5a0076f",
   "metadata": {},
   "source": [
    "### LibSVM\n",
    "\n",
    "On peut obtenir le même type de classification (linéaire) en utilisant [LibSVM](https://www.csie.ntu.edu.tw/~cjlin/libsvm/) et un noyau linéaire.\n",
    "\n",
    "```bash\n",
    "cd ~\n",
    "cd tpsvm\n",
    "wget http://cedric.cnam.fr/~ferecatu/RCP209/libsvm-3.22.tar.gz\n",
    "tar xzvf libsvm-3.22.tar.gz\n",
    "cd libsvm-3.22/\n",
    "make\n",
    "ln -s ~/tpsvm/liblinear-2.1/databases .\n",
    "# Prendre 30000 échantillons aléatoires\n",
    "shuf -n 20000 databases/mnist > mnist30000\n",
    "time ./svm-train -t 0 mnist30000\n",
    ">>> real    3m10.783s\n",
    "time ./svm-predict  databases/mnist.t mnist30000.model mnist.t.output\n",
    ">>> Accuracy = 90.86% (9086/10000) (classification)\n",
    ">>> real    0m33.433s\n",
    "```\n",
    "\n",
    "Avec seulement 30000 échantillons sur la base d'apprentissage, la construction du modèle prend plus de temps que pour LibLinear avec tous les échantillons. Par contre, les performances sont un peu meilleures sur ce jeu de données. L'intérêt de LibLinear est donc la vitesse, ce qui permet de l'appliquer sur de gros jeux de données, mais bien sûr sa limitation est qu'on peut l'appliquer seulement à des problèmes linéaires. La différence entre les deux vient du fait que LibLinear minimise une fonction de perte quadratique alors que LibSVM minimise la perte non-quadratique, ce qui sur cette base semble mieux fonctionner, mais ce n'est pas la règle.\n",
    "\n",
    "<div class=\"admonition-question admonition\">\n",
    "\n",
    "**Question**\n",
    "\n",
    "Expliquez le paramètre `-t 0` (regardez la documentation).\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07cc4ea3",
   "metadata": {},
   "source": [
    "### SVM-toy\n",
    "\n",
    "Par la suite on va explorer l'outil `svm-toy` qui permet de visualiser la classification pour des problèmes en deux dimensions.\n",
    "\n",
    "```bash\n",
    "cd ~/tpsvm/libsvm-3.22/svm-toy/qt\n",
    "make\n",
    "./svm-toy\n",
    "```\n",
    "\n",
    "Si la version Qt ne compile pas, essayez la version `svm-toy/gtk` ou la version java `libsvm-3.22/java`. Cliquez sur la surface d'affichage pour ajouter des points d'apprentissage. Changez de couleur pour ajouter une nouvelle classe (l'outil gère aussi la classification multi-classe). Utilisez l'option `-t 0` pour sélectionner la classification linéaire. Pour changer le paramètre à C=valeur utilisez l'option `-c valeur` (par exemple : `-c 2.5`). Testez plusieurs valeurs pour le paramètre C pour voir son comportement de régularisation pour les fichiers suivants : [01.dat](http://cedric.cnam.fr/vertigo/Cours/ml2/Bloc_svm/docs/01.dat), [02.dat](http://cedric.cnam.fr/vertigo/Cours/ml2/Bloc_svm/docs/02.dat), [03.dat](http://cedric.cnam.fr/vertigo/Cours/ml2/Bloc_svm/docs/03.dat)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
