우왕! 이번엔 새로운 분야
이미지 생성이다! 하지만 난 그닥 흥미가 가지 않는다.
정말 재밌고 놀라운 걸 할 수 있는 분야인 건 확실한데........약간 그거다 불쾌한 골짜기..무서워.......
그리고 사실 정답이 있다는 측면에서 이런 이과쪽 공부를 즐겨했는데 음 이젠 정답을 측정할 수 없는 분야를 한다는 것도 뭔가 흥미가 뚝 떨어진달까 몰라 뭔 미래에는 내가 GAN으로 먹고 살지도... 아닐 수도 있지만! 하하
이번엔 설명이 안 꼼꼼할 수도 있다 대신에 이번엔 코드 리뷰가 들어있찌!
퇴사 이후 오랜만에 만나는 토치...............
깃헙에서 찾은 심플 갠~
Generative Adversarial Nets
초록 Abstract
기존에 있었던 생성 분야의 연구와 달리, 본 논문에서는 적대적 학습으로 이 생성 문제에 접근한다. 생성 모델인 G와 분류 모델인 D 가 하나의 cost 함수를 각각 최대화/최소화 함으로써 생성 문제를 해결한다. 즉 minimax two-player 게임인 것이다. 그렇다면 이는 결국 어떻게 수렴하는가? 유일한 해가 존재하는데, G는 훈련 데이터의 분포를 흉내내고 D는 원본 및 가짜 데이터를 구분하지 못하고 1/2의 값을 토해내는 것으로 수렴된다.
저번 주에 리뷰했던 Intriguing properties of neural network에 소개되었던 adversarial samples는 이후 적대적 학습의 시발점이 된 것 같다. (논문을 검색하니까 적대적 학습밖에 안 뜨더라고) 그리고 이 갠은 그 학습을 이용한 친구인 것 같다 는게 내 생각. 참 이번엔 논문들이 잘 걸린 것 같다. 어떤 모델 중 하나가 백본을 구글 넷으로 쓰고 또 지금 하는 GAN은 적대적 학습을 쓰니.. 허허 빅피쳐가 참 좋다
[알고리즘 구조의 시각화]
이 알고리즘은 크게 두 가지의 모델이 있는데, 본 논문에서 이용하는 비유로 설명하겠다.
가짜 화폐 만드는 범죄자(:Generator) + 위조 화폐를 판별하는 경찰(:Discriminator)
그럼 이 Generator는 경찰 눈을 속이기 위해서 점점 고도의 기술을 쌓게 될 것이다.
한편 Discriminator는 갈수록 기술이 발전하는 범죄자의 가짜 화폐를 잘 찾아내기 위한 그런 능력을 쌓게 될 것이다.
이렇게 둘이 끊임없이 경쟁하다 보면 G를 통해서 우리는 D의 눈을 속이는 완벽한 가짜 화폐를 만들어 낸다는 것이다!
어째서 수렴하는가, 무엇으로 수렴하는가 에 대해서는 수식으로 추후에 설명한다.
결국 우리가 원하는 건 G이다. D는 G의 성능을 높이기 위해서 도와주는 느낌!
그렇다면 이제 비유는 그만하고 원래대로 설명을 해보자.
G 즉 생성모델은 랜덤 노이즈를 받아들여서 다층 퍼셉트론을 통과해 원본 데이터 분포를 흉내내는 샘플을 만들어낸다.
D 즉 분류모델은 G가 생성한 이미지와 원본 이미지 둘 다를 받아들여서 다층 퍼셉트론을 통과해 진짜일 확률값 single vector를 내보낸다.
즉 다층퍼셉트론을 이용해서 G와 D를 만들어낸 것이다!
간단한 gan 튜토리얼이라 아래처럼 간단하게 생성한다!
# Discriminator
D = nn.Sequential(
nn.Linear(image_size, hidden_size),
nn.LeakyReLU(0.2),
nn.Linear(hidden_size, hidden_size),
nn.LeakyReLU(0.2),
nn.Linear(hidden_size, 1),
nn.Sigmoid())
# Generator
G = nn.Sequential(
nn.Linear(latent_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, image_size),
nn.Tanh())
[+ 확률분포를 흉내낸다?]
이 이미지가 이해가 쉽더라!
그냥 평범한 이미지를 흉내낸다~ 의 느낌이다. (본 포스팅은 쉽게쉽게가 목표다)
[모델을 간결하게 설명하는 하나의 equation]
앞에서 말했듯이 G는 가짜 화폐를 잘 만들도록, D는 가짜 화폐를 잘 찾아내도록 훈련한다.
그렇다고 해서 이 둘이 분리된 코스트 함수를 가지진 않고 동일한 코스트 함수를 공유한다.
다만 목적이 다르다.
위 Equation1을 최대화하는 건 G, 최소화하는 건 D의 목표이다.
즉 위 Equation 1은 discriminator가 얼마나 성능이 좋은가를 측정하는 함수 V(G,D)이다.
먼저 notation을 간단하게 보자
x 는 $p_data (x)$의 데이터 분포에서 나온 샘플
z는 $p_z (z)$의 데이터 분포에서 나온 샘플
$p_data (x)$는 원본 데이터의 분포
$p_z (z)$는 노이즈 데이터의 분포다.
이걸 $p_z (z)$ 가지고 $p_g$를 만든다.
하여튼 랜덤한 벡터를 가지고 우리가 원하는 분포를 만드는 거니까!
다시 저 수식으로 들어가자.
만약 G가 진짜 같다? 그럼 Equation1을 통해 계산된 discriminator는 무한대 값을 가지게 될 것이다.
뒤의 텀 $log(1-D(G(z))$ 에서 $G(z)$ 값이 x값이 될 것이기 때문이다. $G(z)$값이 x가 된다면 D(x)는 1을 뱉을 것이고, 결과적으로 저 텀이 무한대가 된다.
즉 G의 목적은 저 Equation을 최대화하는 것이 목표다.
한편 D의 성능이 좋다? 그럼 Equation1을 통해서 계산된 discriminator는 0값을 가지게 될 것이다.
[$p_g$가 $p_data (x)$를 닮아가는 과정 시각화]
위 그림에서 z가 랜덤 노이즈(uniform하게 샘플했다고 한다)
파랑색 점선이 D
초록색 실선이 G를 통해 생성한 데이터 분포
검은색 점들이 원본 데이터 분포
처음에는 D의 성능을 고도화시킨다.
그 후에 G를 통해서 generator의 성능을 높인다.
이후엔 다시 D의 성능을 고도화시킨다.
그리고 다시 G
결과적으로 그림 (d)를 보면 G와 원본 데이터 분포가 유사해짐을 확인할 수 있다.
이 때 기억할 것은 D를 여러번 학습한 후 한 번 G를 학습하는 것이 한 세트라는 점이다.
+ 위 Equation대로 하는 것은 학습이 잘 안된다고 한다. 그래서 대신 log(1-D(G(z))를 최소화하는 대신에, log(D(G(z)) 를 최대화하는 것이 좀 더 훈련에 도움이 된다고 한다.
알고리즘으로 보면 이렇게 정리된다.
[이걸 코드로 보자!]
d_optimizer = torch.optim.Adam(D.parameters(), lr=0.0002)
g_optimizer = torch.optim.Adam(G.parameters(), lr=0.0002)
loss function은 뒤에서 생성
for epoch in range(num_epochs):
for i, (images, _) in enumerate(data_loader):
images = images.reshape(batch_size, -1).to(device)
# Create the labels which are later used as input for the BCE loss
real_labels = torch.ones(batch_size, 1).to(device)
fake_labels = torch.zeros(batch_size, 1).to(device)
# ================================================================== #
# Train the discriminator #
# ================================================================== #
# Second term of the loss is always zero since real_labels == 1
outputs = D(images)
d_loss_real = -1 * torch.log(outputs)
# First term of the loss is always zero since fake_labels == 0
z = torch.randn(batch_size, latent_size).to(device)
fake_images = G(z)
outputs = D(fake_images)
d_loss_fake = loss_fake = -1 * torch.log(1.-outputs)
# Backprop and optimize
d_loss = d_loss_real + d_loss_fake
reset_grad()
d_loss.backward()
d_optimizer.step()
# ================================================================== #
# Train the generator #
# ================================================================== #
# Compute loss with fake images
z = torch.randn(batch_size, latent_size).to(device)
fake_images = G(z)
outputs = D(fake_images)
# We train G to maximize log(D(G(z)) instead of minimizing log(1-D(G(z)))
# For the reason, see the last paragraph of section 3. https://arxiv.org/pdf/1406.2661.pdf
g_loss = -1 * torch.log(1.-outputs)
# Backprop and optimize
reset_grad()
g_loss.backward()
g_optimizer.step()
if (i+1) % 200 == 0:
print('Epoch [{}/{}], Step [{}/{}], d_loss: {:.4f}, g_loss: {:.4f}, D(x): {:.2f}, D(G(z)): {:.2f}'
.format(epoch, num_epochs, i+1, total_step, d_loss.item(), g_loss.item(),
real_score.mean().item(), fake_score.mean().item()))
# Save real images
if (epoch+1) == 1:
images = images.reshape(images.size(0), 1, 28, 28)
save_image(denorm(images), os.path.join(sample_dir, 'real_images.png'))
# Save sampled images
fake_images = fake_images.reshape(fake_images.size(0), 1, 28, 28)
save_image(denorm(fake_images), os.path.join(sample_dir, 'fake_images-{}.png'.format(epoch+1)))
[수식을 통한 증명]
1. $p_g = p_data$ 지점에서 최대값을 가진다. : Global Optimality of $p_g = p_data$
2. Equation1을 풀면 그 수렴하는 최소값을 찾을 수 있다.
사실 2는 이해를 그닥 못했다. 줄줄 글로만 설명해놨다..............읭 스럽다..
- Global Optimality of $p_g = p_data$
먼저 이상적인 D를 먼저 아래와 같이 정의한다.
증명
위와 같이 V는 정의할 수 있음. 원래 minimax 수식에서 Expectation을 적분으로 풀어서 쓴 것
<= 밀도함수니까 expectation 계산이 인테그랄 확률 * 확률변수 이케 됨
그럼 이제 최적의 D는 위의 식의 최대값이다. 최대값을 어떻게 찾는가?
내가 이해한 대로 수식을 풀어버리면
이렇게 최적의 D를 정의하면 이제 최적의 G를 정의해보자.
최적의 G는 앞서 봤던 V(G,D)의 식을 최소화하는 값이다.
위에서 증명한 최적의 D를 이용해서 V(G,D)를 다시 정의하면 아래와 같다.
이때 보면 C 로 바뀐 걸 알 수 있는데 왜냐하면 이미 D는 최적의 D로 결정났기 때문이다. 그래서 D의 값이 cost 변화에 영향을 주지 않는다.
고러면은 위 식을 최소화하는 지점을 찾으면 된다.
그 지점은 $p_{data}= p_{g}$인 지점이다!
정의를 생각해보면 쉽다. 그러니까 저 C는 이제 G가 얼마나 D를 잘 속이냐 인데
당연히 내가 만든 가짜 분포가 진짜 분포와 같다면 최소가 될 것이다.
그리고 그때 위 식의 최소값은 $-log4$가 된다.
위의 식에다가 $p_{data} = p_{g}$를 반영해서 쓰면 아래와 같이 대괄호 안의 값이 각각 1/2로 바뀌게 된다.
그리고 그걸 정리하면 아래처럼 됨!
+ 이 식은 왜 나오는지 잘 모르겠지만
저기서 나오는 JSD는 서로 다른 두 분포가 있을 때 얼마나 다른지를 측정하는 함수다.
나도 옛날에 얼핏 공부했었는데 기억나다니! 나 자신에게 놀랐다.
하여튼 뭐 그래서 보면 JSD는 항상 양수이거나 오직 두 분포가 일치할 때만 0의 값을 갖는 애다.
그러니까 당근 C의 최저점은 -log(4)가 되는 것이다.
그래서 이 section을 통해서 증명하는 것은
최적의 D를 먼저 정의한 후에 최적의 G를 찾고, minimax game의 척도인 V(G,D)가 어떤 값으로 언제 수렴하는가 이다.
결론적으로 특정 값으로 수렴하는 잘 작동하는 함수다~ 라고 얘기하고 싶었다.
- Convergence of Algorithm1
그렇다면 왜 알고리즘 1 을 적용했을 때 이게 수렴하는가? 에 대한 설명이다 .
이건 이해 못함...ㅈㅅ
[실제 결과]
짠!
[feature 출처]
[1]
[2] https://learnai.tistory.com/4
[reference 출처]
[0] https://arxiv.org/pdf/1406.2661.pdf
[1] 내 머리
[나가는 말]
어쩌면 앞으로 이 카테고리는 활성화 안 될지도.....전 개강에 접어듭니다.........
'딥러닝 > CV 논문' 카테고리의 다른 글
[SuperResolution] EDRS (0) | 2021.09.29 |
---|---|
[Classification]FuCiTNeT (0) | 2021.09.16 |
[Visualize&Understand DNN] Intriguing properties of neural network (0) | 2021.08.19 |
[Instance Segmentation] SOLO (0) | 2021.08.12 |
[Object Detection] YOLO (0) | 2021.08.03 |
댓글