rnnlm.py


・ライブラリのインポート
-

``` python
#!/usr/bin/env python

import numpy as np
import chainer
from chainer import cuda, Function, gradient_check, Variable, \
                        optimizers, serializers, utils
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L

```

・ファイル読み込み、前処理
-


``` python
def load_data(filename):
    global vocab
    words = open(filename).read().replace('\n', '<eos>').strip().split()
    dataset = np.ndarray((len(words),), dtype=np.int32)
    for i, word in enumerate(words):
        if word not in vocab:
            vocab[word] = len(vocab)
        dataset[i] = vocab[word]
    return dataset
```

``` python
train_data = load_data(train_path)
eos_id = vocab['<eos>']
```

  - words :文章を読み込み、文末に```<eos>```タグを追加、行頭と行末の改行コードを削除する
  - vocab :単語を重複無しで追加し、インデックスを付けて保存
  - dataset :単語のインデックス番号を配列として保存




・クラス定義
-



・```--init--```部

``` python
class MyRNN(chainer.Chain):
    def __init__(self, v, k):
        super(MyRNN, self).__init__(
            embed = L.EmbedID(v, k), # Word embedding layer
            H  = L.Linear(k, k), # L1 layer
            W = L.Linear(k, v), # L2 layer
        )
```

・```--init--```部は、モデルのセット時に１度だけ用いられる。
・```v```,```k```を引数として受取り、レイヤを定義する。

 - input layer : 語彙数、```len{vocab}```より、439(?)
 - hidden layer : ```demb```より100
 - output layer : input layer と同じ


 
・```__call__```部
-

``` python
	def __call__(self, s):
        accum_loss = None
        v, k = self.embed.W.data.shape 
        h = Variable(np.zeros((1,k), dtype=np.float32)) # define variable h [ it means x = Variable(x_data, dtype=np.float32) ] 
        for i in range(len(s)): # iterate word in sentenc s = [0,1,2,3] -> 4 iterate
        
            next_w_id = eos_id if (i == len(s) - 1) else s[i+1] # if next word is in here, else next word is <eos> 
            tx = Variable(np.array([next_w_id], dtype=np.int32)) # tx is next word ID ( = ground truth data ) 
            x_k = self.embed(Variable(np.array([s[i]], dtype=np.int32))) #x_k ==Wx  
            h = F.tanh(x_k + self.H(h)) # sekf.H(h) is W'h
            loss = F.softmax_cross_entropy(self.W(h), tx)
            accum_loss = loss if accum_loss is None else accum_loss + loss
            
        return accum_loss, x_k, self.W(h)  
```


・```__call__```部は、モデルの定義完了後に実行される。

v,k: ```__init__```で定義したembedレイヤの重みWの行列を定義。v:行、k:列の次元に対応している。
h:中間層のユニット数を定義、中間層は今回は100ユニットとしている。
```s```には受け取った文章のインデックスが、単語ごとに配列として格納されている。これを```for i in range(len(s)):```ループ内で単語ごとに分割し、処理を行う。

####・for内の処理

・```next_w_id```に$t+1$ステップでの出現単語のインデックス番号を格納する。
もし次の単語が存在しない場合、```<eos>```のインデックスが格納される。
その後、```tx```として変数化される。```x_k```では、今ステップで参照している単語のインデックスを変数化し、word embeddingを行う。

####・word embedding

>
 - 辞書の該当単語IDを取得し、そのID部分のみが```1```、それ以外を```0```とするような1-hotベクトル$x_t$を作成する。
 - また、コード内のh (=$h_t$)は、$t-1$ステップの$h_{t-1}$と$x_k$を用いて以下の式で求まる。加えて$W_{embed}$を用いて$x_k$(=```x_k```)を得る。$$x_t:dim(x_t)=len(vocab)$$
 - $$x_k = W_{embed} * x_k,　dim(x_k)=demb$$$$h_t=tanh(W_{embed} * x_t + W_h * h_{t-1})$$
 - $$=tanh(x_k+ W_h*h_{t-1})$$



・```F.softmax_cross_entropy(self.W(h), tx)```により、モデルの尤度が求まる。詳細は以下を参照
[交差エントロピー:neuralnetの日記](http://neuralnet.hatenablog.jp/entry/2016/05/17/010816)
　
####・メモ
観測データ$self.W(h).data=z$,　教師データ$t_x=t$とすると
$$z=[0.166479, 0.060454, 3.714621, ...,  -0.407060],　sum(z)≠ 1$$$$y =softmax(z) = \frac{exp(z_i)}{\sum_iexp(z_i)},　sum(y)=1$$であり、$softmax()$を通す事で、総和が1になるように正規化される。（＝確率とみなせる）
但し、クラスが多い場合、値が極めて小さくなり、アンダーフローの可能性が発生する。logを通す事で、値をそこそこ大きく取る事ができる（？）
クロスエントロピーを通す事で、モデルの損失を測ることができる。$t,y$について$$t=[0, 0, ..., 0, 1, 0, ... , 0],　len(y)=len(t)=len(vocab)$$
$$crossentropy(y,t) = -\sum_it_i*log_2(y_i)=\sum_i\frac{t_i}{log_2(y_i)}$$

教師データ$t$は1-hotベクトルであるから、正解クラス$t_i=t_k$以外の値は0となる。よって、式は以下のように変形可能となる。$$crossentropy(y',t)=\frac{t_k}{log_2(y'_k)}$$


モデルの損失を文章単位で累積し、累積誤差```accum_loss```を返す。

main
=


``` python
for epoch in range(1):
    s = []    
    for pos in range(25): # train_data = [0, 1, 2, 3, 4, 5, 6, 3, 7, 8, 9, 3]
        id = train_data[pos]
        s.append(id)        
        if (id == eos_id):
            ## s = [0,1,2,3] ( index '3' is <eos>  )
            
            loss, embedlist,tmp = model(s) ## loss.data.size=1, embed.data.size=100
            
            model.zerograds()
            loss.backward()
            optimizer.update()
            s = []                
        if (pos % 100 == 0):
            print(pos, "/", len(train_data)," finished")
    outfile = "myrnn-" + str(epoch) + ".model"
    serializers.save_npz(outfile, model)
```

```for epoch in range(n)```: エポックの繰り返し回数n
```for pos in range(len(train_data))```: 文章のwordの数だけ繰り返す