3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Jubatusに実装されたバンディットアルゴリズムを使用して高速A/Bテスト実施

Posted at

概要

どこもかしこもA/Bテストを行っていると言ってますが、それってAテストBテストじゃないですか?
Aパターンを測定した後にBパターンを測定するのはA/Bテストとは言い難いと思います。
同時期に行っていないというのが大きな要因です。
AパターンBパターンを同時期に測定しているところは稀だったりします。
バンディットアルゴリズムを使えばA/Bテストはおろか、A/B/Cだろうが、A/Bで開始して途中からC/Dパターンを追加だろうが簡単に行うことができます。

1.テンプレートの登録

まず始めにAパターン、Bパターンのテンプレートを用意します。
このテンプレートというものはMVCフレームワークのV部分だと思ってください。
このテンプレート名をJubatusのバンディットアルゴリズムに登録しておきます。

python set_template.py [テンプレートファイル名(index_a.phpとか)]

set_template.py
#!/usr/bin/env python
# coding: utf-8

host = '127.0.0.1'
port = 9199
name = 'A/B TEST'

import sys

import jubatus
from jubatus.common import Datum

def register_arm(client, arm):
    client.register_arm(arm)

if __name__ == '__main__':
    argvs = sys.argv
    argc = len(argvs)
    if (argc != 2):
        print 'Usage: # python %s templateName ' % argvs[0]
        quit()
    template_name = argvs[1]

    client = jubatus.Bandit(host, port, name)
    register_arm(client, template_name)

2.試したいテンプレートの選択

テンプレートを選択する直前でJubatusに問い合わせます。
Codeigniterの場合は

sample.php
$this->load->view('index.php');

の直前になります。

sample.php
exec("python ./select_template.py" , $ret1); #index_a.phpとかを返してくれることを期待している
$this->load->view(trim(implode('', $ret1)); #index_a.phpを呼ぶことを想定

とすると、Jubatusが返してくれたテンプレートを読むことができます。
テンプレート名が正しくPHPファイル名になっていれば表示されると思います。
Pythonを実行することになるので負荷には注意してください。Exceptionを正しく設定した方が良いと思います。

select_template.py
#!/usr/bin/env python
# coding: utf-8

host = '127.0.0.1'
port = 9199
name = 'A/B TEST'

import sys

import jubatus
from jubatus.common import Datum

def get_template(client, player):
    reward_point = 0.0 #表示ポイント 表示時は0ポイントとした場合
    arm = client.select_arm(player)
    client.register_reward(player, arm, reward_point ) #取得した時にポイントを付与しておかないと同じarmばかり引くため
    return arm

if __name__ == '__main__':
    argvs = sys.argv
    argc = len(argvs)
    user_id = u'none'
    if (argc == 2):
        user_id = argvs[1]

    client = jubatus.Bandit(host, port, name)
    print get_template(client, user_id )

3.施策達成時のポイントを登録

施策達成時は様々なパターンがあると思います。指定ページへの遷移や経過時間やユーザ登録などです。
その場合には報酬を設定してあげましょう。

sample.php
exec("python ./set_reward.py index_a.php"); #index_a.phpを表示した時に施策達成
set_reward.py
#!/usr/bin/env python
# coding: utf-8

host = '127.0.0.1'
port = 9199
name = 'A/B TEST'

import sys

import jubatus
from jubatus.common import Datum

def set_reward(client, player, arm):
    reward_point = 1.0 #施策達成時のポイント 施策達成時は1ポイントとした場合
    client.register_reward(player, arm, reward_point )

if __name__ == '__main__':
    argvs = sys.argv
    argc = len(argvs)

    if (argc != 2):
        print 'Usage: # python %s templateName [userId]' % argvs[0]
        quit()
    template_name = argvs[1]

    user_id = u'none'
    if (argc == 3):
        user_id = argvs[2]

    client = jubatus.Bandit(host, port, name)
    set_reward(client, user_id, template_name)

流れ

1.テンプレートの登録

サーバのコマンドラインから実行します。
python set_template.py index_a.php
python set_template.py index_b.php

当たり前ですが最低2つは登録してください。

2.表示(Codeigniterの場合)

表示ページ内でJubatusからページ名を取得します。

sample.php
exec("python ./select_template.py" , $ret1); #index_a.phpとかを返してくれることを期待している
$this->load->view(trim(implode('', $ret1)); #index_a.phpを呼ぶことを想定

3.達成時

達成ページの表示時に報酬を設定します。
index_a.php は引数などで持ち歩いている必要があります。

sample.php
exec("python ./set_reward.py index_a.php"); #index_a.phpを表示した時に施策達成

4.途中でパターンを追加

A/Bテストが落ち着いてきたら次の施策を打ちたいことだと思います。
ページを作成した後にそのページのテンプレート名をJubatusに登録するだけで配信され始めます。
python set_template.py index_c.php
python set_template.py index_d.php
python set_template.py index_e.php
AとBでどちらが効果が高かったかはJubatusが勝手に判定してくれます。
その後はA/B/C/D/Eの中で効果が高いものを表示し始めます。

5.削除したいテンプレートが出てきた場合

Jubatusから削除します。
計測をやり直したい場合も、Jubatusから削除後登録するとリセットされます。

python delete_template.py

delete_template.py
#!/usr/bin/env python
# coding: utf-8

host = '127.0.0.1'
port = 9199
name = 'A/B TEST'

import sys

import jubatus
from jubatus.common import Datum

def delete_arm(client, arm):
    client.delete_arm(arm)

if __name__ == '__main__':
    argvs = sys.argv
    argc = len(argvs)
    if (argc != 2):
        print 'Usage: # python %s templateName' % argvs[0]
        quit()
    template_name = argvs[1]

    client = jubatus.Bandit(host, port, name)
    delete_arm(client, template_name)

6.各テンプレートの点数を確認する場合

各テンプレートの表示回数と報酬の合計値を見ることができます。

get_info.py
#!/usr/bin/env python
# coding: utf-8

host = '127.0.0.1'
port = 9199
name = 'A/B TEST'

import sys

import jubatus
from jubatus.common import Datum

def get_arm_info(client, player):
    return client.get_arm_info(player)

if __name__ == '__main__':
    argvs = sys.argv
    argc = len(argvs)
    user_id = u'none'

    client = jubatus.Bandit(host, port, name)
    print get_arm_info(client, user_id)
3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?