公平な機械学習モデルの構築（Reduction）

# はじめに
公平な機械学習モデルを構築する手法を検証します。  
Jupyter Notebookは下記にあります。

https://github.com/toshi-4886/ML_fairness/blob/main/group_fairness/3_reduction.ipynb

### 概要
- 公平性指標としてEqualized Oddsを使用します。  
- reductionと呼ばれる手法を用いて、モデルの精度と公平性指標の両者を考慮してモデルの学習を行います。

# 公平性指標  
### Equalized Odds  
今回考える公平性指標は、性別や人種などの各グループが公平に扱われているか（group fairness）を評価するために用いられます。  
Equalized Oddsは、positiveとnegativeの2クラス分類において、positiveと判定される確率に着目した指標です。  
具体的には、正しくpositiveと判定される確率（true positive rate）と、誤ってpositiveと判定される確率（false positive rate）がグループによらず等しいかを評価します。  

数式で書くと、性別や人種などの属性の集合を$A$、モデルが予測するクラスの集合を$Y=\{0,1\}$、属性が$a \in A$かつ正解クラスが$y \in Y$のデータが、クラス$\hat{Y} =1$と予測される確率を$P(\hat{Y}=1|A=a, Y=y)$とすると、任意の$y, a, a'$で下記が成り立つ場合、モデルはEqualized Oddsを満たします。

$$
P(\hat{Y}=1|A=a, Y=y) = P(\hat{Y}=1|A=a', Y=y)
$$

# Reduction  
モデル$h$の誤分類率などを$err(h)$、Equalized oddsなどの公平性に関する制約を$c(h)$、$\lambda$をパラメータとして、下記損失$L(h,\lambda)$を考えます。

$$
L(h, \lambda) = err(h) + \lambda c(h)
$$

### Exponentiated Gradient  
このアプローチでは、$L(h,\lambda)$を最小化することを目指します。  
$L(h,\lambda)$の鞍点を見つけることで、最小化を行います。  

### Grid Search  
このアプローチでは、$\lambda$として具体的な値を与えて、モデル$h$の最適化を行います。  
$\lambda$としては、多くの値を試して、その中からテスト精度と公平性の観点でよいモデルを選択します。  


# 実装

### 1. ライブラリのインポート
必要なライブラリをインポートします。

```python
import sys
import os
from tqdm import tqdm
import matplotlib.pyplot as plt

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score

import lightgbm as lgb

!pip install fairlearn
from fairlearn.datasets import fetch_adult
from fairlearn.reductions import EqualizedOdds,  ExponentiatedGradient, GridSearch
from fairlearn.metrics import equalized_odds_difference, MetricFrame, true_positive_rate, false_positive_rate, count
```

### 2. データセットの用意  
今回は[Adult Data Set](http://archive.ics.uci.edu/ml/datasets/adult)を使用します。  
年齢や教育歴などの14の属性から、収入が5万ドルを超えるかどうかを予測するタスクのデータセットで、48,842サンプルが含まれます。  
UCI Machine Learning Repositoryからダウンロードすることもできますが、Fairlearnのfetch_adultで取得します。

データを7:3にランダムに分割して、それぞれを教師データとテストデータとして使用します。  
ラベルは、Fairlearnで用いられるように'>50K'は1、'<=50K'は0となるように変換しておきます。  

```python
X, y = fetch_adult(return_X_y=True, as_frame=True)
print(X.head())
print(X.shape)
print(set(y))

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=42)
y_train = (y_train == '>50K') * 1
y_test = (y_test == '>50K') * 1
```

```terminal:実行結果
    age  workclass    fnlwgt     education  education-num      marital-status  \
0  25.0    Private  226802.0          11th            7.0       Never-married   
1  38.0    Private   89814.0       HS-grad            9.0  Married-civ-spouse   
2  28.0  Local-gov  336951.0    Assoc-acdm           12.0  Married-civ-spouse   
3  44.0    Private  160323.0  Some-college           10.0  Married-civ-spouse   
4  18.0        NaN  103497.0  Some-college           10.0       Never-married   

          occupation relationship   race     sex  capital-gain  capital-loss  \
0  Machine-op-inspct    Own-child  Black    Male           0.0           0.0   
1    Farming-fishing      Husband  White    Male           0.0           0.0   
2    Protective-serv      Husband  White    Male           0.0           0.0   
3  Machine-op-inspct      Husband  Black    Male        7688.0           0.0   
4                NaN    Own-child  White  Female           0.0           0.0   

   hours-per-week native-country  
0            40.0  United-States  
1            50.0  United-States  
2            40.0  United-States  
3            40.0  United-States  
4            30.0  United-States  
(48842, 14)
{'<=50K', '>50K'}
```

### 3. 通常の学習  
LightGBMでモデルの学習を行います。  

```python
lgb_params = {
    'objective' : 'binary',
}
model = lgb.LGBMClassifier(**lgb_params)
model.fit(X_train, y_train)
```

### 4. Exponentiated Gradient  
FairlearnのExponentiatedGradientを用いて、公平性が改善するように学習を行います。

```python
model_fair = lgb.LGBMClassifier(**lgb_params)
mitigator = ExponentiatedGradient(model_fair, EqualizedOdds())
mitigator.fit(X_train, y_train, sensitive_features=X_train['sex'])
```

### 5. Grid Search  
FairlearnのGridSearchを用いて、公平性が改善するように学習を行います。  
$\lambda$の値は、100通りで検証します。

```python
sweep = GridSearch(lgb.LGBMClassifier(**lgb_params), constraints=EqualizedOdds(), grid_size=100)
sweep.fit(X_train, y_train, sensitive_features=X_train['sex'])
```

### 6. 公平性評価
モデルの有用性は、今回はF1スコアをsklearn.metrics.f1_scoreで計算します。  
公平性指標はEqualized OddsをFairlearnのequalized_odds_differenceで計算します。  
この関数は、sensitive_featuresに指定した属性のグループ間のtrue positive rateまたはfalse positive rateの差の最大値を出力します。  

これらの指標の散布図を描画して、予測性能と公平性のトレードオフを確認します。  
Grid Searchに関しては、公平性の昇順にソートし、F1スコアが改善した場合のみ、結果を表示しています。  

```python
# Grid search
predictors = sweep.predictors_
score_list = []
for predictor in predictors:
    pred = predictor.predict(X_test)
    score_list.append((f1_score(y_test, pred), equalized_odds_difference(y_test, pred, sensitive_features=X_test['sex'])))

f1_list = []
eo_list = []
min_f1 = 0
for f1, eo in sorted(score_list, key=lambda x: x[1]):
    if f1 > min_f1:
        min_f1 = f1
        f1_list.append(f1)
        eo_list.append(eo)
plt.scatter(eo_list, f1_list, label='Grid search')

# Exponentiated gradient
pred = mitigator.predict(X_test)
plt.scatter(equalized_odds_difference(y_test, pred, sensitive_features=X_test['sex']), f1_score(y_test, pred), label='Exponentiated Gradient')

# Default
pred = model.predict(X_test)
plt.scatter(equalized_odds_difference(y_test, pred, sensitive_features=X_test['sex']), f1_score(y_test, pred),  label='Default')
plt.legend()

plt.xlabel('Equalized Odds')
plt.ylabel('F1 score')
plt.show()
```

![result_reduction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3225334/708364ab-75ef-df5e-407e-a95ceda3f09e.png)


# おわりに

### 今回の結果
Reductionによってモデルの公平性を改善できることが確認できました。  
Grid Searchでは、F1スコアを落とさずに、Equalized Oddsを改善できたモデルもあることが分かりました。  


### 次にやること  
公平性を改善する手法は、Reduction以外も提案されているので、他の手法も検証したいと思います。

### 参考資料
- Adult Data Set  
http://archive.ics.uci.edu/ml/datasets/adult
- Fairlearn  
https://fairlearn.org/v0.8/quickstart.html
- A. Agarwal et al., A Reductions Approach to Fair Classification, ICML, 2018.