Theanoによる雑音除去自己符号化器の実装
この記事はDeep Learning Advent Calendar 2015の9日目です。
Theanoによる自己符号化器の実装(2015/12/3)の続き。
今回は自己符号化器を継承して雑音除去自己符号化器(Denoising autoencoder)を実装した。
ソースコード全体はここ。
自己符号化器は入力をもとの入力に戻すような写像を学習する手法だったが、雑音除去自己符号化器はもっと過酷で雑音(ノイズ)を付与した入力を雑音のない状態に戻せという一見すると無茶な要求を最適化アルゴリズムにつきつける。MNISTの例だと左のようなノイズが入った画像(画像クリックで拡大)を右のようなノイズがない画像に戻す写像を学習させる。最適化アルゴリズムにこういう制約を課すだけでよりロバスト性の高い特徴が自動的に学習できるという。
ノイズ付与
ノイズ付与の方法はいろいろあるらしいがランダムにマスキングする手法が一般的なようだ。おそらく、何らかの統計的な傾向があるノイズでないと復元できないと思われる。Deep Learning Tutorialではget_corrupted_input()
という汚(よご)した画像のシンボルを返すメソッドを定義している。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
ここでは二項分布(binomial)に従うサンプルを画像と同じサイズだけ生成し、それを画像(input)にかけている。二項分布であるが試行回数nが1なのでいわゆるベルヌーイ分布である。これがなぜノイズ付与になるかは下のようなサンプルを書けば理解できる。ここでは画像のサイズが10x10ピクセルと想定している。
#coding:utf-8 import numpy as np import theano from theano.tensor.shared_randomstreams import RandomStreams rng = np.random.RandomState(123) theano_rng = RandomStreams(rng.randint(2 ** 30)) # 二項分布のサンプルを生成 corruption_level = 0.5 binomial = theano.function([], theano_rng.binomial(size=(10, 10), n=1, p=1-corruption_level)) print binomial()
corruption_level = 0
、つまりまったく汚さないとすると
[[1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1]]
が出力される。画像の各ピクセルに1をかけるのでそのままの画像が出力される。次にcorruption_level = 1
、つまり最大限汚すと
[[0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]]
が出力される。画像の各ピクセルに0をかけるので画像の全ピクセルが0になり真っ黒な画像になってしまう。corruption_level = 0.5
とすると
[[0 1 0 1 1 1 1 1 0 1] [0 0 0 1 1 1 0 0 1 0] [0 0 1 0 1 1 1 0 1 0] [1 1 1 1 1 1 0 1 0 0] [1 0 1 0 1 1 1 1 1 1] [1 0 1 0 1 1 1 1 0 1] [0 0 1 1 1 1 1 0 0 0] [0 1 0 0 1 0 0 0 1 0] [1 0 0 1 1 0 0 0 1 0] [0 1 0 1 0 1 0 0 0 0]]
となり、0と1が大体半分ずつ出現する。画像の各ピクセルにかけると0のところだけ黒く塗りつぶされノイズになるというわけか。実験では corruption_level = 0.3
としているため大体30%のピクセルが黒く塗りつぶされることになる。
コスト関数
雑音除去自己符号化器はコスト関数が少しだけ違う。ポイントは2つ。
- ニューラルネットに入力する前に画像にノイズを付与する
- 誤差はノイズを付与する前の画像との間で計算する
この誤差が小さくなるように重みを学習するためうまく学習できればノイズを付与した画像を入れるとノイズを付与する前の画像が出力されるというわけ。
def get_cost_updates(self, corruption_level, learning_rate): """コスト関数と更新式のシンボルを返す""" # 入力の一部にノイズを付与して汚す tilde_x = self.get_corrupted_input(self.x, corruption_level) # 入力を変換 y = self.get_hidden_values(tilde_x) # 変換した値を逆変換で入力に戻す z = self.get_reconstructed_input(y) # コスト関数のシンボル # 汚した入力が汚す前の入力に近くなるように学習する L = - T.sum(self.x * T.log(z) + (1 - self.x) * T.log(1 - z), axis=1) # Lはミニバッチの各サンプルの交差エントロピー誤差なので全サンプルで平均を取る cost = T.mean(L) # 誤差関数の微分 gparams = T.grad(cost, self.params) # 更新式のシンボル updates = [(param, param - learning_rate * gparam) for param, gparam in zip(self.params, gparams)] return cost, updates
重みの可視化
自己符号化器と雑音除去自己符号化器で学習された重みを可視化して比較してみた。左が自己符号化器の重みで右が今回の雑音除去自己符号化器の重みである。重みを描画するコードは前回(2015/12/3)とほとんど同じなので省略。
重みの傾向が異なることはわかったけれどこれだけじゃロバストな特徴かはわからないかな。あとで学習した特徴を用いて数字認識するがそこで精度に差が出ることでロバスト性が示せるようだ。
入力画像の再構築
最後に前回と同様に入力画像を再構築してみよう。上が自己符号化器に入れて再構築した場合、下が雑音除去自己符号化器に入れて再構築した場合である。隠れ層のユニット数はどちらも500である。
雑音除去の方がよりクリアに再現されることがわかる。これは予想できたことでノイズの黒いところを白く補正する傾向が強くなったためだと考えられる。描画用のコードはこちら。
Deep Learning Tutorialでは画像の例しかないけれど、ノイズを工夫すれば自然言語や音声など他のドメインでも応用できるのか気になった。自然言語のノイズというとタイポとかかな?音声は普通に雑「音」なのでわかりやすい。音声の雑音除去は興味があるので余裕があったら試してみたい。
次回は積層自己符号化器(Stacked autoencoder)を実装する。ようやくDeepになる!
参考
- Denoising Autoencoders (dA)
- Vincent, H. Larochelle Y. Bengio and P.A. Manzagol, Extracting and Composing Robust Features with Denoising Autoencoders, Proceedings of the Twenty-fifth International Conference on Machine Learning (ICML‘08), pages 1096 - 1103, ACM, 2008.
- Theano でDeep Learning <4> : Denoising オートエンコーダ