-
CIFAR-10 dataset을 이용한 DCGAN 구현개인 프로젝트/GAN(Generative Adversarial Networks) 2020. 11. 3. 03:51
GAN 관련 논문을 읽었는데 아직 논문 리뷰 정리를 다 하지 못했다.
먼저 DCGAN을 CIFAR-10 dataset와 pytorch을 이용해서 구현했다.
이번 정리에서는 DCGAN 논문 리뷰가 아닌 DCGAN에 대한 개념 설명과 코드 구현을 목표로 한다.
전체 코드는 github에 올려놓았다.
2015년에 발표한 DCGAN(Deep Convolutional GAN)은 original GAN의 구조를 발전시켜 최초로 고화질 영상을 생성시킬 수 있기 때문에 어떤 요인들이 이런 발전을 가능하게 한지 살펴볼 필요가 있다.
불과 2년만에 1000회 이상 인용이 되었을 정도로 유명한 논문이라고 한다.
이미지 분류하는 문제 등에서 CNN을 사용하는 것을 알 수 있다. 이미지를 만들어내는 GAN에서도 이러한 Convolution을 적용하면 어떨까? 하는 생각에서 등장한 DCGAN이다.
이미지는 사실 3차원의 데이터, 큐브라고 생각할 수 있다. 픽셀 하나하나는 RGB 값을 가진다. 컬러영상이면 즉 64*64이미지는 사실 3*64*64의 데이터와 같은 것이다. CNN과 MLP의 차이점은 이 부분이다. MLP는 3차원의 데이터를 1개의 벡터로 풀어서 인식한다. 직관적으로 3차원의 데이터를 1차원으로 쫙 늘려서 계산하다고 생각하면 된다.
이 경우 이미지에서 위치 정보를 무시하게 된다는 문제점이 발생한다. 위치 정보를 가지고 있는 픽셀들이 3차원이 1차원으로 바뀌면서 문제가 발생한다. 사람의 얼굴이라면 만약에 이미지에서 눈이나 코의 위치 정보가 사라지면 이는 사람으로 파악하지 못할 것이다. CNN은 3차원의 이미지를 그대로 입력으로 취하기 때문에 위치 정보를 반영 할 수 있다.
이미지를 10개의 클래스로 분류하는 CNN을 생각해보자.
3차원의 이미지가 layer를 거쳐 각 클래스에 해당하는 확률값들이 나오게 되는데 이는 즉 차원이 줄어드는 효과가 나타난다. Discriminator는 이러한 방식 그대로 작동한다.
이랍ㄴ적인 CNN,Discriminator는 데이터의 차원이 줄어드는데 Generator는 차원을 확장시켜야 한다. 이때 DCGAN의 경우 De-convolution이라는 방식을 사용한다.
이 방식은 내가 CS231n을 보고 정리한 자료에서도 확인 할 수 있다.
간단하게 그림으로 보면 이해가 편할 것이다.
위 그림은 먼저 일반적인 Convolution이다. 파란색이 input이고 초록색이 output이다.
Transposed Convolution이라고도 한다. 2*2 입력이 4*4로 커지는 것을 확인 할 수 있다. 차원을 확장시켜주는 역할을 한다.
또한 입력데이터로 사용하는 입력 잡음(input noise) z 값을 살짝 바꾸면, 생성되는 이미지가 그것에 반응하여 살짝 변하게 되는 vector arithmetic의 개념을 찾아냈다.
DCGAN의 구조적 특징
논문의 저자들은 최적의 결과를 내기 위해, 5가지 방법을 적용했다.
1. max-pooling layer를 없애고, strided convolution이나 fractional-srided convolution을 사용하여 feature-map의 크기를 조절한다.
2. Batch normalization을 적용한다.
3. Fully connected hidden layer를 제거한다.
4. Generator의 출력단의 활성함수로 tanh함수를 사용하고, 나머지 layer는 ReLU를 사용
5. Discriminator의 활성함수로 LeakyReLU를 사용
입력으로 사용하는 variable이 가로*세로 image 형태가 아니기 때문에 이것을 CNN망과 연동을 시키기 위한 image feature-map 형태로 변형시킬 수 있는 Project and reshape 블락이 필요하다.
이 출력은 convolution layer로 넘겨지는데 영상의 크기를 키워주는 fractionally-strided convolution이 필요한데 이것이 위에서 말한 transposed convolution으로 생각하면 된다.
Geneartor에 적용된 방법을 종합하면 위의 그림과 같다.
Discriminator의 구조를 살펴보자. 64*64크기의 컬러 영상을 입력 받아 진짜 혹은 가짜의 1차원 결과를 출력한다. 활성화 함수는 LeakyReLU를 사용한다.
Discriminator의 구조이다.
실험 결과는 생략하겠다. 몇 가지만 얘기하자면
위에서 말한 latent 변수 z 값을 조금씩 변화시키면 이미지가 조금씩 달라지는 것을 확인 할 수 있다.
이미지가 좋아진다고 한다.
DCGAN의 구조는 어떤 이론적인 추론을 통하여 얻어진 것은 아니지만 좋은 결과를 얻을 수 있었고 latent variable z의 의미도 파악 할 수 있었고 unsupervised learning을 통하여 영상이 갖고 있는 정보들에 대하여 잘 파악할 수 있었다.
코드 구현
DCGAN의 코드 구현 중 일부분만 간단하게 설명하겠다.
Youtube Idea Factory KAIST의 딥러닝 홀로서기를 참고했다.
transform = transforms.Compose([ transforms.Scale(64), transforms.ToTensor(), transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)) ]) train_dataset = dset.CIFAR10(root='./data/', train=True, download=True, transform=transform) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size= batch_size, shuffle=True)
입력을 64*64로 바꿔주는 과정이다. dataset은 CIFAR-10을 사용했다.
Generator
class Generator(nn.Module): def __init__(self, ngpu): super(Generator, self).__init__() self.ngpu = ngpu self.main = nn.Sequential( # 입력값은 Z이며 Transposed Convolution을 거칩니다. nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, bias=False), nn.BatchNorm2d(ngf * 8), nn.ReLU(True), # (ngf * 8) x 4 x 4 nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf*4), nn.ReLU(True), # (ngf * 4) x 8 x 8 nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf*2), nn.ReLU(True), # (ngf * 2) x 16 x 16 nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf), nn.ReLU(True), # ngf x 32 x 32 nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False), nn.Tanh() ) def forward(self, input): output = self.main(input) return output
위에서 설명한듯이 Batch를 추가하고 ReLU를 사용하며 마지막 아웃풋은 tanh 함수를 사용했다.
Discriminator
class Discriminator(nn.Module): def __init__(self,ngpu): super(Discriminator, self).__init__() self.ngpu = ngpu self.main = nn.Sequential( # (nc) x 64 x 64) nn.Conv2d(nc, ndf, 4,2,1,bias=False), nn.LeakyReLU(0.2, inplace=True), # ndf x 32 x 32 nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 2), nn.LeakyReLU(0.2, inplace=True), # (ndf * 2) x 16 x 16 nn.Conv2d(ndf*2, ndf*4, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 4), nn.LeakyReLU(0.2, inplace=True), # (ndf * 4) x 8 x 8 nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf*8), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(ndf*8, 1, 4, 1, 0, bias=False), nn.Sigmoid() ) def forward(self, input): output = self.main(input) return output.view(-1, 1).squeeze(1)
구별자 부분에서는 활성화함수를 논문에서 나온 LeakyReLU를 사용했다.
Optimizer로는 Adam을 사용했고 학습을 진행한다.
결과 그림을 보면
원본 이미지도 화질이 별로 좋지 않아 만족스러운 결과를 얻지 못했다.
더 좋은 가짜이미지를 만들기 위한 GAN을 공부하면 좋을 듯 싶다.
Rereference
- 라온피플(주) 머신러닝 아카데미
- Pytorch Tutorial