PyTorch 입문 — Keras와 무엇이 다를까?
nn.Module로 모델을 직접 설계하고 zero_grad, backward, step으로 이어지는 학습 루프를 처음부터 구현하며 PyTorch의 핵심 흐름 정리
2026.04.10
0. 시리즈
| 편 | 제목 | 역할 |
|---|---|---|
| 1편 | NumPy 완벽 정리 — 숫자 배열 다루기 | 데이터 기초 |
| 2편 | Pandas 완벽 정리 — 데이터 불러오고 정리하기 | 데이터 전처리 |
| 3편 | Matplotlib / Seaborn — 데이터 시각화 | 시각화 |
| 4편 | Scikit-learn — 머신러닝 실습 입문 | ML 실습 |
| 5편 | TensorFlow / Keras — 딥러닝 모델 만들기 | DL 실습 |
| 6편⬅️ | PyTorch 입문 — Keras와 무엇이 다를까? | DL 심화 |
1. PyTorch란?
1-1. PyTorch가 뭔가요?
PyTorch(파이토치) 는 Meta(Facebook)가 2017년에 공개한 딥러닝 프레임워크입니다. 현재 AI 연구 분야에서 가장 많이 쓰이는 프레임워크로, 전 세계 AI 논문의 70% 이상이 PyTorch 기반 코드로 공개됩니다.
Keras가 "쉽고 빠르게 모델을 만드는 도구"라면, PyTorch는 "모델 내부를 직접 설계하고 제어하는 도구" 입니다.
1-2. Keras와 결정적인 차이
| 비교 항목 | Keras | PyTorch |
|---|---|---|
| 그래프 방식 | 정적 그래프 (미리 설계 후 실행) | 동적 그래프 (실행하면서 즉시 생성) |
| 코드 방식 | 선언형 (레이어를 쌓음) | 명령형 (클래스로 직접 설계) |
| 디버깅 | 불편함 | 일반 Python처럼 쉬움 |
| 자유도 | 낮음 | 높음 |
| 학습 루프 | 자동 (model.fit) | 직접 구현 |
💡 동적 그래프 란 코드가 실행될 때마다 연산 그래프가 새로 만들어지는 방식입니다. 덕분에 일반 Python 디버거로 모델 내부를 바로 들여다볼 수 있습니다.
1-3. 왜 연구자들은 PyTorch를 선호하는가?
- 유연성 — 표준 패턴을 벗어난 실험적 구조도 자유롭게 구현 가능
- 디버깅 편의 —
print나breakpoint()로 중간 값을 바로 확인 - 논문 구현 — 대부분의 최신 논문 공식 코드가 PyTorch 기반
- 커뮤니티 — HuggingFace, Lightning 등 주요 생태계가 PyTorch 중심
1-4. 설치 및 임포트
bash# CPU 버전
pip install torch torchvision
# GPU 버전 (CUDA 버전에 맞게 설치 — pytorch.org에서 확인)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
pythonimport torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
print(torch.__version__) # 예: 2.2.0
print(torch.cuda.is_available()) # GPU 사용 가능 여부: True / False
2. PyTorch 핵심 개념 — Tensor
2-1. Tensor란?
PyTorch의 기본 자료구조는 Tensor(텐서) 입니다. 1편에서 배운 NumPy의 ndarray와 거의 동일하지만, 결정적인 차이가 있습니다.
| 비교 항목 | NumPy ndarray | PyTorch Tensor |
|---|---|---|
| GPU 연산 | ❌ 불가 | ✅ 가능 |
| 자동 미분 | ❌ 불가 | ✅ 가능 (autograd) |
| 딥러닝 학습 | ❌ | ✅ |
2-2. Tensor 생성하는 방법
pythonimport torch
a = torch.tensor([1, 2, 3, 4, 5])
print(a) # tensor([1, 2, 3, 4, 5])
print(a.dtype) # torch.int64
b = torch.tensor([1.0, 2.0, 3.0])
print(b.dtype) # torch.float32
torch.zeros((3, 3)) # 0으로 채운 3×3
torch.ones((2, 4)) # 1로 채운 2×4
torch.rand((3, 3)) # 0~1 난수
torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
print(b.shape) # torch.Size([3])
print(b.ndim) # 1
2-3. NumPy 배열 ↔ Tensor 변환
pythonimport numpy as np
import torch
# NumPy → Tensor
arr = np.array([1, 2, 3])
tensor = torch.from_numpy(arr)
print(tensor) # tensor([1, 2, 3])
# Tensor → NumPy
arr2 = tensor.numpy()
print(arr2) # [1 2 3]
⚠️
from_numpy로 변환하면 메모리를 공유합니다. 하나를 수정하면 다른 것도 바뀝니다. 독립적인 복사본이 필요하면tensor.clone().numpy()를 사용하세요.
2-4. GPU로 Tensor 올리기
pythondevice = "cuda" if torch.cuda.is_available() else "cpu"
print(f"사용 장치: {device}")
tensor = torch.tensor([1.0, 2.0, 3.0])
tensor = tensor.to(device)
model = model.to(device)
X_batch = X_batch.to(device)
y_batch = y_batch.to(device)
3. Keras와 코드 방식 비교
3-1. Keras — 선언형 (레이어를 쌓는 방식)
pythonfrom tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Dropout
model = Sequential([
Dense(128, activation="relu", input_shape=(784,)),
Dropout(0.3),
Dense(64, activation="relu"),
Dense(10, activation="softmax")
])
구조가 고정되어 있고, 학습은 model.fit 한 줄로 끝납니다. 빠르고 간결하지만 내부를 커스터마이징하기 어렵습니다.
3-2. PyTorch — 명령형 (클래스로 직접 설계)
pythonimport torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 128)
self.dropout = nn.Dropout(0.3)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 10)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
model = MyModel()
코드는 더 길지만, 데이터 흐름을 직접 제어할 수 있어 복잡한 구조도 자유롭게 구현 가능합니다.
3-3. 같은 모델, 두 가지 코드 나란히 비교
# Keras # PyTorch
Sequential([ class MyModel(nn.Module):
Dense(128, activation="relu") def __init__(self):
Dropout(0.3) self.fc1 = nn.Linear(784, 128)
Dense(64, activation="relu") self.drop = nn.Dropout(0.3)
Dense(10, activation="softmax") self.fc2 = nn.Linear(128, 64)
]) self.fc3 = nn.Linear(64, 10)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.drop(x)
x = F.relu(self.fc2(x))
return self.fc3(x)
4. PyTorch로 모델 만들기
4-1. nn.Module — 모든 모델의 기본 클래스
PyTorch의 모든 모델은 nn.Module을 상속받아 만듭니다.
pythonclass MyModel(nn.Module):
def __init__(self): # 레이어 정의
super().__init__()
...
def forward(self, x): # 데이터 흐름 정의
...
return x
__init__— 사용할 레이어들을 미리 선언forward— 데이터가 모델을 통과하는 순서를 직접 작성
4-2. 주요 레이어
pythonimport torch.nn as nn
import torch.nn.functional as F
nn.Linear(in, out) # 완전 연결층 (Keras의 Dense)
nn.ReLU() # 활성화 함수
nn.Dropout(p=0.3) # 드롭아웃
nn.BatchNorm1d(128) # 배치 정규화
nn.Sigmoid() # 이진 분류 출력
nn.Softmax(dim=1) # 다중 분류 출력
# 함수형으로 사용 (forward 안에서)
F.relu(x)
F.dropout(x, p=0.3, training=self.training)
4-3. 모델 구조 확인
pythonmodel = MyModel()
print(model)
total = sum(p.numel() for p in model.parameters())
print(f"전체 파라미터 수: {total:,}")
5. 학습 루프 직접 구현하기
5-1. Keras vs PyTorch 학습 방식
python# Keras — 한 줄
model.fit(X_train, y_train, epochs=20, batch_size=64)
# PyTorch — 직접 구현 (하지만 내부를 완전히 제어 가능)
for epoch in range(20):
for X_batch, y_batch in dataloader:
optimizer.zero_grad()
output = model(X_batch)
loss = criterion(output, y_batch)
loss.backward()
optimizer.step()
5-2. Dataset & DataLoader
데이터를 배치 단위로 나눠 공급합니다.
pythonfrom torch.utils.data import TensorDataset, DataLoader
import torch
X_tensor = torch.tensor(X_train, dtype=torch.float32)
y_tensor = torch.tensor(y_train, dtype=torch.long)
dataset = TensorDataset(X_tensor, y_tensor)
dataloader = DataLoader(dataset, batch_size=64, shuffle=True)
5-3. 손실 함수와 옵티마이저 설정
pythoncriterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
5-4. 전체 학습 루프
pythonimport torch
device = "cuda" if torch.cuda.is_available() else "cpu"
model = model.to(device)
train_losses = []
for epoch in range(20):
model.train()
running_loss = 0.0
for X_batch, y_batch in dataloader:
X_batch = X_batch.to(device)
y_batch = y_batch.to(device)
optimizer.zero_grad() # 1. 기울기 초기화 (반드시 먼저!)
output = model(X_batch) # 2. 순전파 (Forward)
loss = criterion(output, y_batch) # 3. 손실 계산
loss.backward() # 4. 역전파 (Backward)
optimizer.step() # 5. 가중치 업데이트
running_loss += loss.item()
avg_loss = running_loss / len(dataloader)
train_losses.append(avg_loss)
print(f"Epoch {epoch+1:2d}/20 — Loss: {avg_loss:.4f}")
💡 zero_grad()를 왜 먼저 하나요? PyTorch는 기울기를 자동으로 누적합니다. 매 배치마다 초기화하지 않으면 이전 배치의 기울기가 남아서 학습이 잘못됩니다.
5-5. 학습 곡선 시각화
pythonimport matplotlib.pyplot as plt
plt.plot(range(1, 21), train_losses, marker="o", color="blue")
plt.title("PyTorch 학습 손실 변화")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.grid(True)
plt.show()
6. 분류 실습 — MNIST 손글씨 인식
6-1. torchvision으로 MNIST 데이터 불러오기
pythonfrom torchvision import datasets, transforms
from torch.utils.data import DataLoader
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_dataset = datasets.MNIST(root="./data", train=True,
transform=transform, download=True)
test_dataset = datasets.MNIST(root="./data", train=False,
transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
6-2. 모델 설계 및 학습
pythonclass MNISTModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 10)
self.dropout = nn.Dropout(0.3)
def forward(self, x):
x = x.view(-1, 784)
x = F.relu(self.fc1(x))
x = self.dropout(x)
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
model = MNISTModel().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(10):
model.train()
for X_batch, y_batch in train_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
optimizer.zero_grad()
loss = criterion(model(X_batch), y_batch)
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}/10 완료")
6-3. Keras와 비교
| 항목 | Keras | PyTorch |
|---|---|---|
| 모델 정의 | Sequential([...]) | class 상속 |
| 학습 | model.fit(...) | 직접 루프 작성 |
| 코드 길이 | 짧음 | 길음 |
| 정확도 (MNIST) | 약 97~98% | 약 97~98% (동일) |
| 디버깅 편의 | 낮음 | 높음 |
6-4. 정확도 평가 및 시각화
pythonimport numpy as np
import matplotlib.pyplot as plt
model.eval()
correct = 0
total = 0
all_preds = []
all_labels = []
with torch.no_grad():
for X_batch, y_batch in test_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
output = model(X_batch)
preds = output.argmax(dim=1)
correct += (preds == y_batch).sum().item()
total += y_batch.size(0)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(y_batch.cpu().numpy())
print(f"테스트 정확도: {correct / total:.4f}")
test_images, test_labels = next(iter(test_loader))
model.eval()
with torch.no_grad():
preds = model(test_images.to(device)).argmax(dim=1).cpu()
fig, axes = plt.subplots(2, 5, figsize=(14, 6))
for i, ax in enumerate(axes.flat):
ax.imshow(test_images[i].squeeze(), cmap="gray")
color = "blue" if preds[i] == test_labels[i] else "red"
ax.set_title(f"예측:{preds[i].item()} / 정답:{test_labels[i].item()}", color=color)
ax.axis("off")
plt.suptitle("파란색: 정답, 빨간색: 오답")
plt.show()
7. PyTorch vs Keras 최종 정리
7-1. 언제 무엇을 쓰는가?
| 상황 | 추천 |
|---|---|
| 빠르게 프로토타입 만들기 | ✅ Keras |
| 서비스 배포용 모델 | ✅ Keras (TFLite, TF Serving 지원) |
| 논문 구현 / 재현 | ✅ PyTorch |
| 커스텀 학습 루프 필요 | ✅ PyTorch |
| 연구 및 실험적 모델 | ✅ PyTorch |
| HuggingFace 모델 사용 | ✅ PyTorch |
7-2. 입문자 학습 경로 추천
입문 → Keras로 전체 흐름 파악
(모델 설계 → 학습 → 평가)
↓
중급 → PyTorch로 내부 구조 이해
(Tensor, 학습 루프, 자동 미분)
↓
심화 → CNN, RNN, Transformer 구현
↓
최신 트렌드 → HuggingFace로 LLM Fine-tuning
8. 마무리
8-1. 오늘 배운 것 한눈에 정리
| 개념 | 핵심 내용 |
|---|---|
| Tensor | NumPy처럼 사용하지만 GPU + 자동 미분 지원 |
| nn.Module | 모든 PyTorch 모델의 기본 클래스 |
| forward | 데이터 흐름을 직접 정의하는 메서드 |
| 학습 루프 | zero_grad → forward → loss → backward → step |
| model.eval() | 평가 시 Dropout 비활성화 필수 |
| torch.no_grad() | 평가 시 기울기 계산 끄기 (메모리 절약) |