FruitImgAmplify

@esthrelar·2023년 8월 25일
0

전체 코드 :

#%matplotlib inline
import argparse
import os
import random
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.backends.cudnn as cudnn
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
from torch.utils.data import Dataset
from PIL import Image

import time
results_dir = './data/results'
os.makedirs(results_dir, exist_ok=True)

manualSeed = 999
random.seed(manualSeed)
torch.manual_seed(manualSeed)
np.random.seed(manualSeed)


# Batch size during training
batch_size = 128

# Spatial size of training images. All images will be resized to this
#   size using a transformer.
image_size = 64

# Number of channels in the training images. For color images this is 3
nc = 3

# Size of z latent vector (i.e. size of generator input)
nz = 100

# Size of feature maps in generator
ngf = 64

# Size of feature maps in discriminator
ndf = 64

# Number of training epochs
num_epochs = 50000

# Learning rate for optimizers
lr = 0.0002

# Beta1 hyperparam for Adam optimizers
beta1 = 0.5

# Number of GPUs available. Use 0 for CPU mode.
ngpu = 1
     

transform = transforms.Compose([
    transforms.Resize((image_size, image_size)),  # 이미지 크기를 원하는 크기로 조정
    transforms.ToTensor(),
    transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
])


class CustomDataset(Dataset):
    def __init__(self, root, transform=None):
        self.root = root
        self.transform = transform
        self.image_paths = [os.path.join(root, filename) for filename in os.listdir(root) if filename.endswith(".jpg") or filename.endswith(".jpeg")]


    def __len__(self):
        return len(self.image_paths)

    def __getitem__(self, idx):
        image_path = self.image_paths[idx]
        image = Image.open(image_path).convert("RGB")  # Convert to RGB mode
        if self.transform:
            image = self.transform(image)
        return image

train_dataset = CustomDataset(root='./data/mlp/train/Apple', transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
     

real_batch = next(iter(train_loader))
plt.figure(figsize=(8,8))
plt.axis("off")
plt.title("Training Images")

device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64],padding=2,normalize=True).cpu(),(1,2,0)))
     
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, 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
     

netG = Generator(ngpu).to(device)

# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
    netG = nn.DataParallel(netG, list(range(ngpu)))

# Apply the weights_init function to randomly initialize all weights
#  to mean=0, stdev=0.2.
netG.apply(weights_init)


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)
     

# Create the Discriminator
netD = Discriminator(ngpu).to(device)

# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
    netD = nn.DataParallel(netD, list(range(ngpu)))
    
# Apply the weights_init function to randomly initialize all weights
#  to mean=0, stdev=0.2.
netD.apply(weights_init)

criterion = nn.BCELoss()

# Create batch of latent vectors that we will use to visualize
#  the progression of the generator
fixed_noise = torch.randn(64, nz, 1, 1, device=device)

# Establish convention for real and fake labels during training
real_label = 1.
fake_label = 0.

# Setup Adam optimizers for both G and D
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
     
# Training Loop

# Lists to keep track of progress
img_list = []
G_losses = []
D_losses = []
iters = 0

# 시작 시간 기록
start_time = time.time()

print("Starting Training Loop...")
# For each epoch
for epoch in range(num_epochs):
    # For each batch in the dataloader
    for i, data in enumerate(train_loader, 0):
        
        ############################
        # (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
        ###########################
        ## Train with all-real batch# Train with all-real batch
        netD.zero_grad()
        # Format batch
        real_cpu = data.to(device)  # Modify this line
        b_size = real_cpu.size(0)
        label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
        # Forward pass real batch through D
        output = netD(real_cpu).view(-1)
        # Calculate loss on all-real batch
        errD_real = criterion(output, label)
        # Calculate gradients for D in backward pass
        errD_real.backward()
        D_x = output.mean().item()

        # Train with all-fake batch
        # Generate batch of latent vectors
        noise = torch.randn(b_size, nz, 1, 1, device=device)
        # Generate fake image batch with G
        fake = netG(noise)
        label.fill_(fake_label)
        # Classify all fake batch with D
        output = netD(fake.detach()).view(-1)
        # Calculate D's loss on the all-fake batch
        errD_fake = criterion(output, label)
        # Calculate the gradients for this batch
        errD_fake.backward()
        D_G_z1 = output.mean().item()
        # Add the gradients from the all-real and all-fake batches
        errD = errD_real + errD_fake
        # Update D
        optimizerD.step()
        ############################
        # (2) Update G network: maximize log(D(G(z)))
        ###########################
        netG.zero_grad()
        label.fill_(real_label)  # fake labels are real for generator cost
        # Since we just updated D, perform another forward pass of all-fake batch through D
        output = netD(fake).view(-1)
        # Calculate G's loss based on this output
        errG = criterion(output, label)
        # Calculate gradients for G
        errG.backward()
        D_G_z2 = output.mean().item()
        # Update G
        optimizerG.step()
        
        # Output training stats
        if epoch % 10 == 0 and i == 0:
            print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
                  % (epoch, num_epochs, i, len(train_loader),
                     errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))
            
        if errD.item() <= 0.001:
            break

        if iters % 4000 == 0:
            # Save Losses for plotting later
            G_losses.append(errG.item())
            D_losses.append(errD.item())
        
        # Check how the generator is doing by saving G's output on fixed_noise
        if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(train_loader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
        
        iters += 1
    
    if epoch % 2000 == 0:
        # Save generated images at specified interval
        with torch.no_grad():
            fake = netG(fixed_noise).detach().cpu()
        fake_grid = vutils.make_grid(fake, padding=2, normalize=True)
        image_filename = f"generated_epoch_{epoch}.png"
        image_filepath = os.path.join('./data/results', image_filename)
        vutils.save_image(fake_grid, image_filepath, dpi=300)
        print(f"Generated images saved for epoch {epoch}.")
    




plt.figure(figsize=(10,5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses,label="G")
plt.plot(D_losses,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()
     

fig = plt.figure(figsize=(8,8))
plt.axis("off")
ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)

HTML(ani.to_jshtml())
     

# Grab a batch of real images from the dataloader
real_batch = next(iter(train_loader))

# Plot the real images
plt.figure(figsize=(15,15))
plt.subplot(1,2,1)
plt.axis("off")
plt.title("Real Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=5, normalize=True).cpu(),(1,2,0)))

# Plot the fake images from the last epoch
plt.subplot(1,2,2)
plt.axis("off")
plt.title("Fake Images")
plt.imshow(np.transpose(img_list[-1],(1,2,0)))
plt.show()
plt.savefig(os.path.join(results_dir, 'side_by_side_images.png'), dpi=300)
plt.show()     


plt.figure(figsize=(10,5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses,label="G")
plt.plot(D_losses,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.savefig(os.path.join(results_dir, 'loss_graph.png'), dpi=300)
plt.show()

# 종료 시간 기록
end_time = time.time()

# 소요 시간 계산
training_time = end_time - start_time

# 시, 분, 초로 변환
minutes = int(training_time // 60)
seconds = int(training_time % 60)
hours = int(minutes // 60)
minutes = int(minutes % 60)

# 결과 출력
print(f"훈련 소요 시간: {hours}시간 {minutes}{seconds}초")

print("끝!")
     

코드 설명

1. Importing Libraries and Setting Up Environment(라이브러리 가져오기 및 환경 설정):

# 필요한 라이브러리를 가져옵니다.
import argparse  # 명령행 인자를 파싱하는 데 사용되는 모듈
import os  # 운영 체제와 상호작용하는 데 사용되는 모듈
import random  # 난수 생성 및 조작에 사용되는 모듈
import torch  # 파이토치 딥러닝 프레임워크
import torch.nn as nn  # 신경망 모델을 정의하기 위한 모듈
import torch.backends.cudnn as cudnn  # GPU 가속을 위한 모듈
import torch.optim as optim  # 옵티마이저를 정의하기 위한 모듈
import torch.utils.data  # 데이터 로딩 및 배치 처리를 위한 모듈
import torchvision.datasets as dset  # 공개 데이터셋을 제공하는 모듈
import torchvision.transforms as transforms  # 데이터 전처리를 위한 모듈
import torchvision.utils as vutils  # 이미지 유틸리티 관련 모듈
import numpy as np  # 다차원 배열 및 수학 연산을 위한 모듈
import matplotlib.pyplot as plt  # 데이터 시각화를 위한 모듈
import matplotlib.animation as animation  # 애니메이션 생성을 위한 모듈
from IPython.display import HTML  # IPython 환경에서 HTML 요소를 표시하기 위한 모듈
from torch.utils.data import Dataset  # 사용자 정의 데이터셋을 위한 기본 클래스
from PIL import Image  # 이미지 처리를 위한 모듈

import time  # 시간 관련 작업을 위한 모듈

# 결과 이미지를 저장할 디렉토리를 생성합니다.
results_dir = './data/results'
os.makedirs(results_dir, exist_ok=True)

# 실험 결과 재현성을 위한 랜덤 시드를 설정합니다.
manualSeed = 999
random.seed(manualSeed)
torch.manual_seed(manualSeed)
np.random.seed(manualSeed)

2. Configurations(설정):

# Batch size during training
# 한 번에 처리하는 배치 크기
batch_size = 128

# Spatial size of training images. All images will be resized to this size using a transformer.
# 훈련 이미지의 공간 크기. 모든 이미지는 이 크기로 변환됩니다.
image_size = 64

# Number of channels in the training images. For color images this is 3
# 훈련 이미지의 채널 수. 컬러 이미지의 경우 3입니다.
nc = 3

# Size of z latent vector (i.e. size of generator input)
# 생성자 입력 벡터 z의 크기 (즉, 생성자의 입력 크기)
nz = 100

# Size of feature maps in generator
ngf = 64

# Size of feature maps in discriminator
ndf = 64

# Number of training epochs
num_epochs = 50000

# Learning rate for optimizers
lr = 0.0002

# Beta1 hyperparam for Adam optimizers
# Adam 옵티마이저의 Beta1 하이퍼파라미터
beta1 = 0.5

# Number of GPUs available. Use 0 for CPU mode.
# 사용 가능한 GPU 수. CPU 모드로 실행하려면 0을 사용합니다.
ngpu = 1
     
# 이미지 변환을 위한 변환 함수 설정
transform = transforms.Compose([
    transforms.Resize((image_size, image_size)),  # 이미지 크기를 원하는 크기로 조정
    transforms.ToTensor(), # 이미지를 텐서로 변환
    transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)) # 이미지를 정규화
])

3. Custom Dataset Class(커스텀 데이터셋 클래스):

cclass CustomDataset(Dataset):
    def __init__(self, root, transform=None):
        # 데이터셋 초기화 메서드입니다.
        # root: 데이터가 있는 디렉토리 경로
        # transform: 데이터 변환을 위한 함수

        self.root = root  # 데이터 디렉토리 경로 저장
        self.transform = transform  # 이미지 변환 함수 저장

        # 디렉토리 내의 이미지 파일을 찾아서 리스트로 저장합니다.
        self.image_paths = [
            os.path.join(root, filename) for filename in os.listdir(root)
            if filename.endswith(".jpg") or filename.endswith(".jpeg")
        ]

    def __len__(self):
        # 데이터셋의 샘플 수를 반환합니다.
        return len(self.image_paths)

    def __getitem__(self, idx):
        # idx 번째 샘플을 가져오는 메서드입니다.
        image_path = self.image_paths[idx]  # 해당 샘플의 이미지 파일 경로를 가져옵니다.
        image = Image.open(image_path).convert("RGB")  # 이미지를 열어서 RGB 모드로 변환합니다.

        if self.transform:
            image = self.transform(image)  # 지정된 변환 함수로 이미지를 변환합니다.

        return image  # 변환된 이미지를 반환합니다.

4. Loading Data(데이터 로드):

커스텀 데이터셋을 생성하고 이를 모델 학습에 사용할 데이터로더로 만드는 부분입니다.

# 데이터셋을 생성하고 데이터로더를 만드는 과정입니다.

# 데이터셋 생성
train_dataset = CustomDataset(root='./data/mlp/train/Apple', transform=transform)

# 데이터로더 생성
train_loader = torch.utils.data.DataLoader(
    train_dataset,  # 사용할 데이터셋을 지정합니다.
    batch_size=batch_size,  # 배치 크기를 지정합니다.
    shuffle=True  # 데이터를 무작위로 섞어서 사용합니다.
)

위 코드는 커스텀 데이터셋 CustomDataset을 생성하고, 이 데이터셋을 배치 단위로 처리할 수 있는 데이터로더를 생성하는 과정을 나타냅니다.

  1. train_dataset: CustomDataset 클래스를 활용하여 데이터셋을 생성합니다. 이 때 root 인자에는 데이터셋이 있는 디렉토리 경로를, transform 인자에는 이미지 변환 함수를 전달합니다.

  2. train_loader: torch.utils.data.DataLoader 클래스를 사용하여 데이터로더를 생성합니다. 이 데이터로더는 train_dataset을 기반으로 배치 단위로 데이터를 제공합니다. batch_size 인자로 배치 크기를 설정하고, shuffleTrue로 설정하여 데이터를 무작위로 섞어서 사용합니다.

이렇게 생성한 데이터로더를 이용하여 모델을 훈련하면, 데이터를 일정한 배치 크기로 나눠서 모델에 입력하고, 에폭마다 데이터 순서를 무작위로 섞어가며 학습할 수 있습니다.

5. Displaying Training Images(훈련 이미지 표시):

# 실제 훈련 이미지 배치를 가져와서 시각화하는 과정입니다.

# train_loader로부터 다음 배치 데이터를 가져옵니다.
real_batch = next(iter(train_loader))

# 이미지를 시각화하기 위한 설정
plt.figure(figsize=(8, 8))  # 이미지를 보여줄 크기를 설정합니다.
plt.axis("off")  # 축을 표시하지 않도록 설정합니다.
plt.title("Training Images")  # 그래프 제목을 설정합니다.

# CUDA(GPU)가 사용 가능한 경우, device를 설정합니다.
device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")

# 훈련 이미지를 그리드 형태로 만들어 시각화합니다.
# real_batch[0]에는 훈련 이미지 배치가 들어있습니다.
# 배치 크기 중에서 처음 64개 이미지만 사용하도록 선택합니다.
# 그리고 CUDA(GPU)를 사용할 수 있다면 GPU로 데이터를 옮긴 후 시각화합니다.
plt.imshow(
    np.transpose(
        vutils.make_grid(real_batch[0].to(device)[:64], padding=2, normalize=True).cpu(),
        (1, 2, 0)
    )
)

물론입니다. 위 코드 부분은 실제 훈련 이미지 배치를 그리드 형태로 만들어서 시각화하는 과정입니다. 아래에서 코드의 각 부분을 자세히 설명하겠습니다.

plt.imshow(
    np.transpose(
        vutils.make_grid(real_batch[0].to(device)[:64], padding=2, normalize=True).cpu(),
        (1, 2, 0)
    )
)
  1. real_batch[0]: real_batch는 데이터로더에서 가져온 실제 훈련 이미지 배치입니다. 여기서 [0]을 붙여서 첫 번째 배치를 선택합니다.

  2. .to(device): CUDA(GPU)를 사용할 수 있다면, 이미지 데이터를 device로 옮깁니다.

  3. [:64]: 선택한 배치 중에서 처음 64개 이미지만 선택합니다.

  4. vutils.make_grid(...): 이미지 데이터를 그리드 형태로 배치하고, 옵션을 설정합니다. padding은 이미지 사이의 간격을 조절하는 값이며, normalize는 이미지를 정규화할지 여부를 결정하는 옵션입니다.

  5. .cpu(): 이미지 데이터를 CPU로 옮깁니다. (GPU에서 시각화하기 위함)

  6. np.transpose(...): NumPy 배열의 차원을 재배열합니다. 이미지의 차원을 (너비, 채널, 높이)로 재배열합니다. 이렇게 하는 이유는 matplotlib의 기본적인 이미지 표시 방식과 호환성을 유지하기 위함입니다.

  7. plt.imshow(...): 재배열된 이미지 데이터를 plt.imshow() 함수를 사용하여 시각화합니다. 이 함수는 이미지를 그리는 데 사용됩니다.

요약하면, 이 코드는 real_batch에서 첫 번째 배치의 이미지들을 가져와서 그리드 형태로 배치한 후, 필요한 변환과 재배열을 거쳐 시각화합니다. 이렇게 하면 해당 배치의 훈련 이미지들을 한 번에 시각화할 수 있습니다.

6. Model Initialization and Architecture(모델 초기화 및 아키텍처):

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, 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
     
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)

weights_init(m)

신경망 모델의 가중치를 초기화하는 함수 weights_init입니다. 초기화는 모델의 학습 초기 단계에서 가중치 값을 적절한 방식으로 설정하는 과정입니다.

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)

이 함수의 목적은 신경망 모델 내의 각 레이어의 가중치와 바이어스를 적절한 값으로 초기화하는 것입니다. 이때 특정한 유형의 레이어에 따라 다른 초기화 방식을 적용합니다.

  • classname = m.__class__.__name__: 현재 모듈의 클래스 이름을 가져옵니다. 예를 들어, nn.Conv2d 레이어의 클래스 이름은 'Conv2d'입니다.

  • if classname.find('Conv') != -1: 클래스 이름에 'Conv' 문자열이 포함되어 있는 경우 (즉, Convolution 레이어인 경우), 다음 코드 블록을 실행합니다.

  • nn.init.normal_(m.weight.data, 0.0, 0.02): Convolution 레이어의 가중치를 정규 분포에서 샘플링한 값으로 초기화합니다. 평균이 0이고 표준 편차가 0.02인 정규 분포에서 값을 샘플링하여 가중치에 적용합니다.

  • elif classname.find('BatchNorm') != -1: 클래스 이름에 'BatchNorm' 문자열이 포함되어 있는 경우 (즉, Batch Normalization 레이어인 경우), 다음 코드 블록을 실행합니다.

  • nn.init.normal_(m.weight.data, 1.0, 0.02): Batch Normalization 레이어의 가중치를 1.0의 값으로 초기화합니다.

  • nn.init.constant_(m.bias.data, 0): Batch Normalization 레이어의 바이어스를 0의 값으로 초기화합니다.

이렇게 초기화 함수를 통해 각 레이어의 가중치와 바이어스를 적절하게 초기화할 수 있습니다. 초기화 방식은 모델의 학습에 중요한 역할을 하며, 적절한 초기화는 학습 과정을 안정적으로 도와줍니다.


class Generator(nn.Module)

전체 코드:

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

위 코드 부분은 생성자(G) 네트워크를 정의하는 파이토치 모듈입니다. 생성자는 주어진 랜덤 노이즈 벡터를 가지고 가짜 이미지를 생성하는 역할을 합니다. 이제 코드를 하나씩 자세히 설명하겠습니다.

class Generator(nn.Module):
    def __init__(self, ngpu):
        super(Generator, self).__init__()
        self.ngpu = ngpu
        self.main = nn.Sequential(
  • Generator 클래스를 정의합니다. 이 클래스는 nn.Module을 상속합니다. ngpu는 사용 가능한 GPU 수를 나타내며, 생성자 안에서 사용됩니다.

  • nn.Sequential()은 신경망 레이어를 순차적으로 쌓을 때 사용하는 컨테이너입니다. 생성자의 메인 네트워크 레이어들을 순차적으로 추가하기 위해 이 컨테이너를 사용합니다.

nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf * 8),
nn.ReLU(True),
  • 첫 번째 레이어: nn.ConvTranspose2d 레이어로, 랜덤 노이즈 벡터(nz)를 입력으로 받아 가짜 이미지를 생성합니다. 이 레이어는 Transposed Convolution을 사용하여 이미지를 생성합니다. ngf * 8은 생성자의 특성 맵 크기입니다. 커널 크기는 4x4, 스트라이드는 1, 패딩은 0입니다.

  • 두 번째 레이어: nn.BatchNorm2d 레이어로, 배치 정규화를 수행합니다. 생성자 네트워크에서 정규화를 통해 안정적인 학습을 돕습니다.

  • 세 번째 레이어: ReLU 활성화 함수로 활성화합니다. 생성자 네트워크에서 각 레이어의 활성화를 활성화하려는 목적입니다.

이와 같이 생성자 네트워크는 Convolution Transpose, 배치 정규화 및 활성화 함수의 레이어들로 구성됩니다. 이런 레이어들을 연결하여 이미지를 생성하는 데 사용됩니다. 네트워크의 깊이에 따라 점차 이미지의 크기가 커지고, 특성 맵의 깊이가 줄어들게 됩니다.

# 다른 ConvTranspose2d 레이어들도 유사한 구조로 정의됩니다.
def forward(self, input):
    output = self.main(input)
    return output
  • forward 메서드: 이 메서드는 생성자 클래스의 순방향 전파를 정의합니다. 입력으로 주어진 노이즈 벡터를 생성자 네트워크의 순차적인 레이어를 통과시켜 가짜 이미지를 생성합니다. 출력으로 생성된 이미지를 반환합니다.

이렇게 생성자 네트워크를 정의하면, 랜덤 노이즈 벡터를 입력으로 받아 특정한 규칙에 따라 가짜 이미지를 생성할 수 있습니다. 이것은 GAN(Generative Adversarial Network)의 중요한 부분 중 하나로, 생성자는 판별자(Discriminator)가 진짜와 가짜 이미지를 구별하는 것을 속이기 위한 능력을 향상시키도록 학습됩니다.


class Discriminator(nn.Module)

전체 코드 :

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)

위 코드 부분은 판별자(D) 네트워크를 정의하는 파이토치 모듈입니다. 판별자는 주어진 이미지가 실제 이미지인지 가짜 이미지인지를 판별하는 역할을 합니다. 이제 코드를 하나씩 자세히 설명하겠습니다.

class Discriminator(nn.Module):
    def __init__(self, ngpu):
        super(Discriminator, self).__init__()
        self.ngpu = ngpu
        self.main = nn.Sequential(
  • Discriminator 클래스를 정의합니다. 이 클래스는 nn.Module을 상속합니다. ngpu는 사용 가능한 GPU 수를 나타내며, 판별자 안에서 사용됩니다.

  • nn.Sequential()은 신경망 레이어를 순차적으로 쌓을 때 사용하는 컨테이너입니다. 판별자의 메인 네트워크 레이어들을 순차적으로 추가하기 위해 이 컨테이너를 사용합니다.

nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
  • 첫 번째 레이어: nn.Conv2d 레이어로, 입력 이미지(nc)를 받아 이미지의 특징을 추출합니다. 이 레이어는 Convolution을 사용하여 이미지의 특징을 추출하고, 스트라이드 2와 패딩 1을 사용하여 이미지의 크기를 줄입니다.

  • 두 번째 레이어: nn.LeakyReLU 레이어로, LeakyReLU 활성화 함수를 적용합니다. 이 함수는 음수 입력에 대해 작은 기울기를 갖는 ReLU의 변형입니다.

  • 다른 Convolution 레이어들도 유사한 방식으로 정의됩니다.

def forward(self, input):
    output = self.main(input)
    return output.view(-1, 1).squeeze(1)
  • forward 메서드: 이 메서드는 판별자 클래스의 순방향 전파를 정의합니다. 입력으로 주어진 이미지를 판별자 네트워크의 순차적인 레이어를 통과시켜 실제 이미지인지 가짜 이미지인지를 판별합니다. 출력으로 판별 결과를 반환합니다.

  • output.view(-1, 1).squeeze(1): 출력 결과를 모양을 조정하여 판별 결과를 얻습니다. view 함수는 텐서의 모양을 변경하는 역할을 하며, squeeze 함수는 차원 중 사이즈가 1인 차원을 제거합니다.

판별자 네트워크는 Convolution 레이어와 LeakyReLU 활성화 함수의 조합으로 이미지의 특징을 추출하고, 마지막에는 판별 결과를 출력합니다. 판별자의 목적은 주어진 이미지가 실제 이미지인지 가짜 이미지인지를 구분하는 것으로, 이것은 GAN(Generative Adversarial Network)의 중요한 부분 중 하나입니다.

7. Instantiating Models(모델 인스턴스화):

전체 코드 :

netG = Generator(ngpu).to(device)

# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
    netG = nn.DataParallel(netG, list(range(ngpu)))

# Apply the weights_init function to randomly initialize all weights
#  to mean=0, stdev=0.2.
netG.apply(weights_init)

# Create the Discriminator
netD = Discriminator(ngpu).to(device)

# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
    netD = nn.DataParallel(netD, list(range(ngpu)))
    
# Apply the weights_init function to randomly initialize all weights
#  to mean=0, stdev=0.2.
netD.apply(weights_init)

설명 :
이 코드 부분은 생성자(G)와 판별자(D) 네트워크를 초기화하고 GPU를 사용할 경우 멀티 GPU를 지원하는 설정을 하는 부분입니다. 아래 코드를 하나씩 자세히 설명하겠습니다.

netG = Generator(ngpu).to(device)
  • Generator 클래스의 객체 netG를 생성합니다. 생성자 클래스에 ngpu 값을 전달하여 생성자 모델을 초기화합니다.

  • .to(device): 생성자 모델을 지정한 장치(device)로 이동시킵니다. 만약 GPU를 사용할 수 있다면, 이로써 모델은 GPU에 할당됩니다.

if (device.type == 'cuda') and (ngpu > 1):
    netG = nn.DataParallel(netG, list(range(ngpu)))
  • GPU를 사용하며, 사용 가능한 GPU 수 ngpu가 1보다 큰 경우에는 멀티 GPU를 지원하는 nn.DataParallel을 사용하여 생성자 모델 netG를 감싸줍니다. 이렇게 하면 생성자 모델이 여러 GPU에 병렬로 분산되어 처리될 수 있습니다.
netG.apply(weights_init)
  • weights_init 함수를 이용하여 생성자 모델의 모든 가중치를 초기화합니다. 이 함수는 모델의 가중치를 평균이 0이고 표준편차가 0.2인 랜덤한 값으로 초기화합니다.
netD = Discriminator(ngpu).to(device)
  • Discriminator 클래스의 객체 netD를 생성합니다. 판별자 클래스에 ngpu 값을 전달하여 판별자 모델을 초기화합니다.

  • .to(device): 판별자 모델을 지정한 장치(device)로 이동시킵니다. GPU를 사용할 수 있다면, 이로써 모델은 GPU에 할당됩니다.

if (device.type == 'cuda') and (ngpu > 1):
    netD = nn.DataParallel(netD, list(range(ngpu)))
  • GPU를 사용하며, 사용 가능한 GPU 수 ngpu가 1보다 큰 경우에는 멀티 GPU를 지원하는 nn.DataParallel을 사용하여 판별자 모델 netD를 감싸줍니다. 이렇게 하면 판별자 모델이 여러 GPU에 병렬로 분산되어 처리될 수 있습니다.
netD.apply(weights_init)
  • weights_init 함수를 이용하여 판별자 모델의 모든 가중치를 초기화합니다. 이 함수는 모델의 가중치를 평균이 0이고 표준편차가 0.2인 랜덤한 값으로 초기화합니다.

이렇게 생성자와 판별자 네트워크를 초기화하고, GPU와 멀티 GPU 설정을 적용한 후에는 모델들이 준비되어 학습을 진행할 준비가 된 상태입니다.

8. Loss Function and Optimizers(손실 함수 및 옵티마이저):

전체 코드 :

criterion = nn.BCELoss()

# Create batch of latent vectors that we will use to visualize
#  the progression of the generator
fixed_noise = torch.randn(64, nz, 1, 1, device=device)

# Establish convention for real and fake labels during training
real_label = 1.
fake_label = 0.

# Setup Adam optimizers for both G and D
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))

설명 :

criterion = nn.BCELoss()
  • nn.BCELoss()는 이진 교차 엔트로피 손실(Binary Cross-Entropy Loss) 함수를 생성합니다. 이 함수는 이진 분류 문제에서 사용되며, 실제와 예측 사이의 차이를 측정하여 손실 값을 계산합니다. 생성자와 판별자의 학습에서 사용될 손실 함수입니다.
fixed_noise = torch.randn(64, nz, 1, 1, device=device)
  • fixed_noise는 생성자의 학습 과정을 시각화하기 위해 사용되는 고정된 잠재 벡터(batch of latent vectors)입니다. 각 행은 하나의 잠재 벡터로 구성되며, 이러한 잠재 벡터들을 이용해 생성자가 각 단계별로 어떤 이미지를 생성하는지 시각화할 수 있습니다. torch.randn 함수로 랜덤한 값을 생성하며, nz는 잠재 벡터의 차원을 나타냅니다.
real_label = 1.
fake_label = 0.
  • real_labelfake_label은 훈련 과정에서 실제 이미지와 생성된(fake) 이미지를 구분하기 위해 사용되는 레이블입니다. 이진 분류에서 실제 이미지의 정답 레이블과 생성된 이미지의 정답 레이블로 사용됩니다.
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
  • optimizerD는 판별자 네트워크의 가중치를 최적화하는 데 사용되는 Adam 옵티마이저(optimizer)입니다. netD.parameters()는 판별자 네트워크의 학습 가능한 가중치들을 가져와 최적화할 목록으로 만듭니다.

  • optimizerG는 생성자 네트워크의 가중치를 최적화하는 데 사용되는 Adam 옵티마이저(optimizer)입니다. netG.parameters()는 생성자 네트워크의 학습 가능한 가중치들을 가져와 최적화할 목록으로 만듭니다.

  • lr은 학습률(learning rate)로, 옵티마이저가 가중치를 업데이트할 때 사용되는 단계 크기입니다.

  • betas는 Adam 옵티마이저의 하이퍼파라미터 중 하나로, 이전 그래디언트와 이전 그래디언트 제곱의 이동 평균을 계산하는 데 사용됩니다.

이러한 설정은 생성자와 판별자의 학습을 위한 손실 함수, 고정된 잠재 벡터, 옵티마이저 등을 준비하는 부분입니다. 이제 이 설정을 사용하여 실제 훈련 루프를 실행하며 네트워크가 학습되는 과정을 진행할 수 있습니다.

9. Training Loop(훈련 루프):

전체 코드 :

# Training Loop

# Lists to keep track of progress
img_list = []
G_losses = []
D_losses = []
iters = 0

# 시작 시간 기록
start_time = time.time()

print("Starting Training Loop...")
# For each epoch
for epoch in range(num_epochs):
    # For each batch in the dataloader
    for i, data in enumerate(train_loader, 0):
        
        ############################
        # (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
        ###########################
        ## Train with all-real batch# Train with all-real batch
        netD.zero_grad()
        # Format batch
        real_cpu = data.to(device)  # Modify this line
        b_size = real_cpu.size(0)
        label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
        # Forward pass real batch through D
        output = netD(real_cpu).view(-1)
        # Calculate loss on all-real batch
        errD_real = criterion(output, label)
        # Calculate gradients for D in backward pass
        errD_real.backward()
        D_x = output.mean().item()

        # Train with all-fake batch
        # Generate batch of latent vectors
        noise = torch.randn(b_size, nz, 1, 1, device=device)
        # Generate fake image batch with G
        fake = netG(noise)
        label.fill_(fake_label)
        # Classify all fake batch with D
        output = netD(fake.detach()).view(-1)
        # Calculate D's loss on the all-fake batch
        errD_fake = criterion(output, label)
        # Calculate the gradients for this batch
        errD_fake.backward()
        D_G_z1 = output.mean().item()
        # Add the gradients from the all-real and all-fake batches
        errD = errD_real + errD_fake
        # Update D
        optimizerD.step()
        ############################
        # (2) Update G network: maximize log(D(G(z)))
        ###########################
        netG.zero_grad()
        label.fill_(real_label)  # fake labels are real for generator cost
        # Since we just updated D, perform another forward pass of all-fake batch through D
        output = netD(fake).view(-1)
        # Calculate G's loss based on this output
        errG = criterion(output, label)
        # Calculate gradients for G
        errG.backward()
        D_G_z2 = output.mean().item()
        # Update G
        optimizerG.step()
        
        # Output training stats
        if epoch % 10 == 0 and i == 0:
            print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
                  % (epoch, num_epochs, i, len(train_loader),
                     errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))
            
        if errD.item() <= 0.001:
            break

        if iters % 4000 == 0:
            # Save Losses for plotting later
            G_losses.append(errG.item())
            D_losses.append(errD.item())
        
        # Check how the generator is doing by saving G's output on fixed_noise
        if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(train_loader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
        
        iters += 1
    
    if epoch % 2000 == 0:
        # Save generated images at specified interval
        with torch.no_grad():
            fake = netG(fixed_noise).detach().cpu()
        fake_grid = vutils.make_grid(fake, padding=2, normalize=True)
        image_filename = f"generated_epoch_{epoch}.png"
        image_filepath = os.path.join('./data/results', image_filename)
        vutils.save_image(fake_grid, image_filepath, dpi=300)
        print(f"Generated images saved for epoch {epoch}.")

설명 :
이 코드는 DCGAN(Discriminator-Generator Adversarial Network)의 훈련 루프를 구현한 부분으로, 생성자와 판별자를 번갈아가며 학습시키는 과정을 담고 있습니다. 코드의 각 부분을 하나씩 설명하겠습니다.

img_list = []
G_losses = []
D_losses = []
iters = 0
  • img_list: 생성자가 생성한 이미지를 저장할 리스트입니다.
  • G_losses: 각 반복(iteration)마다 생성자의 손실(loss)을 저장하는 리스트입니다.
  • D_losses: 각 반복(iteration)마다 판별자의 손실(loss)을 저장하는 리스트입니다.
  • iters: 현재까지의 반복 횟수를 나타내는 변수입니다.
for epoch in range(num_epochs):
    for i, data in enumerate(train_loader, 0):
  • 전체 훈련 에포크(num_epochs) 동안 반복합니다.
  • enumerate(train_loader, 0)train_loader에서 배치(batch)별 데이터를 가져옵니다. data에는 실제 이미지 배치가 저장됩니다.

(1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))

(1-1) Train with all-real batch

netD.zero_grad()
real_cpu = data.to(device)
b_size = real_cpu.size(0)
label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
output = netD(real_cpu).view(-1)
errD_real = criterion(output, label)
errD_real.backward()
D_x = output.mean().item()
  • 판별자 네트워크(netD)를 초기화하고 실제 이미지 데이터(real_cpu)를 GPU로 옮깁니다.
  • b_size는 배치 크기입니다.
  • label은 현재 배치에 대한 실제 레이블로, 모두 실제 이미지라는 것을 나타냅니다.
  • netD에 실제 이미지를 통과시킨 후 결과(output)와 실제 레이블을 사용하여 손실(errD_real)을 계산합니다.
  • backward()를 호출하여 그레디언트(gradient)를 계산하고, D_x에는 판별자가 실제 이미지에 대해 내놓은 예측의 평균을 저장합니다.

물론입니다! 이 부분은 DCGAN 훈련 루프의 중요한 단계인 생성자와 판별자의 업데이트 과정을 설명하는 부분입니다. 이 코드 블록은 DCGAN의 핵심 아이디어를 반영하고 있습니다.

(1-2) Train with all-fake batch

# Train with all-fake batch
        # Generate batch of latent vectors
        noise = torch.randn(b_size, nz, 1, 1, device=device)
        # Generate fake image batch with G
        fake = netG(noise)
        label.fill_(fake_label)
        # Classify all fake batch with D
        output = netD(fake.detach()).view(-1)
        # Calculate D's loss on the all-fake batch
        errD_fake = criterion(output, label)
        # Calculate the gradients for this batch
        errD_fake.backward()
        D_G_z1 = output.mean().item()
        # Add the gradients from the all-real and all-fake batches
        errD = errD_real + errD_fake
        # Update D
        optimizerD.step()

이 부분은 판별자를 업데이트하는 첫 번째 단계로, 가짜 이미지 배치를 사용하여 판별자를 학습합니다.

  1. noise = torch.randn(b_size, nz, 1, 1, device=device): 먼저 잠재 벡터(noise)를 생성합니다. 이 벡터는 생성자의 입력으로 사용됩니다. b_size는 현재 배치의 크기이며, nz는 잠재 벡터의 차원 수를 나타냅니다.

  2. fake = netG(noise): 생성자에 잠재 벡터를 입력으로 전달하여 가짜 이미지를 생성합니다. fake는 생성된 가짜 이미지 배치입니다.

  3. label.fill_(fake_label): 가짜 이미지에 대한 레이블을 생성합니다. 여기서는 가짜 이미지를 판별할 때 사용되는 레이블로, 가짜 이미지를 의미하는 fake_label 값으로 채워집니다.

  4. output = netD(fake.detach()).view(-1): 생성된 가짜 이미지를 판별자에 전달하여 결과 output을 얻습니다. .detach()를 사용하여 그레디언트 계산을 막고, .view(-1)을 사용하여 결과를 1차원으로 변환합니다.

  5. errD_fake = criterion(output, label): 판별자의 가짜 이미지에 대한 손실(errD_fake)을 계산합니다. 판별자는 가짜 이미지를 실제 이미지로 잘못 판별하도록 학습됩니다.

  6. errD_fake.backward(): 가짜 이미지의 손실에 대한 그레디언트를 계산합니다. 이를 통해 판별자의 가중치가 업데이트됩니다.

  7. D_G_z1 = output.mean().item(): 생성자의 가짜 이미지에 대한 판별자의 출력 평균(D_G_z1)을 계산합니다. 이는 생성자가 얼마나 진짜 같은 이미지를 생성하는지를 나타내는 지표입니다.

  8. errD = errD_real + errD_fake: 실제 이미지와 가짜 이미지에 대한 판별자의 손실을 합칩니다. 이 손실은 판별자가 진짜와 가짜 이미지를 올바르게 분류하는 데 사용됩니다.

  9. optimizerD.step(): 판별자의 가중치를 업데이트합니다. 그레디언트 역전파와 최적화 알고리즘을 통해 가중치를 조정합니다.

(2) Update G network: maximize log(D(G(z)))

# (2) Update G network: maximize log(D(G(z)))
        ###########################
        netG.zero_grad()
        label.fill_(real_label)  # fake labels are real for generator cost
        # Since we just updated D, perform another forward pass of all-fake batch through D
        output = netD(fake).view(-1)
        # Calculate G's loss based on this output
        errG = criterion(output, label)
        # Calculate gradients for G
        errG.backward()
        D_G_z2 = output.mean().item()
        # Update G
        optimizerG.step()

이 부분은 생성자를 업데이트하는 두 번째 단계로, 생성자의 목표는 판별자를 속이는 가짜 이미지를 생성하는 것입니다.

  1. netG.zero_grad(): 생성자의 그레디언트를 초기화합니다.

  2. label.fill_(real_label): 생성자의 손실을 계산하기 위해 실제 레이블을 사용합니다. 생성자는 판별자를 속이려고 하므로 가짜 이미지를 진짜 이미지로 판별하도록 학습됩니다.

  3. output = netD(fake).view(-1): 생성된 가짜 이미지를 판별자에 전달하여 결과 output을 얻습니다. 이때 생성자가 생성한 가짜 이미지를 판별자에게 넘겨 판별자의 출력을 얻습니다.

  4. errG = criterion(output, label): 생성자의 손실(errG)을 계산합니다. 생성자는 판별자를 속이기 위해 가짜 이미지를 만들어야 하므로, 생성자의 목표는 판별자가 가짜 이미지를 진짜로 판별하도록 만드는 것입니다.

  5. errG.backward(): 생성자의 손실에 대한 그레디언트를 계산합니다. 생성자의 가중치를 업데이트하기 위해 역전파를 수행합니다.

  6. D_G_z2 = output.mean().item(): 생성자가 생성한 가짜 이미지에 대한 판별자의 출력 평균(D_G_z2)을 계산합니다. 이는 생성자가 얼마나 판별자를 속였는지를 나타내는 지표입니다.

  7. optimizerG.step(): 생성자의 가중치를 업데이트합니다. 생성자도 그레디언트 역전파와 최적화 알고리즘을 통해 가중치를 조정합니다.

이러한 과정은 판별자와 생성자 간의 균형을 맞추는 중요한 역할을 합니다. 판별자는 실제 이미지와 가짜 이미지를 구분하는 데 학습되며, 생성자는 판별자를 속이기 위한 가짜 이미지를 생성하려고 노력합니다. 이러한 과정을 통해 생성자와 판별자는 서로를 발전시켜가며 점차적으로 훈련됩니다.

Output training stats

# Output training stats
        if epoch % 10 == 0 and i == 0:
            print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
                  % (epoch, num_epochs, i, len(train_loader),
                     errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))
            
        if errD.item() <= 0.001:
            break

        if iters % 4000 == 0:
            # Save Losses for plotting later
            G_losses.append(errG.item())
            D_losses.append(errD.item())
        
        # Check how the generator is doing by saving G's output on fixed_noise
        if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(train_loader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
        
        iters += 1

1)

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

이 부분은 생성자(G)와 판별자(D)의 훈련 중에 발생하는 손실과 출력 상태를 모니터링하고 훈련 진행 상황을 출력하는 역할을 합니다. 먼저, 코드의 구성과 사용된 변수에 대해 설명해드리겠습니다.

  • epoch: 현재 진행 중인 에포크(epoch)의 번호입니다.
  • num_epochs: 설정된 전체 에포크 수입니다.
  • i: 현재 에포크에서 진행 중인 배치(batch)의 인덱스입니다.
  • len(train_loader): 데이터로더(train_loader)의 배치 수입니다.
  • errD.item(): 판별자의 손실 값입니다. .item()을 사용하여 손실 값을 파이썬 숫자로 변환합니다.
  • errG.item(): 생성자의 손실 값입니다. 마찬가지로 .item()을 사용하여 숫자로 변환합니다.
  • D_x: 판별자의 실제 데이터에 대한 출력 값입니다.
  • D_G_z1: 생성자가 생성한 가짜 이미지에 대한 판별자의 출력 값입니다.
  • D_G_z2: 생성자의 출력 값입니다.

여기서 %d, %.4f와 같은 서식문자들은 문자열 내에 변수 값을 포맷팅하여 삽입하는 역할을 합니다.

이 코드 블록은 주로 다음 역할을 수행합니다:

  1. 매 10번째 에포크의 첫 번째 배치에서만 실행됩니다. 이를 통해 매번 모든 배치에서 출력하지 않고, 일정한 간격으로 출력할 수 있습니다.
  2. 현재 에포크, 배치, 손실값 및 출력값 등의 정보를 가시적으로 출력하여 훈련 진행 상황을 모니터링합니다.

출력되는 메시지는 다음과 같이 구성됩니다:

[현재 에포크/전체 에포크][현재 배치/전체 배치]	Loss_D: 판별자 손실값	Loss_G: 생성자 손실값	D(x): 판별자 실제 데이터 출력값	D(G(z)): 생성자가 생성한 이미지 판별자 출력값 / 생성자 출력값

이 메시지를 통해 생성자와 판별자의 훈련 진행 상황 및 손실값 등을 실시간으로 모니터링할 수 있습니다.

2)

 if errD.item() <= 0.001:
            break

이 코드 라인은 생성자(G)와 판별자(D)의 훈련 루프 내에서 판별자의 손실 값(errD)이 특정 임계값(0.001) 이하로 감소했을 때 훈련 루프를 조기에 종료하는 역할을 합니다. 이러한 기능을 "early stopping"이라고 부릅니다.

여기서 errD.item()는 현재 판별자의 손실 값을 숫자로 변환한 것입니다. 판별자의 손실 값이 지정된 임계값(0.001) 이하로 내려가면, 이 부분은 break문을 통해 현재의 훈련 에포크를 종료하게 됩니다. 이는 판별자의 손실이 충분히 낮아졌을 때 생성자와 판별자의 훈련을 조기에 중단하여, 더 이상의 훈련이 필요하지 않을 수 있다는 가정에 기반합니다.

즉, 판별자의 손실이 일정 임계값 이하로 줄어들면 생성자와 판별자의 훈련을 더 이상 진행하지 않고 훈련 루프를 종료하여 시간을 절약하고자 하는 것입니다. 이렇게 하면 불필요한 훈련 시간을 줄이고, 훈련이 조기에 수렴하여 좋은 결과를 얻을 수 있을 가능성이 있습니다.

3)

if iters % 4000 == 0:
            # Save Losses for plotting later
            G_losses.append(errG.item())
            D_losses.append(errD.item())

이 코드 라인은 생성자(G)와 판별자(D)의 훈련 중에 일정한 간격으로 훈련 중에 계산된 손실 값을 저장하는 역할을 합니다. 이러한 손실 값은 나중에 그래프로 시각화하여 훈련의 진행 상황을 확인하는 데 사용됩니다.

  • iters: 훈련 반복 횟수를 나타내는 변수입니다. 이 값은 훈련 중에 매 반복마다 1씩 증가합니다.
  • 4000: 손실 값을 저장하는 간격입니다. 훈련 반복 횟수(iters)가 이 값을 나눈 정수가 될 때마다 손실 값을 저장합니다.
  • G_losses: 생성자(G)의 손실 값을 저장하기 위한 리스트입니다. 생성자의 손실 값(errG.item())이 저장됩니다.
  • D_losses: 판별자(D)의 손실 값을 저장하기 위한 리스트입니다. 판별자의 손실 값(errD.item())이 저장됩니다.

이 코드 블록은 생성자와 판별자의 훈련 중에 손실 값을 일정한 간격으로 저장함으로써, 훈련이 진행되는 동안 손실 값의 추이를 추적하고 나중에 그래프로 시각화할 수 있도록 합니다. 이를 통해 훈련이 진행되는 동안 생성자와 판별자의 성능을 모니터링하고 훈련 과정을 분석할 수 있습니다.

4)

 # Check how the generator is doing by saving G's output on fixed_noise
        if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(train_loader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
        
        iters += 1

이 코드 블록은 생성자(G)의 출력을 고정된 잠재 벡터(fixed_noise)를 이용하여 주기적으로 저장하고 시각화하는 역할을 합니다. 이렇게 함으로써 훈련 중에 생성자가 어떤 이미지를 생성하는지 시각적으로 확인할 수 있습니다.

  • iters: 훈련 반복 횟수를 나타내는 변수입니다. 이 값은 훈련 중에 매 반복마다 1씩 증가합니다.
  • 500: 생성자의 출력을 저장하는 주기입니다. 훈련 반복 횟수(iters)가 이 값을 나눈 정수가 될 때마다 생성자의 출력을 저장하게 됩니다.
  • epoch: 현재 훈련 epoch 번호입니다.
  • num_epochs: 총 훈련 epoch 수입니다.
  • i: 현재 데이터 로더의 배치 인덱스입니다.
  • len(train_loader): 데이터 로더의 배치 개수입니다.
  • fixed_noise: 고정된 잠재 벡터입니다. 이 벡터는 생성자에 주입되어 생성자가 이미지를 생성할 때 사용됩니다.
  • netG: 생성자 모델입니다.
  • detach(): 생성자 출력을 계산 그래프로부터 분리합니다.
  • cpu(): 생성된 이미지를 CPU 메모리로 복사합니다.
  • img_list: 생성된 이미지를 저장하는 리스트입니다. 생성된 이미지들은 vutils.make_grid 함수를 통해 그리드 형태로 만들어지고 저장됩니다.
  • vutils.make_grid: 여러 이미지를 그리드로 결합하여 시각화할 수 있도록 하는 torchvision 유틸리티 함수입니다.
  • padding=2: 이미지 간의 간격을 나타내는 값입니다.
  • normalize=True: 이미지를 정규화하여 시각화할지 여부를 결정하는 값입니다.

이 코드는 주어진 주기(500번 반복마다)나 마지막 epoch의 마지막 배치에서 생성자가 만든 이미지를 저장하고, 훈련이 진행되는 동안 생성자의 출력 이미지를 추적하여 시각화하기 위한 것입니다. 이를 통해 생성자의 훈련 과정을 시각적으로 확인하고 결과 이미지의 진화를 관찰할 수 있습니다.

위 코드 중 일부 -

with torch.no_grad():
    fake = netG(fixed_noise).detach().cpu()
img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
  • 일정한 간격마다 fixed_noise로 생성자(netG)가 가짜 이미지를 생성하고, 그 이미지를 시각화를 위해 img_list에 추가합니다.
  • vutils.make_grid 함수는 그리드 형태의 이미지를 생성하여 시각화하는 함수입니다.

Save generated images at specified interval

if epoch % 2000 == 0:
    with torch.no_grad():
        fake = netG(fixed_noise).detach().cpu()
    fake_grid = vutils.make_grid(fake, padding=2, normalize=True)
    image_filename = f"generated_epoch_{epoch}.png"
    image_filepath = os.path.join('./data/results', image_filename)
    vutils.save_image(fake_grid, image_filepath, dpi=300)
    print(f"Generated images saved for epoch {epoch}.")
  • 특정 에포크(epoch)마다 생성된 가짜 이미지를 저장하고 싶은 경우, fake 이미지를 생성하고 vutils.save_image 함수를 사용하여 이미지를 저장합니다.
  • 저장된 이미지의 파일 이름은 generated_epoch_{epoch}.png 형식으로 지정됩니다.

이 코드는 생성자와 판별자를 번갈아가며 학습하면서 그 결과를 시각화하고, 학습 과정 중에 생성된 가짜 이미지를 저장하는 데 사용됩니다.

+)
물론입니다! 제공해주신 코드 조각은 생성적 적대 신경망(GAN)의 훈련 과정 중 일부입니다. 이 특정 부분은 훈련 도중 일정 간격마다 가짜 이미지를 생성하고 저장하는 역할을 합니다. 코드를 단계별로 설명해보겠습니다:

if epoch % 2000 == 0:
    # 특정 간격마다 생성된 이미지 저장
    with torch.no_grad():
        fake = netG(fixed_noise).detach().cpu()
    fake_grid = vutils.make_grid(fake, padding=2, normalize=True)
    image_filename = f"generated_epoch_{epoch}.png"
    image_filepath = os.path.join('./data/results', image_filename)
    vutils.save_image(fake_grid, image_filepath, dpi=300)
    print(f"{epoch} 에폭의 생성된 이미지 저장 완료.")
  1. if epoch % 2000 == 0:: 이 줄은 현재 에폭이 2000의 배수인지를 확인합니다. 이 조건은 훈련 중 특정 간격마다 이미지를 생성하고 저장하기 위해 사용됩니다.

  2. with torch.no_grad():: 이 컨텍스트 매니저는 다음 작업 중에 그래디언트(기울기)를 계산하지 않도록 합니다. 이미지를 생성하는 것만이 목적이며 모델을 업데이트하지 않을 것이기 때문에 메모리와 계산량을 절약하기 위한 좋은 방법입니다.

  3. fake = netG(fixed_noise).detach().cpu(): 생성자(netG)는 무작위 노이즈(fixed_noise)를 입력으로 받아 가짜 이미지를 생성합니다. .detach()는 생성된 이미지를 계산 그래프에서 분리하여 추가적인 그래디언트 계산을 방지합니다. .cpu()는 생성된 이미지를 CPU로 이동시키며, 이미지를 디스크에 저장할 예정이기 때문입니다.

  4. fake_grid = vutils.make_grid(fake, padding=2, normalize=True): 이 줄은 생성된 가짜 이미지로부터 이미지 그리드를 생성합니다. vutils.make_grid()는 PyTorch의 유틸리티 함수로, 이미지 배치를 그리드 형식으로 배열합니다. padding은 그리드 내의 이미지 사이에 약간의 여백을 추가하며, normalize=True는 이미지 픽셀 값을 [0, 1] 범위로 조정합니다.

  5. image_filename = f"generated_epoch_{epoch}.png": 이 줄은 저장할 이미지의 파일 이름을 생성합니다. 파일 이름에는 어떤 에폭에 해당하는 이미지인지를 나타내는 에폭 번호가 포함됩니다.

  6. image_filepath = os.path.join('./data/results', image_filename): 이 줄은 생성된 이미지가 저장될 전체 파일 경로를 구성합니다. 디렉터리 경로(./data/results)와 이미지 파일 이름을 결합하여 완전한 파일 경로를 만듭니다.

  7. vutils.save_image(fake_grid, image_filepath, dpi=300): 이 줄은 생성된 이미지 그리드를 지정한 파일 경로에 저장합니다. dpi 인자는 저장된 이미지의 인치당 점의 수를 설정하며, 이를 통해 이미지의 해상도를 제어합니다.

  8. print(f"{epoch} 에폭의 생성된 이미지 저장 완료."): 이 줄은 현재 에폭의 생성된 이미지가 저장되었음을 콘솔에 출력합니다.

요약하자면, 이 코드 섹션은 훈련 중 생성자로부터 특정 간격마다 가짜 이미지를 생성하고 디스크에 저장하는 역할을 합니다. 이를 통해 훈련 과정 동안 생성자의 학습 진행 상황을 모니터링하고 시각화하는 데 도움이 됩니다.

10. Visualization and Saving Images(시각화 및 이미지 저장):

위 training loop 내부의 일부분

		if iters % 4000 == 0:
            # Save Losses for plotting later
            G_losses.append(errG.item())
            D_losses.append(errD.item())
        
        # Check how the generator is doing by saving G's output on fixed_noise
        if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(train_loader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
        
        iters += 1
    
    if epoch % 2000 == 0:
        # Save generated images at specified interval
        with torch.no_grad():
            fake = netG(fixed_noise).detach().cpu()
        fake_grid = vutils.make_grid(fake, padding=2, normalize=True)
        image_filename = f"generated_epoch_{epoch}.png"
        image_filepath = os.path.join('./data/results', image_filename)
        vutils.save_image(fake_grid, image_filepath, dpi=300)
        print(f"Generated images saved for epoch {epoch}.")

11. Plotting Loss Graphs(손실 그래프 그리기):

전체 코드 :

plt.figure(figsize=(10,5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses,label="G")
plt.plot(D_losses,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()
     

fig = plt.figure(figsize=(8,8))
plt.axis("off")
ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)

HTML(ani.to_jshtml())
     

# Grab a batch of real images from the dataloader
real_batch = next(iter(train_loader))

설명 :
이 코드 섹션은 훈련 중에 생성된 결과를 시각화하는 부분입니다. 아래 코드 블록을 한 단계씩 설명해드리겠습니다:

plt.figure(figsize=(10,5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses,label="G")
plt.plot(D_losses,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()
  1. plt.figure(figsize=(10,5)): 이 코드는 새로운 Matplotlib 그림(플롯)을 생성하는데, 그림의 크기를 10x5로 설정합니다. 이렇게 플롯의 크기를 조정하여 그래프가 더 잘 보이도록 할 수 있습니다.

  2. plt.title("Generator and Discriminator Loss During Training"): 플롯의 제목을 설정합니다. 여기서는 생성자와 판별자의 손실 변화를 나타내는 제목을 설정하고 있습니다.

  3. plt.plot(G_losses,label="G"): 생성자의 손실 변화를 그래프로 그립니다. G_losses 리스트에는 훈련 과정에서 생성자의 손실이 기록되어 있습니다. "G" 레이블은 이 그래프가 생성자 손실에 대한 것임을 나타냅니다.

  4. plt.plot(D_losses,label="D"): 판별자의 손실 변화를 그래프로 그립니다. D_losses 리스트에는 훈련 과정에서 판별자의 손실이 기록되어 있습니다. "D" 레이블은 이 그래프가 판별자 손실에 대한 것임을 나타냅니다.

  5. plt.xlabel("iterations"): x축의 레이블을 설정합니다. 여기서는 반복 횟수(iterations)를 나타냅니다.

  6. plt.ylabel("Loss"): y축의 레이블을 설정합니다. 여기서는 손실을 나타냅니다.

  7. plt.legend(): 범례를 추가합니다. 생성자와 판별자의 손실에 대한 범례가 그래프에 나타납니다.

  8. plt.show(): 플롯을 화면에 표시합니다.

fig = plt.figure(figsize=(8,8))
plt.axis("off")
ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)

HTML(ani.to_jshtml())
  1. fig = plt.figure(figsize=(8,8)): 새로운 그림(플롯) 객체를 생성하며, 그림의 크기를 8x8로 설정합니다.

  2. plt.axis("off"): 축을 비활성화합니다. 이렇게 하면 플롯 안에 축이 나타나지 않습니다.

  3. ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in img_list]: 이미지 리스트 img_list 안에 저장된 이미지들을 그래프로 그리기 위해 준비합니다. 각 이미지를 (1, 2, 0) 순서로 변환하여 시각화용으로 준비합니다.

  4. ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True): ims 리스트에 있는 이미지들을 애니메이션으로 묶어서 생성합니다. interval은 각 이미지 사이의 간격을 밀리초 단위로 설정하고, repeat_delay는 애니메이션의 반복 딜레이를 밀리초 단위로 설정합니다. blit=True는 빠른 애니메이션 표시를 위한 설정입니다.

  5. HTML(ani.to_jshtml()): 생성된 애니메이션을 HTML 형식으로 출력하여 Jupyter Notebook에서 애니메이션을 볼 수 있도록 합니다.

마지막으로,

real_batch = next(iter(train_loader))

이 코드는 데이터로더에서 실제 이미지 배치를 가져오는 부분입니다. 이것은 시각화를 위해 실제 이미지와 가짜 이미지를 나란히 비교하는 데 사용됩니다.

12. Displaying Side-by-Side Real and Fake Images(진짜와 가짜 이미지 나란히 표시):

전체 코드 :

# Plot the real images
plt.figure(figsize=(15,15))
plt.subplot(1,2,1)
plt.axis("off")
plt.title("Real Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=5, normalize=True).cpu(),(1,2,0)))

# Plot the fake images from the last epoch
plt.subplot(1,2,2)
plt.axis("off")
plt.title("Fake Images")
plt.imshow(np.transpose(img_list[-1],(1,2,0)))
plt.show()
plt.savefig(os.path.join(results_dir, 'side_by_side_images.png'), dpi=300)
plt.show()     


plt.figure(figsize=(10,5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses,label="G")
plt.plot(D_losses,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()

설명 :
이 코드 섹션은 실제 이미지와 훈련 중 생성된 이미지를 나란히 비교하고 손실 그래프를 그리는 역할을 합니다. 이 코드를 한 단계씩 자세히 설명해보겠습니다:

# Plot the real images
plt.figure(figsize=(15,15))
plt.subplot(1,2,1)
plt.axis("off")
plt.title("Real Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=5, normalize=True).cpu(),(1,2,0)))
  1. plt.figure(figsize=(15,15)): 새로운 Matplotlib 그림(플롯)을 생성하고, 그림의 크기를 15x15로 설정합니다.

  2. plt.subplot(1,2,1): 그림 영역을 나누고, 이 영역에서 첫 번째 부분을 지정합니다. 이 코드는 그림을 1x2 그리드로 나누고, 첫 번째 그리드 영역에 이미지를 그릴 것임을 나타냅니다.

  3. plt.axis("off"): 축을 비활성화합니다. 이렇게 하면 이미지 주위에 축이 나타나지 않습니다.

  4. plt.title("Real Images"): 그림의 제목을 "Real Images"로 설정합니다.

  5. plt.imshow(...): 이미지를 그림에 표시합니다. vutils.make_grid() 함수를 사용하여 실제 이미지 배치를 그리드 형식으로 만든 다음, .to(device)[:64]를 사용하여 GPU로 이동시키고 처음 64장의 이미지만 선택합니다. 그리고 .cpu()를 사용하여 이미지를 CPU로 이동시킨 후, np.transpose(...)를 사용하여 이미지의 차원을 조정합니다. 이렇게 편집된 이미지를 그림에 표시합니다.

# Plot the fake images from the last epoch
plt.subplot(1,2,2)
plt.axis("off")
plt.title("Fake Images")
plt.imshow(np.transpose(img_list[-1],(1,2,0)))
plt.show()
plt.savefig(os.path.join(results_dir, 'side_by_side_images.png'), dpi=300)
plt.show()
  1. plt.subplot(1,2,2): 그림 영역을 나누고, 이 영역에서 두 번째 부분을 지정합니다. 이 코드는 그림을 1x2 그리드로 나누고, 두 번째 그리드 영역에 이미지를 그릴 것임을 나타냅니다.

  2. plt.axis("off"): 축을 비활성화합니다. 이렇게 하면 이미지 주위에 축이 나타나지 않습니다.

  3. plt.title("Fake Images"): 그림의 제목을 "Fake Images"로 설정합니다.

  4. plt.imshow(...): img_list 리스트에서 마지막에 저장된 생성된 이미지 그리드를 그림에 표시합니다. 이미지의 차원을 조정하여 표시합니다.

  5. plt.show(): 플롯을 화면에 표시합니다.

  6. plt.savefig(os.path.join(results_dir, 'side_by_side_images.png'), dpi=300): 생성된 이미지를 파일로 저장합니다. os.path.join() 함수를 사용하여 결과 디렉터리 경로와 파일 이름을 결합합니다. dpi=300은 저장된 이미지의 해상도를 설정합니다.

plt.figure(figsize=(10,5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses,label="G")
plt.plot(D_losses,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
  1. plt.figure(figsize=(10,5)): 새로운 Matplotlib 그림(플롯)을 생성하고, 그림의 크기를 10x5로 설정합니다.

  2. plt.title("Generator and Discriminator Loss During Training"): 그림의 제목을 "Generator and Discriminator Loss During Training"으로 설정합니다.

  3. plt.plot(G_losses,label="G"): 생성자 손실 그래프를 그립니다. G_losses 리스트에 기록된 생성자 손실 값들을 기반으로 그래프를 그립니다. "G" 레이블은 그래프가 생성자 손실에 대한 것임을 나타냅니다.

  4. plt.plot(D_losses,label="D"): 판별자 손실 그래프를 그립니다. D_losses 리스트에 기록된 판별자 손실 값들을 기반으로 그래프를 그립니다. "D" 레이블은 그래프가 판별자 손실에 대한 것임을 나타냅니다.

  5. plt.xlabel("iterations"): x축의 레이블을 설정합니다. 이 경우 반복 횟수(iterations)를 나타냅니다.

  6. plt.ylabel("Loss"): y축의 레이블을 설정합니다. 이 경우 손실을 나타냅니다.

  7. plt.legend(): 범례를 추가합니다. 생성자와 판별자 손실에 대한 범례가 그래프에 나타납니다.

요약하면, 이 코드는 훈련 중에 생성된 이미지와 손실 그래프를 시각화하여 훈련 진행 상황을 쉽게 파악하고 분석할 수 있도록 도와주는 부분입니다.

13. Saving Figures(그림 저장):

전체 코드 :

plt.savefig(os.path.join(results_dir, 'loss_graph.png'), dpi=300)
plt.show()

설명 :
이 코드 섹션은 그래프를 이미지 파일로 저장하는 부분입니다. 아래에서 코드 블록을 한 단계씩 설명해드리겠습니다:

plt.savefig(os.path.join(results_dir, 'loss_graph.png'), dpi=300)
  1. plt.savefig(...): 현재 그림(플롯)을 이미지 파일로 저장하는 함수입니다.

  2. os.path.join(results_dir, 'loss_graph.png'): 이미지 파일의 경로와 이름을 설정합니다. results_dir 변수는 이미지를 저장할 디렉터리 경로를 나타내며, 'loss_graph.png'은 저장될 이미지 파일의 이름을 나타냅니다. os.path.join() 함수를 사용하여 디렉터리 경로와 파일 이름을 연결합니다.

  3. dpi=300: 저장된 이미지의 해상도를 설정합니다. dpi는 "dots per inch"의 약자로, 인치당 픽셀 수를 나타내는 값입니다. 높은 dpi 값은 이미지의 품질을 높여주지만 파일 크기도 증가시킬 수 있습니다.

plt.show()
  1. plt.show(): 플롯을 화면에 표시합니다. 이 때, 이미지 파일은 저장되지만 화면에는 그래프가 표시되지 않습니다.

요약하면, 위의 코드는 훈련 중에 생성된 그래프를 이미지 파일로 저장하는 역할을 합니다. plt.savefig() 함수를 사용하여 그래프를 이미지 파일로 저장하고, 저장된 이미지를 화면에 표시하지 않도록 plt.show() 함수를 사용합니다. 이렇게 하면 생성된 그래프를 나중에 쉽게 참조하거나 공유할 수 있습니다.

14. Training Time Calculation and Completion Message(훈련 시간 계산 및 완료 메시지):

# 종료 시간 기록
end_time = time.time()

# 소요 시간 계산
training_time = end_time - start_time

# 시, 분, 초로 변환
minutes = int(training_time // 60)
seconds = int(training_time % 60)
hours = int(minutes // 60)
minutes = int(minutes % 60)

# 결과 출력
print(f"훈련 소요 시간: {hours}시간 {minutes}{seconds}초")

print("끝!")

설명 :
이 코드 부분은 훈련이 끝난 후에 훈련에 소요된 시간을 계산하고 출력하는 부분입니다. 코드를 한 단계씩 자세히 설명해드리겠습니다:

end_time = time.time()
  1. end_time = time.time(): 현재 시간을 기록합니다. 이 코드는 훈련이 끝난 시간을 기록하기 위해 사용됩니다.
training_time = end_time - start_time
  1. training_time = end_time - start_time: 훈련에 소요된 전체 시간을 계산합니다. 훈련 시작 시간(start_time)에서 훈련 종료 시간(end_time)을 빼서 훈련에 걸린 시간을 계산합니다.
minutes = int(training_time // 60)
seconds = int(training_time % 60)
hours = int(minutes // 60)
minutes = int(minutes % 60)
  1. 시간, 분, 초로 변환: 훈련에 소요된 시간을 시간, 분, 초로 변환합니다. 나머지 연산과 정수 변환을 사용하여 시간, 분, 초를 계산합니다. 예를 들어, int(training_time // 60)은 전체 소요 시간을 분으로 변환하고 정수로 변환한 값을 나타냅니다.
print(f"훈련 소요 시간: {hours}시간 {minutes}{seconds}초")
  1. 결과 출력: 계산된 시간을 이용하여 "훈련 소요 시간: 시간 분 초" 형식으로 훈련에 소요된 시간을 출력합니다. f-string을 사용하여 변수 값을 문자열에 삽입합니다.
print("끝!")
  1. "끝!" 출력: 훈련이 완료되었음을 나타내기 위해 "끝!"이라는 문자열을 출력합니다.

요약하면, 이 코드는 훈련이 종료된 후에 전체 훈련 소요 시간을 계산하고 이를 시, 분, 초 형식으로 변환하여 출력합니다. 이렇게 하면 훈련 시간을 쉽게 확인할 수 있습니다.

profile
moved to tistory. ( linked w/ the home btn below. )

0개의 댓글