아나콘다텐서플로우

2-4 텐서플로우 전문가용 예제 “GAN” 해설

coding art 2020. 3. 7. 17:49
728x90

 

 

Deep Convolutional Generative Adversarial Network

https://www.tensorflow.org/tutorials/generative/dcgan

Deep Convolutional 레이어는 입력이미지를 선형적으로 연결된 Convolutional 레이어 구조를 사용 필터링하여 특징을 추출해내는 뉴럴 네트워크이다. 전체 코드 구성에서 학습을 위해 Convolutional 레이어들을 사용하기 때문에 이 전문가용 예제를 익히기 위해서는 CNN에 대한 기본적인 이해를 가지고 있어야 함에 유의하자. 한편 GAN이란 Generative Adversarial Network 의 약어로서 번역하기가 쉽지 않은 듯하다. Generative 란 무엇인가로부터 생성한다는 의미이다. 그 무엇인가는 아무것도 없는 는 아니고 랜덤이라고 보면 된다, Adversarial은 그 뜻이 적대적인또는 대립적인으로 번역이 가능하다.

 

GAN은 오늘 날 컴퓨터 사이언스 분야에서 가장 핫한 주제중의 하나이다. 두 개의 모델들이 하나의 대립적인 과정에 의해 동시에 학습이 이루어진다. 생성(Generator)을 담당하는 모델은 아티스트로 볼 수 있는데 정말 실제처럼 보이는 이미지를 생성할 수 있도록 학습하는 반면에 대립적인 측면에서 날카로운 비판 즉 artistic critics를을 담당해야 하는 Discriminator 모델은 실제 이미지와 가짜(fake)를 구별할 수 있도록 학습한다.

 

GAN 예제 코드를 구글 홈페이지 예제 사이트로부터 Colaboratory에서 한 줄 한 줄 복사 붙여넣기 하여 실행해 보자. 코드는 100% 아무런 이상이 없으나 한 가지 전제적인 요구 사항을 미리 알아서 준비해야 한다. 즉 첫 번째로 런타임 초기화를 실행한 후 런타임 유형 변경에서 GPU 모드로 선택 저장이 되어 있어야 한다. GPU 설정 유무에 따라서 연산 실행 속도가 거의 100배 정도 차이가 일어난다. Epoch 한번 실행에 GPU 가속 모드가 아니면 750초가 소요되나 GPU 모드에서는 6.3초 가량 소요됨에 유의하자. 아울러 Colaboratory를 시작하기 전에 TensorFlow 버전을 확인해 보도록 한다. 2020년 이후 2.0에서 시작하여 2020년 말 현재 2.3 버전으로 업그레이드 되어 있음을 참조하자.

 

라이브러리 import

준비가 되었으면 다음과 같이 구글 홈페이지 예제에서 복사(Cntr+C) 붙여넣기(Cntr+V)를 시작 하도록 하자. 특히 라이브러리 파일 import 작업 이전에 pip 명령에 의한 imageio 설치는 코드 실행과정에서 Epoch 단계별로 생성되는 이미지 파일을 엮어서 전체적으로 애니메이션 파일을 생성하기 위해 사용된다.

 

MNIST 실제 데이타 입력

    GAN 코드에서 생성하고자 하는 실제와 유사한 fake 이미지를 생성하기 위한 MNIST 실제 데이타로서 Keras 로 읽어 들여 1+1 사이의 값으로 Normalization 시킨 후 Batch_Size에 해당하는 크기로 학습용 Batch 데이타 세트를 준비한다.

 

Generator 모델 코드

    처음에 가짜(fake) 이미지를 만들어 내기 위해서는 상당히 큰 NXN 픽셀 어레이가 필요하며 이 어레이를 input_shape=(100,) 100개 단위의 정수배에 해당하는 데이타로 채워 넣기로 하자. 물론 이 데이타는 평균 값 0을 중심으로 하는 랜덤한 정규분포 노이즈가 사용된다. 실제로 make_generator_model()을 불러 사용할 때에 noise=tf.random_normal([1,100]) 명령을 사용하는 것으로 보아 랜덤한 100개의 데이타를 제공하면 첫 번째 Sequential 레이어에서 100X12,544 크기의 웨이트 매트릭스가 도입되는 것으로 보자. use_bias=False가 사용되는데 이 문제는 True로 설정하여 그 계산 결과가 동일하다면 bias 사용 효과가 없는 셈이므로 False 로 두면 된다. 그 다음 100X12,544 개의 요소들을 (7,7,256) shape 형태로 처리하게 되면 100개가 생성되는 셈이므로 출력의 shape==(None, 7, 7, 256) 이 될 것이다. None은 정수 값을 취하는데 여기서는 즉 Batch_Size=256으로 둔다.

 

Keras Sequential의 각 레이어별로 통계학적 Normalization Batchnormalization 명령 실행은 컴퓨팅 효율을 증진시킨다고는 하지만 이미지 Clasification 네트워크 중 가장 높은 인식률을 보여 주었던 ResNet에서 Batchnormalization에 관한 다양한 논의가 있었음을 상기하자. PyTorchTransfer Learning 예제들 중에 ResNet 예제와 관련된 문헌을 참조하면 Batchnormalization의 변칙적인 사용에 관해 많은 언급이 있다. 아울러 LeakyRelu()는 함수f(x)의 독립변수 x가 양이면 f(x)=x 로 처리하고 독립변수 x가 음인 영역에서는 f(x)=αx(0≺α≪1)로 처리한다. LeakyRelu()를 사용하는 이유는 바로 전 입력 데이타의 Normalization 단계에서 (-1, 1) 범위로 처리했기 때문이다. 만약 (0, 1) 범위로 Normalization을 하였다면 Relu()를 사용했을 것이다.

아울러 make_generator_model() 함수 맨 마지막 부분에서 “tanh” 활성화 함수를 사용하게 되면 (-1, 1) 범위의 값이 얻어지게 된다.

한편 Keras Sequential에서 Shape (None, 7, 7, 256)으로부터 Shape (None, 7, 7, 128) 으로 Convolution에 의한 축소 과정은 의문으로 남는다. Shape 이 커지는 경우에는 추가로 파라메터인 웨이트 값과 필터 수를 늘리면 가능하겠지만 축소를 위해서는 파라메터를 줄여야 하나 어떤 원리인지 정확하게 명시되어 있지 않다.

이러한 축소과정을 거쳐 최종적으로 (None, 28, 28,1) 에 해당하는 즉 28X28 이미지를 얻어내자.

    

 

 

다음의 코드를 두 번 실행해 얻어지는 28X28 흑백 이미지들을 관찰해 보자. 랜덤 noise 데이타를 한번 입력 후 얻어지는 이미지이므로 training=False 임에 유의하자. Loss 함수를 최소화하는 Opimizer 사용 학습 시에는 training=True 로 설정되어야 한다.

특정한 사물의 형태를 인식해 내기 불가능한 랜덤한 이미지라 볼 수 있으며, 이 지점부터가 GAN 머신 러닝의 출발점이 된다. 특히 처음 시작하는 곳에서 TensorFlow==2.1.0 설정이 제대로 되어 있지 않다면 이 지점에서 Not Implemented Error 메시지를 받을 수도 있음에 유의하자.

 

 

Discriminator 코드

    Discriminator 코드는 CNN을 중심으로 하는 Image Classifier 이다. 크기가 28X28인 앞서 생성한 generated_image real image 입력이 가능하다. 원래의 CNN에서는 특징 추출(Feature Extraction)이 끝나는 단계에서 Fully Connected 레이어 처리에 의해 ImageNet이 제공하는 1000종의 Class 수에 맞도록 Flatten() 처리 후 활성화함수(Activation)를 사용하여 확률분포 처리가 이루어지지만 이와는 달리 GAN에서는 최종적으로 학습 결과가 Dense(1)이므로 Shape(1,1) 로 처리 되어 하나의 숫자 값이 얻어지며 이 값이 + 이면 진짜(real)이고 이면 가짜(fake)로 각각 판별하게 된다.

 

 

make_discriminateor_model()을 사용하여 연산을 실행하면 다음과 같이 + 또는 값이 출력된다.

 

 

Cross Entropy discriminator_loss

GAN 코드의 학습을 위해 Cross Entropy 함수를 정의하고 이를 사용해 discriminator_loss 함수를 설정하자. discriminator_loss 함수를 계산하기 위하여 필요한 입력 정보는 두 종류의 이미지로서 real_output fake_output이다. real_outputKeras MNIST Datadets에서 Batch_Size 만큼 읽어 들인 실제 이미지를 대상으로 하며 tf.ones_like(real_output) 처리를 통해 리스트형 매트릭스로 표현되는 real_output 텐서의 라벨 값을 1.0 으로 설정하게 된다.

반면에 Generator 모델로부터 생성되는 가짜(fake) 이미지의 학습을 위한 라벨 값 설정은 tf.zeros_like(fake_output) 처리에 의해 라벨 값이 0.0으로 설정 된다.

이와 같이 Cross Entropy 함수의 입력 데이타와 그 라벨 값이 준비된다면 GAN 학습을 위한 Loss 함수들의 설정이 가능해진다. 만약 이 단계에서 real_loss 또는 fake_loss를 사용하여 각각 독립적으로 학습(training) 시킨다면 MNIST real 이미지는 real 이미지라는 주어진 학습 데이타에 맞춰 학습이 되겠지만 fake에 해당하는 generated image는 학습을 위한 라벨 데이타가 없으므로 학습이 불가능할 뿐 더러 이는 아무런 의미가 없음을 알아야 한다. 하지만 이 둘을 합한 합성 Loss 함수를 학습시킨다면 real_loss 파트가 fake_loss 파트에 어떻게든 상호 영향을 미치게 될 것이다. 이 점에 관해서는 학습단계 코드에서 어떤 방식으로 처리하는지 관심을 가지고 살펴 볼 필요가 있을 것이다.

 

 

generator_loss

한편 discriminator_loss 보다 먼저 연산되어야 하는 generator_loss에서는 fake_output 입력에 대해서 라벨 값 텐서가 1.0 으로 설정 되어야 정말로 fake 가 되는 셈이다. 하지만 discriminator_loss에서는 동일한 fake_output 입력에 대해 라벨 값 텐서를 0.0 으로 설정함에 유의하자.

 

discriminator_loss 함수에서 real_loss 는 실제 이미지를 대상으로 학습해야 하며 fake_loss Generator 에 의해서 생성되는 가짜(fake)이미지를 대상으로 학습하게끔 되어 있다. 우선 Generator에서 생성되는 fake_output을 사용하여 generator_loss 처리 시에 초기 텐서 값들이 1.0 tf.ones_like(fake_output) 라벨 값들이 준비되면 이어서 discriminator_loss 함수 루틴에서 fake_loss 연산 시 tf.zeros_like(fake_output)의 라벨 값들이 0.0 으로 설정되면서 의 변화율이 real_loss 대비 fake_loss 가 동시에 병행적으로 학습이 이루어지게 된다.

 

Optimizer

Generatordiscriminator 각각에 Adam Optimizer를 설정하여 병렬로 학습을 시키도록 하자.

 

Checkpoint 폴더 설정

GAN 코드 연산 실행이 상당히 시간 소요가 크므로 학습 Epoch 별로 중간 결과를

저장하여 이미지를 출력하도록 하자. 2020년 현재 1 Epoch 6초가량이지만 현재의 코드를 구글 텐서플로우 홈페이지에 올린 시점에는 GPU 지원 하에 1 Epoch 1분이 소요된다고 기록되어 있으나 현재 2020년 구글 Colaboratory GPU 지원 하에서는 67초 수준으로 거의 10배가량 연산 속도가 빨라졌음에 주목한다.

 

 

GAN 파라메터 설정

GAN 머신러닝을 파라메터를 준비하자. 16은 생성하게 될 이미지 수를 뜻한다.

 

스텝별로 이미지 데이타를 입력하면 학습하는 루틴 train_step(images)를 함수로 정의함과 동시에 TensorFlow에서 실행 가능한 Graph로 생성하기 위한 특수 명령어 @tf.function을 선언하자. Batch_Size 수 만큼에 해당하는 noise 데이타를 준비한다. noise 데이타의 Shape(1,100)으로 주어진다. 두 개의 Loss 함수 Generator_loss Discriminator_loss를 대상으로 Gradient를 계산하여 처리할 수 있도록 녹취 저장이 가능한 GradientTape()를 사용하자. Gradient Descent 와 유사한 경사하강법이긴 하지만 Epoch 별로 연산 과정이 파일로 기록 저장되어 나중에 불러서 오픈할 수 있다는 특징이 있다.

 

 fake_output real_output이 준비되었으면 generator_loss discriminator_lossGradient를 계산하여 파일에 zip 압축파일로 저장하였다가

다시 불러내어 opimizer 에 적용한다. 저장되었던 Gradient 값들은 generator discriminator의 웨이트 값들을 업데이트 하는데 사용된다.

 

@tf.function 에 의한 Graph 생성

    다음과 같이 @tf.function에 의해 Graph 처리된 train_step(images) 함수는 학습단계에서 주어지는 입력 이미지 데이타 images를 대상으로 즉 Session 에 해당하는 학습을 시키게 된다.

 

아울러 @tf.function에 의해 Graph 처리되는 train(dataset, epochs) 함수를 정의하여 MNIST dataset을 구성하는 image_batch 별로 looping 시키면서 epoch 단계별 GIF 동영상을 보여 줄 수 있도록 image_batch 15초 간격으로 생성된 영상을 checkpoint.save() 함수를 사용하여 checkpoint 폴더에 저장한다.

 

한편 train(dataset, epochs) 함수에서 불러들이는 checkpoint.save() 함수는 별도로 아래와 같이 코드가 제공되어야 한다. @tf.function 명령에 의해 ‘Compile’ 작업에 의해 Graph 처리가 되므로 해당 루틴의 위치는 문제가 되지 않는다.

여기까지 TensorFlow Graph 준비가 완료되었으며 tf.Session에 해당하는 학습 명령 train (datasets, EPOCHS)을 실행하자. 67초 간격으로 EPOCH 수가 증가함에 따라 검은색 바탕에서 흰색 자욱이 생성되는 것을 관찰할 수 있을 것이며 EPOCH 수가 50에 도달하면 그 이미지가 일종의 손글씨라는 것을 알 수 있을 것이다.

 

 

checkpont 폴더에 EPOCH 단계별로 생성된 진짜같은 fake image gif 포맷으로 저장되어 있으므로 애니매이션을 볼 수 있을 것이다.

MNIST real_output을 사용하여 fake_output 생성이 가능함을 알 수 있었다. 아울러 MNIST 텐서플로우 코드 실행과 대단히 유사한 Fashion_MNIST를 대상으로 fake_output을 생성해 보기로 한다. 코드에서의 변경 사항은 헤더 영역에서 Keras import 부분과 Keras Fashion_MNIST데이타를 로딩해야 하며 EPOCH 수를 50에서 100 으로 늘리도록 하자.

EPOCH 별로 생성되는 이미지를 관찰해 보자. 처음에는 희뿌연 이미지에서 시작하여 점차 의류 신발류 이미지를 생성함을 볼 수 있을 것이다.