euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

Python用機械学習ライブラリscikit-learnと形態素解析IgoをHeroku上で動かしてみる

動機

Python用の機械学習ライブラリscikit-learnは機械学習がさっぱりわからない私のような素人にも機械学習が使えるというすごいライブラリだ。正直大学時代に機械学習概論の単位を落としかけて以来、あまり寄り付かないようにしていたのだけれど、データサイエンティストっていうのは話しによればお金になるらしい。というわけでそういうスケベゴコロ満開で機械学習ライブラリに何かデータを分析させてみようと思う。

今回のモチベーションは

http://blog.parosky.net/archives/2212

でツイートの文章クラスタリングを見たので、ここのところを2chのレスに変えてやってみよう、というだけの話。それだけでは華がないので、PaaSサービス上で動かしてみたいな、というところ。つまり今回目指すのは

  • scikit-learnを使っての文章クラスタリング
  • scikit-learnに文章を渡すための前段階処理として形態素解析Igo-pythonを使う
  • そのアプリケーションをPaaS上にアップロードしてウェブから見えるようにする

という感じだ。

PaaS選び

まずPythonアプリケーションのデプロイ先として候補に上がるのは当然Google App Engineなのだけど、残念ながらscikit-learnは複数のライブラリ、しかもCへの依存が強いScipy/Numpyに依存しているので、動かすことはできないようだ(Is it possible to run scikit-learn on Google App Engine? - Stack Overflow)。

次に上がるのは前回アカウントを開けてWai+WarpのHaskellウェブアプリをデプロイしたOpenShiftだ。

だけど、ここでいろいろ試したのだけどうまくいかない。scikit-learnは動かないようだ。なのでHerokuを試してみる。Herokuなら動作報告もあるみたいだし、できるはず!

普通のPython+DjangoアプリケーションをHerokuにデプロイする

まず最初はひな形になるプレーンバニラのPython+DjangoウェブアプリをHeroku上にデプロイする。まずはHerokuでアカウントを取得して、HerokuのPythonチュートリアル通りにDjangoアプリケーションをデプロイする。 Getting Started with Python on Heroku | Heroku Dev Center

$ heroku login
Enter your Heroku credentials.
Email: <メールアドレスを入力>
Password (typing will be hidden):
Authentication successful.

チュートリアルと違ってsshキーをアップロードする必要があるので、アップロードする。終わるとメールが来る Managing Your SSH Keys | Heroku Dev Center

この通りにsshキーをインストールしないとgit pushができないのでそこで気づくはず。 テンプレートプロジェクトをクローンする。

git clone https://github.com/heroku/python-getting-started.git
mv python-getting-started <自分の好きなディレクトリ名>
cd <上でつけたディレクトリ名>

このディレクトリ内でプロジェクトを作成する。

heroku create

すると適当名前がついたプロジェクトが作成される。好きな名前をつけてもいいが、だいたい取られてる気がする。

git push heroku master

これでとりあえずコードをプッシュしてプロジェクトを動かしてみる。

heroku ps:scale web=1

プロジェクト作成の時点でDyno(実行単位)が割り当てられているはずだけど、念の為に1つ割り当てておく。1つのDynoを1ヶ月動かせる分だけが無料分に含まれているので、Dynoは1つにしておく。それ以上割り当てて1ヶ月まるまる走らせると無料分を超えてしまうようだ。

heroku open

でブラウザにデプロイしたウェブページが開く。ここでエラーが出ないことを確認しよう。

2ch.scを日本語データのソース先として取り込めるようにする

転載の問題とかあるけどとりあえず2ch.scをデータソースとして使ってみる。心配な人はオープン2ちゃんねるを使うのが良いだろう。先に上げた先人のようにツイートを使うのも良いかもしれない。とにかくたくさん日本語の文章があればよい。

def get_with_url(url):
    page_str = fetch_with_url(url)
    lines = page_str.split('\n')
    responses = []
    i = 0
    for line in lines:
        i += 1
        try:
            [name, mail, date_time_id, message, smth] = line.split('<>')
            thread = Response(i, name, mail, date_time_id, message)
            responses.append(thread)
        except:
            pass

    (a, _) = get_default_name_in_thread(responses)

    for res in responses:
        if res.name == a:
            res.name = ''

    return responses

def fetch_with_url(url):
    # if not ending "subject.txt", add
    logging.info('fetching thread : ' + url)

    try:
        result = requests.get(url)
        logging.info('result code : ' + str(result.status_code))
        if result.status_code == 200:
            ret = result.text
        else:
            ret = "Error: " + str(result.status_code)
        return ret
    except:
        logging.warning('Unexpected exception occurred')

という感じ。特別なところは特にないと思う。エラー処理は雑なので、適宜足してください。URLのフェッチには人間のためのHTTPを使っている。

Igo-Pythonをインストールして形態素解析を試してみる

まずは最初の肝の部分、Igo-Pythonを使って形態素解析をしてみる。これは簡単で、requirements.txtに

igo-python==0.9.3

をたしてgit pushすると使えるようになっているはずです。

__author__ = 'yoshinori'

from igo.Tagger import Tagger


def get_tagger():
    return Tagger('lib/igo/naist-jdic')

def analyzer(text):
    ret = []
    t = get_tagger()
    l = t.wakati(text)
    for m in l:
        ret.append(m)

    return ret


class BagsOfWords(object):
    def __init__(self, text):
        self.words = analyzer(text)

    def __str__(self):
        ret = ''
        for s in self.words:
            ret += s + ', '
        return ret

という感じで使えるはず。

f:id:euphonictechnologies:20140928162908p:plain

という感じにして、naist-jdicを配置してコミットしておく必要がある。

scikit-learnとscipyとnumpyをインストールして機械学習をさせてみる

同様にrequirements.txtに必要なライブラリを指定してコミットしてプッシュすることでインストールできる。

numpy==1.7.0
scipy==0.11.0
scikit-learn==0.13.1

で、書いたコードは以下の様な感じです。主にscikit-learnのサンプルのパクリです。パクリ元は

Clustering text documents using k-means — scikit-learn 0.13.1 documentation

で、実際にコードはこんな感じです。

scikit-learn example

実際に動かしてみよう!

2014-09-28T07:38:33.817668+00:00 heroku[router]: at=info method=GET path="/thread?server=ai&board=newsplus&dat=1411886294.dat" host=obscure-shelf-3820.herokuapp.com request_id=6813cf4b-1ec2-4859-a357-955a681beba0 fwd="1.75.196.170" dyno=web.1 connect=0ms service=393ms status=200 bytes=180695
2014-09-28T07:38:35.330096+00:00 app[web.1]: 2014-09-28 07:38:35 [INFO] fetching thread : http://ai.2ch.sc/newsplus/dat/1411886294.dat
2014-09-28T07:38:35.614954+00:00 app[web.1]: 2014-09-28 07:38:35 [INFO] result code : 200
2014-09-28T07:38:35.626421+00:00 app[web.1]: 2014-09-28 07:38:35 [INFO] Done in 0.0 sec(s)
2014-09-28T07:38:35.626462+00:00 app[web.1]: 2014-09-28 07:38:35 [INFO] Feature extraction...
2014-09-28T07:38:35.626211+00:00 app[web.1]: 2014-09-28 07:38:35 [INFO] Start analyzing...
2014-09-28T07:39:05.327304+00:00 heroku[router]: at=error code=H12 desc="Request timeout" method=GET path="/alzthread?server=ai&board=newsplus&dat=1411886294.dat" host=obscure-shelf-3820.herokuapp.com request_id=7aff11ad-e1ee-4430-b3f3-34d412b86bde fwd="1.75.196.170" dyno=web.1 connect=3ms service=30001ms status=503 bytes=0

クソワロタ。タイムアウトしてますね。Herokuのリクエストラウターはタイムアウト時間で強制的にタイムアウトさせてしまうので結果が見えない。ローカルで確認したところ5分位で結果返してくれるので、動くのは動くみたいだけど、タイムアウトしちゃうのでサーバー上だと使いものにならないみたいだ。

終わりに

非同期に計算できるようにAjaxかなんかでフロントエンドを書き直すよ。全部きれいにしてGithubに公開したりウェブアプリとして公開できるようにいつの日かしてみます、気が向いたら。

動作写真

f:id:euphonictechnologies:20140928164417p:plain

f:id:euphonictechnologies:20140928164425p:plain

2015-02-09 追記

レス数の少ないスレッドだと動作する模様です(目安は200以下)。デモは以下のリンクで見られます。

https://obscure-shelf-3820.herokuapp.com/board?server=ai&board=newsplus

参考文献

http://blog.parosky.net/archives/2212

4.3. Clustering — scikit-learn 0.11 documentation

http://www.cs.kent.edu/~jin/DM08/ClusterValidation.pdf

Clustering text documents using k-means — scikit-learn 0.13.1 documentation