A A
[NLP] AutoEncoder (μ˜€ν† μΈμ½”λ”)
NLPλ₯Ό μ˜€λžœλ§Œμ— 곡뢀해보닀 AutoEncoder에 λŒ€ν•œ λ‚΄μš©μ΄ μ—†λŠ”κ±° κ°™μ•„ ν•œλ²ˆ μ„€λͺ…ν•΄ λ³΄κ² μŠ΅λ‹ˆλ‹€.

μ˜€ν† μΈμ½”λ”(Autoencoder)λž€?

AutoEncoder(μ˜€ν† μΈμ½”λ”)λŠ” 인곡신경망을 기반으둜 ν•œ 비지도 ν•™μŠ΅ λͺ¨λΈλ‘œ, 주어진 λ°μ΄ν„°μ˜ 효율적인 ν‘œν˜„μ„ ν•™μŠ΅ν•˜λŠ” 데 쀑점을 λ‘‘λ‹ˆλ‹€. μ΄λŠ” 데이터λ₯Ό μ••μΆ•ν•˜κ³  차원을 μΆ•μ†Œν•˜κ±°λ‚˜, λ…Έμ΄μ¦ˆλ₯Ό μ œκ±°ν•˜κ³  이상 탐지와 같은 λ‹€μ–‘ν•œ μ‘μš© 뢄야에 μ‚¬μš©λ©λ‹ˆλ‹€.

 

μ˜€ν† μΈμ½”λ”(Autoencoder)의 μž‘λ™ 원리

μ˜€ν† μΈμ½”λ”λŠ” μž…λ ₯ 데이터λ₯Ό μ••μΆ•ν•˜μ—¬ 잠재 곡간(latent space)μ΄λΌλŠ” 저차원 ν‘œν˜„μœΌλ‘œ λ³€ν™˜ν•œ λ‹€μŒ, 이λ₯Ό λ‹€μ‹œ μ›λž˜μ˜ λ°μ΄ν„°λ‘œ λ³΅μ›ν•˜λŠ” 과정을 톡해 ν•™μŠ΅ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 과정은 주둜 λ‹€μŒ 두 가지 μ£Όμš” ꡬ성 μš”μ†Œλ‘œ μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

https://towardsdatascience.com/applied-deep-learning-part-3-autoencoders-1c083af4d798

  1. 인코더(Encoder): μž…λ ₯ 데이터λ₯Ό 저차원 잠재 곡간 λ²‘ν„°λ‘œ λ³€ν™˜ν•˜λŠ” κ³Όμ •μž…λ‹ˆλ‹€. μΈμ½”λ”λŠ” μž…λ ₯μΈ΅μ—μ„œ μ‹œμž‘ν•˜μ—¬ μ—¬λŸ¬ 개의 은닉측을 거쳐, μ΅œμ’…μ μœΌλ‘œ 잠재 곡간 벑터λ₯Ό 좜λ ₯ν•©λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ μž…λ ₯ λ°μ΄ν„°μ˜ μ€‘μš”ν•œ νŠΉμ§•μ„ μ €μ°¨μ›μœΌλ‘œ μ••μΆ•ν•˜κ²Œ λ©λ‹ˆλ‹€.
  2. 디코더(Decoder): μΈμ½”λ”μ—μ„œ μƒμ„±λœ 저차원 잠재 곡간 벑터λ₯Ό λ‹€μ‹œ μ›λž˜μ˜ λ°μ΄ν„°λ‘œ λ³΅μ›ν•˜λŠ” κ³Όμ •μž…λ‹ˆλ‹€. λ””μ½”λ”λŠ” 잠재 곡간 ν‘œν˜„μ—μ„œ μ‹œμž‘ν•˜μ—¬ μ—¬λŸ¬ 측의 신경망을 톡해 좜λ ₯μΈ΅κΉŒμ§€ λ„λ‹¬ν•©λ‹ˆλ‹€. λ””μ½”λ”μ˜ λͺ©ν‘œλŠ” μž…λ ₯ 데이터와 μ΅œλŒ€ν•œ λΉ„μŠ·ν•œ 좜λ ₯을 μƒμ„±ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

μ˜€ν† μΈμ½”λ”(Autoencoder)의 μœ ν˜•

μ˜€ν† μΈμ½”λ”λŠ” κ·Έ ꡬ쑰와 λͺ©μ μ— 따라 λ‹€μ–‘ν•œ μœ ν˜•μœΌλ‘œ λ‚˜λ‰©λ‹ˆλ‹€.
  1. κΈ°λ³Έ μ˜€ν† μΈμ½”λ” (Basic Autoencoder): κ°€μž₯ 기본적인 ν˜•νƒœλ‘œ, ν•˜λ‚˜μ˜ 인코더와 ν•˜λ‚˜μ˜ λ””μ½”λ”λ‘œ κ΅¬μ„±λ©λ‹ˆλ‹€. 주둜 κ°„λ‹¨ν•œ 데이터 ν‘œν˜„ ν•™μŠ΅μ— μ‚¬μš©λ©λ‹ˆλ‹€.
  2. λ”₯ μ˜€ν† μΈμ½”λ” (Deep Autoencoder): μ—¬λŸ¬ 개의 은닉측을 κ°€μ§€λŠ” 심측 신경망 ꡬ쑰둜, λ³΅μž‘ν•œ λ°μ΄ν„°μ˜ νŠΉμ§•μ„ 더 깊이 ν•™μŠ΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” λ°μ΄ν„°μ˜ 더 μ •κ΅ν•œ ν‘œν˜„μ„ κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.
  3. ν¬μ†Œ μ˜€ν† μΈμ½”λ” (Sparse Autoencoder): 잠재 곡간 λ²‘ν„°μ˜ μš”μ†Œ 쀑 일뢀가 0이 λ˜λ„λ‘ μ œμ•½μ„ κ°€ν•˜μ—¬, λ°μ΄ν„°μ˜ μ€‘μš”ν•œ νŠΉμ§•μ„ 더 잘 ν•™μŠ΅ν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€. μ΄λŠ” 고차원 λ°μ΄ν„°μ—μ„œ μ€‘μš”ν•œ νŠΉμ§•λ§Œμ„ μΆ”μΆœν•˜λŠ” 데 μœ μš©ν•©λ‹ˆλ‹€.
  4. λ³€μ΄ν˜• μ˜€ν† μΈμ½”λ” (Variational Autoencoder, VAE): 잠재 곡간을 ν™•λ₯  λΆ„ν¬λ‘œ λͺ¨λΈλ§ν•˜μ—¬, λ°μ΄ν„°μ˜ 잠재 ꡬ쑰λ₯Ό 더 잘 νŒŒμ•…ν•  수 있게 ν•©λ‹ˆλ‹€. μ΄λŠ” μƒˆλ‘œμš΄ 데이터λ₯Ό μƒμ„±ν•˜λŠ” 데에도 μ‚¬μš©λ˜λ©°, 생성 λͺ¨λΈλ‘œλ„ 많이 ν™œμš©λ©λ‹ˆλ‹€.
  5. 작음 제거 μ˜€ν† μΈμ½”λ” (Denoising Autoencoder): μž…λ ₯ 데이터에 λ…Έμ΄μ¦ˆλ₯Ό μΆ”κ°€ν•œ ν›„, 이λ₯Ό μ›λž˜μ˜ κΉ¨λ—ν•œ λ°μ΄ν„°λ‘œ λ³΅μ›ν•˜λŠ” 방법을 ν•™μŠ΅ν•©λ‹ˆλ‹€. 이λ₯Ό 톡해 λ°μ΄ν„°μ˜ λ…Έμ΄μ¦ˆλ₯Ό μ œκ±°ν•˜κ³  더 κ°•μΈν•œ νŠΉμ§•μ„ ν•™μŠ΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜€ν† μΈμ½”λ”(Autoencoder)의 μ‘μš©λΆ„μ•Ό

μ˜€ν† μΈμ½”λ”λŠ” λ‹€μ–‘ν•œ λΆ„μ•Όμ—μ„œ ν™œμš©λ©λ‹ˆλ‹€:
  • 데이터 μ••μΆ• (Data Compression): μž…λ ₯ 데이터λ₯Ό 저차원 λ²‘ν„°λ‘œ μ••μΆ•ν•˜μ—¬ μ €μž₯ 곡간을 μ ˆμ•½ν•˜κ±°λ‚˜ 전솑 μ‹œκ°„μ„ 단좕할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 차원 μΆ•μ†Œ (Dimensionality Reduction): 고차원 데이터λ₯Ό μ €μ°¨μ›μœΌλ‘œ μΆ•μ†Œν•˜μ—¬ μ‹œκ°ν™”ν•˜κ±°λ‚˜, λ‹€λ₯Έ λ¨Έμ‹ λŸ¬λ‹ μ•Œκ³ λ¦¬μ¦˜μ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ…Έμ΄μ¦ˆ 제거 (Noise Reduction): 작음 제거 μ˜€ν† μΈμ½”λ”λ₯Ό μ‚¬μš©ν•΄ λ°μ΄ν„°μ˜ λ…Έμ΄μ¦ˆλ₯Ό μ œκ±°ν•˜κ³  κΉ¨λ—ν•œ 데이터λ₯Ό 볡원할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 이상 탐지 (Anomaly Detection): 정상 λ°μ΄ν„°λ‘œ ν•™μŠ΅ν•œ μ˜€ν† μΈμ½”λ”κ°€ 이상 데이터λ₯Ό λ³΅μ›ν•˜μ§€ λͺ»ν•˜λŠ” νŠΉμ„±μ„ μ΄μš©ν•΄, 비정상 데이터λ₯Ό 탐지할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 생성 λͺ¨λΈ (Generative Model): λ³€μ΄ν˜• μ˜€ν† μΈμ½”λ”λ₯Ό μ‚¬μš©ν•΄ μƒˆλ‘œμš΄ 데이터λ₯Ό 생성할 수 있으며, μ΄λŠ” 이미지 생성, 데이터 증강 등에 ν™œμš©λ©λ‹ˆλ‹€.

μ˜€ν† μΈμ½”λ”(Autoencoder)의 μž₯, 단점

μž₯점

  • 비지도 ν•™μŠ΅: λ ˆμ΄λΈ”μ΄ μ—†λŠ” λ°μ΄ν„°λ‘œ ν•™μŠ΅μ΄ κ°€λŠ₯ν•˜μ—¬ λ‹€μ–‘ν•œ μƒν™©μ—μ„œ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ‹€μ–‘ν•œ μ‘μš© λΆ„μ•Ό: 데이터 μ••μΆ•, λ…Έμ΄μ¦ˆ 제거, 이상 탐지 λ“± μ—¬λŸ¬ μ‘μš© 뢄야에 μ‚¬μš©λ  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ°μ΄ν„°μ˜ μ€‘μš”ν•œ νŠΉμ§• νŒŒμ•…: ν•™μŠ΅λœ 잠재 곡간 벑터λ₯Ό λΆ„μ„ν•˜μ—¬ λ°μ΄ν„°μ˜ μ€‘μš”ν•œ νŠΉμ§•μ„ 이해할 수 μžˆμŠ΅λ‹ˆλ‹€.

단점

  • 볡원 정확도: λ³΅μ›λœ 데이터가 원본 데이터와 μ™„μ „νžˆ μΌμΉ˜ν•˜μ§€ μ•Šμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ³΅μž‘ν•œ 데이터 처리 ν•œκ³„: 맀우 λ³΅μž‘ν•œ 데이터에 λŒ€ν•΄μ„œλŠ” μ„±λŠ₯이 μ œν•œλ  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 과적합 μœ„ν—˜: μ μ ˆν•œ μ •κ·œν™”κ°€ 없을 경우 과적합이 λ°œμƒν•  수 있으며, μ΄λŠ” μΌλ°˜ν™” μ„±λŠ₯을 μ €ν•˜μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜€ν† μΈμ½”λ”(Autoencoder) Example Code

MNIST Dataset을 μ˜ˆμ‹œ & PyTorch Frameworkλ₯Ό μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
import torch  # PyTorch 라이브러리 μž„ν¬νŠΈ
import torch.nn as nn  # 신경망 λͺ¨λ“ˆ μž„ν¬νŠΈ
import torch.optim as optim  # μ΅œμ ν™” μ•Œκ³ λ¦¬μ¦˜ λͺ¨λ“ˆ μž„ν¬νŠΈ
from torchvision import datasets, transforms  # 데이터셋과 데이터 μ „μ²˜λ¦¬ λ³€ν™˜ λͺ¨λ“ˆ μž„ν¬νŠΈ
import matplotlib.pyplot as plt  # 데이터 μ‹œκ°ν™”λ₯Ό μœ„ν•œ matplotlib μž„ν¬νŠΈ
# 데이터셋 λ‘œλ“œ 및 μ „μ²˜λ¦¬
transform = transforms.Compose([
    transforms.ToTensor(),  # 데이터λ₯Ό ν…μ„œλ‘œ λ³€ν™˜ (이미지λ₯Ό PyTorch ν…μ„œλ‘œ λ³€ν™˜)
    transforms.Normalize((0.5,), (0.5,))  # 데이터λ₯Ό μ •κ·œν™” (평균 0.5, ν‘œμ€€νŽΈμ°¨ 0.5둜 μ •κ·œν™”)
])
# MNIST ν›ˆλ ¨ 데이터셋 λ‘œλ“œ (손글씨 숫자 데이터)
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
# ν›ˆλ ¨ 데이터 λ‘œλ” μ •μ˜ (데이터셋을 배치 크기 64둜, λ¬΄μž‘μœ„λ‘œ μ„žμ–΄μ„œ λ‘œλ“œ)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)

# MNIST ν…ŒμŠ€νŠΈ 데이터셋 λ‘œλ“œ
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
# ν…ŒμŠ€νŠΈ 데이터 λ‘œλ” μ •μ˜ (데이터셋을 배치 크기 64둜, 순차적으둜 λ‘œλ“œ)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to ./data/MNIST/raw/train-images-idx3-ubyte.gz
100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 9912422/9912422 [00:11<00:00, 899702.19it/s] 
Extracting ./data/MNIST/raw/train-images-idx3-ubyte.gz to ./data/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz to ./data/MNIST/raw/train-labels-idx1-ubyte.gz
100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 28881/28881 [00:00<00:00, 129719.63it/s]
Extracting ./data/MNIST/raw/train-labels-idx1-ubyte.gz to ./data/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw/t10k-images-idx3-ubyte.gz
100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 1648877/1648877 [00:01<00:00, 1244815.31it/s]
Extracting ./data/MNIST/raw/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz
100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 4542/4542 [00:00<00:00, 8819689.24it/s]Extracting ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# μ˜€ν† μΈμ½”λ”(Autoencoder) 클래슀 μ •μ˜, nn.Module을 μƒμ†λ°›μŒ
class Autoencoder(nn.Module):
    def __init__(self):
        super(Autoencoder, self).__init__()  # nn.Module의 μ΄ˆκΈ°ν™” ν•¨μˆ˜ 호좜

        # 인코더 μ •μ˜: μž…λ ₯ 이미지λ₯Ό 저차원 잠재 곡간 λ²‘ν„°λ‘œ μ••μΆ•
        self.encoder = nn.Sequential(
            nn.Linear(28 * 28, 128),  # μž…λ ₯ 이미지 크기 28x28을 128μ°¨μ›μœΌλ‘œ λ³€ν™˜
            nn.ReLU(),  # ν™œμ„±ν™” ν•¨μˆ˜λ‘œ ReLU μ‚¬μš©
            nn.Linear(128, 64),  # 128μ°¨μ›μ—μ„œ 64μ°¨μ›μœΌλ‘œ μΆ•μ†Œ
            nn.ReLU(),  # ν™œμ„±ν™” ν•¨μˆ˜λ‘œ ReLU μ‚¬μš©
            nn.Linear(64, 32)  # 64μ°¨μ›μ—μ„œ 32μ°¨μ›μœΌλ‘œ μΆ•μ†Œ
        )

        # 디코더 μ •μ˜: 잠재 곡간 벑터λ₯Ό μ›λž˜ 이미지 크기둜 볡원
        self.decoder = nn.Sequential(
            nn.Linear(32, 64),  # 32μ°¨μ›μ—μ„œ 64μ°¨μ›μœΌλ‘œ ν™•μž₯
            nn.ReLU(),  # ν™œμ„±ν™” ν•¨μˆ˜λ‘œ ReLU μ‚¬μš©
            nn.Linear(64, 128),  # 64μ°¨μ›μ—μ„œ 128μ°¨μ›μœΌλ‘œ ν™•μž₯
            nn.ReLU(),  # ν™œμ„±ν™” ν•¨μˆ˜λ‘œ ReLU μ‚¬μš©
            nn.Linear(128, 28 * 28),  # 128μ°¨μ›μ—μ„œ 28x28 크기의 μ›λž˜ μ΄λ―Έμ§€λ‘œ 볡원
            nn.Tanh()  # 좜λ ₯ 값을 -1κ³Ό 1 μ‚¬μ΄λ‘œ μ •κ·œν™”
        )

    def forward(self, x):
        # μˆœμ „νŒŒ ν•¨μˆ˜ μ •μ˜: 인코더 -> λ…Έμ΄μ¦ˆ μΆ”κ°€ -> 디코더
        x = self.encoder(self.noise(x))  # μž…λ ₯ 데이터에 λ…Έμ΄μ¦ˆλ₯Ό μΆ”κ°€ν•œ ν›„ 인코더 톡과
        x = self.decoder(x)  # 디코더λ₯Ό 톡해 μ›λž˜ 이미지 크기둜 볡원
        return x  # λ³΅μ›λœ 이미지 λ°˜ν™˜

    def noise(self, x, noise_factor=0.5):
        # λ…Έμ΄μ¦ˆ μΆ”κ°€ ν•¨μˆ˜: μž…λ ₯ 데이터에 λ…Έμ΄μ¦ˆλ₯Ό μΆ”κ°€ν•˜μ—¬ 더 κ°•μΈν•œ ν•™μŠ΅μ„ μœ λ„
        noise = noise_factor * torch.randn_like(x)  # μž…λ ₯ 데이터와 같은 크기의 랜덀 λ…Έμ΄μ¦ˆ 생성
        x_noisy = x + noise  # 원본 데이터에 λ…Έμ΄μ¦ˆ μΆ”κ°€
        return x_noisy  # λ…Έμ΄μ¦ˆκ°€ μΆ”κ°€λœ 데이터 λ°˜ν™˜
# λͺ¨λΈ μ΄ˆκΈ°ν™”
model = Autoencoder().to(device)  # Autoencoder λͺ¨λΈ μΈμŠ€ν„΄μŠ€ 생성 ν›„ GPU/CPU둜 이동
criterion = nn.MSELoss()  # 손싀 ν•¨μˆ˜λ‘œ 평균 제곱 였차(MSE) μ‚¬μš©
optimizer = optim.Adam(model.parameters(), lr=0.001)  # μ˜΅ν‹°λ§ˆμ΄μ €λ‘œ Adam μ‚¬μš©, ν•™μŠ΅λ₯  0.001둜 μ„€μ •
print(model)
Autoencoder(
  (encoder): Sequential(
    (0): Linear(in_features=784, out_features=128, bias=True)
    (1): ReLU()
    (2): Linear(in_features=128, out_features=64, bias=True)
    (3): ReLU()
    (4): Linear(in_features=64, out_features=32, bias=True)
  )
  (decoder): Sequential(
    (0): Linear(in_features=32, out_features=64, bias=True)
    (1): ReLU()
    (2): Linear(in_features=64, out_features=128, bias=True)
    (3): ReLU()
    (4): Linear(in_features=128, out_features=784, bias=True)
    (5): Tanh()
  )
)
num_epochs = 20  # 총 ν•™μŠ΅ 에폭 수 μ„€μ •

# ν•™μŠ΅ 루프 μ‹œμž‘
for epoch in range(num_epochs):
    for data, _ in train_loader:  # ν›ˆλ ¨ λ°μ΄ν„°μ…‹μ—μ„œ 배치 λ‹¨μœ„λ‘œ 데이터λ₯Ό κ°€μ Έμ˜΄
        data = data.view(-1, 28 * 28).to(device)  # μž…λ ₯ 데이터λ₯Ό 28x28 크기의 μ΄λ―Έμ§€μ—μ„œ 1차원 λ²‘ν„°λ‘œ λ³€ν™˜ν•˜κ³ , GPU/CPU둜 이동

        # μˆœμ „νŒŒ: μž…λ ₯ 데이터λ₯Ό λͺ¨λΈμ— ν†΅κ³Όμ‹œμΌœ 좜λ ₯ 생성
        output = model(data)
        loss = criterion(output, data)  # 좜λ ₯κ³Ό 원본 데이터 κ°„μ˜ 손싀(였차)을 계산 (MSE μ‚¬μš©)

        # μ—­μ „νŒŒ: 손싀을 κΈ°μ€€μœΌλ‘œ λͺ¨λΈμ˜ κ°€μ€‘μΉ˜λ₯Ό μ—…λ°μ΄νŠΈ
        optimizer.zero_grad()  # 이전 배치의 경사도λ₯Ό 0으둜 μ΄ˆκΈ°ν™”
        loss.backward()  # 손싀에 λŒ€ν•œ 경사도 계산 (μ—­μ „νŒŒ)
        optimizer.step()  # κ³„μ‚°λœ 경사도λ₯Ό λ°”νƒ•μœΌλ‘œ λͺ¨λΈμ˜ κ°€μ€‘μΉ˜λ₯Ό μ—…λ°μ΄νŠΈ

    # 각 에폭이 끝날 λ•Œλ§ˆλ‹€ ν˜„μž¬ 에폭과 손싀 값을 좜λ ₯
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

 

 

# ν…ŒμŠ€νŠΈ λ°μ΄ν„°μ…‹μ—μ„œ λͺ‡ 가지 이미지λ₯Ό λ³΅μ›ν•˜μ—¬ μ‹œκ°ν™”
model.eval()  # λͺ¨λΈμ„ 평가 λͺ¨λ“œλ‘œ μ „ν™˜ (λ“œλ‘­μ•„μ›ƒ, 배치 μ •κ·œν™” 등이 λΉ„ν™œμ„±ν™”λ¨)
with torch.no_grad():  # μ—­μ „νŒŒλ₯Ό μΆ”μ ν•˜μ§€ μ•Šλ„λ‘ no_grad() 블둝 μ‚¬μš©
    for data, _ in test_loader:  # ν…ŒμŠ€νŠΈ λ°μ΄ν„°μ…‹μ—μ„œ 배치 λ‹¨μœ„λ‘œ 데이터λ₯Ό λ‘œλ“œ
        data = data.view(-1, 28 * 28).to(device)  # 이미지λ₯Ό 1차원 λ²‘ν„°λ‘œ λ³€ν™˜ν•˜κ³ , GPU/CPU둜 이동
        output = model(data)  # λͺ¨λΈμ„ 톡해 μž…λ ₯ 데이터λ₯Ό 볡원
        output = output.view(-1, 1, 28, 28).cpu()  # λ³΅μ›λœ 데이터λ₯Ό λ‹€μ‹œ 28x28 크기의 μ΄λ―Έμ§€λ‘œ λ³€ν™˜ν•˜κ³  CPU둜 이동
        break  # 첫 번째 배치만 μ‚¬μš©ν•˜μ—¬ 볡원 κ²°κ³Ό 확인

# 원본 이미지와 λ³΅μ›λœ 이미지 μ‹œκ°ν™”
fig, axes = plt.subplots(nrows=2, ncols=10, sharex=True, sharey=True, figsize=(20, 4))  # 2x10 κ·Έλ¦¬λ“œμ˜ μ„œλΈŒν”Œλ‘― 생성

# 첫 번째 행에 원본 이미지, 두 번째 행에 λ³΅μ›λœ 이미지 μ‹œκ°ν™”
for images, row in zip([data.view(-1, 1, 28, 28).cpu(), output], axes):  # 원본과 λ³΅μ›λœ 이미지λ₯Ό 각각 행에 ν• λ‹Ή
    for img, ax in zip(images, row):  # 각 이미지와 μΆ•(axis)을 순차적으둜 κ°€μ Έμ˜΄
        ax.imshow(img.numpy().squeeze(), cmap='gray')  # 이미지λ₯Ό κ·Έλ ˆμ΄μŠ€μΌ€μΌλ‘œ μ‹œκ°ν™”
        ax.get_xaxis().set_visible(False)  # xμΆ• μˆ¨κΉ€
        ax.get_yaxis().set_visible(False)  # yμΆ• μˆ¨κΉ€

plt.show()  # μ‹œκ°ν™”λœ κ²°κ³Όλ₯Ό 화면에 좜λ ₯
Epoch [1/20], Loss: 0.1157
Epoch [2/20], Loss: 0.0861
Epoch [3/20], Loss: 0.0710
Epoch [4/20], Loss: 0.0640
Epoch [5/20], Loss: 0.0600
Epoch [6/20], Loss: 0.0513
Epoch [7/20], Loss: 0.0536
Epoch [8/20], Loss: 0.0501
Epoch [9/20], Loss: 0.0538
Epoch [10/20], Loss: 0.0533
Epoch [11/20], Loss: 0.0480
Epoch [12/20], Loss: 0.0542
Epoch [13/20], Loss: 0.0482
Epoch [14/20], Loss: 0.0386
Epoch [15/20], Loss: 0.0453
Epoch [16/20], Loss: 0.0455
Epoch [17/20], Loss: 0.0428
Epoch [18/20], Loss: 0.0442
Epoch [19/20], Loss: 0.0436
Epoch [20/20], Loss: 0.0414
# ν…ŒμŠ€νŠΈ λ°μ΄ν„°μ…‹μ—μ„œ λͺ‡ 가지 이미지λ₯Ό λ³΅μ›ν•˜μ—¬ μ‹œκ°ν™”
model.eval()
with torch.no_grad():
    for data, _ in test_loader:
        data = data.view(-1, 28 * 28).to(device)
        output = model(data)
        output = output.view(-1, 1, 28, 28).cpu()
        break

# 원본 이미지와 λ³΅μ›λœ 이미지 μ‹œκ°ν™”
fig, axes = plt.subplots(nrows=2, ncols=10, sharex=True, sharey=True, figsize=(20, 4))

for images, row in zip([data.view(-1, 1, 28, 28).cpu(), output], axes):
    for img, ax in zip(images, row):
        ax.imshow(img.numpy().squeeze(), cmap='gray')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

plt.show()