Ledge Tech Blog

We're the data scientists and AI engineers behind Ledge.

StreamlitでベイジアンABテストができるWebアプリを作成してみた

こんにちは。レッジのデータサイエンティストの松本です。今回が3回目の投稿になります。

レッジでは、クライアント先に常駐してデータ・ドリブンな課題解決に取り組んだり、ダイナミックプライシングアルゴリズムを受託開発したり、クライアント先へのBI導入の推進など、幅広くデータ利活用に関わる業務に取り組んでいます。

さて、今回はStreamlitというMLツールを使った、ベイジアンA/BテストができるWebアプリの作り方をご紹介します。 なお、本記事で紹介するA/Bテストは、Webページへの来訪者数とコンバージョン数をもとに実施するものと想定します。

実装コードはGitHubで公開しております。

Streamlitとは

Streamlitはデータサイエンティストや機械学習エンジニアがほんの数時間で美しくパフォーマンスの高いアプリを実装するためのフレームワークです。*1

TableauやPower BIなどの既存のBIツールでは、入力したパラメータをもとに複雑な処理を行って、その処理結果をインタラクティブに可視化することはできません。*2 しかし、Streamlitであれば全てがPythonコードで完結するので上記が可能になります。

ベイジアンA/Bテストとは

ベイジアンA/Bテストとは、乱数シミュレーションを使ったベイズ推論によってパラメータ*3の事後分布*4を推定することです。

ベイズ推論について荒く説明すると、 過去データがない状態では経験則によってパラメータを推定しますが、観測データが溜まっていくにつれてどんどん正確にパラメータを推定できていく手法 です。 もっとベイズ推論について知りたいという方はこちらで詳しく記載しています。

以下、従来のA/BテストとベイジアンA/Bテストを、データサイズ、リアルタイム性、統計知識、解釈性の観点で比較しました。

観点 従来のA/Bテスト ベイジアンA/Bテスト
データサイズ テストには十分なデータ量が必要 少量データでもテスト可能
リアルタイム性 常に十分な量のデータが必要なため厳しい 逐次合理性*5のため可能
統計知識 p値、信頼区間などの統計知識が必要 パラメータ分布の可視化よる直観的な理解が可能
解釈性 有意か有意ではないかの白黒の判断しかできない 柔軟に確率的な解釈が可能*6

上記比較表から分かる通り、圧倒的にベイジアンA/Bテストを利用した方が良さそうです。

ただし、1点だけベイジアンA/Bテストにはデメリットがあります。 それは、計算過程で多量のシミュレーションを必要とするため、計算コストが大きくなる点です。

とはいえ、現在のPCスペックではベイジアンA/Bテストの処理コストが問題にはなることは滅多にないので、ぜひこちらを活用すべきでしょう。

実装

Webアプリの実装コードを説明します。

環境

以下の環境で実装しています。

  • python == 3.7
  • streamlit == 0.61.0
  • matplotlib == 3.2.1
  • seaborn == 0.10.1

Webアプリ実装コードの説明

main.pyにWebアプリを実装していきます。 なお、Streamlit APIのメソッドの説明は公式ドキュメントが非常にわかりやすいので、本記事では省略します。 まず、必要なライブラリをインポートします。

import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
sns.set(font_scale=2)

次に、サイドバーを実装します。 サイドバーでは、A/Bテストに必要なA、Bそれぞれの来訪者数、コンバージョン数を入力できるようにします。

# sidebarにおけるパラメータ設定
st.sidebar.markdown('A/Bテスト対象のデータを入力してください')
visitors_a = st.sidebar.number_input('Aの来訪者数', value=100)
conversion_a = st.sidebar.number_input('Aのコンバージョン数', value=50)
cvr_a = conversion_a / visitors_a
st.sidebar.markdown(f'Aのコンバージョン率: **{"{:.1%}".format(cvr_a)}**')

visitors_b = st.sidebar.number_input('Bの来訪者数', value=100)
conversion_b = st.sidebar.number_input('Bのコンバージョン数', value=50)
cvr_b = conversion_b / visitors_b
st.sidebar.markdown(f'Bのコンバージョン率: **{"{:.1%}".format(cvr_b)}**')

以降、メインコンテンツの実装に入ります。 まず、サイドバーで入力した数値をテーブル形式で確認できるようにします。

# メインコンテンツ
st.header('A/Bテストアプリ')
st.markdown(r'''A/Bテスト結果の来訪者数とコンバージョン数を入力することで、一般的なA/BテストおよびベイジアンA/Bテストによる信頼性を判定できます。''')
st.subheader('テスト対象')
st.markdown(rf'''
    <table>
      <tr>
        <th>パターン</th><th>来訪者数</th><th>コンバージョン数</th><th>コンバージョン率</th>
      </tr>
      <tr>
        <td>A</td><td>{visitors_a}</td><td>{conversion_a}</td><td>{"{:.1%}".format(cvr_a)}</td>
      </tr>
      <tr>
        <td>B</td><td>{visitors_b}</td><td>{conversion_b}</td><td>{"{:.1%}".format(cvr_b)}</td>
      </tr>
    </table>
    ''', unsafe_allow_html=True)

次に、従来のA/Bテストによる結果を確認できるようにします。 p値によって出力メッセージが分岐するようにしています。

st.subheader('A/Bテスト')
st.markdown('一般的なA/Bテスト(統計的仮説検定)の結果です。分散不均等を仮定したt検定を使用しています。')
# t検定
a = np.zeros(visitors_a)
a[:conversion_a] = 1
b = np.zeros(visitors_b)
b[:conversion_b] = 1
res = stats.ttest_ind(a, b, equal_var=False)
# p値によって出力を変更
if res[1] <= 0.05:
  st.markdown(r'''
    <center><font size=7 color="#00B06B">95%の信頼度で有意差あり</font></center>
    ''', unsafe_allow_html=True)
elif res[1] <= 0.1:
  st.markdown(r'''
    <center><font size=7 color="#F2E700">90%の信頼度で有意差あり</font></center>
    ''', unsafe_allow_html=True)
else:
  st.markdown(r'''
    <center><font size=7 color="#FF4B00">有意差なし</font></center>
    ''', unsafe_allow_html=True)

次に、ベイジアンA/Bテストの結果を確認できるようにします。 事前分布を共役なベータ分布とすることで事後分布を解析的に求められるので、pymc3などを使ったMCMCマルコフ連鎖モンテカルロ法)を実行せずに済みます。 事前分布にベータ分布を使う理由の詳しい説明はこちらをご参照ください。

# ベイジアンA/Bテスト
# 参考: https://techblog.zozo.com/entry/bayesian_ab
# 事前分布をベータ分布とすることで、解析的に事後分布を求められる
st.subheader('ベイジアンA/Bテスト')
st.markdown('ベイズ推論を活用したA/Bテストの結果です。A, BそれぞれのCVRがどの程度信用できるのかを確認できます。')
# 事前分布のパラメータのα、βを指定
alpha_prior = 1
beta_prior = 1
# 事後分布の算出
posterior_A = stats.beta(alpha_prior + conversion_a, beta_prior + visitors_a - conversion_a)
posterior_B = stats.beta(alpha_prior + conversion_b, beta_prior + visitors_b - conversion_b)
# サンプリング数
samples = 20000
samples_posterior_A = posterior_A.rvs(samples)
samples_posterior_B = posterior_B.rvs(samples)
# A<Bとなる確率算出
prob = (samples_posterior_A < samples_posterior_B).mean()

最後にパラメータの事後分布と、AのコンバージョンがBのコンバージョンよりも低くなる確率を出力するようにします。

# グラフ設定
fig = plt.figure(figsize=(20,10))
ax = fig.add_subplot(111)
sns.distplot(samples_posterior_A, ax=ax, label='CVR of A')
sns.distplot(samples_posterior_B, ax=ax, label='CVR of B')
ax.set_ylabel('KDE', fontsize='xx-large')
ax.set_xlabel('CVR', fontsize='xx-large')
ax.set_title('distribution of CVR', fontsize='xx-large')
ax.legend(loc='upper right', fontsize='xx-large')
fig.tight_layout()

# 可視化
st.subheader('CVRの信用度の分布')
st.pyplot(fig)
# CVRがA<Bとなる確率を表示
st.markdown(fr'''
  <center><font size=7>CVRがA < Bとなる確率: {"{:.1%}".format(prob)}</font></center>
  ''', unsafe_allow_html=True)

これでWebアプリの実装コードの作成は完了です。

Webアプリの実行

terminalを起動し、上で作成したmain.pyと同一ディレクトリで以下のコマンドを実行します。

streamlit run main.py

実行後、http://localhost:8501にアクセスすると、実装したWebアプリを操作できます。

Webアプリのデモ

Webアプリのデモを録画し、youtubeにアップロードしました。 サイドバーの入力値に応じて、A/Bテストの出力結果が変わっていることが分かります。

表示されているメインコンテンツ上部の従来のA/Bテスト、下部のベイジアンA/Bテストの結果について説明します。

従来のA/Bテストの結果

サイドバーで入力したA、Bの来訪者数とコンバージョン数をもとに分散が異なることを仮定したt検定(Welchの方法)を実行し、以下のようにp値に応じて出力結果を出し分けています。

  • p-valueが0.05以下の場合は「95%の信頼度で有意差あり」と表示
  • p-valueが0.05より大きく、0.10以下の場合は「90%の信頼度で有意差あり」と表示
  • 上記以外の場合は「有意差なし」と表示

ベイジアンA/Bテストの結果

サイドバーで入力したA、Bの来訪者数とコンバージョン数をもとに、ベータ分布を事前分布としたベイズ推論を実行し、事後分布のサンプリング結果を表示しています。 横軸はコンバージョン率であり、縦軸はその信用度(密度)です。*7

さて、個々の分布に着目すると、パラメータ推定結果について分布が尖っている場合はある程度信用できますが、分布がなだらかな場合は信用できません。 また、2つのパラメータ推定結果の差に着目すると、分布がほとんど重なり合っていない場合はある程度信用できますが、分布の大部分が重なり合っている場合は信用できません。

画面下部では、Aのコンバージョン率がBのコンバージョン率よりも低くなる確率を表示しています。

最後に

今回、Streamlitを使ってWebアプリを実装してみて、正直かなり感動しました。

従来こういったシミュレーションを可視化するためには、Flask等を使ってフロント側の実装も考慮する必要がありました。 そのため、データサイエンティストが自分で全て実装するのはかなりハードルが高かったです。

しかし、 Streamlitを使うことで今回説明したような簡単なフロント実装までならデータサイエンティストが担当できるようになります。

PoC(概念実証フェーズ)であればデータサイエンティストや機械学習エンジニアだけで完結できるようになる日もそう遠くないのかもしれません。

参考資料

*1:公式ページに記載されています。

*2:TableauではTabpyというフレームワークを使うことでPythonと連携が可能ですが、入力/出力処理の自由度がかなり小さいです。

*3:ここでいうパラメータとは、コンバージョン率(CVR)のことを指します。

*4:事後分布とは、観測結果が与えられた上でのパラメータの確率分布のことを指します。事後分布を求められれば、来訪者数が100人、コンバージョン数が30のときCVRが25%〜35%になる確率は70%といった確率計算ができます。

*5:ベイズ推論の逐次合理性とは、過去データをすべて保持せずとも、直近のパラメータ推定結果と最新の観測結果のみからパラメータの推定が可能ということです。

*6:AのコンバージョンがBのコンバージョンより高くなる確率は60%などという解釈も可能になるということです。

*7:ここで、従来のA/Bテスト(頻度主義的な観点)では「信頼度」、ベイジアンA/Bテスト(ベイズ推論の観点)では「信用度」というように言葉を使い分けています。いったんは、ベイジアンA/Bテストでは事後分布のサンプルをもとにしたシミュレーション結果から確率的な解釈をするので、従来のA/Bテストとは全く別の観点なのだと思っていただければ良いです。詳しくはこちらをご参照ください。