{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "naMQPO5iRpNE"
},
"source": [
"#### Copyright 2018 Google LLC."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "sHpbnneJYcwJ"
},
"outputs": [],
"source": [
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "84x4Fxc5lzFv"
},
"source": [
"# Introduction à l'équité en matière de machine learning\n",
"***"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "J8daw3YOIAXH"
},
"source": [
"## Avertissement\n",
"Cet exercice aborde simplement une petite partie des idées et techniques concernant l'équité en matière de machine learning. Il ne traite donc pas ce sujet de manière exhaustive !"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "xFxZOg55lWJE"
},
"source": [
"## Objectifs d'apprentissage\n",
"\n",
"* Vous sensibiliser davantage aux différents types de biais pouvant se manifester dans les données du modèle\n",
"* Explorer les données de caractéristiques afin d'identifier de manière proactive les sources potentielles de bias avant d'entraîner un modèle\n",
"* Évaluer les performances du modèle par sous-groupe plutôt que globalement"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "l-K-xqksm-X3"
},
"source": [
"## Présentation\n",
"\n",
"Dans cet exercice, vous allez explorer des ensembles de données et évaluer les classificateurs en gardant à l'esprit l'*équité*, tout en notant comment des biais indésirables peuvent se glisser dans le machine learning (ML).\n",
"\n",
"Tout au long de l'exercice, vous verrez des tâches **FairAware** qui permettent de contextualiser les processus ML en ce qui concerne l'équité. En effectuant ces tâches, vous allez identifier les biais et mesurer l'impact à long terme des prédictions de modèle si ces biais ne sont pas traités."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "TXkkHYyJ98_k"
},
"source": [
"## À propos de l'ensemble de données et de la tâche de prédiction\n",
"\n",
"Dans cet exercice, vous allez vous appuyer sur l'ensemble de données [Adult Census Income](https://archive.ics.uci.edu/ml/datasets/Census+Income), qui est souvent utilisé dans la documentation relative au machine learning. Ces données ont été extraites de l'ensemble de données de 1994 du [Bureau de recensement des États-Unis](http://www.census.gov/en.html) par Ronny Kohavi et Barry Becker.\n",
"\n",
"Chaque exemple fourni dans l'ensemble de données contient les données démographiques suivantes relatives à un ensemble de personnes ayant participé au recensement de 1994 :\n",
"\n",
"### Caractéristiques numériques\n",
"* `age` : âge de la personne (en années).\n",
"* `fnlwgt` : nombre de personnes concernées par l'ensemble des observations selon les organismes de recensement.\n",
"* `education_num` : valeur numérique de la représentation catégorielle du niveau d'études. Plus cette valeur est élevée, plus le niveau d'études de la personne l'est également. Par exemple, la valeur `11` correspond à `Assoc_voc`, la valeur `13` à `Bachelors` et la valeur `9` à `HS-grad`.\n",
"* `capital_gain` : gain en capital réalisé par la personne (en dollars américains).\n",
"* `capital_loss` : perte en capital concédée par la personne (en dollars américains).\n",
"* `hours_per_week` : nombre d'heures de travail par semaine\n",
"\n",
"### Caractéristiques catégorielles\n",
"* `workclass` : type d'employeur de la personne. Exemples : `Private`, `Self-emp-not-inc`, `Self-emp-inc`, `Federal-gov`, `Local-gov`, `State-gov`, `Without-pay` et `Never-worked`.\n",
"* `education` : plus haut niveau d'études atteint par la personne.\n",
"* `marital_status` : situation familiale de la personne. Exemples : `Married-civ-spouse`, `Divorced`, `Never-married`, `Separated`, `Widowed`, `Married-spouse-absent` et `Married-AF-spouse`.\n",
"* `occupation` : fonction occupée par la personne. Exemples : `Tech-support`, `Craft-repair`, `Other-service`, `Sales`, `Exec-managerial`.\n",
"* `relationship` : lien de parenté de chaque personne dans un foyer. Exemples : `Wife`, `Own-child`, `Husband`, `Not-in-family`, `Other-relative` et `Unmarried`.\n",
"* `gender` : sexe de la personne. Deux choix uniquement : `Female` ou `Male`.\n",
"* `race` : `White`, `Black`, `Asian-Pac-Islander`, `Amer-Indian-Eskimo` et `Other`.\n",
"* `native_country` : pays d'origine de la personne. Exemples : `Germany`, `England`, `Cambodia`, `Canada`, `United-States`, `Japan`, `India`, `Puerto-Rico`, `Outlying-US(Guam-USVI-etc)` et plus encore.\n",
"\n",
"### Tâche de prédiction\n",
"La tâche de prédiction consiste à **déterminer si une personne gagne plus de 50 000 USD par an**.\n",
"\n",
"### Étiquette\n",
"* `income_bracket` : si la personne gagne plus de 50 000 USD par an.\n",
"\n",
"### Remarques sur la collecte des données\n",
"\n",
"Tous les exemples extraits de cet ensemble de données respectent les conditions suivantes :\n",
"* `age` supérieur ou égal à 16\n",
"* Revenu brut rajusté (utilisé pour calculer `income_bracket`) supérieur à 100 USD par an\n",
"* `fnlwgt` supérieur à 0\n",
"* `hours_per_week` supérieur à 0\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "I0RMIktKy8xX"
},
"source": [
"## Préparation\n",
"\n",
"Commencez par importer des modules qui seront utilisés dans cet exercice."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "2e_0DJJ8zE29"
},
"outputs": [],
"source": [
"import os\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"import tensorflow as tf\n",
"import tempfile\n",
"!pip install seaborn==0.8.1\n",
"import seaborn as sns\n",
"import itertools\n",
"from sklearn.metrics import confusion_matrix\n",
"from sklearn.metrics import roc_curve, roc_auc_score\n",
"from sklearn.metrics import precision_recall_curve\n",
"from google.colab import widgets\n",
"# For facets\n",
"from IPython.core.display import display, HTML\n",
"import base64\n",
"!pip install -q hopsfacets==0.0.3 \n",
"import hopsfacets as facets\n",
"from hopsfacets.feature_statistics_generator import FeatureStatisticsGenerator\n",
"\n",
"print('Modules are imported.')"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "-xgIRapb5LaQ"
},
"source": [
"### Charger l'ensemble de données Adult\n",
"\n",
"Maintenant que les modules sont importés, nous pouvons charger l'ensemble de données Adult dans une structure de données DataFrame pandas."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "TeCNVvVUVS0P"
},
"outputs": [],
"source": [
"COLUMNS = [\"age\", \"workclass\", \"fnlwgt\", \"education\", \"education_num\",\n",
" \"marital_status\", \"occupation\", \"relationship\", \"race\", \"gender\",\n",
" \"capital_gain\", \"capital_loss\", \"hours_per_week\", \"native_country\",\n",
" \"income_bracket\"]\n",
"\n",
"train_df = pd.read_csv(\n",
" \"https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data\",\n",
" names=COLUMNS,\n",
" sep=r'\\s*,\\s*',\n",
" engine='python',\n",
" na_values=\"?\")\n",
"test_df = pd.read_csv(\n",
" \"https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test\",\n",
" names=COLUMNS,\n",
" sep=r'\\s*,\\s*',\n",
" skiprows=[0],\n",
" engine='python',\n",
" na_values=\"?\")\n",
"\n",
"# Drop rows with missing values\n",
"train_df = train_df.dropna(how=\"any\", axis=0)\n",
"test_df = test_df.dropna(how=\"any\", axis=0)\n",
"\n",
"print('UCI Adult Census Income dataset loaded.')"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "coilRN-hooja"
},
"source": [
"## Analyser l'ensemble de données Adult avec Facets\n",
"\n",
"Comme indiqué dans le cours d'initiation au machine learning, il est essentiel que vous compreniez l'ensemble de données *avant* de vous plonger directement dans la tâche de prédiction.\n",
"\n",
"Voici plusieurs questions importantes à examiner lorsque vous évaluez l'équité d'un ensemble de données :\n",
"\n",
"* **Est-ce qu'il manque des valeurs de caractéristiques pour un grand nombre d'observations ?**\n",
"* **Est-ce que les valeurs de caractéristiques manquantes peuvent avoir une incidence sur d'autres caractéristiques ?**\n",
"* **Est-ce qu'il y a des valeurs de caractéristiques inattendues ?**\n",
"* **Quels signes de données biaisées constatez-vous ?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "9yCIuAqWA1Pm"
},
"source": [
"Pour commencer, nous pouvons utiliser [Facets Overview](https://pair-code.github.io/facets/), un outil de visualisation interactif susceptible de nous aider à explorer l'ensemble de données Adult. Grâce à cet outil, nous pouvons analyser rapidement la répartition des valeurs dans cet ensemble."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "MW-qryqs1gig"
},
"outputs": [],
"source": [
"#@title Visualize the Data in Facets\n",
"fsg = FeatureStatisticsGenerator()\n",
"dataframes = [\n",
" {'table': train_df, 'name': 'trainData'}]\n",
"censusProto = fsg.ProtoFromDataFrames(dataframes)\n",
"protostr = base64.b64encode(censusProto.SerializeToString()).decode(\"utf-8\")\n",
"\n",
"\n",
"HTML_TEMPLATE = \"\"\"\n",
" \n",
" \n",
" \"\"\"\n",
"html = HTML_TEMPLATE.format(protostr=protostr)\n",
"display(HTML(html))"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "91wjnZFpPWw-"
},
"source": [
"### Tâche FairAware 1\n",
"\n",
"Consultez les statistiques descriptives et les histogrammes de chaque caractéristique numérique et continue. Cliquez sur le bouton **Show Raw Data** (Afficher les données brutes) situé au-dessus des histogrammes des caractéristiques catégorielles pour voir la répartition des valeurs par catégorie.\n",
"\n",
"Essayez ensuite de répondre aux questions suivantes :\n",
"\n",
"1. Est-ce qu'il manque des valeurs de caractéristiques pour un grand nombre d'observations ?\n",
"2. Est-ce que les valeurs de caractéristiques manquantes peuvent avoir une incidence sur d'autres caractéristiques ?\n",
"3. Est-ce qu'il y a des valeurs de caractéristiques inattendues ?\n",
"4. Quels signes de données biaisées constatez-vous ?"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "KlF-lQ8yQ69b"
},
"source": [
"### Solution\n",
"\n",
"Cliquez ci-dessous pour en savoir plus sur ce que nous avons découvert."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "xX_qjj5AQ_Hb"
},
"source": [
"Lorsque nous consultons les colonnes **missing** (manquantes) pour les caractéristiques à la fois numériques et catégorielles, nous constatons qu'aucune valeur ne manque, ce qui signifie qu'il n'y a pas de problème à ce niveau-là. \n",
"\n",
"Si nous regardons les valeurs minimales et maximales, ainsi que les histogrammes pour chaque caractéristique numérique, nous pouvons identifier toutes les grosses anomalies dans l'ensemble de données. Par exemple, la valeur minimale de la caractéristique `hours_per_week` est de 1, ce qui peut paraître surprenant dans le sens où la plupart des emplois impliquent généralement plusieurs heures de travail par semaine. Pour les caractéristiques `capital_gain` et `capital_loss`, plus de 90 % des valeurs sont ici nulles. Étant donné que les gains ou pertes en capital ne sont enregistrés que par des personnes qui investissent, il est parfaitement plausible que moins de 10 % des exemples aient une valeur non nulle pour ces caractéristiques. Toutefois, étudions cela de plus près pour vérifier la validité des valeurs de ces caractéristiques.\n",
"\n",
"En consultant l'histogramme relatif au sexe, nous pouvons observer que plus des deux tiers des exemples (environ 67 %) sont des hommes, ce qui laisse fortement supposer des données biaisées, car nous nous attendions à ce que la répartition entre les sexes soit plus proche de 50/50."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "hKj2hz-Sql7V"
},
"source": [
"### Étude plus approfondie\n",
"\n",
"Pour explorer davantage l'ensemble de données, nous pouvons utiliser [Facets Dive](https://pair-code.github.io/facets/), un outil qui fournit une interface interactive où chaque élément individuel dans la visualisation illustre un point de données. Toutefois, pour utiliser Facets Dive, les données doivent être converties en tableau JSON.\n",
"Heureusement, la méthode DataFrame `to_json()` s'en charge parfaitement.\n",
"\n",
"Exécutez le code de la cellule ci-dessous pour effectuer cette transformation et chargez également Facets Dive. "
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "stlklrG_xssF"
},
"outputs": [],
"source": [
"#@title Set the Number of Data Points to Visualize in Facets Dive\n",
"\n",
"SAMPLE_SIZE = 2500 #@param\n",
" \n",
"train_dive = train_df.sample(SAMPLE_SIZE).to_json(orient='records')\n",
"\n",
"HTML_TEMPLATE = \"\"\"\n",
" \n",
" \n",
" \"\"\"\n",
"html = HTML_TEMPLATE.format(jsonstr=train_dive)\n",
"display(HTML(html))"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "LxqAPDcRDFB2"
},
"source": [
"## Tâche FairAware 2\n",
"\n",
"Utilisez les menus du panneau de gauche de la visualisation pour modifier l’organisation des données :\n",
"\n",
"1. Dans le menu **Faceting | X-Axis** (Facet | Axe des X), sélectionnez **education**, puis dans les menus **Display | Color** (Affichage | Couleur) et **Display | Type** (Affichage | Type), sélectionnez **income_bracket**. Comment décririez-vous le lien entre le niveau d'études et la tranche de revenu ?\n",
"\n",
"2. Dans le menu **Faceting | X-Axis**, sélectionnez **marital_status**, puis dans les menus **Display | Color** et **Display | Type**, sélectionnez **gender**. Quelles observations notables pouvez-vous faire sur la répartition par sexe pour chaque catégorie de situation familiale ?\n",
"\n",
"Lorsque vous effectuez les tâches ci-dessus, gardez à l'esprit les questions suivantes concernant l'équité :\n",
"\n",
"* **Que manque-t-il ?**\n",
"* **Qu'est-ce qui est trop généralisé ?**\n",
"* **Qu'est-ce qui est sous-représenté ?**\n",
"* **Dans quelle mesure les variables et leurs valeurs reflètent-elles la réalité ?**\n",
"* **Que pourrions-nous laisser de côté ?**"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "qZ-9vJgSEpHj"
},
"source": [
"### Solution\n",
"\n",
"Cliquez ci-dessous pour en savoir plus sur ce que nous avons découvert."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "uYpbgdATEx8L"
},
"source": [
"1. Dans l'ensemble de données, les niveaux d'études plus élevés ont généralement tendance à être en corrélation avec une tranche de revenu plus élevé. Un niveau de revenu supérieur à 50 000 $ est ainsi plus largement représenté dans les exemples où le niveau d'études correspond à une licence, voire au-delà.\n",
"\n",
"2. Dans la plupart des catégories de situation familiale, la répartition des valeurs entre hommes et femmes est proche de 1 pour 1. La seule exception notable concerne \"married-civ-spouse\", où les hommes sont plus nombreux que les femmes de plus de 5 pour 1. Étant donné que nous avons déjà constaté dans la première tâche qu'il y avait une représentation disproportionnée d'hommes dans l'ensemble de données, nous pouvons maintenant en déduire que ce sont précisément les femmes mariées qui sont sous-représentées dans les données."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "7YVH8hYfSjer"
},
"source": [
"### Résumé\n",
"\n",
"Tracer des histogrammes, classer des exemples courants par ordre décroissant, identifier les exemples manquants ou en double, s'assurer que les ensembles d'apprentissage et d'évaluation sont similaires, calculer les quantiles des caractéristiques, **toutes ces analyses à effectuer sur vos données sont essentielles**. \n",
"\n",
"**Mieux vous comprenez vos données, plus vous aurez une idée précise de l'endroit où l'iniquité peut se glisser !**"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "2ivWw9Wpj67m"
},
"source": [
"### Tâche FairAware 3\n",
"\n",
"Maintenant que vous avez exploré l'ensemble de données avec Facets, voyons si vous parvenez à identifier certains problèmes susceptibles de se présenter en matière d'équité en fonction de ce que vous avez appris sur les caractéristiques.\n",
"\n",
"Lesquelles des caractéristiques suivantes sont susceptibles de poser problème du point de vue de l'équité ?\n",
"\n",
"Sélectionnez une caractéristique parmi les options de la liste déroulante dans la cellule ci-dessous, puis exécutez le code de cette cellule pour vérifier votre réponse. Explorez ensuite les options restantes pour mieux comprendre comment chacune d'elles influence les prédictions du modèle."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "8bFDVCV1sxiX"
},
"outputs": [],
"source": [
"feature = 'capital_gain / capital_loss' #@param [\"\", \"hours_per_week\", \"fnlwgt\", \"gender\", \"capital_gain / capital_loss\", \"age\"] {allow-input: false}\n",
"\n",
"\n",
"if feature == \"hours_per_week\":\n",
" print(\n",
"'''It does seem a little strange to see 'hours_per_week' max out at 99 hours,\n",
"which could lead to data misrepresentation. One way to address this is by\n",
"representing 'hours_per_week' as a binary \"working 40 hours/not working 40\n",
"hours\" feature. Also keep in mind that data was extracted based on work hours\n",
"being greater than 0. In other words, this feature representation exclude a\n",
"subpopulation of the US that is not working. This could skew the outcomes of the\n",
"model.''')\n",
"if feature == \"fnlwgt\":\n",
" print(\n",
"\"\"\"'fnlwgt' represents the weight of the observations. After fitting the model\n",
"to this data set, if certain group of individuals end up performing poorly \n",
"compared to other groups, then we could explore ways of reweighting each data \n",
"point using this feature.\"\"\")\n",
"if feature == \"gender\":\n",
" print(\n",
"\"\"\"Looking at the ratio between men and women shows how disproportionate the data\n",
"is compared to the real world where the ratio (at least in the US) is closer to\n",
"1:1. This could pose a huge probem in performance across gender. Considerable\n",
"measures may need to be taken to upsample the underrepresented group (in this\n",
"case, women).\"\"\")\n",
"if feature == \"capital_gain / capital_loss\":\n",
" print(\n",
"\"\"\"Both 'capital_gain' and 'capital_loss' have very low variance, which might\n",
"suggest they don't contribute a whole lot of information for predicting income. It\n",
"may be okay to omit these features rather than giving the model more noise.\"\"\")\n",
"if feature == \"age\":\n",
" print(\n",
"'''\"age\" has a lot of variance, so it might benefit from bucketing to learn\n",
"fine-grained correlations between income and age, as well as to prevent\n",
"overfitting.''')\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "n3OT-YVpftEI"
},
"source": [
"## Prédiction avec des instances Estimator TensorFlow\n",
"\n",
"Maintenant que nous avons une meilleure idée de l'ensemble de données Adult, nous pouvons commencer par créer un réseau de neurones afin de faire des prédictions sur les revenus. Dans cette section, nous allons utiliser l'API TensorFlow Estimator pour accéder à la classe `DNNClassifier`."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "ECBRATBVG4rn"
},
"source": [
"### Convertir l'ensemble de données Adult en Tensors\n",
"Nous devons d'abord définir notre fonction d'entrée, qui récupère l'ensemble de données Adult qui se trouve dans une structure de données DataFrame pandas pour la convertir ensuite en Tensors à l'aide de la fonction ```tf.estimator.inputs.pandas_input_fn()```."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "Bt-rQvJLx4Hm"
},
"outputs": [],
"source": [
"def csv_to_pandas_input_fn(data, batch_size=100, num_epochs=1, shuffle=False):\n",
" return tf.estimator.inputs.pandas_input_fn(\n",
" x=data.drop('income_bracket', axis=1),\n",
" y=data['income_bracket'].apply(lambda x: \">50K\" in x).astype(int),\n",
" batch_size=batch_size,\n",
" num_epochs=num_epochs,\n",
" shuffle=shuffle,\n",
" num_threads=1)\n",
"\n",
"print 'csv_to_pandas_input_fn() defined.'"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "0mz2sts6IjBO"
},
"source": [
"### Représenter des caractéristiques dans TensorFlow\n",
"TensorFlow nécessite que des données correspondent à un modèle. Pour ce faire, vous devez utiliser ```tf.feature_columns``` pour ingérer et représenter des caractéristiques dans TensorFlow."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "tAG5hUJwx725"
},
"outputs": [],
"source": [
"#@title Categorical Feature Columns\n",
"\n",
"# Since we don't know the full range of possible values with occupation and\n",
"# native_country, we'll use categorical_column_with_hash_bucket() to help map\n",
"# each feature string into an integer ID.\n",
"occupation = tf.feature_column.categorical_column_with_hash_bucket(\n",
" \"occupation\", hash_bucket_size=1000)\n",
"native_country = tf.feature_column.categorical_column_with_hash_bucket(\n",
" \"native_country\", hash_bucket_size=1000)\n",
"\n",
"# For the remaining categorical features, since we know what the possible values\n",
"# are, we can be more explicit and use categorical_column_with_vocabulary_list()\n",
"gender = tf.feature_column.categorical_column_with_vocabulary_list(\n",
" \"gender\", [\"Female\", \"Male\"])\n",
"race = tf.feature_column.categorical_column_with_vocabulary_list(\n",
" \"race\", [\n",
" \"White\", \"Asian-Pac-Islander\", \"Amer-Indian-Eskimo\", \"Other\", \"Black\"\n",
" ])\n",
"education = tf.feature_column.categorical_column_with_vocabulary_list(\n",
" \"education\", [\n",
" \"Bachelors\", \"HS-grad\", \"11th\", \"Masters\", \"9th\",\n",
" \"Some-college\", \"Assoc-acdm\", \"Assoc-voc\", \"7th-8th\",\n",
" \"Doctorate\", \"Prof-school\", \"5th-6th\", \"10th\", \"1st-4th\",\n",
" \"Preschool\", \"12th\"\n",
" ])\n",
"marital_status = tf.feature_column.categorical_column_with_vocabulary_list(\n",
" \"marital_status\", [\n",
" \"Married-civ-spouse\", \"Divorced\", \"Married-spouse-absent\",\n",
" \"Never-married\", \"Separated\", \"Married-AF-spouse\", \"Widowed\"\n",
" ])\n",
"relationship = tf.feature_column.categorical_column_with_vocabulary_list(\n",
" \"relationship\", [\n",
" \"Husband\", \"Not-in-family\", \"Wife\", \"Own-child\", \"Unmarried\",\n",
" \"Other-relative\"\n",
" ])\n",
"workclass = tf.feature_column.categorical_column_with_vocabulary_list(\n",
" \"workclass\", [\n",
" \"Self-emp-not-inc\", \"Private\", \"State-gov\", \"Federal-gov\",\n",
" \"Local-gov\", \"?\", \"Self-emp-inc\", \"Without-pay\", \"Never-worked\"\n",
" ])\n",
"\n",
"print 'Categorical feature columns defined.'"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "Jwtuu8MmyKCJ"
},
"outputs": [],
"source": [
"#@title Numeric Feature Columns\n",
"# For Numeric features, we can just call on feature_column.numeric_column()\n",
"# to use its raw value instead of having to create a map between value and ID.\n",
"age = tf.feature_column.numeric_column(\"age\")\n",
"fnlwgt = tf.feature_column.numeric_column(\"fnlwgt\")\n",
"education_num = tf.feature_column.numeric_column(\"education_num\")\n",
"capital_gain = tf.feature_column.numeric_column(\"capital_gain\")\n",
"capital_loss = tf.feature_column.numeric_column(\"capital_loss\")\n",
"hours_per_week = tf.feature_column.numeric_column(\"hours_per_week\")\n",
"\n",
"print 'Numeric feature columns defined.'"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "3WqAbug6jePb"
},
"source": [
"#### Convertir la caractéristique numérique `age` en caractéristique catégorielle\n",
"\n",
"Si vous avez sélectionné la caractéristique `age` lors de la **tâche FairAware 3**, vous avez remarqué que nous indiquions qu'elle pourrait tirer avantage d'un *binning*, en réunissant des âges similaires dans différents groupes. De cette façon, le modèle peut mieux se généraliser quel que soit l'âge. C'est pourquoi nous allons convertir la caractéristique numérique `age` (techniquement parlant, une [caractéristique ordinale](https://en.wikipedia.org/wiki/Ordinal_data)) en caractéristique catégorielle."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "HxVm8X15yLR7"
},
"outputs": [],
"source": [
"age_buckets = tf.feature_column.bucketized_column(\n",
" age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "2lx4JuLdi7jw"
},
"source": [
"#### Examiner les sous-groupes clés\n",
"\n",
"Lorsque vous effectuez une extraction de caractéristiques, il est important de garder à l'esprit que vous pouvez utiliser des données provenant de personnes appartenant à des sous-groupes, pour lesquels vous voulez évaluer les performances du modèle séparément.\n",
"\n",
"**_REMARQUE :_** *Dans ce contexte, un sous-groupe est défini comme un groupe de personnes partageant une caractéristique donnée (telle que l'origine, le sexe ou l'orientation sexuelle) qui mérite une attention particulière lorsque vous évaluez un modèle en gardant à l'esprit la notion d'équité.*\n",
"\n",
"Lorsque nous voulons que nos modèles atténuent ou exploitent le signal appris d'une caractéristique appartenant à un sous-groupe, nous souhaitons utiliser différents types d'outils et de techniques **dont la plupart font encore à ce stade l'objet de recherches ouvertes**.\n",
"\n",
"Lorsque vous employez différentes variables et que vous leur attribuez des tâches, il peut être utile de réfléchir à la suite. Par exemple, *à quels endroits l'interaction de la variable et de la tâche pourrait représenter un problème ?*"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "5aD1OM8egad9"
},
"source": [
"### Définir les caractéristiques du modèle\n",
"\n",
"À présent, nous pouvons définir clairement quelle caractéristique nous allons inclure dans notre modèle.\n",
"\n",
"Nous allons considérer `gender` comme un sous-groupe pour ensuite l'enregistrer dans une liste `subgroup_variables` distincte afin de pouvoir y ajouter un traitement spécial si nécessaire."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "O68xV_24gbnD"
},
"outputs": [],
"source": [
"# List of variables, with special handling for gender subgroup.\n",
"variables = [native_country, education, occupation, workclass,\n",
" relationship, age_buckets]\n",
"subgroup_variables = [gender]\n",
"feature_columns = variables + subgroup_variables"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "3nYSMg67jWaA"
},
"source": [
"### Entraîner un modèle de réseau de neurones profond sur l'ensemble de données Adult"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "_kRL5rScH1F7"
},
"source": [
"Maintenant que les caractéristiques sont prêtes, nous pouvons essayer de prédire les revenus en nous appuyant sur le deep learning.\n",
"\n",
"Pour simplifier, nous allons \"alléger\" l'architecture de réseau de neurones en **définissant simplement un réseau de neurones prédictif avec deux couches cachées**.\n",
"\n",
"Mais d'abord, nous devons convertir les caractéristiques catégorielles de grande dimension en un vecteur dense à valeur réelle et de faible dimension, que nous appelons vecteur d'incorporation. Heureusement, ```indicator_column``` (que vous pouvez assimiler à un encodage one-hot) et ```embedding_column``` (qui convertit les caractéristiques creuses en caractéristiques denses) nous aident à rationaliser le processus.\n",
"\n",
"La cellule suivante crée les colonnes profondes nécessaires pour avancer dans la définition du modèle."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "code",
"colab": {},
"colab_type": "code",
"id": "bnyw4cyLTSUB"
},
"outputs": [],
"source": [
"deep_columns = [\n",
" tf.feature_column.indicator_column(workclass),\n",
" tf.feature_column.indicator_column(education),\n",
" tf.feature_column.indicator_column(age_buckets),\n",
" tf.feature_column.indicator_column(gender),\n",
" tf.feature_column.indicator_column(relationship),\n",
" tf.feature_column.embedding_column(native_country, dimension=8),\n",
" tf.feature_column.embedding_column(occupation, dimension=8),\n",
"]\n",
"\n",
"print deep_columns\n",
"print 'Deep columns created.'"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "lBaCn_Z1PshC"
},
"source": [
"Le prétraitement de toutes les données pris en charge, nous pouvons maintenant définir le modèle de réseau de neurones profond. Commençons par utiliser les paramètres ci-dessous. (Plus tard, après avoir défini les statistiques d'évaluation et évalué le modèle, vous pourrez y revenir et ajuster ces paramètres pour comparer les résultats.)\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "tQZ1kumWk8XO"
},
"outputs": [],
"source": [
"#@title Define Deep Neural Net Model\n",
"\n",
"HIDDEN_UNITS = [1024, 512] #@param\n",
"LEARNING_RATE = 0.1 #@param\n",
"L1_REGULARIZATION_STRENGTH = 0.0001 #@param\n",
"L2_REGULARIZATION_STRENGTH = 0.0001 #@param\n",
"\n",
"model_dir = tempfile.mkdtemp()\n",
"single_task_deep_model = tf.estimator.DNNClassifier(\n",
" feature_columns=deep_columns,\n",
" hidden_units=HIDDEN_UNITS,\n",
" optimizer=tf.train.ProximalAdagradOptimizer(\n",
" learning_rate=LEARNING_RATE,\n",
" l1_regularization_strength=L1_REGULARIZATION_STRENGTH,\n",
" l2_regularization_strength=L2_REGULARIZATION_STRENGTH),\n",
" model_dir=model_dir)\n",
"\n",
"print 'Deep neural net model defined.'"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "Tjhqo9XOP2VV"
},
"source": [
"Pour simplifier, nous allons procéder à l'apprentissage pour 1 000 pas. Toutefois, n'hésitez pas à effectuer de petits ajustements avec ce paramètre."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "UtrhAXwvqtVD"
},
"outputs": [],
"source": [
"#@title Fit Deep Neural Net Model to the Adult Training Dataset\n",
"\n",
"STEPS = 1000 #@param\n",
"\n",
"single_task_deep_model.train(\n",
" input_fn=csv_to_pandas_input_fn(train_df, num_epochs=None, shuffle=True),\n",
" steps=STEPS);\n",
"\n",
"print \"Deep neural net model is done fitting.\""
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "m0UHu5t-P7G7"
},
"source": [
"Nous pouvons maintenant évaluer les performances globales du modèle à l'aide de l'ensemble de test en attente."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "HDV8hYqvncCy"
},
"outputs": [],
"source": [
"#@title Evaluate Deep Neural Net Performance\n",
"\n",
"results = single_task_deep_model.evaluate(\n",
" input_fn=csv_to_pandas_input_fn(test_df, num_epochs=1, shuffle=False),\n",
" steps=None)\n",
"print(\"model directory = %s\" % model_dir)\n",
"print(\"---- Results ----\")\n",
"for key in sorted(results):\n",
" print(\"%s: %s\" % (key, results[key]))"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "7j0LrXMGlTDl"
},
"source": [
"Vous pouvez essayer d'entraîner de nouveau le modèle avec différents paramètres. Au bout du compte, vous constaterez qu'un réseau de neurones profond aboutit à une prédiction de revenus convenable. \n",
"\n",
"**Mais ce qui manque ici, ce sont les statistiques d'évaluation concernant les sous-groupes.** Dans la section suivante, nous allons donc aborder certaines manières dont vous pouvez effectuer une évaluation au niveau des sous-groupes."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "sbwmbnUUU1kY"
},
"source": [
"## Évaluer l'équité avec une matrice de confusion\n",
"\n",
"L'évaluation des performances globales du modèle nous donne une idée de la qualité de ce dernier, mais nous en dit peu sur les performances concernant différents sous-groupes.\n",
"\n",
"Lorsqu'on évalue l'équité d'un modèle, il est important de déterminer si les erreurs de prédiction sont uniformes dans tous les sous-groupes ou si certains d'entre eux sont plus susceptibles de présenter certaines erreurs de prédiction que d'autres. \n",
"\n",
"Pour comparer la fréquence de différents types d’erreurs du modèle, une *matrice de confusion* constitue un outil clé. Rappelez-vous, comme nous l'avons vu dans le [module de classification du cours d'initiation au Machine Learning](https://developers.google.com/machine-learning/crash-course/classification/true-false-positive-negative), qu'une matrice de confusion est une grille qui illustre les prédictions de votre modèle par rapport à la réalité du terrain, et présente sous forme tabulaire des statistiques résumant la fréquence à laquelle votre modèle a effectué les bonne et mauvaise prédictions. \n",
"\n",
"Commençons par créer une matrice de confusion binaire pour le modèle de prédiction de revenus (nous l'appelons \"binaire\" dans le sens où l'étiquette `income_bracket` ne peut avoir que deux valeurs : `<50K` ou `>50K`). Nous allons définir un revenu `>50K` comme l'**étiquette positive** et un revenu `<50K` comme l'**étiquette négative**.\n",
"\n",
"**REMARQUE** : Dans ce contexte, les termes *positive* et *négative* ne doivent pas être interprétés comme des jugements de valeur (nous ne sous-entendons pas qu'une personne qui gagne plus de 50 000 USD par an est meilleure qu'une autre qui en gagne moins de 50 000 USD). Ce ne sont que des termes standards permettant de distinguer les deux prédictions possibles du modèle.\n",
"\n",
"Les cas où le modèle établit la bonne prédiction (quand la prédiction correspond à la réalité du terrain) sont classés comme **vrai**, et ceux où le modèle établit la mauvaise prédiction comme **faux**.\n",
"\n",
"Notre matrice de confusion représente ainsi quatre états possibles :\n",
"\n",
"* **vrai positif** : la prévision du modèle est `>50K`, ce qui correspond à la réalité du terrain.\n",
"* **vrai négatif** : la prévision du modèle est `<50K`, ce qui correspond à la réalité du terrain.\n",
"* **faux positif** : la prévision du modèle est `>50K`, ce qui est en contradiction avec la réalité.\n",
"* **faux négatif** : la prévision du modèle est `<50K`, ce qui est en contradiction avec la réalité.\n",
"\n",
"**REMARQUE** : Si besoin, nous pouvons utiliser le nombre de résultats pour chacun de ces états afin de calculer des statistiques d'évaluation secondaires, telles que [la précision et le rappel](https://developers.google.com/machine-learning/crash-course/classification/precision-and-recall)."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "nsUj_XZHU_mI"
},
"source": [
"### Tracer la matrice de confusion\n",
"\n",
"La cellule suivante définit une fonction qui utilise le module `sklearn.metrics.confusion_matrix` pour calculer toutes les instances (vrai positif, vrai négatif, faux positif et faux négatif) nécessaires au calcul de notre matrice de confusion binaire et des statistiques d'évaluation."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "CmOWym53VITS"
},
"outputs": [],
"source": [
"#@test {\"output\": \"ignore\"}\n",
"#@title Define Function to Compute Binary Confusion Matrix Evaluation Metrics\n",
"def compute_eval_metrics(references, predictions):\n",
" tn, fp, fn, tp = confusion_matrix(references, predictions).ravel()\n",
" precision = tp / float(tp + fp)\n",
" recall = tp / float(tp + fn)\n",
" false_positive_rate = fp / float(fp + tn)\n",
" false_omission_rate = fn / float(tn + fn)\n",
" return precision, recall, false_positive_rate, false_omission_rate\n",
"\n",
"print 'Binary confusion matrix and evaluation metrics defined.'"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "ooYe_3lqTCPz"
},
"source": [
"Nous aurons également besoin d’aide pour tracer la matrice de confusion binaire. Pour ce faire, la fonction ci-dessous combine différents modules tiers (DataFrame pandas, Matplotlib, Seaborn)."
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "TMIN_nl1VFuS"
},
"outputs": [],
"source": [
"#@title Define Function to Visualize Binary Confusion Matrix\n",
"def plot_confusion_matrix(confusion_matrix, class_names, figsize = (8,6)):\n",
" # We're taking our calculated binary confusion matrix that's already in form \n",
" # of an array and turning it into a Pandas DataFrame because it's a lot \n",
" # easier to work with when visualizing a heat map in Seaborn.\n",
" df_cm = pd.DataFrame(\n",
" confusion_matrix, index=class_names, columns=class_names, \n",
" )\n",
" fig = plt.figure(figsize=figsize)\n",
" \n",
" # Combine the instance (numercial value) with its description\n",
" strings = np.asarray([['True Positives', 'False Negatives'],\n",
" ['False Positives', 'True Negatives']])\n",
" labels = (np.asarray(\n",
" [\"{0:d}\\n{1}\".format(value, string) for string, value in zip(\n",
" strings.flatten(), confusion_matrix.flatten())])).reshape(2, 2)\n",
"\n",
" heatmap = sns.heatmap(df_cm, annot=labels, fmt=\"\");\n",
" heatmap.yaxis.set_ticklabels(\n",
" heatmap.yaxis.get_ticklabels(), rotation=0, ha='right')\n",
" heatmap.xaxis.set_ticklabels(\n",
" heatmap.xaxis.get_ticklabels(), rotation=45, ha='right')\n",
" plt.ylabel('References')\n",
" plt.xlabel('Predictions')\n",
" return fig\n",
"\n",
"print \"Binary confusion matrix visualization defined.\""
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "hUvBYtwXVzlQ"
},
"source": [
"Maintenant que nous avons défini toutes les fonctions nécessaires, nous pouvons générer la matrice de confusion binaire et les statistiques d'évaluation en utilisant les résultats de [notre modèle de réseau de neurones profond](#scrollTo=3nYSMg67jWaA). Le résultat de cette cellule se présente sous la forme d'une vue à onglets, ce qui nous permet de basculer entre la matrice de confusion et le tableau des statistiques d'évaluation."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "9enf_Jfi-AVS"
},
"source": [
"### Tâche FairAware 4\n",
"\n",
"Utilisez le formulaire ci-dessous pour générer des matrices de confusion pour les deux sous-groupes relatifs au sexe : `Female` et `Male`. Comparez le nombre de faux positifs et de faux négatifs pour chaque sous-groupe. Y a-t-il des disparités significatives au niveau des taux d'erreur suggérant que le modèle fonctionne mieux pour un sous-groupe que pour un autre ?"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "5TBzaWs1VKTa"
},
"outputs": [],
"source": [
"#@title Visualize Binary Confusion Matrix and Compute Evaluation Metrics Per Subgroup\n",
"CATEGORY = \"gender\" #@param {type:\"string\"}\n",
"SUBGROUP = \"Male\" #@param {type:\"string\"}\n",
"\n",
"# Given define subgroup, generate predictions and obtain its corresponding \n",
"# ground truth.\n",
"predictions_dict = single_task_deep_model.predict(input_fn=csv_to_pandas_input_fn(\n",
" test_df.loc[test_df[CATEGORY] == SUBGROUP], num_epochs=1, shuffle=False))\n",
"predictions = []\n",
"for prediction_item, in zip(predictions_dict):\n",
" predictions.append(prediction_item['class_ids'][0])\n",
"actuals = list(\n",
" test_df.loc[test_df[CATEGORY] == SUBGROUP]['income_bracket'].apply(\n",
" lambda x: '>50K' in x).astype(int))\n",
"classes = ['Over $50K', 'Less than $50K']\n",
"\n",
"# To stay consistent, we have to flip the confusion \n",
"# matrix around on both axes because sklearn's confusion matrix module by\n",
"# default is rotated.\n",
"rotated_confusion_matrix = np.fliplr(confusion_matrix(actuals, predictions))\n",
"rotated_confusion_matrix = np.flipud(rotated_confusion_matrix)\n",
"\n",
"tb = widgets.TabBar(['Confusion Matrix', 'Evaluation Metrics'], location='top')\n",
"\n",
"with tb.output_to('Confusion Matrix'):\n",
" plot_confusion_matrix(rotated_confusion_matrix, classes);\n",
"\n",
"with tb.output_to('Evaluation Metrics'):\n",
" grid = widgets.Grid(2,4)\n",
"\n",
" p, r, fpr, fomr = compute_eval_metrics(actuals, predictions)\n",
"\n",
" with grid.output_to(0, 0):\n",
" print \" Precision \"\n",
" with grid.output_to(1, 0):\n",
" print \" %.4f \" % p\n",
"\n",
" with grid.output_to(0, 1):\n",
" print \" Recall \"\n",
" with grid.output_to(1, 1):\n",
" print \" %.4f \" % r\n",
"\n",
" with grid.output_to(0, 2):\n",
" print \" False Positive Rate \"\n",
" with grid.output_to(1, 2):\n",
" print \" %.4f \" % fpr\n",
"\n",
" with grid.output_to(0, 3):\n",
" print \" False Omission Rate \"\n",
" with grid.output_to(1, 3):\n",
" print \" %.4f \" % fomr"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "TF3B5h3c-7Fb"
},
"source": [
"### Solution\n",
"\n",
"Cliquez ci-dessous pour en savoir plus sur ce que nous avons découvert"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "dhKR49AT_5ZK"
},
"source": [
"En utilisant les paramètres de modèle par défaut, vous constaterez que le modèle fonctionne mieux pour les hommes que pour les femmes. En particulier, dans notre processus, nous avons observé que la précision et le rappel pour les hommes (0,7490 et 0,4795, respectivement) dépassaient les performances relatives aux femmes (0,6787 et 0,3716, respectivement).\n",
"\n",
"Dans le cadre de la démonstration pour cette matrice de confusion, vous constaterez que les résultats diffèrent légèrement des statistiques de performances globales, soulignant l'importance d'évaluer les performances du modèle par sous-groupe plutôt que globalement.\n",
"\n",
"Assurez-vous également de trouver les bons compromis entre les faux positifs, les faux négatifs, les vrais positifs et les vrais négatifs. Par exemple, vous voudrez peut-être un très faible taux de faux positifs, mais un taux de vrais positifs élevé, voire peut-être une haute précision, mais un rappel faible.\n",
"\n",
"**Définissez vos paramètres d'évaluation à la lumière des compromis souhaités.**"
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [
"KlF-lQ8yQ69b",
"qZ-9vJgSEpHj",
"TF3B5h3c-7Fb"
],
"name": "intro_to_fairness.ipynb",
"provenance": [],
"version": "0.3.2"
},
"kernelspec": {
"display_name": "Python 2",
"name": "python2"
}
},
"nbformat": 4,
"nbformat_minor": 0
}