diff --git a/TP5/TP5_ex2_sujet.ipynb b/TP5/TP5_ex2_sujet.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..6bedcda08bf175b07398dca4d8565a4dd537359f
--- /dev/null
+++ b/TP5/TP5_ex2_sujet.ipynb
@@ -0,0 +1,246 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Introduction à l'apprentissage automatique: TP5 - Exercice 2\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "### Reconnaissance d'images par réseaux de neurones\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "On reprend la base de données Fashion-MNIST du TP4. Rappelons que l'on se restreint à un sous-ensemble de 10000 observations pour garder des temps de calcul raisonnables pendant la séance. Néanmoins, il serait plus satisfaisant d'utiliser l'ensemble de la base de données à notre disposition (à faire si vous avez une machine performante et du temps). "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt\n",
+    "import time\n",
+    "from sklearn import datasets, metrics, neural_network, model_selection\n",
+    "%matplotlib notebook \n",
+    "\n",
+    "# Fashion-Mnist database sur openML: (il faut quelques dizaines de secondes pour charger la base)\n",
+    "size_images=(28,28)\n",
+    "X_fashion, y_fashion = datasets.fetch_openml(data_id=40996, return_X_y=True, as_frame=False)\n",
+    "X_fashion=X_fashion[:10000,:]/255.  # normalisation des niveaux de gris entre 0 et 1\n",
+    "y_fashion=y_fashion[:10000]\n",
+    "\n",
+    "for i in range(10):\n",
+    "    n=np.sum(y_fashion==str(i))\n",
+    "    print(\"nombre d'observations dans la classe %d: %d\" %(i,n))\n",
+    "\n",
+    "n_samples = len(X_fashion)\n",
+    "print(\"nombre total d'observations (apprentissage + test): %d\" % n_samples)\n",
+    "\n",
+    "n_features = len(X_fashion[0])\n",
+    "print(\"nombre de caractéristiques par observation: %d\" % n_features)\n",
+    "\n",
+    "X_train, X_test, y_train, y_test = model_selection.train_test_split(X_fashion, y_fashion, test_size=0.2, random_state=1)\n",
+    "\n",
+    "print(\"nombre d'observations dans la base d'apprentissage: %d\" %len(X_train))\n",
+    "print(\"nombre d'observations dans la base de test: %d\" %len(X_test))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "__Remarque__: ici, les données d'apprentissage sont les vecteurs des niveaux de gris pixel à pixel. On divise par 255 le niveau de gris (codé sur 8 bits, donc entre 0 et 255) de manière à normaliser les données entre 0 et 1. Comme on l'a déjà dit, cette manipulation améliore le comportement des algorithmes d'optimisation numérique. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "La cellule suivante définit la fonction qui permet d'afficher les 150 premières images de la base de test, ainsi que la classe véritable et la classe déterminée par l'algorithme de classification:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def affichage_150_images(X_test,y_test,y_pred):\n",
+    "    plt.figure(figsize=[10,12])   \n",
+    "    for n in range(150):\n",
+    "        plt.subplot(10,15,n+1,xticks=[],yticks=[])\n",
+    "        plt.imshow(np.reshape(X_test[n,:],size_images),cmap='gray_r')\n",
+    "        if y_pred[n]==y_test[n]:\n",
+    "            plt.text(0.1,0.1,str(y_pred[n])+' / '+str(y_test[n]),fontsize=6,bbox=dict(facecolor='white', alpha=1))    \n",
+    "        else:\n",
+    "            plt.text(0.1,0.1,str(y_pred[n])+' / '+str(y_test[n]),fontsize=6,bbox=dict(facecolor='red', alpha=1))    \n",
+    "    plt.suptitle('classe predite / classe réelle')\n",
+    "    plt.show();"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "L'entraînement d'un réseau de neurones _feedforward_ (perceptron multicouche) peut se faire de la manière suivante:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "MLP = neural_network.MLPClassifier(hidden_layer_sizes=(10,), max_iter=20, alpha=0,\n",
+    "                    solver='sgd', verbose=\"true\", learning_rate_init=.1, random_state=1)\n",
+    "MLP.fit(X_train, y_train)\n",
+    "print(\"Score sur l'ensemble d'apprentissage: %.3f\" % MLP.score(X_train, y_train))\n",
+    "print(\"Score sur l'ensemble de test: %.3f\" % MLP.score(X_test, y_test))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Chaque itération correspond à un parcours de la base d'apprentissage (une _epoch_ ): ce n'est pas une itération de l'algorithme de descente. On voit que le _loss_ décroît au fur et à mesure des itérations, ce qui est logique. Cependant, une faible augmentation du loss pourrait être observée d'une itération à l'autre car on utilise l'algorithme du gradient stochastique.\n",
+    "\n",
+    "Consultez la documentation __[ici](https://scikit-learn.org/stable/modules/neural_networks_supervised.html)__ et __[là](https://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPClassifier.html)__ et faites le lien avec le cours. En particulier, notez que dans la cellule précédente: on a déclaré un réseau à une seule couche cachée de 10 neurones (`hidden_layer_sizes=(10,)`), on n'a pas de régularisation $L^2$ des poids (car `alpha=0`), et on a précisé le solveur _Stochastic Gradient Descent_ pour lequel on a fixé le taux d'apprentissage ( `learning_rate=.1` ). Notez qu'il existe d'autres solveurs (on ne pourra pas rentrer dans les détails dans ce cours), et notez également dans la documentation comment est fixée la taille du lot ( _batch_ ).\n",
+    "\n",
+    "Comme on l'a vu en cours, le loss (entropie croisée après la couche SoftMax) n'est pas le taux de classifications correctes. Le petit script suivant permet de suivre l'évolution du taux de classification sur la base d'apprentissage et sur la base de test au cours des itérations (ignorez l'avertissement). Notez les valeurs des paramètres.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "niter=150\n",
+    "score_train=[]\n",
+    "score_test=[]\n",
+    "loss_train=[]\n",
+    "MLP = neural_network.MLPClassifier(hidden_layer_sizes=(10,), alpha=0, activation=\"relu\",\n",
+    "                                   max_iter=1, warm_start=True,\n",
+    "                                   solver='sgd', learning_rate_init=.01, random_state=1)\n",
+    "t0=time.time()\n",
+    "for k in range(1,niter):\n",
+    "    print(\"\\r %d / %d\" %(k,niter),end=\"\")\n",
+    "    MLP.fit(X_train,y_train)\n",
+    "    loss_train.append(MLP.loss_)\n",
+    "    score_train.append(MLP.score(X_train,y_train))\n",
+    "    score_test.append(MLP.score(X_test,y_test))\n",
+    "t1=time.time()\n",
+    "print(\"\\ntemps par epoch: %.3f s\" %((t1-t0)/niter))\n",
+    "    \n",
+    "# train & test score plot\n",
+    "plt.figure(figsize=[10,6])\n",
+    "X=np.array(range(1,niter))\n",
+    "plt.plot(X,score_train,X,score_test)\n",
+    "plt.legend(['train','test'])\n",
+    "plt.title('score de classification')\n",
+    "plt.ylim(bottom=0.7,top=1.)\n",
+    "plt.grid()\n",
+    "plt.show();\n",
+    "\n",
+    "# train loss plot\n",
+    "# (pas de possibilité simple d'obtenir le test loss)\n",
+    "plt.figure(figsize=[10,6])\n",
+    "X=np.array(range(1,niter))\n",
+    "plt.plot(X,loss_train)\n",
+    "plt.legend(['train loss'])\n",
+    "plt.title('valeur du loss')\n",
+    "plt.ylim(bottom=0.,top=0.8)\n",
+    "plt.grid()\n",
+    "plt.show();\n",
+    "\n",
+    "# scores finaux:\n",
+    "print('MLP train score: %.3f' %MLP.score(X_train,y_train)) \n",
+    "print('MLP test score: %.3f' %MLP.score(X_test,y_test))\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "__Questions__:\n",
+    "\n",
+    "- Pourquoi, avec ce jeu de paramètres, devrait-on se limiter à une soixantaine d'itérations?\n",
+    "\n",
+    "- Observez les problèmes de convergence si `learning_rate` est trop grand (essayez $0.05$) ou trop petit (essayez $0.001$). Revenez à $0.01$.\n",
+    "\n",
+    "- Essayez différentes combinaison de l'architecture (une couche cachée avec 10 neurones, 50 neurones, 100 neurones, puis deux couches cachées avec le même nombre de neurones), et du paramètre `alpha` (0 pour ne pas régulariser, 1e-4 par défaut, 1e-2, 1e-1, 1), ou de l'activation (`relu` ou `logistic`). Quel est le réseau le plus performant ? Soyez attentif aux situations de surapprentissage.\n",
+    "\n",
+    "Trois principes peuvent vous guider: \n",
+    "- en situation de surapprentissage (grande différence entre scores d'apprentissage et de test qui finit par décroître), régularisez davantage\n",
+    "- si la régularisation ne fait que diminuer le score d'apprentissage mais n'augmente pas le score de test, c'est que votre réseau est trop compliqué: dans ce cas simplifiez-le\n",
+    "- si le score de test croît à peu près continûment, augmentez le nombre d'itérations car vous n'avez pas encore atteint l'optimum.\n",
+    "\n",
+    "Fixer l'architecture et les hyperparamètres d'un réseau de neurones est assez rébarbatif... Il faut s'aider de l'expérience et de guides de \"bonnes pratiques\". Ne consacrez pas trop de temps à cet exercice!\n",
+    "\n",
+    "_Indication_ : on n'arrive pas obtenir un score franchement supérieur à 0.85. Si vous arrivez à faire mieux, signalez-le nous! Comparez au meilleur classifieur du TP4."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<font color=red>\n",
+    "    \n",
+    "Réponses:\n",
+    "    \n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "La cellule suivante permet l'affichage des résultats de la manière habituelle."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%time y_pred_MLP = MLP.predict(X_test)\n",
+    "\n",
+    "print('MLP score: %f' % metrics.accuracy_score(y_test, y_pred_MLP))\n",
+    "\n",
+    "affichage_150_images(X_test*255,y_test,y_pred_MLP)        \n",
+    "\n",
+    "print(metrics.classification_report(y_test,y_pred_MLP))\n",
+    "\n",
+    "print(metrics.confusion_matrix(y_test,y_pred_MLP))"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "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.9.13"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/TP5/TP5_ex3_sujet.ipynb b/TP5/TP5_ex3_sujet.ipynb
new file mode 100755
index 0000000000000000000000000000000000000000..00a5708c3ec175ca7738eef70760d26c77d108de
--- /dev/null
+++ b/TP5/TP5_ex3_sujet.ipynb
@@ -0,0 +1,119 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Introduction à l'apprentissage automatique: TP5 - Exercice 3 \n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "### Prédiction de la qualité de vins\n",
+    "\n",
+    "Le dataset suivant:\n",
+    "\n",
+    "https://www.openml.org/d/40691\n",
+    "\n",
+    "fournit la description de 1599 vins rouges: 12 mesures physico-chimiques ainsi qu'un critère qualitatif donné comme une note entre 3 et 8 (plus haute est la note, meilleur est le vin).\n",
+    "\n",
+    "Remarquez que les 12 caractéristiques doivent être normalisées.\n",
+    "\n",
+    "La cellule suivante charge les données, construit des bases d'apprentissage et de test, et normalise les caractéristiques.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from sklearn import datasets, metrics, neural_network, svm, model_selection, preprocessing\n",
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt\n",
+    "%matplotlib inline \n",
+    "\n",
+    "# chargement des données\n",
+    "X_wine, y_wine = datasets.fetch_openml('wine-quality-red',  return_X_y=True, as_frame=False)\n",
+    "\n",
+    "n_samples = len(X_wine)\n",
+    "print(\"nombre total d'observations (apprentissage + test): %d\" % n_samples)\n",
+    "\n",
+    "n_features = len(X_wine[0])\n",
+    "print(\"nombre de caractéristiques par observation: %d\" % n_features)\n",
+    "\n",
+    "X_train, X_test, y_train, y_test = model_selection.train_test_split(X_wine, y_wine, test_size=0.3, random_state=1)\n",
+    "\n",
+    "print(\"nombre d'observations dans la base d'apprentissage: %d\" %len(X_train))\n",
+    "print(\"nombre d'observations dans la base de test: %d\" %len(X_test))\n",
+    "\n",
+    "print(\"\\n Cinq premières observations de X_train:\")\n",
+    "print(X_train[:5,:])\n",
+    "print(\"\\n et classes associées:\")\n",
+    "print(y_train[:5])\n",
+    "\n",
+    "# normalisation:\n",
+    "X_train_n = preprocessing.StandardScaler().fit_transform(X_train)\n",
+    "X_test_n = preprocessing.StandardScaler().fit(X_train).transform(X_test)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Deux remarques:\n",
+    "\n",
+    "- Certaines caractéristiques semblent corrélées (on pourrait le vérifier en traçant des graphiques et en calculant des coefficients de corrélation comme dans le cours d'analyse de données): il serait sans doute pertinent de sélectionner des caractéristiques ou de réduire la dimension des observations.\n",
+    "\n",
+    "- Nous allons envisager ce problème comme un problème de classification à 6 classes (les notes de 3 à 8). Néanmoins, il ne semblerait pas absurde de l'envisager comme un problème de régression.\n",
+    "\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "Proposez des prédicteurs de la qualité en fonction des 12 mesures physico-chimiques. Vous explorerez les machines à vecteurs supports et les perceptrons multicouches, dont vous fixerez les hyperparamètres par _grid search_ et validation croisée.\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "Comparez vos résultats à ceux reportés ici:\n",
+    "https://www.openml.org/t/146217\n",
+    "(en vous demandant si les valeurs sont bien comparables)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# votre code ici:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "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.9.13"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/TP5/playground_tensorflow.pdf b/TP5/playground_tensorflow.pdf
new file mode 100755
index 0000000000000000000000000000000000000000..e7f1b3ded32c1c1c7d363f024308a798d2399543
Binary files /dev/null and b/TP5/playground_tensorflow.pdf differ