高速に仮説を検証するために ~A/Bテスト実践~

会員事業部エンジニアの佐藤です。クックパッドでは日々データと向き合い、データを基にした施策作りに関わっています。

Cookpad TechConf 2018で新井が発表した「クックパッドの "体系的" サービス開発」の中で、社内で仮説検証を行う際に使われているツールについて触れている箇所がありました。 本記事ではそのツールと実際の取組み方について、実際の流れを踏まえながらもう少し詳しく説明していきます。

仮説検証

仮説検証は以下のフローで進んでいきます。

  1. 前提条件を確認する
  2. 検証の設計をする
  3. 各パターンの機能を実装する
  4. 各パターンにログを仕込む
  5. デプロイ後の監視
  6. 検証結果の振り返りとネクストアクション

小さく・手戻りなく・高速な検証を行うためには手を動かす前の段階、上記フローにおける1・2のステップが重要となります。

具体例として「朝と夜はプレミアム献立の需要が高まる」という仮説の検証フローを見ていきます。 これは献立プレミアム献立のアクセス分布をみると朝と夜にもアクセスが増加していたことから得た仮説です。

前提条件を確認する

下記の2つの点について合意が取れている必要があります。

  • 確かめたい価値(仮説)が明確化されている
  • その検証にA/Bテストを用いる

今回は話をわかりやすくするため「朝と夜はプレミアム献立の需要が高まる」という仮説が既にたっており、それをA/Bテストで確かめるという流れになります。ですが実際にはそもそも仮説が検証可能な状態にまで明確化されていないといった状況が考えられます。 手段を具体化する前にチームで方針決定・合意形成がなければ検証は始まりません。 ごく当たり前のように感じますが、いつでも振り替えられるよう土台を固めておくことが大事です。

検証の設計をする

仮説を確かめるためにA/Bテストの設計を行います。 まず、仮説を確かめるために何と何を比較するか考えます。 この記事で例題として扱う仮説は「朝と夜はプレミアム献立の需要が高まる」という仮説でした。 前提知識として人気順検索とプレミアム献立では人気順検索の方が需要があり、単純に人気順検索の枠をプレミアム献立に差し替えて比較すると前者が有効であることがわかっているとします。 よって、今回は「普段は人気順検索での訴求に使っていた枠を朝と夜の時間にだけプレミアム献立に切り替える」施策に取り組みます。

  • パターンA: 人気順検索(通常)
  • パターンB: 朝と夜だけプレミアム献立、それ以外の時間帯は人気順検索

パターンA パターンB
f:id:ragi256:20180221172307p:plain f:id:ragi256:20180221172324p:plain

この時、対象も出来る限り明確にしておきます。 今回はサイト内の該当部分を訪れたプレミアム会員以外の全てのユーザーを対象とすることにします。 また、検証の結果がどうなったかによって次にとるアクションまで決めます。

次にA/Bテストで監視・比較をするKPIも設定します。今回はプレミアムサービス会員の転換率(CVR)をKPIとします。 KPIが決定したことで同時に具体的なログの測定箇所と測定内容も決定します。 今回はそれぞれのパターンにおける訪問ユーザー数とプレミアムサービスへの転換数が必要となります。

ここで検証期間の見積もりを行うため、必要となるサンプルサイズを算出しておきます。 サンプルサイズの算出には「A/B両パターンの平均値」と「求める確度」を事前に決めておく必要があります。 言い方を変えると「どれだけの改善を確認したいのか」と「どれだけ偶然を排除したいか」という点です。 統計学では前者を効果量、後者を有意水準と検出力と呼びます。 詳しくは「仮説検証とサンプルサイズの基礎」を御覧ください。 これらを基にしてサンプルサイズを計算し、サンプルサイズと現状のUUから今回の仮説検証に必要とする日数を求めます。

そして検証設計の最後に、検証期間が経過した時点でどういう結果だったらどうするということを決めておきます。 実際に手を動かす前に、最終的な結果を大雑把に場合分けして次の行動を決めておくことが手戻りの防止につながります。

両パターンを実装する

パターンAには従来通りの挙動を、パターンBには時間帯によって枠内表示が変わるように実装をします。 この際、プロトタイプ開発用プラグインである「Chanko」とChankoのA/Bテスト用拡張である「EasyAb」を使うことで下記のように書くことができます。

パターンの制御を行うChanko内部のコントローラー

module TimeSlotPsKondate
  include Chanko::Unit
  include EasyAb

  split_test.add('default', partial: 'default_view')
  split_test.add('time_slot_ps_kondate', partial: 'time_slot_ps_kondate_view`')

  split_test.log_template('show', 'ab_test.time_slot_ps_kondate.[name].show')
  split_test.log_template('click', 'ab_test.time_slot_ps_kondate.[name].click')

  split_test.define(:card) do
      next run_default if premium_service_user? # プレミアムサービスユーザーは対象としない
      ab_process.log('show') # 訪問ユーザー数カウント用ログ
      render ab_process.fetch(:partial), time_slot: target_time?
  end
end

パターンの差し替えを行うChanko外部のviewファイル(haml)

-# 対象となるviewの書かれているファイル

= invoke(:time_slot_ps_kondate, card) do
  -# 差し替え部分 
end

これらに加え、パーシャルとして必要となる default_card.hamltime_slot_ps_kondate_card.hamlとCSSを追加すれば実装は完了です。 既存コードとの接点はinvokeメソッドの部分のみであるため、A/Bテストのon/offはごくわずかな変更で制御することができます。 Chankoは既存のコードと切り離された場所に置かれるため、検証の後始末もスムーズに行えます。

このように、ChankoとEasyAbを使うことで必要最低限のコードのみで検証を行えます。

両パターンにログを仕込む

今回はCVRをKPIとして追いかけていく必要があります。 不要なログを大量にとっても仕方がないのでログは必要最低限に留めるべきですが、後になって「あのログをとっておけばよかった」と後悔しても遅いため必要なログに抜け漏れがないよう列挙しておきます。 今回は該当部分のページに訪れた人(show)とプレミアムサービス枠をクリックした人(click)のログを取ります。 実際にCVRを取るには前者だけで十分なのですが、後者のログもとっておくことでCTRを算出できるようになり、クリエイティブに問題があったかどうか振り返るのに役立ちます。

A/Bテストに限らず一時的なログをササッと仕込みたい場合、社内ではKPI管理ツール「Hakari2」を使っています。 Ruby・JavaScript・HTMLそれぞれで利用することができます。

Ruby

Hakari2Logger.post("ab_test.hakari_log.ruby.A", user: user, request: request)

JavaScript

hakari2.post(['ab_test.hakari_log.javascript.A'])

HTML(hamlで書いた場合)

= link_to xx_path, class: 'track_hakari2', data: { hakari2_keywords: 'ab_test.hakari_log.html.A' }

このようにしてクライアント側でセットされたログは共通ログ基盤Figlogを経由し、最終的にDWHチームの管理するRedshift内へ格納されます。 A/Bテストを開始した後、ログの監視や分析を行う時にはこのログを他のデータと組み合わせて利用します。 今回の検証で必要となるCVRはshowのログとプレミアム会員登録のログを結合することで求まります。

デプロイ後の監視

A/Bテスト用の実装をデプロイし、公開した後に実装やログ取得にミスがないか確認をする必要があります。 日次の集計結果などはcookpadの管理用アプリケーション「papa」上のダッシュボードで確認できます。 検証期間後の最終的な検証結果もこちらで確認します。

f:id:ragi256:20180221172442p:plain

検証結果の振り返りとネクストアクション

「仮説検証の設計をする」の段階であらかじめ決めておいた目標サンプルサイズに到達したところで検証を終えます。 その時点で再度ダッシュボードを確認し、今回の施策の結果がどうであったかを結論づけます。

ダッシュボードでは集計値だけでなく、数値をもとにして描かれた確率分布や計測期間中の推移を見ることができます。 これらをみることで有意差がありそうかどうか、特定日時のイベントによる影響がないかどうかを確認します。

確率分布 時系列変化
f:id:ragi256:20180221172500p:plain f:id:ragi256:20180221172509p:plain

このステップでは知見を得るための考察や議論を行いますが、よほど想定外の結果にならない限り「検証の設計」で決めた方針に従い次の行動を決定します。 この施策に関しては当初想定していた量の改善が得られなかったため、仮説の正しさを証明する結果が得られませんでした。 この仮説は献立とプレミアム献立のアクセス分布から得た仮説でしたが、その仮説を得る過程をアプローチ方法から見直す必要があります。

まとめ

クックパッドで高速に仮説を検証するために普段行っている作業についてお話しました。 6ステップに分けて説明をしてきましたが、「前提条件の確認」と「検証の設計」までがキチンとこなせていればその後は特に考えることなく実行することができます。 このサイクルを回す作業に慣れていくことで、実際に手を動かす作業よりもサービス改善のためにどうするべきか頭を使う作業へ労力を割くことができるようになります。

また、今回はwebでのA/Bテストの説明をしましたが、iOS/Androidでも同様にA/Bテスト用のツールを利用することで手軽に仮説検証を行うことができます。

クックパッドでは日々このように各種ツールを利用してサービス改善のサイクルを高速にまわしています。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/