Junior KAIST · AI 2차시

End-to-end 가짜 이미지 탐지 모델 학습

ResNet-18을 직접 학습시키는 11단계 파이프라인 완전 해설

📖 사용법: 각 섹션마다 "✏️ 메모 추가" 로 공부 내용을 기록할 수 있고, 우측 하단 🤖 버튼으로 Claude AI에게 막히는 부분을 바로 물어볼 수 있어요. (질문 내용은 자동으로 클립보드에 복사되어 claude.ai 에서 Cmd+V / Ctrl+V로 붙여넣을 수 있습니다.)

전체 흐름 — 11단계 End-to-end 파이프라인

1
설치
2
import
3
경로 설정
4
Dataset
5
전처리
6
train/test
7
ResNet-18
8
손실/최적화
9
train_model
10
evaluate
11
실행 & 평가
1

라이브러리 설치

셀 1 — torch · torchvision · matplotlib · tqdm · scikit-learn · gdown
왜 필요할까?
Google Colab의 기본 환경에 PyTorch(=torch) 는 이미 깔려 있지만, 버전이 구형이거나 일부 라이브러리가 없을 수 있어서 명시적으로 설치해요. gdown 은 Google Drive에서 데이터셋을 받기 위한 도구입니다.
셀 1 — Colab 전용
!pip install torch torchvision matplotlib tqdm scikit-learn gdown
🧰
비유 — 공구 상자

새 집에 이사하면 먼저 드라이버·망치부터 챙기죠. Colab에서 첫 실행 셀은 "이번 프로젝트에 필요한 공구를 모으는 단계"입니다.

2

라이브러리 불러오기

셀 2 — 딥러닝 + 이미지 처리 도구 한 번에 import
셀 2
import os                                    # 폴더와 파일을 다루기 위한 라이브러리
import random                                # 랜덤하게 데이터를 섞기 위한 라이브러리
import numpy as np                           # 수학 계산용 라이브러리
from PIL import Image                        # 이미지를 다루기 위한 라이브러리
import matplotlib.pyplot as plt              # 그래프와 이미지를 시각화
from tqdm import tqdm                        # 진행 상태를 보여주는 라이브러리

import torch                                 # 딥러닝의 핵심 라이브러리
import torch.nn as nn                        # 신경망 구성에 필요한 모듈
import torch.optim as optim                  # 최적화를 위한 모듈
from torchvision import models, transforms   # 사전 학습된 모델과 데이터 변환
from torch.utils.data import DataLoader, Dataset
모듈역할
torch.nn신경망 레이어 (Conv2d, Linear, ReLU, CrossEntropyLoss 등) 정의용
torch.optim학습 최적화 알고리즘 (Adam, SGD 등)
torchvision.models사전학습된 모델 (ResNet-18, EfficientNet 등)
torchvision.transforms이미지 크기 조정·정규화·증강
DataLoader, Dataset데이터를 배치 단위로 모델에 먹이는 인프라
Tip — 한글 폰트는 필요할 때만
이 실습은 matplotlib 그래프를 그리지 않으므로 한글 폰트 설정이 필요 없습니다. 만약 학습 곡선 같은 그래프를 직접 그리려면 코랩에서 !pip install -q koreanize-matplotlibimport koreanize_matplotlib 한 줄로 해결됩니다.
3

데이터셋 다운로드 & 경로 설정

셀 3 — 1차시와 같은 real_vs_fake_dataset 재사용
셀 3
!gdown https://drive.google.com/uc?id=1qBmDau3_VCXqKamNqkfqVw3hAzy600jD
!unzip -oq /content/real_vs_fake_dataset.zip

real_image_dir = '/content/real_vs_fake_dataset/0_real'
fake_image_dir = '/content/real_vs_fake_dataset/1_fake'
핵심 개념 — 라벨링 규약
폴더명이 0_real, 1_fake 인 것이 포인트. 숫자가 바로 정답 라벨 이 됩니다 (Real=0, Fake=1). 이후 ImageDataset에서 이 숫자를 그대로 모델이 학습할 정답으로 사용합니다.
4

ImageDataset 클래스 정의

셀 4 — PyTorch Dataset 인터페이스 구현
왜 클래스로 만들까?
PyTorch의 DataLoaderDataset 인터페이스 (즉 __len____getitem__) 를 가진 객체만 먹는다. 이 약속을 지키면 자동으로 batch 묶음·셔플·병렬 로딩이 다 공짜.
셀 4
class ImageDataset(Dataset):
    def __init__(self, image_dirs, labels, transform=None):
        self.image_paths = []
        self.labels = []
        for image_dir, label in zip(image_dirs, labels):
            paths = [os.path.join(image_dir, img)
                     for img in os.listdir(image_dir)
                     if img.endswith(('.png', '.jpg', '.jpeg'))]
            self.image_paths.extend(paths)
            self.labels.extend([label] * len(paths))
        self.transform = transform

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

    def __getitem__(self, idx):
        image = Image.open(self.image_paths[idx]).convert('RGB')
        label = self.labels[idx]
        if self.transform:
            image = self.transform(image)
        return image, label
메서드역할
__init__폴더들을 훑어 파일 경로 리스트 + 라벨 리스트 생성
__len__데이터셋 크기 반환 (DataLoader 가 배치 수 계산할 때 사용)
__getitem__idx 번째 (이미지 tensor, 라벨) 쌍 반환. transform 자동 적용
5

데이터 전처리 — transform

셀 5 — ResNet-18 입력 규격 맞추기
셀 5
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])
단계하는 일
Resize((224,224))모든 이미지를 224×224 픽셀로 통일 — ResNet-18 이 학습한 크기
ToTensor()PIL 이미지(0~255 정수) → PyTorch 텐서(0.0~1.0 실수)
Normalize(mean, std)ImageNet 전체 평균·표준편차로 정규화 — 사전학습 모델 입력 규약
주의 — mean/std 숫자의 의미
mean=[0.485, 0.456, 0.406] 은 R·G·B 채널 각각의 ImageNet 학습셋 전체 평균. 이 값을 빼고 std로 나누면 입력이 "ImageNet 학습할 때 본 분포"와 맞춰져서 사전학습 가중치가 제 성능을 낸다. 다른 숫자를 쓰면 정확도가 10~20%p 떨어질 수 있어요.
6

train/test 분리 + DataLoader

셀 6 — 8:2, batch_size=16
셀 6
# Real과 Fake 이미지에 라벨 부여
real_label = 0   # Real → 0
fake_label = 1   # Fake → 1

dataset = ImageDataset(
    [real_image_dir, fake_image_dir],
    [real_label, fake_label],
    transform=transform,
)

train_size = int(0.8 * len(dataset))
test_size = len(dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])

batch_size = 16
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader  = DataLoader(test_dataset,  batch_size=batch_size, shuffle=False)
📚
비유 — 시험 전 분할

전체 문제집(200문제)을 학습용(160) + 시험용(40) 으로 8:2 나누기. 학습용으로만 공부하고, 시험용은 "모델이 한 번도 본 적 없는 문제"로 남겨둔다.

7

ResNet-18 불러오기 + 판사 교체

셀 7 — pretrained=True, fc 2-class 로
셀 7
# 사전학습된 ResNet-18 모델 불러오기
model = models.resnet18(pretrained=True)

# 마지막 레이어를 2개의 출력 노드(진짜/가짜)로 변경
model.fc = nn.Linear(model.fc.in_features, 2)

# GPU가 있으면 GPU로 이동
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)

print('device:', device)
핵심 — 왜 마지막 레이어만 바꿀까?
ResNet-18 원본은 ImageNet 1,000 클래스 분류용 (고양이·개·비행기·...). 우리는 진짜/가짜 2 클래스 만 필요하니 마지막 fc(Fully-Connected Layer = 판사) 만 2-output 으로 교체.

앞쪽 Conv Layer (탐정) 들은 "선·모서리·질감" 같은 보편적 시각 특징을 이미 학습해 놨으니 그대로 재사용하는 것이 Transfer Learning (전이학습) 의 핵심.
pretrained=TrueImageNet 으로 미리 학습된 가중치 다운로드
model.fc.in_features기본 ResNet-18 에서 512 (탐정이 마지막에 정리한 단서 개수)
nn.Linear(512, 2)512 → 2 개 점수 (Real 점수, Fake 점수) 로 출력하는 새 판사
model.to(device)GPU 가능 시 GPU 메모리로 이동. 학습 속도 10~50배 ↑
8

손실 함수 & 최적화 도구

셀 8 — CrossEntropyLoss + Adam(lr=0.001)
셀 8
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
핵심 — 손실 vs 최적화
  • 손실 함수 (criterion): "얼마나 틀렸는지" 계산하는 자. 작을수록 좋음. CrossEntropyLoss 는 softmax + log 를 내부에 포함해서 분류에 딱 맞춤.
  • 최적화 도구 (optimizer): 손실을 줄이는 방향으로 가중치를 조금씩 수정. Adam 은 각 파라미터에 맞는 적응적 학습률을 자동으로 쓰는 "똑똑한" 방식.
비유 — 골프

손실 = 공과 홀컵의 거리, 옵티마이저 = 공을 홀컵 쪽으로 굴리는 샷. lr(learning rate)은 한 번에 얼마나 세게 칠지. 너무 크면 홀컵을 지나치고, 너무 작으면 영영 못 도달.

9

train_model 함수

셀 9 — forward → loss → backward → step
셀 9
def train_model(model, train_loader, criterion, optimizer, epochs=5):
    model.train()   # 모델을 학습 모드로 설정
    for epoch in range(epochs):
        total_loss = 0
        for images, labels in tqdm(train_loader, desc=f'Epoch {epoch + 1}/{epochs}'):
            images, labels = images.to(device), labels.to(device)

            # 모델이 예측한 결과와 손실 계산
            outputs = model(images)                 # ① forward
            loss = criterion(outputs, labels)     # ② 손실 계산

            # 역전파를 통해 모델 가중치 업데이트
            optimizer.zero_grad()                # ③ 이전 gradient 초기화
            loss.backward()                      # ④ 역전파로 gradient 계산
            optimizer.step()                      # ⑤ 가중치 업데이트

            total_loss += loss.item()
        print(f'Epoch {epoch + 1}, Loss: {total_loss / len(train_loader):.4f}')
핵심 5단계 — 학습의 심장
  1. forward: 이미지 → 모델 → 예측값 (2개 점수)
  2. loss 계산: 예측 vs 정답 → 얼마나 틀렸나
  3. zero_grad: 이전 gradient 지우기 (누적 방지)
  4. backward: 체인룰로 모든 가중치의 gradient 계산
  5. step: gradient 반대 방향으로 가중치 이동 (경사하강법)
이 5단계가 한 batch 당 한 번씩 돌아가고, 전체 데이터 한 바퀴 = 1 epoch.
10

evaluate_model 함수

셀 10 — model.eval() + no_grad + torch.max
셀 10
def evaluate_model(model, test_loader):
    model.eval()   # 모델을 평가 모드로 설정
    correct = 0
    total = 0
    with torch.no_grad():   # 평가 시에는 가중치 업데이트 안 함
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs, 1)   # 가장 높은 점수의 클래스를 예측값으로
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    print(f'Accuracy: {100 * correct / total:.2f}%')
model.eval()평가 모드로 전환 — Dropout, BatchNorm 의 동작 방식이 바뀜
torch.no_grad()gradient 계산 비활성화 → 메모리 절약 + 속도 ↑
torch.max(outputs, 1)2개 점수 중 더 큰 것의 인덱스 반환 → 예측 클래스
11

실행 & 결과 확인

셀 11 — 학습 5 epoch + 평가
셀 11
train_model(model, train_loader, criterion, optimizer, epochs=5)
evaluate_model(model, test_loader)
예상 출력 (데이터·시드·GPU 환경에 따라 다름)
Epoch 1, Loss: 0.5~0.7
Epoch 2, Loss: 0.3~0.5
Epoch 3, Loss: 0.2~0.4
Epoch 4, Loss: 0.1~0.3
Epoch 5, Loss: 0.1~0.3
Accuracy: 75~92% 정도
Tip — 체크포인트
Loss 가 매 에폭 감소하면 정상. 만약 Loss 가 거꾸로 올라가거나 NaN이 뜨면 학습률을 0.0001 로 낮춰 보세요.

🎯 핵심 정리 — 11단계를 한 줄로

✏️ 내 메모 모음
아직 작성한 메모가 없어요
🤖 Claude에게 질문하기
1질문 입력 후 ↑
2클립보드 복사
3claude.ai 에서 붙여넣기
✅ 복사됨!
pretrained=True 가 무슨 뜻? CrossEntropyLoss 의 공식? Adam 의 lr=0.001 이 표준인 이유? zero_grad() 를 왜 매번 호출? train() 과 eval() 차이? Residual Block 이 정확히 뭐?
질문과 함께 이 실습의 코드 맥락이 자동 포함돼요