Skip to main content
rails A/B testing
by split gem
Ryuma Tsukano
A/B testingとは

●   WebサイトでUIのパターンを複数準備
●   同時期にユーザー毎にパターンを切替え
●   コンバーション率を計測
●   より効果的な物を選択
典型的なA/B testingのイメージ

User毎にRandomに2つのUIを切り分け




         30%      60%
         がclick   がclick


  LOSE                     WIN! => 採用
やや強引に言葉を整理

広義な意味のA/B testing = split test                                               ※人によって定義違うが


  ○ A/B test (marketing/lean startup etc)
    ■ 特定のUIを切替                                                                             ※今日話すsplitが
    ■ 直列にBefore/Afterを比較                                                                   どちらかというと
                                                                                           こっち



  ○ split test(design/UXがどちらかというとこっち)
    ■ (主に)Pageレベルの切り分け
    ■ 並列に複数のpatternと比較

  ○ 多変量テスト
    ■ 要素組み合わせ
    ■ 複数patternを比較
                     http://www.massivesplash.com/Blog/Entry/is-there-a-difference-between-split-test-and-ab-test
なぜA/B testing?

● 静的なWebサイトだけの話?

● ECサイト/ソシャゲー等Design重視site向け?

● Designの微かな差の反応を確認したいだけ?

● Designer/UX系の人たちの話で、開発には関
  係無い?

=> 開発現場の話を。
現場で

● 1)機能単位の効果を知りたい。
 ○ 機能改修したが、本当に改善?
 ○ PV/UU見れば反応分かる。
   ■ 但し、これらは沢山の要因で動く。


● 2)数値の正しい目安を知りたい。
 ○ Click数等で機能単位で数値取れるが、時系列の異なる
   比較になる。
 ○ 時系列の違う数値の比較は意味があるのか?
   ■ ex.先月のclick数30万=>今月のclick数50万
   ■ 実は機能改修以外の他の要因で数値が上がってる
       だけだったりして...
そこでA/B testing

1)UI/Page単位でテストする事で
 ○ 機能毎の効果が明確になる


2)同じタイミング同士の結果を比較する事で
 ○ より正しい数値比較が可能になる
A / Btestingの最大のメリット



(画面変化を伴う )

機能改修毎に、
明確な成績が付く事
A / B testing導入すると…

● 実は、***機能改修は何も改善してなかった
● 実は、コンバーション変化無かった。




=>等々、諸々のエグい事実が丸裸に!
(なるかも)
A / B testingの方法

外部サイト
● google / adobe / sitecatalyst / optimizely等
=>多くが、静的ページのためのもの

作り
● JSでtrack埋め込み
● URLを切り替えて集計
● JSで要素毎に表示切替等
外部サイトからのA / B testing

●   コンバーションのタイミングに限界
●   A / B の切り替えが煩雑
●   JSで勝手にhtmlすり替わる?物もある
●   サーバーsideの手から離れるので扱いにくい
●   そもそも決定打が無い?
●   綺麗なグラフ有り難いが、殆ど有料


=> そこで、ruby gemでA/B testing
ruby gemのA/B testing

● A/Bingo : rails2時代のstandard?
  ○ 公式でrails3無い?非公式forkあるが、動作謎
● vanity : rails2/3切替り時のstandard?
  ○ 何故か最近のrails3で動かず、ググると同じerrorで困っ
    てる人いて、解決策無かった
  ○ そもそも、レポジトリ1年位更新されてない?
● split : rails3で動く。開発は進行中。


=>という事で、split
split
https://github.com/andrew/split
1. 簡潔な記述
        ■ 簡潔にA/B testingを記述できて気持ち良い!
2. pattern管理
        ■ sessionでpatternを管理してて頼れる!
3. Redis
        ■ Redisに結果を保存してて、とても今時!
4. dashboard
        ■ dashboardで非開発者と情報共有出来る!
5. bandit algorithm
        ■ bandit algorithmで実験の幅が広がる!
1)簡潔な記述

A/B testing

    ab_test(テスト名, 旧パターン, 新パターン)

コンバーション

  finished(テスト名)

これだけ。
scaffold apples
#app/views/apples/index.html.erb

<% ab_test("link-abtest", "NEWNEW!", "新規に作成!") do
|link_label| %>
  <%= link_to link_label, new_apple_path %>
<% end %>

#app/controllers/apples_controll

def new
 finished("link-abtest")
 ・・・
ブラウザ

ある人には「NEWNEW!」
ある人には「新規に作成!」
こんな書き方も出来る
#app/views/apples/index.html.erb

<% ab_test("link-abtest", "A", "B") do |pattern| %>
 <% if pattern == "A" %>
  ・・・
 <% elsif pattern == "B" %>
  ・・・
 <% end %>
<% end %>


=>柔軟に切り分けれる。
ちなみに開発時は?

パラメータでパターンを指定できる
● ?テスト名=パターン名

=>これで強制的にパターンを表示させて確認
ab_testの記述

設定ファイルにも書ける
# cofig/initializer/split.rb
Split.configure do |config|
 config.experiments = {
   "ex1" => { :alternatives => ["a", "b"], :resettable => false},
   "ex2" => { :alternatives => ["X", "Y"]}
 }
end


● 書けるoptionが増える(後述)
● 複数のA/Btestingやる時に混乱無さそう。
補足

ab_test
   ○   パターンは幾つでも追加可能
   ○   controller/viewどこでも書ける
   ○   hashで重み付け可能(新パターンを5%のuserに等)
   ○   今後、メトリクス追加できるらしい(今は無理)
finished
   ○ controller/viewどこでも書ける(今後jsも)
   ○ Client側のreset書き分けれる(reset => true/false)
     ■ コロコロUI変わると不自然なので、基本はresetせず
         同パターンを表示すべき
     ■ 広告等はresetしてもいいかも。CaseByCase
   ○ 今後、finishedも複数分けれるらしい(今は無理)
2)patternの管理

sessionにpatternが入ってる(設定でcookie可)

 "split"=>{"hoge:2"=>"ptnB",
           "hoge:2:finished"=>true}
2つの情報
● テスト名:version=>パターン
● finished=>true
パターン

 "split"=>{"hoge:2"=>"ptnB",
           "hoge:2:finished"=>true}

テスト名:version=>表示パターンを記録
 ○ versionはdataを(サーバー側)Resetすると繰上がる
 ○ 1度表示したらクライアント側のResetしない限り、同じ物
   を表示し続ける(しっかりしてる)
 ○ Resetについては、後述
Clickの記録

  "split"=>{"hoge:2"=>"ptnB",
            "hoge:2:finished"=>true}

finished=>true
  ○ ゴール(finished)にたどり着いたらつくFlag
  ○ 既にカウントしたら2回以上カウントしない(これもクライア
    ント側のResetしない限り)
2つのReset

● クライアント側のReset
 ○ 方法:finished (reset => true)
   ■ ※defaultなのでreset指定しないとreset => trueになってる
 ○ 変わる事:sessionのクリア。違うパターンが表示される
   可能性有り。再度ゴールすると1count。


● サーバー側のReset
 ○ 方法:Dashboard(後述)からReset
 ○ 変わる事:testのversion繰り上げ


まだ、開発中でごちゃごちゃしてる。
はまる所

開発時、reset/versionでハマる。
 1.   testX:1でptnAを表示
 2.   表示内容修正
 3.   サーバ側Resetして再開(testX:2)
 4.   split testのsessionとしてカウントされず。ptnAのまま固
      定。あれ?動いてない?


=>SessionClearで解決。
  ○ v1でptnAを表示したのでclient側resetしないと、v2でも
    ptnAを表示し続ける
=>Resetの扱いはご注意を
3) Redis
普段使ってるRDBとは別にRedisで管理
           conversion数/         普段のデータ   パターン/完了flag
           session数

                                            Session/cookie
               Redis             RDBMS      (設定次第)




                       Server
                       (app/
                       web)



=>RDBへの影響を心配しなくて済む
Redis とは

KVS(in memory/後でdiskにも書込)
  ○ read/writeいずれもnosqlでTOPクラスのspeed
  ○ データ構造がprogramming言語ちっく
  ○ 設定/install が簡易



事例
  ○ github/instagram/Flickr/ニコ生/ソシャゲーetc...
  ○ 丁度、web+db press vol.73(2013/3)でも特集
Redis install / 設定まで(mac)

install
   ○ $ brew install redis


実行
   ○ $ redis-server




おしまい。
SplitのRedisデータ構造

hogeテストでptnA/ptnBを試した場合

 1.   split:experiment_start_times =>test開始時刻
 2.   split:experiments =>test名
 3.   split:hoge =>"ptnA","ptnB"
 4.   split:hoge:ptnA => 全session数/conversion数
 5.   split:hoge:ptnB => 同じ。それぞれの値を保持。



–>シンプル。
自分でも管理画面やグラフ画面作れそう。
splitのRedisデータ上の注意

以下のデータは辿れない(記録してない)
 ○ 1countの時系列データ(いつ押した?)
 ○ 1countのUser情報(誰が押したか?)




splitに無い欲しい情報があれば、
別の仕組みを用意する必要ある
Q&A
Q:Redis落ちたら?
 ● Split側でcatchする仕組み有り。
   ○ config.db_failover_on_db_error = proc{|error| ... }
 ● その結果、アプリはエラーにならない。
   ○ logはくだけ。
   ○ ちなみに、その際は一番目のpatternを表示


Q:冗長化は?
 ○ Redisの設定fileにslaveOf書くだけでReplication
 ○ 本家が自動failoverの管理サーバ開発中
   ■ redis sentinel
4)dashboard
https://localhost:3000/split に、皆で見れるdashboard




gem 'split' , :require => 'split/dashboard'
#config/routes.rb
mount Split::Dashboard, :at => 'split'
dashboardの意味



列の説明
 ○   ALTERNATIVE NAME : 選択肢名
 ○   PARTICIPANTS : セッションの数
 ○   NON-FINISHED : まだゴール到達してない数
 ○   COMPLETED : ゴール到達数
 ○   CONVERSION RATE : コンバーション率
 ○   CONFIDENCE : コンバーション率の信頼性
 ○   FINISH : Use thisを押すと、同pattern固定
信頼性が95%を上回ったらほぼ確定。
ラベルの意味




 ● control : 比較元
 ● CONVERSION RATEの+50:0% : 比較元
   に対して何%改善したか?の割合
そもそもの数値の話

Q : なんか異常にcompletedが少ない気が?
   ○ Reset次第で数値の意味合いが違うのでご注意を


● finished(reset => false) :
   ○ 1userが複数click => completedは1
   ○ completed≒セッション数


● finished(reset => true)
   ○ 1userが複数click => completedは複数回
   ○ completed≒click数
Tips
Use thisは使わなくてもいいかも...
  ○ 結果がredis見ないと分からず保守しづらい
  ○ ab_testのコードがソース中に沢山あると、内容が掴み
    辛くなりそう。
  ○ 突然、Redisデータ無くなったら...恐ろしい事に..


=>結果が明らかな時に
  一時的にUse thisしておいて、
  すぐab_testのソースを消した方がいいかも
5)bandit algorithm
実験と利益を最大限にするための戦略

● Aに価値がある時はなるべくAを表示
● Bに価値がある時はなるべくBを表示


現状のdataから学習して、
出現割合に融通を利かせる
splitでの実装

設定ファイルでabtest記述。
# cofig/initializer/split.rb
Split.configure do |config|
 config.experiments = {
   "ex" => { :alternatives => ["X", "Y"],
              :algorithm => 'Split::Algorithms::Whiplash'}
 }
end




これだけ。
やってみた。

AとBの2patternで、ひたすらBだけ押しまくる
  回数   1    2    3    4    5    6    7    8    9    10

  表示   A    B    B    A    A    A    A    B    A    B

  回数   11   12   13   14   15   16   17   18   19   20

  表示   A    B    B    A    A    B    B    A    B    A

  回数   21   22   23   24   25   26   27   28   29   30

  表示   B    A    B    B    A    B    B    B    B    B

  回数   31   32   33   34   35   36   37   38   39   40

  表示   B    A    B    B    B    B    A    A    B    B


=>後半、Bが主流に!(もっと回数多いと分かり易いかも )
何が嬉しいのか?

● A/B testing実験期間中も利益を最大化
  ○ win/loseがある程度分かっているなら、その割合に応じ
    てwinnerをいっぱい出した方が得。


● continuous experiments出来る
  ○ 要するに、A/B testingで放置
  ○ ex. ハローウィンはオレンジと黒、クリスマスは赤白が好
    まれる
  ○ ex. 12,1月は忘年会/新年会探す社会人、3,4月は引越
    しする大学生と、利用する層がコロコロ変わる
  ○ 季節要因で結果がコロコロ変わってもカバー可能
まとめ

A/B testing
  ○ 機能毎の成績表。
  ○ 見てはいけないものが丸裸に!(なるかも)


Split
  ○     記述が死ぬ程簡単
  ○     sessionでばっちり管理
  ○     Redisで楽チン。構造もsimple
  ○     dashboardで非開発者と情報共有出来る
  ○     bandit algorithmで色々応用出来そう
おしまい