Pythonで機械学習 - Deployment


BIツールTableauからPythonの機械学習モデルを呼び出す仕組みであるTabPy Serverへのデプロイを説明します。

# TabPy Server

## インストール

以下のサイトを参考にTabPy Serverをインストールします。  
https://github.com/tableau/TabPy/blob/master/server.md

TabPy Serverは以下の2つの方法でインストールが可能です。
- セットアップスクリプトでAnaconda環境ごとインストールする
- 既存のPython環境にpipでインストールする

TabPy Serverをインストールすると、あわせてTabPy Clientもインストールされます。Clientは、Python環境からTabPy Serverへモデルをデプロイしたり、テスト用にTabPy Serverの処理を呼び出したりするのに使います。

## サーバの起動

インストール後は、TabPy Serverのインストールフォルダにあるstartup.shまたはstartup.batによりTabPy Serverが起動できます。デフォルトではTabPy Serverはポート9004でListenします。

## TabPy Serverの仕組み

### TabPy Serverの使い方
Tabpy Serverは主に2つの使い方ができます。
- Evaluate : Pythonのコードをパラメータとして渡し、TabPy Server上で実行して結果を返却する
- Query : あらかじめ関数をエンドポイントとして登録しておき、呼び出し時にパラメータを渡して実行結果を返却する

教師あり機械学習による予測モデルを使う場合、あらかじめ予測モデルを構築するため、Queryの方を使います。
一方、クラスタリングなど教師なしモデルの場合、Evaluateでアドホックにコードを実行できます。

![Tab.png](https://qiita-image-store.s3.amazonaws.com/0/186483/4b0a6512-52d1-56d1-63d2-e1e352e1c1d7.png)
)

### TabPy Serverの操作
TabPy Serverの操作はTableauからの接続の他、HTTPによるRESTでの呼び出し、またはPythonからTabPy-Clientライブラリ経由での操作が可能です。

## 教師ありモデルによる予測

### 関数のデプロイ

教師あり学習の予測モデルの場合、事前に学習済みの予測モデルを構築し、TabPy Serverにデプロイしておきます。以下に、その手順を説明します。

ライブラリをインポートします。


```python
import tabpy_client
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
```

データを読み込みます。


```python
train = pd.read_csv("train_prep.csv")
```

学習のための説明変数と目的変数を準備します。


```python
X_train = train[["Pclass","SexInt","AgeFillNa","IsAlone","FareFillNa","EmbarkedInt"]]
Y_train = train["Survived"]
```

アルゴリズムを選択して学習データを与え、予測モデルを生成します。ここではランダムフォレストを例として使います。


```python
clf = RandomForestClassifier()
clf.fit(X_train, Y_train)
```




    RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
                max_depth=None, max_features='auto', max_leaf_nodes=None,
                min_impurity_split=1e-07, min_samples_leaf=1,
                min_samples_split=2, min_weight_fraction_leaf=0.0,
                n_estimators=10, n_jobs=1, oob_score=False, random_state=None,
                verbose=0, warm_start=False)



生成した予測モデルを使って、説明変数をパラメータとして受け取って目的変数を予測する関数を定義します。

クラスを判別する関数を定義します。


```python
def predict(Pclass,Sex,Age,SibSp,Parch,Fare,Embarked):
    SexMap = {"male":0,"female":1}
    EmbarkedMap = {"S":0,"C":1,"Q":2}
    import pandas as pd
    import numpy as np
    SexInt = map(lambda val:SexMap[val],Sex)
    IsAlone = map(lambda val:1 if val==0 else 0, np.array(SibSp) + np.array(Parch))
    EmbarkedInt = map(lambda val:EmbarkedMap[val],Embarked)
    data = pd.DataFrame({
        "Pclass":Pclass,
        "SexInt":SexInt,
        "AgeFillNa":Age,
        "IsAlone":IsAlone,
        "FareFillNa":Fare,
        "EmbarkedInt":EmbarkedInt
    })
    return clf.predict(data).tolist()
```

クラスに属する確率を返す関数を定義します。（任意）


```python
def predict_proba(Pclass,Sex,Age,SibSp,Parch,Fare,Embarked):
    SexMap = {"male":0,"female":1}
    EmbarkedMap = {"S":0,"C":1,"Q":2}
    import pandas as pd
    import numpy as np
    SexInt = map(lambda val:SexMap[val],Sex)
    IsAlone = map(lambda val:1 if val==0 else 0, np.array(SibSp) + np.array(Parch))
    EmbarkedInt = map(lambda val:EmbarkedMap[val],Embarked)
    data = pd.DataFrame({
        "Pclass":Pclass,
        "SexInt":SexInt,
        "AgeFillNa":Age,
        "IsAlone":IsAlone,
        "FareFillNa":Fare,
        "EmbarkedInt":EmbarkedInt
    })
    return clf.predict_proba(data)[:,1].tolist()
```

TabPy ClientでTabPy Serverに接続します。


```python
client = tabpy_client.Client("http://localhost:9004")
```

関数をデプロイします。


```python
client.deploy("predict",predict,"predict titanic survival",override=True)
client.deploy("predict_proba",predict_proba,"predict probability of titanic survival",override=True)
```

### デプロイした関数の確認・テスト

関数がエンドポイントとしてデプロイされていることを確認します。


```python
client.get_endpoints()
```




    {u'predict': {'name': 'predict', 'description': 'predict titanic survival', 'creation_time': datetime.datetime(2018, 2, 10, 16, 3, 43), 'version': 8, 'dependencies': [], 'last_modified_time': datetime.datetime(2018, 2, 11, 5, 50, 54), 'schema': None, 'type': 'model'},
     u'predict_proba': {'name': 'predict_proba', 'description': 'predict probability of titanic survival', 'creation_time': datetime.datetime(2018, 2, 10, 16, 32, 34), 'version': 4, 'dependencies': [], 'last_modified_time': datetime.datetime(2018, 2, 11, 5, 50, 54), 'schema': None, 'type': 'model'}}



関数を実行してみます。


```python
client.query("predict",[1,3],["male","female"],[0,30],[0,0],[0,1],[20,40],["Q","S"])
```




    {u'model': u'predict',
     u'response': [1, 0],
     u'uuid': u'c9f6230a-c8c7-4b9e-b50c-cffe4d242e64',
     u'version': 8}



REST APIでの関数実行も試してみます。


```python
import requests
```


```python
url = "http://localhost:9004/query/predict_proba"
payload = """
{
    "data":{
        "Pclass":[1,3],
        "Sex":["male","male"],
        "Age":[0,30],
        "SibSp":[0,0],
        "Parch":[0,1],
        "Fare":[20,40],
        "Embarked":["Q","S"]
    }
}
"""
res = requests.post(url,data=payload)
print res.text
```

    {"model": "predict_proba", "version": 4, "response": [0.9, 0.0], "uuid": "d4bcd22c-d055-4777-8424-afb841957ace"}


### Tableauからの呼び出し

Tableauから呼び出す場合、以下の手順を実施します。
- ヘルプから外部サーバ接続の設定を表示し、TabPy ServerのURL、ポート番号をセット
- 関数を定義し、SCRIPT_XXX関数を計算式として定義

計算式の例(エンドポイント呼び出しの場合)：  
SCRIPT_INT('return tabpy.query("predict",_arg1,_arg2)["response"]',AVG([Pclass]),AVG(Age))

[戻る](https://qiita.com/nittaryo1977/private/b16386ef78152a20bc2b)