AREKORE

daikikatsuragawaのアレコレ

機械学習における反実仮想説明(Counterfactual Explanations)を生成するライブラリ“DiCE”を活用したサービス開発についての考察

個人的に、機械学習における反実仮想説明(Counterfactual Explanations)*1を生成するPythonのライブラリ“DiCE”に興味を持ちました。そして、DiCEを活用したサービス開発について考察をしました。本記事はその考察についてのメモです。

機械学習における意思決定を促進する説明

機械学習を活用した予測において予測対象者の行動を促す、つまり「意思決定の促進」を目的とする場合があります。このとき、意思決定者(予測対象者)に予測の根拠についての説明を提供することが重要です。そのゆえ、機械学習の文脈で説明についての研究、議論が進められています。

例えば、機械学習モデルの出力の解釈性を扱った手法の例としてSHAP(SHapley Additive exPlanations)というものがあります。

proceedings.neurips.cc

これは各予測の根拠として、各特徴量の貢献度(寄与度)を算出します。これを提供することで、予測に対して納得したり妥当だと感じることにつながります。このとき、その予測への信頼が高まると考えられるため「意思決定の促進」が期待されます。

反実仮想説明を生成するDiCE

DiCEとは?

上記で紹介した手法では、予測を実施したタイミングにおける、機械学習モデルの出力の解釈性として、各特徴量の貢献度などの出力が可能です。ただし、それらはあくまで事実における結果自体の根拠の説明です。もし、予測を覆したいシチュエーションだった場合、意思決定者は具体的にどの特徴量をどれだけ変動させたら良いのかが明らかではありません。意思決定者へ行動を促すためには、どれだけでもわかりやすく説明することが望ましいです。

それに対して「未来の状態を提示する」というアプローチで説明を提供するDiCEを紹介します。DiCEとは、目的とする出力を得るための反実仮想サンプルを生成するアルゴリズムおよびそれが実装されているライブラリです。DiCEはMicrosoft Researchにより開発されています。

www.microsoft.com

反実仮想、つまり「事実と反対のことを想定すること」という概念に基づき、予測結果が覆る(反対になる)特徴量の変動例を提示する説明を反事実的説明(Counterfactual Explanations)と呼びます。DiCEは反事実的説明を生成します。具体的には、DiCEは予測結果に対して、異なる結果が得られたであろう「入力の特徴量を変動させたサンプル」を生成することにより、説明を提供します。この説明を受け取った人は「どの特徴量をどれだけ変動させたら良いのか」といった指針を得ます。これにより「何にどれだけ取り組めば良いのか」といった具体的な行動を促すことが期待されます。「サンプルを生成することによる直接的な材料提供」という点において、他の類似するアルゴリズムとは異なります。

具体的なDiCEの活用シーンについて、README.rstなどでも紹介されている「金融会社におけるローン審査モデル」にDiCEを適用する例を説明します。

github.com

前提として、ある金融会社では、機械学習モデルの分類アルゴリズムを活用したローンの審査をサービスとして提供しているとします。これにDiCEを組み合わせてみると、DiCEは「ローンを申請したが拒否された人」に対して、特徴量を変動させてローンの申請が承認されるサンプルを提示します。例えばサービスは「もしあなたの収入が今より10,000ドル高かった場合、ローンを認めます。」と説明します。この説明を受けた人は、 「どの特徴量をどれだけ変動させたら良いのか」といった指針を得ることが可能です。指針を得ることにより「具体的に何をしたら良いのか」を想像できます。このように、DiCEは 予測に貢献した重要な特徴だけを提供するのではなく、反実仮想を考慮し、意思決定の対象者が望ましい結果を得るために次に何をすべきかを決定するのに役立ちます 。

また、DiCEは実現可能性やドメインの制約を考慮できます。事実サンプルと反実仮想サンプルのベクトルとしての距離を考慮し、実現可能性が高い反実仮想サンプルを提供します。そして、ドメインの制約を考慮して、特定の特徴量を変動の対象外にしたり、特徴量の値の上限と下限を設定することなども可能です。

DiCEの使用方法と動作の紹介

プログラムよってDiCEの使用方法と動作を紹介します。DiCEはPythonで実装されており、OSSオープンソースソフトウェア)としてGitHubで公開されています。

github.com

そして、PythonサードパーティーソフトウェアリポジトリであるPyPIに登録されています。PyPIにおけるDiCEの識別子はdice_mlです。

pypi.org

題材は「タイタニック号の生存者予測」とします。これは以下の点でDiCEの例を紹介するために適していると考えられます。

  • 生存か否かという2クラス分類であること
  • 予測対象者が一方のクラス(生存)に予測されることを望むであろうこと
  • 機械学習チュートリアルとして一般的に知られている例であること

シチュエーションとしては、「生存か否かを判別を事前に判別するサービス」があったとしてその開発を考えます。このシチュエーションは多少無理がある気もしますが、ローンなど良さそうなサンプルデータを見つけられなかったこともあり、あくまでDiCEの例を紹介するためなので流してもらえると幸いです。

実行環境はGoogle Colaboratoryとします。まずはDiCEをインストールします。

!pip install dice_ml

DiCEで反実仮想説明を生成するために、以下2点が必要です。

  • 学習データ/テストデータ
  • 学習済みモデル

はじめにこれらを準備します。「タイタニック号の生存者予測」のデータはseabornから取得します。最低限の前処理を実施します。ホールドアウト検証として、学習データとテストデータに分割します。モデルはロジスティック回帰を選定しました。scikit-learnで実装されているものを利用します。余談ですが、個人的にロジスティック回帰のような予測確率を算出できる手法はDiCEとの相性が良いと考えています。理由は組み合わせることによって説明に有用な情報を増すことができる点です。

import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import seaborn as sns

titanic_df = sns.load_dataset("titanic")

titanic_df = titanic_df.drop(columns="alive")
titanic_df["age"] = titanic_df["age"].fillna(titanic_df["age"].median())
titanic_df["sex"], _ = pd.factorize(titanic_df["sex"], sort=True)
titanic_df["embarked"], _ = pd.factorize(titanic_df["embarked"], sort=True)
titanic_df["class"], _ = pd.factorize(titanic_df["class"], sort=True)
titanic_df["who"], _ = pd.factorize(titanic_df["who"], sort=True)
titanic_df["adult_male"], _ = pd.factorize(titanic_df["adult_male"], sort=True)
titanic_df["deck"], _ = pd.factorize(titanic_df["deck"], sort=True)
titanic_df["embark_town"], _ = pd.factorize(titanic_df["embark_town"], sort=True)
titanic_df["alone"], _ = pd.factorize(titanic_df["alone"], sort=True)

X = titanic_df.drop(columns="survived")
y = titanic_df["survived"]

train_x, test_x, train_y, test_y = train_test_split(X, y, test_size=0.2, random_state=123)

model = LogisticRegression()
model.fit(train_x, train_y_bin)

ここまでで以下の用意が完了しました。

  • 学習データ(train_xtrain_y)/テストデータ(test_xtest_y
  • 学習済みモデル(model

ここからはDiCEによる準備です。dice_ml.Dataでテストデータについての設定をし、dice_ml.Modelで学習済みモデルについての設定をします。

import dice_ml

d = dice_ml.Data(dataframe = pd.concat([test_x, test_y], axis=1),
                 continuous_features=['age', 'fare'], 
                 outcome_name = "survived")

m = dice_ml.Model(model=model, 
                  backend="sklearn")

exp = dice_ml.Dice(d, m)

それでは、試しにテストデータ1件(index == 524)の反実仮想説明を生成します。パラメータについては生成する反実仮想の数をtotal_CFsに、変動対象の特徴をfeatures_to_varyに、変数のとりうる値の上限と下限をpermitted_rangeに設定します。そして、望むクラス(0)をdesired_classに設定します。

pre_counter = test_x.query('index == 524')

dice_exp = exp.generate_counterfactuals(
    pre_counter,
    total_CFs=5,
    features_to_vary=["pclass", "sibsp", "parch", "fare", "embarked", "class", "deck"],
    permitted_range = {"fare":[0, 300]},
    desired_class = 1, 
)

dice_exp.visualize_as_dataframe(show_only_changes=True)

上記のような出力がなされます。もともと、生存予測の結果は否(0)でしたが、例えば、sibspを2、fareを約8.03、classを0に変動させると生存予測の結果が変わるというサンプルが生成されたということが示されています。また、このようなサンプルがtotal_CFsに基づき、5件生成されています。またこれらは、features_to_varypermitted_rangeで設定した制約を満たすものであり、より実現性の高いものが提示されています。

本記事では概要の理解までを目的としているためここまでとします。詳しくはドキュメントをご覧ください。

interpret.ml

以上より、DiCEを活用して反実仮想説明を生成することが可能です。

DiCEを活用したサービスの例

DiCEを活用したサービスの例について考察します。ユーザの意思決定の促進を目的としたサービスにおいてDiCEは有用であると考えられます。以下の例が考えられます。

  • 金融機関における機械学習に基づくローンの審査
  • 医療における機械学習に基づく病気の判定
  • 模擬試験の結果の通知とアドバイス
  • マーケティングにおけるファンクラブ加入者の分析と増加のための施策選定

“DiCEとは?”でも説明しましたが、ローンの審査結果として融資の可否を提示するとき、DiCEは有用です。ローンの審査結果として、拒否となってしまった人がいるとします。その人はローンを受けたいため、現状の課題を解決して再び挑もうとするはずです。そのとき、何をどれだけ改善すれば良いのかという情報が得られたら、それを参考に現状の課題の改善に取り組むことができます。少し先の未来で、その方がローンの審査を通過した場合、サービス提供元も利益が得られます。このように単にユーザのためだけでなく、サービス提供元も利益が得られるといった点で、投資する価値のあるサービスであると考えられます。このようにサービス提供元、提供先のともどもに利益が考えられます。

他にも医療において、機械学習を活用して病気の判定を実施するとき、DiCEは有用です。例えば、特定の病気である、もしくはその可能性が高いと判断された人がいるとします。その人はその病気に打ち勝つために行動を起こすはずです。また、その判定が医療の現場で行われる場合であっても、医師がその病気を治療するはずです。そのとき、何をどれだけ改善すれば良いのかという情報が得られたら、それを参考に現状の課題の改善に取り組むことができます。

予備校や通信教育など学習を提供する企業による模擬試験の結果の通知とアドバイスにもDiCEが有用であると考えられます。例えば大学入試、単純な点数の和ではなく、場合によっては必要十分条件などもあるかと思われます。それらも加味した上で、ある教科の点数をこれだけ上げて、その他の教科の点数をこれだけ上げると合格可能性が高いクラスに変動するということを説明できます。今後の学びの指針を明らかにすることで、現状の課題の改善に取り組むことができます。

上記3点は主にユーザによる、ユーザのための反実仮想説明の提示、つまり主な目的をUX(ユーザーエクスペリエンス)向上として掲げている場合です。サービス提供元も利益が得られる場合もありますが、ユーザが主なトリガーです。。それに対して、サービス提供者がユーザを動かすための施策を設計する参考として反実仮想説明を扱う場合もあり得ます。例えば、ファンクラブ加入者の分析と増加のための施策の設計はそれに当たります。企業はファンクラブ加入者を増やしたいと考えているとします。そのために、ファンクラブ加入者とそうでない人の特徴を分析し、比較してどうしたら、そうでない人をファンクラブ加入者にできるかということを考えます。DiCEを活用すると、各ユーザの何をどれだけ変動させることができたらファンクラブ加入者になる、もしくはファンクラブ加入者に近づけることができるということを理解できます。これは施策の設計におけるターゲットの選定、施策内容の決定、具体的な数値目標の設定の参考として活用が可能であることが考えられます。

上記のようにさまざまなサービスにおいてDiCEは有用です。特に、ユーザの現状に基づいた反実仮想サンプルを生成するため、行動を促すハードルは低くなるはずです。また、時間や費用と相談して、最適な意思決定につなげることはできるはずです。

DiCEを活用したサービス開発における課題と解決策

DiCEを活用したサービス開発について考えたとき、いくつかの課題が考えられました。その解決策として以下の点が考えられました。

  • 設計の工夫による現実に起こりうる反実仮想説明の生成
  • 複数の反実仮想説明に基づく施策の設計

これらについて説明、考察します。

設計の工夫による現実に起こりうる反実仮想説明の生成

DiCEは、各説明変数に対して「変動対象か否か」や「変動量の幅」の設定が可能です。これをサンプルのフィルタリングに用いることで、ドメインを考慮した実現可能性の高く、存在しうるサンプルを提供します。しかし、この時、以下が課題として挙げられます。

  • モデルは特徴に潜在的に存在するルールの解釈が難しいという点
  • モデルは特徴量間の関係を解釈が難しいという点
  • フィルタリングを厳しくした場合、モデルが解釈できない珍しく重要なサンプルを見逃してしまう可能性もあるという点

このような機械に解釈させるのが難しいドメインの制約については、DiCEに限らず機械学習を活用したサービス開発全体に発生しうる課題です。それゆえ、機械学習を活用したサービス開発において、機械学習ドメインとの付き合い方がとても重要であると考えられます。また、それは機械学習やDiCEの進展に任せ切ってしまう話ではなく、サービス全体で対応すべき課題であると考えています。DiCEを活用するサービス開発において、そもそも扱っているデータが何かの誤りで存在しないものだったり、DiCEでも設定しきれないドメインにおける制約を見逃してしまい、存在しないサンプルを提示しまうことも考えられます。以下ではそんなDiCEを活用したサービス開発について記述します。特に、ドメインを考慮した反実仮想クラスの設計による対応について紹介します。DiCEを活用したサービス開発において、ドメインを考慮した反実仮想サンプルの生成の方法を提案します。主な要素は以下の2つです。

  • 特徴間の関係を加味したクラスの設計
  • 特徴の扱い方としての値オブジェクトの採用

特徴間の関係を加味したクラスの設計

ある特徴量の値が、他の特徴量の値に影響を及ぼす場合があります。例えば、人によって、値の上限と下限が違う場合です。その場合、存在し得ない状態を提示してしまう可能性があります。そんな存在し得ない状態はドメインを考慮したクラスの設計により検知できます。

具体的なプログラムの例を紹介します。先に紹介した「タイタニック号の生存者予測」の例より、費用(fare)、年齢(age)のみを抜粋します。

例えば、以下のようなクラス(Sample)を用意しておきます。これは費用と年齢に制約を与えているため、生成されたインスタンスドメインにおける制約を満たすことになります。

import dataclasses

@dataclasses.dataclass(frozen=True)
class Sample:
    fare: float
    age: int

    def __post_init__(self):
      if self.fare <= 0 or 300 <= self.fare:
        raise Exception()
      if self.age <= 0 or 100 <= self.age:
        raise Exception()
      if self.age < 20 and 150 <= self.fare:
        raise Exception()

例えば、DiCEが以下のようなドメインにおける制約を満たすサンプルを生成したとし、これを表現するインスタンスを生成してみます。

sample = Sample(150, 20)
sample 

すると問題なく生成できます。これは反実仮想説明として提示しても良いでしょう。

Sample(fare=150, age=20)

逆に、DiCEが以下のようなドメインにおける制約を満たさないサンプルを生成したとし、これを表現するインスタンスを生成してみます。

sample = Sample(150, 19)

するとこのインスタンスの生成は失敗します。これによりサービスはあり得ないサンプルを説明として提示することを回避します。

---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-20-81b073a3d0d2> in <module>()
----> 1 sample = Sample(150, 19)

<string> in __init__(self, fare, age)

<ipython-input-17-cb29eedcdebf> in __post_init__(self)
     12         raise Exception()
     13       if self.age < 20 and 150 <= self.fare:
---> 14         raise Exception()

Exception: 

このように、特徴間の関係を加味したクラスの設計により、DiCEが生成した反実仮想説明に対して、現実に存在しうる反実仮想説明のみを抽出が可能であると考えられます。

特徴の扱い方としての値オブジェクトの採用

DiCEでは特徴に対して、変動の有無や変動の幅を設定できます。ただし、その設定もドメインに忠実に適応できない場合があると考えられます。それゆえ、設計でカバーすることも望ましいと考えられます。DiCEが生成した反実仮想サンプルに対して、現実に存在しうる反実仮想サンプルのみを抽出する方法として、値オブジェクトが考えられます。値オブジェクトとは、ドメイン駆動設計の要素のひとつです。簡単に説明すると、プログラムの中で、その値の振る舞いをドメインに基づくのもに制限するという設計です。上述したクラスの設計の例と同様に、これによりよりドメインに忠実に、異常な状態を検知できます。

具体的なプログラムの例を紹介します。

import dataclasses

@dataclasses.dataclass(frozen=True)
class fare:
    value: float
    def __post_init__(self):
      if self.value <= 0 or 300 <= self.value:
        raise Exception()

@dataclasses.dataclass(frozen=True)
class age:
    value: int
    def __post_init__(self):
      if self.value <= 0 or 100 <= self.value:
        raise Exception()

例えば、DiCEが以下のような全ての特徴量がドメインにおける制約を満たす値であるサンプルを生成したとし、それぞれを値オブジェクトとしてインスタンスを生成してみます。

fare = Fare(150)
fare

age = Age(20)
age

すると問題なく生成できます。これは実現可能性の高い説明として提示しても良いでしょう。

Fare(value=150)
Age(value=20)

逆に、DiCEが以下のような特徴量にドメインにおける制約を満たさない値を持つサンプルを生成したとし、それぞれを値オブジェクトとしてインスタンスを生成してみます。

fare = Fare(1000)
fare

するとこのインスタンスの生成は失敗します。これによりサービスはあり得ないサンプルを説明として提示することを回避します。

---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-33-4dbe6b6b540a> in <module>()
----> 1 fare = Fare(1000)
      2 fare

<string> in __init__(self, value)

<ipython-input-27-2319445039c4> in __post_init__(self)
      6     def __post_init__(self):
      7       if self.value <= 0 or 300 <= self.value:
----> 8         raise Exception()
      9 
     10 @dataclasses.dataclass(frozen=True)

Exception: 

このように、値オブジェクトを使うことで、DiCEが生成したサンプルに対して、現実に存在しうるサンプルのみをサービスとして扱うことが可能であると考えられます。

また、値オブジェクトは上述したクラスの設計と組み合わせて、クラスが持つプロパティ全てを値オブジェクトにすると効果的であると考えられます。例えば以下のような記述が考えられます。

@dataclasses.dataclass(frozen=True)
class Sample:
  fare: Fare = fare
  age: Age = age
  
  def __post_init__(self):
      if self.age.value < 20 and 150 <= self.fare.value:
        raise Exception()
fare = Fare(150)
age = Age(20)
sample = Sample(fare, age)
sample
Sample(fare=Fare(value=150), age=Age(value=20))

これらを実施することで、機械学習を活用したサービス開発における安全性・信頼性といった品質保証の担保にも貢献が可能です。

複数の反実仮想説明に基づく施策の設計

マーケティングにおけるファンクラブ加入者の分析と増加のための施策選定」のように多くの人を動かすこと、つまり複数の意思決定の促進を目的とする場合があります。前提として、人を動かすために、施策を設計します。施策の設計のために、反実仮想説明が有用であると考えられます。この時、DiCEが生成する反実仮想説明は一人を対象とした説明です。もちろん一人の反実仮想説明を確認して、特定の説明変数の値を変化させるためにどうすれば良いのかを考えることができると思います。しかし、複数人を動かそうとした場合は反実仮想説明を生成した後、施策を設計するまでにハードルがあることが考えられます。

まずは、各反実仮想説明を理解、解釈し、要約したうえで、施策を設計する必要があります。これを実現する手法(処理)が必要です。その理由はマーケティングにおいて、複数の意思決定を促進させるための各反実仮想説明への対応が現実的ではないことです。

上述した手法が確立されたとして、次は「その施策によって意思決定を促す人の数を最大化させる要望」も生まれます。例えば、ファンクラブ加入者をX人にすることをKGI(経営目標達成指標)とした時、それに向けて設定する施策が中間数値指標としてのKPI(重要業績評価指標)となります。このKPIを設定する際、最も効果がある施策は何なのかを設定することが難しいと考えられます。

それゆえ、「複数の反実仮想説明に基づく施策の設計」に関する議論が重要になってくると考えています。以下、「複数の反実仮想説明に基づく施策の設計」に関して私が考えていることを紹介します。まずは概念図を以下に示します。

複数の反実仮想説明に基づく施策の設計の概念図

予測対象1件に対して、m件の反実仮想説明を生成し、その合計をn件とします。これは施策の設計の参考になる情報であり、これらを包括して理解、解釈することで複数の反実仮想の実現につなげる施策を設計する際のヒントとなります。反実仮想×nの生成はDiCEにより実現が可能です。議論すべきは「反実仮想×nから施策を設計する処理」です。まずは反実仮想×nを要約する必要があります。

Minimum Viable Productに基づき実用最小限の手法を考えました。処理の流れを以下に示します。

  1. 反実仮想をクラスタリング(例:k平均法)
  2. クラスタをサマライズ(例:中央値)
  3. クラスタごとにサマライズされた情報を参考にしてマーケティング担当者が施策を設計

人が介入する必要はありますが、これにより「反実仮想×nから施策を設計する処理」がなんとか実現できるのではと考えています。これをベースラインにして、これらの方法については思案・調査中です。もし有識者の方から意見をいただけるととてもありがたいです。

反実仮想説明の関連研究

事実から反実仮想まで変動させるための状態遷移の考慮

事実から反実仮想を目指す場合、そのベクトルが直線でない場合も考えられます。具体的には「暗黙的に存在している状態」を経由する必要がある場合です。これを正しく理解しないと、事実を反実仮想まで変動させるのに想定以上の苦労がかかったり、そもそも不可能だったりする可能性が考えられます。それゆえ、事実から反実仮想まで変動させるための状態遷移を考慮した手法やサービスの設計が必要になります。このような背景から、事実から反実仮想まで変動させるための状態遷移を考慮した手法を提案する研究もなされています。

富士通研究所北海道大学は、実現の可能性と順序を考慮した適切な変化方法を見いだすことが課題であると捉え、反実仮想説明の考え方に基づき、事実を反実仮想に変動させる手順として、行動自体とその実現の順序を提示する方法を提案した。

pr.fujitsu.com

今回、両者による共同研究において開発した新技術は、反実仮想説明(注5)という考えに基づき、属性変更におけるアクションとその実施順序を手順として提示します。過去の事例の分析を通して非現実的な変更を避けつつ、属性値の変更がほかの属性値に与える因果関係などの影響をAIが推定し、それに基づいて実際に利用者が変更しなければならない量を計算することで、適切な順序、かつ一番少ない労力で最適な結果が得られるアクションの提示を可能としました。

上記の研究では「順序」を考慮しているという点により、実現可能性が高い反実仮想説明の生成されます。これにより、意思決定の促進が期待できます。

本記事のまとめ

本記事では、機械学習における反実仮想説明を生成するライブラリ“DiCE”を紹介しました。また、DiCEのサービスへの活用例を紹介しました。そして、DiCEを活用したサービス開発における課題と解決策について、以下2点を紹介しました。

  • 設計の工夫による現実に起こりうる反実仮想説明の生成
  • 複数の反実仮想説明に基づく施策の設計

関連研究でも紹介しましたが、ドメインへの対応が重要だと考えられます。設計で対応できることもあるので、ぜひ検討をしてください。そして、特定のシチュエーションにおいて施策の設計が必要とされると考えられます。今後、考えていこうと思います。

本記事は追記、修正する可能性があります。ご了承ください。もし意見、質問、指摘などがあれば、以下に記載されている連絡先に連絡をいただけるととても嬉しいです。

https://daikikatsuragawa.github.io/

*1:本記事ではCounterfactual Explanationsを反実仮想説明と和訳します。他にも反事実的説明、反実仮想的説明、反実仮想的な説明として和訳されている場合があります。