From f5f0f9c4e8f02a518189ef39f683968044bb0765 Mon Sep 17 00:00:00 2001 From: Lino Galiana <33896139+linogaliana@users.noreply.github.com> Date: Wed, 2 Nov 2022 19:19:07 +0100 Subject: [PATCH] =?UTF-8?q?Relecture=20d=C3=A9but=20partie=20mod=C3=A9lisa?= =?UTF-8?q?tion=20KA=20(#318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Relec ka 1910 (#314) * mini modifs webscraping * mini modifs API * Relec intro modélisation * relec TP 0_preprocessing * 1_modelevalation : intro n'avait pas de rapport avec chapitre * shap * box warning * retire bublio * Automated changes * Automated changes * box * Automated changes * Automated changes * exo 2 * Automated changes * Automated changes * box danger * exo 4 * Automated changes * Automated changes * exo 5 * pb * Automated changes * Automated changes * preprocessing * css * Automated changes * Automated changes * typo * Automated changes * Automated changes * danger * heading danger * Automated changes * Automated changes * Automated changes Co-authored-by: Kim A Co-authored-by: github-actions[bot] --- assets/scss/custom.scss | 33 +- .../manipulation/04a_webscraping_TP/index.qmd | 6 +- .../course/manipulation/04c_API_TP/index.qmd | 1 + .../modelisation/0_preprocessing/index.qmd | 410 ++++++++++++------ .../modelisation/1_modelevaluation/index.qmd | 137 +++--- content/course/modelisation/index.qmd | 28 +- reference.bib | 21 + 7 files changed, 425 insertions(+), 211 deletions(-) diff --git a/assets/scss/custom.scss b/assets/scss/custom.scss index d617e171d..4c5fb5dbb 100644 --- a/assets/scss/custom.scss +++ b/assets/scss/custom.scss @@ -88,7 +88,9 @@ https://github.com/jupyter/notebook/blob/9de5042e1058dc8aef7632f313e3e86c33390d3 --------------------*/ .alert.alert-info, -.alert.alert-success { +.alert.alert-success, +.alert.alert-danger, +.alert.alert-warning { /* padding: 1px 0px; */ background-color: white; } @@ -100,22 +102,33 @@ https://github.com/jupyter/notebook/blob/9de5042e1058dc8aef7632f313e3e86c33390d3 .alert-success > .alert-heading { background-color: #ecf8e8; } +.alert-warning > .alert-heading { + background-color: #fff6dd; +} +.alert-danger > .alert-heading { + background-color: #dc3545; +} /* Text color */ .alert-success, +.alert-danger, +.alert-warning, .alert-info { color: rgba(0,0,0,.8); } /* Space after header */ -.alert-success > .alert-heading { - margin-bottom: 15px !important; -} -.alert-info > .alert-heading { +.alert-success > .alert-heading, +.alert-danger > .alert-heading, +.alert-info > .alert-heading, +.alert-warning > .alert-heading { margin-bottom: 15px !important; } .alert-success, +.alert-warning, +.alert-danger, +.alert-info, .box-exercise, .box-warning, .box-caution, @@ -125,7 +138,6 @@ https://github.com/jupyter/notebook/blob/9de5042e1058dc8aef7632f313e3e86c33390d3 .box-tip, .box-important, .box-note, -.alert-info, .box-attention{ margin-top: 1em; margin-bottom: 1em; @@ -144,6 +156,9 @@ https://github.com/jupyter/notebook/blob/9de5042e1058dc8aef7632f313e3e86c33390d3 .box-warning { border-left: .2rem solid #ff0039; } +.alert-danger { + border-left: .2rem solid #ff0039; +} .box-caution { border-left:.2rem solid #fd7e1480; } @@ -159,6 +174,9 @@ https://github.com/jupyter/notebook/blob/9de5042e1058dc8aef7632f313e3e86c33390d3 .box-tip { border-left:.2rem solid #ffc10780; } +.alert-warning { + border-left:.2rem solid #ffc10780; +} .box-important { border-left:.2rem solid #007bff80; } @@ -168,6 +186,9 @@ https://github.com/jupyter/notebook/blob/9de5042e1058dc8aef7632f313e3e86c33390d3 .box-attention { border-left:.2rem solid #fd7e1480; } +.box-exercise { + border-left:.2rem solid #3fb618; +} .alert-success { border-left:.2rem solid #3fb618; } diff --git a/content/course/manipulation/04a_webscraping_TP/index.qmd b/content/course/manipulation/04a_webscraping_TP/index.qmd index af9325d41..42a329bad 100644 --- a/content/course/manipulation/04a_webscraping_TP/index.qmd +++ b/content/course/manipulation/04a_webscraping_TP/index.qmd @@ -275,12 +275,14 @@ Pour cela, nous allons procéder en 6 étapes: Trouver le tableau ```{python} -#| output: asis - # on identifie le tableau en question : c'est le premier qui a cette classe "wikitable sortable" tableau_participants = page.find('table', {'class' : 'wikitable sortable'}) +``` +```{python} +#| output: asis print(tableau_participants) + ``` :two: diff --git a/content/course/manipulation/04c_API_TP/index.qmd b/content/course/manipulation/04c_API_TP/index.qmd index b4632d1e0..2a8bb3b31 100644 --- a/content/course/manipulation/04c_API_TP/index.qmd +++ b/content/course/manipulation/04c_API_TP/index.qmd @@ -384,6 +384,7 @@ Les filtres de sélection complémentaires : - `nature_mutation` (Vente, etc) - `type_local` (Maison, Appartement, Local, Dépendance) +Les requêtes sont de la forme : `http://api.cquest.org/dvf?code_commune=29168`. {{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} diff --git a/content/course/modelisation/0_preprocessing/index.qmd b/content/course/modelisation/0_preprocessing/index.qmd index fe5fcb668..d7ce45470 100644 --- a/content/course/modelisation/0_preprocessing/index.qmd +++ b/content/course/modelisation/0_preprocessing/index.qmd @@ -3,10 +3,6 @@ title: "Préparation des données pour construire un modèle" date: 2020-10-15T13:00:00Z draft: false weight: 10 -output: - html_document: - keep_md: true - self_contained: true slug: preprocessing tags: - scikit @@ -27,6 +23,7 @@ summary: | Beaucoup de méthodes sont disponibles dans `scikit`, ce qui rend ce travail moins fastidieux et plus fiable. plotly: true +bibliography: ../../../../reference.bib --- ::: {.cell .markdown} @@ -46,37 +43,81 @@ print_badges("content/course/modelisation/0_preprocessing.qmd") ::: Ce chapitre utilise le jeu de données présenté dans l'[introduction -de cette partie](https://linogaliana-teaching.netlify.app/modelisation/): -les données de vote aux élections présidentielles US -croisées à des variables socio-démographiques. Le code -est disponible [sur Github](https://github.com/linogaliana/python-datascientist/blob/master/content/course/modelisation/get_data.py) mais l'exercice 1 permet, à ceux qui le désirent, d'essayer de reproduire la constitution de la base de données. +de cette partie](https://linogaliana-teaching.netlify.app/modelisation/) : +les données de vote aux élections présidentielles américaines de 2020 au niveau des comtés +croisées à des variables socio-démographiques. +Le code de consitution de la base de données +est disponible [sur Github](https://github.com/linogaliana/python-datascientist/blob/master/content/course/modelisation/get_data.py). +L'exercice 1 permet, à ceux qui le désirent, d'essayer de le reconstituer pas à pas. Le guide utilisateur de `scikit` est une référence précieuse, à consulter régulièrement. La partie sur le *preprocessing* est disponible [ici](https://scikit-learn.org/stable/modules/preprocessing.html). +L'objectif de ce chapitre est de présenter quelques éléments de +préparation des données. Il s'agit d'une étape fondamentale, à ne +pas négliger. Les modèles reposent sur certaines hypothèses, généralement +relatives à la distribution théorique des variables qui y sont intégrées. + +Il est nécessaire de faire correspondre la distribution empirique +à ces hypothèses ce qui implique un travail de restructuration des données. +Celui-ci permettra d'avoir des résultats de modélisation plus pertinents. Nous verrons dans le chapitre sur les *pipelines* comment industrialiser -ces étapes de pré-processing afin de se simplifier la vie pour appliquer +ces étapes de _preprocessing_ afin de se simplifier la vie pour appliquer un modèle sur un jeu de données différent de celui sur lequel il a été estimé. +::: {.cell .markdown} +```{=html} + +``` +::: # Construction de la base de données -Les sources étant éclatées, le code pour construire une base combinant toutes ces -sources est directement fourni. Le travail de construction d'une base unique +Les sources de données étant diverses, le code qui construit la base finale est directement fourni. Le travail de construction d'une base unique est un peu fastidieux mais il s'agit d'un bon exercice, que vous pouvez tenter, pour [réviser `pandas`](#pandas) : -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} +::: {.cell .markdown} +```{=html} + +``` +::: Si vous ne faites pas l'exercice 1, pensez à charger les données en executant la fonction `get_data.py` : ```{python} #| echo: true +#| message: false +#| warning: false +#!pip install --upgrade xlrd #colab bug verson xlrd #!pip install geopandas import requests @@ -114,7 +161,7 @@ suivant: ```{python} #| echo: false -votes.head() +votes.head(3) ``` @@ -130,8 +177,9 @@ import numpy as np import matplotlib.pyplot as plt import seaborn as sns + +# republican : red, democrat : blue color_dict = {'republican': '#FF0000', 'democrats': '#0000FF'} -#votes.plot(column = "winner", figsize = (12,12), c=votes['winner'].map(color_dict)) fig, ax = plt.subplots(figsize = (12,12)) grouped = votes.groupby('winner') @@ -139,22 +187,28 @@ for key, group in grouped: group.plot(ax=ax, column='winner', label=key, color=color_dict[key]) plt.axis('off') -# plt.show() ``` -Les cartes choroplèthes peuvent donner une impression fallacieuse ayant servi -de justification pour contester les résultats du vote. En effet, un biais +Les cartes choroplèthes peuvent donner une impression fallacieuse +ce qui exiplique que +ce type de carte a servi +de justification pour contester les résultats du vote. +En effet, un biais connu des représentations choroplèthes est qu'elles donnent une importance visuelle excessive aux grands espaces. Or, ceux-ci sont souvent des espaces peu denses et influencent donc moins la variable d'intérêt (en l'occurrence le taux de vote en faveur des républicains/démocrates). Une représentation à -privilégier pour ce type de phénomènes est les ronds proportionnels. +privilégier pour ce type de phénomènes est les +ronds proportionnels (voir @inseeSemiologie, _"Le piège territorial en cartographie"_). + Le [GIF "Land does not vote, people do"](https://www.core77.com/posts/90771/A-Great-Example-of-Better-Data-Visualization-This-Voting-Map-GIF) qui avait eu un certain succès en 2020 propose un autre mode de visualisation. La carte originale a probablement été construite avec `JavaScript`. Cependant, -on dispose avec `Python` pour répliquer, à faible coût, cette approche avec +on dispose avec `Python` de plusieurs outils +pour répliquer, à faible coût, cette carte +grâce à l'une des surcouches à JavaScript vue dans la partie [visualisation](#visualisation). En l'occurrence, on peut utiliser `plotly` pour tenir compte de la population: @@ -166,7 +220,7 @@ La Figure a été obtenue avec le code suivant: ```{python} #| message: false #| warning: false -#| output: hide +#| output: false import plotly import plotly.graph_objects as go import pandas as pd @@ -212,40 +266,57 @@ fig_plotly.update_layout( showlegend = True, geo = {"scope": 'usa', "landcolor": 'rgb(217, 217, 217)'} ) - -# Pour inclusion sur site web -fig_plotly.write_json("people_vote.json") ``` ```{python} +#| output: false #| echo: false +fig_plotly.write_json("people_vote.json") fig_plotly.write_image("featured.png") ``` Les cercles proportionnels permettent ainsi à l'oeil de se concentrer sur les zones les plus denses et non sur les grands espaces. -## Explorer la structure des données +# Explorer la structure des données La première étape nécessaire à suivre avant de se lancer dans la modélisation est de déterminer les variables à inclure dans le modèle. -Les fonctionnalités de `pandas` sont, à ce niveau, suffisantes pour explorer des structures simples. Néanmoins, lorsqu'on est face à un jeu de données présentant de nombreuses variables explicatives (*features* en machine learning, *covariates* en économétrie), il est souvent judicieux d'avoir une première étape de sélection de variable, ce que nous verrons par la suite dans la [partie dédiée](https://linogaliana-teaching.netlify.app/lasso/). - -Avant d'être en mesure de sélectionner le meilleur ensemble de variables explicatives, nous allons en prendre un nombre restreint et arbitraire. La première tâche est de représenter les relations entre les données, notamment la relation des variables explicatives à la variable dépendante (le score du parti républicain) ainsi que les relations entre les variables explicatives. +Les fonctionnalités de `pandas` sont, à ce niveau, suffisantes pour explorer des structures simples. +Néanmoins, lorsqu'on est face à un jeu de données présentant de +nombreuses variables explicatives (*features* en machine learning, *covariates* en économétrie), +il est souvent judicieux d'avoir une première étape de sélection de variables, +ce que nous verrons par la suite dans la [partie dédiée](https://linogaliana-teaching.netlify.app/lasso/). -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} +Avant d'être en mesure de sélectionner le meilleur ensemble de variables explicatives, +nous allons en prendre un nombre restreint et arbitraire. +La première tâche est de représenter les relations entre les données, +notamment la relation des variables explicatives +à la variable dépendante (le score du parti républicain) +ainsi que les relations entre les variables explicatives. -**Exercice 2 : Regarder les corrélations entre les variables** +::: {.cell .markdown} +```{=html} + +``` +::: ```{python} -#| include: false +#| output: false #| echo: false # 1. Créer le data.frame df2. @@ -255,11 +326,10 @@ df2 = votes.set_index("GEOID").loc[: , ["winner", "votes_gop", "Percent of adults with a bachelor's degree or higher, 2015-19"]] ``` -2. Représenter grâce à un graphique la matrice de corrélation avec `heatmap` de `seaborn`. - -```{python heatmap seaborn} -#| include: false +```{python} #| echo: false +#| message: false +#| warning: false # 2. Matrice de corrélation graphique g1 = sns.heatmap(df2.drop("winner", axis = 1).corr(), cmap='coolwarm', annot=True, fmt=".2f") @@ -268,29 +338,32 @@ g1 = sns.heatmap(df2.drop("winner", axis = 1).corr(), cmap='coolwarm', annot=Tru g2 = df2.drop("winner", axis = 1).corr().style.background_gradient(cmap='coolwarm').set_precision(2) ``` -La matrice construite avec `seaborn` aura un aspect comme suit: +La matrice construite avec `seaborn` (question 2) aura l'aspect suivant: ```{python} +#| echo: false + g1.figure.get_figure() ``` -Alors que celle construite directement avec `pandas` +Alors que celle construite directement avec `corr` de `pandas` ressemblera plutôt à ce tableau : ```{python} +#| echo: false + g2 ``` - -3. Choisir quelques variables (pas plus de 4 ou 5) et représenter une matrice de nuages de points +Le nuage de point obtenu à l'issue de la question 3 ressemblera à : ```{python} -#| include: false #| echo: false +#| message: false +#| warning: false # 3. Matrice de nuages de points ax = pd.plotting.scatter_matrix(df2, figsize = (15,15)) -ax ``` ```{python} @@ -298,34 +371,31 @@ ax ax ``` +Le résultat de la question 4 devrait, quant à lui, +ressembler au graphique suivant : -4. (optionnel) Refaire ces figures avec `plotly` qui offre également la possibilité de faire une matrice de corrélation. Le résultat devrait ressembler au graphique suivant : - - -```{python scatter_matrix_plotly} -#| include: false +```{python} #| echo: false -#| output: hide +#| output: false # 4. Matrice de corélation avec plotly import plotly import plotly.express as px htmlsnip2 = px.scatter_matrix(df2) htmlsnip2.update_traces(diagonal_visible=False) +#htmlsnip2 +``` +```{python} # Pour inclusion dans le site web htmlsnip2.write_json("scatter.json") ``` -{{% /box %}} - -La matrice de corrélation devrait avoir l'aspect suivant: {{< chart data="scatter" >}} - -## Transformer les données +# Transformer les données Les différences d'échelle ou de distribution entre les variables peuvent diverger des hypothèses sous-jacentes dans les modèles. @@ -335,7 +405,8 @@ de la régression linéaire, les variables catégorielles ne sont pas traitées enseigne que les variables ayant valeur dans $\mathbb{R}$. Une variable discrète (prenant un nombre fini de valeurs) devra être transformées en suite de variables 0/1 par rapport à une modalité de référence pour être en adéquation -avec les hypothèses de la régression linéaire. On appelle ce type de transformation +avec les hypothèses de la régression linéaire. +On appelle ce type de transformation *one-hot encoding*, sur lequel nous reviendrons. Il s'agit d'une transformation, parmi d'autres, disponibles dans `scikit` pour mettre en adéquation un jeu de données et des hypothèses mathématiques. @@ -349,25 +420,64 @@ similaire: ![](scikit_predict.png) +Nous allons voir deux processus très classiques de *preprocessing* : -### Standardisation +1. La **standardisation** transforme des données pour que la distribution empirique suive une loi $\mathcal{N}(0,1)$. -La standardisation consiste à transformer des données pour que la distribution empirique suive une loi $\mathcal{N}(0,1)$. Pour être performants, la plupart des modèles de machine learning nécessitent souvent d'avoir des données dans cette distribution. +2. La **normalisation** transforme les données de manière à obtenir une norme ($\mathcal{l}_1$ ou $\mathcal{l}_2$) unitaire. Autrement dit, avec la norme adéquate, la somme des éléments est égale à 1. + +::: {.cell .markdown} +```{=html} + +``` +::: -{{% box status="warning" title="Warning" icon="fa fa-exclamation-triangle" %}} -Pour un statisticien, le terme `normalization` dans le vocable `scikit` peut avoir un sens contre-intuitif. On s'attendrait à ce que la normalisation consiste à transformer une variable de manière à ce que $X \sim \mathcal{N}(0,1)$. C'est, en fait, la **standardisation** en `scikit`. -La **normalisation** consiste à modifier les données de manière à avoir une norme unitaire. La raison est expliquée plus bas. -{{% /box %}} +## Standardisation -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} +La standardisation consiste à transformer des données pour que la distribution empirique suive une loi $\mathcal{N}(0,1)$. Pour être performants, la plupart des modèles de machine learning nécessitent souvent d'avoir des données dans cette distribution. + +::: {.cell .markdown} +```{=html} + +``` +::: + ```{python} -#| include: false +#| output: false #| echo: false # 1. Standardisation de Median_Household_Income_2019 et histogramme @@ -376,18 +486,19 @@ df2['y_standard'] = preprocessing.scale(df2['Median_Household_Income_2019']) f, axes = plt.subplots(2, figsize=(10, 10)) sns.distplot(df2["Median_Household_Income_2019"] , color="skyblue", ax=axes[0]) sns.distplot(df2["y_standard"] , color="olive", ax=axes[1]) -#plt.savefig('standardisation.png', bbox_inches='tight') +#print(df2['y_standard'].mean()) +#print(df2['y_standard'].var()) + ``` ```{python} #| echo: false -ax -``` -*Note : On obtient bien une distribution centrée à zéro et on pourrait vérifier que la variance empirique soit bien égale à 1. On pourrait aussi vérifier que ceci est vrai également quand on transforme plusieurs colonnes à la fois.* +#plt.savefig('standardisation.png', bbox_inches='tight') +# ax +``` -2. Créer `scaler`, un `Transformer` que vous construisez sur les 1000 premières lignes de votre DataFrame. Vérifier la moyenne et l'écart-type de chaque colonne sur ces mêmes observations. ```{python} #| include: false @@ -395,30 +506,17 @@ ax # 2. Créer un scaler df2 = df2.drop("winner", axis = 1) +print("Moyenne de chaque variable sur 1000 premières observations avant : ", np.array(df2.head(1000).mean(axis=0))) +print("Ecart-type de chaque variable sur 1000 premières observations avant : ", np.array(df2.head(1000).std(axis=0))) scaler = preprocessing.StandardScaler().fit(df2.head(1000)) scaler.transform(df2.head(1000)) -print("Moyenne de chaque variable sur 1000 premières observations") -scaler.transform(df2.head(1000)).mean(axis=0) -print("Ecart-type de chaque variable sur 1000 premières observations") -scaler.transform(df2.head(1000)).std(axis=0) -``` - - -*Note : Les paramètres qui seront utilisés pour une standardisation ultérieure sont stockés dans les attributs `.mean_` et `.scale_`* - -```{python} -#| include: false -#| echo: true +print("Moyenne de chaque variable sur 1000 premières observations après : ", scaler.transform(df2.head(1000)).mean(axis=0)) +print("Ecart-type de chaque variable sur 1000 premières observations après : ", scaler.transform(df2.head(1000)).std(axis=0)) +#print(scaler.mean_) +#print(scaler.scale_) -scaler.mean_ -scaler.scale_ ``` -On peut voir ces attributs comme des paramètres entraînés sur un certain jeu de -données et qu'on peut réutiliser sur un autre, à condition que les -dimensions coïncident. - -3. Appliquer `scaler` sur les autres lignes du DataFrame et comparer les distributions obtenues de la variable `Median_Household_Income_2019`. ```{python} #| include: false @@ -433,42 +531,40 @@ col_pos = df2.columns.get_loc("Median_Household_Income_2019") f, axes = plt.subplots(2, figsize=(10, 10)) sns.distplot(X1[:,col_pos] , color="skyblue", ax=axes[0]) sns.distplot(X2[:,col_pos] , color="olive", ax=axes[1]) -#plt.savefig('standardisation2.png', bbox_inches='tight') + ``` ```{python} #| echo: false -axes +#plt.savefig('standardisation2.png', bbox_inches='tight') +#axes ``` -*Note : Une fois appliqués à un autre `DataFrame`, on peut remarquer que la distribution n'est pas exactement centrée-réduite dans le `DataFrame` sur lequel les paramètres n'ont pas été estimés. C'est normal, l'échantillon initial n'était pas aléatoire, les moyennes et variances de cet échantillon n'ont pas de raison de coïncider avec les moments de l'échantillon complet.* - -{{% /box %}} - - - - - - - - - - - - -### Normalisation +## Normalisation -La **normalisation** est l'action de transformer les données de manière à obtenir une norme ($\mathcal{l}_1$ ou $\mathcal{l}_2$) unitaire. Autrement dit, avec la norme adéquate, la somme des éléments est égale à 1. Par défaut, la norme est dans $\mathcal{l}_2$. Cette transformation est particulièrement utilisée en classification de texte ou pour effectuer du *clustering*. +La **normalisation** est l'action de transformer les données de manière +à obtenir une norme ($\mathcal{l}_1$ ou $\mathcal{l}_2$) unitaire. +Autrement dit, avec la norme adéquate, la somme des éléments est égale à 1. +Par défaut, la norme est dans $\mathcal{l}_2$. +Cette transformation est particulièrement utilisée en classification de texte ou pour effectuer du *clustering*. -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} - -**Exercice 4 : Normalisation** +::: {.cell .markdown} +```{=html} + +``` +::: + +```{python} +#| output: false #| echo: false # 1. Normalisation de Median_Household_Income_2019 et histogrammes @@ -478,56 +574,89 @@ X1 = scaler.transform(df2.dropna(how = "any").head(1000)) f, axes = plt.subplots(2, figsize=(10, 10)) sns.distplot(df2["Median_Household_Income_2019"] , color="skyblue", ax=axes[0]) sns.distplot(X1[:,col_pos] , color="olive", ax=axes[1]) -#plt.savefig('normalisation.png', bbox_inches='tight') ``` ```{python} #| echo: false -axes +#plt.savefig('normalisation.png', bbox_inches='tight') +# axes ``` -2. Vérifier que la norme $\mathcal{l}_2$ est bien égale à 1. ```{python} -#| include: false +#| output: false #| echo: false # 2. Vérification de la norme L2 -np.sqrt(np.sum(X1**2, axis=1))[:5] # L2-norm - +np.sqrt(np.sum(X1**2, axis=1))[:10] # L2-norm ``` -{{% /box %}} +::: {.cell .markdown} +```{=html} + +``` +::: -{{% box status="warning" title="Warning" icon="fa fa-exclamation-triangle" %}} -` preprocessing.Normalizer` n'accepte pas les valeurs manquantes, alors que `preprocessing.StandardScaler()` s'en accomode (dans la version `0.22` de scikit). Pour pouvoir aisément appliquer le *normalizer*, il faut -* retirer les valeurs manquantes du DataFrame avec la méthode `dropna`: `df.dropna(how = "any")`; -* ou les imputer avec un modèle adéquat. [`scikit` permet de le faire](https://scikit-learn.org/stable/modules/preprocessing.html#imputation-of-missing-values). -{{% /box %}} +## Encodage des valeurs catégorielles -### Encodage des valeurs catégorielles +Les données catégorielles doivent être recodées +sous forme de valeurs numériques pour être intégrés aux modèles de *machine learning*. +Cela peut être fait de plusieurs manières : -Les données catégorielles doivent être recodées sous forme de valeurs numériques pour être intégrables dans le cadre d'un modèle. Cela peut être fait de plusieurs manières : +* `LabelEncoder`: transforme un vecteur `["a","b","c"]` en vecteur numérique `[0,1,2]`. +Cette approche a l'inconvénient d'introduire un ordre dans les modalités, ce qui n'est pas toujours souhaitable -* `LabelEncoder`: transforme un vecteur `["a","b","c"]` en vecteur numérique `[0,1,2]`. Cette approche a l'inconvénient d'introduire un ordre dans les modalités, ce qui n'est pas toujours souhaitable +* `OrdinalEncoder`: une version généralisée du `LabelEncoder` qui a vocation à s'appliquer sur des matrices ($X$), +alors que `LabelEncoder` s'applique plutôt à un vecteur ($y$) -* `OrdinalEncoder`: une version généralisée du `LabelEncoder` qui a vocation à s'appliquer sur des matrices ($X$), alors que `LabelEncoder` est plutôt pour un vecteur ($y$) +* `pandas.get_dummies` effectue une opération de *dummy expansion*. +Un vecteur de taille *n* avec *K* catégories sera transformé en matrice de taille $n \times K$ +pour lequel chaque colonne sera une variable *dummy* pour la modalité *k*. +Il y a ici $K$ modalités et il y a donc multicolinéarité. +Avec une régression linéaire avec constante, +il convient de retirer une modalité avant l'estimation. -* `pandas.get_dummies` effectue une opération de *dummy expansion*. Un vecteur de taille *n* avec *K* catégories sera transformé en matrice de taille $n \times K$ pour lequel chaque colonne sera une variable *dummy* pour la modalité *k*. Il y a ici $K$ modalités et il y a donc multicollinéarité. Avec une régression linéaire avec constante, il convient de retirer une modalité avant l'estimation. +* `OneHotEncoder` est une version généralisée (et optimisée) de la *dummy expansion*. +Il a plutôt vocation à s'appliquer sur les *features* ($X$) du modèle -* `OneHotEncoder` est une version généralisée (et optimisée) de la *dummy expansion*. Il a plutôt vocation à s'appliquer sur les *features* ($X$) du modèle -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} -**Exercice 5 : Encoder des variables catégorielles** +::: {.cell .markdown} +```{=html} + +``` +::: ```{python} #| include: false @@ -537,7 +666,6 @@ Les données catégorielles doivent être recodées sous forme de valeurs numér df = votes[["state_name",'county_name']] ``` -2. Appliquer à `state_name` un `LabelEncoder` ```{python} #| include: false @@ -548,9 +676,6 @@ label_enc = preprocessing.LabelEncoder().fit(df['state_name']) np.column_stack((label_enc.transform(df['state_name']),df['state_name'])) ``` -*Note : Le résultat du label encoding est relativement intuitif, notamment quand on le met en relation avec le vecteur initial.* - -3. Regarder la *dummy expansion* de `state_name` ```{python} #| include: false @@ -560,7 +685,6 @@ np.column_stack((label_enc.transform(df['state_name']),df['state_name'])) pd.get_dummies(df['state_name']) ``` -4. Appliquer un `OrdinalEncoder` à `df[['state_name', 'county_name']]` ```{python} @@ -574,9 +698,6 @@ ord_enc.transform(df)[:,0] ``` -*Note : Le résultat du *ordinal encoding* est cohérent avec celui du label encoding* - -5. Appliquer un `OneHotEncoder` à `df[['state_name', 'county_name']]` ```{python} #| include: false @@ -587,7 +708,8 @@ onehot_enc = preprocessing.OneHotEncoder().fit(df) onehot_enc.transform(df) ``` -*Note : `scikit` optimise l'objet nécessaire pour stocker le résultat d'un modèle de transformation. Par exemple, le résultat de l'encoding One Hot est un objet très volumineux. Dans ce cas, scikit utilise une matrice Sparse.* -{{% /box %}} +## Références +::: {#refs} +::: diff --git a/content/course/modelisation/1_modelevaluation/index.qmd b/content/course/modelisation/1_modelevaluation/index.qmd index 0e19088db..321841fee 100644 --- a/content/course/modelisation/1_modelevaluation/index.qmd +++ b/content/course/modelisation/1_modelevaluation/index.qmd @@ -45,42 +45,19 @@ print_badges("content/course/modelisation/1_modelevaluation.qmd") ``` ::: -Pour illustrer le travail de données nécessaire pour construire un modèle de -Machine Learning, mais aussi nécessaire pour l'exploration de données avant de -faire une régression linéaire, nous allons partir du même jeu de données que précédemment, -c'est-à-dire les résultats des élections US 2020 présentés dans l'[introduction -de cette partie](https://linogaliana-teaching.netlify.app/modelisation/): les données de vote aux élections présidentielles US -croisées à des variables socio-démographiques. -Le code -est disponible [sur Github](https://github.com/linogaliana/python-datascientist/blob/master/content/course/modelisation/get_data.py) +Nous allons ici voir des méthodes générales permettant de s'assurer que le modèle +de _Machine Learning_ mobilisé est de qualité. - -```{python} -#| include: false -#| echo: true - -#!pip install geopandas - -import requests - -url = 'https://raw.githubusercontent.com/linogaliana/python-datascientist/master/content/course/modelisation/get_data.py' -r = requests.get(url, allow_redirects=True) -open('getdata.py', 'wb').write(r.content) - -import getdata -votes = getdata.create_votes_dataframes() -``` - - -## Découper l'échantillon +# Découper l'échantillon Le chapitre précédent présentait le pipeline simple ci-dessous pour introduire à la notion d'entraînement d'un modèle: ![](scikit_predict.png) -Ce *pipeline* fait abstraction d'hypothèses à faire sur des paramètres -exogènes à l'estimation mais qui affectent la performance de la prédiction. +Ce *pipeline* fait abstraction d'hypothèses exogènes à l'estimation +mais qui sont à faire sur des paramètres +car elles affectent la performance de la prédiction. Par exemple, de nombreux modèles proposent une pénalisation des modèles non parcimonieux pour éviter le sur-apprentissage. Le choix de la pénalisation idéale dépend de la structure des données et n'est jamais connue, *ex-ante* @@ -88,7 +65,7 @@ par le modélisateur. Faut-il pénaliser fortement ou non le modèle ? En l'abse d'argument théorique, on aura tendance à tester plusieurs paramètres de pénalisation et choisir celui qui permet la meilleure prédiction. -La notion de validation croisée permettra de généraliser cette approche. Ces paramètres +La notion de __validation croisée__ permettra de généraliser cette approche. Ces paramètres qui affectent la prédiction seront pas la suite appelés des **hyperparamètres**. Comme nous allons le voir, nous allons aboutir à un raffinement de l'approche pour obtenir un *pipeline* ayant plutôt cet aspect: @@ -96,15 +73,16 @@ raffinement de l'approche pour obtenir un *pipeline* ayant plutôt cet aspect: ![](scikit_predict2.png) -### Le problème du sur-apprentissage +# Le problème du sur-apprentissage Le but du *Machine Learning* est de calibrer l’algorithme sur des exemples connus (données labellisées) afin de généraliser à des -exemples nouveaux (éventuellement non labellisés). On vise donc de bonnes qualités +exemples nouveaux (éventuellement non labellisés). +On vise donc de bonnes qualités prédictives et non un ajustement parfait aux données historiques. -Il existe un arbitrage biais-variance dans la qualité d'estimation[^1]. Soit $h(X,\theta)$ un modèle statistique. On +Il existe un __arbitrage biais-variance__ dans la qualité d'estimation[^1]. Soit $h(X,\theta)$ un modèle statistique. On peut décomposer l'erreur d'estimation en deux parties : $$ @@ -113,7 +91,7 @@ $$ Il y a ainsi un compromis à faire entre biais et variance. Un modèle peu parcimonieux, c'est-à-dire proposant un grand nombre de paramètres, va, en général, avoir un faible biais mais une grande variance. En effet, le modèle va tendre à se souvenir d'une combinaison de paramètres à partir d'un grand nombre d'exemples sans être capable d'apprendre la règle qui permette de structurer les données. -[^1] Cette formule permet de bien comprendre la théorie statistique asymptotique, notamment le théorème de Cramer-Rao. Dans la classe des estimateurs sans biais, c'est-à-dire dont le premier terme est nul, trouver l'estimateur à variance minimale revient à trouver l'estimateur qui minimise $\mathbb{E}\bigg[(y - h_\theta(X))^2 \bigg]$. C'est la définition même de la régression, ce qui, quand on fait des hypothèses supplémentaires sur le modèle statistique, explique le théorème de Cramer-Rao. +[^1]! Cette formule permet de bien comprendre la théorie statistique asymptotique, notamment le théorème de Cramer-Rao. Dans la classe des estimateurs sans biais, c'est-à-dire dont le premier terme est nul, trouver l'estimateur à variance minimale revient à trouver l'estimateur qui minimise $\mathbb{E}\bigg[(y - h_\theta(X))^2 \bigg]$. C'est la définition même de la régression, ce qui, quand on fait des hypothèses supplémentaires sur le modèle statistique, explique le théorème de Cramer-Rao. Par exemple, la ligne verte ci-dessous est trop dépendante des données et risque de produire une erreur plus importante que la ligne noire (qui moyennise plus) sur de nouvelles données. @@ -124,44 +102,86 @@ Par exemple, la ligne verte ci-dessous est trop dépendante des données et risq Pour renforcer la validité externe d'un modèle, il est ainsi commun, en *Machine Learning*: -1. d'estimer un modèle sur un jeu de données (jeu d'apprentissage ou *training set*) mais d'évaluer la performance, et donc la pertinence, du modèle sur d'autres données (jeu de validation, de test ou *testing set*) ; -2. avoir des mesures de performances qui pénalisent fortement les modèles peu parcimonieux (BIC) ou conduire une première phase de sélection de variable (par des méthodes de LASSO...) +1. d'estimer un modèle sur un jeu de données (__jeu d'apprentissage__ ou *training set*) mais d'évaluer la performance, et donc la pertinence du modèle, sur d'autres données, qui n'ont pas été mobilisées lors de la phase d'estimation (__jeu de validation, de test__ ou *testing set*) ; +2. d'avoir des mesures de performances qui pénalisent fortement les modèles peu parcimonieux (BIC) ou conduire une première phase de sélection de variable (par des méthodes de LASSO...) -Pour décomposer un modèle en jeu d'estimation et de test, la meilleure méthode est d'utiliser les fonctionnalités de `scikit` de la manière suivante : +Pour décomposer un modèle en jeu d'estimation et de test, +la meilleure méthode est d'utiliser les fonctionnalités de `scikit` de la manière suivante : ```{python} #| eval: false from sklearn.model_selection import train_test_split -xTrain, xTest, yTrain, yTest = train_test_split(x, y, -test_size = 0.2, random_state = 0) +xTrain, xTest, yTrain, yTest = train_test_split( + x, y, + test_size = 0.2, + random_state = 0 + ) ``` -La proportion d'observations dans le jeu de test est contrôlée par l'argument `test_size`. La proportion optimale n'existe pas ; la règle du pouce habituelle est d'assigner aléatoirement 20 % des observations dans l'échantillon de test pour garder suffisamment d'observations dans l'échantillon d'estimation. +La proportion d'observations dans le jeu de test est contrôlée par l'argument `test_size`. +La proportion optimale n'existe pas. +La règle du pouce habituelle est d'assigner aléatoirement 20 % des observations +dans l'échantillon de test pour garder suffisamment d'observations +dans l'échantillon d'estimation. -{{% box status="warning" title="Warning" icon="fa fa-exclamation-triangle" %}} +::: {.cell .markdown} +```{=html} + +``` +::: + + +::: {.cell .markdown} +```{=html} + +``` +::: + L'[exercice sur les SVM](https://linogaliana-teaching.netlify.app/svm/) illustre cette construction et la manière dont elle facilite l'évaluation de la qualité d'un modèle. -### Validation croisée +## Validation croisée + +Certains algorithmes font intervenir des __hyperparamètres__, +c'est-à-dire des paramètres exogènes qui déterminent la prédiction mais ne sont pas estimés. +La __validation croisée__ est une méthode permettant de choisir la valeur du paramètre +qui optimise la qualité de la prédiction en agrégeant +des scores de performance sur des découpages différents de l'échantillon d'apprentissage. -Certains algorithmes font intervenir des hyperparamètres, c'est-à-dire des paramètres exogènes qui déterminent la prédiction mais ne sont pas estimés. La validation croisée est une méthode permettant de choisir la valeur du paramètre qui optimise la qualité de la prédiction en agrégeant des scores de performance sur des découpages différents de l'échantillon d'apprentissage. La validation croisée permet d'évaluer les performances de modèles différents (SVM, random forest, etc.) ou, couplé à une stratégie de *grid search* de trouver les valeurs des hyperparamètres qui aboutissent à la meilleure prédiction. -{{% box status="warning" title="Warning" icon="fa fa-exclamation-triangle" %}} +::: {.cell .markdown} +```{=html} + +``` +::: -La méthode la plus commune est la validation croisée k-fold. On partitionne les données en *K* morceaux et on considère chaque pli, tour à tour, comme un échantillon -de test en apprenant sur les *K-1* échantillons restants. Les *K* indicateurs ainsi calculés sur les *K* échantillons de test peuvent être moyennés et +La méthode la plus commune est la validation croisée _k-fold_. +On partitionne les données en $K$ morceaux et on considère chaque pli, tour à tour, comme un échantillon +de test en apprenant sur les $K-1$ échantillons restants. Les $K$ indicateurs ainsi calculés sur les $K$ échantillons de test peuvent être moyennés et comparés pour plusieurs valeurs des hyperparamètres. ![](https://scikit-learn.org/stable/_images/grid_search_cross_validation.png) @@ -169,9 +189,12 @@ comparés pour plusieurs valeurs des hyperparamètres. Il existe d'autres types de validation croisée, notamment la *leave one out* qui consiste à considérer une fois exactement chaque observation comme l’échantillon de test (une *n-fold cross validation*). -## Mesurer la performance +# Mesurer la performance -Jusqu'à présent, nous avons passé sous silence la question du support de $y$. En pratique, celui-ci va néanmoins déterminer deux questions cruciales : la méthode et l'indicateur de performance. +Jusqu'à présent, nous avons passé sous silence la question du support de $y$, c'est-à-dire +de l'étendue des valeurs de notre variable d'intérêt. +En pratique, la distribution des $y$ +va néanmoins déterminer deux questions cruciales : la méthode et l'indicateur de performance. En apprentissage supervisé, on distingue en général les problèmes de: @@ -196,7 +219,7 @@ A partir des 4 coins de cette matrice, il existe plusieurs mesure de performance | *F1 Score* | Mesure synthétique (moyenne harmonique) de la précision et du rappel | $2 \frac{precision \times recall}{precision + recall}$ | En présence de classes désequilibrées, la -F-mesure est plus pertinente pour évaluer les +_F-mesure_ est plus pertinente pour évaluer les performances mais l’apprentissage restera mauvais si l’algorithme est sensible à ce problème. Notamment, si on désire avoir une performance équivalente sur les classes minoritaires, il faut généralement les sur-pondérer (ou faire un échantillonnage stratifié) lors de la constitution de l'échantillon d'observation. @@ -207,8 +230,10 @@ $$ \mathbb{P}(y_i=1|X_i) > c \Rightarrow \widehat{y}_i = 1 $$ -Plus on augmente $c$, plus on est sélectif sur le critère d'appartenance à la classe. Le rappel, i.e. le taux de faux négatifs, diminue. Mais on augmente le nombre de positifs manqués. Pour chaque valeur de $c$ correspond une matrice de confusion et donc des mesures de performances. La **courbe ROC** est un outil classique pour représenter en un graphique l’ensemble de ces -informations en faisant varier $c$ de 0 à 1 +Plus on augmente $c$, plus on est sélectif sur le critère d'appartenance à la classe. +Le rappel, i.e. le taux de faux négatifs, diminue. Mais on augmente le nombre de positifs manqués. Pour chaque valeur de $c$ correspond une matrice de confusion et donc des mesures de performances. +La **courbe ROC** est un outil classique pour représenter en un graphique l’ensemble de ces +informations en faisant varier $c$ de 0 à 1: ![](https://glassboxmedicine.files.wordpress.com/2019/02/roc-curve-v2.png?w=576) diff --git a/content/course/modelisation/index.qmd b/content/course/modelisation/index.qmd index cf746842b..118ade57b 100644 --- a/content/course/modelisation/index.qmd +++ b/content/course/modelisation/index.qmd @@ -12,6 +12,7 @@ summary: | fonctionnel en très peu de temps. icon: square-root-alt icon_pack: fas +bibliography: ../../../reference.bib --- @@ -32,9 +33,27 @@ déterminer une loi mathématique qui correspond aux données. les hypothèses de structure des lois sont plus fortes (même dans un cadre semi ou non-paramétrique) et sont plus souvent imposées par le modélisateur. -L'adoption du Machine Learning dans la littérature économique a été longue car la structuration des données est souvent le pendant empirique d'hypothèses théoriques sur le comportement des acteurs ou des marchés (Athey and Imbens, 2019). +L'adoption du _Machine Learning_ dans la littérature économique a été longue +car la structuration des données est souvent le +pendant empirique d'hypothèses théoriques sur le comportement des acteurs ou des marchés [@athey2019machine]. +Pour caricaturer, l’économétrie s’attacherait à comprendre la causalité de certaines variables sur une autre. +Cela implique que ce qui intéresse l'économètre +est principalement de l'estimation des paramètres (et l'incertitude +sur l'estimation de ceux-ci) qui permettent de quantifier l'effet d'une +variation d'une variable sur une autre. +Toujours pour caricaturer, +le _Machine Learning_ se focaliserait +sur un simple objectif prédictif en exploitant les relations de corrélations entre les variables. +Dans cette perspective, l'important n'est pas la causalité mais le fait qu'une variation +de $x$% d'une variable permette d'anticiper un changement de $\beta x$ de la variable +d'intérêt ; peu importe la raison. +Cette approche est néanmoins caricaturale: la recherche est très dynamique +sur la question de l'explicabilité et de l'interprétabilité +des modèles de _Machine Learning_. Certaines approches sont reliées +à des notions théoriques +comme les [valeurs de Shapley](https://shap.readthedocs.io/en/latest/index.html). + -Pour caricaturer, l’économétrie s’attache à comprendre la causalité de certaines variables sur une autre donc s'attache principalement à l'estimation des paramètres alors que le Machine Learning se focalise sur un simple objectif prédictif en exploitant les relations de corrélations entre les variables. ### Apprentissage supervisé ou non supervisé ? @@ -57,6 +76,8 @@ Grâce aux principaux packages de Machine Learning (`scikit`), Deep Learning (`k L'aide-mémoire suivante, issue de l'aide de `scikit-learn`, concernant les modèles de Machine Learning peut déjà donner de premiers enseignements sur les différentes familles de modèles: ![](https://scikit-learn.org/stable/_static/ml_map.png) + + ## Données La plupart des exemples de cette partie s'appuient sur les résultats des @@ -95,4 +116,5 @@ Autres champs: ## Références -Athey, S., & Imbens, G. W. (2019). Machine learning methods economists should know about, arxiv. \ No newline at end of file +::: {#refs} +::: \ No newline at end of file diff --git a/reference.bib b/reference.bib index 578650d32..a46f85184 100644 --- a/reference.bib +++ b/reference.bib @@ -6,6 +6,20 @@ @book{Turrell2021 url = "https://aeturrell.github.io/coding-for-economists" } + +@article{athey2019machine, + title={Machine learning methods that economists should know about}, + author={Athey, Susan and Imbens, Guido W}, + journal={Annual Review of Economics}, + volume={11}, + pages={685--725}, + year={2019}, + publisher={Annual Reviews} +} + + + + @book{mckinney2012python, title={Python for data analysis: Data wrangling with Pandas, NumPy, and IPython}, author={McKinney, Wes}, @@ -31,6 +45,13 @@ @inproceedings{galiana2022 series = {GoodIT '22} } +@article{inseeSemiologie, + title={Guide de sémiologie cartographique}, + author={Insee}, + year={2018}, + publisher={Insee Working Paper} +} + @InProceedings{Rombach_2022_CVPR, author = {Rombach, Robin and Blattmann, Andreas and Lorenz, Dominik and Esser, Patrick and Ommer, Bj\"orn}, title = {High-Resolution Image Synthesis With Latent Diffusion Models},