Tech Circle ML #8 Chainer with Reccurent Neural Language Model 

#リカレントニューラルネットワーク言語モデル
事前準備
http://qiita.com/GushiSnow/items/9ab8761082e29002f735

ハンズオンのコードがあるgithub
https://github.com/SnowMasaya/Chainer-with-Neural-Networks-Language-model-Hands-on.git

#アプリケーションの動作確認
仮想環境の有効化

Mac/Linux

```
source my_env/bin/activate
```

```
pyenv install 3.4.1
pyenv rehash
pyenv local 3.4.1 
```

動作確認
直下で、以下を実行。

ipython notebook

Chainerのノートブックがこれで開きます。

#リカレントニューラル言語モデルを作成するプロセスを体験する

iPython notebookの方を開いてください。こちらにはリカレントニューラル言語モデルの作成のための各ステップを順番に書いてあります。
iPython notebookでは文中のコードが実際に実行できるため、上から順に解説＆実行をしていきましょう(詳しい使い方は[こちら](
http://qiita.com/icoxfog417/items/175f69d06f4e590face9
)を参照してください)。

![Screen Shot 2015-09-15 at 7.30.33.jpg](https://qiita-image-store.s3.amazonaws.com/0/10496/83141c02-4ea4-ea43-7f01-98fdbea11403.jpeg)

ここからコーディング箇所までipython notebookを見て下さい。

##リカレントニューラル言語モデル設定(コーディング箇所）！！！！

モデルは別クラスで定義しています。
この部分で自由にモデルを変えることが出来ます。
この部分でリカレントニューラル言語モデル独特の特徴を把握してもらうことが目的です。
・`F.EmbedID`は辞書データを、入力ユニット数分のデータに変換する処理（潜在ベクトル空間への変換）を行っています。
・出力が4倍の理由は入力層、入力制限層、出力制限層、忘却層をLSTMでは入力に使用するためです。
・`h1_in   = self.l1_x(F.dropout(h0, ratio=dropout_ratio, train=train)) + self.l1_h(state['h1'])`は過去の情報を保持しながらどれだけのdropoutでユニットを削るかを表しています。
Drop outについては下記をご覧下さい。

http://olanleed.hatenablog.com/entry/2013/12/03/010945

・`c1, h1  = F.lstm(state['c1'], h1_in)`はlstmと呼ばれる魔法の工夫によってリカレントニューラルネットがメモリ破綻、勾配消失を起こさずにいい感じで学習するための工夫です。詳しく知りたい人は下記をご覧下さい。

http://www.slideshare.net/nishio/long-shortterm-memory

・`return state, F.softmax_cross_entropy(y, t)`は予測した文字と実際の文字を比較して損失関数を更新している所になります。ソフトマックス関数を使用している理由は出力層の一つ前の層の全入力を考慮して出力を決定できるので一般的に出力層の計算にはソフトマックス関数が使用されます。

```py
#-------------Explain2 in the Qiita-------------
class CharRNN(FunctionSet):

    """
    ニューラルネットワークを定義している部分です。
    上から順に入力された辞書ベクトル空間を隠れ層のユニット数に変換し、次に隠れ層の入
    力と隠れ層を設定しています。
    同様の処理を2層にも行い、出力層では語彙数に修正して出力しています。
    なお最初に設定するパラメータは-0.08から0.08の間でランダムに設定しています。
    """
    
    def __init__(self, n_vocab, n_units):
        super(CharRNN, self).__init__(
            embed = F.EmbedID(n_vocab, n_units),
            l1_x = F.Linear(n_units, 4*n_units),
            l1_h = F.Linear(n_units, 4*n_units),
            l2_x = F.Linear(n_units, 4*n_units),
            l2_h = F.Linear(n_units, 4*n_units),
            l3   = F.Linear(n_units, n_vocab),
        )
        for param in self.parameters:
            param[:] = np.random.uniform(-0.08, 0.08, param.shape)

    """
    順伝搬の記述です。
    順伝搬の入力をVariableで定義し、入力と答えを渡しています。
    入力層を先ほど定義したembedを用います。
    隠れ層の入力には、先ほど定義したl1_xを用いて、引数にdropout、隠れ層の状態を渡して
    います。
    lstmに隠れ層第1層の状態とh1_inを渡します。
    2層目も同様に記述し、出力層は状態を渡さずに定義します。
    次回以降の入力に使用するため各状態は保持しています。
    出力されたラベルと答えのラベル比較し、損失を返すのと状態を返しています。
    """

    def forward_one_step(self, x_data, y_data, state, train=True, dropout_ratio=0.5):
        x = Variable(x_data, volatile=not train)
        t = Variable(y_data, volatile=not train)

        h0      = self.embed(x)
        h1_in   = self.l1_x(F.dropout(h0, ratio=dropout_ratio, train=train)) + self.l1_h(state['h1'])
        c1, h1  = F.lstm(state['c1'], h1_in)
        h2_in   = self.l2_x(F.dropout(h1, ratio=dropout_ratio, train=train)) + self.l2_h(state['h2'])
        c2, h2  = F.lstm(state['c2'], h2_in)
        y       = self.l3(F.dropout(h2, ratio=dropout_ratio, train=train))
        state   = {'c1': c1, 'h1': h1, 'c2': c2, 'h2': h2}

        return state, F.softmax_cross_entropy(y, t)

    """
    dropoutの記述を外して予測用のメソッドとして記述しています。
    dropoutにはtrainという引数が存在し、trainの引数をfalseにしておくと動作しない
    ので、予測の時は渡す引数を変えて学習と予測を変えても良いですが、今回は明示的に分る
    ように分けて記述しました。
    """

    def predict(self, x_data, state):
        x = Variable(x_data, volatile=True)

        h0      = self.embed(x)
        h1_in   = self.l1_x(h0) + self.l1_h(state['h1'])
        c1, h1  = F.lstm(state['c1'], h1_in)
        h2_in   = self.l2_x(h1) + self.l2_h(state['h2'])
        c2, h2  = F.lstm(state['c2'], h2_in)
        y       = self.l3(h2)
        state   = {'c1': c1, 'h1': h1, 'c2': c2, 'h2': h2}

        return state, F.softmax(y)
    
"""
状態の初期化です。
"""

def make_initial_state(n_units, batchsize=100, train=True):
    return {name: Variable(np.zeros((batchsize, n_units), dtype=np.float32),
            volatile=not train)
            for name in ('c1', 'h1', 'c2', 'h2')}
#-------------Explain2 in the Qiita-------------
```


#言語の予測
＜Handson #2 解説＞

予測用の文字列データ取得ですが、通常は学習データとテストデータは当然分けますが、今回はハンズオンで効果を実感してもらいたかったのであえて学習データとテストデータを同一にしました。
予測では作成されたモデル変更と文字列予測を行ないます。

・モデルを変更する。
・文字列を予測する。

予測するモデルの変更はここではiPython notebook内の下記のコードを変更します。
作成されたモデルはcvフォルダの中にあるので
あまり数は出来ていませんが、確認して見て下さい。

```py
# load model
#-------------Explain5 in the Qiita-------------
model = pickle.load(open("cv/charrnn_epoch_x.chainermodel", 'rb'))
#-------------Explain5 in the Qiita-------------
```

・`state, prob = model.predict(np.array([index], dtype=np.int32), state)`で予測された確率と状態を取得しています。次の予測にも使用するため状態も取得しています。
・`index = np.argmax(cuda.to_cpu(prob.data))`は`cuda.to_cpu(prob.data)`部分で各単語の重み確率を取得できるため、その中で一番確率が高いものが予測された文字なのでその文字のインデックスを返すようにしています。
・`index = np.random.choice(prob.data.argsort()[0,-sampling_range:][::-1], 1)[0]`はリカレントだと同じような文字が出力される確率が高いので、候補の中から上位５つからランダムに出力する処理も載せています。ここはあえてバラエティに富んだ出力が出ることを見ていただきたい部分なので本来は最大値を選択する必要があります。


```py    
#-------------Explain7 in the Qiita-------------
    state, prob = model.predict(np.array([index], dtype=np.int32), state)
    #index = np.argmax(prob.data)
    index = np.random.choice(prob.data.argsort()[0,-sampling_range:][::-1], 1)[0]
#-------------Explain7 in the Qiita-------------
```

#モデルを賢くして予測

今回のHands Onでは限られた時間でしか学習していないのでひどい精度のモデルしかできていません。
そこでパラメータを調整してモデルを使って再作成してみましょう。
調整するパラメータ

```
#-------------Explain7 in the Qiita-------------
n_epochs    = 30
n_units     = 625
batchsize   = 100
bprop_len   = 10
grad_clip   = 0.5
#-------------Explain7 in the Qiita-------------
```

それぞれのパラメータの役割
n_epochsは学習回数を表しています。モデルが複雑な場合は学習回数を多くしないと収束しないのでモデルが複雑な場合は大きな数を設定する必要があります。

n_unitsは隠れ層の数です。この数が多くなればなるほどモデルが複雑になります。この数を多くすると必然的に学習回数を多くしないと学習は収束しません。特に言語モデルの場合は語彙数に応じて、変えた方が良いです。語彙数よりユニット数の数が多いと潜在空間への写像が出来ていないことになり結果的に意味がない処理になります。

batchsizeは一度に学習するデータの数です。データの大きさに依存します。この点は経験的に調整する場合が多いですが、基本的に大きくすると学習精度が向上する代わりに学習スピードが落ち、小さくすると学習精度が低下する代わりに学習スピードが早くなります。

bprop_lenはリカレントニューラルネット特有のパラメータでどれだけ過去の文字を保有するかを表します。
これは解く問題によって変わるパラメータなので長い文章を予測させたい場合は大きな数値を設定し、比較的長くない文章であれば短い数値を設定します。

 optimizer.clip_grads(grad_clip)は勾配（重みの更新幅）の大きさに上限を設けており、重みが爆発するのを防いでいます。
大きな値にすると学習を許容し、小さな値にすると学習を抑えます。

ハイパーパラメータの最適化についてもっと知りたい方は下記をご覧ください

http://colinraffel.com/wiki/neural_network_hyperparameters

#Handson Advance

言語処理はすごく時間がかかるのでGPU設定をおススメしています。
しかし一概に使えば良いということではなく下記のような設定では有効に働きます。
詳細な仕組みの中身を知りたい方は下記をご覧ください。
http://www.kumikomi.net/archives/2008/06/12gpu1.php?page=1

＊得意
行列計算
メモリにシーケンシャルにアクセスし、かつ条件分岐の無い計算（演算密度の高い処理）に強い。

＊苦手
二分探索
メモリにランダムアクセスし、かつ条件分岐が多い。

##高速で試したい方へ 

http://sla.hatenablog.com/entry/chainer_on_ec2

github(今回のGPU用）NVIDIAが公開しているCUDA環境入りGPUインスタンス（Amazon Linuxベース)を使用

https://github.com/SnowMasaya/Chainer-with-Neural-Networks-Language-model-Hands-on-Advance.git

##GPUドライバ設定

AWSでのGPU設定は下記のサイトを参考に行いました。

http://tleyden.github.io/blog/2014/10/25/cuda-6-dot-5-on-aws-gpu-instance-running-ubuntu-14-dot-04/

```shell-session
apt-get update && apt-get install build-essential
```

Cudaのインストーラーを取得

```shell-session
wget http://developer.download.nvidia.com/compute/cuda/6_5/rel/installers/cuda_6.5.14_linux_64.run
```

Cudaのインストーラーのみ取得

```shell-session
chmod +x cuda_6.5.14_linux_64.run
mkdir nvidia_installers
./cuda_6.5.14_linux_64.run -extract=`pwd`/nvidia_installers
```

image-extractを取得

```shell-session
sudo apt-get install linux-image-extra-virtual
```

再起動

```shell-session
reboot
```

ファイルを作成

```shell-session
vi /etc/modprobe.d/blacklist-nouveau.conf
```

nouveauとlbm-nouveauの起動しないように設定

```
blacklist nouveau
blacklist lbm-nouveau
options nouveau modeset=0
alias nouveau off
alias lbm-nouveau off
```

Kernel Nouveauを起動しないように設定

```
 echo options nouveau modeset=0 | sudo tee -a /etc/modprobe.d/nouveau-kms.conf
```

カーネル起動時にあらかじめメモリに展開することでファイルシステムを構成する設定をしてから再起動

```
update-initramfs -u
reboot
```
カーネルのソースを取得

```shell-session
apt-get install linux-source
apt-get install linux-headers-3.13.0-37-generic
```

NVIDIAのドライバをインストール

```shell-session
cd nvidia_installers
./NVIDIA-Linux-x86_64-340.29.run
```
下記のコマンドでドライバがインストールされたかを確認する。

```shell-session
nvidia-smi
Wed Aug  5 07:48:36 2015
+------------------------------------------------------+
| NVIDIA-SMI 340.29     Driver Version: 340.29         |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GRID K520           Off  | 0000:00:03.0     Off |                  N/A |
| N/A   54C    P0    80W / 125W |    391MiB /  4095MiB |     99%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Compute processes:                                               GPU Memory |
|  GPU       PID  Process name                                     Usage      |
|=============================================================================|
|    0      8013  python                                               378MiB |
+-----------------------------------------------------------------------------+
```

上記のGPUに振られている番号がGPUのIDである。これは後でChainerを動作させるときに使用する。

`error while loading shared libraries: libcurand.so.5.5: cannot open shared object file: No such file or directory `が出た場合

http://linuxtoolkit.blogspot.jp/2013/09/error-while-loading-shared-libraries.html

PATHも通しておく

```
export PATH=$PATH:/usr/local/cuda-6.5/bin/
```

##Pythonの設定

Python3の設定を行いました。

下記のコマンドを実行して事前に必要なものをインストールしておく

```shell-session

apt-get update
apt-get install gcc gcc++ kmod perl python-dev
sudo reboot
```

pip導入手順
https://pip.pypa.io/en/stable/installing.html

Pyenv導入手順
https://github.com/yyuu/pyenv

```shell-session

pip install virtualenv

pyenv install 3.4

virtualenv my_env -p = ~/.pyenv/versions/3.4.0/bin/python3.4

```

requirement.txtの設定を行いました。

```shell-session
numpy
scikit-learn
Mako
six
chainer
scikit-cuda
```

必要なライブラリをインストール

```shell-session
pip install -r requirement.txt
```

下記から"install-headers"をダウンロードしてくる。

https://android.googlesource.com/toolchain/python/+/47a24ea6662f20c8e165d541ab6facdf009bfee4/Python-2.7.5/Lib/distutils/command/install_headers.py

PyCudaのインストール

```shell-session
wget https://pypi.python.org/packages/source/p/pycuda/pycuda-2015.1.2.tar.gz
tar zxvf pycuda-2015.1.2.tar.gz
cd pycuda-2015.1.2
./configure.py
make
make install
```

#Handson Advance2

ipython notebookをサーバー上で動作させて動作確認を行う(AWS上)

https://thomassileo.name/blog/2012/11/19/setup-a-remote-ipython-notebook-server-with-numpyscipymaltplotlibpandas-in-a-virtualenv-on-ubuntu-server/

設定ファイル作成

```
ipython profile create myserver
```

設定ファイル修正

```
vim /home/ec2-user/.ipython/profile_myserver/ipython_config.py
```

行の追加

```
c = get_config()

c.IPKernelApp.pylab = 'inline'
c.NotebookApp.ip = '*'
c.NotebookApp.open_browser = False
c.NotebookApp.password = u'sha1:yourhashedpassword'
c.NotebookApp.port = 9999
```

cudaのPATHを通しておく

```
export PATH=$PATH:/usr/local/cuda-6.5/bin/
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda-6.5/lib64/
```

AWSのセキュリティグループから開けたいポートを開ける

１：セキュリティグループを選択
２：編集でルールの追加
３：タイプ：カスタム TCP ルール
４：プロトコル：TCP
５：ポート：9999
６：送信元は任意の場所で設定

実行
通常の手順では反映されないので直接プロファイリングファイルを反映させる。

```
sudo ipython notebook --config=/home/ec2-user/.ipython/profile_myserver/ipython_config.py --no-browser
```

#まだまだ勉強したい向上心のある方へ

下記サイトの" Statistical Language Models based on Neural Networks"がすごくまとまっていて分かりやすいです。英語ですが。

http://rnnlm.org/

#参考サイト一覧

言語モデルのカバレージ、パープレキシティの説明

http://marujirou.hatenablog.com/entry/2014/08/22/235215

ディープラーニングフレームワークChainerをEC2のGPUインスタンスで動かす g2.2xlarge instance

http://ukonlly.hatenablog.jp/entry/2015/07/04/210149

Drop Out

http://olanleed.hatenablog.com/entry/2013/12/03/010945

Learning to forget continual prediction with lstm

http://www.slideshare.net/FujimotoKeisuke/learning-to-forget-continual-prediction-with-lstm

Zaremba, Wojciech, Ilya Sutskever, and Oriol Vinyals. "Recurrent neural network regularization." arXiv preprint arXiv:1409.2329 (2014).

Google Mikolov

http://www.rnnlm.org/

Neural Network(NN)を利用したLanguage Model(LM)，つまりNeural Network Language Model(NNLM)の一種であり， Recurrent Neural Network(RNN)を使ったRecurrent Neural Network Language Model(RNNLM)

http://kiyukuta.github.io/2013/12/09/mlac2013_day9_recurrent_neural_network_language_model.html

Long Short-term Memory

http://www.slideshare.net/nishio/long-shortterm-memory

Chainerのptbサンプルを解説しつつ、自分の文章を深層学習させて、僕の文章っぽい文を自動生成させてみる

http://d.hatena.ne.jp/shi3z/20150714/1436832305

RNNLM

http://www.slideshare.net/uchumik/rnnln

スパース推定概観：モデル・理論・応用

http://www.is.titech.ac.jp/~s-taiji/tmp/sparse_tutorial_2014.pdf

正則化学習法における最適化手法

http://imi.kyushu-u.ac.jp/~waki/ws2013/slide/suzuki.pdf

リカレントニューラル言語モデル作成参考
https://github.com/yusuketomoto/chainer-char-rnn

ニューラルネット　自然言語処理
http://www.orsj.or.jp/archive2/or60-4/or60_4_205.pdf

言語モデル作成
http://www.slideshare.net/uchumik/rnnln

自然言語処理プログラミング勉強会n-gram言語モデル
http://www.phontron.com/slides/nlp-programming-ja-02-bigramlm.pdf

Statistical Semantic入門 ~分布仮説からword2vecまで~ 
http://www.slideshare.net/unnonouno/20140206-statistical-semantics

linux source code
https://github.com/torvalds/linux

0. なぜGPUコンピューティングが注目を浴びているか - 慶應義塾
http://www.yasuoka.mech.keio.ac.jp/gpu/gpu_0.php

CUDA技術を利用したGPUコンピューティングの実際（前編） ―― グラフィックス分野で磨かれた並列処理技術を汎用数値計算に応用
http://www.kumikomi.net/archives/2008/06/12gpu1.php?page=1

GPGPU
https://ja.wikipedia.org/wiki/GPGPU#.E7.89.B9.E5.BE.B4.E3.81.A8.E8.AA.B2.E9.A1.8C

自然言語処理論Ｉ
http://www.jaist.ac.jp/~kshirai/lec/i223/02.pdf

STATISTICAL LANGUAGE MODELS BASED ON NEURAL
NETWORKS
http://www.rnnlm.org/

Neural Network Hyperparameters
http://colinraffel.com/wiki/neural_network_hyperparameters

Random Search for Hyper-Parameter Optimization
http://www.jmlr.org/papers/volume13/bergstra12a/bergstra12a.pdf