『実践 機械学習システム』の第6章をやってみる

この記事は5月30日のAITCオープンラボにてお話しした感情分類器の作り方の補足記事を兼ねています。積み残しがあるのでまだ未完です。

最近オライリーから発売された『実践機械学習システム』はとても良い本で、機械学習の概要と何をしてどのような結果が得られるのかを一通り学ぶことができます。数学的な補足説明もあり良書の類である一方、教師データが手に入らない、ソースコードが間違っている等初心者を殺す罠が満載です。このエントリは罠を回避しつつ、第6章の感情分析を一通りやってみます。

必要なもの

必要なものはPythonが動く環境と以下のライブラリです。Windowsで行う場合はWinPythonをおすすめします(WinPythonには必要なライブラリが全部入ってます)。 Macの場合はpip入れてライブラリを導入していくのがおすすめです。anacondaなども便利ですが、いかんせん使ったことがないので当該環境を使用する場合はWeb上のドキュメントをあさってみてください。ガンバレ。

使用するライブラリ

上述の通り、WinPythonを使っている場合はこのあたりのライブラリが全部入っています。特に導入する必要はありません。OSXLinux、他ゼロから環境構築する場合の参考まで。

  • pandas
  • numpy
  • sklearn

この先はWindows環境でWinPythonを使用していることを想定しています。WinPythonには無償かつ強力なIDEであるSpyderが同梱してあるので、それを起動している前提で話を進めます。他環境の人は適宜読み替えておくれやす。

教師データを収集する(本通りやって失敗する場合)

この本の6.2『ツイートデータを取得する』ではスクリプトを使用して教師データを収集するんですが、このスクリプトがまず上手く動きません。スクリプトを動かすためには必要なファイルやディレクトリを用意してあげる必要があります。この課題をgithub上のコードだけで解消するのはキツいので、Niek Sanders氏のサイトからソースコードを落としてくると良いです。このコードは45時間ほどスクリプトを回し続けて教師データを作成します。ちなみにそのソースコードを実行した結果がこれ。

f:id:kazutaka83:20160605215043p:plain

引き当てたツイートが全部エラーになっているようで、教師データとして使えませんでした。動かし方が悪い可能性もあるので、ちゃんと動いた方がいらっしゃればご指摘ください。

というわけで別のデータを使います。

教師データを収集する(別データを使用する場合)

今回は以下のサイトからツイートデータを拝借してきました。

github.com

上述のgithubにあるtweets_clean.txtを使用しています。本の内容に合わせるために感情ラベル1をPositive、感情ラベル0をNegativeに置換しています。ラベルの置換はサクラエディタを使って行いました。ラベルとそれに紐づくツイートデータの区切りはタブのままにしておきます。また、20,000件のツイート全件をライブラリに読み込ませたらメモリエラーで落ちたので、5,000件ほどに削りましょう。メモリ16G積んでるのに落ちたのでちょっとびっくりした。

5,000件ほどに切り出したデータをTweetData.tsvという形で保存しておきます。後々これを読み込むので作業用フォルダとかに突っ込んでおいてください。

閑話休題(Spyderの使い方)

WinPythonをインストールすると展開したフォルダ内にPython環境が色々と展開されます。今回使用するIDE Spyderもこのフォルダの中に実行ファイルが格納されています。

f:id:kazutaka83:20160606212836p:plain

Pandas,Numpy,Matplotlibほか良く使用するライブラリは一通り入ってますし、便利な開発環境Spyderも展開フォルダから呼び出せます。上記のフォルダからSpyderを呼び出してみると以下のような画面が出てきます。

f:id:kazutaka83:20160606213229p:plain

左側の画面がコードを記していく主なエリア。右下が対話的にプログラムを打ち込んでいく環境(IPythonコンソール)です。Spyderを使ってスクリプトを作成するときには右下の対話環境でテストを行い、上手く走れば左側に書いていくというやり方を良く行っています。右上のエリアは必要に応じて色々と使えますが、私自身は変数のエクスプローラを良く表示させています。

f:id:kazutaka83:20160606213924p:plain

右下でリストやテキストを変数に格納してますが、この格納した結果が右上に表示されています。変数の型忘れとか共有リファレンスのうっかりミスを防ぐのに便利です。Spyderは個人の好みに応じてカスタマイズできるので、使い方に応じて必要な項目を出しとくと良いです。

この後は上で用意した教師データを作業フォルダにおいて、以下に記しているコードをSpyder右下のIPythonコンソールにポコポコ打ち込んでいきます。 各ウィンドウの役割はこんな感じです。

f:id:kazutaka83:20160612145116p:plain

データの読み込み

データの読み込みにはPandasを使用します。本ではスクリプト一発起動でX,Yに必要な情報が入ると書いていますけど、そもそもスクリプト動きません。作者のGithubにあるコードを読み解きながらX,Yの中身を自作します。ここと本を見比べると、ツイートをX、感情ラベルをYとしてNumpyのarray型に格納しているのが分かります。さっき用意した教師データを同じ形になるように整形していきます。

import pandas as pd
import numpy as np

TweetData = pd.read_csv('TweetData.tsv', delimiter='\t', header=None)
tweets = list(TweetData.values.T[1])
labels = list(TweetData.values.T[0])

X = np.asarray(tweets)
Y = np.asarray(labels)

TweetData.tsvが呼び出せない場合は作業フォルダとIPythonの実行フォルダが異なっていると思うので、TweetData.tsvのところを絶対パスで指定してみてください。実行結果は特に出力されません。Variable exploreを表示させている場合にはそこに変数と対応する値が表示されるかと思います。こんな感じ。

f:id:kazutaka83:20160612175356p:plain

ここまでが上手くいってた場合にはprint文で変数X,Yの中身を呼び出してやると結果が返ってくるかと思います。*1

print(X)
Print(Y)

このようにPrintに変数を引き渡してあげると中身が表示されます。IPythonコンソールの場合は途中省略されるので全部は表示されません。以下の図のようになってると多分大丈夫です。

f:id:kazutaka83:20160612175454p:plain

そのあとラベルをNumpyに割り振ってやって、読み込むまでの前準備をします。

pos_neg_idx = np.logical_or(Y=="positive",Y=="negative")

X = X[pos_neg_idx]
Y = Y[pos_neg_idx]

Y = Y=='positive'

Yの値はもともとPositiveかNegativeでしたけど、この処理でTrueかFalseに切り替わります。こっちも特に結果が出力されることはありません。

f:id:kazutaka83:20160612175617p:plain

Scikit-learnに引き渡す(特徴量抽出/交差検定)

Scikit-learn(モジュール名:sklearn)を使って特徴量の抽出と交差検定を行います。ここのコードは特にハマるとこはありません。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline

def create_ngram_model():
    tfidf_ngrams = TfidfVectorizer(ngram_range=(1,3),
                                   analyzer="word",
                                   binary=False) 
    clf = MultinomialNB()
    pipline = Pipeline([('vect',tfidf_ngrams),('clf',clf)])
    return pipline

ここではsklearnのモジュールを呼び出してcreate_ngram_modelという関数を作っています。同じくsklearnを用いて交差検定の関数を作ります。

from sklearn.metrics import precision_recall_curve, auc
from sklearn.cross_validation import ShuffleSplit

def train_model(clf_factory, X, Y):
    cv = ShuffleSplit(n=len(X), n_iter=10, test_size=0.3,
                          indices=True, random_state=0)
    scores = []
    pr_scores = []

    for train, test in cv:
        X_train, y_train = X[train], Y[train]
        X_test, y_test = X[test], Y[test]
        clf = clf_factory()
        clf.fit(X_train, y_train)
        train_score = clf.score(X_train, y_train)
        test_score = clf.score(X_test, y_test)
        scores.append(test_score)
        proba = clf.predict_proba(X_test)
        precision, recall, pr_thresholds = precision_recall_curve(y_test, proba[:,1])
        pr_scores.append(auc(recall, precision))

    summary = (np.mean(scores), np.std(scores),np.mean(pr_scores), np.std(pr_scores))
    return summary

f:id:kazutaka83:20160612164749p:plain

ここまでで準備ができました。用意したデータを関数に読み込ませます。ここにも罠があって、関数train_modelは引数を3つとるはずなんですが、本で指定している引数は1個だけです。そのまま動かすとエラーが返ってくるかと思います。

train_model(create_ngram_model,X,Y)

ここは結果が返ってきて、、となるはずなんですけどエラーが。。

train_model(create_ngram_model,X,Y)
Traceback (most recent call last):

  File "<ipython-input-65-f4b92c9af384>", line 1, in <module>
    train_model(create_ngram_model,X,Y)

  File "<ipython-input-64-4cf9700a36ba>", line 5, in train_model
    cv = ShuffleSplit(n=len(X), n_iter=10, test_size=0.3, indices=True, random_state=0)

TypeError: __init__() got an unexpected keyword argument 'indices'

この辺は積み残し。もう少しやってみて追記します。

日本語を使った分析について

日本語を使う場合には、ラベル付きのデータを使う場合が手を出しやすいと思います。日本語はTwitterのような短文であっても感情のポジティブ・ネガティブを一文に収めることができるので、ラベル付けが非常に難しいです。

qiita.com

他にもAmazonの評価などを拾って来れば日本語の感情分析ができるかもしれません。ここら辺は自身の宿題でもあります。

*1:Pandas使わない読み込み方法はあるけど、大体データ読み込みの時にはPandas経由してるんでここでも踏襲。Pandas使うとエンコード問題をうまく避けられることが多いので。