#!/usr/bin/env python
# coding: utf-8
# オリジナルの作成: 2016/05/15
#
#
# ここでは、 # 人工知能に関する断創録 # のTheanoに関連する記事をSageのノートブックで実装し、Thenoの修得を試みます。 #
## 今回は、TheanoのTutorialからDenoisingオートエンコーダの例を以下のページを参考にSageのノートブックで試してみます。 #
# # # ## SageでTheanoのtutorialのCNNを実行すると、TypeError: 'sage.rings.integer.Integer' object is not iterable # のエラーになるため、今回もPythonを使用します。 #
## そこで、ノートブックの処理系をSageからPythonに切り替えます。上部の左から4つめのプルダウンメニューから # 「python」を選択してください。 #
## #
# # ## 最初に、theanoを使うのに必要なライブラリをインポートします。 #
# # In[1]: # 必要なライブラリのインポート import six.moves.cPickle as pickle import gzip import timeit import time import numpy as np import matplotlib.pyplot as plt get_ipython().run_line_magic('matplotlib', 'inline') import theano import theano.tensor as T from theano.tensor.shared_randomstreams import RandomStreams # これまで確認したlogistic_sgd.pyのLogisticRegressionをインポートする from logistic_sgd import LogisticRegression, load_data # 重みの可視化用 from PIL import Image from utils import tile_raster_images # ## オートエンコーダ(Autoencoder)は、日本語では自己符号化器と呼ばれています。 #
## ニューラルネットワークの計算では、重みの初期値が収束に大きく影響を及ぼすことが知られています。 # オートエンコーダでは特徴を抽出しやすいような重みの初期値を、入力・隠れ層・出力から成る3層の # ニューらネットワークを使って入力と同じ出力を生成することで求めます。 #
## DeepLearning 0.1 Documentationの式では、真ん中の隠れ層の値yは、以下の様になります。 # $$ # y = s(W x + b) # $$ # そして、出力層の値zをyを使って表すと、 # $$ # z = s(W' y + b') # $$ # のようになります。xをyに変換するプロセスを符号化(encode)、yをzに変換するプロセスを復号化(decode)と呼びます。 # これらを一つにまとめると以下の様になります。 # $$ # z = s( W'( s(W x + b) ) + b') # $$ #
## Theano で Deep Learning <4> : Denoising オートエンコーダ # からオートエンコーダの構成図を引用します。 #
## #
#
# Theanoによる自己符号化器の実装 # のコメントに、入力xが[0,1]で正規化されていることを前提にしてとあるこの部分が大切です。 # オートエンコーダの損失関数は、交差エントロピー誤差関数が使えます。 # $$ # L_H(x, z) = - \sum_{k=1}^d \left (x_k log z_k + (1 - x_k) log( 1 - z_k) \right ) # $$ #
# # ## オートエンコーダとDenoisingオートエンコーダでコードを再利用できるように、 # オートエンコーダに入力をそのまま返すget_corrupted_inputメソッドを追加しました。 #
# def get_corrupted_input(self, input, corruption_level): # return input ## #
# 損失関数の値L(結果はミニバッチサイズのベクトル)は、get_hidden_valuesメソッドは隠れ層の出力yを計算し、get_reconstructed_inputメソッドは、 # 出力層の出力zを使って以下の様に計算します。 #
# 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) ## #
# Theanoによる自己符号化器の実装 # の、損失関数計算の模式図がとても分かりやすいので、引用させて頂きます。 #
## #
# # In[2]: class Autoencoder(object): def __init__(self, numpy_rng, theano_rng=None, input=None, n_visible=784, n_hidden=500, W=None, bhid=None, bvis=None): self.n_visible = n_visible self.n_hidden = n_hidden if not theano_rng: theano_rng = RandomStreams(numpy_rng.randint(2 ** 30)) if not W: # 入力層と出力層の間の重み initial_W = np.asarray( numpy_rng.uniform( low=-4 * np.sqrt(6.0 / (n_hidden + n_visible)), high=4 * np.sqrt(6.0 / (n_hidden + n_visible)), size=(n_visible, n_hidden) ), dtype=theano.config.floatX ) W = theano.shared(value=initial_W, name='W', borrow=True) if not bvis: # 入力層(visible)のユニットのバイアス bvis = theano.shared( value=np.zeros(n_visible, dtype=theano.config.floatX), borrow=True) if not bhid: # 隠れ層(hidden)のユニットのバイアス bhid = theano.shared( value=np.zeros(n_hidden, dtype=theano.config.floatX), name='b', borrow=True) # パラメータ self.W = W self.b = bhid self.W_prime = self.W.T self.b_prime = bvis self.params = [self.W, self.b, self.b_prime] self.theano_rng = theano_rng if input is None: self.x = T.dmatrix(name='input') else: self.x = input # Denoisingに合わせたメソッドで入力をそのまま返す def get_corrupted_input(self, input, corruption_level): return input def get_hidden_values(self, input): """入力層の値を隠れ層の値に変換""" return T.nnet.sigmoid(T.dot(input, self.W) + self.b) def get_reconstructed_input(self, hidden): """隠れ層の値を入力層の値に逆変換""" return T.nnet.sigmoid(T.dot(hidden, self.W_prime) + self.b_prime) def get_cost_updates(self, corruption_level, learning_rate): """コスト関数と更新式のシンボルを返す""" # Denoisingされたxを求める tilde_x = self.get_corrupted_input(self.x, corruption_level) # 入力を変換 y = self.get_hidden_values(tilde_x) # 変換した値を逆変換で入力に戻す z = self.get_reconstructed_input(y) # コスト関数のシンボル # 元の入力と再構築した入力の交差エントロピー誤差を計算 # 入力xがミニバッチのときLはベクトルになる 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 def feedforward(self): """入力をフィードフォワードさせて出力を計算""" y = self.get_hidden_values(self.x) z = self.get_reconstructed_input(y) return z def __getstate__(self): """パラメータの状態を返す""" return (self.W.get_value(), self.b.get_value(), self.b_prime.get_value()) def __setstate__(self, state): """パラメータの状態をセット""" self.W.set_value(state[0]) self.b.set_value(state[1]) self.b_prime.set_value(state[2]) # ## MNISTのデータの1/10のサブセットミニMNISTのデータを使って、オートエンコーダを試してみましょう。 # training_epochsは、サイズを1/10にしたので、10倍の200としました。 #
## さくらの1GメモリのVPSで、約15分掛かりました。 #
# # In[3]: # MNISTの1/10のサブセットなので、学習回数のtraining_epochsを10倍の200で実行 learning_rate = 0.1 training_epochs = 200 batch_size = 20 # 学習データのロード datasets = load_data('data/mini_mnist.pkl.gz') # 自己符号化器は教師なし学習なので訓練データのラベルは使わない train_set_x = datasets[0][0] # ミニバッチ数 n_train_batches = train_set_x.get_value(borrow=True).shape[0] // batch_size # ミニバッチのインデックスを表すシンボル index = T.lscalar() # ミニバッチの学習データを表すシンボル x = T.matrix('x') # モデル構築 rng = np.random.RandomState(123) theano_rng = RandomStreams(rng.randint(2 ** 30)) autoencoder = Autoencoder(numpy_rng=rng, theano_rng=theano_rng, input=x, n_visible=28 * 28, n_hidden=100) # コスト関数と更新式のシンボルを取得 cost, updates = autoencoder.get_cost_updates(corruption_level=0., learning_rate=learning_rate) # 訓練用の関数を定義 train_da = theano.function([index], cost, updates=updates, givens={ x: train_set_x[index * batch_size: (index + 1) * batch_size] }) # モデル訓練 start_time = time.clock() for epoch in xrange(training_epochs): c = [] for batch_index in xrange(n_train_batches): c.append(train_da(batch_index)) if epoch%10 == 0: print "Training epoch %d, cost %f" % (epoch, np.mean(c)) end_time = time.clock() training_time = (end_time - start_time) print "time: %ds" % (training_time) # ## 重みの可視化は、Deep Learning Tutorialのソースutils.pyに含まれているtile_raster_images関数を使用しました。 #
## 結果は、ちょっと気持ち悪い形をしていますが、黒色のくぼみの部分に注目してみてください。 #
# # In[4]: # 重みの可視化(utils.pyのtile_raster_imagesを利用) Image.fromarray(tile_raster_images( X=autoencoder.W.get_value(borrow=True).T, img_shape=(28, 28), tile_shape=(10, 10), tile_spacing=(1, 1))) # ## テストデータを使ってオートエンコーダで復元された画像をオリジナルと比べてみましょう。そこそこ復元できているのが分かります。 #
# # In[5]: # テスト用データで確認 test_set_x = datasets[2][0] y = autoencoder.get_hidden_values(test_set_x[:100]) z = autoencoder.get_reconstructed_input(y).eval() # テスト画像 Image.fromarray(tile_raster_images( X=test_set_x.eval(), img_shape=(28, 28), tile_shape=(10, 10), tile_spacing=(1, 1))) # In[6]: # Autoencoderで復元された画像 Image.fromarray(tile_raster_images( X=z, img_shape=(28, 28), tile_shape=(10, 10), tile_spacing=(1, 1))) # ## Denosingとは聞き慣れない言葉ですが、ノイズを画像に加えることを言います。 #
## 与えるノイズは二項分布の乱数で生成します。以下に10 x 10の空間に、ノイズ30%(corruption_level=0.3)で # 生成されたノイズの分布を示します。30%と言ってもかなりノイズが埋まってしまうものだと感じました。 #
## この様子を 説明した # Theano で Deep Learning <4> : Denoising オートエンコーダ # のアニメキャラクタの図を引用します。 #
## # # In[7]: corruption_level = 0.3 rng = np.random.RandomState(123) theano_rng = RandomStreams(rng.randint(2 ** 30)) binomial = theano_rng.binomial(size=(10, 10), n=1, p=1 - corruption_level, dtype=theano.config.floatX) binomial.eval() # #
# Denoisingオートエンコーダの実装は、とても簡単です。 # get_corrupted_inputメソッドで入力データに二項分布のノイズを掛け合わせた結果を返すだけです。 #
# # In[8]: class DenoisingAutoencoder(Autoencoder): def __init__(self, numpy_rng, theano_rng=None, input=None, n_visible=784, n_hidden=500, W=None, bhid=None, bvis=None): Autoencoder.__init__(self, numpy_rng, theano_rng, input, n_visible, n_hidden, W, bhid, bvis) 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 # ## 早速、ミニMNISTのデータでDenosingの効果をみてみましょう。 #
## ミニバッチの条件はオートエンコーダと同じなので、まずDenosingAutoencoderを生成し、dAにセットします。 #
## つぎに、コスト関数と更新式を取得し、訓練用の関数を定義します。 #
## 計算はさくらのVPSで約17分で、コスト関数の値はオートエンコーダのときより若干高いです。 #
# # In[9]: # 以下の項目は、Autoencoderで設定しているので省略 ## 学習データのロード ## ミニバッチ数 ## ミニバッチのインデックスを表すシンボル ## ミニバッチの学習データを表すシンボル # モデル構築 dA = DenoisingAutoencoder(numpy_rng=rng, theano_rng=theano_rng, input=x, n_visible=28 * 28, n_hidden=100) # コスト関数と更新式のシンボルを取得 cost, updates = dA.get_cost_updates(corruption_level=0.3, learning_rate=learning_rate) # 訓練用の関数を定義 train_da = theano.function([index], cost, updates=updates, givens={ x: train_set_x[index * batch_size: (index + 1) * batch_size] }) # モデル訓練 start_time = time.clock() for epoch in xrange(training_epochs): c = [] for batch_index in xrange(n_train_batches): c.append(train_da(batch_index)) if epoch%10 == 0: print "Training epoch %d, cost %f" % (epoch, np.mean(c)) end_time = time.clock() training_time = (end_time - start_time) print "time: %ds" % (training_time) # ## オートエンコーダの場合と同様に重み行列を可視化して比べてみましょう。 #
## Denosingオートエンコーダの方が、形がシャープになっていることが分かります。 #
## あの気持ち悪い画像もいくつか残っています。もしかすると気持ちが悪い画像はランダムなもので、 # 隠れ層のサイズを100としましたが、まだ小さくても数値画像の特徴を表現するには十分であることを # 意味してるのではないかと感じました。 #
# # In[10]: # 重みの可視化(utils.pyのtile_raster_imagesを利用) Image.fromarray(tile_raster_images( X=dA.W.get_value(borrow=True).T, img_shape=(28, 28), tile_shape=(10, 10), tile_spacing=(1, 1))) # ## オートエンコーダとDenoisingオートエンコーダで復元された数字画像を比較してみます。 #
## オートエンコーダよりもDenoisingオートエンコーダの数字の方がはっきりしているように思います。 #
# # In[11]: # DenoisingAutoencoderで復元された画像 y = dA.get_hidden_values(test_set_x[:100]) z = dA.get_reconstructed_input(y).eval() Image.fromarray(tile_raster_images( X=z, img_shape=(28, 28), tile_shape=(10, 10), tile_spacing=(1, 1))) # In[ ]: