본문 바로가기

딥러닝/딥러닝 실습 (PyTouch)

PyTorch 기초

들어가며

이론은 공부했으니 이제 PyTorch로 직접 코드를 짜볼 차례다. PyTorch는 딥러닝 프레임워크 중 연구와 실무 모두에서 가장 많이 쓰인다. 웹 개발로 비유하면 React 같은 존재다. 직관적이고 유연하며 커뮤니티가 크다. 핵심 개념부터 실제 학습 파이프라인까지 정리해둔다.


설치

# CPU 버전
pip install torch torchvision torchaudio

# GPU 버전 (CUDA 11.8)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 설치 확인
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"

텐서 (Tensor)

PyTorch의 핵심 자료구조다. NumPy의 ndarray와 거의 동일하지만 GPU 연산과 자동 미분을 지원한다.

텐서 생성

import torch
import numpy as np

# 직접 생성
a = torch.tensor([1, 2, 3, 4, 5])
b = torch.tensor([[1.0, 2.0], [3.0, 4.0]])

print(a.shape)   # torch.Size([5])
print(b.dtype)   # torch.float32

# 특수 텐서
zeros    = torch.zeros(3, 4)
ones     = torch.ones(3, 4)
rand     = torch.randn(3, 4)          # 정규분포
randint  = torch.randint(0, 10, (3, 4))
arange   = torch.arange(0, 10, 2)
linspace = torch.linspace(0, 1, 5)

# NumPy 변환
arr      = np.array([1.0, 2.0, 3.0])
t        = torch.from_numpy(arr)      # 메모리 공유
arr_back = t.numpy()                  # 다시 NumPy로

텐서 연산

a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])

print(a + b)          # [5, 7, 9]
print(a * b)          # [4, 10, 18]
print(torch.sqrt(a))  # [1, 1.41, 1.73]

# 행렬 곱
A = torch.randn(3, 4)
B = torch.randn(4, 5)
C = A @ B             # (3, 5)

# 집계
x = torch.randn(3, 4)
print(x.sum(dim=0))      # 행 방향 합 (4,)
print(x.sum(dim=1))      # 열 방향 합 (3,)
print(x.argmax(dim=1))   # 각 행의 최댓값 인덱스

Shape 변환

# reshape / view
x = torch.randn(4, 6)
y = x.reshape(2, 3, 4)
y = x.view(2, 12)        # 메모리 연속적일 때만

# 차원 추가/제거
x  = torch.randn(3, 4)
x1 = x.unsqueeze(0)      # (1, 3, 4)
x2 = x.unsqueeze(-1)     # (3, 4, 1)
x3 = x1.squeeze(0)       # (3, 4)

# permute (차원 순서 변경)
x = torch.randn(2, 3, 4)
print(x.permute(0, 2, 1).shape)   # (2, 4, 3)

# 딥러닝에서 자주 쓰는 패턴
image = torch.randn(3, 224, 224)      # (C, H, W)
batch = image.unsqueeze(0)            # (1, C, H, W) 배치 추가
flat  = batch.view(batch.size(0), -1) # (1, 150528) 펼치기

autograd (자동 미분)

x = torch.tensor([2.0], requires_grad=True)
w = torch.tensor([3.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)

y    = w * x + b    # y = 7
loss = y ** 2       # loss = 49

loss.backward()

print(f"x.grad: {x.grad}")  # 42
print(f"w.grad: {w.grad}")  # 28
print(f"b.grad: {b.grad}")  # 14

# 기울기 초기화 (매 스텝마다 필수)
x.grad.zero_()
w.grad.zero_()
b.grad.zero_()

torch.no_grad()

# 추론 시 기울기 계산 불필요 → 메모리/연산 절약
with torch.no_grad():
    output = model(x)

# 또는 데코레이터로
@torch.no_grad()
def predict(model, x):
    return model(x)

nn.Module로 모델 정의

import torch.nn as nn

# 방법 1: nn.Sequential
model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Dropout(0.3),
    nn.Linear(256, 128),
    nn.ReLU(),
    nn.Dropout(0.3),
    nn.Linear(128, 10)
)

# 방법 2: nn.Module 상속 (복잡한 구조)
class MLP(nn.Module):
    def __init__(self, input_size, hidden_sizes, output_size, dropout=0.3):
        super().__init__()

        layers = []
        prev_size = input_size
        for hidden_size in hidden_sizes:
            layers += [
                nn.Linear(prev_size, hidden_size),
                nn.BatchNorm1d(hidden_size),
                nn.ReLU(),
                nn.Dropout(dropout)
            ]
            prev_size = hidden_size
        layers.append(nn.Linear(prev_size, output_size))

        self.network = nn.Sequential(*layers)

    def forward(self, x):
        return self.network(x)

model = MLP(input_size=784, hidden_sizes=[256, 128], output_size=10)

total     = sum(p.numel() for p in model.parameters())
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"전체 파라미터: {total:,}")
print(f"학습 가능 파라미터: {trainable:,}")

Dataset과 DataLoader

from torch.utils.data import Dataset, DataLoader

class TabularDataset(Dataset):
    def __init__(self, X, y):
        self.X = torch.tensor(X, dtype=torch.float32)
        self.y = torch.tensor(y, dtype=torch.long)

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

    def __getitem__(self, idx):
        return self.X[idx], self.y[idx]

dataset = TabularDataset(X, y)

train_loader = DataLoader(
    dataset,
    batch_size=32,
    shuffle=True,
    num_workers=0    # Windows에서는 0 권장
)

# 학습/검증 분리
from torch.utils.data import random_split

train_size = int(0.8 * len(dataset))
val_size   = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader   = DataLoader(val_dataset,   batch_size=64, shuffle=False)

완전한 학습 파이프라인

import torch.optim as optim
from torch.utils.data import TensorDataset

torch.manual_seed(42)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 모델, 손실함수, 옵티마이저
model     = MLP(20, [64, 32], 2).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5, factor=0.5)

# 학습 함수
def train_epoch(model, loader, optimizer, criterion, device):
    model.train()
    total_loss, correct, total = 0.0, 0, 0

    for X_batch, y_batch in loader:
        X_batch, y_batch = X_batch.to(device), y_batch.to(device)

        optimizer.zero_grad()                    # 기울기 초기화
        outputs = model(X_batch)                 # 순전파
        loss = criterion(outputs, y_batch)       # 손실 계산
        loss.backward()                          # 역전파
        optimizer.step()                         # 가중치 업데이트

        total_loss += loss.item() * len(X_batch)
        _, pred = outputs.max(1)
        correct += pred.eq(y_batch).sum().item()
        total   += len(X_batch)

    return total_loss / total, 100. * correct / total

# 검증 함수
def validate(model, loader, criterion, device):
    model.eval()
    total_loss, correct, total = 0.0, 0, 0

    with torch.no_grad():
        for X_batch, y_batch in loader:
            X_batch, y_batch = X_batch.to(device), y_batch.to(device)
            outputs = model(X_batch)
            loss    = criterion(outputs, y_batch)

            total_loss += loss.item() * len(X_batch)
            _, pred = outputs.max(1)
            correct += pred.eq(y_batch).sum().item()
            total   += len(X_batch)

    return total_loss / total, 100. * correct / total

# 학습 루프
best_val_loss = float('inf')

for epoch in range(50):
    train_loss, train_acc = train_epoch(model, train_loader, optimizer, criterion, device)
    val_loss,   val_acc   = validate(model, val_loader, criterion, device)

    scheduler.step(val_loss)

    if val_loss < best_val_loss:
        best_val_loss = val_loss
        torch.save(model.state_dict(), 'best_model.pth')

    if (epoch + 1) % 10 == 0:
        lr = optimizer.param_groups[0]['lr']
        print(f"Epoch {epoch+1:3d}: "
              f"Train={train_acc:.1f}% | Val={val_acc:.1f}% | lr={lr:.6f}")

모델 저장과 로드

# 가중치만 저장 (권장)
torch.save(model.state_dict(), 'model.pth')
model.load_state_dict(torch.load('model.pth'))
model.eval()

# 체크포인트 (학습 재개 가능)
torch.save({
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'val_loss': val_loss,
}, 'checkpoint.pth')

checkpoint  = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
start_epoch = checkpoint['epoch'] + 1

GPU 사용

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

model   = model.to(device)
X_batch = X_batch.to(device)
y_batch = y_batch.to(device)

# Mixed Precision (GPU 메모리 절약, 속도 향상)
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

for X_batch, y_batch in train_loader:
    X_batch, y_batch = X_batch.to(device), y_batch.to(device)

    optimizer.zero_grad()
    with autocast():
        outputs = model(X_batch)
        loss    = criterion(outputs, y_batch)

    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

자주 발생하는 에러와 해결

# 1. Shape 불일치
# RuntimeError: Expected input batch_size to match target batch_size
# → 손실 함수 입력/타겟 shape 확인

# 2. dtype 불일치
# RuntimeError: expected scalar type Long but found Float
y = y.long()    # 분류 레이블은 torch.long

# 3. device 불일치
# RuntimeError: Expected all tensors to be on the same device
X = X.to(device)

# 4. CUDA out of memory
# → 배치 크기 줄이기 또는 Mixed Precision 사용
torch.cuda.empty_cache()

# 5. 기울기 폭발 (Gradient Explosion)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

정리

개념 설명 핵심 포인트
Tensor PyTorch 기본 자료구조 NumPy + GPU + autograd
autograd 자동 미분 requires_grad=True
nn.Module 모델 기본 클래스 forward() 구현
DataLoader 배치 데이터 로더 shuffle, num_workers
optimizer.zero_grad() 기울기 초기화 매 스텝 필수
loss.backward() 역전파 autograd 자동 처리
optimizer.step() 가중치 업데이트 기울기 반대 방향
model.train() 학습 모드 Dropout, BatchNorm 활성화
model.eval() 추론 모드 Dropout, BatchNorm 비활성화
torch.no_grad() 기울기 추적 끄기 추론 시 메모리 절약

'딥러닝 > 딥러닝 실습 (PyTouch)' 카테고리의 다른 글

NumPy / Pandas 기초  (1) 2026.05.19