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

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

TheanoでDenoising Autoencoder

今回は下記のdocumantationを整理して、Autoencoder(自己符号化器)を理解する。

Denoising Autoencoders (dA) — DeepLearning 0.1 documentation

下記のまとめが分かりやすかった。

sinhrks.hatenablog.com
aidiary.hatenablog.com

文献としては下記を参考とした。
深層学習 | 書籍情報 | 株式会社 講談社サイエンティフィク

自分の理解のためにやっていることをなぞっていく。

Autoencoder(自己符号化器)

Autoencoderとは、

  • 目標出力を伴わない、教師なし学習
  • 目的は、データをより表す特徴を獲得すること
  • ディープネットの事前学習、重みの初期値を得る目的でも用いられる

MLP 深層学習 より引用)
と定義される。

まず、入力層と出力層だけの単層ネットワークを用意する。
f:id:kento1109:20171128134241p:plain
この時、
y=f(Wx+b)
Autoencoderでは、その出力を入力層と同じユニット数を持つ層に接続する。
f:id:kento1109:20171128134007p:plain
この時、
\hat { x } =\tilde { f } (\tilde { W } y+\tilde { b} )
2つの変換をまとめると、
\hat { x } =\tilde { f } (\tilde { W } (f(Wx+b))+\tilde { b} )
で表される。
以上の2層ネットワークの重みとバイアスを調節して、入力xに対する出力\hat { x }を、元の入力xになるべく近づけるようにする。

ちなみに、最初の変換(y=f(Wx+b))を符号化(encode
二番目の変換(\hat { x } =\tilde { f } (\tilde { W } y+\tilde { b} ))を復号化(decodeと言う。

こんなことて何が嬉しいのかと最初は思ったが、中間層の(W,b)
が大事になるそう。これは、(データを表す)特徴と呼ばれる。

Autoencoderの目的は、このような特徴の学習を通じて、サンプルxの別な表現であるyを得ることにあるらしい。

{\rm y}の計算は、重み行列{\rm W}と入力{\rm x}の積から始まりますが、これは{\rm W}の各行ベクトルと{\rm x}との内積の計算であって、そこでは入力{\rm x}{\rm W}の各行ベクトルが表す成分がどれだけ含まれているかを取り出しているといえます

MLP 深層学習)

イメージはこんな感じ。
f:id:kento1109:20171201195541p:plain
入力と1つの隠れ層への重みであるが、入力層が756次元、隠れ層のユニット数が100の場合、重みは756×100の行列となる。
1つの隠れ層への重みは、(w_{j,1}, ..., w_{j,756})^Tのベクトルとなる。
つまり、これは1枚の画像を表している。
(全てのユニットを画像にすることで、その時点の特徴を可視化できる。)

あと、たくさんある入力の特徴のうち、全てを使うのではなく、入力データを表すためのより良い特徴を抽出した入力の方が、後の分類タスクとかでも精度が高くなるらしい。

重み共有

Autoencoderでは、\tilde { W }=W^T場合がある。(必須ではないらしいが・・)
実際に後で見るDeep Learning Tutorialでは、重み共有で実装されている。

Denoising Autoencoder(デノイジング自己符号化器)

入力xを確率的に変動(例えば、平均0、分散\sigma ^2ガウス分布に従うノイズを付与)させる。
ノイズを\varepsilon \sim N(0,σ^2I)とし、入力x\tilde { x } =x+\varepsilonとする。
出力\hat { x }ノイズ加算前の元のサンプルxに近くなるように学習を行う。
※それ以外は通常のAutoencoderと同じ。

以降で実際のコードを見ていく。

class dA

呼び出しているのは、test_dA関数。
(ネットワーク自体はシンプルなので、Denoising Autoencoderで特徴的な箇所のみを見ていく。)

重み共有

ここでは、\tilde { W }=W^Tとしている。
コードで言うとこの部分(W_prime\tilde { W }

# tied weights, therefore W_prime is W transpose
self.W_prime = self.W.T

なので、最後のself.paramsにも含まれない。

self.params = [self.W, self.b, self.b_prime]
ノイズの付与

get_corrupted_inputで入力xにノイズを加えた\tilde { x } =x+\varepsilonを生成する。

def get_corrupted_input(self, input, corruption_level):
    return self.theano_rng.binomial(size=input.shape, n=1,
                                    p=1 - corruption_level,
                                    dtype=theano.config.floatX) * input

ここでは、ガウス分布に基づくノイズではなく、二項分布を利用してノイズ入りの\tilde { x }を生成している。

yの出力

get_hidden_valuesy=f(Wx+b)の計算をしている。

def get_hidden_values(self, input):
    """ Computes the values of the hidden layer """
    return T.nnet.sigmoid(T.dot(input, self.W) + self.b)

xはミニバッチ単位なので、ミニバッチ数×784の行列、
Wは784×500の行列
この内積を取るので、yは、ミニバッチ数×500の行列になる。

\hat { x }の出力

get_reconstructed_input\hat { x } =\tilde { f } (\tilde { W } y+\tilde { b} )の計算をしている。

def get_reconstructed_input(self, hidden):
    """Computes the reconstructed input given the values of the
    hidden layer
    """
    return T.nnet.sigmoid(T.dot(hidden, self.W_prime) + self.b_prime)

hidden=yなので、ミニバッチ数×500の行列、
self.W_prime=W^Tなので、500×784の行列、
この内積を取るので、\hat { x }は、ミニバッチ数×784の行列になる。
当然であるが、\hat { x }xの行列の形と一致している。

損失関数

復習であるが、負の交差エントロピー関数が使われている。
L=-\sum _{i=1  }^{ d }{ x_i log⁡(\hat { x_i } )+(1-x_i)  log⁡(1−\hat { x_i } ) }
活性化関数がシグモイド関数の場合、\hat { x_i }は[0, 1]の確率値をとる。
今回の場合、値が二値(0, 1)なので、これでOK。
 \hat { x_i }の各成分が任意の連続値の場合、損失関数は二乗誤差の総和を用いる。
また、 \tilde { f }は恒等写像とするのが良い。

以降の学習部分は、今までと変わらないので割愛する。

今回は短めだったが、これで終わりとする。