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

# 概要
どこもかしこも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とか)]

```py: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の場合は

```php:sample.php
$this->load->view('index.php');
```
の直前になります。

```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を正しく設定した方が良いと思います。

```py: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.施策達成時のポイントを登録
施策達成時は様々なパターンがあると思います。指定ページへの遷移や経過時間やユーザ登録などです。
その場合には報酬を設定してあげましょう。

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

```py: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からページ名を取得します。

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

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

```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

```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.各テンプレートの点数を確認する場合
各テンプレートの表示回数と報酬の合計値を見ることができます。

```py: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)
```