Super Kawaii Cute Cat Kaoani PyTorch로 AutoEncoder 구현하기

연구/PyTorch

PyTorch로 AutoEncoder 구현하기

치킨고양이짱아 2023. 8. 16. 17:16
728x90
728x90

1. 개념요약

AutoEncoder는 앞부분을 Encoder, 뒷부분을 Decoder라고 부른다.

Encoder는 정보를 받아서 이를 압축하고 Decoder는 압축된 정보를 복원하는 역할을 한다. AutoEncoder를 사용하면 필연적으로 정보의 손실이 일어나지만, 이는 불필요한 정보를 줄이고 필요한 정보만 남기는 데이터 가공이라고 볼 수 있다.

이렇게 불필요한 정보를 줄이는 AutoEncoder를 사용하면 복잡한 데이터의 차원을 줄일 수 있다. 따라서 AutoEncoder는 Input data의 feature를 추출할 때 많이 사용된다.

2. AutoEncoder 구현

import torch
import torchvision
import torch.nn.functional as F
from torch import nn, optim
from torchvision import transforms, datasets

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D #생성되는 이미지를 관찰하기 위함입니다. 3차원 플롯을 그리는 용도입니다.
from matplotlib import cm # 데이터포인트에 색상을 입히는 것에 사용됩니다.
import numpy as np

필요한 패키지는 위와 같다.

# 하이퍼파라미터 준비
EPOCH = 10
BATCH_SIZE = 64
USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")
print("Using Device:", DEVICE)
# Fashion MNIST 데이터셋 불러오기 (학습데이터만 사용)
trainset = datasets.FashionMNIST(
    root      = './.data/', 
    train     = True,
    download  = True,
    transform = transforms.ToTensor()
)
train_loader = torch.utils.data.DataLoader(
    dataset     = trainset,
    batch_size  = BATCH_SIZE,
    shuffle     = True,
    num_workers = 2
)

데이터셋을 위와 같이 준비해주면 된다.

# 오토인코더 모듈 정의
class Autoencoder(nn.Module):
    def __init__(self):
        super(Autoencoder, self).__init__()

        #인코더는 간단한 신경망으로 분류모델처럼 생겼습니다.
        self.encoder = nn.Sequential( # nn.Sequential을 사용해 encoder와 decoder 두 모듈로 묶어줍니다.
            nn.Linear(28*28, 128), #차원을 28*28에서 점차 줄여나갑니다.
            nn.ReLU(),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Linear(64, 12),
            nn.ReLU(),
            nn.Linear(12, 3),   # 입력의 특징을 3차원으로 압축합니다 (출력값이 바로 잠재변수가 됩니다.)
        )
        self.decoder = nn.Sequential(
            nn.Linear(3, 12), #디코더는 차원을 점차 28*28로 복원합니다.
            nn.ReLU(),
            nn.Linear(12, 64),
            nn.ReLU(),
            nn.Linear(64, 128),
            nn.ReLU(),
            nn.Linear(128, 28*28),
            nn.Sigmoid(),       # 픽셀당 0과 1 사이로 값을 출력하는 sigmoid()함수를 추가합니다.
        )

    def forward(self, x):
        encoded = self.encoder(x) # encoder는 encoded라는 잠재변수를 만들고
        decoded = self.decoder(encoded) # decoder를 통해 decoded라는 복원이미지를 만듭니다.
        return encoded, decoded

Encoder는 입력 데이터(28*28 차원)을 받아 점차 차원을 줄여나가 3차원의 latent variable로 압축시킨다. 그리고 Decoder는 이 latent variable을 받아 차원을 점차 늘려나가며 입력 데이터와 차원이 같은 데이터로 복원을 시킨다.

autoencoder = Autoencoder().to(DEVICE)
optimizer = torch.optim.Adam(autoencoder.parameters(), lr=0.005) 
# Adam()을 최적화함수로 사용합니다. Adam은 SGD의 변형함수이며 학습중인 기울기를 참고하여 학습 속도를 자동으로 변화시킵니다.
criterion = nn.MSELoss() #원본값과 디코더에서 나온 값의 차이를 계산하기 위해 평균제곱오차(Mean Squared Loss) 오차함수를 사용합니다.

Adam optimizer를 사용하였고, loss function으로는 MSELoss를 사용해주었다.

# 학습하기 위한 함수
def train(autoencoder, train_loader):
    autoencoder.train()
    for step, (x, label) in enumerate(train_loader):
        x = x.view(-1, 28*28).to(DEVICE)
        y = x.view(-1, 28*28).to(DEVICE) #x(입력)와 y(대상 레이블)모두 원본이미지(x)인 것을 주의해야 합니다.
        label = label.to(DEVICE)

        encoded, decoded = autoencoder(x)

        loss = criterion(decoded, y) # decoded와 원본이미지(y) 사이의 평균제곱오차를 구합니다
        optimizer.zero_grad() #기울기에 대한 정보를 초기화합니다.
        loss.backward() # 기울기를 구합니다.
        optimizer.step() #최적화를 진행합니다.

학습하기 위한 train 함수는 위와 같이 설정해주었다. 사진 데이터를 autoencoder에 Input 형태로 변형을 시켜 autoencoder에 넣어주며, Input과 label이 모두 원본이미지이다.

#학습하기
for epoch in range(1, EPOCH+1):
    train(autoencoder, train_loader)

    # 디코더에서 나온 이미지를 시각화 하기
    # 앞서 시각화를 위해 남겨둔 5개의 이미지를 한 이폭만큼 학습을 마친 모델에 넣어 복원이미지를 만듭니다.
    test_x = view_data.to(DEVICE)
    _, decoded_data = autoencoder(test_x)

    # 원본과 디코딩 결과 비교해보기
    f, a = plt.subplots(2, 5, figsize=(5, 2))
    print("[Epoch {}]".format(epoch))
    for i in range(5):
        img = np.reshape(view_data.data.numpy()[i],(28, 28)) #파이토치 텐서를 넘파이로 변환합니다.
        a[0][i].imshow(img, cmap='gray')
        a[0][i].set_xticks(()); a[0][i].set_yticks(())

    for i in range(5):
        img = np.reshape(decoded_data.to("cpu").data.numpy()[i], (28, 28)) 
        # CUDA를 사용하면 모델 출력값이 GPU에 남아있으므로 .to("cpu") 함수로 일반메모리로 가져와 numpy행렬로 변환합니다.
        # cpu를 사용할때에도 같은 코드를 사용해도 무방합니다.
        a[1][i].imshow(img, cmap='gray')
        a[1][i].set_xticks(()); a[1][i].set_yticks(())
    plt.show()

위와같이 학습을 시킬 수 있다.

[Epoch 1의 결과]

[Epoch 2의 결과]

[Epoch 3의 결과]

[Epoch 4의 결과]

[Epoch 5의 결과]

[Epoch 10의 결과]

데이터의 손실이 일어났지만, 어느정도 원본 데이터를 잘 복원해내는 모습을 확인할 수 있다.

이렇게 잘 학습을 시키면 AutoEncoder가 만들어내는 latent variable 값을 사용하고 싶을텐데, AutoEncoder의 forward 함수가 encoded value와 decoded value를 모두 return 하므로, return하는 tuple에서 0번째 index 값을 사용하면 된다.

 

참고문헌

https://dacon.io/codeshare/4551

728x90
728x90