敵対的オートエンコーダー(PyTorchを使用)
紹介
「人間と動物の学習のほとんどは、教師なし学習です。もし知能がケーキだとしたら、教師なし学習はケーキの基盤であり、教師あり学習はケーキのアイシング、強化学習はケーキの上のさくらんぼです。私たちはアイシングとさくらんぼの作り方を知っていますが、ケーキの作り方は知りません。」
FacebookのAI研究部門のディレクターであるヤン・ルカン教授は、彼の話の中でこの類推を繰り返し言及しています。彼が言う無監督学習とは、「環境をモデル化し、可能な未来を予測し、観察し行動することによって世界の仕組みを理解する機械の能力」を指します。
深層生成モデルは、機械学習における教師なし学習の問題を解決しようとする技術の1つです。この枠組みでは、機械学習システムはラベルのないデータ内に隠れた構造を発見する必要があります。深層生成モデルは、密度推定、画像/音声ノイズ除去、圧縮、シーンの理解、表現学習、半教師あり分類など、さまざまな応用があります。
変分オートエンコーダー(VAE)は、確率的グラフィカルモデルの枠組みでこの問題を形式化することを可能にし、データの対数尤度の下限を最大化します。この記事では、VAEに触発された最近開発されたアーキテクチャである敵対的オートエンコーダーに注目しますが、これはデータを潜在次元にマッピングする方法においてより柔軟性を提供します(この点がまだ明確でない場合でも心配しないでください。記事の中で再度このアイデアを見直します)。敵対的オートエンコーダーの最も興味深いアイデアの1つは、敵対的学習を使用してニューラルネットワークの出力に事前分布を課す方法です。
PyTorchのコードに触りたい場合は、GitHubリポジトリを自由に訪問してください。この記事では、最初にノイズ除去オートエンコーダーと変分オートエンコーダーについてのバックグラウンドをカバーし、その後、PyTorchの実装である敵対的オートエンコーダー、追随するトレーニング手順、およびMNISTデータセットを使用した分離と半教師あり学習に関するいくつかの実験について説明します。
背景
デノイジングオートエンコーダー (dAE)
オートエンコーダの最も単純なバージョンは、ネットワークをトレーニングして入力を再構成させるものです。言い換えれば、ネットワークが何らかの形で恒等関数 $f(x)=x$を学習することを望んでいます。この問題が単純でないようにするために、入力の次元よりもはるかに次元が低い中間層(潜在空間)を通るという条件をネットワークに課します。このボトルネック条件により、ネットワークは入力情報を圧縮しなければなりません。したがって、ネットワークは2つの部分に分かれており、エンコーダは入力を受け取り、それの潜在または隠れた表現を作成し、デコーダはこの中間表現を取り、入力を再構成しようとします。オートエンコーダの損失は再構成損失と呼ばれ、入力と生成されたサンプルとの間の平方誤差として単純に定義できます:
L_R (x, x’)=||x - x’||^2
入力が範囲 $[0,1]^N$に正規化されている場合に広く使用される別の再構築損失は、クロスエントロピー損失です。
変分オートエンコーダ(VAE)
変分オートエンコーダは、隠れた表現を構築する方法に第二の制約を課します。これにより、潜在コードにはデザインによって定義された事前分布 $p(x)$ が存在します。言い換えれば、エンコーダは潜在空間全体を自由に使用することはできず、この事前分布 $p(x)$ の下で生成される隠れたコードを制限しなければなりません。たとえば、潜在コードの事前分布が平均0、標準偏差1のガウス分布である場合、値が1000の潜在コードを生成することは非常にありそうにないことになります。
これは潜在コードに保存できる情報量に対する第二の種類の正則化と見なすことができます。この利点は、今やシステムを生成モデルとして使用できるという事実に依存しています。データ分布$p(x)$から新しいサンプルを作成するためには、$p(z)$からサンプリングし、そのサンプルをデコーダーに通して新しい画像を再構築するだけです。この条件が課されない場合、潜在コードは潜在空間内で自由に分布する可能性があり、そのため出力を生成するために有効な潜在コードを単純な方法でサンプリングすることは不可能です。
この特性を強制するために、エンコーダによって生成された分布と事前分布との間のカルバック・ライブラー (KL) ダイバージェンスの形で損失関数に第2項が追加されます。VAEは確率的解釈に基づいているため、使用される再構成損失は前述のクロスエントロピー損失です。これをまとめると、次のようになります:
L(x, x’)=L_R(x, x’) + KL(q(z|x)||p(z)) または L(x, x’)=- ∑ _{k=1} ^{N} x _k log(x’ _k) + (1- x _k) log(1 - x’ _k) + KL(q(z|x)|| p(z))
ここで、$q(z|x)$は我々のネットワークのエンコーダーであり、$p(z)$は潜在コードに課せられた事前分布です。これにより、このアーキテクチャは逆伝播を使用して共同で訓練できます。
対抗オートエンコーダ (AAE)
生成モデルとしてのAAE
変分オートエンコーダの主な欠点の一つは、KLダイバージェンス項の積分がわずかな分布を除いて閉形式の解析解を持たないことです。さらに、潜在コード $z$に対して離散分布を使用するのは簡単ではありません。これは、離散変数を通じて逆伝播が一般的に不可能であるため、モデルの効率的な学習が困難になるからです。VAE設定でこれを行う1つのアプローチがここに紹介されました。
敵対的オートエンコーダは、敵対的学習を使用することでKLダイバージェンスの使用を完全に回避します。このアーキテクチャでは、新しいネットワークが、サンプルがオートエンコーダの隠れコードから来ているのか、ユーザーによって決定された事前分布$p(z)$から来ているのかを識別的に予測するために訓練されます。エンコーダの損失は、再構成損失と識別器ネットワークによって与えられる損失の合計で構成されます。
この画像は、潜在コードにガウス事前分布を使用する際のAAEの動作を図式的に示しています(アプローチは一般的で、任意の分布を使用できます)。上段はオートエンコーダーに相当します。まず、生成ネットワーク $q(z|x)$に従ってサンプル $z$が描画され、それがデコーダーに送られ、$z$から $x’$が生成されます。再構成損失は $x$と $x’$の間で計算され、勾配はそれに応じて $p$と $q$を通って逆伝播され、その重みが更新されます。
図1. AAEの基本アーキテクチャ。上段はオートエンコーダーで、下段は敵対的ネットワークであり、エンコーダーの出力が分布$p(z)$に従うように強制します。
敵対的正則化部分では、識別器は$z$を$q(z|x)$として分布させ、真の事前分布$p(z)$からサンプリングされた$z’$を受け取り、各々が$p(z)$から来た確率を割り当てます。発生した損失は識別器を通じて逆伝播され、重みが更新されます。その後、このプロセスが繰り返され、生成器はそのパラメータを更新します。
現在、敵対的ネットワークのジェネレーター(オートエンコーダのエンコーダでもある)が被った損失を使用して、KL発散の代わりにそれがどのようにして分布 p(z) に従ってサンプルを生成するかを学習させることができます。この修正により、潜在コードのプライアとしてより広範な分布のセットを使用することが可能になります。
識別器の損失は
$$L_D=- \frac{1}{m} \sum _{k=1} ^m \log(D(z')) + \log(1 - D(z))$$(プロット)
ここで $m$はミニバッチサイズ、$z$はエンコーダによって生成され、$z'$は真の事前分布からのサンプルです。
敵対的生成器には私たちがいます
L_G=- \frac{1}{m} \sum _{k=1} ^m \log(D(z)) (プロット)
方程式とプロットを見て、このように定義された損失が、識別器に偽のサンプルを認識させ、生成器には識別器を欺くように促すことを自分自身で納得させるべきです。
ネットワークの定義
このモデルのトレーニング手順に入る前に、これまでの内容をPytorchでどのように実装するかを見ていきます。エンコーダー、デコーダー、ディスクリミネーターのネットワークには、ReLU非線形関数と確率0.2のドロップアウトを持つ3つの1000隠れ状態層を持つシンプルなフィードフォワードニューラルネットワークを使用します。
#Encoder
class Q_net(nn.Module):
def __init__(self):
super(Q_net, self).__init__()
self.lin1 = nn.Linear(X_dim, N)
self.lin2 = nn.Linear(N, N)
self.lin3gauss = nn.Linear(N, z_dim)
def forward(self, x):
x = F.droppout(self.lin1(x), p=0.25, training=self.training)
x = F.relu(x)
x = F.droppout(self.lin2(x), p=0.25, training=self.training)
x = F.relu(x)
xgauss = self.lin3gauss(x)
return xgauss
Decoder
class P_net(nn.Module):
def __init__(self):
super(P_net, self).__init__()
self.lin1 = nn.Linear(z_dim, N)
self.lin2 = nn.Linear(N, N)
self.lin3 = nn.Linear(N, X_dim)
def forward(self, x):
x = self.lin1(x)
x = F.dropout(x, p=0.25, training=self.training)
x = F.relu(x)
x = self.lin2(x)
x = F.dropout(x, p=0.25, training=self.training)
x = self.lin3(x)
return F.sigmoid(x)
Discriminator
class D_net_gauss(nn.Module):
def __init__(self):
super(D_net_gauss, self).__init__()
self.lin1 = nn.Linear(z_dim, N)
self.lin2 = nn.Linear(N, N)
self.lin3 = nn.Linear(N, 1)
def forward(self, x):
x = F.dropout(self.lin1(x), p=0.2, training=self.training)
x = F.relu(x)
x = F.dropout(self.lin2(x), p=0.2, training=self.training)
x = F.relu(x)
return F.sigmoid(self.lin3(x))
この定義からの注目すべき点はいくつかあります。まず、エンコーダの出力がガウス分布に従う必要があるため、最終層では
非線形関数は使用しません。デコーダの出力にはシグモイド非線形があり、これは入力が0と1の間に値を持つように正規化されているためです。識別器ネットワークの出力は、真の事前分布から入力が来る確率を表す0と1の間の1つの数値です。
ネットワーククラスが定義されたら、それぞれのインスタンスを作成し、使用するオプティマイザーを定義します。エンコーダー(敵対的ネットワークのジェネレーターでもある)の最適化手順に独立性を持たせるために、このネットワークの部分に対して2つのオプティマイザーを以下のように定義します。
torch.manual_seed(10)
Q, P = Q_net() = Q_net(), P_net(0) # Encoder/Decoder
D_gauss = D_net_gauss() # Discriminator adversarial
if torch.cuda.is_available():
Q = Q.cuda()
P = P.cuda()
D_cat = D_gauss.cuda()
D_gauss = D_net_gauss().cuda()
Set learning rates
gen_lr, reg_lr = 0.0006, 0.0008
Set optimizators
P_decoder = optim.Adam(P.parameters(), lr=gen_lr)
Q_encoder = optim.Adam(Q.parameters(), lr=gen_lr)
Q_generator = optim.Adam(Q.parameters(), lr=reg_lr)
D_gauss_solver = optim.Adam(D_gauss.parameters(), lr=reg_lr)
トレーニング手順
このアーキテクチャのトレーニング手順は、各ミニバッチに対して次のように実行されます。
1) エンコーダ/デコーダ部分を通して前方パスを行い、再構成損失を計算し、エンコーダQとデコーダPネットワークのパラメータを更新します。
z_sample = Q(X)
X_sample = P(z_sample)
recon_loss = F.binary_cross_entropy(X_sample + TINY,
X.resize(train_batch_size, X_dim) + TINY)
recon_loss.backward()
P_decoder.step()
Q_encoder.step()
2) 潜在表現 z=Q(x) を作成し、事前分布 p(z) からサンプル z’ を取得します。それぞれを識別器に通し、各々に割り当てられたスコアを計算します (D(z) および D(z’))。
Q.eval()
z_real_gauss = Variable(torch.randn(train_batch_size, z_dim) * 5) # Sample from N(0,5)
if torch.cuda.is_available():
z_real_gauss = z_real_gauss.cuda()
z_fake_gauss = Q(X)
3) 識別器における損失を計算し、それを識別器ネットワークを通して逆伝播させて重みを更新します。コードでは、
# Compute discriminator outputs and loss
D_real_gauss, D_fake_gauss = D_gauss(z_real_gauss), D_gauss(z_fake_gauss)
D_loss_gauss = -torch.mean(torch.log(D_real_gauss + TINY) + torch.log(1 - D_fake_gauss + TINY))
D_loss.backward() # Backpropagate loss
D_gauss_solver.step() # Apply optimization step
4) ジェネレーター ネットワークの損失を計算し、それに応じて Q ネットワークを更新します。
Generator
Q.train() # Back to use dropout
z_fake_gauss = Q(X)
D_fake_gauss = D_gauss(z_fake_gauss)
G_loss = -torch.mean(torch.log(D_fake_gauss + TINY))
G_loss.backward()
Q_generator.step()
画像を生成する
さて、AAEがどのように画像を標準偏差5の2次元ガウス潜在表現にエンコードするかを視覚化しようとしています。これにはまず、2次元の隠れ状態でモデルを訓練します。それから、(-10,-10)(左上隅)から (10,10)(右下隅)までのこの潜在空間に均一な点を生成し、それらをデコーダネットワークに通します。
潜在空間。$x$軸と$y$軸の両方で-10から10まで均等に2Dの潜在空間を探索しながらの画像再構成。
AAEを使用して離散的な表現を学ぶ
データの理想的な中間表現は、観察されたデータを生成した根本的な因果要因の変動を捉えるものであるべきです。ヨシュア・ベンジオと彼の同僚はこの論文で「私たちは、表現が変動要因を分離できることを望んでいます。データの異なる説明要因は、入力分布内で互いに独立に変化する傾向があります」と述べています。また、「特徴学習に対する最も堅牢なアプローチは、できるだけ多くの要因を分離し、データに関する情報を実用的に少ししか捨てないことです」とも言及しています。
MNISTデータ(これは手書き数字の大規模なデータセットです)の場合、2つの根本的な因果要因を定義できます。一方で生成される数字があり、他方でそれを書くスタイルや方法があります。
指導されたアプローチ
この部分では、前のアーキテクチャより一歩進めて、潜在コード$z$に特定の構造を課そうとしています。特に、完全に監視されたシナリオにおいて、アーキテクチャがクラス情報をトレーススタイルから分離できるようにしたいと考えています。そのために、前のアーキテクチャを下の図に示すものに拡張します。潜在次元を二つの部分に分けます:最初の部分$z$は前の例で使用したものに類似しています;隠れコードの二番目の部分は、オートエンコーダに供給される数字のアイデンティティを示すワンホットベクトル$y$です。
教師あり敵対的オートエンコーダーアーキテクチャ。
この設定では、デコーダがワンホットベクトル $y$と隠れコード $z$を使用して元の画像を再構築します。エンコーダには、$z$にスタイル情報をエンコードするタスクが残されています。下の画像では、10,000 のラベル付き MNIST サンプルを用いてこのアーキテクチャをトレーニングした結果を見ることができます。図は再構築された画像を示しており、各行で隠れコード $z$は特定の値に固定され、クラスラベル $y$は 0 から 9 までの範囲です。実際に、スタイルは列を通して維持されています。
左から右にかけて、$z$を固定しながら潜在コード$y$を探索することによる画像再構成。
##### セミスーパーバイズドアプローチ 最後の実験として、ラベル情報のサンプルが少ない場合に類似した分離結果を得るための代替手段を検討します。前のアーキテクチャを修正し、AAEがクラスまたはラベルを示すベクトル$y$(ソフトマックスを使用)と連続的な潜在変数$z$(線形層を使用)の連結から構成される潜在コードを生成するようにできます。ベクトル$y$をワンホットベクトルとして機能させたいので、第二の敵対的ネットワーク$D_{cat}$を使用して、Categorical分布に従うことを強制します。エンコーダーは現在$q(z, y|x)$です。デコーダーは、クラスラベルと連続的な隠れコードの両方を使用して、画像を再構築します。
半教師あり敵対的オートエンコーダーアーキテクチャ。
ラベルのないデータは、再構成損失に基づいて隠れコードを作成するエンコーダの方法を改善し、ラベル情報を必要としないジェネレーターとディスクリミネーターのネットワークを向上させることで、訓練手順に貢献します。
半教師ありアプローチによる解消。
今や、ラベルされた情報が少なくても画像を生成できるだけでなく、ラベルがない画像を潜在コード$y$を見て最も高い値のものを選ぶことで分類することができるという点に注目する価値があります。現在の設定では、100のラベル付きサンプルと47,000のラベルなしサンプルを使用した際の分類損失は約3%です。
GPUトレーニングで
最後に、Paperspaceプラットフォームの2つの異なるGPUとCPUで、この最後のアルゴリズムのトレーニング時間について短い比較を行います。このアーキテクチャは非常に複雑ではなく、いくつかの線形層で構成されていますが、GPUアクセラレーションを利用することでトレーニング時間の改善は非常に大きいです。500エポックのトレーニング時間は、CPUでほぼ4時間からNvidia Quadro M4000を使用して約9分に、さらにNvidia Quadro P5000で6分に短縮されます。
GPUアクセラレーションありとなしのトレーニング時間
もっと素材
- 変分オートエンコーダーとは何ですか(チュートリアル)
- 自己符号化変分ベイズ(原論文)
- 対抗オートエンコーダー(原論文)
- 想像し推論する機械の構築:深層生成モデルの原則と応用







