본문 바로가기

딥러닝/개인구현 정리

[DeepLearning] GAN을 활용한 새로운 산타클로스 얼굴 만들기 feat_Dropout 기법

320x100
지난 시간에 생성한 산타클로스 이미지의 GAN 생성 모델.
이번에는 성능 향상을 위해서 Dropout을 다시 조정해보았습니다

 

지난번 IS THAT SANTA? 를 활용한 모델에서는 이미지를 학습하고 생성자 VS 판별자를 갖고 있는

GAN을통해 epoch 100회, 200회, 추가적으로 150회 까지 진행했었습니다.

(실제로는 공개하지 않았지만 이 코드는 10회 → 20회 → 50회 → 90회 등 수많은 테스트가 존재했습니다)

 

추가적으로 Discriminator (판별자)에 여러가지 시도를 진행하던 중, 이런 의식이 생겼습니다.

 

다른 딥러닝 모델에서 사용한 Dropout을 더해서 실험해보면 어떨까? 

 

물론 이미 최적화를 진행해본 생성자, 판별자 구조에 dropout, early stop등 추가적인 기능을 넣는다고 해서

이전보다 100% 좋아질 것이라는 보장은 없습니다. 특별한 차이점이 있는지 보는 것을 주요 내용으로

삼고자 합니다.

 

일단 100회의 결과물부터 아래와 같이 공개합니다.

 

 

interval 종료시 이미지입니다.

 

 

 

지난 분석에서의 epoch 100회 결과에 비해 유의할 만한 차이가 보입니다.

 

Dropout 미진행시 100회  결과물

 

 

학습 참조

 

[DeepLearning] GAN을 활용한 새로운 산타클로스 얼굴 만들기 (tistory.com)

 

[DeepLearning] GAN을 활용한 새로운 산타클로스 얼굴 만들기

산타가 오는 겨울이 지나고, 많은 인파들 사이에 숨어버렸다고 합니다. 숨어있는 산타의 얼굴을 찾아 새로운 산타를 만들어 볼까요? 이번 신경망에서도 GAN(생성 적대적 신경망)을 사용할 예정입

astart.tistory.com

 

좋습니다. 그렇다면 Dropout은 무엇일까요.

 

안쓰는 방은 샷다를 내립니다, Dropout

 

 

신경망을 설계할 때, 일반적으로 layer도 만들고, 이미지의 경우 convolution도 만들고, 풀링도 쓰고,

가중치도 업데이트 하고, 로스 함수, 최적화 함수 등등 많은 기법들을 사용합니다.

 

좋은 모델이라고 판단할 수 있는 조건중의 하나는, 그것이 잘 학습되면서도 (fit) 단순해야 하는 조건을 가집니다

오버피팅이 되지 않으면서도, 의미있는 가중치를 업데이트 하는 이상적인 형태가 모두가 바라는

모델이라고 할 수 있겠습니다. 여기서 우리가 필요로 하는 것은 Regularization(정규화)입니다.

 

용어상으로는 dropout은 탈락, 생략 등의 의미를 가지는데요. 

Dropout은 정규화의 개념을 신경망 모델로 갖고와 비교적 덜 튀는 수치로 학습하기 위한 기법 이라고 할 수 있습니다.

 

 

 

위처럼 connected layer에서 0부터 1사이의 확률로 뉴런(neuron)을 꺼버리는 기법입니다.

드랍아웃 계층에서 일부 뉴런의 스위치는 일시적으로 꺼집니다.

즉, 해당 뉴런은 network에서 연결이 끊어지게 됩니다.

 

이러한 기법으로 신경망이 전체 test data를 기억하는 대신에 일반화 가능한 패턴을 학습하게 해서

간혹 특정 뉴런이 없는 경우에도 모델이 잘 기능하도록 만들어줍니다.

물론 몇몇 test의 경우 드랍아웃을 하기 전과 후가 아무런 차이가 없거나, 하지 않는 것이 더 효과적일 때도 있었습니다.

 

일반 convolution network와 dropout을 적용한 network를 비교한 사례

 

출처 : <Modeling Neural Variability in Deep Networks with Dropout>, biorxiv, 2021.08

 

기능과 패러미터에 대한 설명 참고

Dropout — PyTorch 1.13 documentation

 

Dropout — PyTorch 1.13 documentation

Shortcuts

pytorch.org

 

산타 이미지 데이터에서는 2D feature map을 사용하는 Dropout2d를 사용했습니다.

이번 모델에서 처음 사용하는 기법인데, 특장점은 Conv2d(컨볼루션 투 디멘션)에서 종종 사용되고

feature map의 독립성을 높이는 점이 있다고 합니다.

 

Dropout2d — PyTorch 1.13 documentation

 

Dropout2d — PyTorch 1.13 documentation

Shortcuts

pytorch.org


Discriminator 다시 수정

 

class Discriminator(nn.Module):
    def __init__(self, n_f, n_c, relu_slope=0.2):
        super().__init__()
        self.network = nn.Sequential(
            nn.Conv2d(in_channels=n_c, out_channels=n_f, kernel_size=4, stride=2, padding=1, bias=False),
            # 드랍아웃 추가
            nn.Dropout2d(0.25),
            nn.LeakyReLU(negative_slope=relu_slope,inplace=True),
            nn.Conv2d(in_channels=n_f, out_channels=n_f*2, kernel_size=4, stride=2, padding=1, bias=False),
            # 드랍아웃 추가
            nn.Dropout2d(0.25),
            nn.BatchNorm2d(n_f*2),
            nn.LeakyReLU(negative_slope=relu_slope,inplace=True),
            nn.Conv2d(in_channels=n_f*2, out_channels=n_f*4, kernel_size=4, stride=2, padding=1, bias=False),
            # 드랍아웃 추가
            nn.Dropout2d(0.25),
            nn.BatchNorm2d(n_f*4),
            nn.LeakyReLU(negative_slope=relu_slope,inplace=True),
            nn.Conv2d(in_channels=n_f*4, out_channels=n_f*8, kernel_size=4, stride=2, padding=1, bias=False),
            # 드랍아웃 추가
            nn.Dropout2d(0.25),
            nn.BatchNorm2d(n_f*8),
            nn.LeakyReLU(negative_slope=relu_slope,inplace=True),
            nn.Conv2d(in_channels=n_f*8, out_channels=1, kernel_size=4, stride=1, padding=0, bias=False),
            nn.Sigmoid()
            )
    def forward(self, x):
        return self.network(x)

 

자, 판별자를 이번에 dropout2d 4개를 추가하는 방향으로 수정해주었습니다.

생성자를 조정하는 대신, 판별자만 건드리는 이유는 지난 모델부터 판별자가 극도로 낮은 

loss 수치를 보였기 때문입니다. 생성자에 비해서 판별자가 지나치게 똑똑해지지 않기를 빌어봅니다.

 

from tqdm import notebook

# epoch
num_epochs = 100

# epoch 시작
# 시간 체크 yes
for epoch in notebook.tqdm(range(num_epochs)):
    for i, batch in enumerate(dataloader):
        
        # 식별자 
        dis.train()
        gen.train()
        X, _ = batch
        X = X.to(device)
        dis.zero_grad()
        
        label = torch.full((X.shape[0],),1.,dtype=torch.float,device=device)
        output = dis(X).view(-1)
        errD_real = criterion(output, label)
        errD_real.backward()
        D_x = output.mean().item()


        noise = torch.randn(batch_size, n_z, 1, 1, device=device)
        fake = gen(noise)
        label.fill_(0.)
        output = dis(fake.detach()).view(-1)
        errD_fake = criterion(output, label)
        errD_fake.backward()
        D_G_z1 = output.mean().item()
        errD = errD_real + errD_fake
        optimizer_d.step()

        # 판별자 업데이트
        gen.zero_grad()
        label.fill_(1)  
        output = dis(fake).view(-1)
        errG = criterion(output, label)
        errG.backward()
        D_G_z2 = output.mean().item()
        # 생성자 업데이트
        optimizer_g.step()

        if i % 10 == 0:
            print('[%d/%d][%d/%d]\tLoss_D: %.5f\tLoss_G: %.5f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
                  % (epoch, num_epochs, i, len(dataloader),
                     errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))

        g_loss.append(errG.item())
        d_loss.append(errD.item())

        dis.eval()
        gen.eval()

    with torch.no_grad():
        fake = gen(fixed_noise).detach().cpu()
    img_list.append(torchvision.utils.make_grid(fake, padding=2, normalize=True))

 

판별자가 loss의 하강폭이 낮으면 생성자는 이전보다 비교적 이미지를 well 학습할 것입니다.

그 이외에는 최대한 원본을 유지했습니다.

 

그래프 loss에 대한 시각화

 

100회 까지의 진행시 loss 입니다.

 

아래는 서두에도 언급했었던 이미지 생성자의 결과물입니다.

 

이미지 생성

 

 

epoch를 계속 진행한다면 어떻게 변화하는지 확인해보았습니다.

 

epoch 1.5배 증량하여 150회 시작

 

 

놀랍게도 생성자가 갑자기 정신을 잘 못차리는 것을 볼수 있었습니다.

200회 까지 진행하고 Dropout 계수를 변경해야할 필요가 있는지 진단하기로 했습니다.

현재 dropout2d 계수는 nn.Dropout2d(0.25) 입니다.

 

150회 이미지 생성물

 

 

다만 150회 진행시 이전 생성 이미지와의 차이점은 포즈가 다양해진 점입니다.

산타가 날라차기를 하는 것처럼 보이는 이미지도 생성되었네요.

 

 

728x90