Amazon SageMaker Clarifyのバイアスレポートを活用しよう

記事タイトルとURLをコピーする

クラウドインテグレーション部の村上です。NBAが大好きです。

2年ほど前、仕事後にNBAを見ていると、選手全員のユニフォームに「Black Lives Matter」という文字が書かれていました。

アフリカ系アメリカ人の方が亡くなられた事件を契機としたものでしたが、顔認証などのAI技術の在り方にも影響を及ぼし、最近ではAI倫理とか責任あるAIという言葉を聞く機会が増えたように思います。

「Black Lives Matter」に関心がある方はブログもありますので、良ければどうぞ。

blog.serverworks.co.jp

今回は責任あるAIの実現に寄与するAmazon SageMaker Clarifyについて書いていきます。特に、バイアス検出に関する部分を紹介します。

責任あるAIってなに

私のざっくり解釈ですが、責任あるAIとは「AIの公平性・透明性を担保すること」と理解しています。

また、以前開催されたセッションでは以下のように定義されていました。

責任あるAIとは、AIシステムの誤⽤、悪⽤、設計不良、その他AIシステムが直⾯したり引き起こしたりする意図しない悪影響を最⼩限に抑えられるように、AIシステムを設計、開発、デプロイ、運⽤する枠組みおよび⼀連の原則
AWSを活用したresponsible AI 〜機械学習のバイアス、公平性、説明可能性〜 | AWS AI/ML Updateと事例紹介 - YouTube

完全に機械任せにはできない

機械学習モデルが差別的な推論をしてしまった事例は、調べればいくつか出てきます。

その原因には、知らず知らずのうちに学習データにバイアス(偏り)が生じ、それを機械が学習してしまったことが挙げられます。

学習データを用意したり、機械学習モデルを評価したりするのは人間なので、すべてを機械に任せることはできません。

機械学習でバイアスが生じる例を挙げてみます。

例①学習データの偏り

例えば、年齢や性別などから身長を推論する機械学習モデルを作るとします。

このとき、極端に女性の学習データが少ないと、良い精度は出にくいでしょう。性別という特徴量にバイアスがあるからです。

学習データを準備するのは人間なので、学習前にデータセットのバイアスを確認することが大事です。

例②機械学習モデルの評価段階での偏り

例えば、日本人の0.001%が陽性である病気を診断する機械学習モデルを作るとします。

仮に、常に「陰性」を返す機械学習モデルでも、正解率は99.999%です。

これは良いのでしょうか?もちろん駄目です。

学習後、モデルの良し悪しを評価するのも人間です。学習後にもバイアスの確認が大事です。

バイアス検出ができるSageMaker Clarifyを使ってみる

上記で挙げたような問題に、SageMaker Clarifyはどのように役立つのでしょうか。以下のサンプルノートブックをやってみましたので紹介します。

github.com

【まとめ】サンプルノートブックでやること

  • 年齢や性別、最終学歴などの14個の特徴量から年収が5万ドル以上かどうかを予測する二値分類をします。
  • XGBoostというアルゴリズムでモデルをトレーニングします。
  • Amazon SageMaker Clarifyを使用してバイアスレポートを出力します。
    • バイアスレポートにはバイアスメトリクスが記載されています。
    • バイアスメトリクスには学習前のメトリクスと学習後のメトリクスがあります。
    • 学習前のバイアスメトリクスは「女性より男性のデータが多い」など、データセットから検出されたバイアスです。
    • 学習後のバイアスメトリクスとは「男性の方が年収が高いと予測されやすい」など、学習済みモデルから検出されたバイアスです。

事前準備

必要なライブラリや使用するS3バケット、IAMロールを定義します。

S3バケットは入出力するデータの保管場所として使用します。IAMロールはのちにAmazon SageMaker Clarifyが作成するインスタンスのロールとして渡すために定義しています。

from sagemaker import Session

session = Session()
bucket = session.default_bucket()
prefix = "sagemaker/DEMO-sagemaker-clarify"
region = session.boto_region_name
from sagemaker import get_execution_role
import pandas as pd
import numpy as np
import os
import boto3
from datetime import datetime

role = get_execution_role()
s3_client = boto3.client("s3")

データセットのダウンロード

サンプルノートブックに従い、データをダウンロードします。

adult_columns = [
    "Age",
    "Workclass",
    "fnlwgt",
    "Education",
    "Education-Num",
    "Marital Status",
    "Occupation",
    "Relationship",
    "Ethnic group",
    "Sex",
    "Capital Gain",
    "Capital Loss",
    "Hours per week",
    "Country",
    "Target",
]
if not os.path.isfile("adult.data"):
    s3_client.download_file(
        "sagemaker-sample-files", "datasets/tabular/uci_adult/adult.data", "adult.data"
    )
    print("adult.data saved!")
else:
    print("adult.data already on disk.")

if not os.path.isfile("adult.test"):
    s3_client.download_file(
        "sagemaker-sample-files", "datasets/tabular/uci_adult/adult.test", "adult.test"
    )
    print("adult.test saved!")
else:
    print("adult.test already on disk.")

次に、ダウンロードしたデータをもとに、学習データをtraining_data、テストデータをtesting_dataと定義します。

training_data = pd.read_csv(
    "adult.data", names=adult_columns, sep=r"\s*,\s*", engine="python", na_values="?"
).dropna()

testing_data = pd.read_csv(
    "adult.test", names=adult_columns, sep=r"\s*,\s*", engine="python", na_values="?", skiprows=1
).dropna()

データ探索

いったんAmazon SageMaker Clarifyは使用せず、matplotlibという可視化用のライブラリを使用します。

以下の結果から、学習データのうち女性のデータ数が男性のデータ数の半分くらいしかないことが分かります。

training_data["Sex"].value_counts().sort_values().plot(kind="bar", title="Counts of Sex", rot=0)

また、年収が5万ドル以上である男性・女性の数を比較すると、女性は男性の1/7程度であることがわかります。

training_data["Sex"].where(training_data["Target"] == ">50K").value_counts().sort_values().plot(
    kind="bar", title="Counts of Sex earning >$50K", rot=0
)

前処理~モデル作成

サンプルノートブックに従ってエンコーディングやモデルのトレーニングを進めていきます。

from sklearn import preprocessing

def number_encode_features(df):
    result = df.copy()
    encoders = {}
    for column in result.columns:
        if result.dtypes[column] == np.object:
            encoders[column] = preprocessing.LabelEncoder()
            result[column] = encoders[column].fit_transform(result[column].fillna("None"))
    return result, encoders

training_data = pd.concat([training_data["Target"], training_data.drop(["Target"], axis=1)], axis=1)
training_data, _ = number_encode_features(training_data)
training_data.to_csv("train_data.csv", index=False, header=False)

testing_data, _ = number_encode_features(testing_data)
test_features = testing_data.drop(["Target"], axis=1)
test_target = testing_data["Target"]
test_features.to_csv("test_features.csv", index=False, header=False)

エンコードの結果、以下のように性別の特徴量(カラム名Sex)で、女性が0、男性が1になっています。

前処理後のデータはAmazon SageMaker Clarifyでも使用しますので、S3にアップロードしておきます。

from sagemaker.s3 import S3Uploader
from sagemaker.inputs import TrainingInput

train_uri = S3Uploader.upload("train_data.csv", "s3://{}/{}".format(bucket, prefix))
train_input = TrainingInput(train_uri, content_type="csv")
test_uri = S3Uploader.upload("test_features.csv", "s3://{}/{}".format(bucket, prefix))

つづいてモデルを作成します。

from sagemaker.image_uris import retrieve
from sagemaker.estimator import Estimator

container = retrieve("xgboost", region, version="1.2-1")
xgb = Estimator(
    container,
    role,
    instance_count=1,
    instance_type="ml.m5.xlarge",
    disable_profiler=True,
    sagemaker_session=session,
)

xgb.set_hyperparameters(
    max_depth=5,
    eta=0.2,
    gamma=4,
    min_child_weight=6,
    subsample=0.8,
    objective="binary:logistic",
    num_round=800,
)

xgb.fit({"train": train_input}, logs=False)

model_name = "DEMO-clarify-model-{}".format(datetime.now().strftime("%d-%m-%Y-%H-%M-%S"))
model = xgb.create_model(name=model_name)
container_def = model.prepare_container_def()
session.create_model(model_name, role, container_def)

Amazon SageMaker Clarifyを使用する

モデルの作成が完了したら、いよいよAmazon SageMaker Clarifyを使います。

SageMaker SDKで実行していきます。

SageMakerClarifyProcessorでバイアスメトリクス計算用のインスタンスを作成

後ほど紹介するバイアスメトリクスを計算するためのインスタンスを作成します。

インスタンスのIAMロールには事前準備で定義したロールを渡しています。

from sagemaker import clarify

clarify_processor = clarify.SageMakerClarifyProcessor(
    role=role, 
    instance_count=1, 
    instance_type="ml.m5.xlarge", 
    sagemaker_session=session
)

DataConfigでデータの入出力場所を定義

入力するデータの場所s3_data_input_pathは学習データを格納したS3 URIを指定します。バイアスレポートの出力場所は任意です。

labelには学習データの推論したいターゲットのカラム名、headersには学習データのカラムのリストを渡します。

bias_report_output_path = "s3://{}/{}/clarify-bias".format(bucket, prefix)
bias_data_config = clarify.DataConfig(
    s3_data_input_path=train_uri,
    s3_output_path=bias_report_output_path,
    label="Target",
    headers=training_data.columns.to_list(),
    dataset_type="text/csv",
)

ModelConfigで推論エンドポイントを作成する

ここではAmazon SageMaker Clarify専用の推論エンドポイントを作成します。推論後、エンドポイントは自動的に削除されます。

model_config = clarify.ModelConfig(
    model_name=model_name,
    instance_type="ml.m5.xlarge",
    instance_count=1,
    accept_type="text/csv",
    content_type="text/csv",
)

ModelPredictedLabelConfigで予測形式を定義する

ここではモデルが推論した確率をYes or Noに変換する閾値を定義しています。

ModelPredictedLabelConfigクラスが対応しているのは以下のタスクです。

  • Regression task(回帰)
  • Binary classification(二値分類)
  • Multiclass classification(多クラス分類)

今回は年収が5万ドル以上かどうかを予測する二値分類タスクです。

以下のようにすることで、「年収が5万ドル以上である確率80%以上」と推論された場合のみ、Yesと判断されます。

predictions_config = clarify.ModelPredictedLabelConfig(probability_threshold=0.8)

BiasConfigでバイアス検出したい観点を定義する

データ探索のところで見たように、男性に比べて女性のデータ数が少なかったです。

ここにバイアスがありそうなので、facet_nameSexを渡します。ファセットとは「物事の側面や切り口」という意味らしいです。

Sexの中でも女性のバイアス検出をしたいので、facet_values_or_threshold[0]を渡します。※前処理で女性は0、男性は1にエンコードしました。

label_values_or_thresholdには望ましい結果を渡します。今回、「年収が5万ドル以上である」がポジティブな結果なので、[1]を渡します。※データセットのTarget列で年収5万ドル以上の場合は1のラベルがついています。

group_nameは後ほど紹介するバイアスメトリクスCDDで使用されるサブグループを定義します。

bias_config = clarify.BiasConfig(
    label_values_or_threshold=[1], 
    facet_name="Sex", 
    facet_values_or_threshold=[0], 
    group_name="Age"
)

run_biasで分析開始

run_biasでトレーニング前とトレーニング後の両方の分析を同時に実行できます。

トレーニング前のバイアス検出だけしたい場合はrun_pre_training_biasというメソッドも使えます(この場合、トレーニング済みのモデルは不要です)。トレーニング後のバイアス検出だけしたい場合はrun_post_training_biasを使用します。

引数にはこれまでの手順で定義してきた設定を渡します。pre_training_methods引数とpost_training_methods引数は出力したいメトリクスを指定します。どちらも全種類のメトリクスを見たいのでallを指定しています。

clarify_processor.run_bias(
    data_config=bias_data_config,
    bias_config=bias_config,
    model_config=model_config,
    model_predicted_label_config=predictions_config,
    pre_training_methods="all",
    post_training_methods="all",
)

分析が終わるとバイアスレポートが出力されます。SageMaker Studio上でも見れますし、PDFなどの形式で出すこともできます。

バイアスレポートを見てみる

メトリクスの考え方は以下の公式ブログが非常に参考になります。

aws.amazon.com

トレーニング前のメトリクス

トレーニング前のメトリクスは以下のようにレポートされます。

まず、BiasConfigで指定したように女性0とそれ以外の割合が表示されています。冒頭のデータ探索で見たように、女性のサンプル数が少ないことが分かります。

また、CDDLCIなどのメトリクスも確認できます。

各メトリクスの詳細についてはドキュメントをご参照ください。主なメトリクスを簡単にまとめます。

docs.aws.amazon.com

メトリクス 説明
クラス不均衡 (CI) -1~1の値をとる。2 つのファセット値で構成されるデータの割合の差。
今回ファセット値とした女性はデータ全体の32.4%(男性は67.6%)なので、0.676-0.324=0.352。もしCI=1ならば、データセットには男性の情報しか存在しないということ
ラベルの比率の差 (DPL) -1~1の値をとる。正のラベルを持つファセットの割合の差。今回の例だと、正のラベル=「年収が5万ドル以上」。
学習データ内の男性のうち正のラベルは31.4%、学習データ内の女性のうち正のラベルは11.4%であるため、DPL = 0.314 - 0.114 = 0.20
条件付き属性格差 (CDD)
CDDLと表記されることもある。
-1~1の値をとる。前述の手順のBiasConfigで引数にgroup_name="Age"を設定しました。CDDはファセット間(男性・女性)の結果の格差をサブグループごと(年齢ごと)に測定します。
私のざっくり理解ですが、例えば今回学習データの女性が全員25歳以下の若い社会人だった場合、年収5万ドル以下の人が多いのは性差別とかではなく自然なことですよね。この年齢による偏りをなくすために、サブグループ(年齢)ごとにDPLを計算し、その加重平均をとったのがCDDと理解しています。

CI=0.352であることから、データセットでは男性のサンプル数が女性のサンプル数よりも多いことが分かりました。また、DPL=0.20であることから、年収5万ドル以上の男性のサンプル数の方が女性のそれよりも多く、CDDもDPLと近い値(=0.21)であることから、年齢別にみても同様のことが言えることがわかります。

トレーニング後のメトリクス

今回の場合、トレーニング後のメトリクスは、「男性の方が年収5万ドル以上と予測されやすくなっていないか」等の観点でチェックします。

混合行列

トレーニング後のメトリクスを見るには、混合行列についてある程度知っていた方が良いので概要を紹介します。

以下の図は今回のデータセットにおける女性の混合行列です。

縦軸にあるObserved Labelは実際のラベルです。0が年収5万ドル以下、1が年収5万ドル以上であるものを示します。横軸にあるPredicted Labelはモデルが推論した値です。

これにより4つのエリアに分かれています。それぞれ以下のような意味です。

表記 説明
TP(True Positive) 実際の年収が5万ドル以上であり、モデルも正しく年収5万ドル以上と推論したもの
FP(False Positive) 実際の年収は5万ドル未満なのに、モデルが誤って年収5万ドル以上と推論したもの
TF(True Negative) 実際の年収が5万ドル未満であり、モデルも正しく年収5万ドル未満と推論したもの
FN(False Negative) 実際の年収は5万ドル以上なのに、モデルが誤って年収5万ドル未満と推論したもの

上記の値を使って、モデルをいくつかの評価指標で評価できます。

説明
精度 ( TN + TP ) / ( TN + FP + FN + TP ) モデルの正解率
再現率(Recall) TP / ( TP + FN ) Positiveであるデータのうち、どれだけ正しくPositiveと分類できたか
適合率(Precision) TP / ( TP + FP ) Positiveと推論したデータのうち、どれだけ正しくPositiveと分類できたか
特異性(Specificity) TF / ( TN + FP ) Negativeであるデータのうち、どれだけ正しくNegativeと分類できたか

精度だけ見ればいいのでは?と思うかもしれませんが、冒頭で例を挙げたとおり、発症率の低い病気を診断するモデルで常に「陰性」と推論する駄目モデルでも、精度が高くなってしまうことを思い出していただくと良いと思います。

あらためてトレーニング後のメトリクスを見る

それでは主なメトリクスを簡単にまとめます。各メトリクスの詳細についてはドキュメントをご参照ください。

docs.aws.amazon.com

メトリクス 説明
予測ラベルにおける正の比率の差 (DPPL) -1~1の値をとる。ファセット間の正の予測の割合の差。DPLの推論版と理解しています。
今回作成したモデルによって女性の4.5%が年収5万ドル以上と予測されました(同様に男性は13.7%)。よってDPPL=0.137-0.045=0.092
精度差 (AD) ファセット間の精度差
リコール差 (RD) ファセット間のリコール差
特異性差 (SD) ファセット間の特異性差
処理の同等性 (TE) ファセット値ごとの偽陰性 (FN) と偽陽性 (FP) の割合の差。今回、女性の FN/FP 比率は 679/10 = 67.9(男性は 3678/84 = 43.786)なのでTE = 67.9 - 43.786 = 24.114

処理の同等性 (TE)の値から、男性より女性の方が誤って年収が低く見られやすいことが分かりました。値の絶対値がどうこうというよりは、継続的にこれらのメトリクスを監視して、モデルが偏りだしていないかチェックすることが大事だと思います。

まとめ

いかがでしたでしょうか。正直とっつきにくい分野かと思います。

ただ、SageMaker Clarify自体の料金は無料ですし、バイアスを可視化・数値化してくれるので、機械学習のワークロードに組み込んで継続的に使っていくと便利だと思いました。

村上博哉 (執筆記事の一覧)

2020年4月入社。機械学習が好きです。記事へのご意見など:hiroya.murakami@serverworks.co.jp