Gorio Tech Blog search

f-GAN(f-GAN 논문 설명)

|

이 글에서는 2016년 6월 Sebastian Nowozin 등이 발표한 f-GAN - Training Generative Neural Samplers using Variational Divergence Minimization를 살펴보도록 한다.

f-GAN은 특정한 구조를 제안했다기보다는 약간 divergence에 대한 내용을 일반적으로 증명한 수학 논문에 가깝다.

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.

이 논문은 수학이 넘쳐흐르는 논문이다.


f-GAN

논문 링크: f-GAN

초록(Abstract)

2016년에 나온 논문임을 생각하라.

Generative neural sampler는 random input vector를 입력으로 받아 network weights에 정의된 확률분포로부터 sample을 만들어내는 확률적 모델이다. 이 모델들은 sample과 도함수 계산이 효율적이지만 우도(likelihood)나 주변화(marginalization)을 계산하진 못한다. 적대생성적 학습방법은 이런 모델이 추가 신경망을 통해 이를 학습할 수 있게 해준다.
우리는 이 적대생성적 접근법이 더 일반적인 변분 발산(variational divergence) 추정 접근의 특별한 경우임을 보일 것이다. 우리는 임의의 f-divergence가 Generative neural sampler에 쓰일 수 있음을 보일 것이다. 우리는 이렇게 다양한 divergence 함수를 쓸 수 있는 것이 학습 복잡도와 생성모델의 품질 면에서 이득임을 논할 것이다.


서론(Introduction)

확률적 생성모델은 주어진 domain $\chi$ 상의 확률분포를 서술한다. 예를 들면 자연언어 문장, 자연 이미지, 녹음된 파형 등의 분포가 있다.

가능한 모델 집합 $Q$에서 생성모델 Q가 주어졌을 때 우리는 일반적으로 다음에 관심이 있다:

  • Sampling. Q로부터 sample을 생성한다. sample을 살펴보거나 어떤 함숫값을 계산해봄으로써 우리는 분포에 대한 통찰을 얻거나 결정문제를 풀 수 있다.
  • Estimation. 알려지지 않은 진짜 분포 P로부터 iid sample ${x_1, x_2, …, x_n}$이 주어졌을 때, 이 진짜 분포를 가장 잘 설명하는 Q $\in Q$를 찾는다.
  • Point-wise 우도 측정. sample $x$가 주어지면, 우도 Q($x$)를 계산한다.

GAN은 정확한 sampling과 근사추정이 가능한 인상적인 모델이다. 여기서 사용된 모델은 균등분포 같은 random input vector를 받는 feedforward 신경망이다. 최종적으로 모델을 통과하여 나오는 것은 예를 들면 이미지이다. GAN에서 sampling하는 것은 딱 1개의 input이 신경망을 통과하면 정확히 하나의 sample이 나온다는 점에서 효율적이다.

이런 확률적 feedforward 신경망을 generative neural samplers라고 부를 것이다. GAN도 여기에 포함되며, 또한 variational autoencoder의 decoder 모델이기도 하다.

original GAN에서, neural sample를 JSD의 근사적 최소화로 추정하는 것이 가능함이 증명되어 있다.

[D_{JS}(P | Q) = {1 \over 2} D_{KL}(P | {1 \over 2}(P+Q)) + {1 \over 2} D_{KL}(Q | {1 \over 2}(P+Q))]

$D_{KL}$은 Kullback–Leibler divergence이다.

GAN 학습의 중요한 테크닉은 동시에 최적화된 Discriminator 신경망을 만든 것에 있다. 왜냐하면 $D_{JS}$는 진짜 분포 $P$는 충분한 학습을 통해 Q가 $P$에 충분히 가까워졌을 때 분포 간 divergence를 측정하는 적정한 방법이기 때문이다.

우리는 이 논문에서 GAN 학습목적(training objectives)과 이를 임의의 f-divergence로 일반화하고자, GAN을 variational divergence 추정 framework로 확장할 것이다.

구체적으로, 이 논문에서 보여주는 state-of-the-art한 것은:

  • GAN 학습목적을 모든 f-divergence에 대해 유도하고 여러 divergence 함수를 소개할 것이다: Kullback-Leibler와 Pearson Divergence를 포함한다.
  • 우리는 GAN의 saddle-point 최적화를 단순화할 것이고 또 이론적으로 증명할 것이다.
  • 자연 이미지에 대한 generative neural sampler을 측정하는 데 어느 divergence 함수가 적당한지 실험적 결과를 제시하겠다.

방법(Method)

먼저 divergence 추정 framework를 리뷰부터 하겠다. 이후 divergence 추정에서 model 추정으로 확장하겠다.

The f-divergence Family

Kullback-Leibler divergence같이 잘 알려진 것은 두 확률분포 간 차이를 측정한다.

두 분포 $P$와 $Q$가 있고, domain $\chi$에서 연속밀도함수 $p$와 $q$에 대해 f-divergence
$ f : \mathbb{R}_+ \rightarrow \mathbb{R} $이 $f(1)=0$인 볼록이고 하반연속인(convex, lower-semicontinuous) 함수 $f$에 대해

[D_f(P \Vert Q) = \int_{\chi} q(x) f \Bigl( {p(x) \over q(x)} \Bigr) dx]

로 정의된다.

Variational Estimation of f-divergences

Nyugen 등 연구자는 $P$와 $Q$로부터의 sample만 주어진 경우에서 f-divergence를 측정하는 일반적인 변분법을 유도했다. 우리는 이를 고정된 모델에서 그 parameter를 측정하는 것으로까지 확장할 것이고, 이를 variational divergence minimization(VDM)이라 부를 것이다. 또한 적대적 생성 학습법은 이 VDM의 특수한 경우임을 보인다.

모든 볼록이고 하반연속인 볼록 켤레함수 $f^\ast$ (Fenchel conjugate)를 갖는다. 이는

[f^\ast(t) = \quad sup \quad { ut-f(u) } \ u \in dom_f \qquad]

로 정의된다.

또한 $f^\ast$ 역시 볼록이며 하반연속이고 $f^{\ast\ast} = f$이므로 $ f(u) = sup_{t \in dom_{f^\ast}} { tu-f^\ast(t) } $로 쓸 수 있다.

Nguyen 등 연구자는 lower bound를 구했다: $\tau$가 $T: \chi \rightarrow \mathbb{R} $인 함수들의 임의의 집합일 때,

[D_f(P \Vert Q) \ge sup_{T \in \tau} (\mathbb{E}{x \sim P} [T(x)] - \mathbb{E}{x \sim Q} [f^\ast(T(x))])]

변분법을 취해서,

[T^\ast(x) = f^{‘} \Bigl( {p(x) \over q(x)} \Bigr)]

아래는 여러 f-divergence를 생성함수와 함께 나타낸 것이다.

Variational Divergence Minimization(VDM)

이제 실제 분포 $P$가 주어졌을 때 생성모델 $Q$를 측정하기 위해 f-divergence $D_f(P\Vert Q)$에 하한을 적용할 수 있다.

벡터 $\theta$를 받는 모델 $Q$를 $Q_{\theta}$, $\omega$를 쓰는 $T$를 $T_{\omega}$로 썼을 때, 우리는 다음 f-GAN 목적함수의 saddle-point를 찾는 것으로 $Q_{\theta}$를 학습시킬 수 있다.

[F(\theta, \omega) = \mathbb{E}{x \sim P} [T{\omega}(x)] - \mathbb{E}{x \sim Q{\theta}} [f^\ast({T_\omega}(x))]]

주어진 유한한 학습 데이터셋에 대해 위 식을 최적화하려면, minibatch를 통해 기댓값을 근사해야 한다.

  • \(\mathbb{E}_{x \sim P}[\cdot]\)를 근사하기 위해 학습 셋으로부터 비복원추출하여 $B$개를 뽑고,
  • \(\mathbb{E}_{x \sim Q_{\theta}}[\cdot]\)를 근사하기 위해 현재 생성모델 $Q_{\theta}$로부터 $B$개를 뽑는다.

Representation for the Variational Function

위의 식을 다른 f-divergence에도 사용하려면 켤레함수 $f^\ast$의 도메인 $dom_{f^\ast}$를 생각해야 한다. $T_\omega (x) = g_f(V_\omega(x)) $로 바꿔 쓸 수 있다.

이제 GAN 목적함수를 보면, divergence가 sigmoid이므로

[F(\theta, \omega) = \mathbb{E}{x \sim P} [log D{\omega}(x)] - \mathbb{E}{x \sim Q{\theta}} [log(1-D_\omega(x))]]

출력 활성함수는 Table 6을 보라(부록).

Example: Univariate Mixture of Gaussians

가우시안 sample에 대해 근사한 결과를 적어 놓았다.


VDM 알고리즘(Algorithms for Variational Divergence Minimization(VDM))

이제 우리는 목적함수의 saddle point를 찾기 위한 수치적 방법을 논할 것이다.

  1. Goodfellow가 제안한 교대(alternative) 학습 방법
  2. 더 직접적인 single-step 최적화 과정

두 가지를 쓴다.

Single-Step Gradient Method

원래 것과는 달리 inner loop가 없고, 단 하나의 back-propagation으로 $\omega$와 $\theta$의 gradient가 계산된다.

saddle point 근방에서 $\theta$에 대해 볼록하고 $\omega$ 에 대해 오목한 $F$에 대해 위 알고리즘 1은 saddle point $(\theta^\ast, \omega^\ast)$에서 수렴함을 보일 수 있다.

이를 위해 다음 정리를 논문 부록에서 보이고 있다.

Theorem 1. $\pi^t := (\theta^t, \omega^t) $ 라 하고, 조금 위의 근방 조건을 만족하는 saddle point $ \pi^\ast = (\theta^\ast, \omega^\ast) $ 가 존재한다고 가정하자. 더욱이 위 근방에 포함되는 $ J(\pi) = {1\over 2} \Vert \nabla F(\pi) \Vert_2^2 $ 를 정의할 수 있고, $F$는 $ \pi^\ast $ 근방 모든 $ \pi, \pi^{‘} $ 에 대해 $ \Vert \nabla J(\pi^{‘}) - \nabla J(\pi) \Vert_2 \le L \Vert \pi^{‘} - \pi \Vert_2 $ 를 만족하는 상수 $ L > 0 $ 가 존재할 수 있게 하는 $F$는 충분히 smooth하다.
알고리즘 1에서 step-size를 $ \eta=\delta / L$ 라 할 때,

[J(\pi^t) \le \Bigl( 1 - {\lambda^2 \over 2L} \Bigr)^t J(\pi^0)]

를 얻을 수 있다.
또 gradient $ \nabla F(x) $ 의 2차 norm은 기하적으로 감소한다.

Practical Considerations

Goodfellow가 GAN 논문 당시 제안한 팁 중에 \( \mathbb{E}_{x \sim Q_{\theta}} [log(1-D_\omega(x))]\)를 최소화하는 대신 \( \mathbb{E}_{x \sim Q_{\theta}} [log D_\omega(x)] \)를 최대화하는 것으로 속도를 빠르게 하는 것이 있었다.
이를 더 일반적인 f-GAN에 적용하면

[\theta^{t+1} = \theta^t + \eta \nabla_\theta \mathbb{E}{x \sim Q{\theta^t}} [g_f(V_{\omega^t}(x))]]

그렇게 함으로써 generator 출력을 최대화할 수 있다.

실험적으로, 우리는 Adam과 gradient clipping이 LSUN 데이터셋의 대규모 실험에서는 특히 유용함을 발견하였다.


실험(Experiments)

이제 VDM에 기초하여 MNIST와 LSUN에 대해 학습시킨 결과는 다음과 같다.

결과 요약을 하면… 약간 예상 외로 divergence 함수가 달라져도 결과의 품질은 큰 차이가 없었다고 한다.


관련 연구(Related Work)

오직 신경망에 적용할 수 있는 것에 대해서만 논하겠다.

  • Mixture density networks: 유한한 mixture 모델의 parameter를 직접 회귀시키는 데 쓸 수 있다.
  • NADE and RNADE: 사전에 정의되었고 어느 정도 임의의 출력 차원을 가진 출력의 factorization을 수행한다.
  • Diffusion probabilistic models: 자명하고 알려진 분포에서 출발하는 학습된 발산과정의 결과로 목표 분포를 정의한다.
  • Noise contrasive estimation(NCE): 임의로 생성된 noise로부터 데이터를 식별하는 비선형 logistic 회귀를 수행하여 비정규화된 확률모델의 parameter를 추정하는 방법이다.
  • Variational auto-encoders(VAE): 변분법적 베이지안 학습 목표함수를 갖고 sample을 잠재표현식으로 매핑하는 확률적 encoder와 decoder 모델의 쌍이다.

토의(Discussion)

Generative neural samplers는 factorizing 가정 없이도 복잡한 분포를 표현하는 강력한 방법을 제공한다. 그러나 이 논문에서 사용된 순수 generative neural samplers는 관측된 데이터에 대한 조건부로 적용할 수 없고 따라서 그로부터 추론할 것이 없다는 한계를 갖고 있다.

우리는 미래에는 표현의 불확실성을 위한 neural samplers의 진면목이 식별 모델에서 발견될 것이며 생성자와 조건부 GAN 모델에 추가적인 input을 넣음으로써 쉽게 이 경우에 대해 확장할 수 있을 것이라 믿는다.


참고문헌(References)

논문 참조!


부록

  • Section A: 이 부분이다.
  • Section B: f-divergence의 확장된 리스트(생성함수와 볼록 켤레함수)를 나열하였다.
  • Section C: Theorem 1를 증명한다. (논문에는 Theorem 2라 되어 있는데 같은 것이다)
  • Section D: 현재 GAN 최적화 알고리즘과 차이를 논한다.
  • Section E: 다양한 divergence 측정방법을 써서 Gaussian을 혼합 Gaussian 분포에 맞춤으로써 우리의 접근법을 증명한다.
  • Section F: 본문에서 사용한 모델의 세부 구조를 보여준다.

증명의 자세한 부분은 논문을 보는 것이 빠르므로 생략하겠다.

MNIST 생성자:
$z \rightarrow Linear(100, 1200) \rightarrow BN \rightarrow ReLU \rightarrow Linear(1200, 1200) $
$ \rightarrow BN \rightarrow ReLU \rightarrow Linear(1200, 784) \rightarrow Sigmoid $

모든 weights는 0.05 scale로 초기화되었다.

MNIST Variational Function:
$ x \rightarrow Linear(784, 240) \rightarrow ELU \rightarrow Linear(240, 240) \rightarrow ELU \rightarrow Linear(240, 1) $

ELU는 exponential linear unit이다. 모든 weights는 0.005 scale로 초기화되었다.

LSUN Natural Images:
$ z \rightarrow Linear(100, 6\ast6\ast512) \rightarrow BN \rightarrow ReLU \rightarrow Reshape(512, 6, 6) \rightarrow Deconv(512, 256) \rightarrow BN $
$ \rightarrow ReLU \rightarrow Deconv(256, 128) \rightarrow BN \rightarrow ReLU \rightarrow Deconv(128, 64) \rightarrow BN \rightarrow ReLU \rightarrow Deconv(64, 3) $

deconv는 kernel size 4, stride 2를 사용하였다.

Deconv는 Deconvolution을 의미하는데, DCGAN 글에서도 설명하였듯 잘못된 표현이다.


이후 연구들

GAN 이후로 수많은 발전된 GAN이 연구되어 발표되었다.

많은 GAN들(catGAN, Semi-supervised GAN, LSGAN, WGAN, WGAN_GP, DRAGAN, EBGAN, BEGAN, ACGAN, infoGAN 등)에 대한 설명은 다음 글에서 진행하도록 하겠다.


Comment  Read more

CGAN(Conditional GAN), C-GAN 논문 설명

|

이 글에서는 2014년 11월 Mehdi Mirza 등이 발표한 Conditional Generative Adversarial Nets(CGAN)를 살펴보도록 한다.

CGAN은 GAN의 변형 모델이다.

(즉 DCGAN보다는 먼저 나왔다. 하지만 DCGAN이 GAN의 역사에서 제일 중요한 것 중 하나이기 때문에 CGAN을 나중으로 미뤘다.)

CGAN은 GAN과 학습 방법 자체는 별로 다를 것이 없다(D 학습 후 G 학습시키는 것).
GAN의 변형 모델들은 대부분 그 모델 구조를 바꾼 것이다.

CGAN을 도식화한 구조는 다음과 같다. 출처

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


Conditional GAN(CGAN)

논문 링크: Conditional GAN

초록(Abstract)

2014년에 나온 논문임을 생각하라.

최근 GAN이 생성모델을 학습시키는 근사한 방법으로 소개되었다. 우리는 이 GAN의 조건부(conditional) 버전, 간단히 $y$ 데이터를 추가하여 만든 적대적 망을 소개하려 한다. 이 CGAN이 class label(숫자 0~9)에 맞는 MNIST 이미지를 생성할 수 있음을 보일 것이다. 또한 이 모델이 multi-modal 모델에 어떻게 사용될지, 또 이미지 태깅에 어떻게 응용 가능할지도 또한 설명할 것이다.


서론(Introduction)

생성 모델을 학습하기 위해, 다루기 힘든 엄청난 확률적 계산의 어려움을 대체하는 GAN이 최근 소개되었다. 적대신경망은 Markov chain이 필요없이 오직 back-propagation만으로 학습이 가능하고, 별다른 추측도 할 필요가 없다.

Unconditional 생성모델에서, 데이터가 생성되는 종류(mode)를 제어할 방법은 없다. 그러나, 추가 정보를 통해 데이터 생성 과정을 제어할 수 있다. 이러한 조건 설정(conditioning)은 class label 등에 기반할 수 있다.

이 논문에서 우리는 conditional 적대신경망을 구현할 것이다. 또 이를 MNIST와 MIR Flickr 데이터셋에 대해 테스트한다.


관련 연구(Related Works)

궁금하면 읽어보자.

Multi-modal Learning for Image Labelling

굉장히 많은 카테고리를 다룰 수 있는 모델에 관한 문제는 추가 modality에 대한 정보를 다루는 것으로 일부 해결 가능하다. 단어를 vector representation으로 변형하는 것 등이 있다.

input-output 1-1 매칭에만 치중한 문제는 conditional 확률적 생성모델을 사용하는 것이 한 방법이 될 수 있다.

자세한 내용은 원문을 보고 각 논문을 찾아보라. 이미 요약된 부분이라 그냥 건너뛰거나 본문을 보는 것이 더 낫다.


조건부 적대신경망(Conditional Adversarial Nets)

GAN(Genearative Adversarial Nets)

최근 소개된 GAN은 다음 두 부분으로 이루어졌다. 둘 다 non-linear하게 매핑하는 함수일 수 있다.

  • 데이터분포를 입력받아 실제에 가깝게 데이터를 생성하는 생성모델 G
  • 입력받은 데이터가 진짜 데이터인지 G가 만들어낸 것인지를 판별하는 D

다음 식으로 표현되는 minimax 게임을 G와 D가 진행하게 된다:

[min_G max_D V(D, G) = \mathbb{E}{x \sim p{data}(x)}[log D(x)] + \mathbb{E}{x \sim p{z}(z)}[log (1-D(G(z)))]]

수식에 대한 자세한 설명은 GAN을 참고하라.

CGAN(Conditional Adversarial Nets)

G와 D가 추가 정보 $y$라는 조건이 붙는다면 조건부 생성모델을 만들 수 있다. $y$는 어떤 보조 정보라도 될 수 있는데, class label이나 다른 modality의 데이터 등이다. 우리는 $y$를 G와 D의 input layer에 추가로 같이 집어넣음으로써 이를 수행할 수 있다.

G에서는 input noise $p_z(z)$와 $y$가 합쳐진 형태가 된다. 이 적대적 학습 framework는 이 hidden representation이 어떻게 생겼는지에 별 영향을 받지 않는다.
D에서는 $x$와 $y$가 input으로써 들어가게 된다.

좀 전 수식을 conditional 버전으로 바꿔보면,

[min_G max_D V(D, G) = \mathbb{E}{x \sim p{data}(x)}[log D(x y)] + \mathbb{E}{x \sim p{z}(z)}[log (1-D(G(z y)))]]

참고: D와 G에 들어가는 input이 단지 조건부로 바뀌었다. 실제 들어가는 형태는 합쳐진 형태이다.


실험 결과(Experimental Results)

이미 좋다는 게 알려진 논문의 경우에는 굳이 실험 조건 등을 자세히 볼 필요는 없다. 여기서는 결과만 소개한다.

Unimodal

모델 구조는 다음과 갈다.

  • G
    • uniform distribution $z$. size=100
    • $z$와 $y$는 각각 size 200, 1000짜리 hidden layer(ReLU)로 매핑됨
    • 1200짜리 hidden layer로 합쳐짐(ReLU)
    • 마지막으로 784차원으로 변환됨(MNIST 이미지는 $28^2$이다)
  • D
    • $x$는 240 unit과 5 piece짜리 maxout layer, $y$는 50 unit과 5 piece짜리 maxout layer로 매핑됨
    • 240 unit, 5 piece짜리 maxout layer로 합쳐진 후 Sigmoid

MNIST로 실험한 결과이다. Log-likelihood 값이 잘 나왔음을 확인할 수 있다.

Model MNIST
DBN 138 $\pm $ 2
Stacked CAE 121 $\pm $ 1.6
Deep GSN 214 $\pm $ 1.1
Adversarial nets 225 $\pm $ 2
Conditional adversarial nets 132 $\pm $ 1.8

$y$ 데이터는 각 row별로 0~9까지 들어갔다. 아래는 CGAN을 통해 생성된 이미지이다.

주어지는 조건($y$)에 따라 class가 잘 나뉘는 것은 확인할 수 있다(이미지 품질은 original GAN과 비슷하다).

Multimodal

여러 이미지들에 대해 사람이 직접 넣은 태그와 CGAN이 생성해낸 태그를 비교한 테이블을 가져왔다.

가장 오른쪽 열이 생성된 태그 중 제일 나은 것 10개를 나열한 것인데, 꽤 잘 된 것으로 보인다.


추후 연구(Future work)

이 논문에서 소개된 결과는 서론 정도의 내용이지만, 각각은 조건부 생성모델의 잠재력과 다른 많은 분야로의 응용에 대한 가능성을 보여 준다.

이번 실험에서는 태그를 독립적으로 사용했지만, 한번에 여러 태그를 사용한다면 더 나은 결과를 얻을 수 있을 것이다.

추후 연구의 또 다른 방향은 언어 모델을 배우는 학습계획을 구현하는 것이 있겠다.

Acknowledgments

이 프로젝트는 Pylearn2 framework로 개발되었다.

참고문헌(References)

논문 참조!


튜토리얼

GAN의 핵심 부분을 제외한 부분은 여기를 참고하면 된다.

여기에서 CGAN을 학습시켜볼 수 있다. 해당 repository에는 CGAN뿐 아니라 많은 종류의 GAN이 Pytorch로 구현되어 있으므로 참고하면 좋다.


이후 연구들

GAN 이후로 수많은 발전된 GAN이 연구되어 발표되었다.

많은 GAN들(catGAN, Semi-supervised GAN, LSGAN, WGAN, WGAN_GP, DRAGAN, EBGAN, BEGAN, ACGAN, infoGAN 등)에 대한 설명은 여기, f-GAN에 대한 설명은 여기에서 진행하도록 하겠다.


Comment  Read more

DCGAN(Deep Convolutional GAN, DCGAN 논문 설명)

|

이 글에서는 2015년 11월 Alec Radford 등이 발표한 DCGAN(Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks)를 살펴보도록 한다.

DCGAN은 GAN의 개선 모델로 GAN과 다른 점은 다음과 같다.

  • $D$
    • Strided Convolution을 사용한다.
    • Batch Normalization을 사용한다. 입력 레이어(첫 번째)에는 사용하지 않는다.
    • activation function으로 Leaky ReLU를 사용한다.
  • $G$
    • Fractional Strided Convolution(Transposed Convolution)을 사용한다.
    • Batch Normalization을 사용한다. 출력 레이어(마지막)에는 사용하지 않는다.
    • activation function으로 ReLU를 사용하고 마지막 레이어에는 tanh를 사용한다.

참고: 논문에서 deconvolution이라 되어 있는 것은 Transposed 또는 fractional strided convolution을 의미한다. 이 연산은 엄밀히 말해 convolution의 역연산이 아니기 때문에(그 비슷한 것을 의도하긴 했지만) deconvolution은 사실 틀린 표현이다.

그래서 나아진 점, 혹은 알아낸 것은?

  • (흔히 생각하는 FHD를 넘는 고해상도랑은 거리가 멀지만) 고해상도 이미지를 생성할 수 있게 되었다.
  • 거의 대부분의 상황에서 안정적인 학습이 가능하다.
  • 단순히 이미지를 기억(overfitting)하는 것이 아님을 보였다.
  • convolution의 각 filter는 의미 있는 부분에 대한 정보를 갖고 있다. 논문에서는 침실 데이터를 사용하였는데, 어떤 필터는 창문에 대한 정보를 갖고 있는 식이다. 논문에서는 이를 시각화하여 보여주었다.
  • input인 noise($z$)는 별 의미 없는 값이 아니라, 이것이 생성될 이미지의 특징을 결정하는 벡터이다. 논문에서는,
    • 웃는 여자를 생성한 noise $z_1$
    • 무표정 여자를 생성한 noise $z_2$
    • 무표정 남자를 생성한 noise $z_3$
    • $z_4 :=$ $z_1$ - $z_2$ + $z_3$이라 할 때
    • $z_4$를 noise로 쓰면 웃는 남자를 생성해낸다.
  • 또 왼쪽을 보는 사람과 오른쪽을 보는 사람을 생성한 두 벡터를 interpolating하면 마치 얼굴을 회전시킨 듯한 중간 결과들이 얻어진다.

DCGAN은 GAN과 학습 방법 자체는 별로 다를 것이 없다(D 학습 후 G 학습시키는 것).

참고: $G$로 들어가는 입력 벡터를 뜻하는 noise는 latent variable이라고도 하며, Auto-encoder에서 출력 영상을 만들기 위한 source와 비슷하기에 이 표현도 사용된다.

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


논문(DCGAN)

논문 링크: Deep Convolutional GAN

초록(Abstract)

2015~2016년에 나온 논문임을 생각하라.

최근에는 CNN을 통한 supervised learning 연구가 많이 이루어졌지만 unsupervised learning은 별 주목을 받지 못했다. 우리는 Deep Convolutional GANs를 소개하여 그 간극을 좁히고자 한다. 여러 이미지 데이터셋을 학습시키면서 우리는 DCGAN의 G와 D 모두가 object로부터 유의미한 표현 구조를 찾았음을 보였다. 또, 이를 일반적인(general) 이미지 표현에도 응용해 보았다.


서론(Introduction)

GAN은 최대우도(maximum likelihood) 테크닉의 매력적인 대체재이다. 또한 그 학습 방법과 heuristic cost function가 적다는 것 때문에 representation learning에도 훌륭히 잘 쓸 수 있다. 다만 학습이 불안정하고 G가 터무니없는 output을 내뱉을 때가 있다. 그래서 상당히 제한적으로 쓰일 수밖에 없었다.

이 논문에서는, 우리는 다음과 같은 것들을 보일 것이다:

  • 거의 대부분의 상황에서 학습이 안정적인 Convolutional GAN을 제안하고 평가한다. 이것이 DCGAN이다.
  • D에게 image classification를 시켜봤는데, 거의 state-of-the-art한 결과를 보인다.
  • 특정 필터가 특정 object를 그려낸다는 것을 시각화한다.
  • G에 들어가는 noise에 산술 연산을 한 결과로 많은 의미있는 이미지를 생성함을 보인다.

관련 연구(Related Works)

궁금하면 읽어보자.

Representation Learning from Unlabeled Data

Unsupervised representation learning은 꽤 잘 연구되었다. 전통적인 접근 방법으로는 clustering(K-means)이 있다.
이미지 쪽에서는 image representation을 학습하기 위한 구조적 clustering, auto-encoder를 학습시키는 것, what/where 분리 구조, image를 간략한 code로 encode하고 다시 이미지로 복원하는 decoder를 포함하는 사다리 구조 등등이 있었다.
Deep belief networks도 구조적 표현방식을 학습하는 데 좋은 성능을 보였다.

Generating Natural Images

이건 두 종류가 있다: parametric과 non-parametric.

database에 존재하는 이미지 찾기 등을 수행하는 non-parametric 모델들은 texture synthesis, super-resolution, in-painting 등에 사용되었다.

Parameteric 모델은 꽤 널리 알려졌지만(MNIST), 성공적인 것은 별로 없다. 대부분 흐린(blurry) 이미지만을 생성해냈다.
GAN이 생성한 것은 noise가 많고 이해하기 어려웠다. Laplcian pyramid extension, recurrent network, deconvolution network 등의 접근은 자연 이미지를 생성하는 데 성공적이었지만 supervised task에 generator를 활용하진 않았다.

Visualizing the Internals of CNNs

Neural Networks의 문제점은 너무 black-box같다는 것이다(참고: 네트워크의 각 필터 등이 정확히 무엇을 의미하는지 사람이 이해할 수가 없다). 다만 각 필터의 의미를 찾으려는 시도는 있었다.

자세한 내용은 원문을 보고 각 논문을 찾아보라.


접근법과 모델 아키텍처(Approach and Model Architecture)

GAN에 CNN을 써서 이미지 품질을 높이려는 시도는 지금까지 성공적이지 못했다.

우리는 많은 시도 끝에 다양한 데이터셋에서 안정적인 그리고 더 높은 해상도의 이미지를 생성하는 모델 구조를 찾아내었다.
핵심은 다음 3가지를 CNN 구조에 적용시키는 것이다.

  1. max-pooling과 같은 미분불가능한 레이어를 strided convolution으로 바꿔 spatial downsampling이 가능하게 한 것이다. 이는 G에 사용된 것이고, D에는 upsampling이 가능하게 바꿨다.
  2. 요즘 트렌드는 FC(Fully Connected) Layer를 없애고 convolution layer로 바꾸는 것이다.
  3. Batch Normalization을 사용하여 학습을 안정화시킨다(참고: 2019년 현재 BN은 거의 필수처럼 되어 있다). 이는 weight 초기화가 나쁘게 된 경우와 깊은 모델에서 gradient flow를 도우며, 이는 학습 초기에 잘못된 방향으로 학습이 진행되어 망하는 경우를 막아준다. 그러나 sample이 요동치는 것을 막기 위해 G의 출력 레이어와 D의 input layer에는 넣지 않았다(이건 많은 시도 끝에 알아낸 듯).

G에서는 activation function으로 ReLU를 사용하고 마지막 레이어에는 tanh를 사용한다. Bounded activation(tanh)은 더 빠르게 수렴하고 학습샘플의 분포를 따라갔다. D에는 Leaky ReLU를 사용하여 높은 해상도를 만들 수 있게 하였다. 이는 GAN과 다른 부분이다.

적대적 학습 상세(Details of Adversarial Training)

우리는 Large-scale Scene Understanding(LSUN), Imagenet-1k, Faces 데이터셋으로 학습을 진행했다.

  • pre-processing은 쓰지 않았고
  • size 128인 mini-batch SGD
  • (0, 0.02) 정규분포를 따르는 초기화
  • Leaky ReLU의 기울기는 0.2
  • AdamOptimizer(0.0002, 0.9)

로 했다. AdamOptimizer의 beta1을 0.5로 줄이는 것보다 학습 안정성이 좋았다.

모델 구조는 아래와 같다.

단 1 epoch만 학습시켰을 때의 결과. minibatch SGD를 썼기 때문에 이미지를 기억한다고는 볼 수 없다. 따라서 overfitting 없이 잘 생성하고 있는 것이다.

5 epoch만 학습시켰을 때의 결과. 침대 근처 noise로 볼 때 오히려 underfitting이 일어난 것 같다.


DCGAN의 능력의 경험적 검증(Empirical Validation of DCGANs Capabilities)

Unsupervised representation learning 알고리즘을 평가하는 일반적인 방법은 supervised 데이터셋에 대해 특징 추출을 시킨 뒤 performance를 측정하는 것이다.

검증 요약:

  • CIFAR-10 데이터셋에 대해 검증한 결과, 다른 방법들(K-means, Exemplar CNN 등)과 비교하여 정확도가 별 차이가 없었다!(80.6~84.3%, DCGAN은 82.8%)
  • StreetView House Numbers dataset(SVHN)은 state-of-the-art 결과를 얻었다.

네트워크 내부 조사 및 시각화(Investigating and Visualizing the Internals of the Networks)

우리는 가장 가까운 학습 데이터 이미지를 찾거나, 최근접 픽셀이나 특징 혹은 log-likelihood metric 같은 방법은 별로이기 때문에 사용하지 않았다.

생성된 2개의 이미지에 사용된 noise인 $z$를 선형 보간하며 그 보간된 $z$로 이미지를 생성시켜본 결과 한 이미지에서 다른 이미지로 서서히 변해가는 결과를 얻었다(아래 그림). 이미지를 보면 창문 없는 방이 거대한 창문이 있는 방으로 변해 가거나(6th row), TV가 창문으로 변해가는 과정(10th row)을 볼 수 있다.

벡터 산술 연산을 통해, vec(웃는 여자) $-$ vec(무표정 여자) $+$ vec(무표정 남자) $=$ vec(웃는 남자) 같은 결과를 얻을 수 있다.

네트워크 내부의 각 필터는 이해할 수 없는 형식이 아닌 특정 object나 특징을 추출하였음을 알 수 있다.


결론 및 추후 연구(Conclusions and future work)

우리는 안정적인 생성모델을 제안하였고 이 적대정 생성모델은 image representation에 탁월함을 보여 주었다. 그러나 아직 오래 학습시킬 시 필터 일부가 요동치는 것 등 모델에 불안정성이 남아 있다.

추후 연구는 이를 안정화하는 방법을 찾는 것이 될 것이다. 또한 이 framework를 영상 또는 음성 등의 다른 domain에도 확장시킬 수도 있다.

Acknowledgments

Ian GoodFellow 등의 연구자와 Nvidia Titan-X GPU에 감사를 표한다.

(광고인줄)

참고문헌(References)

논문 참조!


튜토리얼

공식 튜토리얼

DCGAN이 특별히 중요하기 때문인지 Pytorch 공식 홈페이지에 튜토리얼이 있다.

GAN의 핵심 부분을 제외한 부분은 여기를 참고하면 된다.

https://pytorch.org/tutorials/beginner/dcgan_faces_tutorial.html


이후 연구들

GAN 이후로 수많은 발전된 GAN이 연구되어 발표되었다. 가장 중요한 것 두 개는 GAN의 학습 불안정성을 많이 개선시킨 DCGAN(Deep Convolutional GAN), 단순 생성이 목적이 아닌 원하는 형태의 이미지를 생성시킬 수 있게 하는 CGAN(Conditional GAN)일 듯 하다.

많은 GAN들(catGAN, Semi-supervised GAN, LSGAN, WGAN, WGAN_GP, DRAGAN, EBGAN, BEGAN, ACGAN, infoGAN 등)에 대한 설명은 여기에서, CGAN에 대한 설명은 다음 글에서 진행하도록 하겠다.


Comment  Read more

GAN(Generative Adversarial Networks), GAN 논문 설명

|

이 글에서는 2014년 6월 Ian J. Goodfellow 등이 발표한 Generative Adversarial Networks(GAN, 생성적 적대신경망)를 살펴보도록 한다.

간단히 GAN은 두 가지 모델을 동시에 학습시키는 구조이다. G(Generator, 생성자)라는 모델은 직접 볼 수 없는 진짜 데이터와 최대한 비슷하게 생긴 가짜 데이터를 만드려고 하고, D(Distriminator, 식별자 또는 감별자)라는 모델은 자신에게 주어진 데이터가 진짜 데이터인지 가짜 데이터인지 최대한 구분하려고 한다.

GAN을 도식화한 구조는 다음과 같다. 출처

논문에서는 설명을 위한 예시로 화폐 위조범($G$)와 경찰($D$)을 제시하였다. 다만 차이가 있다면,

  • 위조범은 진짜를 볼 수 없다는 것(그래서 장님blind라 불린다)
  • 경찰은 자신이 판별한 결과를 위조범에게 알려준다 는 것이 있다.

참고: $G$로 들어가는 입력 벡터를 뜻하는 noise는 latent variable이라고도 하며, Auto-encoder에서 출력 영상을 만들기 위한 source와 비슷하기에 이 표현도 사용된다.
또 GAN은 특정한 모델 구조를 가진 것이 아니므로 코드가 특별히 정해진 것은 아니다.

논문을 적절히 번역 및 요약하는 것으로 시작한다. 많은 부분을 생략할 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


Generative Adversarial Networks(GAN)

논문 링크: Generative Adversarial Networks

초록(Abstract)

이 논문에서는 적대적으로 동작하는 두 생성 모델을 동시에 학습시키는 새 framework를 제안한다. 생성자 G는 원본 data distribution을 흉내내려 하고, D는 눈앞의 데이터가 G에게서 온 것인지를 판별한다. G의 목적은 D가 최대한 실수하게 만드는 것이고, D는 당연히 최대한 정확하게 진짜/가짜를 판별하는 것이다.
이는 2인 minimax 게임과 비슷하다. 어떤 유일한 해가 존재하여 최종적으로 D는 실수할 확률이 0.5가 된다(즉 찍는 수준).
G와 D가 multi-layer perceptron으로 구성되면 전체 시스템은 backpropagation으로 학습될 수 있다.
GAN에는 어느 과정에서든 마르코프 체인이나 기타 다른 네트워크가 필요가 전혀 없다.


서론(Introduction)

적대적인 두 네트워크를 학습시킨다. D는 원본 data distribution인지 G에서 온 것인지를 판별하고, G는 D가 실수하도록 가짜 데이터를 잘 만들어내는 것이 목표이다.
이 framework는 많은 특별한 학습 알고리즘과 optimizer를 사용할 수 있다. 앞서 말한 대로 multi-layer perception을 쓰면 다른 복잡한 네트워크는 필요 없이 오직 forward/backpropagation만으로 (이 논문에서는 dropout을 또 쓴다) 학습이 가능하다.


관련 연구(Related Works)

궁금하면 읽어보자.

  • RBMs: restricted Boltzmann machines, 잠재 변수를 가진 유향 그래프 모델에 대한 대안으로, 무향 그래프 모델
  • DBMs: deep Boltzmann machines, RBMs와 비슷함. 다양한 변형이 존재
  • MCMC: Markov chain Monte Carlo methods, 위 모델의 측정 방법
  • DBNs: Deep belief networks, 하나의 무향 레이어와 여러 유향 레이어의 hybrid 모델. 계삭적 문제가 있음
  • NCE: noise-contrasive estimation, log-likelihood를 근사하거나 경계값을 구하지 않는 방법
  • GSN: generative stochastic network, 확률분포를 명시적으로 정의하지 않고 분포 샘플을 생성하도록 학습시키는 방법을 사용
  • adversarial nets: 적대적 망은 생성 중 feedback loop를 필요로 하지 않아 sampling에서 Markov chain이 필요가 없다. 이는 backpropagation 성능 향상으로 이어진다.
  • auto-encoding varitional Bayes와 stochastic backpropagation은 생성 머신을 학습시키는 방법들 중 하나이다.

적대적 망(Adversarial nets)

기호 설명
$x$ 데이터
$p_g$ $x$에 대한 생성자의 분포
$p_z(z)$ input noise 변수
$\theta_g$ multilayer perceptrions의 parameters
$G$ $\theta_g$에 의해 표현되는 미분가능한 함수
$G(z; \theta_g$) data space에 대한 mapping
$D(x)$ $x$가 $p_g$가 아니라 원본 데이터에서 나왔을 확률
$D(x; \theta_d)$ 두 번째 multilayer perceptron

D의 목적은 데이터가 ‘원본’인지 ‘G가 생성한 데이터’인지 판별하는 것이므로 어떤 데이터에 대해 정확한 label(‘원본’ 또는 ‘G로부터’)을 붙이는 것이다. G의 목적은 D가 실수하게 만드는 것, 즉 어떤 데이터가 주어졌을 때 D가 ‘원본’이라고 판별할 확률과 ‘G로부터 나온 데이터’라고 판별할 확률을 모두 높이는 것(정확히는 같게)이다.
즉 $log(1-D(G(z)))$를 최소화하도록 G를 훈련시킨다.

D와 G 모두에 대해 value function $V(G, D)$를 정의하면,

[min_G max_D V(D, G) = \mathbb{E}{x \sim p{data}(x)}[log D(x)] + \mathbb{E}{x \sim p{z}(z)}[log (1-D(G(z)))]]

위 식의 의미는,

  • $min_G$: G는 V를 최소화하려고 한다.
  • $max_D$: D는 V를 최대화하려고 한다. 2-player minimax 게임과 같으므로 당연하다.
  • $\mathbb{E}$: 기댓값
  • $x \sim p_{data}(x)$: $x$가 원본 데이터 분포에서 왔을 때

D가 아주 똑똑한 경찰이라면, $x$가 실제로 원본에서 온 것이라면 $D(x)=1$이 될 것이고, $G(z)$에서 온 것이라면 $D(G(z))=0$이 된다. 만약 G가 완벽한 위조범이 되었다면, $D(x) = {1 \over 2}$이다.
따라서 D의 입장에서 V의 최댓값은 0이 되며, G의 입장에서 V의 최솟값은 $-\infty$임을 알 수 있다.

학습시킬 때, inner loop에서 D를 최적화하는 것은 매우 많은 계산을 필요로 하고 유한한 데이터셋에서는 overfitting을 초래하기 때문에, $k$ step만큼 D를 최적화하고 G는 1 step만 최적화하도록 한다.
학습 초반에는 G가 형편없기 때문에 D는 진짜인지 G가 생성한 것인지를 아주 잘 구분해 낸다.
또 G가 $log(1-D(G(z)))$를 최소화하도록 하는 것보다는 $log(D(G(z)))$를 최대화하도록 하는 것이 더 학습이 잘 된다. 이는 G가 형편없을 때는 $log(1-D(G(z)))$의 gradient를 계산했을 때 너무 작은 값이 나와 학습이 느리기 때문이라고 한다.

파란 점선은 disctiminative distribution(D), 검정색은 원본 데이터($p_x$), 초록색은 생성된 분포$p_g$(G), $x$는 원본 데이터 분포를, 화살표는 $x=G(z)$ mapping을 나타낸다. (a) 초기 상태. (b) D 학습 후, (c) G 학습 후, 분포가 비슷해지는 것을 볼 수 있다. (d) 여러 번의 학습 끝에 G가 완전히 원본을 흉내낼 수 있는 경지에 도달함. 즉 $p_g = p_{data}$. D는 이제 진짜인지 가짜인지 구분할 수 없다. 즉 $D(x) = {1 \over 2}$.


이론적 결과(Theoretical Results)

수학을 좋아한다면 직접 읽어보자.

  • Algorithm 1
    • for epochs do
      • for k steps do
        • noise prior $p_g(z)$로부터 $m$개의 noise sample $z^{(1)}, …, z^{(m)}$을 뽑는다.
        • noise prior $p_{data}(x)$로부터 $m$개의 noise sample $x^{(1)}, …, x^{(m)}$을 뽑는다.
        • D를 다음 stochastic gradient로 update한다. (ascending)
          • $ \nabla_{\theta_d} {1 \over m} \sum^m_{i=1} [log D(x^{(i)}) + log (1-D(G(z^{(i)})))] $
      • noise prior $p_g(z)$로부터 $m$개의 noise sample $z^{(1)}, …, z^{(m)}$을 뽑는다.
      • G를 다음 stochastic gradient로 update한다. (descending)
        • $ \nabla_{\theta_d} {1 \over m} \sum^m_{i=1} [log (1-D(G(z^{(i)})))] $
  • 이 minimax 게임은 $p_g = p_{data}$에 대한 global optimum을 가진다.
    • G를 고정했을 때, optimal한 D는 다음과 같다.

[D^*G(x) = {p{data}(x) \over p_{data}(x) + p_g(x)}]

  • Algorithm 1은 수렴한다.

실험(Experiments)

MNIST, Toronto Face Database(TFD), CIFAR-10에 대해 학습을 진행했다.

  • G는 rectifier linear activations와 sigmoid를 사용했고, D는 maxout activations를 사용했다.
  • Dropout은 D를 학습시킬 때 사용했다.
  • noise는 G에서 가장 밑의 레이어에만 input으로 넣었다.

자세한 실험 조건은 직접 읽어보자.

가장 오른쪽 열은 바로 옆에 있는 생성된 이미지와 가장 비슷한 학습 샘플이다. a) MNIST b) TFD c) CIFAR-10(fully connected model) d) CIFAR-10(convolutional D와 “deconvolutional” G)

숫자 간 보간을 했을 때는 위와 같이 된다. 물론 GAN을 통해 생성한 것이다.


장단점(Advantages and disadvantages)

단점

  • $p_g(x)$가 명시적으로 존재하지 않는다.
  • D는 G와 균형을 잘 맞추어서 성능이 향상되어야 한다(G는 D가 발전하기 전 너무 발전하면 안 된다).

장점

  • 마르코프 체인이 전혀 필요 없이 backprop만으로 학습이 된다.
  • 특별히 어떤 추론(inference)도 필요 없다.
  • 다양한 함수들이 모델에 접목될 수 있다.
  • 마르코프 체인을 썼을 때에 비해 훨씬 선명한(sharp) 이미지를 결과로 얻을 수 있다.

결론 및 추후 연구(Conclusions and future work)

  1. conditional generative model로 발전시킬 수 있다(CGAN).
  2. Learned approximate inference는 $x$가 주어졌을 때 $z$를 예측하는 보조 네트워크를 학습함으로써 수행될 수 있다.
  3. parameters를 공유하는 조건부 모델을 학습함으로써 다른 조건부 모델을 대략 모델링 할 수 있다. 특히, deterministic MP-DBM의 stochastic extension의 구현에 대부분의 네트워크를 쓸 수 있다.
  4. Semi-supervised learning에도 활용 가능하다. classifier의 성능 향상을 꾀할 수 있다.
  5. 효율성 개선: G와 D를 조정하는 더 나은 방법이나 학습하는 동안 sample $z$에 대한 더 나은 distributions을 결정하는 등의 방법으로 속도를 높일 수 있다.

참고문헌(References)

논문 참조!


보충 설명

목적함수

D의 목적함수는 G를 고정한 채로 진짜 데이터 $m$개와 가짜 데이터 $m$개를 D에 넣고, G에 대한 V를 계산한 뒤 gradient를 구하고 V를 높여 D를 최종적으로 업데이트한다.

[max_D V(D) = {1 \over m } \sum^m_{i=1} log D(x^i) + {1 \over m } \sum^m_{i=1} log D(1 - D(G(z^i)))]

G의 목적함수는 D를 고정한 채로 가짜 데이터 $m$개를 생성해 V을 계산한 뒤, G에 대한 V의 gradient를 계산하고 V를 낮춰 G를 업데이트한다.
G의 목적함수는 gradient가 0에 가까워지는 것을 막기 위해 논문에서 언급된 팁을 반영한 것이다.

[min_G V(G) = {1 \over m} \sum^m_{j=1} log(D(G(z^j)))]

목적함수 최적화의 의미

Machine Learning 관점에서 보면 모델이 loss가 최소화되는 parameter를 찾아가는 과정이다.
또는 진짜 데이터의 분포와 G가 생성한 가짜 데이터 분포 사이의 차이를 줄이는 것과도 같다.

수학적으로는 D가 이미 최적이라는 가정 하에, GAN이 목적함수를 최적화한다는 과정($p_{data}$와 $p_g$를 똑같이 만드려는 것)은 $p_{data}$와 $p_g$ 사이의 Jensen-Shannon divergence(JSD)를 최소화하는 것과 같다.
JSD는 Kullback–Leibler divergence의 대칭(symmetrized and smoothed) 버전이다. 그래서 GAN은 KLD를 최소화하는 것이라고 말하기도 한다.

분포 $P$와 $Q$에 대해, $KLD = D(P \Vert Q), M = {1 \over 2}(P+Q)$라 할 때, JSD는

[JSD(P \Vert Q) = {1 \over 2} D(P \Vert M) + {1 \over 2} D(Q \Vert M)]

이다.


학습 방법

GAN은 서로 경쟁하는 두 가지 모델을 학습시킨다. GAN을 쓰려면 다음 방법을 따른다.

  1. 우선 다음을 정의한다.
    1. R(Real): 실제 데이터. 논문에선 $x$로 표시
    2. I(Input 또는 Imaginary): G가 가짜 데이터를 생성할 source. 논문에선 $z$로 표시.
      • $G(z)$는 $G$가 $z$를 입력으로 받아 생성한 가짜 데이터이다.
    3. $G$(generator): 생성자, 위조범
    4. $D$(Distriminator): 감별자 또는 식별자, 경찰
  2. 다음 전체 과정을 num_epochs 동안 반복한다:
    1. D를 training하는 과정(d_steps만큼 반복): D와 G를 모두 사용은 하지만 D의 parameter만 업데이트한다.
      1. $D$에 실제 데이터($x$)와 정답(1)을 입력으로 주고 loss를 계산한다.
      2. $D$에 가짜 데이터($G(z)$)와 정답(0)을 입력으로 주고 loss를 계산한다.
      3. 두 loss를 합친 후 $D$의 parameter를 업데이트한다.
    2. G를 training하는 과정(g_steps만큼 반복): D와 G를 모두 사용은 하지만 G의 parameter만 업데이트한다.
      1. $D$에 가짜 데이터($G(z)$)와 정답(1)을 입력으로 주고 loss를 계산한다.
      2. 계산한 loss를 이용하여 $G$의 parameter를 업데이트한다.

단점 및 극복방안

GAN 논문에서는 수학적인 증명이 포함되어 있지만(최소 해를 가지며, 충분히 학습할 시 항상 그 해답을 찾는다), 여러 요인들로 인해 실제 학습시킬 때에는 학습이 좀 불안정하다는 단점이 있다.

Mode Collapsing

간단히 이 현상은 학습 모델이 실제 데이터의 분포를 정확히 따라가지 못하고 그저 뭉뚱그리기만 하면서 다양성을 잃어버리는 것이다.
예를 들면 1~9까지의 숫자 9개를 만드는 대신 5만 9개 만드는 것과 비슷하며, MNIST의 경우 10종류의 모든 숫자가 아닌 특정 숫자들만 생성하는 경우이다.

이는 GAN이 단순히 목적함수의 loss만을 줄이려는 방향으로 설정되어 있어 생기는 현상이다. 이 현상은 GAN의 개선 모델들에서 대부분 해결된다.

Oscillation

G와 D가 수렴하지 않고 진동하는 모양새를 보일 때가 있다. 이 역시 비슷한 이유로 발생하며, 나중 모델들에서 해결된다.

G와 D 사이의 Imbalance

학습을 진행하면 처음에는 D가 발전하고 나중에 G가 급격히 학습되는 형상을 보이는데, 처음부터 D가 너무 성능이 좋아져버리면 오히려 G가 학습이 잘 되지 않는 문제가 발생한다(D가 시작부터 G의 기를 죽이는 셈).

해결방안

  • 진짜 데이터와 가짜 데이터 간 Least Square Error를 목적함수에 추가한다(LSGAN).
  • 모델의 구조를 convolution으로 바꾼다(DCGAN)
  • mini-batch별로 학습을 진행할 경우 이전 학습이 잘 잊혀지는 것을 막기 위해 이를 기억하는 방향으로 학습시킨다.

튜토리얼

50줄로 짜보는 튜토리얼

원문 링크는 여기, 번역본은 여기에서 볼 수 있다.
해당 튜토리얼에서는

  1. 이 전체 과정을 num_epochs(여기서는 5000)만큼 반복한다.
    1. training D(d_steps만큼 반복):
      1. 가우시안 분포를 따르는 데이터를 Real Data로 생성하고
      2. 그 momentum(mean, std, skews, kurtoses)를 계산하여 D에게 전달, error를 계산한다.
      3. 또 Fake data를 G가 생성하게 하고
      4. D가 error를 계산하게 한다.
      5. 위 두 과정(1~2, 3~4)으로 D의 parameter를 업데이트한다.
    2. training G(g_steps만큼 반복):
      1. G로 Fake data를 생성한다.
      2. D에게서 판별 결과를 받아온다.
      3. G가 error를 계산하게 한다.
      4. G의 parameter를 업데이트한다.

코드는 원문에도 소개되어 있지만 전체는 사실 186줄이다(…) 물론 GAN의 핵심 코드는 50줄 정도이다.

MNIST 튜토리얼

GAN의 핵심 부분을 제외한 부분은 여기를 참고하면 된다.

우선 기본 설정부터 하자.

import torch
import torch.nn as nn
from torch.optim import Adam
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

import argparse

from matplotlib import pyplot as plt
import numpy as np

import pickle
import os
import imageio


parser = argparse.ArgumentParser(description='GAN tutorial: MNIST')

parser.add_argument('--epochs', type=int, default=100, help='number of epochs')
parser.add_argument('--batch-size', type=int, default=64, help='size of mini-batch')
parser.add_argument('--noise-size', type=int, default=100, help='size of random noise vector')
parser.add_argument('--use-cuda', type=bool, default=True, help='use cuda if available')
parser.add_argument('--learning-rate', '-lr', type=float, default=0.0002, help='learning rate of AdamOptimizer')
parser.add_argument('--beta1', type=float, default=0.5, help='parameter beta1 of AdamOptimizer')
parser.add_argument('--beta2', type=float, default=0.999, help='parameter beta2 of AdamOptimizer')
parser.add_argument('--output-dir', type=str, default='output/', help='directory path of output')
parser.add_argument('--log-file', type=str, default='log.txt', help='filename of logging')

args = parser.parse_args()

os.makedirs(args.output_dir, exist_ok=True)

use_cuda = args.use_cuda and torch.cuda.is_available()

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=(0.5,), std=(0.5,))
])

mnist = datasets.MNIST(root='data', download=True, transform=transform)
dataloader = DataLoader(mnist, batch_size=args.batch_size, shuffle=True)

Generator는 다음과 같이 선언한다. 레이어는 총 4개, activation function은 LeakyRELU와 Tanh를 사용하였다.

class Generator(nn.Module):
    
    def __init__(self):
        super(Generator, self).__init__()
        self.linear1 = nn.Linear(in_features=100, out_features=256)
        self.linear2 = nn.Linear(in_features=256, out_features=512)
        self.linear3 = nn.Linear(in_features=512, out_features=1024)
        self.linear4 = nn.Linear(in_features=1024, out_features=28 ** 2)
    
    
    def forward(self, x):
        """
        :param x: input tensor[batch_size * noise_size]
        :return: output tensor[batch_size * 1 * 28 * 28]
        """
        x = nn.LeakyReLU(0.2)(self.linear1(x))
        x = nn.LeakyReLU(0.2)(self.linear2(x))
        x = nn.LeakyReLU(0.2)(self.linear3(x))
        x = nn.Tanh()(self.linear4(x))
        return x.view(-1, 1, 28, 28)

Discriminator는 다음과 같다. Linear Layer는 G의 역방향으로 가는 것과 비슷하지만, activation function에는 차이가 있다.

class Discriminator(nn.Module):
    
    def __init__(self):
        super(Discriminator, self).__init__()
        self.linear1 = nn.Linear(in_features=28 ** 2, out_features=1024)
        self.linear2 = nn.Linear(in_features=1024, out_features=512)
        self.linear3 = nn.Linear(in_features=512, out_features=256)
        self.linear4 = nn.Linear(in_features=256, out_features=1)
    
    
    def forward(self, x):
        """
        :param x: input tensor[batch_size * 1 * 28 * 28]
        :return: possibility of that the image is real data
        """
        x = x.view(-1, 28 ** 2)
        x = nn.LeakyReLU(0.2)(self.linear1(x))
        x = nn.Dropout()(x)
        x = nn.LeakyReLU(0.2)(self.linear2(x))
        x = nn.Dropout()(x)
        x = nn.LeakyReLU(0.2)(self.linear3(x))
        x = nn.Dropout()(x)
        return nn.Sigmoid()(self.linear4(x))

GAN의 핵심 부분은 다음과 같다. 위의 Gaussian 분포 예제와 크게 다르지 않아서 크게 설명은 필요없을 듯 하다. 차이점을 조금 적어보자면

  1. 분포의 momentum을 G의 데이터 생성 source로 사용하는 대신 길이 100(MNIST의 경우 보통)짜리 random vector를 사용한다. G는 이 길이 100짜리 벡터를 갖고 MNIST의 숫자 이미지와 비슷한 이미지를 생성하려고 하게 된다.
  2. .cuda()DataLoader를 사용하는 것 정도가 있겠으나 GAN의 핵심 부분은 아니다.
    for epoch in range(args.epochs):
        for D_real_data, _ in dataloader:
            
            batch_size = D_real_data.size(0)
            
            # Training D with real data
            D.zero_grad()
            
            target_real = torch.ones(batch_size, 1)
            target_fake = torch.zeros(batch_size, 1)
            
            if use_cuda:
                D_real_data, target_real, target_fake = \
                D_real_data.cuda(), target_real.cuda(), target_fake.cuda()
            
            D_real_decision = D(D_real_data)
            D_real_loss = criterion(D_real_decision, target_real)
            
            # Training D with fake data
            
            z = torch.randn((batch_size, args.noise_size))
            if use_cuda: z = z.cuda()
            
            D_fake_data = G(z)
            D_fake_decision = D(D_fake_data)
            D_fake_loss = criterion(D_fake_decision, target_fake)
            
            D_loss = D_real_loss + D_fake_loss
            D_loss.backward()
            
            D_optimizer.step()
            
            # Training G based on D's decision
            G.zero_grad()
            
            z = torch.randn((batch_size, args.noise_size))
            if use_cuda: z = z.cuda()
            
            D_fake_data = G(z)
            D_fake_decision = D(D_fake_data)
            G_loss = criterion(D_fake_decision, target_real)
            G_loss.backward()
            
            G_optimizer.step()

전체 코드는 여기를 참조하라.


이후 연구들

사실 2014년 발표된 original GAN은

  • 학습이 불안정하고
  • 고해상도 이미지는 생성하지 못하는 한계를 갖고 있었다. 논문에서 optimal point가 있고 그쪽으로 수렴한다는 것을 보였지만, 실제로는 여러 변수 때문에 학습이 항상 잘 되는 것이 아니라는 현상을 보인다. 이러한 문제를 보완하기 위해 GAN 이후로 수많은 발전된 GAN이 연구되어 발표되었다.

그 중에서 가장 중요한 것을 3가지 정도만 뽑자면

  1. Convolution을 사용하여 GAN의 학습 불안정성을 많이 개선시킨 DCGAN(Deep Convolutional GAN, 2015)
  2. 단순 생성이 목적이 아닌 원하는 형태의 이미지를 생성시킬 수 있게 하는 시초인 CGAN(Conditional GAN, 2014)
  3. GAN이 임의의 divergence를 사용하는 경우에 대해 local convergence함을 보여주고 그에 대해 실제 작동하는 GAN을 보여준 f-GAN(2016)

일 듯 하다.

많은 GAN들(catGAN, Semi-supervised GAN, LSGAN, WGAN, WGAN_GP, DRAGAN, EBGAN, BEGAN, ACGAN, infoGAN 등)에 대한 설명은 여기에서, DCGAN에 대해서는 다음 글에서 진행하도록 하겠다.


Comment  Read more

Python argparse 사용법

|

이 글에서는 Python 패키지인 argparse에 대해 알아본다. Machine Learning 코드를 볼 때 꽤 자주 볼 수 있을 것이다.


Import

import argparse

argparse

python train.py --epochs 50 --batch-size 64 --save-dir weights

Machine Learning을 포함해서, 위와 같은 실행 옵션은 많은 코드에서 볼 수 있었을 것이다. 학습 과정을 포함하여 대부분은 명령창 또는 콘솔에서 python 파일명 옵션들...으로 실행시키기 때문에, argparse에 대한 이해는 필요하다.

중요:

  • 기본적으로 argparse 라이브러리는 명령창(터미널)에서 실행하는 것을 원칙으로 한다. Jupyter notebook이나 (iPython) 대화형 실행 framework에서는 제대로 실행되지 않을 수 있다. 또한 이러한 대화형 framework에서는 코드 상에서 명시적으로 집어 넣는 게 아닌 이상 인자에 값을 바로 줄 수도 없다.
  • 그래도 쓰고 싶다면 args = parser.parse_args()args = parser.parse_args(args=[])로 바꾸고 사용할 수는 있다…하지만 위의 이유로 인해 별 의미는 없을 듯하다.

필자는 이 글에서 위의 명령 중 --epochs와 같은 것을 인자, 50과 같은 것을 (같이 준) 으로 부르겠다.

argparse는 python에 기본으로 내장되어 있다.

import argparse
import os

import os는 output directory를 만드는 등의 역할을 위해 필요하다.

argparse를 쓰려면 기본적으로 다음 코드가 필요하다.

import argparse

parser = argparse.ArgumentParser(description='Argparse Tutorial')
# argument는 원하는 만큼 추가한다.
parser.add_argument('--print-number', type=int, 
                    help='an integer for printing repeatably')

args = parser.parse_args()

for i in range(args.print_number):
    print('print number {}'.format(i+1))
  1. 일단 ArgumentParser에 원하는 description을 입력하여 parser 객체를 생성한다. description 외에도 usage, default value 등을 지정할 수 있다.
  2. 그리고 add_argument() method를 통해 원하는 만큼 인자 종류를 추가한다.
  3. parse_args() method로 명령창에서 주어진 인자를 파싱한다.
  4. args라는 이름으로 파싱을 성공했다면 args.parameter 형태로 주어진 인자 값을 받아 사용할 수 있다.

실행 결과

> python argparseTest.py -h
usage: argparseTest.py [-h] [--print-number PRINT_NUMBER]

Argparse Tutorial

optional arguments:
  -h, --help            show this help message and exit
  --print-number PRINT_NUMBER
                        an integer for printing repeatably

> python argparseTest.py --print-number 5
print number 1
print number 2
print number 3
print number 4
print number 5

argparse의 인자는 지정할 수 있는 종류가 상당히 많다.

–help, -h

--help 또는 -h: 기본으로 내장되어 있는 옵션이다. 이 인자를 넣고 python으로 실행하면 인자 사용법에 대한 도움말이 출력된다.

> python argparseTest.py -h
usage: argparseTest.py [-h] [--print-number PRINT_NUMBER]
...

argument 이름 정의

인자의 이름을 지정할 때 여러 이름을 짓는 것이 가능하다. 지정할 때 두 개를 연속해서 나열한다. 보통 1~2개를 지정하는데, --help-h같이 fullname과 약자를 하나씩 지정하는 편이다. 또 help=에서 description을 써줄 수 있다.
참고로 help 메시지는 % formatting을 지원한다.

parser.add_argument('--print-number', '-p', help='an integer for printing repeatably')

type 지정

기본적으로 parse_args()가 주어진 인자들을 파싱할 때는 모든 문자를 숫자 등이 아닌 문자열 취급한다. 따라서 데이터 타입을 지정하고 싶으면 add_argument()에서 type=을 지정해 주어야 한다. default는 말한 대로 str이다.

  • ex) parser.add_argument('--print-number', '-p', type=int, ...)
  • type으로 사용 가능한 것은 한 개의 문자열을 받아들여 return 문이 있는 모든 callable 객체이다.
  • Common built-in types과 functions이 사용 가능한데, str, int, float, boolopen 등이 있다. list와 같은 것은 불가능하다. list처럼 쓰고 싶으면 아래쪽에서 설명할 action=append를 이용한다.
  • argparse.FileType() 함수도 type=에 사용 가능한데, mode=, bufsize=, encoding=, errors= parameter를 취하는 함수로서 다양한 파일을 여러 옵션으로 지정할 수 있다. 예를 들어 argparse.FileType('w')는 쓰기 가능한 파일을 만든다. 자세한 것은 여기를 참조한다.

positional / optional 인자

positional 인자와 optional 인자가 있다. 인자의 이름 앞에 -가 붙어 있으면 optional, 아니면 positional 인자로서 필수로 지정해야 한다.
단, positional 인자도 필수로 넣어야 하게끔 할 수 있다. add_argument() 함수에 required=True를 집어넣으면 된다. 그러나 C언어에서 #define true false같은 짓인 만큼 권장되지 않는다.

# argparseTest.py
# ...
parser.add_argument('--foo', '-f') # optional
parser.add_argument('bar')         # positional
args = parser.parse_args()
print('args.foo:', args.foo)
print('args.bar:', args.bar)
# optional 인자는 지정하지 않아도 되고, 그럴 경우 기본값이 저장된다.
> python argparseTest.py bar_value
args.foo: None
args.bar: bar_value

# positional 인자는 반드시 값을 정해 주어야 한다.
> python argparseTest.py --foo 1
usage: argparseTest.py [-h] [--foo FOO] bar
argparseTest.py: error: the following arguments are required: bar

# optional 인자 뒤에는 반드시 저장할 값을 지정해야 한다. 
# 이는 `action=store`인 optional 인자에 해당한다. 6번 항목에서 설명하겠다.
> python argparseTest.py bar_value --foo
usage: argparseTest.py [-h] [--foo FOO] bar
argparseTest.py: error: argument --foo/-f: expected one argument

# optional 인자는 `--foo 3`또는 `--foo=3` 두 가지 방식으로 지정할 수 있다.
# positional 인자는 그런 거 없다.
> python argparseTest.py --foo=5 bar=bar_value
args.foo: 5
args.bar: bar_value

# positional 인자가 여러 개라면 순서를 반드시 지켜야 한다.
# optional 인자는 값만 잘 지정한다면 어디에 끼워 넣어도 상관없다.
> python argparseTest.py bar_value --foo 7
args.foo: 7
args.bar: bar_value

default 값 지정

값을 저장할 때 명시적으로 지정하지 않았을 때 들어가는 기본값을 설정할 수 있다. add_argument()에서 default= 옵션을 지정한다. - argparse.SUPPRESS를 적을 경우, 인자를 적지 않았을 때 None이 들어가는 것이 아닌 아예 인자 자체가 생성되지 않는다. 또한 --help에도 표시되지 않는다.

parser.add_argument('--foo', '-f', type=int, default=5)
> python argparseTest.py
args.foo: 5

# 그러나 인자를 적어 놓고 값은 안 주면 에러가 난다. 
# 기본적으로 한 개의 값을 추가로 받아야 하기 때문이다.
# 이걸 바꾸려면 6번이나 7번 항목을 참조한다.
> python argparseTest.py --foo
usage: argparseTest.py [-h] [--foo FOO]
argparseTest.py: error: argument --foo/-f: expected one argument

action의 종류 지정

인자를 정의(add_argument()에 의해)할 때 action을 지정할 수 있다. 액션에는 다음과 같은 것들이 있으며, 기본값은 store이다.

  • store: action을 지정하지 않으면 store이 된다. 인자 이름 바로 뒤의 값을 해당 인자에 대입(저장)시킨다.
  • store_const: add_argument()에서 미리 지정되어 있는 const=에 해당하는 값이 저장된다. const=는 반드시 써 주어야 한다.
  • store_true, store_false: 인자를 적으면(값은 주지 않는다) 해당 인자에 TrueFalse가 저장된다.
  • append: 값을 하나가 아닌 여러 개를 저장하고 싶을 때 쓴다. 인자를 여러 번 호출하면 같이 주는 값이 계속 append된다.
  • append_const: append와 비슷하지만 사전에 지정한 const 값이 저장된다.
  • count: 인자를 적은 횟수만큼 값이 올라간다. 보통 verbose 옵션에 많이 쓴다.
  • help: 도움말 메시지를 출력하게 하고 종료하여 코드는 실행시키지 않는다. --help 역할을 대신한다.
  • version: version 인자에 사용가능하다. 버전 정보를 출력하고 종료한다.
parser.add_argument('--foo', action='store_const', const=10)
> python argparseTest.py --foo
args.foo: 10

# 인자를 적지 않으면 default 값(None)이 저장된다.
parser.add_argument('--foo', action='store_const', const=10)
> python argparseTest.py
args.foo: None

# default 값을 지정하면 당연히 바뀐다.
parser.add_argument('--foo', action='store_const', const=10, default=5)
> python argparseTest.py
args.foo: 5

# store_true의 경우 default 값은 false이며, 인자를 적어 주면 true가 저장된다.
# store_false의 경우 반대이다.
parser.add_argument('--foo1', action='store_true')
parser.add_argument('--foo2', action='store_true')
parser.add_argument('--foo3', action='store_false')
parser.add_argument('--foo4', action='store_false')
args = parser.parse_args()

print('args.foo1:', args.foo1)
print('args.foo2:', args.foo2)
print('args.foo3:', args.foo3)
print('args.foo4:', args.foo4)
> python argparseTest.py --foo1 --foo4
args.foo: True
args.foo: False
args.foo: True
args.foo: False

# 참고로 한 번만 호출해도 args.foo는 데이터 타입이 list가 된다. 안 하면 None이다.
parser.add_argument('--foo', action='append')
> python argparseTest.py --foo 1 --foo 123 --foo=xyz
args.foo: ['1', '123', 'xyz']

attribute name: -, _ 구분

인자의 이름에는 -_을 쓸 수 있다. 단, python 기본 문법은 변수명에 -를 허용하지 않기 때문에, 인자의 이름에 -가 들어갔다면 args.인자로 접근하려면 -_로 바꿔 주어야 한다.

  • --print-number의 경우 args.print_number로 접근할 수 있다.
  • --print_number의 경우 args.print_number로 동일하다.

dest: 적용 위치 지정

argument를 지정할 때 store나 action의 저장 또는 적용 위치를 바꿔서 지정할 수 있다. 예를 들어 --foodest= 옵션을 --foo-list로 지정하면, args.foo_list에 값이 저장되는 식이다.

parser.add_argument('--foo', action='append', dest='foo_list')
parser.add_argument('--bar', dest='bar_value')
args = parser.parse_args()

print('args.foo_list:', args.foo_list)
print('args.bar_value:', args.bar_value)

try:
    if args.foo is not None:
        print('Hmm?')
except AttributeError as e:
    print('Where are you gone?', e)
> python argparseTest.py --foo 1 --foo 123 --foo=xyz --bar ABC
args.foo_list: ['1', '123', 'xyz']
args.bar_value: ABC
Where are you gone? 'Namespace' object has no attribute 'foo'

nargs: 값 개수 지정

argparse는 일반적으로 1개의 값을 추가로 받거나, action=store_true의 경우는 값을 추가로 받지 않는다. 이를 바꿔 주는 것이 nargs= 이다.

  • N: N개의 값을 읽어들인다.
  • ?: 0개 또는 1개의 값을 읽어들인다.
    • 인자와 값을 모두 적은 경우 해당 값이 저장된다.
    • 인자만 적은 경우 const 값이 저장된다.
    • 아무것도 적지 않았으면 default 값이 저장된다.
  • *: 0개 이상의 값을 전부 읽어들인다.
  • +: 1개 이상의 값을 전부 읽어들인다. 정규표현식의 것과 매우 비슷하다.
  • argparse.REMAINDER: 남은 값을 개수 상관없이 전부 읽어들인다.

예제는 원문이나 번역본을 참조한다.

choices: 값 범위 지정

인자와 같이 주어지는 값의 범위를 제한하고 싶으면 choices= 옵션을 쓰면 된다. choices=에 들어갈 수 있는 정의역은 list 등 iterable 객체이다(in 조건검사를 할 수 있으면 된다).

parser.add_argument('--foo', choices=range(1, 5))
> python argparseTest.py --foo 5
usage: argparseTest.py [-h] [--foo {1,2,3,4}]
argparseTest.py: error: argument --foo: 
invalid choice: '5' (choose from 1, 2, 3, 4)

metavar: 이름 재지정

metavar은 help=에서 도움말 메시지를 생성할 때 표시되는 이름을 변경할 수 있다(직접 값을 참조하는 args.foo 같은 경우 기본 이름 또는 dest=에 의해 재지정된 이름을 써야 한다).


References

Comment  Read more