본문 바로가기
AIML/GANs

[학부생의 딥러닝] GANs | InfoGAN : Information maximizing GAN

by 하우론 2018. 6. 27.

InfoGAN - Tensorflow 구현, PyTorch 구현

레퍼런스 

- InfoGAN - Interpretable Representation Learning by Information Maximizing Generative Adversarial Nets : https://arxiv.org/abs/1606.03657

- 상호정보량 위키 : https://en.wikipedia.org/wiki/Mutual_information

- 유재준님 블로그 : http://jaejunyoo.blogspot.com/2017/03/infogan-2.html

- Gaussian Loss 참고: http://aiden.nibali.org/blog/2016-12-01-implementing-infogan/

- 수식 전개 과정 참고 : http://www.modulabs.co.kr/dmb/15967

- Visual Information Theory : 조건부 엔트로피, joint 엔트로피 그림 : http://colah.github.io/posts/2015-09-Visual-Information/

- 위 링크 간략하게 설명 : https://math.stackexchange.com/questions/2505015/relation-between-cross-entropy-and-joint-entropy

- 저작자 코드 (알아보기 힘듦) : https://github.com/openai/InfoGAN/blob/master/infogan/algos/infogan_trainer.py

- 김태오님 블로그 : https://taeoh-kim.github.io/blog/generative-models-part-2-improvedganinfoganebgan/

 


 

배경

기존의 GAN은 input noise를 interpolation 할 때 latent vector의 각 원소들이 특별한 의미를 가지지는 않았다. 그래서 원하는 조건의 이미지를 얻으려면 일일이 노가다 해보는 수 밖에 없었다. 하지만 보통 input noise 차원 수는 상당히 크고 딥러닝 학자들같은 고급인력이 이런 일을 하고 있는 것은 너무 비생산적일 것이다.

2016년 6월에 OpenAI 팀이 효율적인 방법을 알아냈다.

 

input noise에 output 이미지와의 상호정보량이 큰 latent variable을 만들어내는 것으로 해결할 수 있다.

 

code라는 새로운 latent variable $c$를 삽입한 후 생성된 이미지와의 상호정보량이 커지도록 생성기학습을 제한시킨다. 그러면 우리의 목표는 다음과 같아진다.

$$\min_G \max_D V_I(D, G) = V(D, G)- \lambda I(c;G(z, c))$$

$V$는 원래 GAN의 loss 함수다.

 

상호정보량을 $I$로 정의는 했지만 막상 이렇게 추상적인 개념을 수식으로 표현하는 것은 정말 어렵다. 하지만 이걸 이미 100년 전에 해낸 사람이 있다!!! 클로드 섀넌이라는 사람이 엔트로피 개념을 사용해 수식으로 유도해냈다.

 


상호정보량

말 그대로 확률 변수들 상호간의 정보라는 뜻이다. $X$가 정해지는 것이 $Y$에 대해 얼마나 많은 정보를 주는가?를 양으로 표현한건데, 예시를 몇 개 들어보면

 

1) 주사위를 두 번 던진다고 했을 때 첫 번째 눈을 보고 두 번째 눈을 예측해본다고 해보자. 첫 번째 눈이 3이 나왔다. 하지만 두 번째 눈이 뭐 나올지는 이와 아무 상관이 없다. 있으나 마나한 정보이다.

 

2) 오늘 날씨로 내일 날씨를 예측해 본다고 하자. 오늘 밤에 비가 많이 왔다. 그럼 내일은 오늘보다 추울 것이라는 것을 예측할 수 있다. 다음 번에도 다음날 추울지 예측할 때에 그 전날 비가 왔는지에 대한 정보가 있으면 예측이 조금 수월할 것이다.

 

1)은 상호 정보량이 0이고 2)는 양수값이다. 우리의 목표는 GAN을 학습시키는 와중에 이 상호정보량이 높은 latent code를 얻어내는 것이다.

 


 

상호정보량의 정의

$$I(X; Y) = D_{\text{KL}} (P_{(X, Y)} || P_X \otimes P_Y)$$

 

$\otimes$는 단순히 곱을 의미한다고 생각하면 된다. 스칼라면 $P_X(x)P_Y(y)$처럼 그냥 막 곱하면 되는데, $P_X, P_Y$는 distribution이기 때문에 $\otimes$로 표기해 놓은 것이다. "확률 변수 $X$, $Y$가 서로 독립이라 가정했을 때의 joint distribution과 true joint distribution 간의 거리가 얼마나 되나"라는 뜻이다.

 

정의를 통해 일단 수식적인 의미부터 살펴보자. 일단 $D_{\text{KL}}$은 Kullback-Leibler divergence이고 두 분포 사이의 거리를 의미한다. 두 분포가 같다면 최솟값인 0을 가진다.

그럼 상호정보량이 최소일 때에는 $P_{(X, Y)}$와 $P_X \otimes P_Y$(즉, $P_{X, Y}(x, y)$와 $P_X(x)P_Y(y)$)가 같은 분포라고 생각할 수 있겠다. 따라서 $P_X, P_Y$가 서로 독립일 때 최솟값을 가진다.

 

말로 풀어 써보면 "$Y$가 어떤 값으로 정해졌을 때의 $X$의 불확정성" 정도로 생각할 수 있는데, 이는 극단적인 상황을 생각해보면 금방 이해할 수 있다.

만약 $X$와 $Y$가 독립이라면 $Y=3$이라는 정보는 $X$를 결정하는 데에 아무 영향을 주지 않는다. $X$는 여전히 불확정적이다. (위의 1번 예시를 생각해보자.)

하지만 만약 $P(X \neq Y)=0$이라면?($X$랑 $Y$가 항상 같다면?) $Y$가 어떤 값으로 결정되면 $X$도 무조건 결정할 수 있다. 이러면 불확정성이 완전히 깨져버리고 $Y$가 $X$의 완전한 정보를 제공한다. 이 때 상호정보량은 양으로 발산한다. (상호정보량은 음이 될 수 없다!)

 

정의를 이용해 다음과 같이 계산할 수 있다.

$$I(X; Y) = \int_\mathcal{Y} \int_\mathcal{X} p(x, y) \log \left ( {p(x, y) \over p(x) p(y)} \right ) dx dy$$

 

"불확정성"이라는 것은 엔트로피 $H$를 이용해 표현할 수 있다.

$$H(X) = - \int_\mathcal{X} p(x) \log p(x) dx$$

 

이 값은 $p(x)$가 모든 $x$에 대해 같아지면 가장 불확정적이므로 최댓값을 갖는다.

어떤 다른 확률변수가 condition으로 들어왔을 때도 불확정성을 표현할 수 있다.

$$H(X|Y) = - \int_\mathcal{X} \int_\mathcal{Y} p(x, y) \log {p(x, y) \over p(y)} dydx = - \int_\mathcal{X} \int_\mathcal{Y} p(x, y) \log p(x|y) dydx \\ = - \mathbb{E}_{x \sim P_X} [\mathbb{E}_{y \sim P_Y} [\log P(X|Y)]]$$

 

그럼 상호정보량을 엔트로피를 이용해서 표현이 가능해진다.

$$I(X; Y) = H(X) - H(X|Y) = \mathbb{E}_{X \sim P_X} [\mathbb{E}_{Y \sim P_{Y|X}} [\log P(X|Y)]] + H(X)$$

 


Variational Information Maximization

이제 위 식에 $X \leftarrow c, Y \leftarrow G(z, c)$를 대입하면 상호정보량을 계산할 수 있다.

$$I(c;G(z, c)) = \mathbb{E}_{x \sim G(z, c)}[\mathbb{E}_{c' \sim P(c|x)}[\log P(c'|x)]] + H(c)$$

하지만 여기서 장벽이 하나 더 생긴다. 우리가 $P(c|x)$를 알고 있나?? 어떤 이미지의 code $c$를 알아내는 것이 목표인데, 알아내고 싶은 분포를 이용해 수식을 계산한다?? 이건 말이 안 된다. 그래서 이 논문의 저자들은 Auxiliary Distribution $Q$를 두어 $P(c|x)$를 직접적으로 이용하는 것을 피했다.

 

$P(c|x)$는 관측된 $x$에 대한 $c$의 분포이고 $P(c)$는 그냥 $c$의 분포다. 만약 $P(c|x)$를 이미 알고 있다면 관측된 이미지 $x$를 가장 잘 표현할 수 있는 $c$를 아는 것이나 다름 없다. 그럼 학습이 의미가 없다.

 

$$\begin{align*} I(c;G(z,c)) &= H(c) - H(c|G(z, c)) \\ &= \mathbb{E}_{x \sim G(z, c)}[\mathbb{E}_{c' \sim P(c|x)}[\log P(c'|x)]] + H(c) \\ &= \mathbb{E}_{x \sim G(z, c)}[\underbrace{D_{KL}(P(\cdot|x)||Q(\cdot|x))}_{\ge 0} + \mathbb{E}_{c' \sim P(c|x)}[\log Q(c'|x)]] + H(c) \\ &\ge \mathbb{E}_{x \sim G(z, c)}[\mathbb{E}_{{\color{Red} {c' \sim P(c|x)}}}[\log Q(c'|x)]] + H(c) \\ &= \mathbb{E}_{{\color{Blue} {c \sim P(c)}}, x \sim G(z, c)}[\log Q(c|x)] + H(c) \\ &\stackrel{\text{let}}= L_I(G, Q) \end{align*}$$

 

값을 구하기 힘든 식의 계산을 lower bound를 이용해 해결하는 모습을 볼 수 있다. 이를 Variational Information Maximization이라 한다.

 

빨간색으로 줄 친 부분을 보면 $P(c|x)$를 필요로 하는 부분을 Law of total Expectation을 이용해 계산했다.

$$\mathbb{E}_Y[\mathbb{E}_X[X|Y]]=\mathbb{E}[X]$$

아담의 정리라고도 부른다. 논문에서는 이를 더 일반화해 사용했다.

lemma 5.1 증명

$$\LARGE \mathbb{E}_{{\color{Blue} {x \sim X}}, y \sim Y|x}[f(x,y)]=\mathbb{E}_{x\sim X, y \sim Y|x, {\color{Red} {x' \sim X|y}}}[f(x', y)]$$

아랫첨자가 주인공이니까 크게 썼다.

 

암튼 간에, 이를 통해 $c$를 데이터 $x$에 상관없이 마구잡이로 샘플링 해 계산해도 원하는 대로 학습이 된다는 것을 예상할 수 있다. 샘플링 할 $c$는 진짜 해당 step에서 생성된 이미지에 상관 없이 "아무거나" 뽑으면 된다. 진짜 아무거나!!

 

따라서 최종 목표는

$$\min_{G, Q} \max_D V_{\text{InfoGAN}}(D,G,Q) = V(D, G) - \lambda L_I (G, Q)$$

가 된다.

 


구현

모델 구현은 논문에 상세히 기술되어 있어서 상당히 수월하다.

생성기 마지막에 stride 2가 없어서 추가했다. stride 2가 있어야 28x28 크기가 된다.

 

일반적인 DCGAN에서 추가된 건 구분기 최상단(output 쪽)에 Q network 밖에 없다. 변수의 개수를 계산해보면 순수 구분기의 변수 개수는 25,694,208 개 추가로 달린 $Q$의 parameter 개수는 132,608 개이다. 거의 0.5% 추가된 거다. 논문에서 말한 대로 거의 공짜수준인 것을 알 수 있다.

 


Categorical(Discrete) Codes

categorical code는 모아서 softmax해주면 되고 continuous code는 factored(단순히 곱했다는 뜻이다; 각 차원의 원소들이 모두 독립) Gaussian으로 생각하고 구현해도 충분하다고 한다.

Objective function은 softmax해준 결과와 $Q$-network의 output과 cross-entropy를 취해 구하면 된다.

 

categorical(discrete) code의 loss 취한 부분을 보자.

# tensorflow
loss_Q_disc = tf.reduce_mean(
    tf.nn.sigmoid_cross_entropy_with_logits(
        logits=Q_logits[:, :10],
        labels=C[:, :10]
)

# pytorch
loss_Q_disc = nn.CrossEntropyLoss(disc_logits, code_disc.argmax(dim=1))

Logit은 sigmoid나 softmax가 씌워지기 전의 값을 의미한다.

 


Continuous Codes

Continuous한 변수의 분포를 추정하는 기존의 머신러닝 방법 중에 gaussian log likelihood라는 방법이 있다. 목표 분포가 그냥 gaussian이라고 가정하고 mean, std를 추정하는 방법이다. 그러면 $P(c|x)$를 gaussian이라고 가정 하고 Q_logits가 $\hat{c} = \mu_{c|x}, \sigma = \sigma_{c|x}$를 뱉는것으로 모델링 할 수 있다. MLE 방법으로 $p(c|\hat{c}, \sigma^2)$가 최대가 되도록 학습하면 된다.

 

원래대로라면 $\sigma$가 상수여서

$$\ell = -\log p(c|\hat{c}, \sigma^2)={1 \over 2\sigma^2}||c - \hat{c}||^2 + {N \over 2}\log \sigma^2 + {N \over 2}\log(2\pi)$$

이 되어 $\min_\hat{c} \text{MSE}$와 동치이지만 지금은 $\sigma$도 모델에서 받아오기 때문에 변수이므로 얘도 최적화 해야한다.

$$\max \log p(c|\hat{c}, \sigma^2)=\log \prod_{i} {1 \over \sqrt{2\pi}\sigma_i} e^{- {(c_i - \hat{c}_i)^2 \over 2\sigma_i^2}}$$

$\log$가 붙어 식을 간단하게 만들 수 있다.

$$\min_{\hat{c}, \sigma} \ell = -\log p(c|\hat{c}, \sigma^2)= \sum_{i=1}^N \left \{ \log \sigma_i + {(c_i - \hat{c}_i)^2 \over 2 \sigma^2_i} \right \} + {N \over 2}\log(2\pi)$$

# tensorflow
pass

# pytorch
class GaussianNLLLoss(nn.Module):

    def __init__(self):
        super().__init__()

    def forward(self, c, c_hat, sigma):
        assert sigma > 0
        l = (c - c_hat) ** 2
        l /= (2 * sigma ** 2)
        l += torch.log(sigma)
        return l.mean()

 


4/16 추가

$\sigma$까지 학습시키면 학습시에 continuous code의 영향이 다른 원소들보다 너무 커져서 mode drop이 생긴다.

continuous code의 loss에 작은 값을 곱하면 해결할 수 있다.

loss_Q = loss_Q_disc + loss_Q_cont * .15

 

$\sigma$없이 $\mu$만 MSE로 학습시키는 것도 충분할 것 같다.

 


lemma 5.1 - 임성빈 박사님의 증명

 

\begin{align*} \mathbb{E}_{x \sim X, y \sim Y|x}[f(x,y)] &= \int_\mathcal{X} \int_\mathcal{Y}P(x)P(y|x)f(x,y)dydx \qquad(\,\because \text{ Definition of pdf})\\ &= \int_\mathcal{X} \int_\mathcal{Y} P(x,y)f(x,y)dydx \qquad(\,\because \text{ Bayes rule}) \\ &= \int_\mathcal{X} \int_\mathcal{Y} P(x', y)f(x',y)dydx' \qquad(\,\because \text{ no difference}) \\ &= \int_\mathcal{X} \int_\mathcal{Y} \left[ \int_\mathcal{X} P(x|y)dx \right] P(x',y)f(x',y)dydx' \qquad \left(\,\because \int_\mathcal{X}P(x|y)dx=1 \right) \\ &= \int_\mathcal{X} \int_\mathcal{Y} \left[ \int_\mathcal{X} P(x|y){P(y) \over P(y)}dx \right] P(x',y)f(x',y)dydx' \quad \quad(\,\because \text{ Bayes rule}) \\ &= \int_\mathcal{X} \int_\mathcal{Y} \left[ \int_\mathcal{X} P(x,y)dx \right] P(x'|y)f(x',y)dydx' \qquad(\,\because \text{ Bayes rule}) \\ &= \int_\mathcal{X} \int_\mathcal{Y} \left[ \int_\mathcal{X} P(x)P(y|x)dx \right] P(x'|y)f(x',y)dydx' \qquad(\,\because \text{ Bayes rule}) \\ &= \int_\mathcal{X} \int_\mathcal{Y} \int_\mathcal{X} P(x)P(y|x)P(x'|y)f(x',y)dx'dydx \qquad(\,\because \text{ Fubini theorem}) \\ &= \mathbb{E}_{x \sim X, y \sim Y|x, x' \sim X|y}[f(x',y)] \end{align*}

 


결과

 

각 열의 categorical code는 같다. 세로로 continuous code를 -1부터 1까지 interpolation 시켰다.

왼쪽에서 네 번째 열이 1에서 2로 변하는 것을 볼 수 있다. 9와 4가 비슷한 것만 빼면 아무 prior 정보도 없이(prior 정보가 없다는 이 부분이 cGANs와의 차별점이다.) 10개 숫자를 모두 구분해 내는 것을 볼 수 있다. 

 

다른 code들은 다 가만히 냅두고 첫 번째 continuous code만 -2에서 2까지 interpolation 시켰다(위). 두 번째 것으로도 해봤다(아래). 학습 시킨 범위는 -1~1이었는데 범위 바깥에서 extrapolate 되는 것도 확인할 수 있다. 위는 회전정보를 잡아낸 것으로 볼 수 있다. 논문에서는 두 번째 code가 굵기 정보도 잡아냈지만 나는 실패했다.

 

개인적으로 가장 신기한 논문이었다.