들어가며
이론은 공부했으니 이제 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 |
|---|