Tech-Circle 機械学習を利用したアプリケーション開発をはじめよう(自習編)

本記事は、先日実施したハンズオン形式の勉強会[Tech-Circle 機械学習を利用したアプリケーション開発をはじめよう](http://techcircle.connpass.com/event/13901/)・・・をブラッシュアップして発表した[PyCon2015 チュートリアル](https://pycon.jp/2015/ja/schedule/presentation/42/)の内容を自習形式にカスタマイズしたものです。


# ゴール

* 機械学習の概要を理解する
* 機械学習の使いどころについてヒントを得る
* 機械学習の開発環境を手に入れる

# 事前準備

下記記事に環境構築の手順をまとめていますので、お使いの環境に合わせてセットアップを行ってください。

[Pythonで機械学習アプリケーションの開発環境を構築する](http://qiita.com/icoxfog417/items/950b8af9100b64c0d8f9)

# 機械学習についての解説

まずは、機械学習の概要について解説します。

[Machine Learning Bootstrap](http://www.slideshare.net/takahirokubo7792/machine-learningbootstrap-47179613)

P37からハンズオンスタートです。スライドが解説、本記事が手を動かす際の手順となっていますので、スライドで解説を読む->手順を試してみる、という風に進めていってください。

# ハンズオン手順

**ハンズオンはPython3ベースで進みます**

## #0 準備

### #0-1 ソースコードのダウンロード

以下のGitHubリポジトリを**forkして**、そこからダウンロード
[icoxfog417/number_recognizer](https://github.com/icoxfog417/number_recognizer)

※そのままcloneして頂いてもOKですが、forkしないと、自分で学習させた内容などを反映することができません。

### #0-2 アプリケーションの動作確認

#### 仮想環境の有効化
(以下は、事前準備通り用意してきた場合(condaで仮想環境ml_envを作成)を想定しています。変えている場合は、適宜読み替えてください)。

Windows
```
activate ml_env
```

Mac/Linux

```
# pyenvとのバッティングを防ぐため、activateは仮想環境のパスを確認し直接実行する
conda info -e
# conda environments:
#
ml_env                   /usr/local/pyenv/versions/miniconda3-3.4.2/envs/ml_env
root                  *  /usr/local/pyenv/versions/miniconda3-3.4.2

source /usr/local/pyenv/versions/miniconda3-3.4.2/envs/ml_env/bin/activate ml_env
```

#### 動作確認

アプリケーション(localhost:3000で起動)

`number_recognition`フォルダの直下で、`run_application.py`を実行。

```
python run_application.py
```

機械学習モデルを構築するためのiPython notebook(localhost:8888で起動)

`number_recognition/machines/number_recognizer`直下で、以下を実行。

```
ipython notebook
```

アプリケーションの方は、最初ものすごくダメな感じです。これを賢くしていきます。

## #1 機械学習モデルを作成するプロセスを体験する

iPython notebookの方を開いてください。こちらには機械学習の各ステップを順番に書いてあります。
iPython notebookでは文中のコードが実際に実行できるため、上から順に解説＆実行をしていきましょう(詳しい使い方は[こちら](http://qiita.com/icoxfog417/items/175f69d06f4e590face9)を参照してください)。

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/6a40aeeb-d302-4802-2bf9-0445acdd026f.png)

最後の保存まで行けば、実際にモデル(`number_recognition/machines/number_recognizer/machine.pkl`)が更新されているはずです。

## #2 学習データの分割を行う

＜Handson #2 解説＞

ここでは、以下2つを実施します。

* データを、学習用と評価用に分ける
* 学習データに対する精度と、評価データに対する精度をそれぞれ算出する

学習データの分割を行には、[`cross_validation.train_test_split`](http://scikit-learn.org/stable/modules/generated/sklearn.cross_validation.train_test_split.html)を使用します。
こちらを利用し、Training the Modelの前に、以下の処理を入れます。

```py3
def split_dataset(dataset, test_size=0.3):
    from sklearn import cross_validation
    from collections import namedtuple

    DataSet = namedtuple("DataSet", ["data", "target"])
    train_d, test_d, train_t, test_t = cross_validation.train_test_split(dataset.data, dataset.target, test_size=test_size, random_state=0)

    left = DataSet(train_d, train_t)
    right = DataSet(test_d, test_t)
    
    return left, right

# use 30% of data to test the model
training_set, test_set = split_dataset(digits, 0.3)
print("dataset is splited to train/test = {0} -> {1}, {2}".format(
        len(digits.data), len(training_set.data), len(test_set.data))
     )

```

上記で`training_set`と`test_set`にデータを分割したので、Training the Modelを以下のように修正します。

```
classifier.fit(training_set.data, training_set.target)
```

これで学習は完了しました。データを分割したおかげで、評価用のデータが30%分のこっています。これを使って学習していないデータに対する精度を計測することができます。

Evaluate the Modelの精度計算部分を、以下のように修正します。

```py3
print(calculate_accuracy(classifier, training_set))
print(calculate_accuracy(classifier, test_set))
```

## #3 モデルの評価を行う

＜Handson #3 解説＞

* 過学習になっていないか確認するため、学習データ・評価データそれぞれについての精度がどう変化しているかを測定します。
* データ不均衡による「なんちゃって精度が高いモデル」を防ぐため、適合率・再現率を確認します。

### 学習/評価データに対する精度の確認

以下のスクリプトで、学習/評価データに対する精度の推移を確認します。
この、横軸に学習データ数、縦軸に精度を取ったグラフを学習曲線(learning curver)と呼び、scikit-learnでは[`sklearn.learning_curve`](http://scikit-learn.org/stable/modules/generated/sklearn.learning_curve.learning_curve.html)を利用することで簡単に描画することができます。

```py3
def plot_learning_curve(model_func, dataset):
    from sklearn.learning_curve import learning_curve
    import matplotlib.pyplot as plt
    import numpy as np

    sizes = [i / 10 for i in range(1, 11)]
    train_sizes, train_scores, valid_scores = learning_curve(model_func(), dataset.data, dataset.target, train_sizes=sizes, cv=5)
    
    take_means = lambda s: np.mean(s, axis=1)
    plt.plot(sizes, take_means(train_scores), label="training")
    plt.plot(sizes, take_means(valid_scores), label="test")
    plt.ylim(0, 1.1)
    plt.title("learning curve")
    plt.legend(loc="lower right")
    plt.show()

plot_learning_curve(make_model, digits)
```

追加し終わったら、実行してみて下さい。以下のように図がプロットされるはずです。

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/441770dd-db03-dd82-99d5-98b7ef3cde31.png)

### 適合率・再現率の確認

scikit-learnでは[classification_report](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html)関数を使うことで、簡単に確認できます。
各ラベル(#0～#9)内で、具体的に予測したもののうちどれだけが合っていたのかなどの分析は[confusion_matrix](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html#sklearn.metrics.confusion_matrix)で行うことができます。

```py3
def show_confusion_matrix(model, dataset):
    from sklearn.metrics import classification_report
    
    predicted = model.predict(dataset.data)
    target_names = ["#{0}".format(i) for i in range(0, 10)]

    print(classification_report(dataset.target, predicted, target_names=target_names))

show_confusion_matrix(classifier, digits)
```

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/ba6f0580-4093-7d11-0270-bbd0e95a698b.png)

## Handson Advanced

### Herokuへのデプロイ

Herokuボタンをおしてみて下さい。

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/26df98a3-7dcf-e171-3178-851b90ff4151.png)

conda-buildpackを利用することで、Heroku上でcondaによるアプリケーション環境構築が可能になります。
これにより、Herokuでも簡単に機械学習系アプリケーションを稼働させることができます。
詳細は[こちら](http://qiita.com/icoxfog417/items/1343395a7c42da9426b7)を参照してください

### モデルのチューニング

GridSearchを利用し、モデルのパラメータを変化させながら、どのパラメータで精度が最も高くなるかを探索します。
scikit-learnでは、[GridSearchCV](http://scikit-learn.org/stable/modules/generated/sklearn.grid_search.GridSearchCV.html)を利用することでこの探索が可能です。

Evaluate the Modelの前に、以下のコードを入れてチューニングを行ってみて下さい。

```py3
def tuning_model(model_func, dataset):
    from sklearn.grid_search import GridSearchCV
    
    candidates = [
        {"loss": ["hinge", "log"],
         "alpha": [1e-5, 1e-4, 1e-3]
        }]
    
    searcher = GridSearchCV(model_func(), candidates, cv=5, scoring="f1_weighted")
    searcher.fit(dataset.data, dataset.target)
    
    for params, mean_score, scores in sorted(searcher.grid_scores_, key=lambda s: s[1], reverse=True):
        print("%0.3f (+/-%0.03f) for %r" % (mean_score, scores.std() / 2, params))
    
    return searcher.best_estimator_
    
classifier = tuning_model(make_model, digits)
```

### オンライン機械学習

今回のアプリケーションは、予測された数値が違った場合に正しい答えを教えられるようになっています。その値は、データフォルダ内にfeedback.txtとして格納されるとともに、[モデルの学習に使用されます](https://github.com/icoxfog417/number_recognizer/blob/master/application/server.py#L43)。

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/173cfa3c-f2a7-ec1d-3dd5-7cf92b65b00a.png)

こちらについても、学習させることでどう変化が生じるのか確認してみて下さい。

※注意

* ユーザーの入力ほど信頼できないものはないので、普通はこうしたスタイルで学習させることはありません。一旦データを集めて、的外れなデータを除外したのちに学習させる方が賢明です。
* 動的に学習してしまうと予期しない方向へ学習が進んでしまうことも想定されます。そのため、オンライン機械学習の採用に当たってはその設計を慎重に行う必要があります。

### その他

* データセットの効果的な利用：[Cross Validation](http://nbviewer.ipython.org/github/icoxfog417/scikit-learn-notebook/blob/master/scikit-learn-tutorial.ipynb#Split-the-Data)
* 複数の学習機を組み合わせて精度を上げる： [Ensemble Learning](http://nbviewer.ipython.org/github/icoxfog417/scikit-learn-notebook/blob/master/scikit-learn-tutorial.ipynb#Ensemble-Lerning)
* モデルの精度の評価について、より深く: [Confusion Matrix](http://nbviewer.ipython.org/github/icoxfog417/scikit-learn-notebook/blob/master/scikit-learn-tutorial.ipynb#Evaluate-Training-Result)
* データの整備方法について、より深く： [Arrange the Data](http://nbviewer.ipython.org/github/icoxfog417/scikit-learn-notebook/blob/master/scikit-learn-tutorial.ipynb#Arrange-the-Data)