Copyright 2018 Google LLC.


In [0]:
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Introduction à l'équité en matière de machine learning


Avertissement

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 !

Objectifs d'apprentissage

 Vous sensibiliser davantage aux différents types de biais pouvant se manifester dans les données du modèle 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 * Évaluer les performances du modèle par sous-groupe plutôt que globalement

Présentation

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).

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.

À propos de l'ensemble de données et de la tâche de prédiction

Dans cet exercice, vous allez vous appuyer sur l'ensemble de données Adult 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 par Ronny Kohavi et Barry Becker.

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 :

Caractéristiques numériques

  • age : âge de la personne (en années).
  • fnlwgt : nombre de personnes concernées par l'ensemble des observations selon les organismes de recensement.
  • 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.
  • capital_gain : gain en capital réalisé par la personne (en dollars américains).
  • capital_loss : perte en capital concédée par la personne (en dollars américains).
  • hours_per_week  : nombre d'heures de travail par semaine

Caractéristiques catégorielles

  • 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.
  • education : plus haut niveau d'études atteint par la personne.
  • marital_status : situation familiale de la personne. Exemples : Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent et Married-AF-spouse.
  • occupation : fonction occupée par la personne. Exemples : Tech-support, Craft-repair, Other-service, Sales, Exec-managerial.
  • relationship : lien de parenté de chaque personne dans un foyer. Exemples : Wife, Own-child, Husband, Not-in-family, Other-relative et Unmarried.
  • gender : sexe de la personne. Deux choix uniquement : Female ou Male.
  • race : White, Black, Asian-Pac-Islander, Amer-Indian-Eskimo et Other.
  • 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.

Tâche de prédiction

La tâche de prédiction consiste à déterminer si une personne gagne plus de 50 000 USD par an.

Étiquette

  • income_bracket : si la personne gagne plus de 50 000 USD par an.

Remarques sur la collecte des données

Tous les exemples extraits de cet ensemble de données respectent les conditions suivantes :

  • age supérieur ou égal à 16
  • Revenu brut rajusté (utilisé pour calculer income_bracket) supérieur à 100 USD par an
  • fnlwgt supérieur à 0
  • hours_per_week supérieur à 0

Préparation

Commencez par importer des modules qui seront utilisés dans cet exercice.


In [0]:
import os
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import tensorflow as tf
import tempfile
!pip install seaborn==0.8.1
import seaborn as sns
import itertools
from sklearn.metrics import confusion_matrix
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.metrics import precision_recall_curve
from google.colab import widgets
# For facets
from IPython.core.display import display, HTML
import base64
!pip install facets-overview==1.0.0
from facets_overview.feature_statistics_generator import FeatureStatisticsGenerator

print('Modules are imported.')

Charger l'ensemble de données Adult

Maintenant que les modules sont importés, nous pouvons charger l'ensemble de données Adult dans une structure de données DataFrame pandas.


In [0]:
COLUMNS = ["age", "workclass", "fnlwgt", "education", "education_num",
           "marital_status", "occupation", "relationship", "race", "gender",
           "capital_gain", "capital_loss", "hours_per_week", "native_country",
           "income_bracket"]

train_df = pd.read_csv(
    "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data",
    names=COLUMNS,
    sep=r'\s*,\s*',
    engine='python',
    na_values="?")
test_df = pd.read_csv(
    "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test",
    names=COLUMNS,
    sep=r'\s*,\s*',
    skiprows=[0],
    engine='python',
    na_values="?")

# Drop rows with missing values
train_df = train_df.dropna(how="any", axis=0)
test_df = test_df.dropna(how="any", axis=0)

print('UCI Adult Census Income dataset loaded.')

Analyser l'ensemble de données Adult avec Facets

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.

Voici plusieurs questions importantes à examiner lorsque vous évaluez l'équité d'un ensemble de données :

  • Est-ce qu'il manque des valeurs de caractéristiques pour un grand nombre d'observations ?
  • Est-ce que les valeurs de caractéristiques manquantes peuvent avoir une incidence sur d'autres caractéristiques ?
  • Est-ce qu'il y a des valeurs de caractéristiques inattendues ?
  • Quels signes de données biaisées constatez-vous ?

Pour commencer, nous pouvons utiliser Facets Overview, 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.


In [0]:
#@title Visualize the Data in Facets
fsg = FeatureStatisticsGenerator()
dataframes = [
    {'table': train_df, 'name': 'trainData'}]
censusProto = fsg.ProtoFromDataFrames(dataframes)
protostr = base64.b64encode(censusProto.SerializeToString()).decode("utf-8")


HTML_TEMPLATE = """<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.3.3/webcomponents-lite.js"></script>
        <link rel="import" href="https://raw.githubusercontent.com/PAIR-code/facets/1.0.0/facets-dist/facets-jupyter.html">
        <facets-overview id="elem"></facets-overview>
        <script>
          document.querySelector("#elem").protoInput = "{protostr}";
        </script>"""
html = HTML_TEMPLATE.format(protostr=protostr)
display(HTML(html))

Tâche FairAware 1

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.

Essayez ensuite de répondre aux questions suivantes :

  1. Est-ce qu'il manque des valeurs de caractéristiques pour un grand nombre d'observations ?
  2. Est-ce que les valeurs de caractéristiques manquantes peuvent avoir une incidence sur d'autres caractéristiques ?
  3. Est-ce qu'il y a des valeurs de caractéristiques inattendues ?
  4. Quels signes de données biaisées constatez-vous ?

Solution

Cliquez ci-dessous pour en savoir plus sur ce que nous avons découvert.

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à.

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.

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.

Étude plus approfondie

Pour explorer davantage l'ensemble de données, nous pouvons utiliser Facets Dive, 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. Heureusement, la méthode DataFrame to_json() s'en charge parfaitement.

Exécutez le code de la cellule ci-dessous pour effectuer cette transformation et chargez également Facets Dive.


In [0]:
#@title Set the Number of Data Points to Visualize in Facets Dive

SAMPLE_SIZE = 2500 #@param
  
train_dive = train_df.sample(SAMPLE_SIZE).to_json(orient='records')

HTML_TEMPLATE = """<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.3.3/webcomponents-lite.js"></script>
        <link rel="import" href="https://raw.githubusercontent.com/PAIR-code/facets/1.0.0/facets-dist/facets-jupyter.html">
        <facets-dive id="elem" height="600"></facets-dive>
        <script>
          var data = {jsonstr};
          document.querySelector("#elem").data = data;
        </script>"""
html = HTML_TEMPLATE.format(jsonstr=train_dive)
display(HTML(html))

Tâche FairAware 2

Utilisez les menus du panneau de gauche de la visualisation pour modifier l’organisation des données :

  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 ?

  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 ?

Lorsque vous effectuez les tâches ci-dessus, gardez à l'esprit les questions suivantes concernant l'équité :

  • Que manque-t-il ?
  • Qu'est-ce qui est trop généralisé ?
  • Qu'est-ce qui est sous-représenté ?
  • Dans quelle mesure les variables et leurs valeurs reflètent-elles la réalité ?
  • Que pourrions-nous laisser de côté ?

Solution

Cliquez ci-dessous pour en savoir plus sur ce que nous avons découvert.

  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à.

  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.

Résumé

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.

Mieux vous comprenez vos données, plus vous aurez une idée précise de l'endroit où l'iniquité peut se glisser !

Tâche FairAware 3

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.

Lesquelles des caractéristiques suivantes sont susceptibles de poser problème du point de vue de l'équité ?

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.


In [0]:
feature = 'capital_gain / capital_loss' #@param ["", "hours_per_week", "fnlwgt", "gender", "capital_gain / capital_loss", "age"] {allow-input: false}


if feature == "hours_per_week":
  print(
'''It does seem a little strange to see 'hours_per_week' max out at 99 hours,
which could lead to data misrepresentation. One way to address this is by
representing 'hours_per_week' as a binary "working 40 hours/not working 40
hours" feature. Also keep in mind that data was extracted based on work hours
being greater than 0. In other words, this feature representation exclude a
subpopulation of the US that is not working. This could skew the outcomes of the
model.''')
if feature == "fnlwgt":
  print(
"""'fnlwgt' represents the weight of the observations. After fitting the model
to this data set, if certain group of individuals end up performing poorly 
compared to other groups, then we could explore ways of reweighting each data 
point using this feature.""")
if feature == "gender":
  print(
"""Looking at the ratio between men and women shows how disproportionate the data
is compared to the real world where the ratio (at least in the US) is closer to
1:1. This could pose a huge probem in performance across gender. Considerable
measures may need to be taken to upsample the underrepresented group (in this
case, women).""")
if feature == "capital_gain / capital_loss":
  print(
"""Both 'capital_gain' and 'capital_loss' have very low variance, which might
suggest they don't contribute a whole lot of information for predicting income. It
may be okay to omit these features rather than giving the model more noise.""")
if feature == "age":
  print(
'''"age" has a lot of variance, so it might benefit from bucketing to learn
fine-grained correlations between income and age, as well as to prevent
overfitting.''')

Prédiction avec des instances Estimator TensorFlow

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.

Convertir l'ensemble de données Adult en Tensors

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().


In [0]:
def csv_to_pandas_input_fn(data, batch_size=100, num_epochs=1, shuffle=False):
  return tf.estimator.inputs.pandas_input_fn(
      x=data.drop('income_bracket', axis=1),
      y=data['income_bracket'].apply(lambda x: ">50K" in x).astype(int),
      batch_size=batch_size,
      num_epochs=num_epochs,
      shuffle=shuffle,
      num_threads=1)

print('csv_to_pandas_input_fn() defined.')

Représenter des caractéristiques dans TensorFlow

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.


In [0]:
#@title Categorical Feature Columns

# Since we don't know the full range of possible values with occupation and
# native_country, we'll use categorical_column_with_hash_bucket() to help map
# each feature string into an integer ID.
occupation = tf.feature_column.categorical_column_with_hash_bucket(
    "occupation", hash_bucket_size=1000)
native_country = tf.feature_column.categorical_column_with_hash_bucket(
    "native_country", hash_bucket_size=1000)

# For the remaining categorical features, since we know what the possible values
# are, we can be more explicit and use categorical_column_with_vocabulary_list()
gender = tf.feature_column.categorical_column_with_vocabulary_list(
    "gender", ["Female", "Male"])
race = tf.feature_column.categorical_column_with_vocabulary_list(
    "race", [
        "White", "Asian-Pac-Islander", "Amer-Indian-Eskimo", "Other", "Black"
    ])
education = tf.feature_column.categorical_column_with_vocabulary_list(
    "education", [
        "Bachelors", "HS-grad", "11th", "Masters", "9th",
        "Some-college", "Assoc-acdm", "Assoc-voc", "7th-8th",
        "Doctorate", "Prof-school", "5th-6th", "10th", "1st-4th",
        "Preschool", "12th"
    ])
marital_status = tf.feature_column.categorical_column_with_vocabulary_list(
    "marital_status", [
        "Married-civ-spouse", "Divorced", "Married-spouse-absent",
        "Never-married", "Separated", "Married-AF-spouse", "Widowed"
    ])
relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    "relationship", [
        "Husband", "Not-in-family", "Wife", "Own-child", "Unmarried",
        "Other-relative"
    ])
workclass = tf.feature_column.categorical_column_with_vocabulary_list(
    "workclass", [
        "Self-emp-not-inc", "Private", "State-gov", "Federal-gov",
        "Local-gov", "?", "Self-emp-inc", "Without-pay", "Never-worked"
    ])

print('Categorical feature columns defined.')

In [0]:
#@title Numeric Feature Columns
# For Numeric features, we can just call on feature_column.numeric_column()
# to use its raw value instead of having to create a map between value and ID.
age = tf.feature_column.numeric_column("age")
fnlwgt = tf.feature_column.numeric_column("fnlwgt")
education_num = tf.feature_column.numeric_column("education_num")
capital_gain = tf.feature_column.numeric_column("capital_gain")
capital_loss = tf.feature_column.numeric_column("capital_loss")
hours_per_week = tf.feature_column.numeric_column("hours_per_week")

print('Numeric feature columns defined.')

Convertir la caractéristique numérique age en caractéristique catégorielle

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) en caractéristique catégorielle.


In [0]:
age_buckets = tf.feature_column.bucketized_column(
    age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])

Examiner les sous-groupes clés

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.

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é.

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.

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 ?

Définir les caractéristiques du modèle

À présent, nous pouvons définir clairement quelle caractéristique nous allons inclure dans notre modèle.

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.


In [0]:
# List of variables, with special handling for gender subgroup.
variables = [native_country, education, occupation, workclass,
             relationship, age_buckets]
subgroup_variables = [gender]
feature_columns = variables + subgroup_variables

Entraîner un modèle de réseau de neurones profond sur l'ensemble de données Adult

Maintenant que les caractéristiques sont prêtes, nous pouvons essayer de prédire les revenus en nous appuyant sur le deep learning.

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.

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.

La cellule suivante crée les colonnes profondes nécessaires pour avancer dans la définition du modèle.


In [0]:
deep_columns = [
    tf.feature_column.indicator_column(workclass),
    tf.feature_column.indicator_column(education),
    tf.feature_column.indicator_column(age_buckets),
    tf.feature_column.indicator_column(gender),
    tf.feature_column.indicator_column(relationship),
    tf.feature_column.embedding_column(native_country, dimension=8),
    tf.feature_column.embedding_column(occupation, dimension=8),
]

print(deep_columns)
print('Deep columns created.')

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.)


In [0]:
#@title Define Deep Neural Net Model

HIDDEN_UNITS = [1024, 512] #@param
LEARNING_RATE = 0.1 #@param
L1_REGULARIZATION_STRENGTH = 0.0001 #@param
L2_REGULARIZATION_STRENGTH = 0.0001 #@param

model_dir = tempfile.mkdtemp()
single_task_deep_model = tf.estimator.DNNClassifier(
    feature_columns=deep_columns,
    hidden_units=HIDDEN_UNITS,
    optimizer=tf.train.ProximalAdagradOptimizer(
      learning_rate=LEARNING_RATE,
      l1_regularization_strength=L1_REGULARIZATION_STRENGTH,
      l2_regularization_strength=L2_REGULARIZATION_STRENGTH),
    model_dir=model_dir)

print('Deep neural net model defined.')

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.


In [0]:
#@title Fit Deep Neural Net Model to the Adult Training Dataset

STEPS = 1000 #@param

single_task_deep_model.train(
    input_fn=csv_to_pandas_input_fn(train_df, num_epochs=None, shuffle=True),
    steps=STEPS);

print('Deep neural net model is done fitting.')

Nous pouvons maintenant évaluer les performances globales du modèle à l'aide de l'ensemble de test en attente.


In [0]:
#@title Evaluate Deep Neural Net Performance

results = single_task_deep_model.evaluate(
    input_fn=csv_to_pandas_input_fn(test_df, num_epochs=1, shuffle=False),
    steps=None)
print("model directory = %s" % model_dir)
print("---- Results ----")
for key in sorted(results):
  print("%s: %s" % (key, results[key]))

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.

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.

Évaluer l'équité avec une matrice de confusion

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.

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.

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, 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.

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.

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.

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.

Notre matrice de confusion représente ainsi quatre états possibles :

  • vrai positif : la prévision du modèle est >50K, ce qui correspond à la réalité du terrain.
  • vrai négatif : la prévision du modèle est <50K, ce qui correspond à la réalité du terrain.
  • faux positif : la prévision du modèle est >50K, ce qui est en contradiction avec la réalité.
  • faux négatif : la prévision du modèle est <50K, ce qui est en contradiction avec la réalité.

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.

Tracer la matrice de confusion

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.


In [0]:
#@test {"output": "ignore"}
#@title Define Function to Compute Binary Confusion Matrix Evaluation Metrics
def compute_eval_metrics(references, predictions):
  tn, fp, fn, tp = confusion_matrix(references, predictions).ravel()
  precision = tp / float(tp + fp)
  recall = tp / float(tp + fn)
  false_positive_rate = fp / float(fp + tn)
  false_omission_rate = fn / float(tn + fn)
  return precision, recall, false_positive_rate, false_omission_rate

print('Binary confusion matrix and evaluation metrics defined.')

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).


In [0]:
#@title Define Function to Visualize Binary Confusion Matrix
def plot_confusion_matrix(confusion_matrix, class_names, figsize = (8,6)):
    # We're taking our calculated binary confusion matrix that's already in form 
    # of an array and turning it into a Pandas DataFrame because it's a lot 
    # easier to work with when visualizing a heat map in Seaborn.
    df_cm = pd.DataFrame(
        confusion_matrix, index=class_names, columns=class_names, 
    )
    fig = plt.figure(figsize=figsize)
    
    # Combine the instance (numercial value) with its description
    strings = np.asarray([['True Positives', 'False Negatives'],
                          ['False Positives', 'True Negatives']])
    labels = (np.asarray(
        ["{0:d}\n{1}".format(value, string) for string, value in zip(
            strings.flatten(), confusion_matrix.flatten())])).reshape(2, 2)

    heatmap = sns.heatmap(df_cm, annot=labels, fmt="");
    heatmap.yaxis.set_ticklabels(
        heatmap.yaxis.get_ticklabels(), rotation=0, ha='right')
    heatmap.xaxis.set_ticklabels(
        heatmap.xaxis.get_ticklabels(), rotation=45, ha='right')
    plt.ylabel('References')
    plt.xlabel('Predictions')
    return fig

print('Binary confusion matrix visualization defined.')

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. 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.

Tâche FairAware 4

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 ?


In [0]:
#@title Visualize Binary Confusion Matrix and Compute Evaluation Metrics Per Subgroup
CATEGORY  =  "gender" #@param {type:"string"}
SUBGROUP =  "Male" #@param {type:"string"}

# Given define subgroup, generate predictions and obtain its corresponding 
# ground truth.
predictions_dict = single_task_deep_model.predict(input_fn=csv_to_pandas_input_fn(
    test_df.loc[test_df[CATEGORY] == SUBGROUP], num_epochs=1, shuffle=False))
predictions = []
for prediction_item, in zip(predictions_dict):
    predictions.append(prediction_item['class_ids'][0])
actuals = list(
    test_df.loc[test_df[CATEGORY] == SUBGROUP]['income_bracket'].apply(
        lambda x: '>50K' in x).astype(int))
classes = ['Over $50K', 'Less than $50K']

# To stay consistent, we have to flip the confusion 
# matrix around on both axes because sklearn's confusion matrix module by
# default is rotated.
rotated_confusion_matrix = np.fliplr(confusion_matrix(actuals, predictions))
rotated_confusion_matrix = np.flipud(rotated_confusion_matrix)

tb = widgets.TabBar(['Confusion Matrix', 'Evaluation Metrics'], location='top')

with tb.output_to('Confusion Matrix'):
  plot_confusion_matrix(rotated_confusion_matrix, classes);

with tb.output_to('Evaluation Metrics'):
  grid = widgets.Grid(2,4)

  p, r, fpr, fomr = compute_eval_metrics(actuals, predictions)

  with grid.output_to(0, 0):
    print(' Precision ')
  with grid.output_to(1, 0):
    print(' %.4f ' % p)

  with grid.output_to(0, 1):
    print(' Recall ')
  with grid.output_to(1, 1):
    print(' %.4f ' % r)

  with grid.output_to(0, 2):
    print(' False Positive Rate ')
  with grid.output_to(1, 2):
    print(' %.4f ' % fpr)

  with grid.output_to(0, 3):
    print(' False Omission Rate ')
  with grid.output_to(1, 3):
    print(' %.4f ' % fomr)

Solution

Cliquez ci-dessous pour en savoir plus sur ce que nous avons découvert

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).

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.

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.

Définissez vos paramètres d'évaluation à la lumière des compromis souhaités.