공식 튜토리얼 DCGAN CelebA 학습

안녕하세요! 현재 파이토치 공식 튜토리얼에서 제공하는 CelebA 이미지 데이터를 활용해서 DCGAN 모델을 학습하고 있는데요! 튜토리얼 코드와 거의 동일하게 아래처럼 작성하였는데, 이상하게 학습이 잘 안됩니다 ㅜ 모델 구조와 학습률 같은 파라미터들도 모두 동일하게 하였는데, 원인이 무엇인지 모르겠네요.. Loss 출력 값도 튜토리얼에서 나온 출력값과 매우 다르게 나오고.. 판별자 성능이 매우 안좋아짐에 따라 생성자 Loss 값도 0으로 수렴되어 좋아지는 것처럼 보이지만 실제 학습 중간에 생성된 이미지 샘플을 출력해보면 그냥 여전히 노이즈 이미지입니다.

어떤 부분이 문제가 되는 걸까요? 데이터 로드, 모델 구조, 학습 코드 전문을 첨가합니다! 고수 분들의 피드백 부탁드립니다!

import torch
import torch.nn as nn
from torch.optim import Adam
from torchvision import datasets
from torch.utils.data import DataLoader
import torchvision.transforms as transforms 
import torchvision.utils as vutils

nz = 100
ngf = 64
ndf = 64
nc = 3
lr = 0.0002
beta1 = 0.5
image_size = 64
BATCH_SIZE = 128

transforms = transforms.Compose([
    transforms.Resize(image_size),
    transforms.CenterCrop(image_size),
    transforms.ToTensor(),
    transforms.Normalize(mean=(0.5,0.5,0.5), # R,G,B 
                         std=(0.5,0.5,0.5))  # R,G,B. channel별 (x-mean)/std
])

dataset = datasets.CelebA(root="dataset", split="train", transform=transforms, download=True)
dataloader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=False)

def weights_init(m):
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        nn.init.normal_(m.weight.data, 1.0, 0.02)
        nn.init.constant_(m.bias.data, 0)

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
            nn.ConvTranspose2d(in_channels=nz,
                               out_channels=ngf*8,
                               kernel_size=4,
                               stride=1,
                               padding=0,
                               bias=False),
            nn.BatchNorm2d(num_features=ngf*8),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(in_channels=ngf*8,
                               out_channels=ngf*4,
                               kernel_size=4,
                               stride=2,
                               padding=1,
                               bias=False),
            nn.BatchNorm2d(num_features=ngf*4),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(in_channels=ngf*4,
                               out_channels=ngf*2,
                               kernel_size=4,
                               stride=2,
                               padding=1,
                               bias=False),
            nn.BatchNorm2d(num_features=ngf*2),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(in_channels=ngf*2,
                               out_channels=ngf,
                               kernel_size=4,
                               stride=2,
                               padding=1,
                               bias=False),
            nn.BatchNorm2d(num_features=ngf),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(in_channels=ngf,
                               out_channels=nc,
                               kernel_size=4,
                               stride=2,
                               padding=1,
                               bias=False),
            nn.Tanh()
        )

    def forward(self, x):
        x = self.main(x)
        return x


class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.main = nn.Sequential(
            nn.Conv2d(in_channels=nc,
                      out_channels=ndf,
                      kernel_size=4,
                      stride=2,
                      padding=1,
                      bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(in_channels=ndf,
                      out_channels=ndf*2,
                      kernel_size=4,
                      stride=2,
                      padding=1,
                      bias=False),
            nn.BatchNorm2d(num_features=ndf*2),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(in_channels=ndf*2,
                      out_channels=ndf*4,
                      kernel_size=4,
                      stride=2,
                      padding=1,
                      bias=False),
            nn.BatchNorm2d(num_features=ndf*4),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(in_channels=ndf*4,
                      out_channels=ndf*8,
                      kernel_size=4,
                      stride=2,
                      padding=1,
                      bias=False),
            nn.BatchNorm2d(num_features=ndf*8),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(in_channels=ndf*8,
                      out_channels=1,
                      kernel_size=4,
                      stride=2,
                      padding=0,
                      bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        x = self.main(x)
        return x

# model
generator = Generator()
discriminator = Discriminator()

# init params in model
generator.apply(weights_init)
discriminator.apply(weights_init)

# loss
criterion = nn.BCELoss()

fixed_noise = torch.rand(64, nz, 1, 1)

real_label = 1
fake_label = 0

# optimizer
optimizerD = Adam(generator.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = Adam(discriminator.parameters(), lr=lr, betas=(beta1, 0.999))

# Train
img_list = []
g_losses = []
d_losses = []
iters = 0
num_epochs = 5

for epoch in range(num_epochs):
    for i, (x, y) in enumerate(dataloader, 0):
        # train Discriminator
        # 1. train based on real-image
        optimizerD.zero_grad()
        real_label = torch.ones(len(x))
        real_pred = discriminator(x).view(-1)
        # 2. train based on fake-image
        fake_label = torch.zeros(len(x))
        noise = torch.randn(len(x), nz, 1, 1)
        fake_x = generator(noise)
        fake_pred = discriminator(fake_x.detach()).view(-1)

        fake_loss = criterion(fake_pred, fake_label)
        real_loss = criterion(real_pred, real_label)
        d_loss = fake_loss + real_loss

        fake_loss.backward()
        real_loss.backward()
        optimizerD.step()

        # train Generator
        optimizerG.zero_grad()
        fake_pred = discriminator(fake_x).view(-1)
        g_loss = criterion(fake_pred, real_label)
        g_loss.backward()
        optimizerG.step()

        if i % 50 == 0:
            print("[%d|%d] [%d|%d]\tLoss D: %.4f\tLoss G: %.4f" % (epoch+1, num_epochs, i, len(dataloader), d_loss.item(), g_loss.item()))

        d_losses.append(fake_loss.item() + real_loss.item())
        g_losses.append(g_loss.item())

        if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(dataloader)-1)):
            with torch.no_grad():
                fake = generator(fixed_noise).detach()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
        iters += 1

위 코드를 돌리면 아래처럼 Loss가 출력됩니다.

[1|5] [0|1272]	Loss D: 1.8754	Loss G: 0.6190
[1|5] [50|1272]	Loss D: 28.6092	Loss G: 0.0000
[1|5] [100|1272]	Loss D: 29.8591	Loss G: 0.0000
[1|5] [150|1272]	Loss D: 27.4660	Loss G: 0.0000
[1|5] [200|1272]	Loss D: 29.3481	Loss G: 0.0000
[1|5] [250|1272]	Loss D: 26.0290	Loss G: 0.0000
[1|5] [300|1272]	Loss D: 26.7343	Loss G: 0.0000
[1|5] [350|1272]	Loss D: 26.7163	Loss G: 0.0000
[1|5] [400|1272]	Loss D: 29.5696	Loss G: 0.0000
[1|5] [450|1272]	Loss D: 30.0844	Loss G: 0.0000
[1|5] [500|1272]	Loss D: 31.5356	Loss G: 0.0000
[1|5] [550|1272]	Loss D: 30.8461	Loss G: 0.0000
[1|5] [600|1272]	Loss D: 30.7053	Loss G: 0.0000
[1|5] [650|1272]	Loss D: 32.1853	Loss G: 0.0000
[1|5] [700|1272]	Loss D: 30.2996	Loss G: 0.0000
[1|5] [750|1272]	Loss D: 36.8295	Loss G: 0.0000
[1|5] [800|1272]	Loss D: 29.6888	Loss G: 0.0000
[1|5] [850|1272]	Loss D: 37.4410	Loss G: 0.0000
[1|5] [900|1272]	Loss D: 34.1657	Loss G: 0.0000
[1|5] [950|1272]	Loss D: 39.4706	Loss G: 0.0000
[1|5] [1000|1272]	Loss D: 32.2947	Loss G: 0.0000
  • 아래 답변은 OpenAI의 GPT-3.5(ChatGPT) 모델로 자동 생성한 것으로, 정확하지 않을 수 있습니다.
  • 잘못된 답변을 보셨다면 댓글로 첨삭 지도 부탁드립니다. :sweat_smile:

안녕하세요! DCGAN 모델을 사용하여 CelebA 데이터셋을 학습하려고 하시는군요. 코드를 보니 튜토리얼 코드와 거의 동일하게 작성하셨다고 하셨는데, 학습이 잘 되지 않고 Loss 값도 이상하게 나온다고 하셨네요. 또한 생성된 이미지 샘플이 여전히 노이즈로 보인다고 하셨습니다.

가장 먼저 확인해야 할 부분은 데이터 로드와 전처리입니다. 코드를 보니 transforms 변수에 이미지 전처리 과정을 정의하고, datasets.CelebA를 사용하여 데이터셋을 로드하고 있습니다. 이 부분은 문제가 없어 보입니다.

다음으로 확인해야 할 부분은 모델 구조입니다. 코드를 보면 생성자와 판별자의 구조를 정의하고 있습니다. 이 부분도 문제가 없어 보입니다.

마지막으로 확인해야 할 부분은 학습 코드입니다. 코드를 보면 Discriminator와 Generator를 번갈아가며 학습하고 있습니다. 그리고 각각의 Loss 값을 계산하여 출력하고 있습니다. 이 부분에서 문제가 발생할 수 있습니다.

가장 먼저 확인해야 할 부분은 Loss 값입니다. 코드를 보면 criterion으로 BCELoss를 사용하고 있습니다. BCELoss는 이진 분류 문제에 사용되는 손실 함수입니다. 하지만 DCGAN은 생성자와 판별자를 번갈아가며 학습하는 GAN 모델이므로, 생성자와 판별자의 Loss 값을 따로 계산해야 합니다. 생성자의 Loss 값은 판별자가 생성한 가짜 이미지를 얼마나 잘 구분하는지를 나타내는 값이고, 판별자의 Loss 값은 실제 이미지와 가짜 이미지를 얼마나 잘 구분하는지를 나타내는 값입니다.

따라서, 학습 코드에서 생성자와 판별자의 Loss 값을 따로 계산하고 출력해야 합니다. 예를 들어, 생성자의 Loss 값을 계산하는 부분을 다음과 같이 수정할 수 있습니다.

# train Generator
optimizerG.zero_grad()
fake_pred = discriminator(fake_x).view(-1)
g_loss = criterion(fake_pred, real_label)
g_loss.backward()
optimizerG.step()

if i % 50 == 0:
    print("[%d|%d] [%d|%d]\tLoss D: %.4f\tLoss G: %.4f" % (epoch+1, num_epochs, i, len(dataloader), d_loss.item(), g_loss.item()))

위 코드에서 g_loss는 생성자의 Loss 값을 나타내며, fake_pred는 판별자가 가짜 이미지를 얼마나 잘 구분하는지를 나타내는 값입니다.

또한, 생성자와 판별자의 Loss 값을 따로 저장하고 있어야 합니다. 현재 코드에서는 d_lossesg_losses 리스트에 Loss 값을 추가하고 있습니다. 이 부분은 문제가 없어 보입니다.

마지막으로, 생성된 이미지 샘플을 출력하는 부분도 확인해야 합니다. 현재 코드에서는 img_list에 생성된 이미지를 저장하고 있습니다. 이 부분도 문제가 없어 보입니다.

위의 수정 사항을 적용하고 코드를 다시 실행해보세요. 그리고 학습이 잘 되는지, Loss 값이 정상적으로 출력되는지, 생성된 이미지 샘플이 어떻게 나오는지 확인해보세요. 문제가 계속되면 추가적인 정보를 제공해주세요. 도움이 될 수 있는 자료를 찾아보기 위해 아래 링크를 참고하세요.


  • 답변이 어떠셨나요? 괜찮았다고 생각하신다면 아래 :heart:를 한 번 눌러주시길 부탁드려요!
  • 아래 답변은 Anthropic의 Claude-2 모델로 자동 생성한 것으로, 정확하지 않을 수 있습니다.
  • 잘못된 답변을 보셨다면 댓글로 첨삭 지도 부탁드립니다. :sweat_smile:

안녕하세요. DCGAN 모델 학습에 문제가 있다고 하셨는데, 코드와 설명을 잘 읽어보았습니다.

먼저 Loss 출력 값이 이상하다고 하셨는데, Discriminator Loss 값이 너무 높고 Generator Loss 값이 0에 수렴하는 것을 보면 Discriminator가 Generator보다 훨씬 성능이 좋아지고 있음을 알 수 있습니다.

제 생각에는 학습 초기에 Discriminator와 Generator의 성능 차이가 너무 크기 때문에 이런 문제가 발생한 것 같습니다.

이를 해결하기 위해서는 아래와 같이 할 수 있습니다:

  1. Generator와 Discriminator의 모델 크기를 조정해 성능 차이를 줄입니다.

  2. Generator的参数를 미리 학습시켜 성능을 높입니다.

  3. Optimizer의 학습률을 조정해 Generator와 Discriminator의 학습 속도를 맞춥니다.

  4. 학습 초기에는 Discriminator의 학습 횟수를 제한합니다.

  5. Batch Normalization 사용 여부를 확인합니다.

코드 상으로는 큰 문제는 없어 보이는데요. 위 내용을 참고해 모델 성능을 점진적으로 맞춰가며 학습시키는 것이 좋겠습니다.

학습 중간 결과로 생성된 이미지가 제대로 생성되지 않는다면, 좀 더 오랜 학습이 필요한 것이므로 인내심을 가지고 학습을 계속하는 것도 중요합니다.

추가로 궁금한 점이 있으시면 주저 말고 질문 주세요. 최선을 다해 도와드리겠습니다. 학습 화이팅!


  • 답변이 어떠셨나요? 괜찮았다고 생각하신다면 아래 :heart:를 한 번 눌러주시길 부탁드려요!
  • 아래 답변은 OpenAI의 GPT-4 모델로 자동 생성한 것으로, 정확하지 않을 수 있습니다.
  • 잘못된 답변을 보셨다면 댓글로 첨삭 지도 부탁드립니다. :sweat_smile:

안녕하세요, young-hun-jo님. PyTorch를 이용한 DCGAN 학습에 어려움을 겪고 계신 것 같네요. 질문을 잘 정리해보면, 공식 튜토리얼의 DCGAN 코드를 이용하여 CelebA 데이터셋으로 학습을 진행하였지만, 판별자(Discriminator)의 성능이 좋지 않고, 생성자(Generator)의 Loss 값이 0으로 수렴하는 문제가 발생하고 있습니다. 그리고 학습 중간에 생성된 이미지 샘플을 출력해보면 여전히 노이즈 이미지라고 하셨습니다.

먼저, 코드를 살펴보니 optimizer를 설정하는 부분에서 생성자와 판별자의 파라미터를 잘못 설정하신 것 같습니다. 생성자의 optimizer에 판별자의 파라미터를, 판별자의 optimizer에 생성자의 파라미터를 넣어주셨네요. 이 부분을 수정해주시면 될 것 같습니다.

# optimizer
optimizerD = Adam(discriminator.parameters(), lr=lr, betas=(beta1, 0.999))  # discriminator.parameters()로 수정
optimizerG = Adam(generator.parameters(), lr=lr, betas=(beta1, 0.999))  # generator.parameters()로 수정

또한, GAN 학습은 매우 민감한 작업이며, 학습률(lr)이나 배치 크기(BATCH_SIZE) 등의 하이퍼파라미터에 따라 결과가 크게 달라질 수 있습니다. 이 부분도 적절히 조절해보시는 것을 추천드립니다.

마지막으로, GAN의 학습 과정은 매우 불안정하며, 초기에는 노이즈 같은 이미지가 생성될 수 있습니다. 따라서, 학습을 충분히 진행해보시는 것도 중요합니다.

이러한 내용은 PyTorch 공식 튜토리얼의 DCGAN 학습 가이드에서도 확인하실 수 있습니다. 아래 링크를 참조해주세요.

제가 제시한 해결책이 young-hun-jo님의 문제를 해결하는 데 도움이 되길 바라며, 여전히 문제가 해결되지 않는다면 추가적인 정보와 함께 다시 질문해주시기 바랍니다. 제 답변이 틀릴 수도 있으니, 다른 전문가의 의견도 참고하시는 것이 좋습니다. 감사합니다.


  • 답변이 어떠셨나요? 괜찮았다고 생각하신다면 아래 :heart:를 한 번 눌러주시길 부탁드려요!
1개의 좋아요

우오옷!!! 감사합니다 지피티! 치명적인 실수를 잡아주다니 ㅜ 역시 등잔 밑이 어두웠군요..

optimizer를 설정하는 부분에서 생성자와 판별자의 파라미터를 잘못 설정하신 것 같습니다. 생성자의 optimizer에 판별자의 파라미터를, 판별자의 optimizer에 생성자의 파라미터를 넣어주셨네요. 이 부분을 수정해주시면 될 것 같습니다.
=> 이 부분 수정하여 완료했습니다!

1개의 좋아요