機械学習・自然言語処理の勉強メモ

学んだことのメモやまとめ

TheanoでSentiment analysis (CNN)①

Sentiment analysis

TheanoでCNNによるSentiment analysisのコードを読む。
コードは
github.com

process_data.py

ここは前処理。Theanoでの処理もない。
この前処理でどういうデータセットが出来るかを押さえる。

cPickleで保存する変数は、revs, W, W2, word_idx_map, vocab。

revs:
形式は、List(Dict,Dict,...)
Dictの内容は、
{
y:正解タグ(pos=1, neg=0)
text:整形した文章。(文字型)「I am a boy.」みたいな
num_words:文章の単語数
split:0~9の任意の数値(CV用)
}

  • W:ndarray(単語の分散ベクトル(事前学習済))shape=(18766,300)
  • W2:ndarray(単語の分散ベクトル(ランダム値))shape=(18766,300)
  • word_idx_map:dict(単語とその単語のID(W,W2の行番号))
  • vocab:dict(文書内に出現する単語とその頻度)
conv_net_sentence.py

これがメインプログラム。
ここから、次のconv_net_sentence.py(クラス)が呼ばれる。
学習モジュールに渡されるdatasetsは、下記のレイアウト

単語ID .. .. .. label
単語ID 単語ID 単語ID ... 0
単語ID 単語ID ... ... 1
単語ID 単語ID 単語ID ... 0
. . . . .
単語ID 単語ID ... ... 1

単語IDは、その単語の分散ベクトルの行番号を表す。
1行は1文章に相当。

datasets(train,test)と分けて格納されている。
いずれの文章も同じ長さになるようゼロ埋めされている。

train_conv_net

~72行目:パラメータの定義(省略)
74行目~:モデルの構築

x = T.matrix('x')   
y = T.ivector('y')
Words = theano.shared(value = U, name = "Words")
zero_vec_tensor = T.vector()
zero_vec = np.zeros(img_w)
set_zero = theano.function([zero_vec_tensor], updates=[(Words, T.set_subtensor(Words[0,:], zero_vec_tensor))], allow_input_downcast=True)
layer0_input = Words[T.cast(x.flatten(),dtype="int32")].reshape((x.shape[0],1,x.shape[1],Words.shape[1]))                                  
conv_layers = []
layer1_inputs = []

まず、Wordsについて
sharedは

s = shared("値", name="変数名")

のように宣言される。
なので、Wordsには、Uの値が与えられる。
このUというのは、conv_net_sentence.pyのmainで下記のような形で与えられていた。

if word_vectors=="-rand":
    print "using: random vectors"
    U = W2
elif word_vectors=="-word2vec":
    print "using: word2vec vectors"
    U = W

つまり、ランダム値 or 事前学習済の単語の分散表現ベクトルであることが分かる。


次に、set_zero関数。
入力にzero_vec_tensorを受け取り、WordsをT.set_subtensor(Words[0,:], zero_vec_tensor)に更新する。
set_zero関数が使われるのはモデルのバッチ学習のループの中。

if shuffle_batch:
    for minibatch_index in np.random.permutation(range(n_train_batches)):
        cost_epoch = train_model(minibatch_index)
        set_zero(zero_vec)
else:
    for minibatch_index in xrange(n_train_batches):
        cost_epoch = train_model(minibatch_index)  
        set_zero(zero_vec)

つまり、set_zeroの入力の「zero_vec_tensor」シンボルに、zero_vec(300次元の0ベクトル)を与え、Words[0,:]をzero_vec_tensorで更新するという関数。

T.set_subtensorはupdatesで用いられる関数。使い方は下記。

X = shared(numpy.array([0,1,2,3,4]))
Y = T.vector()
X_update = (X, T.set_subtensor(X[2:4], Y))
f = function([Y], updates=[X_update])
f([100,10])
print X.get_value() # [0 1 100 10 4]

では、次に

layer0_input = Words[T.cast(x.flatten(),dtype="int32")].reshape((x.shape[0],1,x.shape[1],Words.shape[1]))                                  
conv_layers = []
layer1_inputs = []
for i in xrange(len(filter_hs)):
    filter_shape = filter_shapes[i]
    pool_size = pool_sizes[i]
    conv_layer = LeNetConvPoolLayer(rng, input=layer0_input,image_shape=(batch_size, 1, img_h, img_w),
                            filter_shape=filter_shape, poolsize=pool_size, non_linear=conv_non_linear)
    layer1_input = conv_layer.output.flatten(2)
    conv_layers.append(conv_layer)
    layer1_inputs.append(layer1_input)

ここでは、3×300,4×300,5×300のフィルターを定義し、LeNetConvPoolLayerクラスの呼び出しを行っている。
LeNetConvPoolLayerクラスに関してはこっちに詳しく書いた。
kento1109.hatenablog.com
conv_layerのoutputは、ダウンサンプリングされた特徴マップ(N層×1×1)
これをflattenで2次元にする。(N層×1)
繰り返し処理の結果、フィルターサイズ別の畳み込み層・プーリング層が定義される。
ここまでで下図の赤枠までのモデルが定義される。
各特徴マップのうち、最も重要と思われる特徴量を一つ選出する。

次に、

layer1_input = T.concatenate(layer1_inputs,1)
hidden_units[0] = feature_maps*len(filter_hs) 
classifier = MLPDropout(rng, input=layer1_input, layer_sizes=hidden_units, activations=activations, dropout_rates=dropout_rate)

T.concatenateでlayer1_inputs(フィルターサイズ別の層list)を一つのlayer1_inputにまとめる。
2行目では、次のMLP層の隠れ層を定義している。
feature_maps×len(filter_hs)なので、100×3=300となる。
最後に、マックスプーリング後の特徴量を結合したものをinputとしてMLPDropoutクラスを呼び出す。
長くなったので、MLPDropoutクラスの説明は次でまとめる。