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

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

TheanoでSentiment analysis (CNN)②

前回でマックスプーリングまでの層の定義をまとめた。

kento1109.hatenablog.com

今回は、MLP層についてまとめる。

MLPDropput

ここに書いたこととほとんど同じ。

kento1109.hatenablog.com

呼び出し側

classifier = MLPDropout(rng, 
                        input=layer1_input, 
                        layer_sizes=hidden_units, 
                        activations=activations, 
                        dropout_rates=dropout_rate)

引数について

  • input:入力のshape(マックスプーリング後の特徴量を結合したもの(ここでは、[batch_num,300]))
  • layer_sizes:中間層の入力・出力のshape(ここでは、[300,2])
  • activations:活性化関数(ここでは、「恒等関数」 )
  • dropout_ratesドロップアウトの割合(ここでは、「0.5」)

_dropout_from_layer

まずは、_dropout_from_layerの処理を追いかける。

def _dropout_from_layer(rng, layer, p):
    """p is the probablity of dropping a unit"""
    srng = theano.tensor.shared_randomstreams.RandomStreams(rng.randint(999999))
    # p=1-p because 1's indicate keep and p is prob of dropping
    mask = srng.binomial(n=1, p=1-p, size=layer.shape)
    # The cast is important because
    # int * float32 = float64 which pulls things off the gpu
    output = layer * T.cast(mask, theano.config.floatX)
    return output

theanoでの二項分布に基づく乱数の生成プログラムは下記の通り。

import numpy
import theano
import theano.tensor as T
from theano.tensor.shared_randomstreams import RandomStreams

numpy_rng = numpy.random.RandomState(99999)
theanoRng = RandomStreams(numpy_rng.randint(99999))

fbino = theanoRng.binomial(n=1, p=0.5, size=(4, 5))

f1 = theano.function([], fbino)

print f1()
>> [[0 1 1 1 1]
>>  [1 1 1 1 0]
>>  [1 1 1 0 0]
>>  [1 1 0 1 0]]

n=1, p=0.5でnの生成確率を決めることが出来る。 (p=0.1の場合、1の出現確率は0.1になるということ。)

T.cast()は型変換。 GPUは倍精度(64bit)浮動小数点数の計算が苦手で,単精度(32bit)の方がよいらしいので、 theano.config.floatXで指定した型に変換しておく。

*呼び出し時のTHEANO_FLAGS.floatXの型となるので、float32となる

THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python conv_net_sentence.py

outputは、二項分布(p=0.5)に従い、入力を0にした(半分は×1なのでそのまま)もの。 それをMLPDropoutに返す。

ドロップアウトのイメージはこんなん。
image.png

DropoutHiddenLayer

次は、隠れ層(DropoutHiddenLayer)の呼び出し。
結論から言うと、layer_sizes=(300,2)の場合、隠れ層は作られない。
MLPの入力に対し、ドロップアウトを行うだけ

next_dropout_layer_input = _dropout_from_layer(rng, input, p=dropout_rates[0])
layer_counter = 0
for n_in, n_out in self.weight_matrix_sizes[:-1]:
    next_dropout_layer = DropoutHiddenLayer(rng=rng,
            input=next_dropout_layer_input,
            activation=activations[layer_counter],
            n_in=n_in, n_out=n_out, use_bias=use_bias,
            dropout_rate=dropout_rates[layer_counter])
    self.dropout_layers.append(next_dropout_layer)
    next_dropout_layer_input = next_dropout_layer.output
    # Reuse the parameters from the dropout layer here, in a different
    # path through the graph.
    next_layer = HiddenLayer(rng=rng,
            input=next_layer_input,
            activation=activations[layer_counter],
            # scale the weight matrix W with (1-p)
            W=next_dropout_layer.W * (1 - dropout_rates[layer_counter]),
            b=next_dropout_layer.b,
            n_in=n_in, n_out=n_out,
            use_bias=use_bias)
    self.layers.append(next_layer)
    next_layer_input = next_layer.output
    #first_layer = False
    layer_counter += 1

ここでは、weight_matrix_sizesに基づき、繰り返し処理を行っているが、 MLP層の隠れ層が1層の場合hidden_units=(300,2)の場合、weight_matrix_sizes[:-1]=[]となり、 繰り返し処理は行われない。
hidden_units=(300,100,2)とした場合、weight_matrix_sizes[:-1]=[300,100]となるので、DropoutHiddenLayerクラスが1回だけ呼ばれる。
このクラスでは、隠れ層が複数あった場合、それぞれにドロップアウトを適用している。
hidden_units=(300,2)の場合、ループ処理が行われず、隠れ層が作られないので、「あれ」と思ったが、 issueにもある通り、標準では隠れ層なしで直接、出力層に繋げるようになっているらしい。

github.com

隠れ層の処理は通常のMLPと同じなので、今回は標準の隠れ層なしパターンで進める。

LogisticRegression

次は、出力層(LogisticRegression)の呼び出し。(インスタンスの生成)

# Set up the output layer
n_in, n_out = self.weight_matrix_sizes[-1]
dropout_output_layer = LogisticRegression(
        input=next_dropout_layer_input,
        n_in=n_in, n_out=n_out)
self.dropout_layers.append(dropout_output_layer)
# Again, reuse paramters in the dropout output.
output_layer = LogisticRegression(
    input=next_layer_input,
    # scale the weight matrix W with (1-p)
    W=dropout_output_layer.W * (1 - dropout_rates[-1]),
    b=dropout_output_layer.b,
    n_in=n_in, n_out=n_out)
self.layers.append(output_layer)

一応、dropout_output_layeroutput_layerの2つのLogisticRegressionインスタンスが生成されていることは押さえておく。

# LogisticRegression class
# initialize with 0 the weights W as a matrix of shape (n_in, n_out)
if W is None:
    self.W = theano.shared(
             value=numpy.zeros((n_in, n_out), 
             dtype=theano.config.floatX), name='W')
else:
    self.W = W

# initialize the baises b as a vector of n_out 0s
if b is None:
    self.b = theano.shared(
             value=numpy.zeros((n_out,), 
             dtype=theano.config.floatX), name='b')
else:
    self.b = b

dropout_output_layerインスタンスの生成時は、パラメータW,bは未指定。
なので、0で初期化される。
inputには、next_dropout_layer_inputを指定している。
これは、_dropout_from_layer関数のoutputであり、乱数でドロップアウトした結果である。
output_layernext_layer_inputインスタンスのinputには、next_layer_inputが指定されている。
これは、DropoutHiddenLayerが存在しない場合、MLPDropoutのインスタンスの生成時にセットされたinputである。
このinputは、下記の通りCNN層の結合結果であることが分かる。

layer1_input = T.concatenate(layer1_inputs,1)

パラメータはdropout_output_layerのW,bを利用している。
多分、CNNの処理結果を受け取るoutput_layerインスタンスがメインとなるLogisticRegressionで、dropout_output_layerはその事前のドロップアウトの役割に過ぎないと思われる。

LogisticRegressionクラス自体は、通常のMLPと同じ処理なので、深く突っ込まず、ロジスティック分類により入力が二値分類されるという程度にしておく。

最後に、MLPDropputに戻って

# Use the negative log likelihood of the logistic regression layer as
# the objective.
self.dropout_negative_log_likelihood = self.dropout_layers[-1].negative_log_likelihood
self.dropout_errors = self.dropout_layers[-1].errors

self.negative_log_likelihood = self.layers[-1].negative_log_likelihood
self.errors = self.layers[-1].errors

# Grab all the parameters together.
self.params = [ param for layer in self.dropout_layers for param in layer.params ]

各モデル関数の損失関数としては、output_layerのnegative_log_likelihood,errorsが使われている。
dropout_output_layerのdropout_negative_log_likelihoodは、勾配計算のsgd_updates_adadeltaの引数として用いられる。
dropout_output_layerのdropout_errorsに関しては、参照されることがない。。

MLPDropoutのinit関数はここまで。
次回、MLPDropoutがメインの関数train_conv_netに返された後を見ていきたい。