TheanoでDenoising Autoencoder
今回は下記のdocumantationを整理して、Autoencoder(自己符号化器)を理解する。
Denoising Autoencoders (dA) — DeepLearning 0.1 documentation
下記のまとめが分かりやすかった。
sinhrks.hatenablog.com
aidiary.hatenablog.com
文献としては下記を参考とした。
深層学習 | 書籍情報 | 株式会社 講談社サイエンティフィク
自分の理解のためにやっていることをなぞっていく。
Autoencoder(自己符号化器)
Autoencoderとは、
- 目標出力を伴わない、教師なし学習
- 目的は、データをより表す特徴を獲得すること
- ディープネットの事前学習、重みの初期値を得る目的でも用いられる
(MLP 深層学習 より引用)
と定義される。
まず、入力層と出力層だけの単層ネットワークを用意する。
この時、
Autoencoderでは、その出力を入力層と同じユニット数を持つ層に接続する。
この時、
2つの変換をまとめると、
で表される。
以上の2層ネットワークの重みとバイアスを調節して、入力に対する出力を、元の入力になるべく近づけるようにする。
ちなみに、最初の変換()を符号化(encode)、
二番目の変換()を復号化(decode)と言う。
こんなことて何が嬉しいのかと最初は思ったが、中間層の
が大事になるそう。これは、(データを表す)特徴と呼ばれる。
Autoencoderの目的は、このような特徴の学習を通じて、サンプルの別な表現であるを得ることにあるらしい。
の計算は、重み行列と入力の積から始まりますが、これはの各行ベクトルととの内積の計算であって、そこでは入力にの各行ベクトルが表す成分がどれだけ含まれているかを取り出しているといえます
(MLP 深層学習)
イメージはこんな感じ。
入力と1つの隠れ層への重みであるが、入力層が756次元、隠れ層のユニット数が100の場合、重みは756×100の行列となる。
1つの隠れ層への重みは、のベクトルとなる。
つまり、これは1枚の画像を表している。
(全てのユニットを画像にすることで、その時点の特徴を可視化できる。)
あと、たくさんある入力の特徴のうち、全てを使うのではなく、入力データを表すためのより良い特徴を抽出した入力の方が、後の分類タスクとかでも精度が高くなるらしい。
重み共有
Autoencoderでは、場合がある。(必須ではないらしいが・・)
実際に後で見るDeep Learning Tutorialでは、重み共有で実装されている。
Denoising Autoencoder(デノイジング自己符号化器)
入力を確率的に変動(例えば、平均0、分散のガウス分布に従うノイズを付与)させる。
ノイズをとし、入力をとする。
出力がノイズ加算前の元のサンプルに近くなるように学習を行う。
※それ以外は通常のAutoencoderと同じ。
以降で実際のコードを見ていく。
class dA
呼び出しているのは、test_dA関数。
(ネットワーク自体はシンプルなので、Denoising Autoencoderで特徴的な箇所のみを見ていく。)
重み共有
ここでは、としている。
コードで言うとこの部分(W_prime
は
# 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で入力にノイズを加えたを生成する。
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
ここでは、ガウス分布に基づくノイズではなく、二項分布を利用してノイズ入りのを生成している。
の出力
get_hidden_valuesでの計算をしている。
def get_hidden_values(self, input): """ Computes the values of the hidden layer """ return T.nnet.sigmoid(T.dot(input, self.W) + self.b)
はミニバッチ単位なので、ミニバッチ数×784の行列、
は784×500の行列
この内積を取るので、は、ミニバッチ数×500の行列になる。
の出力
get_reconstructed_inputでの計算をしている。
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
=なので、ミニバッチ数×500の行列、
self.W_prime
=なので、500×784の行列、
この内積を取るので、は、ミニバッチ数×784の行列になる。
当然であるが、はの行列の形と一致している。