機械学習した結果から予測をHTTPで返す仕組みの一例

# はじめに

先日、機械学習した結果を使って勘定科目を予測するプログラムを作成したのですが、予測結果の応答が異常に遅くて使いものにならないというご意見があったので、少し工夫してみました。

[会計データを学習して、仕訳の入力時に摘要の内容から勘定科目を予測してみる - Qiita](http://qiita.com/mix_dvd/items/9c2ef5e6fcc390be067e)

# 応答の仕組み

学習結果をメモリー内に保持し、摘要を送信すると勘定科目を返してくるというものを簡易に実現するため、HTTPを利用することにしました。

そこで、PythonでHTTPサーバーを作成し、HTTPサーバー起動時に学習結果を読み込んでおき、GETで摘要を送信したら勘定科目を予測して返すというものにしてみました。

# PythonでHTTPサーバーを構築

以下の記事を参考にしました。

[Pythonで簡単にHTTPサーバを作る - Qiita](http://qiita.com/shinido/items/b4fdc907a37424bcf15b)

概ねそのまま使用しているのですが、BaseHTTPServerというライブラリが変更になっていることと、日本語の処理でエラーが出たことから少々修正してあります。
ちなみに、Python3です。

```py3:CallbackServer.py
#!/usr/local/bin/python
# coding: utf-8

import requests
import http.server
import socketserver
from http.server import BaseHTTPRequestHandler
from urllib.parse import urlparse, unquote

def start(port, callback):
    def handler(*args):
        CallbackServer(callback, *args)
    server = socketserver.TCPServer(('', int(port)), handler)
    server.serve_forever()

class CallbackServer(BaseHTTPRequestHandler):
    def __init__(self, callback, *args):
        self.callback = callback
        BaseHTTPRequestHandler.__init__(self, *args)

    def do_GET(self):
        parsed_path = urlparse(self.path)
        query = unquote(parsed_path.query)
        
        self.send_response(200)
        self.end_headers()
        
        result = self.callback(query)
        
        self.wfile.write(result.encode('utf-8'))
        
        return
```

# サーバー起動スクリプト

起動時に学習結果を読み込み、GETのコールバックで予測した結果を返します。
        
```py3:server.py
#!/usr/local/bin/python
# coding: utf-8

import sys
import CallbackServer

import pandas as pd
import numpy as np

homedir = "/home/scripts/"
filename = "data/code.csv"

df = pd.read_csv(homedir + filename, header=None)
df.index = df.pop(0)

df_rs = df.pop(1)

from sklearn.externals import joblib

scaler = joblib.load(homedir + 'data/scaler.pkl')
clf = joblib.load(homedir + 'data/clf.pkl')
vect = joblib.load(homedir + 'data/vect.pkl')

from janome.tokenizer import Tokenizer

t = Tokenizer()

def callback_method(query):
    texts = [
        query,
    ]

    notes = []
    for note in texts:
        tokens = t.tokenize(note)
        words = ""
        for token in tokens:
            words += " " + token.surface
        notes.append(words)
    
    X = vect.transform(notes)
    
    result = clf.predict(X)
    ans = ""

    for i in range(len(texts)):
        ans = df_rs.ix[result[i]]
        
    return ans

if __name__ == '__main__':
    port = sys.argv[1]
    CallbackServer.start(port, callback_method)
```

以下のコマンドで起動します。

```sh:
$ chmod a+x server.py
$ ./server.py 8080 &
```

# 動作テスト

Rubyを使って予測結果を取得してみます。

```rb:test.rb
require 'net/http'
require 'uri'

puts Net::HTTP.get_print('localhost', URI.escape('/?高速道路利用料'), 8080)
```

実行します。

```sh:
$ ruby test.rb
旅費交通
```

できた(^-^)

# LINEボットに組み込んでみる。

![スクリーンショット 2017-03-11 7.33.33.png](https://qiita-image-store.s3.amazonaws.com/0/114240/adf92be2-bf82-c31d-0f81-855c439fc4d6.png)

いい感じ(^-^)

LINEボットの作り方については以下の記事をご参照下さい。

[LINEのMessaging APIで自動応答のBOTを作成する with Ruby](http://qiita.com/mix_dvd/items/b0065d9adb8f486838c4)

ちなみに、このLINEボットは以下のQRコードでお友達になれます。

![2e099ea3-2a40-49b9-ca84-431c809cf153.png](https://qiita-image-store.s3.amazonaws.com/0/114240/160c2e12-8671-8b31-471c-aecb1e533553.png)

さて、次は何しようかな！