공부/AIFFEL

Exploration 20 : 난 스케치를 할 테니 너는 채색을 하거라

dong_dong_2 2021. 3. 23. 21:15

1. 조건 없는 생성모델(Unconditional Generative Model), GAN
    - GAN을 이용하여 MNIST, CIFAR-10 등의 데이터셋을 학습하고 생성해 본 경험이 있는가?
    - 성공적으로 학습되었다면, 학습에 사용한 실제 손글씨 이미지와 매우 유사한 손글씨 이미지를 생성했을 것이다.
    - 그런데 한 가지 생각을 해보자.
       1) 우리가 7이란 이미지를 만들고자 MNIST 데이터셋을 이용해 GAN을 열심히 학습을 시켰다.
       2) 학습이 완료된 모델을 이용해 7 이라 쓰여있는 이미지를 얻기 위해 어떤 방법을 이용해야 할까?
       3) 여기서 할 수 있는 방법은 다양한 노이즈를 입력으로 넣어보면서 7이 나올 때 까지 시도해보는 것이다.
       4) 이렇듯 잘 학습된 GAN을 이용해 실제 이미지를 생성할 때 조금 답답한 점이 하나 있다면, 바로 내가 원하는 종류의 이미지를 생성해 내지 못한다는 점이다.
       5) 다시 말해 일반적인 GAN과 같은 unconditioned generative model은 내가 생성하고자 하는 데이터에 대해 제어하기 힘들다.
    - 우리가 원하는 이미지를 바로바로 생성해 내기 위해 어떤 방법을 사용해야 할까? GAN이란 생성모델에 내가 원하는 이미지를 만들기 위한 조건을 줄 순 없을까?
    - 오늘 우리는 이 주제에 대해서 배울 것이다.
2. 조건 있는 생성모델(Conditional Generative Model), cGAN
    - Conditional Generative Adversarial Nets (cGAN)은 내가 원하는 종류의 이미지를 생성하고자 할 때 GAN이 가진 생성 과정의 불편함을 해소하여, 원하는 종류의 이미지를 생성할 수 있도록 고안된 방법이다.
    - GAN을 잘 이해하고 있다면 cGAN은 전혀 어렵지 않다고 한다.
    - GAN의 목적 함수 (식에 대한 사진은 블로그엔 있지만 페이스북엔 없으므로 찾아보면 좋다)
       1) 먼저 GAN에 대해 복습해보자.
       2) GAN 구조는 Generator 및 Discriminator라 불리는 두 신경망이 minmax game을 통해 서로 경쟁하며 발전한다.
       3) 이를 식으로 나타낼 수 있으며 Generator는 이 식을 최소화하려하고, Discriminator는 이 식을 최대화하려 학습한다.


       4) 식에서 z는 임의 노이즈를, D와 G는 각각 Discriminator 및 Generator를 의미한다.
       5) 먼저 D의 입장으로 식을 보자
          (1) 실제 이미지를 1, 가짜 이미지를 0으로 두었을 때, D는 이 식을 최대화해야 하며, 우변의 +를 기준으로 양쪽의 항이 모두 최대가 되게 해야 한다.
          (2) 이를 위해서 두 개의 log가 1이 되게 해야 한다.
          (3) D(x)는 1이 되도록, D(G(z))는 0이 되도록 해야 한다.
          (4) 다시 말하면, 진짜 데이터(x)를 진짜로, 가짜 데이터(G(z))를 가짜로 정확히 예측하도록 학습한다는 뜻이다.
       6) 그럼 이번엔 G입장에서 식을 보자
          (1) D와 반대로 G는 식을 최소화해야 하고 수식에서는 마지막 항만을 최소화 하면 된다. (첫 번째항은 G와 관련이 없기에)
          (2) 이를 최소화한다는 것은 log 내부가 0이 되도록 해야함을 뜻하며, D(G(z))가 1이 되도록 한다는 말과 같다.
          (3) 즉, G는 z를 입력받아 생성한 데이터 G(z)를 D가 진짜 데이터라고 예측할 만큼 진짜 같은 가짜 데이터를 만들도록 학습한다는 뜻이다.
    - cGAN의 목적 함수
       1) GAN과 비교하며 알아볼 cGAN의 목적함수의 식을 보면


       2) cGAN의 목적함수와 GAN의 목적함수를 비교해보자.
          (1) GAN의 식에서의 D(x)와 G(z)의 부분이 cGAN에서는 D(x | y), G(z | y)로 바뀌었다.
       3) GAN의 목적함수와 비교해 식에서 달라진 부분을 잘 찾아냈다면 cGAN이 크게 어렵지 않을 것이다.
       4) 위 식에서 바뀐 부분은 우변의 + 를 기준으로 양쪽 항에 y가 추가되었단 것 뿐이다.
       5) G와 D의 입력에 특정 조건을 나타내는 정보인 y를 같이 입력한다는 것이다.
       6) 이외에는 GAN의 목적함수와 동일하므로 각각 y를 추가로 입력받아 G의 입장에서 식을 최소화하고, D의 입장에서 식을 최대화하도록 학습한다.
       7) 여기서 함께 입력하는 y는 어떤 정보여도 상관없으며, MNIST 데이터셋을 학습시키는 경우 y는 0 ~ 9까지의 label 정보가 된다.
       8) Generator가 어떤 노이즈 z를 입력받았을 때, 특정 조건 y가 함께 입력되기 때문에, y를 통해 z를 어떤 이미지로 만들어야 할지 방향을 제어할 수 있게 된다.
       9) 조금 다르게 표현하면 y가 임의 노이즈 입력인 z의 가이드라고 볼 수 있다.
3. 내가 원하는 숫자 이미지 만들기 (1) Generator 구성하기
    - 지금부터는 앞에서 비교해온 GAN과 cGAN을 각각 간단하게 구현하고 실험해봤다.
    - 간단한 실험을 위해 MNIST 데이터셋을 이용했다. 실습 코드는 과제를 하면서 같이 실습했다.
    - 순서는 아래와 같다.
       1) 데이터를 준비하고, 전처리해주는 함수를 만들었다.
       2) GAN Generator를 구성했다. 
       3) cGAN Generator를 구성했다.
4. 내가 원하는 숫자 이미지 만들기 (2) Discriminator 구성하기
    - 이 부분도 코드 위주로 실습했다. 순서는 아래와 같다.
       1) GAN Discriminator를 구성했다.
       2) cGAN Discriminator를 구성했다.
5. 내가 원하는 숫자 이미지 만들기 (3) 학습 및 테스트하기
    - 지금까지 정의한 Generator 및 Discriminator를 이용해 MNIST를 학습하고 각 모델로 직접 숫자 손글씨를 생성했다.
       1) 우선 GAN, cGAN 각각의 모델 학습에 공통적으로 필요한 loss fuction과 optimizer를 정의했다.
       2) 진짜 및 가짜를 구별하기 위해 Binary Cross Entropy를 사용하고, Adam optimizer를 이용해 학습했다.
       3) 그리고 GAN을 학습시키고, 결과물을 시각화 했다. 10 epochs를 했는데 결과는 만족스럽지 않다.
       4) 내가 한 구현을 그대로 500 epoch 학습한 가중치를 다운 받아서 시각화 해봤다. 10개의 서로 다른 결과가 나올 수도, 아닐 수도 있었는데 나는 다른 결과가 나왔다.
       5) 그리고 cGAN을 학습시키고 결과물을 시각화 했다. 이것도 결과가 안 좋을게 뻔해서 1epoch만 해보고, 500번 학습한 가중치를 이용해서 시각화했는데 나는 4를 해봤는데 10개 다 4를 그렸었다.
6. GAN의 입력에 이미지를 넣는다면? Pix2Pix
    - 지금까지 cGAN에 대해 알아보고 실험해보면서, 작은 조건만으로 우리가 원하는 클래스의 이미지를 생성할 수 있다는 것을 확인했다.
    - 만약 입력자체가 조건이 된다면 어떨까? cGAN과 같이 클래스 레이블 등의 조건을 함께 입력하는 것이 아니라, 조금 더 자세하게 내가 원하는 이미지를 얻기 위해 이미지를 조건으로 줄 수는 없을까?
    - 이제 배울 Pix2Pix는 기존 노이즈 입력을 이미지로 변환하는 일반적인 GAN이 아니라, 이미지를 입력으로 하여 원하는 다른 형태의 이미지로 변환시킬 수 있는 GAN 모델이다.
    - Pix2PIx를 제안한 논문의 제목은 Image-to-Image Translation with Conditional Adversarial Networks로 하고자 하는 바가 제목 그대로 적혀있다.
    - Conditional Adversarial Networks로 Image-to-Image Translation을 수행한다는 뜻이다.
    - Conditional Adversarial Networks는 이전까지 알아봤던 cGAN과 같은 구조를 말하는 것인데, Image-to-Image Translation이란 무엇을 뜻하는 것일까?
    - Image-to-Image Translation은 이미지 간 변환을 말한다.
    - 한 이미지의 픽셀에서 다른 이미지의 픽셀로(pixel to pixel) 변환한다는 뜻에서 Pix2Pix라는 이름으로 불린다.
    - 이 구조는 최근 활발하게 연구 및 응용되는 GAN 기반의 Image-to-Image Translation 작업에서 가장 기초가 되는 연구이다.
    - 노이즈와 레이블 정보를 함께 입력했던 cGAN은 fully-connected 레이어를 연속적으로 쌓아 만들었지만
    - 이미지 변환이 목적인 Pix2Pix는 이미지를 다루는데 효율적인 convolution 레이어를 활용한다.
    - GAN 구조를 기반으로 하기 때문에 크게 Generator와 Discriminator 두 가지 구성 요소로 이루어진다. 아래에서 자세히 다뤄본다.
    - Pix2Pix (Generator)


       1) Generator는 어떤 이미지를 입력받아 변환된 이미지를 출력하기 위해 사용된다.
       2) 여기서 입력 이미지와 변환된 이미지의 크기는 동일해야 하며, 이런 문제에서 흔히 사용되는 구조는 Encoder-Decoder 구조이다.
       3) Encoder에서 입력 이미지(x)를 받으면 단계적으로 이미지를 down-sampling하면서 입력 이미지의 중요한 representation을 학습한다.
       4) Decoder에서는 이를 이용해 반대로 다시 이미지를 up-sampling하여 입력 이미지와 동일한 크기의 변환된 이미지(y)를 생성해낸다.
       5) 이런 과정은 모두 convolution 레이러로 진행되며, 레이어 내의 수많은 파라미터를 학습하여 잘 변환된 이미지를 얻도록 한다.
       6) 여기서 한 자기 짚고 넘어갈 부분은, Encoder의 최종 출력은 bottleneck이라 불리는 부분이며 입력 이미지(x)의 가장 중요한 특징만을 담고 있다.
       7) 근데 이 중요하지만 작은 특징이 변환된 이미지(y)를 생성하는데 충분한 정보를 제공할 것인가? 이와 같은 점을 보완하기 위해 논문에서는 Generator 구조를 하나 더 제안했다.
       8) 바로 U-Net이라는 구조이다. 단순한 Encoder-Decoder로 구성된 Generator와 다른 점은, 각 레이어마다 Encoder와 Decoder가 연결(skip connection) 되어 있다는 것이다.


       9) Decoder가 변환된 이미지를 더 잘 생성하도록 Encoder로부터 더 많은 추가 정보를 이용하는 방법이며, 이러한 U-Net 구조의 Generator를 사용해 단순한 Encoder-Decoder 구조의 Generator를 사용한 결과에 비해 비교적 선명한 결과를 얻을 수 있다.
      10) 여기서 U-Net은 이전에 segmentation 작업을 위해 제안된 구조이다. U-Net에 대한 자세한 내용은 이번 노드의 학습 범위를 벗어나므로 생략한다.
    - Pix2Pix (Loss Function)
       1) 위 Generator 구조를 보면 Generator만으로도 이미지 변환이 가능할 거 같은 생각이 든다. 물론 가능하다. AutoEncoder 형태의 접근은 Generator만으로 이미지 변환을 진행한다.
       2) 당연하게도 변환하고자 하는 이미지를 Encoder에 입력하여 Decoder의 출력으로 변환된 이미지를 얻을 수 있다.
       3) 출력된 이미지와 실제 이미지의 차이로 L2(MSE), L1(MAE) 같은 손실을 계산한 후 이를 역전파하여 네트워크를 학습시키면 된다.
       4) 이미지 변환이 가능은 하지만 문제는 변환된 이미지의 품질에 있다.
       5) L1 손실을 이용한 Generator만으로 생성된 결과는 매우 흐릿하게 나온다.
       6) 이미지를 변환하는데 L1(MAE)이나 L2(MSE) 손실만을 이용해서 학습하는 경우 결과가 흐릿해지는 경향이 있다.
       7) Generator가 단순히 이미지의 평균적인 손실만을 줄이고자 파라미터를 학습하기 때문에 이런 현상은 불가피하다.
       8) 반면 cGAN의 GAN 기반의 학습 방법은 비교적 훨씬 더 세밀한 정보를 표현하게 된다.
       9) Discriminator를 잘 속이려면 Generator가 진짜 같은 이미지를 만들어야 하기 때문이다.
      10) 논문에서는 L1손실과 GAN 손실을 같이 사용하면 더욱더 좋은 결과를 얻을 수 있다고 한다.
    - Pix2Pix (Discriminator)
       1) 위에서 보듯 실제 같은 이미지를 얻기 위해서는 GAN의 학습 방법을 이용해야 하며, 위에서 설명한 Generator를 발전시킬 서폿이자 경쟁자인 Discriminator가 필요하다.
       2) 이전에 다룬 DCGAN을 기억하는가? DCGAN의 Discriminator를 다시 상기시켜보자.
       3) DCGAN의 Discriminator는 생성된 가짜 이미지 혹은 진짜 이미지를 하나씩 입력받아 convolution 레이어를 이용해 점점 크기를 줄여나가면서, 최종적으로 하나의 이미지에 대해 하나의 확률값을 출력했다.
       4) Pix2Pix는 이 과정에서 의문을 가졌다. "하나의 전체 이미지에 대해 하나의 확률값만을 도출하는 것이 진짜 혹은 가짜를 판별하는데 좋을 것인가?" 라는 의문이다.
       5) Pix2Pix는 이런 의문점을 가지고 조금 다른 방식의 Discriminator를 사용한다.


       6) 인터넷에 찾아보면 Pix2Pix에서 사용되는 Discriminator를 간략하게 나타내는 그림을 쉽게 볼 수 있다.
       7) 하나의 이미지가 Discriminator의 입력으로 들어오면, convolution 레이어를 거쳐 확률값을 나타내는 최종 결과를 생성하는데, 그 결과는 하나의 값이 아닌 여러 개의 값을 갖는다.
       8) 그림에서 입력이미지의 파란색 점선은 여러 개의 출력 중 하나의 출력을 계산하기 위한 입력이미지의 receptive field 영역을 나타내고 있으며, 전체 영역을 다 보는 것이 아닌 일부 영역(파란색 점선)에 대해서만 진짜 / 가짜를 판별하는 하나의 확률값을 도출한다는 것이다.
       9) 이런 방식으로 서로 다른 영역에 대해 진짜 / 가짜를 나타내는 여러 개의 확률값을 계산할 수 있으며 이 값을 평균하여 최종 Discriminator의 출력을 생성한다.
      10) 이런 방법은 이미지의 일부 영역(patch)을 이용한다고 하여 PatchGAN이라고 불린다.
      11) 일반적으로 이미지에서 거리가 먼 두 픽셀은 서로 연관성이 거의 없기 때문에 특정 크기를 가진 일부 영역에 대해서 세부적으로 진짜 / 가짜를 판별하는 것이 Generator로 하여금 더 진짜 같은 이미지를 만들도록 하는 방법이다.
7. 난 스케치를 할 테니 너는 채색을 하거라 (1) 데이터 준비하기
    - 지금부터는 앞서 알아본 Pix2Pix 모델에 대해서 직접 구현해보고 실험해봤다. 이번에 사용할 데이터셋은 Sketch2Pokemon 이라는 데이터셋이다.
    - 이제부터 코드 위주의 진행이다. 순서는 다음과 같다.
       1) 먼저 링크(혹은 터미널 명령어)를 통해 데이터를 다운받는다.
       2) 데이터를 읽어오고, 시각화를 통해 데이터가 어떤 형태인지 본다.
       3) 하나의 이미지에 스케치와 실제 포켓몬 이미지가 같이 있어서 이 둘을 잘라냈다.
       4) 이미지가 800개 뿐이라 augmentation 방법을 적용하여 데이터를 다양하게 만들었다.
       5) 그리고 다양하게 만든 데이터 일부를 시각화 해봤다.
8. 난 스케치를 할 테니 너는 채색을 하거라 (2) Generator 구성하기
    - 이제 본격적으로 Pix2Pix 구조를 구현한다. 앞서 cGAN 구현과 같이 Tensorflow의 Subclassing 방법을 이용해 모델을 만들었다.
    - Generator의 구성요소 알아보기
       1) 먼저, pix2pix 논문에서 Generator를 구성하는데 필요한 정보를 가져왔다.
       2) 논문에서 표기한 encoder의 C64라는 부분은 어떤 하이퍼파라미터를 가진 레이어들의 조합인가?
          (1) 64개의 4x4 필터에 stride 2를 적용한 Convolution -> 0.2 slope의 LeakyReLU 이다.
          (2) 그리고 논문에서는 BatchNorm을 사용하지 않는다고 쓰여있다.
       3) 논문에서 표기한 decoder의 CD512라는 부분은 어떤 하이퍼파라미터를 가진 레이어들의 조합인가?
          (1) 512개의 4x4필터에 stride 2를 적용한 (Transposed) Convolution -> BatchNorm -> 50% Dropout -> ReLU의 조합니다.
    - 구현은 논문의 내용을 토대로 Generator의 Encoder, Decoder를 구현했다.
9. 난 스케치를 할 테니 너는 채색을 하거라 (3) Generator 재구성하기
    - 위에서 Encoder와 Decoder를 연결시켜 Generator를 만들었다.
    - 하지만 앞서 말한대로 Pix2Pix의 Generator 구조는 두 가지를 제안했다. (Encoder-Decoder / U-Net)
    - 단순한 Encoder-Decoder 구조에 비해 Encoder와 Decoder 사이를 skip connection으로 연결한 U-Net 구조를 사용한 결과가 훨씬 실제 이미지에 가까운 품질을 보인다.
    - 이전 단계에서 구현한 것은 Encoder-Decoder 구조였다.
    - Encoder에서 출력된 결과를 Decoder의 입력으로 연결했고, 이 외에 추가적으로 Encoder와 Decoder를 연결시키는 부분은 없었다.
    - 더 좋은 결과를 위해 U-Net 구조를 만들어서 실험했다. (코드로)
10. 난 스케치를 할 테니 너는 채색을 하거라 (4) Discriminator 구성하기
    - Generator만으로 좋은 결과를 도출하기에 부족하다. 조금 더 사실적인 이미지를 생성하기 위한 Pix2Pix를 완성시키기 위해서는 Discriminator가 필요하다
    - Discriminator의 구성요소 알아보기
       1) 논문을 보면 Generator의 구성 요소와 똑같이 C64 등으로 표기되어있다. 진짜 및 가짜 이미지를 판별하기 위해 최종 출력에 sigmoid를 사용하는 것을 제외하면 특별한 변경 사항은 없어 보인다.
       2) 이어서 쉽게 표현한 그림을 통해 Discriminator의 구조를 자세히 그려보자.


          (1) Discriminator는 2개의 입력(그림의 in, unknown)을 받아 연결(CONCAT)한 후, ENCODE 라고 쓰인 5개의 블록을 통과한다.
          (2) 이 중 마지막 블록을 제외한 4개 블록은 논문에서 표기된 C64-C128-C256-C512에 해당하며, 마지막 1(채널)차원 출력을 위한 블록이 추가되었다.
          (3) 최종적으로 출력되는 크기는 (30, 30, 1)이며, 그림의 출력 이전의 2개의 ENCODE 블록을 보면 각각의 출력 크기가 32, 31, 30으로 1씩 감소하는 것을 알 수 있다.
          (4) Generator에서도 2 stride convolution에 패딩을 이용하면 (width, height) 크기가 절반씩 감소할 것이다.
          (5) 1 stride convolution에 패딩을 하지 않는다면 (width, height) 크기는 (필터 크기가 4이므로) 3씩 감소할 텐데, 어떻게 1씩 감소시켰을까? 이 대답은 실습코드에 있다.
          (6) 추가적으로 그림에서 최종 출력 크기가 (30, 30)이 되어야 하는 이유는 앞서 Discriminator에 대해 알아봤던 70x70 PatchGAN을 사용했기 때문이다.
          (7) 최종 (30, 30) 출력에서 각 픽셀의 receptive field 크기를 (70, 70)으로 맞추기 위해 Discriminator의 출력 크기를 (30, 30) 크기로 강제로 맞추는 과정이다.
    - Discriminator를 코드로 구현해봤다.
11. 난 스케치를 할 테니 너는 채색을 하거라 (5) 학습 및 테스트 하기
    - 이번 단계에서는 구현된 Generator와 Discriminator를 학습시켜보고, 스케치를 입력으로 채색된 이미지를 생성해 봤다.
    - 먼저 학습에 필요한 손실함수부터 정의했다.
    - 논문에서 여러 실험 결과 중 손실함수 선택에 따른 결과의 차이를 보여주는 부분이 있었다.
    - 레이블 정보만 있는 입력에 대해 여러 손실함수를 사용해 실제 이미지를 만들어 낸 결과는, 일반적인 GAN의 손실함수에 L1을 추가로 이용했을 때 가장 실제에 가까운 이미지를 생성해냈다.
    - 이번 실험에서도 두 가지 손실 함수를 모두 사용해봤다.
    - 데이터를 학습시켜보고(10epoch), 테스트를 해봤다. 조금 오래 학습했어도 채색해야 할 전체적인 색감 정도만 학습되며 세부적으로 제대로 채색되지는 않았다.
12. 프로젝트 : Segmentation map으로 도로 이미지 만들기
    - 이전 pix2pix 논문에서 제시한 결과 중 도로 레이블 정보를 활용해 이미지를 생성해낸 결과가 있었다. 
    - 이번 프로젝트는 그 결과와 같은 데이터셋을 이용해 Pix2Pix를 학습시켜보는 것이다.
    - 데이터는 링크를 통해 다운 받았다. 1000개의 학습용 이미지와 5개의 평가 이미지를 포함하고 있다.
    - 이전 포켓몬 데이터에서 사용했던 스케치와 조금 다른 레이블 정보 이미지를 입력으로 사용하기 때문에, 전처리 과정에 대해 약간의 혼란이 있을 것이라 한다.