import numpy as np
import matplotlib.pyplot as plt
확률분포에 의해 가장 먼저 1인가를 물어봐야 합니다. 절반 정도는 한번 질문에 1임을 확인할 수 있고, 아니라면 4인가를 물어보는 것이 가장 합리적입니다. 그래도 아니면 그때는 2,3이 나올 확률은 동일하므로 아무것이나 물어봐도 됩니다. 위 계산을 이 시스템에서 다시 반복해보면 $q_{1}=1$, $q_{2}=3$, $q_{3}=3$, $q_{4}=2$ 이므로 아래와 같습니다.
$$
\begin{align}
\mathbb{E}(q)
&= \frac{500 \times q_{1} + 125 \times q_{2} + 125 \times q_{3}+ 250 \times q_{4}}{1000} \\[4pt]
&= \frac{500}{1000}q_{1}+\frac{125}{1000}q_{2}+\frac{125}{1000}q_{3}+\frac{250}{1000}q_{4} \\[4pt]
&= \frac{500}{1000}\times 1+\frac{125}{1000}\times 3+\frac{125}{1000}\times 3+\frac{250}{1000}\times 2 \\[4pt]
&= Q(X=1)\log_{2}2 + Q(X=2)\log_{2}8 + Q(X=3)\log_{2}8 + Q(X=4)\log_{2}4 \\[4pt]
&= \left(-Q(X=1)\log_{2}\frac{1}{2}\right) + \left(-Q(X=2)\log_{2}\frac{1}{8}\right) + \left(-Q(X=3)\log_{2}\frac{1}{8}\right) + \left(-Q(X=4)\log_{2}\frac{1}{4}\right) \\[4pt]
&= -Q(X=1)\log_{2}Q(X=1)-Q(X=2)\log_{2}Q(X=2)-Q(X=3)\log_{2}Q(X=3)-Q(X=4)\log_{2}Q(X=4) \\[4pt]
&= - \sum_{i=1}^{4} Q(X=x_{i})\log_{2}Q(X=x_{i}) = 1.75
\end{align}
$$
"Cross entropy can be interpreted as the expected message-length per datum when a wrong distribution $Q$ is assumed while the data actually follows a distribution $P$."
import random
#동일한 비율로 2000개 샘플을 만든다.
P = [1]*500 + [2]*500 + [3]*500 + [4]*500
#무작위로 섞어 버리고
random.shuffle(P)
q = q2 = 0
#1000개만 뽑아서 질문을 한다.
#위에서 H(P)를 구할 때 처럼 1또는 2입니까? 를 먼저 질문한다.
for x in P[:1000] :
q2 += 1
if x == 1 or x == 2:
q2 += 1
if x == 1:
pass #print(q2)
else :
pass #print(q2)
else :
q2 += 1
if x == 3:
pass #print(q2)
else :
pass #print(q2)
q += q2
q2 = 0
print("H(P(X)) = {}".format(q / 1000))
#50%, 12.5%, 12.5%, 25%로 샘플을 만들고
Q = [1]*1000 + [2]*250 + [3]*250 + [4]*500
random.shuffle(Q)
q = q2 = 0
#위에서 H(Q)를 구할 때 처럼 1번입니까? 4번입니까? 순으로 질문한다.
for x in Q[:1000] :
q2 += 1
if x == 1 :
pass#print(q2)
else :
q2 += 1
if x == 4 :
pass#print(q2)
else :
q2 += 1
if x == 3:
pass#print(q2)
else :
pass#print(q2)
q += q2
q2 = 0
print("H(Q(X)) = {}".format(q / 1000))
q = q2 = 0
#동일한 비율로 숫자가 존재하는 샘플 P를 대상으로 Q방식으로 질문한다.
#H(P,Q)
for x in P[:1000] :
q2 += 1
if x == 1 :
pass#print(q2)
else :
q2 += 1
if x == 4 :
pass#print(q2)
else :
q2 += 1
if x == 3:
pass#print(q2)
else :
pass#print(q2)
q += q2
q2 = 0
print("H(P(X),Q(X)) = {}".format(q / 1000))
H(P(X)) = 2.0 H(Q(X)) = 1.733 H(P(X),Q(X)) = 2.268
실제 우리의 계산 결과에 수렴하는것을 실험적으로도 확인할 수 있습니다.
즉, $H(P,Q)-H(P)$ 로 정의를 하면 다음과 같고 이를 쿨백-라이블러 발산이라 합니다.
$$
\begin{align}
D_{KL}(P || Q)
&=H(P,Q)-H(P) \\[4pt]
&= -\sum_{i=1} P(X=x_{i}) \log Q(X=x_{i}) - \left( -\sum_{i=1} P(X=x_{i}) \log P(X=x_{i}) \right)\\[4pt]
&= -\sum_{i=1} \left( P(X=x_{i}) \left( \log Q(X=x_{i}) - \log P(X=x_{i}) \right) \right) \\[4pt]
&= -\sum_{i=1} P(X=x_{i}) \frac{\log Q(X=x_{i})}{\log P(X=x_{i})} \\[4pt]
&= \sum_{i=1} P(X=x_{i}) log \frac{P(X=x_{i})}{Q(X=x_{i})} \\[4pt]
\end{align}
$$
두 확률분포의 상대적인 엔트로피를 나타내는 $D_{KL}$은 크로스엔트로피에 엔트로피를 뺀 것으로 항상 0보다 같거나 크며, 볼록함수Convex Function인 특징을 가지고 있습니다. 예를 들어 $P(X)$는 평균 6, 표준편차 1.5인 정규분포, $Q(X)$를 평균 0, 표준편차 1인 정규분포라 하면 $P(X)$, $Q(X)$를 확률분포로 가지는 두 확률변수로 부터 $D_{KL}$을 계산하면 0보다 큰 양수가 나오게 됩니다. $Q$가 $P$와 비슷해지면 값은 점점 작아지다 $P$와 동일해 지면 0이 됩니다.
하여튼 이 분포에서 임의로 5개씩 샘플을 추출하면 $P(X)$는 6 근처의 값이, $Q(X)$는 0 근처의 값이 추출될 것입니다. 이것이 GANs과 무슨 상관이 있는지 GANs 관점에서 이야기해보면 $P(X)$가 진짜 실세계의 확률분포라면 $Q(X)$에서 추출된 샘플은 쉽게 진짜가 아니라는 것을 알 수 있습니다. 만약 $Q(X)$의 분포를 조정해서 $P(X)$와 유사하게 만든다면 $Q(X)$에서 추출된 샘플을 $P(X)$에서 추출된 샘플과 구별할 수 없게 될것입니다. GANs의 핵심이 바로 $Q(X)$를 조정해서 $P(X)$와 같게 만드는 과정입니다. 어떻게 조정하는지 구체적인 이야기는 차차 하도록하고 여기서는 $D_{KL}$을 목적함수로 이용하여 확률분포 $P$, $Q$의 차이를 줄이는 최적화 과정을 실습해보겠습니다. $D_{KL}$은 확률분포 $Q(X)$ 도메인에서 볼록함수이고 볼록성에 대한 증명은 CSE 533: Information Theory in Computer Science [3] 세번째 강의 노트에서 확인할 수 있습니다. 하지만 우리 실험에서는 정규분포의 평균과 표준편차를 설계변수로 하고 쿨백-라이블러 발산을 코스트로 사용하면서 경사하강법을 적용할 것입니다. $D_{KL}$은 확률분포 $Q(X)$ 도메인에서 볼록함수이나 $Q(X)$의 평균과 표준편차에 대해서는 볼록성이 보장되지 않으므로 경사하강법이 잘 동작하기 위해서는 $D_{KL}$이 매개변수 공간에서 볼록한지 확인해봐야 합니다. 정규분포간의 쿨백-라이블러 발산이 매개변수 공간에서 볼록한지 엄밀하게 보이기 보다 여기서는 간단히 그래프를 통해서 확인해보도록하겠습니다. (TF-KR의 김성엽님께서 $D_{KL}$의 볼록성은 확률분포함수 공간에서 보장되지 함수의 매개변수 공간에서까지 보장되는 것이 아님을 코멘트해주셔서 오류를 수정했습니다.)
"""
쿨백-라이블러 발산 그래프 그리기
"""
import numpy as np
import matplotlib.pyplot as plt
#A normal continuous random variable.
from scipy.stats import norm
from scipy import stats
def cost(mu, sigma) :
P = norm(6, 1.5)
Q = norm(mu, sigma)
x = np.linspace(-10, 10, 100)
return stats.entropy(P.pdf(x), Q.pdf(x))
mus = np.linspace(4, 8, 100)
sigmas = np.linspace(0.5, 2.0, 100)
MUS, SIGMAS = np.meshgrid(mus, sigmas)
#X,Y를 순회하면서 cost를 계산해서 cost를 reshape
Z = np.array([cost(mu, sigma) for mu, sigma in zip(MUS.reshape(-1), SIGMAS.reshape(-1))]).reshape(MUS.shape)
levels1 = np.linspace(0, 0.1, 5)
levels2 = np.linspace(0.2, 2, 10)
CS = plt.contour(MUS, SIGMAS, Z, levels=np.concatenate((levels1, levels2)))
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Convexity of DKL')
plt.show()
import numpy as np
import matplotlib.pyplot as plt
#A normal continuous random variable.
from scipy.stats import norm
from scipy import stats
h = 0.01 #미분을 위한 작은 구간
epsilon = 1.e-5 #수렴 판단을 위한 매우 작은 수
alpha = 0.1 #step size
#확률변수의 구간
x = np.linspace(-5, 11, 100)
#진짜 확률 분포를 가지는 확률변수
P = norm(6, 1.25)
#params theta = [μ, σ] 확률분포를 조정하는 파라메터는 평균과, 표준편차
mu, sigma = 0, 1.0
#P와는 많이 다른 확률분포를 가지는 확률변수
Q = norm(mu, sigma)
plt.plot(x, P.pdf(x), 'r-', lw=2, alpha=0.6, label='P pdf')
plt.plot(x, Q.pdf(x), 'g-', lw=2, alpha=0.6, label='Q pdf')
plt.title('Init. P(x) and Q(x)')
plt.show()
"""
두 확률분포는 다르기 때문에 임의로 5개씩 샘플을 추출하면
p는 0 근처의 값이, q는 5 근처의 값이 추출됨.
"""
p = P.rvs(size=5)
q = Q.rvs(size=5)
print("Init. Samples of P(x) : {}".format(p))
print("Init. Samples of Q(x) : {}".format(q))
#P의 확률분포 엔트로피
print("Entorpy of P(x) : {:+f}".format(stats.entropy(P.pdf(x))))
#P와 Q의 확률분포 간의 DKL
dkl = stats.entropy(P.pdf(x), Q.pdf(x))
print("DKL of P(x),Q(x) : {:+f}".format(dkl))
dkls = [dkl]
for i in range(1000):
#경사 구하기 미분 {f(x+h)-f(x-h)} / 2h
dmu = (stats.entropy(P.pdf(x), norm(mu+h, sigma).pdf(x))-stats.entropy(P.pdf(x), norm(mu-h, sigma).pdf(x))) / (h*2)
dsigma = (stats.entropy(P.pdf(x), norm(mu, sigma+h).pdf(x))-stats.entropy(P.pdf(x), norm(mu, sigma-h).pdf(x))) / (h*2)
#경사하강 w = w - η* ∇f
mu -= alpha*dmu
sigma -= alpha*dsigma
#업데이트된 파라메터로 확률변수를 다시 만든다.
Q = norm(mu, sigma)
#목적함수를 평가하고 입실론보다 작으면 그만
dkl = stats.entropy(P.pdf(x),Q.pdf(x))
dkls.append(dkl)
if dkl < epsilon :
break;
plt.plot(dkls)
plt.title('Objective function')
plt.show()
print("ITER:{}, COST:{:+f}, mu:{:+f}, sigma:{:+f}".format(i, dkl, mu, sigma))
plt.plot(x, P.pdf(x), 'r-', lw=2, alpha=0.6, label='P pdf')
plt.plot(x, Q.pdf(x), 'g-', lw=2, alpha=0.6, label='Q pdf')
plt.title('Result')
plt.show()
"""
이제 Q에서 뽑은 샘플을 P에서 뽑은 샘플과 구별할 수 없어졌다.
"""
p = P.rvs(size=5)
q = Q.rvs(size=5)
print("Samples of P(x) : {}".format(p))
print("Samples of Q(x) : {}".format(q))
Init. Samples of P(x) : [ 6.66354488 5.42054792 5.50000437 5.08535966 9.21986506] Init. Samples of Q(x) : [ 0.05317294 0.55764156 0.29733264 -0.70787709 -0.0614358 ] Entorpy of P(x) : +3.464382 DKL of P(x),Q(x) : +18.057248
ITER:491, COST:+0.000009, mu:+5.994762, sigma:+1.250326
Samples of P(x) : [ 7.28329914 3.53113044 6.05022567 4.79881754 6.92992907] Samples of Q(x) : [ 5.40751218 4.32419414 5.38011482 5.60707463 7.25062323]
GANs를 numpy만으로 구현하기에는 코드양이 꽤 되고 GPU의 힘을 빌리지 않고는 훈련시키기에 많은 인내가 필요하므로 Keras를 쓰도록 하겠습니다. 우선 필요한 모듈을 로딩하고 보조 함수를 만듭니다. GAN은 D와 G를 따로 훈련시키는데 Goodfellow et al[7]에 의하면 G를 훈련할 때 D는 훈련하지 않습니다. Keras에는 모델과 레이어에 trainable이라는 속성을 제공합니다. 이 속성을 false 또는 true로 만드는 보조함수 make_trainable을 정의합니다.[8] (https://github.com/osh/KerasGAN)
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
from keras.models import Sequential, Model
from keras.layers import Dense, Activation, Input, normalization
from keras import optimizers
from keras.utils import np_utils
#np.random.seed(78)
#np.random.seed(0)
batch_size = 200
print_interval = 5000
def make_trainable(net, val):
"""
D의 param.들을 학습안되게 했다가 학습되게 했다가 전환시키기 위한 보조함수
"""
net.trainable = val
for l in net.layers:
l.trainable = val
Using Theano backend. WARNING (theano.sandbox.cuda): The cuda backend is deprecated and will be removed in the next release (v0.10). Please switch to the gpuarray backend. You can get more information about how to switch at this URL: https://github.com/Theano/Theano/wiki/Converting-to-the-new-gpu-back-end%28gpuarray%29 Using gpu device 0: GeForce GTX 1070 (CNMeM is enabled with initial size: 80.0% of memory, cuDNN 5105)
################################################################
# 학습 데이터 생성
################################################################
mu, sigma = 6, 1.25
def get_distribution_sampler(mu, sigma, N):
"""
주어진 평균과 표준편차로 N개의 정규분포 난수를 발생시키고 그 라벨로 1을 붙여서 되돌림
"""
data_xp, data_yp = np.random.normal(mu, sigma, N), np.ones(N)
data_p = np.vstack((data_xp, data_yp)).T
return data_p
"This is just the standard cross-entropy cost that is minimized when training
a standard binary classifier with a sigmoid output. The only difference is that the classifier is trained on two minibatches of data; one coming from the dataset, where the label is 1 for all examples, and one coming from the generator, where the label is 0 for all examples."
위 식에서 $\boldsymbol{\theta}^{(D)}$는 $D(\boldsymbol{x})$를 조정하는 매개변수, $\boldsymbol{\theta}^{(G)}$는 $G(\boldsymbol{z})$를 조정하는 매개변수입니다. 먼저 첫번째 항에 대해 이야기하면 $D(\boldsymbol{x})$는 데이터 $\boldsymbol{x}$를 입력받아 0~1을 출력하는 함수입니다. $\boldsymbol{x} \sim p_{\text{data}}$는 우리가 모은 데이터의 확률분포에서 추출한 데이터 $\boldsymbol{x}$라는 뜻으로 그냥 우리의 데이터셋에서 뽑은 데이터라는 뜻입니다. 즉, 진짜 데이터가 되겠습니다. 이 진짜 데이터에 대한 기대값이란 의미이며 또는 $\boldsymbol{x}$에 대한 평균으로 생각해도 되겠습니다. 두번째 항에서 $G(\boldsymbol{z})$는 잠재변수latent variable $\boldsymbol{z}$를 입력받아 우리가 원하는 데이터를 출력하는 함수입니다. 잠재변수는 보통 노이즈인데 우리 예제에서는 균등분포 난수를 사용하겠습니다. 이는 목표로 하는 확률분포가 정규분포인데 $G$의 입력을 정규분포로 넣어주는 것보다 균등분포로 넣어주는 것이 문제를 더 어렵게 만들기 때문입니다. 이것이 다시 $D$에 입력되니 결국 0~1의 값이 되고 $D$가 똑똑하다면 0 근처의 값을 출력해야 합니다. G를 고정시키고(지금 $G$는 그냥 열심히 가짜 데이터를 만들기만 하면 됨) $\boldsymbol{\theta}^{(D)}$에 대해서 위 식을 최소화 시킵니다. 자세한 상황은 아래 그림과 같습니다.
#loss for discriminator
loss_disc_real = tf.nn.sigmoid_cross_entropy_with_logits(disc_real, targets=tf.ones(batch_size))
loss_disc_fake = tf.nn.sigmoid_cross_entropy_with_logits(disc_fake, targets=tf.zeros(batch_size))
loss_disc = 0.5 * loss_disc_real + 0.5 * loss_disc_fake
################################################################
# 모델 생성
################################################################
#Discriminator
D = Sequential()
D.add(Dense(30, activation='relu', input_dim=1))
D.add(Dense(30, activation='relu'))
D.add(Dense( 2, activation='softmax'))
D_opt = optimizers.Adam(lr=0.001*1.58, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
D.compile(loss='binary_crossentropy', optimizer=D_opt, metrics=['accuracy'])
D.summary()
#Generator
G = Sequential()
G.add(Dense(20, input_dim=1))
G.add(Activation('sigmoid'))
G.add(Dense(40))
G.add(Activation('sigmoid'))
G.add(Dense(1))
#G.add(Activation('linear'))
G.summary()
#GAN 1 - D(G(z))
# 이 모델을 훈련시킬때 D는 업데이트 되면 안되므로 D의 trainable 을 False로 세팅
make_trainable(D, False)
gan_input = Input(shape=[1])
GAN = Model( gan_input, D(G(gan_input)) )
G_opt = optimizers.Adam(lr=0.001*1.1, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
GAN.compile(loss='mean_squared_logarithmic_error', optimizer=G_opt )
GAN.summary()
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 30) 60 _________________________________________________________________ dense_2 (Dense) (None, 30) 930 _________________________________________________________________ dense_3 (Dense) (None, 2) 62 ================================================================= Total params: 1,052 Trainable params: 1,052 Non-trainable params: 0 _________________________________________________________________ _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_4 (Dense) (None, 20) 40 _________________________________________________________________ activation_1 (Activation) (None, 20) 0 _________________________________________________________________ dense_5 (Dense) (None, 40) 840 _________________________________________________________________ activation_2 (Activation) (None, 40) 0 _________________________________________________________________ dense_6 (Dense) (None, 1) 41 ================================================================= Total params: 921 Trainable params: 921 Non-trainable params: 0 _________________________________________________________________ _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 1) 0 _________________________________________________________________ sequential_2 (Sequential) (None, 1) 921 _________________________________________________________________ sequential_1 (Sequential) (None, 2) 1052 ================================================================= Total params: 1,973 Trainable params: 921 Non-trainable params: 1,052 _________________________________________________________________
################################################################
#학습전에 G로 부터 숫자 생성하고 분포를 그려봄
################################################################
Z1 = np.random.uniform(0,1,10000)
fake1 = G.predict(Z1)
n, bins1, patches = plt.hist(Z1, 50, normed=1, facecolor='grey', alpha=0.2)
plt.title('Distribution of Init. P_g(x)')
n, bins, patches = plt.hist(fake1, 50, normed=1, facecolor='red', alpha=0.5)
plt.grid(True)
plt.axis([-1.5, 1.5, 0, 2])
plt.show()
print("G가 만들어낸 값 10개")
print(fake1[:10])
G가 만들어낸 값 10개 [[ 0.491036 ] [ 0.51990318] [ 0.52715808] [ 0.5348295 ] [ 0.5243696 ] [ 0.52756739] [ 0.49337378] [ 0.52823377] [ 0.52532172] [ 0.53196764]]
"in particular, G must not be trained too much without updating D, in order to avoid “the Helvetica scenario” in which G collapses too many values of $\boldsymbol{z}$ to the same value of $\boldsymbol{x}$ to have enough diversity to model $p_{\text{data}}$"
################################################################
# Discriminator 미리 학습
################################################################
print("*----------------------------------------------------------------")
print("* Discriminator 미리 학습")
print("*----------------------------------------------------------------")
make_trainable(D, True)
k = 200
real_mb = get_distribution_sampler(mu, sigma, batch_size*k)
fake_mb = np.hstack(
( G.predict( np.random.uniform(0,1,batch_size*k) ) ,
np.zeros(batch_size*k).reshape(batch_size*k,1)
)
)
print("- 진짜 샘플 5개")
print(real_mb[:5])
print("\n")
print("- 가짜 샘플 5개")
print(fake_mb[:5])
print("\n")
train_D = np.vstack((real_mb, fake_mb))
train_D = train_D[np.random.permutation(train_D.shape[0]), :]
train_Dx = train_D[:,0]
train_Dy = np_utils.to_categorical(train_D[:,1], 2)
D.fit(train_Dx, train_Dy, epochs=1, batch_size=batch_size)
print("\n")
print("*----------------------------------------------------------------")
print("* 선학습된 Discriminator 테스트")
print("*----------------------------------------------------------------")
Z = np.concatenate((real_mb[:5,0], fake_mb[:5,0]))
detec = D.predict(Z)
print("- Discriminator의 예측")
print(" 입력 출력1 출력2")
print(np.hstack((Z.reshape(10,1), detec)))
*---------------------------------------------------------------- * Discriminator 미리 학습 *---------------------------------------------------------------- - 진짜 샘플 5개 [[ 6.68444115 1. ] [ 6.36763976 1. ] [ 7.80758091 1. ] [ 4.53754296 1. ] [ 5.21056491 1. ]] - 가짜 샘플 5개 [[ 0.5125246 0. ] [ 0.51059741 0. ] [ 0.52134663 0. ] [ 0.52095461 0. ] [ 0.49219534 0. ]] Epoch 1/1 80000/80000 [==============================] - 0s - loss: 0.1023 - acc: 0.9739 *---------------------------------------------------------------- * 선학습된 Discriminator 테스트 *---------------------------------------------------------------- - Discriminator의 예측 입력 출력1 출력2 [[ 6.68444115e+00 8.25808977e-10 1.00000000e+00] [ 6.36763976e+00 3.05065284e-09 1.00000000e+00] [ 7.80758091e+00 8.90559241e-12 1.00000000e+00] [ 4.53754296e+00 5.79159314e-06 9.99994159e-01] [ 5.21056491e+00 3.60710459e-07 9.99999642e-01] [ 5.12524605e-01 9.97720420e-01 2.27951678e-03] [ 5.10597408e-01 9.97706890e-01 2.29313527e-03] [ 5.21346629e-01 9.97775495e-01 2.22444069e-03] [ 5.20954609e-01 9.97774541e-01 2.22549099e-03] [ 4.92195338e-01 9.97572720e-01 2.42733327e-03]]
################################################################
# 본학습
################################################################
print("*----------------------------------------------------------------")
print("* 본 학습 시작")
print("*----------------------------------------------------------------")
# the histogram of the data
plt.title('Distribution of P_data(x)')
n, bins1, patches = plt.hist(get_distribution_sampler(mu, sigma, 10000)[:,0], 50, normed=1, facecolor='blue', alpha=0.75)
y = mlab.normpdf(bins1, mu, sigma)
l = plt.plot(bins1, y, 'r--', linewidth=1)
plt.axis([-5, 12, 0, 0.45])
plt.grid(True)
plt.show()
nb_epoch = 45000
D_losses = []
GAN_losses = []
for epoch in range(nb_epoch) :
#train D
for k in range(3):
#진짜 데이터를 받아온다.
real_mb = get_distribution_sampler(mu, sigma, batch_size)
#가짜 데이터를 받아온다.
fake_mb = np.hstack(
( G.predict( np.random.uniform(0,1,batch_size) ) ,
np.zeros(batch_size).reshape(batch_size,1)
)
)
#진짜 가짜를 섞어서
train_D = np.vstack((real_mb, fake_mb))
train_D = train_D[np.random.permutation(train_D.shape[0]), :]
train_Dx = train_D[:,0]
train_DY = np_utils.to_categorical(train_D[:,1], 2)
#학습을 한다.
d_loss = D.train_on_batch(train_Dx, train_DY)
D_losses.append(d_loss)
#train GAN for G 균등분포 난수와 , 라벨 1을 입력으로 학습힌다.
g_loss = GAN.train_on_batch( np.random.uniform(0,1,batch_size) , np_utils.to_categorical(np.ones(batch_size),2) )
GAN_losses.append(g_loss)
if epoch % print_interval == 0:
print( "Epoch : {}, D:{}, G loss:{}".format(epoch, d_loss, g_loss) )
if epoch % 5000 == 0 :
fake = G.predict(Z1)
plt.title('Epoch {} Distribution of P_g(x)'.format(epoch))
plt.hist(fake, 50, normed=1, facecolor='green', alpha=0.75)
l = plt.plot(bins1, y, 'r--', linewidth=1)
plt.axis([-5, 12, 0, 0.45])
plt.grid(True)
plt.show()
#################################################################
# 그림 그리는 보조 코드들
#################################################################
plt.title('Discriminator loss and accuracy')
plt.plot(D_losses)
plt.show()
plt.title('GAN loss')
plt.plot(GAN_losses)
plt.show()
fake = G.predict(Z1)
plt.title('Distribution of P_g(x)')
plt.hist(fake, 50, normed=1, facecolor='green', alpha=0.75)
l = plt.plot(bins1, y, 'r--', linewidth=1)
plt.axis([-5, 12, 0, 0.45])
plt.grid(True)
plt.show()
print(fake[:10])
plt.title('Discriminator prediction to samples from G(z)')
detec = D.predict(fake)
plt.plot(detec)
plt.axis([0, 10000, 0, 1])
plt.show()
print(detec[:10])
*---------------------------------------------------------------- * 본 학습 시작 *----------------------------------------------------------------
Epoch : 0, D:[array(0.006260748021304607, dtype=float32), array(0.9975000023841858, dtype=float32)], G loss:0.4781073331832886
Epoch : 5000, D:[array(1.0960466312326389e-07, dtype=float32), array(1.0, dtype=float32)], G loss:0.48045283555984497
Epoch : 10000, D:[array(1.0960466312326389e-07, dtype=float32), array(1.0, dtype=float32)], G loss:0.48045283555984497
Epoch : 15000, D:[array(0.6459751725196838, dtype=float32), array(0.612500011920929, dtype=float32)], G loss:0.14864879846572876
Epoch : 20000, D:[array(0.6478808522224426, dtype=float32), array(0.5849999785423279, dtype=float32)], G loss:0.1522078514099121
Epoch : 25000, D:[array(0.6298986673355103, dtype=float32), array(0.5824999809265137, dtype=float32)], G loss:0.13378502428531647
Epoch : 30000, D:[array(0.6259119510650635, dtype=float32), array(0.5924999713897705, dtype=float32)], G loss:0.14465180039405823
Epoch : 35000, D:[array(0.645881175994873, dtype=float32), array(0.5774999856948853, dtype=float32)], G loss:0.1455087661743164
Epoch : 40000, D:[array(0.6705172061920166, dtype=float32), array(0.5174999833106995, dtype=float32)], G loss:0.13108354806900024
[[ 3.88417435] [ 6.38488531] [ 6.92629957] [ 7.74166536] [ 6.69731283] [ 6.96260023] [ 4.31207705] [ 7.02328825] [ 6.77207232] [ 7.40280771]]
[[ 0.53172719 0.46827284] [ 0.48797449 0.51202548] [ 0.46306628 0.53693372] [ 0.58682293 0.41317704] [ 0.47358564 0.52641439] [ 0.46170425 0.53829575] [ 0.4780958 0.52190423] [ 0.47148177 0.52851826] [ 0.47014824 0.52985179] [ 0.53282434 0.46717566]]
결과를 다시 한번 찍어보면 아래처럼 모양이 그럭저럭 잘 나오는 것을 확인할 수 있습니다.
plt.title('Distribution of P_g(x)')
plt.hist(G.predict(np.random.uniform(0,1,10000)), 50, normed=1, facecolor='green', alpha=0.75)
l = plt.plot(bins1, y, 'r--', linewidth=1)
plt.axis([-5, 12, 0, 0.45])
plt.grid(True)
plt.show()
Z = np.linspace(0,1,1000)
X = G.predict(Z)
fig = plt.figure()
fig.suptitle('G(z)', fontsize=14, fontweight='bold')
ax = fig.add_subplot(111)
ax.set_xlabel('z')
ax.set_ylabel('x')
plt.plot(Z,X)
plt.show()
어쨌거나 우리는 분포를 아는 확률변수 $\boldsymbol{z}$로 부터 어떤 매핑을 통해 $p_{g}(\boldsymbol{x})$를 분포로 가지는 확률변수 $\boldsymbol{x}$를 만들어주는 함수 $G(\boldsymbol{z})$를 찾아냈습니다. (일반 적으로 $\boldsymbol{z}$도 벡터 변수인데 우리 예제에서는 0~1사이의 숫자라서 엄밀히 말하면 볼드 $\boldsymbol{z}$가 아니라 그냥 $z$ 임)
실습을 통해 다음과 같은 사실을 알 수 있습니다.
The generator G implicitly defines a probability distribution $p_g$ as the distribution of the samples $G(z)$ obtained when $z ∼ p_z$.
$$ D^{*}_{G}(\boldsymbol{x}) = \frac{p_{data}(\boldsymbol{x})}{p_{data}(\boldsymbol{x})+p_{g}(\boldsymbol{x})} $$Proposition 1. For G fixed, the optimal discriminator D is
$$\int_{z \in Z} p(z)f(g(z)) dz = \int_{x \in X} p(x)f(x) dx$$
There is no need for the change of variable formula. $\int p_x(x) f(x) dx = \int p_z(z) f(g(z)) dz$ because $ \int p_x(x) dx$ is already equal by definition to $\int_{z: x=g(z)} p_z(z) dz$
마지막으로 $D(\boldsymbol{x})$는 $p_{data}(\boldsymbol{x})$와 $p_{g}(\boldsymbol{x})$가 0이 아닌 집합에 대해서만 정의되면 되므로(어떤 샘플 $\boldsymbol{x}$가 일어날 확률이 0인 것에 대해서는 참 거짖을 판별할 필요 없음) $Supp(p_{data}(\boldsymbol{x})) \cup Supp(p_{g}(\boldsymbol{x}))$ 에서만 정의되면 된다 라고 논문에서 언급합니다.
논문에서 증명 하기를 $(a,b) \in \mathbb{R}^{2} \backslash \{0,0\}$ (집합 (a,b)는 집합 {0,0}을 제외한 실수쌍인 집합)인 a, b에 대해서 $a \log(y) + b \log(1-y)$라는 함수를 [0,1]에서 y에 대해 미분해서 0인 점을 찾으면 최대값이 $ \frac{a}{a+b}$에서 나타난다고 하고 이 식에서 a에 해당하는것이 $p_{data}$이고 b에 해당하는 것이 $p_{g}$ 니까 a, b가 0이 아니었듯이 $p_{data}$, $p_{g}$도 0이 아닌 경우에 대해서 생각하기 위해 $Supp(p_{data}(\boldsymbol{x})) \cup Supp(p_{g}(\boldsymbol{x}))$ 에서만 정의되면 된다고 언급하고 증명을 마무리합니다.
Theorem 1. The global minimum of the virtual training criterion $C(G)$ is achieved if and only if $p_g = p_{data}$. At that point, $C(G)$ achieves the value $−log4$.
Proposition2. * If G and D have enough capacity,and at each step of Algorithm 1,the discriminator is allowed to reach its optimum given G, and $p_g$ is updated so as to improve the criterion*
$$
\mathbb{E}_{\boldsymbol{x} \sim p_{data}} \left[ \log D^{*}_{G}(\boldsymbol{x}) \right] +
\mathbb{E}_{\boldsymbol{x} \sim p_{g}} \left[ \log (1-D^{*}_{G}(\boldsymbol{x})) \right]
$$
The subderivatives of a supremum of convex functions include the derivative of the function at the point where the maximum is attained.
위 그림에서 보면 수프리멈은 구간별로 $ sup_{D} U(p_{g}, D) = U(p_g, D_{k}) $ 이라는 것을 알 수 있습니다. 그림에서 가장 왼쪽 구간은 $U(p_{g}, D_{3}) = sup_{D} U(p_{g}, D)$, 가운데 구간에서는 $U(p_{g}, D_{1}) = sup_{D} U(p_{g}, D)$, 왼쪽 구간에서는 $U(p_{g}, D_{2}) = sup_{D} U(p_{g}, D)$라는 것을 알 수 있습니다. 위에서 말했듯이 함수가 바뀌는 지점이외의 점에서는 유일한 미분값이 존재하고 함수가 바뀌는 지점에서는 무수히 많은 미분값이 subderivative를 이루므로 각 함수 $U(p_{g}, D_{1})$, $U(p_{g}, D_{2})$, $U(p_{g}, D_{3})$의 subderivative를 $sup_{D} U(p_{g}, D)$의 subderivative가 다 포함하게 됩니다. 그래서 어떤 임의의 $p_{g}$에서 맥시멈이 달성된 그 함수의 미분(the derivative of the function at the point where the maximum is attained.)은 $sup_{D} U(p_{g}, D)$의 subderivative(The subderivatives of a supremum of convex functions)가 다 포함하게 됩니다. 그림으로 다시 특정 예를 들어보겠습니다.
$\hat{p}_{g}$에서 맥시멈이 달성된 함수는 $U(p_{g}, D_{3})$입니다. $U(p_{g}, D_{3})$의 미분은 $sup_{D} U(p_{g}, D)$의 subderivative에 다 포함됨은 명백합니다. 이 상황을 논문에서 좀 더 포멀하게 다시 한번 더 이야기합니다.
In other words, if $f(x) = sup_{\alpha \in \mathcal{A}} f_{\alpha}(x)$ and $f_{\alpha}(x)$ is convex in $x$ for every $\alpha$, then $\partial f_{\beta}(x) \in \partial f$ if $\beta = argsup_{\alpha \in \mathcal{A}}f_{\alpha}(x)$.
This is equivalent to computing a gradient descent update for $p_g$ at the optimal $D$ given the corresponding $G$.
위 그림에 그 과정을 나타내었습니다. 우선 임의의 $\hat{p}_{g}$가 있을 때 1번 과정으로 $D$에 대해 최대화 시키고 그 상태에서 2번 과정으로 $p_g$에 대해 최소화를 시킵니다. 업데이트된 $p_g$에서 3번 과정으로 $D$에 대해 최대화 시키고 다시 4번 과정으로 $p_g$에 대해 최소화를 시켜서 수프리멈의 전역 최솟점을 찾을 수 있습니다. 이는 Algorithm 1에서 제안했던 방식과 정확히 일치하는 방식입니다. 이런 방식으로 $p_{g}=p_{data}$가 되는 최적점으로 수렴한다는것을 보인것 입니다. 다만 위 그림처럼 순차적으로 $D$에 대해 최대화, $p_g$에 대해 최소화를 반복하면 어떤 $U(p_{g}, D_{k_{1}})$, $U(p_{g}, D_{k_{2}})$의 최소점 사이를 왔다 갔다 할 수 있습니다. 실제로 그런 현상이 일어나는 것을 위에 실험 결과로 정리했습니다.