ニューラルネットワーク(GAN)の戦争を理解する

生成的敵対ネットワーク(GAN)はますます人気が高まっています。 多くの人々がそれらについて話し、誰かがそれを使用します...しかし、結局のところ、少数の人々(それを使用する人さえ)は理解して説明することができます。 ;-)

それらがどのように機能するか、何を学び、何を実際に生成するかの最も単純な例を見てみましょう。



はじめに、優れた記事「銀行家に対する偽造品:Theanoの敵対ネットワークの出血」を読むことをお勧めします。 さらに、それから例を取り上げます。



この記事は一部の人にとっては冗長に思えるかもしれませんが、説明は冗長であり、繰り返しですらあります。 しかし、そうです。 ただし、繰り返しが学習の母であることを忘れないでください。 さらに、人によってテキストの認識が大きく異なるため、言葉遣いの小さな変更でさえ大きな役割を果たすことがあります。 一般に、すでに明確になっているものはすべて、ただめくるだけです。



競合ネットワーク



したがって、2つのニューラルネットワークがあります。 y G = G(z)の関数としてそれを生成する最初のネットワークを示します。入力ではzの値を取り、出力ではy Gの値を与えます 区別する2番目のネットワーク。これをy D = D(x)の関数、つまり入力-x 、出力-y Dで示します



生成ネットワークは、識別ネットワークDが実際の参照サンプルと区別できないサンプルy Gを生成することを学習する必要があります。 そして、差別的なネットワークは、逆に、これらの生成されたパターンを実際のパターンと区別することを学ばなければなりません。



最初は、ネットワークは何も知らないので、訓練する必要があります。 しかし、どのように? 実際、通常のネットワークのトレーニングには、トレーニングデータの特別なセットが必要です- (X i 、Y i 。 X iはネットワーク入力に連続して供給され、 y i = N(X i )が計算されます。 次に、 Y i (実際の値)とy i (ネットワークの結果)の差に応じて、ネットワーク係数が再計算されます。



だから、そうではない。 実際、トレーニングの各ステップで損失関数の値が計算され、その勾配がネットワーク係数を再計算します。 しかし、損失関数はすでに何らかの方法でY iy iの差を考慮しています。 しかし、厳密に言えば、これはまったく必要ありません。 第一に、損失関数は、たとえばネットワーク係数の値または他の関数の値を考慮して、完全に異なる方法で配置できます。 次に、ネットワークトレーニング中に損失関数を最小化する代わりに、たとえば成功関数を最大化するなど、別の最適化問題を解決できます。 そして、これはGANでのみ使用されます。



最初の生成ネットワークの学習は、関数D(G(z))を最大化することです。 つまり、このネットワークは結果を最大化するのではなく、2番目の差別的なネットワークの結果を最大化しようとします。 簡単に言えば、生成ネットワークは、入力に適用される値を学習して、出力でそのような値を生成し、識別ネットワークの入力を供給すると、出力で最大値を取得する必要があります(2回読む価値があるかもしれません)

識別ネットワークの出力にシグモイドがある場合、「正しい」値がネットワーク入力に適用される確率を返すと言えます。 したがって、生成ネットワークは、識別ネットワークが生成ネットワークの結果を「参照」パターンから区別しない可能性を最大化しようとします(つまり、生成ネットワークが正しいパターンを生成することを意味します)。

生成ネットワークのトレーニングに一歩を踏み出すには、生成ネットワークの結果だけでなく、識別ネットワークの結果も計算する必要があることを覚えておくことが重要です。 不器用ではありますが、直感的にそれを定式化します。生成ネットワークは、判別ネットワークの結果の勾配から学習します。



2番目の特徴的なネットワークの学習は、機能的なD(x)(1-D(G(z)))を最大化することです。 大まかに、それ(ネットワーク、つまり関数D() )は、参照サンプルに対して「1」を生成し、生成ネットワークによって生成されたサンプルに対して「ゼロ」を生成する必要があります。

さらに、ネットワークは、入力に供給されたもの(標準または偽)について何も知りません。 これについては機能のみが知っています。 ただし、ネットワークはこの機能の勾配の方向で学習しているため、「現状のまま」透過的にそれがいかに優れているかがわかります。

識別ネットワークをトレーニングする各ステップで、生成ネットワークの結果が識別ネットワークの結果の1倍と2倍に計算されることに注意してください:初めて、参照サンプルがそれに供給され、2番目は生成ネットワークの結果です。



一般に、両方のネットワークは相互学習の切っても切れない輪に接続されています。 そして、この設計全体が機能するためには、参照サンプルトレーニングデータ(X i )のセット)が必要です。 ここでY iは必要ないことに注意してください。 ただし、実際には、デフォルトで各X iY i = 1に対応することが暗示されていることは明らかです。



学習プロセスでは、生成ネットワークの入力のステップで、いくつかの値が与えられます。たとえば、完全にランダムな数値(または完全に非ランダムな数値-制御された生成のレシピ)です。 そして、識別ネットワークの入力で、各ステップで、次の参照サンプルと生成ネットワークの次の結果が供給されます。

結果として、生成ネットワークは、参照パターンに可能な限り近いパターンを生成することを学習する必要があります。 識別ネットワークは、生成された参照パターンと参照パターンを区別することを学習する必要があります。



生成ネットワークをトレーニングする目的は類似性関数を最大化することであるため、生成されたパターンが失敗する可能性があることを覚えておく価値があります。 そして、達成されたこの最大値はゼロよりわずかに大きいだけです。つまり、ネットワークGは実際には何も学習していません。 そのため、すべてが正常であることを確認してください。



コードを解析する



ここでコードを見てみましょう。コードはそれほど単純ではないからです。



まず、必要なすべてのモジュールをインポートします。

import numpy as np import lasagne import theano import theano.tensor as T from lasagne.nonlinearities import rectify, sigmoid, linear, tanh
      
      





セグメント[-5.5]で均一なノイズを返す関数を定義し、生成ネットワークの入力に適用します。

 def sample_noise(M): return np.float32(np.linspace(-5.0, 5.0, M) + np.random.random(M) * 0.01).reshape(M,1)
      
      





生成するネットワークの入力となるシンボル変数を作成します。

 G_input = T.matrix('Gx')
      
      





そして、ネットワーク自体について説明します。

 G_l1 = lasagne.layers.InputLayer((None, 1), G_input) G_l2 = lasagne.layers.DenseLayer(G_l1, 10, nonlinearity=rectify) G_l3 = lasagne.layers.DenseLayer(G_l2, 10, nonlinearity=rectify) G_l4 = lasagne.layers.DenseLayer(G_l3, 1, nonlinearity=linear) G = G_l4 G_out = lasagne.layers.get_output(G)
      
      





ラザニアライブラリに関しては、このネットワークには4つの層があります。 しかし、学術的な観点からは、入力層と出力層は考慮されていないため、2層のネットワークが得られます。

ネットワークの結果は、入力に( G_inputに )何らかの値が供給された後、 G_out変数に書き込まれます。 その後、 G_outは識別ネットワークの入力に送信されるため、その形式ではG_outD_inputが一致する必要があります。



次に、シンボリック変数を作成します。シンボリック変数は、識別ネットワークの入力となり、そこに「参照」サンプルを送信します。

 D1_input = T.matrix('D1x')
      
      





そして、特徴的なネットワークを説明します。 この場合、シグモイドの出力でのみ、生成するものとほとんど変わりません。

 D1_target = T.matrix('D1y') D1_l1 = lasagne.layers.InputLayer((None, 1), D1_input) D1_l2 = lasagne.layers.DenseLayer(D1_l1, 10, nonlinearity=tanh) D1_l3 = lasagne.layers.DenseLayer(D1_l2, 10, nonlinearity=tanh) D1_l4 = lasagne.layers.DenseLayer(D1_l3, 1, nonlinearity=sigmoid) D1 = D1_l4
      
      







では、トリッキーなフェイントを作成しましょう。 覚えているように、参照サンプルまたは生成ネットワークの結果を識別ネットワークの入力に適用する必要があります。 しかし、計算グラフ(つまり、Theano、TensorFlowおよび同様のライブラリ)では、これは不可能です。 したがって、3つ目のネットワークを作成します。これは、前述の識別ネットワークの完全なコピーです。

 D2_l1 = lasagne.layers.InputLayer((None, 1), G_out) D2_l2 = lasagne.layers.DenseLayer(D2_l1, 10, nonlinearity=tanh, W=D1_l2.W, b=D1_l2.b) D2_l3 = lasagne.layers.DenseLayer(D2_l2, 10, nonlinearity=tanh, W=D1_l3.W, b=D1_l3.b) D2_l4 = lasagne.layers.DenseLayer(D2_l3, 1, nonlinearity=sigmoid, W=D1_l4.W, b=D1_l4.b) D2 = D2_l4
      
      





そして、ここでは、値G_outがネットワークの入力に供給されます。これは、生成ネットワークの結果です。 さらに、第3ネットワークのすべてのレイヤーの係数は、第2ネットワークの係数に等しくなります。 したがって、3番目と2番目のネットワークは互いの完全なコピーです。



ただし、これら2つの同一のネットワークの結果は異なる変数で表示されます。

 D1_out = lasagne.layers.get_output(D1) D2_out = lasagne.layers.get_output(D2)
      
      





そこで、最適化機能のタスクに到達しました。

 G_obj = (T.log(D2_out)).mean() D_obj = (T.log(D1_out) + T.log(1 - D2_out)).mean()
      
      





これで、識別ネットワークの2つの出力変数が必要な理由がわかりました。



次に、生成ネットワークトレーニング関数を作成します。

 G_params = lasagne.layers.get_all_params(G, trainable=True) G_lr = theano.shared(np.array(0.01, dtype=theano.config.floatX)) G_updates = lasagne.updates.nesterov_momentum(1 - G_obj, G_params, learning_rate=G_lr, momentum=0.6) G_train = theano.function([G_input], G_obj, updates=G_updates)
      
      





G_paramsは、生成ネットワークのすべてのレイヤーのすべての係数をリストします。

G_lrは学習速度を保存します。

G_updates-実際には、勾配降下法によって係数を更新する機能。 最初のパラメーターは損失関数を使用します。つまり、 G_objを最大化せず、 (1-G_obj)を最小化します(ただし、これはTheanoの単なる機能です)。 2番目のパラメーターは、すべてのネットワーク係数を渡してから、学習速度とパルス値の定数(勾配降下法としてNesterovパルス法が選択されているためにのみ必要です)。

その結果、 G_trainでは 、入力でG_input供給されるネットワークトレーニング関数を取得し、計算結果はG_obj 、つまり生成ネットワークの最適化関数です。



これで、識別ネットワークのすべてが同じになりました。

 D_params = lasagne.layers.get_all_params(D1, trainable=True) D_lr = theano.shared(np.array(0.1, dtype=theano.config.floatX)) D_updates = lasagne.updates.nesterov_momentum(1 - D_obj, D_params, learning_rate=D_lr, momentum=0.6) D_train = theano.function([G_input, D1_input], D_obj, updates=D_updates)
      
      





D_trainはすでに2つの変数G_input (生成ネットワークの入力)とD1_input (参照サンプル)の関数であることに注意してください。



最後に、トレーニングを開始します。 エポックサイクル:

 for i in range(epochs):
      
      





まず、識別ネットワークを1回ではなくK回トレーニングします。

  for j in range(K):
      
      





x-参照サンプル(この場合、パラメーターmuおよびsigmaを持つ正規分布の数値):

z-ランダムノイズ

  x = np.float32(np.random.normal(mu, sigma, (M,1))) z = sample_noise(M)
      
      





次に、Theanoマジックが開始されます。

-参照サンプルは識別ネットワークの入力に供給されます

-ランダムノイズzは生成ネットワークの入力に供給され、その結果は識別ネットワークに入力されます。

-その後、識別ネットワークの最適化機能が計算されます。

  D_train(z, x)
      
      





D_train関数の結果、そしてこれはあなたが思い出すように、D_objの最適化関数であり、それ自体は必要ありませんが、わずかに目立たない方法ではありますが、このネットワークのトレーニングに明示的に使用されます。

次に、生成ネットワークをトレーニングします。ランダム値のベクトルを再度生成して結果を生成しますが、トレーニング段階では、最適化関数の計算プロセスでのみ使用されます。

  z = sample_noise(M) G_train(z)
      
      





理論的には、問題の元の記述に基づいて、両方のネットワークは入力で1つの実際の値を取る必要がありますが、学習を高速化するために、 M値のベクトルをすぐに提供します。



10エポックごとに、両方のネットワークの学習速度がわずかに低下します。

  if i % 10 == 0: G_lr *= 0.999 D_lr *= 0.999
      
      





最後に、トレーニングが完了し、 Gネットワークを使用してサンプルを生成し、入力ランダムデータまたは特定のプロパティを持つサンプルを生成できる非常に非ランダムなデータ(レシピ)を与えることができます。



All Articles