<jupyter_start><jupyter_text>Fig.1 - Logo d'Aequitas ⚠️⚠️⚠️⚠️⚠️⚠️ Le but de ce notebook est de présenter l'API d'Aequitas. L'analyse de biais ainsi que les choix réalisés ne sont pas forcément les meilleurs choix à faire. La réduction de biais doit être faite dans le respect du cadre réglementaire et du contexte social ⚠️⚠️⚠️⚠️⚠️⚠️ 1. Introduction**Aequitas** est un outil open-source d'audit de biais et de discrimination dans les modèles de machine learning. 1.1. InterfaceAequitas peut être utilisé via trois interfaces.- Un **[package python](https://dssg.github.io/aequitas/using_python.html)**- Une **[interface web](https://dssg.github.io/aequitas/30_seconds_webapp.html)** qui peut être utilisée soit à partir du site d'Aequitas soit localement.- Une **[CLI](https://dssg.github.io/aequitas/CLI.html)**Alors que les trois interfaces permettent de mener le même type d'analyse, la CLI et l'interface web permettent de générer des rapports d'analyse de biais très rapidement. Le package python est très pratique lors d'une analyse exploratoire du biais et lors de l'interaction avec d'autres packages.Dans ce notebook, seules les fonctionnalités du package python seront présentées.  1.2. Données d'entrée Pour le moment, Aequitas permet de mener des analyses seulement dans le cas de problématiques de **classification binaire**. En données d'entrée, Aequitas s'attend au moins à deux colonnes:- **score**: Cette colonne doit correspondre à la sortie du modèle.     - Elle peut prendre comme valeur soit des **0/1 ou des probabilités**.     - En cas de probabilités, l'utilisateur doit fournir le seuil permettant de séparer les deux classes.- **attribute**: Cette colonne correspond à la variable sensible.     - Aequitas permet d'analyser le biais avec **plusieurs variables sensibles** à la fois et *plusieurs colonnes* peuvent donc être fournies en entrée.    - Aequitas ne prend que **des variables à protéger catégorielles**.    - Les variables sensibles dépendent du cadre réglementaire et du contexte de l'analyse mais l'âge et le sexe sont des exemples de variables qui sont souvent considérées comme variables à protéger.- **label_value**: correspond à la valeur réelle (ground-truth) de l'output. Cette colonne n'est pas obligatoire mais le fait de la fournir permet de calculer plusieurs métriques en plus.Mots réservés dans Aequitas: *score*, *label_value*, *id*, *model_id*, *entity_id*, *rank_abs* et *rank_pct*. Fig.2 - Données d'entrée pour Aequitas  1.3. Structure d'Aequitas Aequitas contient 4 classes. Chacune de ces 4 classes a un rôle spécifique.Dans la pratique, les trois classes suivantes sont utilisées l'une à la suite de l'autre.- `Group`: Cette classe permet de **définir les variables à protéger** et de **calculer les métriques** de groupe dessus.- `Bias`: Cette classe **calcule les disparités** entre les différents groupes, et ce à partir des résultats des métriques calculées par les méthodes de la classe `Group`.- `Fairness`: Cette classe permet de **définir la mesure d'équité** et indique si la définition est respectée par l'output du modèle.La dernière classe, en l'occurrence `Plot` contient plusieurs fonctions qui permettent d'obtenir rapidement **des visualisations adaptées** à l'output de chacune des classes ci-dessus.  2. Cas pratique - COMPAS  2.1. Contexte**Disclaimer: Alors que l'analyse du dataset issu de l'outil COMPAS est un usecase classique dans l'analyse des biais des modèles, cet exemple s'inspire beaucoup de la documentation d'Aequitas ainsi que de plusieurs présentations de l'équipe d'Aequitas.***COMPAS* est un outil d'évaluation de risques d'individus qui est utilisé dans plusieurs états aux Etats-Unis. Le but de l'outil est d'évaluer les risques que peuvent présenter les individus et d'aider ainsi les juges à prendre **certains types de décisions** telle que la possibilité de mise en liberté provisoire sous caution.En 2016, ProPublica a mené une investigation de l'impact de COMPAS et des biais qu'il présente. Pour plus d'éléments sur l'analyse menée par ProPublica du modèle utilisé dans l'outil COMPAS, n'hésitez pas à consulter:- [Machine Bias: Risk Assessments in Criminal Sentencing](https://www.propublica.org/article/machine-bias-risk-assessments-in-criminal-sentencing)- [Le dépôt github de l'analyse de ProPublica](https://github.com/propublica/compas-analysis)Fig.3 - <NAME>, à gauche, évalué risque élevé; <NAME> évalué risque basSource: ProPublica  2.2. Jeu de données COMPAS produit un score qui correspond au **risque de récidivisme**. Ce risque correspond au risque de commettre un crime dans les deux ans qui suivent.Le **résultat de COMPAS** correspond à un score qui va de 1 à 10. Dans ce cas pratique, ce score a été converti à un score binaire, avec:- Score de 0: Risque de récidivisme bas- Score de 1: Risque de récidivisme élevéLes **variables à protéger** dans ce cas pratique sont l'*âge*, le *sexe* et l'*origine ethnique*.Le jeu de données à disposition est issu du dépôt Github d'Aequitas et est déjà dans le bon format pour Aequitas. Il correspond à deux ans de prédictions de COMPAS sur la population concernée parmi les habitants de Broward County, FL.<jupyter_code>from pathlib import Path

import pandas as pd
import matplotlib.pyplot as plt

pd.set_option("display.max_columns", None)
project_path = Path("..")
data_path = project_path/"datasets"
df_compas = pd.read_csv(data_path/"compas_for_aequitas.csv")
df_compas.head()<jupyter_output><empty_output><jupyter_text>2.3. Analyse exploratoire Dans un premier temps, une analyse des distributions des modalités des différentes variables à protéger peut révéler plusieurs pistes/hypothèses à explorer. Cette analyse est loin d'être exhaustive.  2.3.1. Croisement avec le score **Croisement de l'origine avec le score du modèle**<jupyter_code># Croisement origine ethnique - Score du modèle
(
    pd.crosstab(df_compas.race, df_compas.score, normalize="index")
    .plot.bar(stacked=True)
)<jupyter_output><empty_output><jupyter_text>**Croisement du sexe avec le score du modèle**<jupyter_code># Croisement Sexe - Score du modèle
(
    pd.crosstab(df_compas.sex, df_compas.score, normalize="index")
    .plot.bar(stacked=True)
)<jupyter_output><empty_output><jupyter_text>**Croisement de la tranche d'âge avec le score du modèle**<jupyter_code># Croisement Tranche d'âge - Score du modèle
(
    pd.crosstab(df_compas.age_cat, df_compas.score, normalize="index")
    .plot.bar(stacked=True)
)<jupyter_output><empty_output><jupyter_text>**Quelques observations**Ces observations font l'hypothèse que les données des différents sous-groupes sont représentatives des populations étudiées et que les sous-groupes comparés sont comparables.- **Origine ethnique** :Les afro-américains et les populations indigènes sont majoritairement prédits à risque élevé par le modèle. Ceci n'est pas le cas des autres modalités.- **Sexe**: Le score du modèle a quasiment la même répartition par rapport au sexe.- **Âge**: Selon le modèle, le risque de récidivisme a l'air d'être fortement corrélé à l'âge. La majorité des personnes de moins de 25 ans sont à risque élevé. L'un des arguments qui reviennent souvent est **"Et si ça correspond à la réalité?"**L'analyse qui vient d'être réalisée correspond à l'analyse de la sortie du modèle. Les sources de biais dans un pipeline de modélisation sont légion.Fig.4 - Sources de biais - Source: Aequitas AAAI 2021Au vu du pipeline présenté, est-ce que vous pouvez penser aux failles de l'argument présenté?  2.3.2. Croisement avec le label Le croisement des variables sensibles avec le label permet de voir si on a les mêmes distributions que celles obtenues en croisant les variables sensibles avec le score en sortie du modèle.Par contre, il est important de souligner l'une limites du label dont on dispose. Alors que le modèle prédit le risque de récidivisme, un label égal à 1 correspond correspond à une infraction jugée ainsi. - Si la présence des patrouilles de police est renforcée dans des quartiers à dominante afro-américaines, la prévalence de récidive dans cette population va augmenter.- Le label ne peut être calculé que pour des personnes qui ont récidivé. Le score ne peut pas être calculé pour les personnes ayant commis un premier crime et qui ont eu de lourdes peines -e.g. personnes condamnées à des dizaines d'année de prison ou à vie-.<jupyter_code># Croisement origine ethnique - Score du modèle & label
fig, axes = plt.subplots(figsize=(12, 6), nrows=1, ncols=2)

(
    pd.crosstab(df_compas.race, df_compas.score, normalize="index")
    .plot.bar(ax=axes[0], ylim=(0, 1.1), stacked=True)
)

(
    pd.crosstab(df_compas.race, df_compas.label_value, normalize="index")
    .plot.bar(ax=axes[1], ylim=(0, 1.1), stacked=True)
)

# Croisement Sexe - Score du modèle & label
fig, axes = plt.subplots(figsize=(12, 6), nrows=1, ncols=2)

(
    pd.crosstab(df_compas.sex, df_compas.score, normalize="index")
    .plot.bar(ax=axes[0], ylim=(0, 1.1), stacked=True)
)

(
    pd.crosstab(df_compas.sex, df_compas.label_value, normalize="index")
    .plot.bar(ax=axes[1], ylim=(0, 1.1), stacked=True)
)
# Croisement Âge - Score du modèle & label
fig, axes = plt.subplots(figsize=(12, 8), nrows=1, ncols=2)

(
    pd.crosstab(df_compas.age_cat, df_compas.score, normalize="index")
    .plot.bar(ax=axes[0], ylim=(0, 1.1), stacked=True)
)

(
    pd.crosstab(df_compas.age_cat, df_compas.label_value, normalize="index")
    .plot.bar(ax=axes[1], ylim=(0, 1.1), stacked=True)
)<jupyter_output><empty_output><jupyter_text>D'après ces visualisations, on constate par exemple que le pourcentage des afro-américains et des populations indigènes à risque élevé de récidivisme est bien plus élevé que la réalité.Les métriques et les méthodes mises à disposition dans Aequitas permettent de **quantifier cet écart et de le qualifier de biais ou pas selon la définition d'équité choisie**.  2.4. L' utilisation de `Group`  2.4.1. Calcul des métriques de groupe<jupyter_code>from aequitas.group import Group<jupyter_output><empty_output><jupyter_text>La classe `Group` permet de **définir les variables à protéger** et de **calculer plusieurs métriques de groupe**.Les métriques à calculer peuvent être choisies au moment de l'instanciation d'un objet de la classe `Group`.🚨🚨🚨 A l'image du choix des métriques dans un problème de modélisation, différentes métriques peuvent être inspectées afin mieux appréhender les limites du modèle. Par contre, les métriques choisies pour évaluer le biais doivent correspondre à la définition de l'équité choisie en amont du calcul.🚨🚨🚨 **Les différentes définitions d'équité ne peuvent pas toutes être respectées en même temps**. Pour plus d'éléments sur ce point, veuillez vous référer à:- [21 fairness definitions and their politics](https://www.youtube.com/results?search_query=21+fairness+definitions)- [Fair prediction with disparate impact](https://www.andrew.cmu.edu/user/achoulde/files/disparate_impact.pdf)> If an instrument satisfies **predictive parity** but the **prevalence differs between groups**, the instrument **cannot achieve equal false positive and false negative rates across those groups**.From *Fair Prediction with disparate impact* - *<NAME>* La méthode `get_crosstabs` permet de calculer les métriques de groupe pour chaque variable à protéger. Deux types de métriques sont calculés:- Les **[comptages](https://dssg.github.io/aequitas/examples/compas_demo.htmlGroup-Counts-Calculated:)**: Nombre de vrais positifs, nombre de faux positifs, etc.- Les **[ratios](https://dssg.github.io/aequitas/examples/compas_demo.htmlAbsolute-Metrics-Calculated:)**: Taux de faux positifs, précision, etc.<jupyter_code>g = Group()
grp_table, protected_attributes = g.get_crosstabs(df_compas, attr_cols=["race", "sex", "age_cat"])
grp_table.round(2)<jupyter_output><empty_output><jupyter_text>2.4.2. Plotting des métriques Pour tracer les métriques issues des calculs de `Group`, il existe deux méthodes dans la classe `Plot`.- `plot_group_metric`: trace la valeur de la métrique en question pour chaque sous-groupe des variables à protéger.- `plot_group_metric_all`: trace plusieurs pour chaque sous-groupe des variables à protéger.<jupyter_code>from aequitas.plotting import Plot
aq_plot = Plot()
fpr = aq_plot.plot_group_metric(group_table=grp_table,
                               group_metric="fpr")
fpr_tpr_plot = aq_plot.plot_group_metric_all(grp_table, metrics=["fpr", "tpr"], ncols=2)<jupyter_output><empty_output><jupyter_text>2.5. L' utilisation de `Bias`  2.5.1. Calcul des disparités La classe `Group` permet de calculer différentes métriques pour chaque sous-groupe. A partir de ces résultats, la classe `Bias` permet de calculer des **ratios de disparité**.Un **ratio de disparité** pour une métrique donnée correspond au ratio de la métrique pour un sous-groupe donné et la métrique pour le **groupe de référence**. Les groupes de référence sont choisis selon la problématique en question.Dans le cas où:- Métrique d'intérêt: taux de faux positifs (FPR)- la variable à protéger: *origines ethniques*- la classe de référence: *Caucasian*La disparité avec les autres sous-groupes est définie de la façon suivante:$$Disparity_{FPR} = \frac{ FPR_{group} } { FPR_{Caucasian} }$$🚨🚨🚨 La définition de disparité utilisée dans Aequitas correspond au **ratio de parité démographique**. Il est important de noter que d'autres définitions de disparité existent dans la littérature.<jupyter_code>from aequitas.bias import Bias
aq_bias = Bias()<jupyter_output><empty_output><jupyter_text>Il existe trois méthodes pour calculer les disparités:- `get_disparity_predefined_groups`: permet de calculer les ratios de disparité en définissant les groupes de référence pour chaque variable à protéger.- `get_disparity_min_metric`: le groupe de référence est celui avec la valeur min de la métrique en question parmi tous les groupes de la variable à protéger.- `get_disparity_major_group`: le groupe de référence est celui avec le maximum de lignes dans le jeu de données.<jupyter_code>bias_dpg = aq_bias.get_disparity_predefined_groups(grp_table, 
                                           original_df=df_compas, 
                                           ref_groups_dict={'race':'Caucasian', 'sex':'Male', 'age_cat':'25 - 45'})
bias_dpg<jupyter_output><empty_output><jupyter_text>2.5.2. Plotting des disparités Pour plotter les graphes de disparités, il existe deux méthodes. Le point de départ de ces deux méthodes est un objet `Plot`.- `plot_disparity`: trace les valeurs de disparité pour la métrique en question.- `plot_disparity_all`: trace les valeurs de disparité pour plusieurs métriques.Pour tracer les plots de disparités, on va utiliser l'objet plot défini précédemment.<jupyter_code>fpr_race_disp_plot = aq_plot.plot_disparity(bias_dpg, 
                       group_metric='fpr_disparity', 
                       attribute_name='race')
fpr_fnr_disp_plot = aq_plot.plot_disparity_all(bias_dpg, metrics=['fpr_disparity', "fnr_disparity"])<jupyter_output><empty_output><jupyter_text>2.6. L' utilisation de `Fairness`  2.6.1. Contraintes d'équité L'étape qui suit le calcul des disparités est de voir si les disparités calculées respectent les contraintes d'équité choisie au début de l'étude.Pour plus d'éléments sur les **contraintes de parité**, veuillez-vous référer à la [documentation de Fairlearn](https://fairlearn.org/v0.7.0/user_guide/mitigation.htmlfairness-constraints-for-binary-classification). Pour les différentes variables à protéger, on va opter pour un seuil de 0.8. Le choix de ce seuil ne doit pas être fait de façon aléatoire. Les textes réglementaires et la problématique taitée doivent guider le choix du seuil.Par exemple, dans le cas des *origines ethniques*, on va vérifier si les disparités entre les différents groupes respectent cette inégalité. $$ 0.8 \leq Disparity_{FPR} = \frac{ FPR_{group} } { FPR_{Caucasian} } \leq 1.25 = 1 / 0.8 $$ La classe `Fairness` dispose de trois méthodes pour l'analyse du respect des contraintes de parité.- `get_group_value_fairness`: permet de voir si les contraintes de parité sont respectées pour chaque sous-groupe de chaque variable à protéger.- `get_group_attribute_fairness`: permet de voir si les contraintes de parité sont respectées pour tous les sous-groupes de chaque variable à protéger.- `get_overall_fairness`: permet de voir si les contraintes de parité sont respectées pour toutes les variables à protéger.<jupyter_code>from aequitas.fairness import Fairness
aq_fairness = Fairness()
fairness_table = aq_fairness.get_group_value_fairness(bias_dpg, tau=0.8)
fairness_table<jupyter_output><empty_output><jupyter_text>Les **contraintes de parité** evaluées par Aequitas peuvent être retrouvées dans la [documentation d'Aequitas](https://dssg.github.io/aequitas/examples/compas_demo.htmlPairities-Calcuated:).<jupyter_code>aq_fairness.list_parities(fairness_table)
aq_fairness.get_group_attribute_fairness(fairness_table)<jupyter_output><empty_output><jupyter_text>2.6.2. Plotting d'équité La classe `Fairness` dispose de quatre méthodes pour tracer les résultats obtenus.Ces deux premières méthodes tracent les valeurs des métriques avec un code couleur indiquant si la contrainte de parité est respectée.- `plot_fairness_group`: tracer le résultat de respect de parité pour une seule métrique en affichant la valeur de la métrique en question pour les différents sous-groupes.- `plot_fairness_group_all`: tracer le résultat de respect de parité pour plusieurs métriques en affichant les valeurs des métriques choisies pour les différents sous-groupes.Ces deux méthodes affichent les valeurs du ratio de disparité avec un code couleur indiquant si la contrainte de parité est respectée.- `plot_fairness_disparity`: trace le ratio de disparité pour une variable à protéger et pour une seule métrique. - `plot_fairness_disparity_all`: trace le ratio de disparité pour plusieurs variables à protéger et plusieurs métriques.<jupyter_code>fpr_fairness_plot = aq_plot.plot_fairness_group(fairness_table,
                                                group_metric="fpr",
                                                title="FPR")
aq_plot.plot_fairness_disparity(fairness_table, group_metric="fpr", attribute_name="race")
fpr_fnr_fairness_plot = aq_plot.plot_fairness_group_all(fairness_table,
                           metrics=["fpr", "fnr"],
                           ncols=2)
fpr_fnr_fairness_disparity_plot = aq_plot.plot_fairness_disparity_all(fairness_table,
                                                                    attributes=["race"],
                                                                    metrics=["fpr", "fnr"])<jupyter_output><empty_output><jupyter_text>🤓💎💎 **Remarque**: Aequitas dispose aussi de plots qui ne sont pas documentés et qui permettent d'avoir une visualisation interactive du respect des contraintes de parité pour les différentes métriques💎💎🤓<jupyter_code>import aequitas.plot as ap 
ap.disparity(bias_dpg, ["fpr", "fnr"], 'race', fairness_threshold = 1.5)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08152344979540448, 'fraction_numerical': 0.00718707375931172, 'mean_word_length': 4.883641975308642, 'pattern_counts': {'":': 0, '<': 64, '<?xml version=': 0, '>': 65, 'https://': 11, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '291350', 'license': 'MIT', 'n_tokens_mistral': 6262, 'n_tokens_neox': 6211, 'n_words': 2184, 'path': 'notebooks/aequitas_presentation.ipynb', 'repo_name': 'chsafouane/aequitas_disc', 'size': '19092'}