https://github.com/clayryu/clayryu-22_dev_maskgan_project
전체 코드는 위의 링크에서 볼 수 있습니다.
마스크를 착용한 이미지와 마스크를 착용하지 않은 얼굴 이미지를 생성하는 생성 모델을 만드는 과제이다. 아래의 두 가지 대표적인 GAN 모델을 실습한 뒤에 커스텀 아키텍처의 GAN 모델을 바탕으로 FID score에서 좋은 점수를 내는 것이 목표이다.
Generative Adversarial Networks (NIPS 2014)
Deep Convolutional Generative Adversarial Networks (ICLR 2016)
점수를 계산하는 방식은 FID Score를 사용한다.
Rethinking the Inception Architecture for Computer Vision (CVPR 2016)
Fréchet Inception Distance(FID)는 생성된 이미지들과 실제 이미지들을 ImageNet에 넣어 특징(feature)을 추출한다.
생성된 이미지 특징 분포 G와 실제 이미지 특징 분포 X를 가우시안 분포라고 가정하고, 두 분포가 얼마나 다른지 계산하기 위해 FID(X, G)를 계산한다.
FID score for PyTorch 라이브러리를 이용해 간단히 계산할 수 있다.
실습에는 Face Mask Classification 데이터셋을 사용한다.
!git clone https://github.com/ndb796/Face-Mask-Classification-20000-Dataset
캐글 경진대회 형식으로 다른 팀들과 겨루는 방식이었다. 최종 성적은 전체 4위였다.
이번에 배우게 된 가장 중요한 것은 성능과 점수의 향상을 위해서는 크게 2가지, 데이터와 모델을 더 발전 시킬 필요가 있다는 것이었다.
다만 모델의 향상은 그 모델이 제안된 배경을 알고 알고리즘의 특징을 제대로 이해를 해야 실제 코드로 적용을 하고 또 학습이 잘 되지 않을 때 제대로된 피드백을 할 수 있다는 것을 배울 수 있었다. 또한 다른 팀과 비교 했을 때 우리는 데이터 augmentation이나 데이터를 늘리는 기본적인 아이디어를 완전히 망각하고 있었다. 이 부분만 잡았어도 훨씬 더 좋은 성능을 낼 수 있었을텐데 참 아쉬웠다.
이전의 과제들과는 다르게 이번에는 무엇을 해야하는 지를 어느정도 이해하고, 내 방식대로 따라가고 있다는 점이 재밌었다. 인공지능 모델을 학습이 될 수 있도록 network와 학습 코드를 '제대로' 구조화 한다는 것에 목표를 두고 VanilaGAN, CGAN, DCGAN, WGAN, InfoGAN, ACGAN 등을 분석하고 데이터셋에 적용해보는 과정으로 연구를 해보았다.
DCGAN은 Convolution을 적용한 GAN이다. 나동빈 강사님이 코드를 준비를 해주셨기 때문에 그 코드를 뜯어보면서 GAN이 실제로 어떻게 작동하는지를 이해해보았다.
이후에 custom GAN을 만들기 위해서 다양한 GAN 모델들을 뜯어보면서 학습을 시키는 일을 해보았다.
WGAN은 wasserstein distance, discriminator 대신 critic의 사용 하는 GAN이다. discriminator와 generator 사이의 밸런스를 다른 방식으로 잡기위해서 구상이 되었다고 한다.
WGAN에 대한 자세한 내용은 https://engineer-mole.tistory.com/52 참고
WGAN의 모델은 https://github.com/eriklindernoren/PyTorch-GAN 참고를 했으며,
Conditional GAN의 구현은 https://github.com/gcucurull/cond-wgan-gp 참고를 했다.
모델들을 살펴보면 WGAN의 model architecture 자체는 vanilaGAN을 따라 fully connected layer를 사용했기 때문에 특별한 것이 없다고 느껴졌다. 하지만 학습에서 loss를 구현하는 부분은 상당히 어려웠다. loss함수의 알고리즘에 대한 원리는 이해하지를 못하고, 우선 적용을 해보았지만 결과는 실패였다.
아래 이미지는 실패한 학습의 결과이다. label을 1로 주었기에 캐릭터들이 마스크를 쓰고 있어야 하는데, 자기들 멋대로다.
wgan에서 특별한 점은 학습에서 사용하는 loss가 vanila GAN과는 다르다는 것이다.
기존의 vanila GAN은 generator에 대해서는 생성된 가짜 이미지를 1에 최대한 가깝게 하는 것을 목표로 하며, discriminator에 대해서는 진짜 이미지는 1에 가깝게 판별하고, 생성된 가짜 이미지는 0에 가깝게 하는 것을 목표로 한다.
# vanila gan
# 이미지 생성
generated_imgs = simple_generator(z, generated_labels)
# 생성자(generator)의 손실(loss) 값 계산
g_loss = adversarial_loss(simple_discriminator(generated_imgs, generated_labels), real)
# 생성자(generator) 업데이트
g_loss.backward()
optimizer_G.step()
# 판별자(discriminator)의 손실(loss) 값 계산
real_loss = adversarial_loss(simple_discriminator(real_imgs, labels), real)
fake_loss = adversarial_loss(simple_discriminator(generated_imgs.detach(), generated_labels), fake)
d_loss = (real_loss + fake_loss) / 2
# 판별자(discriminator) 업데이트
d_loss.backward()
optimizer_D.step()
WGAN에서도 vanila gan과 같이 Discriminator 혹은 critic의 판단력을 높이는 방향으로 loss 알고리즘을 설정하되, loss 값이 판단값의 평균이라는 점이 특이하다. code reproducing의 문제인지 학습은 제대로 되질 않았다. 추가적인 연구가 필요할 듯 하다.
# wgan
# Real images
real_validity = wg_discriminator(real_imgs, labels)
# Fake images
fake_validity = wg_discriminator(fake_imgs, labels)
# Gradient penalty
gradient_penalty = compute_gradient_penalty(
wg_discriminator, real_imgs.data, fake_imgs.data,
labels.data)
# Adversarial loss
loss_D = -torch.mean(real_validity) + torch.mean(fake_validity) + lambda_gp * gradient_penalty
# Loss measures generator's ability to fool the discriminator
# Train on fake images
fake_validity = wg_discriminator(fake_imgs, labels)
loss_G = -torch.mean(fake_validity)
loss_G.backward()
optimizer_G.step()
우선 InfoGAN을 참고하다가 중요한 알고리즘은 다 쓰지 않고 network에서의 주요 내용인 conv, deconv층을 사용하기로 했다. 이 network의 특징은 generator에서는 Deconv를 사용하고, discriminator에서는 conv를 사용한다는 것이다.
# generator의 주요 층
self.fc = nn.Sequential(
nn.Linear(self.input_dim + self.class_num, 1024),
nn.BatchNorm1d(1024),
nn.ReLU(),
nn.Linear(1024, 128 * (self.input_size // 4) * (self.input_size // 4)),
nn.BatchNorm1d(128 * (self.input_size // 4) * (self.input_size // 4)),
nn.ReLU(),
)
self.deconv = nn.Sequential(
nn.ConvTranspose2d(128, 64, 4, 2, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.ConvTranspose2d(64, self.output_dim, 4, 2, 1),
nn.Tanh(),
)
generator의 구조는 fc층과 deconv층으로 이루어져 있다.
deconvolution에 대한 자세한 설명은 아래를 참조하면 좋을듯 하다.
https://medium.com/apache-mxnet/transposed-convolutions-explained-with-ms-excel-52d13030c7e8
https://deep-mind-learning.tistory.com/36
generator층의 구조는 입력 이미지를 층이 128개이고 사이즈가 16 x 16으로줄어든 fc층으로 만들어주고(하지만 값 자체는 64, 128 x 16 x 16인 2D이다.), 그것을 2배씩 2번 upsampling해서 다시 64 x 64인 크기로 맞춰주는 방식이다.
# discriminator 층의 구조
def make_block(in_channels, out_channels, bn=True):
# 하나의 블록(block)을 반복할 때마다 너비와 높이는 2배씩 감소
block = [nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=2, padding=1)]
block.append(nn.LeakyReLU(0.2, inplace=True))
block.append(nn.Dropout2d(0.25))
if bn:
block.append(nn.BatchNorm2d(out_channels, 0.8))
return block
# 너비와 높이가 2배씩 감소
self.conv_blocks = nn.Sequential(
*make_block(2, 32, bn=False),
*make_block(32, 64),
*make_block(64, 128),
*make_block(128, 256),
*make_block(256, 512),
)
self.fc = nn.Sequential(
#nn.Linear(128 * (self.input_size // 4) * (self.input_size // 4), 1024),
nn.Linear(512 * 2 * 2, 1024),
nn.BatchNorm1d(1024),
nn.LeakyReLU(0.2),
nn.Linear(1024, self.output_dim),
nn.Sigmoid(),
)
discriminator는 DCGAN에서 conv층을 사용하는 방식을 차용을 했다. conv 층을 5번을 사용해서 이미지의 크기를 32배 줄여주고 이를 fc층으로 통과 시켜서 sigmoid를 통과한 하나의 값으로 만들어 주었다.
학습에서는 앞서 설명한 vanila gan의 방식을 그대로 사용했으며
특이하게도 이 model이 가장 좋은 결과를 냈다.
하지만 deconv와 conv를 통과한 층의 효과가 가장 좋은 이유를 알 수가 없어서 조금 답답하다.