건프의 소소한 개발이야기

[Deep Learning - GAN] Simple Generative Adversarial Network with MNIST dataset 본문

개발 이야기/Machine Learning 이야기

[Deep Learning - GAN] Simple Generative Adversarial Network with MNIST dataset

건강한프로그래머 2018. 1. 23. 18:34

안녕하세요, 건프입니다.


이번에는 최근에 제가 관심있게 연구하고 있는 Generative Adversarial Network 모델, 속칭 GAN 모델에 대해서 알아보고, 

우리에게 친숙한 손글씨 데이터인 MNIST 를 이용해서, 간단히 Code Level 에서의 Implement 를 해보고자 합니다.


GAN 모델을 간단하게 설명해보면,

- 두개의 모델이 존재합니다.

- 하나는 생성기(Generator) 이며, 또 하나는 분류기(Discriminator) 입니다.

- 두 모델은 학습하면서 서로에게 영향을 미치면서 고도화 됩니다.

- 생성기는 노이즈로부터, 어떠한 결과물을 만들어내는데, 이 결과물은 분류기에 의해서 평가됩니다.

- 분류기는 생성기로부터 온 결과물과, 실제 데이터(Real Data) 를 구분해내는 역할을 합니다.

- 즉, 생성기는 분류기를 속일 정도로 "잘 만들어야" 하며

- 분류기는 생성기가 생산한 Fake Data 를 진짜(Real data) 와 "잘 구분해야" 합니다.

- 이는 <경찰과 도둑> 모델이라고 생각해도 무방합니다.


개념은 그럴싸하지만, 실제로 코드를 짜보는 건 또 다른 차원의 이야기라는 것 잘 아실 겁니다.


게다가 GAN 모델이 그 높은 이상에 비해서, 실제로 OUTPUT 이 어떻게 나올지,

GAN 을 구축함에 있어서 주의해야할 점은 없는지, 고민해볼 필요가 있습니다.


우선 우리 간단하게 코드레벨로 들어가봅시다.

먼저 제가 진행한 개발환경정보입니다.

[개발환경]

Ubuntu 16.04

Conda VirtualEnv

Python=3.5

pip install tensorflow-gpu keras

tensorflow 로 부터 MNIST 데이터를 로드해옵니다.

[MNIST 로드 시 전차리단계에서 주의해아할점]

일반적으로 MNIST Dataset 을 로드하는 방법은 2가지 정도가 혼용됩니다.

1. Tensorflow 모듈로 로드하는 방법

2. Keras 모듈로 로드하는 방법

둘다 똑같은 MNIST 로드라고 생각해서, 같은 전처리를 하게 되면, 이상한 결과를 얻을 수 있습니다. 

(1) 의 데이터를 확인해보시면, 이미 255 로 나눠져 있는 소수점 데이터이고,

(2) 의 데이터는 각 픽셀값이 255 로 나눠져있지 않은, [0, 255] 사이의 데이터입니다. 

1의 데이터에서 다른 사람의 예제코드를 그대로 따라하여, 전처리를 255로 나누면, 숫자가 너무 작아져 학습자체가 잘 진행되지 않습니다. (결과가 안나온다는 의미입니다)


저는 네트워크를 구성함에 있어서 굉장히 유용한 라이브러리인 Keras 를 이용하겠습니다.

이 글을 보고 직접 작업하시는 여러분은 TFlearn 이든, 아니면 Tensorflow Low 코드로 짜셔도 무방합니다.

Keras 는 Backend 로 Tensorflow 또는 Theano 를 이용하는 Wrapper Lib 입니다. 


자, 그럼 Generative Model 부터 구성해봅니다.

복잡해보이지만 사실은 나름 간단하게 짜여져 있습니다.

단계는 다음과 같습니다.

1. 픽셀 100개 짜리의 Noise(노이즈) 데이터가 인풋으로 들어올 겁니다.

2. Generator Model 을 만드는 과정에서, Batch Normalization Layer 를 사용하는 것은 거의 대세가 되어가고 있습니다. 이 레이어에 대해서는 이글의 핵심은 아니기 때문에, 설명은 생략하지만, 궁금하시다면 꼭한번 찾아보시면 좋을 것 같습니다.

3. MNIST Dataset 이미지 한장의 차원은 28 * 28 * 1  입니다. 이를 만들어가기 위해서 우선 14*14 로 차원을 재배치해줍니다. : Reshape

4. 14 * 14 => 28 * 28 차원을 만들기 위해서, UpSampling2D Layer 를 삽입했습니다. 

5. padding="same" 옵션을 주는 Conv2D Layer 를 삽입하여, 근접한 픽셀들에 대한 위치정보를 파악하는 CNN Layer 를 얻으면서, 이미지의 크기를 변화시키지 않습니다. 

6. 마지막으로 노드 사이즈=1, 필터 사이즈=1 의 Conv2D 를 통해서 28*28*1 크기의 MNIST dataset 차원의 데이터를 생성해냅니다. 

Keras 에서 모델을 구축할때, 흔히 사용하는 Sequential 이 아니라, 

Model 을 이용해서, Functional API 로 네트워크를 구성한 것이 조금 의아하신 분이 계실지 모르겠습니다.


Sequential Model 의 장점은, 네트워크 구성에 있어 가독성이 좋다는 장점이 있지만,

두 모델을 연결하거나 커스터마이징하게 재배열하는데 있어서는 약간 불편한 점이 있었습니다. 

그래서 저는 이번에 Functional API 호출방법으로 네트워크를 구성해보았고, 생각보다 훌륭하게 동작합니다.



다음은 분류기에 해당하는 Discriminator Model 을 빌드해봅니다,


 이 역시 복잡해보이지만, 간단한 레이어들의 조합으로 이루어져 있습니다. (그리고 MNIST 는 그렇게 네트워크가 깊을 필요가 없습니다.)

1. 28*28*1 차원의 이미지가 들어올 것이기 때문에, 이를 처리할 Conv2D Layer 를 준비합니다. Activation Function 을 Discriminator 측면에서는 Leaky_Relu 를 사용하는게 더 효과적이라고 합니다만은, 무슨 이유에서인지 Keras 의 Leaky Relu Function 이 제대로 동작하지 않아서, 일단 저는 Relu Function 을 사용했습니다. (나쁘지 않습니다.)

2. 과적합(OverFitting) 현상을 막아주기 위해서 Neural Network 가 쉽고 효율적으로 채택하고 있는 방법이 Dropout Layer 입니다. 학습한 Neural Network 의 노드들을 학습과정에서 일정비율을 강제로 제외하여 진행하는 방법인데, 만약 궁금하시다면 꼭 한번 찾아보시고 개념을 잡아주시는 게 좋습니다.

3. 뒤에는 특별한 게 없습니다. 마지막에 softmax 가 아니라 sigmoid, categorical crossentropy 가 아니라, binary crossentropy 를 사용하셔도 같은 결과를 낼겁니다. (결국 2개의 Class 를 구분하는 행위니까요 )

그리고 마침내, Generator + Discriminator 두 모델을 합쳐주면, GAN_MODEL 의 Network 를 구성하게 되는 겁니다.


학습 진행을 확인하는 Visualization 기능도 중요한 과정입니다. 

아래 코드를 한번 참조하시면 좋을 것 같습니다.



아래코드에서 확인해야하는 부분은 

1. noise_gen 을 실제 데이터의 한단위 학습뭉치(Batch_Size)와 동일하게 노이즈 데이터를 생성한 뒤,

2. 실제데이터와 noise_gen 을 연결(Concat) 하고

3. 실제(Real) 데이터에 대해서는 Discriminator 가 [0, 1] 로 예측, 

4. 생성된(Fake) 데이터에 대해서는 Discriminator 가 [1, 0] 으로 예측하도록 데이터를 레이블링 시킨다는데 있습니다.

그리고 딱 한번(Epoch), Discriminator 에 대해서 초벌학습을 합니다. 

이는 Generator 가 Discriminator 의 판단에 따라서 학습하는데, Discriminator 가 "아무것도 모르는 바보" 라면 아무 의미 없기 때문입니다 :D


그리고, 딱 한번밖에 학습하지 않으면, Discriminator 가 워낙 깐깐하게 체크를 하면서, Generator 가 너무 낮은 확률로 Positive Feedback 를 받아, 학습이 매우 더디거나 계속 바보상태로 남는 이슈가 생깁니다. 


따라서 GAN 은 적절히 바보인 Discriminator 를 Generator 가 학습해가면서, Discriminator 도 함께 학습해가는

<열린 교육> 시스템 입니다(?)


초벌학습을 끝낸 Discriminator 가 분류의 개념은 잡았는지를 테스트해보죠.

음 좋습니다. 이제 분류기는 적어도 랜덤 노이즈 데이터와 실제데이터는 구분할 수 있는 아이입니다. 


이제 본격적으로  GAN 학습을 진행해봅시다.

이 과정에서 tqdm 이라는 라이브러리를 알게되었습니다. 

Iterator 를 이용하는 Loop 에서 진행도를 기가막히게 표시해주는 재미있는 라이브러리입니다. (혹시 저만 아직 몰랐나요?;;)

전체적인 과정을 한번 살펴보죠.

train_for_n 함수안에는 크게 5가지 덩어리의 로직이 순차적으로 진행됩니다.

1. 노이즈 데이터로부터 우선 Generator 가 이미지를 생성합니다.

2. Real Image 는 [0, 1] 로 Fake Image 는 [1, 0] 으로 레이블링을 시킵니다.

3. Discriminator 의 Weights 를 trainable=True 로 바꿔준 뒤, 분류를 시켜봅니다. 그리고 그 loss 를 d_loss 에 기록해둡니다. 

4. 이제 다시 Discriminator 의 Weights 를 trainable=False 로 바꾸면서, 학습은 못하게 얼립니다. 이제 Generator 가 Discriminator 의 결과를 바탕으로 학습을 해봅니다. 이때 중요한 건, Generator 입장에서 랜덤한 인풋이 들어간 내용이, [0, 1] 로 결과가 나와야 한다는 것이죠! 고로 그렇게 레이블링을 진행합니다. (도둑은 경찰을 속여야 잘한 겁니다.)

5. 특정 체크 포인트마다, plot 을 그려주고, plot 이미지는 로컬에 저장합니다.

6. 이를 반복합니다.


[Checkpoint 28 순간의 Generator Output]

[Checkpoint 1280 순간의 Generator Output]


[Checkpoint 4890 순간의 Generator Output]






결론 : 

GAN 모델링 방법이 세상에 소개된지 제법 지났습니다. 

그럼에도 GAN 은 아직도 더 좋은 방법이 제시되는 살아있는 연구분야인 듯 합니다. 

왜냐하면, 이들의 학습방법이 마치 우리가 학교에서 배우는 방법과 닮았기 때문이지 않을까요?


저는 위의 코드와 방법이 가장 Simple 한 GAN 방법이라고 생각합니다. 

Generator 는 노이즈로부터 가장 손글씨에 적합하게 그리고 있을 뿐, 7 을 그리고자 7을 그려내지는 않는 모델이고

Discriminator 역시 숫자의 Class 를 구분하는게 아니라, Real/Fake 여부만 가리고 있는 모델이기 때문입니다.


즉 저는 좀 더 짜야할 코드와 실험들이 있는 것 같네요!

저는 이 기술을 자연어처리기술(NLP)에 적용할 부분이 있는지 연구해보아야겠습니다.


Image Deep Learning 을 해보고 계시는 많은 동료 엔지니어분들에게 조금이라도 도움이 되었으면 좋겠습니다.


감사합니다.


References:

https://www.kdnuggets.com/2016/07/mnist-generative-adversarial-model-keras.html

Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks - ICLR 2016, Alee, Luke, Soumith

http://jaejunyoo.blogspot.com/2017/02/deep-convolutional-gan-dcgan-1.html

https://github.com/noamraph/tqdm




Comments