Le 23 octobre 2020 le Parlement européen a émis des propositions et des recommandations pour un développement éthique de l’intelligence artificielle dans l’UE.
Un des éléments clés de ces recommandations était d’éviter les biais et les discriminations des modèles de Machine Learning.
Nous allons voir ici comment évaluer la présence de biais dans un algorithme, c’est-à-dire comment estimer sa justesse ou en anglais sa “fairness”.
Pour ce faire nous allons reprendre l’exemple d’une banque accordant des crédits, développé dans un précédent article et évaluer si nos modèles favorisent les personnes âgées de plus de 65 ans que celles de moins de 35 ans.
Cet article fait suite à celui expliquant comment personaliser une fonction de coût avec lightgbm pour maximiser un chiffre d’affaire, disponible ici : https://hureauxarnaud.medium.com/maximiser-le-chiffre-daffaires-d-une-banque-gr%C3%A2ce-au-machine-learning-bb33f2ef5c3e
Nous allons utiliser le code disponible sur ce Google Colab (il contient également le code du précédent article ): https://colab.research.google.com/drive/1QtDeC2uM9Z2B5r7nyLO81LrRn5kOaFi6?usp=sharing
Nous allons utiliser le dataset GiveMeSomeCredit en accès libre sur Kaggle : https://www.kaggle.com/c/GiveMeSomeCredit/data?select=cs-training.csv
Rappel : Dans le précédent article nous avons testé 50 modèles différents en faisant varier l’importance des poids des faux positifs et des faux négatifs.
Nous avons trouvé que plus nous accordions d’importance au faux positifs et moins aux faux négatifs, plus le chiffre d’affaire augmentait :
Et plus la précision diminuait :
Maintenant évaluons la fairness de chacun des 50 modèles. Comment ? Il existe plusieurs indicateurs de la fairness entre 2 groupes (ici les < 35 ans et les >65 ans):
Le premier indicateur est la différence de chance d’être évalué comme positif (ici solvable) si on est effectivement positif (solvable) et s’appelle l’Equal Opportunity Difference (EOD). Plus l’EOD est grand, moins le modèle est considéré comme “fair”.
Concrètement, si je suis solvable (cad que j’ai toutes les conditions réunies pour rembourser un crédit), est-il plus probable que la banque m’accorde un crédit si j’ai plus de 65 ans que si j’ai moins de 35 ans ?
Exemple : sur 100 jeunes solvables la banque accorde 60 crédits, sur 100 personnes âgées solvable la banque accorde 80 crédits.
EOD(vieux/jeunes)=80%-60%=20%
Note : il est important de comprendre qu’ici solvable signifie “capable de rembourser le crédit”, et que l’injustice ne se situe pas dans le fait que les jeunes soient considérés comme moins fiables que les vieux, mais qu’elle se situe dans le fait que les jeunes solvables soient considérés comme moins fiable que les vieux solvables alors qu’ils sont, par définition, tous les deux fiables. Autrement dit, que le modèle fasse plus d’erreurs sur les jeunes que sur les vieux.
Le deuxième est un mix entre le premier et le % de chance d’être évalué comme positif (solvable) alors qu’on est négatif (insolvable), il s’appelle l’Average Odds Difference (AOD).
Exemple : Sur 100 jeunes solvables la banque accorde 60 crédits, sur 100 personnes âgées solvables la banque accorde 80 crédits.
Sur 100 jeunes insolvables la banque accorde 10 crédits, sur 100 personnes âgées insolvables la banque accorde 20 crédits.
AOD(vieux/jeunes)=½ x (EOD(vieux/jeunes) + 20%-10%)=½ x (20%+20%-10%)=15%
Le troisième est le rapport entre la chance d’être évalué comme positif (ici solvable) si on est effectivement positif (solvable) entre le groupe favorisé et le groupe défavorisé et s’appelle la Disparate Impact (DI).
Exemple : Sur 100 jeunes solvables la banque accorde 60 crédits, sur 100 personnes âgées solvables la banque accorde 80 crédits.
DI(vieux/jeunes)=20%/10%=2
Le quatrième est la mesure de l’entropie/d’inégalité d’un groupe, est très utilisé en économie et s’appelle le Theil Index. Nous ne le détaillerons pas ici car trop complexe pour une initiation.
Ici nous n’évaluerons que la Equal Opportunity Difference car elle est facilement interprétable.
Ainsi désormais nous pouvons évaluer l’EOD de nos 50 modèles, et nous obtenons la courbe suivante :
Ainsi plus nous avons optimisé notre CA plus nous avons augmenté l’EOD entre les < 35 et les >65 ans pour atteindre plus de 40% au 28e modèle, où le CA est maximum.
Ainsi les clients jeunes et solvables de la banque qui utilisera ce modèle auront 40% de chances supplémentaires de se voir refuser leur crédit que les plus de 65 ans solvables.
Une banque pourrait voir cette inégalité de traitement d’un mauvais œil et pourrait désirer réduire cet écart, quitte à réduire son CA..
Comment réduire cet écart ? De la même manière que nous avons augmenté le chiffre d’affaire dans l’article précédent, en modifiant la fonction de coût.
Tout à l’heure nous avons identifié les faux positifs comme étant ceux qui faisaient plonger notre CA, et bien ici posons-nous la même question, qu’est ce qui fait que notre EOD(vieux/jeunes) est si haut ?
L’EOD est la différence des taux positifs entre les >65 ans et les <35 ans, la formule complète est la suivante :
EOD(vieux/jeunes) = TP_vieux/(TP_vieux+FN_vieux) — TP_jeunes/(TP_jeunes+FN_jeunes)
- TP_vieux : true positif >65 ans
- FN_vieux : faux négatif >65 ans
- TP_jeunes : true positif < 35 ans
- FN_jeunes : faux négatif < 35 ans
Ici, pour réduire l’EOD il suffirait de diminuer le nombre de faux négatifs pour les < 35 ans et d’augmenter le nombre de faux négatifs pour les >65 ans. Pour ce faire, comme pour le CA, il suffit d’augmenter le poid d’erreur associé aux faux négatifs des < 35 ans et diminuer le poid d’erreur associé aux faux négatifs des >35 ans.
Pour cela il me suffit de remplacer dans la fonction logloss_objective:
error[error == 1] = w_tp
error[error == 2] = w_fp
error[error == -1] = w_fn
error[error == 0] = w_tn
par :
error[error == 1] = w_tp
error[error == 2] = w_fp
error[(error==-1)&(jeune==1)]=w_fn*(fairness)
error[(error==-1)&(vieux==1)]=w_fn*(1/fairness)
error[error == -1] = w_fn
error[error == 0] = w_tn
Ainsi l’ajout du multiplicateur fairness, tel que fairness>1, permet d’augmenter le poid d’erreur associé aux faux négatifs des jeunes et 1/fairness permet de diminuer le poids d’erreur associé aux faux négatifs des jeunes, plus le coefficient fairness sera grand, plus le modèle devrait être “fair” entre les >65 et < 35 ans.
Ce qui me donne les fonctions suivantes :
def logloss_objective(preds: np.ndarray,
train_data: np.ndarray,
w_tp: float,
w_fn:float,
w_fp: float,
w_tn: float,
fairness:float,
jeune:np.ndarray,
vieux:np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
label_preds = (preds > .5).astype(int)
y = train_data.get_label()
error = 2 * label_preds - y
error[error == 1] = w_tp
error[error == 2] = w_fp
error[(error==-1)&(jeune==1)]=w_fn*(fairness)
error[(error==-1)&(vieux==1)]=w_fn*(1/fairness)
error[error == -1] = w_fn
error[error == 0] = w_tn
p = special.expit(preds)
grad = p - y
grad = error * grad
hess = p * (1 - p)
hess = error * hess
return grad, hessdef logloss_metric(preds, train_data):
y = train_data.get_label()
p = special.expit(preds)
ll = np.empty_like(p)
pos = y == 1
ll[pos] = np.log(p[pos])
ll[~pos] = np.log(1 - p[~pos])
is_higher_better = False
return 'logloss', -ll.mean(), is_higher_better
De même ma fonction d’entraînement sera désormais :
def train_param_fair(fairness,param):
jeune=(X_fit.age<25).astype(int).values
vieux=(X_fit.age>60).astype(int).values
dict_weight = {'w_tp': 1, 'w_fn':1/param , 'w_fp': param, 'w_tn':1,'fairness':fairness,'jeune':jeune,'vieux':vieux}
log_loss = partial(logloss_objective, **dict_weight)
model = lightgbm.train(
params={'learning_rate': 0.01},
train_set=fit,
num_boost_round=10000,
valid_sets=(fit, val),
valid_names=('fit', 'val'),
early_stopping_rounds=20,
verbose_eval=False,
fobj=log_loss,
feval=logloss_metric)
y_pred = special.expit(model.predict(X_test))
CA=getCA(y_pred,y_test)
EOD,_,_=getFairness(y_pred,y_test,X_test)
accuracy=metrics.accuracy_score(y_test, (y_pred>0.5).astype(int))
return CA,accuracy,EOD
Désormais je peux tester 50 valeurs différentes de fairness pour 50 modèles plus ou moins fair, et évaluer pour chaque modèle son accuracy, son CA, et sa fairness traduit ici par son EOD(vieux/jeunes) :
CAs=[]
EODs=[]
accuracys=[]
for k in range(1,50):
print(k)
fairness=1+k*0.1
CA,accuracy,EOD=train_param_fair(fairness,28)
CAs.append(CA)
EODs.append(EOD)
accuracys.append(accuracy)
J’obtiens les précisions suivantes :
Les CAs suivants :
Et les EODs suivants :
- Nous remarquons que plus le coefficient de fairness est grand plus mon EOD(vieux/jeunes) est faible, notre raisonnement pour augmenter la fairness du modèle était donc bon.
- Nous remarquons aussi que plus le modèle est fair entre les vieux et les jeunes, plus le CA est faible, ce qui est logique, car notre algorithme ne se concentre désormais plus uniquement sur la maximisation du CA mais aussi sur la fairness du modèle.
- Nous remarquons également que l’impact sur le CA reste limité, le CA descend au minimum à 80 millions d’euros pour un EOD approchant les 1%.
Nous pouvons désormais tracer les points des 50 modèles selon le CA et l’EOD(vieux/jeune) qu’ils génèrent :
Et voilà, la banque pour laquelle nous travaillons n’aura plus qu’à choisir un modèle qui convient à la fois à ses besoins financiers et à ses engagements RSE. Elle pourra choisir entre un modèle générant plus de 110 millions d’euros mais avec un EOD de 35%, un autre de 100 millions d’euros avec un EOD de 15%, ou encore un autre générant seulement 85 millions mais avec un EOD de seulement 1% !
Point d’amélioration de la démarche :
- nous aurions pu évaluer la fairness en utilisant les autres métriques à disposition
- nous aurions pu utiliser la librairie spécialisé en ethique/fairness AIF360 : https://aif360.mybluemix.net/
- nous aurions pu tester d’autre limites d’âge que les < 35 et >65ans qui restent des limites arbitraires et qui ne nous disent rien sur la fairness du modèle envers les 36–64 ans.
- nous aurions pu tuner les hyperparamètres du modèles pour obtenir une meilleure performance, même si notre recherche d’une bonne fairness a déjà nécessité un nombre important d’entraînements
Cet article reste une initiation au concept de fairness et d’éthique de l’IA, le domaine reste très riche et peut se révéler beaucoup plus complexe que ce qui vous a été donné de voir, avec par exemple une étude de la fairness sur un nombre de groupe > 2 selon plusieurs critères dans un contexte de régression.
Dans un prochain article nous verrons les possibilités de la librairie AIF360 qui est très puissante une fois maîtrisée : https://aif360.mybluemix.net/
N’hésitez pas à me contacter sur Linkedin pour des questions/faire connaissance : www.linkedin.com/in/arnaud-hureaux-895421159