Joonas' Note
[딥러닝 일지] Auto Encoder (with MNIST) 본문
이전 글 - [딥러닝 일지] MNIST Competition
생성 모델
이번에는 MNIST 데이터셋으로 0~9 사이의 숫자를 주면 28x28 크기의 숫자 이미지를 만들어내는 생성 모델을 연습했다.
그 중에서도, 가장 기초적인 형태의 오토 인코더(Auto Encoder) 모델이다.
입력 이미지를 잠재 공간(Latent space)의 어떤 형태로 만드는 Encoder 부분과, 잠재 공간의 값을 다시 재구성하는 Decoder 부분으로 이루어진다.
여기서 잠재 공간의 차원은 2개, 10개 등 상관없고 당연하겠지만 고차원일수록 많은 표현들을 내포할 수 있으므로 좋다.
레이어를 분리해서 학습을 진행하는 경우도 있고 하나로 합쳐서 학습해도 되는데, 중간값을 확인할 수 있도록 분리해서 진행했다.
encoded = Encoder(inputs)
decoded = Decoder(encoded)
모델
모델은 여러가지 형태로 구현해봤는데, 이 블로그에 있는 이미지들을 만든 모델 구조는 아래와 같다.
__________________________________________________________________________________________
Layer Type Output Shape Param #
==========================================================================================
autoencoder AutoEncoder (-1, 1, 28, 28) 0
├─encoder Sequential (-1, 2) 0
| └─0 Conv2d (-1, 32, 28, 28) 320
| └─1 BatchNorm2d (-1, 32, 28, 28) 129
| └─2 LeakyReLU (-1, 32, 28, 28) 0
| └─3 Conv2d (-1, 64, 14, 14) 18,496
| └─4 BatchNorm2d (-1, 64, 14, 14) 257
| └─5 LeakyReLU (-1, 64, 14, 14) 0
| └─6 Conv2d (-1, 64, 7, 7) 36,928
| └─7 BatchNorm2d (-1, 64, 7, 7) 257
| └─8 LeakyReLU (-1, 64, 7, 7) 0
| └─9 Flatten (-1, 3136) 0
| └─10 Linear (-1, 2) 6,274
| └─11 LeakyReLU (-1, 2) 0
├─decoder Sequential (-1, 1, 28, 28) 0
| └─0 Linear (-1, 3136) 9,408
| └─1 Unflatten (-1, 64, 7, 7) 0
| └─2 LeakyReLU (-1, 64, 7, 7) 0
| └─3 ConvTranspose2d (-1, 64, 14, 14) 36,928
| └─4 BatchNorm2d (-1, 64, 14, 14) 257
| └─5 LeakyReLU (-1, 64, 14, 14) 0
| └─6 ConvTranspose2d (-1, 32, 28, 28) 18,464
| └─7 BatchNorm2d (-1, 32, 28, 28) 129
| └─8 LeakyReLU (-1, 32, 28, 28) 0
| └─9 ConvTranspose2d (-1, 1, 28, 28) 289
| └─10 Tanh (-1, 1, 28, 28) 0
==========================================================================================
마지막에 활성화 함수로 Tanh 가 있는데, 이건 없어도 상관없다.
초반에는 Batch Nomalization도 없이 아주 간단하게 아래와 같이 만들어서 사용했다.
class AutoEncoder(nn.Module):
def __init__(self, z_dim=2):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv2d(1, 32, 3, padding=1),
nn.LeakyReLU(),
nn.Conv2d(32, 64, 3, padding=1, stride=2),
nn.LeakyReLU(),
nn.Conv2d(64, 64, 3, padding=1, stride=2),
nn.LeakyReLU(),
nn.Flatten(),
nn.Linear(64 * 7 * 7, z_dim),
nn.LeakyReLU(),
)
self.decoder = nn.Sequential(
nn.Linear(z_dim, 64 * 7 * 7),
nn.Unflatten(1, (64, 7, 7)),
nn.LeakyReLU(),
nn.ConvTranspose2d(64, 64, 3, padding=1, output_padding=1, stride=2),
nn.LeakyReLU(),
nn.ConvTranspose2d(64, 32, 3, padding=1, output_padding=1, stride=2),
nn.LeakyReLU(),
nn.ConvTranspose2d(32, 1, 3, padding=1),
)
def forward(self, x):
x = self.encoder(x)
x = self.decoder(x)
return x
잠재 공간
각 weight들이 어떤 feature를 의미하고 있을 지는 알기 어렵다. 하지만 MNIST는 매우 간단한 형태이므로 2차원의 잠재 공간으로 학습한 후에, 어떤 값이 들어있는 지 2차원 좌표 평면으로 확인할 수 있다 (라고 생각했다)
2차원
학습은 생각보다 잘 되는데, 2차원 잠재 공간만으로 학습하기에는 최적화 함수와 스케줄러도 여러가지 시도해보다가 SGD + CyclicLR 이 가장 잘 나와서 그대로 몇번 더 진행해봤다.
(스케줄러 비교하기 좋은 글: https://sanghyu.tistory.com/113)
하지만 책이나 인터넷에서 본대로 예쁘게 클러스터링 되지는 않는다.
확률적으로 아래와 같은 학습 결과도 만날 수 있었다.
이 상태에서 특정 영역에 있는 값들을 Decoder에 넣어서 이미지를 뽑아보았다.
여기서는 2차원 좌표 선상의 (x ,y)가 입력이 되겠다.
먼저 망한 모델부터 보자.
0과 1 외에는 착시현상이 생길것만 같은 결과들만 잔뜩 나왔다.
왼쪽은 2차원 잠재 공간상의 레이블을, 오른쪽은 검은색 테두리 안의 포인트들로부터 생성한 이미지 결과이다.
클러스터가 굉장히 겹쳐있어서 결과도 모호하게 나오지만, 이런 식으로 결과가 나오는구나 알 수 있다.
조금 더 촘촘하게 확인해보면, 여러 숫자들의 포인트가 섞여있는 부분에서는 굉장히 난해한 이미지가 생성된 것을 확인할 수 있다.
아래 링크한 캐글 노트북에서 Version 8 이후로 확인해보면 여러 케이스를 확인할 수 있다.
위에서 클러스터링이 잘 된 케이스의 결과는 이러하다.
(실험) 지도 학습
2차원 잠재공간에서 인터넷처럼 아름답게 나누어진 결과를 얻기 위해 학습에 직접 개입했다.
모든 숫자들의 위치(잠재 공간의 값)를 직접 지정해주고 학습을 진행해보았다.
latent_map = {
0: (-2, 6),
1: (-2, -6),
2: (-6, 2),
3: (-3, 4),
4: (6, 2),
5: (2, 4),
6: (0, 2),
7: (0, -2),
8: (-2, 0),
9: (2, -2),
}
각 숫자의 포인트를 정한 근거는 매우 주관적이다.
생각대로 잘 나온다. 숫자 9로 보이는 빨간색 레이블들이 제대로 위치를 못 잡았지만 이정도면 만족스러운 결과이다.
10차원
학습이 잘 되는 것 같아 보이기는 한데, (5 vs 6), (3 vs 8), (4 vs 9) 에서 매우 힘겨워한다.
flat하게 펴진 상태에서 특징을 추려내기에는 어떤 난관이 존재해보인다.
Conv2D 유지
잠재 공간을 (2, 7, 7) 크기로 유지했더니 학습도 엄청나게 빠르고 정확도도 높다.
마치며
ReLU보다 LeakyReLU의 결과가 더 나았다. 잠재 벡터가 몇 안되기 때문에 하나라도 0에 가까워지면 그대로 학습이 멈춰버린다. 음수여도 의미있는 weight, bias 일 수 있기 때문이라고 생각한다.
LR Scheduler에 따라서 학습 진행 양상이 매우 달라지는 것을 느꼈다. global minimum까지 가는 길에 그만큼 많은 local minima들이 존재했다는 의미가 아닐까.
노트북
자세한 결과는 아래의 캐글 노트북에서 확인할 수 있다.
전체 스크립트는 kaggle에 업로드해서 실행해봤는데, 만들어지는 GIF를 실시간으로 출력하는 display 때문에 저장이 안되어서 수정했다.
'AI > 딥러닝' 카테고리의 다른 글
[딥러닝 일지] PyTorch로 DCGAN 훈련해보기 (4) | 2022.06.08 |
---|---|
[딥러닝 일지] VAE; Variational Auto Encoder (0) | 2022.06.05 |
[PyTorch] RuntimeError: DataLoader worker is killed by signal: Bus error. (0) | 2022.05.30 |
[PyTorch] AssertionError: Torch not compiled with CUDA enabled (0) | 2022.05.30 |
[PyTorch] GPU 메모리가 부족할 때 확인할 내용들 (0) | 2022.04.25 |