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クラスの説明は次でまとめる。