PyTorch 기초 완벽 가이드
FashionMNIST 데이터셋을 활용한 딥러닝 전체 워크플로우
목차
- 개요
- Tensors - 기본 데이터 구조
- Datasets & DataLoaders - 데이터 처리
- Transforms - 데이터 전처리
- 모델 구축
- Autograd - 자동 미분
- 최적화
- 모델 저장 및 로드
- 전체 워크플로우 예제
개요
PyTorch는 7가지 핵심 개념을 통해 머신러닝 워크플로우를 구현합니다:
학습 목표: FashionMNIST 데이터셋으로 의류 이미지를 10개 카테고리로 분류
- T-shirt/top, Trouser, Pullover, Dress, Coat, Sandal, Shirt, Sneaker, Bag, Ankle boot
1. Tensors - 기본 데이터 구조
정의
Tensors는 배열 및 행렬과 유사한 특수 데이터 구조입니다. NumPy의 ndarray와 비슷하지만 GPU 가속과 자동 미분을 지원합니다.
생성 방법
1.1 직접 생성
import torch
# 데이터로부터 생성 (자동 타입 추론)
data = [[1, 2], [3, 4]]
tensor = torch.tensor(data)
# NumPy 배열로부터 변환
import numpy as np
np_array = np.array(data)
tensor = torch.from_numpy(np_array)
# 기존 텐서로부터 생성 (shape 유지)
ones_tensor = torch.ones_like(tensor) # 모두 1로 채움
rand_tensor = torch.rand_like(tensor, dtype=torch.float) # 랜덤값
# 특정 값으로 초기화
shape = (2, 3)
rand_tensor = torch.rand(shape) # 랜덤 [0, 1)
ones_tensor = torch.ones(shape) # 모두 1
zeros_tensor = torch.zeros(shape) # 모두 0주요 속성
tensor = torch.rand(3, 4)
print(f"Shape: {tensor.shape}") # torch.Size([3, 4])
print(f"Datatype: {tensor.dtype}") # torch.float32
print(f"Device: {tensor.device}") # cpu 또는 cuda연산
인덱싱 & 슬라이싱 (NumPy 스타일)
tensor = torch.ones(4, 4)
tensor[:, 0] = 0 # 첫 번째 열을 0으로
tensor[0, :] = 0 # 첫 번째 행을 0으로
tensor[..., -1] = 0 # 마지막 열을 0으로텐서 결합
# 차원을 따라 연결
t1 = torch.cat([tensor, tensor, tensor], dim=1)
# 새로운 차원으로 쌓기
t2 = torch.stack([tensor, tensor], dim=0)산술 연산
# 행렬 곱셈
result = tensor @ tensor.T
result = tensor.matmul(tensor.T)
# 요소별 곱셈
result = tensor * tensor
result = tensor.mul(tensor)
# In-place 연산 (메모리 효율적이지만 gradient 계산 시 주의)
tensor.add_(5) # tensor에 5를 더함 (원본 수정)
tensor.t_() # 전치 (원본 수정)단일 요소 변환
agg = tensor.sum()
agg_item = agg.item() # Python 숫자로 변환GPU 사용
# GPU 사용 가능 여부 확인 및 텐서 이동
if torch.accelerator.is_available():
device = torch.accelerator.current_accelerator().type
tensor = tensor.to(device)지원 가속기: CUDA, MPS (Apple Silicon), MTIA, XPU
NumPy 브릿지
CPU의 텐서와 NumPy 배열은 메모리를 공유하므로 하나를 변경하면 다른 하나도 변경됩니다.
# Tensor → NumPy
t = torch.ones(5)
n = t.numpy()
t.add_(1) # n도 함께 변경됨
# NumPy → Tensor
n = np.ones(5)
t = torch.from_numpy(n)
np.add(n, 1, out=n) # t도 함께 변경됨주의: GPU 텐서는 NumPy 변환 시 CPU로 먼저 복사해야 함
2. Datasets & DataLoaders - 데이터 처리
핵심 개념
torch.utils.data.Dataset: 샘플과 라벨을 저장torch.utils.data.DataLoader: Dataset을 순회 가능한 객체로 감싸서 배치 처리, 셔플링, 멀티프로세싱 지원
사전 구축된 데이터셋 로드
from torchvision import datasets
from torchvision.transforms import ToTensor
# FashionMNIST 다운로드 및 로드
training_data = datasets.FashionMNIST(
root="data", # 데이터 저장 경로
train=True, # 학습 데이터
download=True, # 없으면 다운로드
transform=ToTensor() # PIL Image → Tensor 변환
)
test_data = datasets.FashionMNIST(
root="data",
train=False, # 테스트 데이터
download=True,
transform=ToTensor()
)커스텀 Dataset 생성
3가지 필수 메서드를 구현해야 합니다:
import os
import pandas as pd
from torch.utils.data import Dataset
from torchvision.io import decode_image
class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None):
"""데이터셋 초기화"""
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
def __len__(self):
"""데이터셋의 총 샘플 수 반환"""
return len(self.img_labels)
def __getitem__(self, idx):
"""주어진 인덱스의 샘플 로드 및 반환"""
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = decode_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
return image, labelDataLoader 사용
from torch.utils.data import DataLoader
train_dataloader = DataLoader(
training_data,
batch_size=64, # 배치당 샘플 수
shuffle=True # 에폭마다 데이터 순서 랜덤화 (과적합 방지)
)
test_dataloader = DataLoader(
test_data,
batch_size=64,
shuffle=False # 테스트는 셔플 불필요
)데이터 순회
# 첫 번째 배치 가져오기
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}") # [64, 1, 28, 28]
print(f"Labels batch shape: {train_labels.size()}") # [64]
# 전체 데이터 순회
for batch, (X, y) in enumerate(train_dataloader):
# X: [batch_size, channels, height, width]
# y: [batch_size]
pass3. Transforms - 데이터 전처리
개요
원시 데이터는 머신러닝에 바로 사용할 수 없는 경우가 많습니다. Transforms를 사용하여 데이터를 전처리합니다.
주요 Transform
ToTensor
PIL 이미지나 NumPy 배열을 PyTorch 텐서로 변환하고 픽셀값을 [0.0, 1.0] 범위로 정규화합니다.
from torchvision.transforms import ToTensor
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor() # PIL Image → Tensor, [0, 255] → [0.0, 1.0]
)Lambda Transforms
사용자 정의 함수로 커스텀 전처리를 수행합니다.
from torchvision.transforms import Lambda
# 정수 라벨 → One-hot 인코딩
target_transform = Lambda(
lambda y: torch.zeros(10, dtype=torch.float).scatter_(
0, torch.tensor(y), value=1
)
)
# 예: 라벨 3 → [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]TorchVision에서 적용
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(), # 이미지 변환
target_transform=target_transform # 라벨 변환
)일반적인 Transforms
from torchvision import transforms
# 여러 Transform 조합
transform = transforms.Compose([
transforms.Resize(256), # 크기 조정
transforms.CenterCrop(224), # 중앙 크롭
transforms.ToTensor(), # Tensor 변환
transforms.Normalize( # 정규화 (평균, 표준편차)
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])4. 모델 구축
기본 구조
PyTorch 신경망은 nn.Module을 상속하여 구축합니다.
import torch
from torch import nn
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28 * 28, 512), # 입력층 → 은닉층1
nn.ReLU(),
nn.Linear(512, 512), # 은닉층1 → 은닉층2
nn.ReLU(),
nn.Linear(512, 10) # 은닉층2 → 출력층
)
def forward(self, x):
"""순전파 정의"""
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits핵심 레이어
nn.Flatten
다차원 텐서를 1D로 펼칩니다 (배치 차원은 유지).
flatten = nn.Flatten()
# 입력: [batch_size, 1, 28, 28]
# 출력: [batch_size, 784]nn.Linear
선형 변환 (y = xW^T + b)을 수행합니다.
layer = nn.Linear(in_features=784, out_features=512)
# 가중치: [512, 784], 편향: [512]nn.ReLU
비선형 활성화 함수로 복잡한 패턴 학습을 가능하게 합니다.
relu = nn.ReLU()
# 음수 → 0, 양수 → 그대로nn.Sequential
레이어들을 순서대로 실행하는 컨테이너입니다.
seq_modules = nn.Sequential(
flatten,
nn.Linear(784, 512),
nn.ReLU(),
nn.Linear(512, 10)
)nn.Softmax
로짓(unbounded 값)을 확률 분포로 변환합니다.
softmax = nn.Softmax(dim=1)
# 입력: [batch, 10] (로짓)
# 출력: [batch, 10] (확률, 합=1)모델 파라미터 확인
model = NeuralNetwork()
# 모든 파라미터 출력
for name, param in model.named_parameters():
print(f"{name}: {param.size()}")
# 출력 예:
# linear_relu_stack.0.weight: torch.Size([512, 784])
# linear_relu_stack.0.bias: torch.Size([512])
# linear_relu_stack.2.weight: torch.Size([512, 512])
# linear_relu_stack.2.bias: torch.Size([512])
# linear_relu_stack.4.weight: torch.Size([10, 512])
# linear_relu_stack.4.bias: torch.Size([10])디바이스로 이동
device = (
torch.accelerator.current_accelerator().type
if torch.accelerator.is_available()
else "cpu"
)
model = NeuralNetwork().to(device)5. Autograd - 자동 미분
개요
torch.autograd는 자동으로 미분을 계산하는 엔진입니다. 역전파 알고리즘에 필수적입니다.
계산 그래프
PyTorch는 순전파 시 **방향 비순환 그래프(DAG)**를 생성합니다:
- Leaf 노드: 입력 텐서
- Root 노드: 출력 텐서
- 역전파 시 체인 룰을 사용하여 gradient 계산
requires_grad 속성
Gradient 추적을 활성화합니다.
x = torch.ones(5, requires_grad=True)
# 이 텐서와 관련된 연산의 gradient가 계산됨중요: Leaf 노드이면서 requires_grad=True인 텐서만 .grad 속성에 접근 가능
grad_fn
각 텐서는 생성 연산의 역전파 함수를 grad_fn에 저장합니다.
y = x + 2
print(y.grad_fn) # <AddBackward0 object>
z = y * y * 3
print(z.grad_fn) # <MulBackward0 object>Gradient 계산
# 간단한 예제
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
# 역전파
out.backward()
# Gradient 확인
print(x.grad) # dout/dx주의: PyTorch는 gradient를 누적하므로 매번 .zero_grad()로 초기화해야 합니다.
Gradient 추적 비활성화
방법 1: torch.no_grad() 컨텍스트
with torch.no_grad():
y = x + 2
print(y.requires_grad) # False방법 2: .detach()
y = x.detach()
print(y.requires_grad) # False사용 사례:
- 파라미터 고정 (특정 레이어 학습 중지)
- 추론 가속 (순전파만 필요)
동적 그래프
.backward() 호출 후 그래프는 초기화되고 매번 새로 생성됩니다. 이를 통해 동적 제어 흐름이 가능합니다.
6. 최적화
학습 프로세스
- 예측 수행
- 손실(loss) 계산
- Gradient 계산
- 파라미터 업데이트
하이퍼파라미터
epochs = 10 # 전체 데이터셋 반복 횟수
batch_size = 64 # 파라미터 업데이트 전 처리할 샘플 수
learning_rate = 1e-3 # 각 단계에서 파라미터 조정 크기손실 함수
모델 예측과 실제 값의 차이를 정량화합니다.
# 회귀 문제
loss_fn = nn.MSELoss()
# 분류 문제 (일반)
loss_fn = nn.NLLLoss()
# 분류 문제 (LogSoftmax + NLLLoss 통합)
loss_fn = nn.CrossEntropyLoss()옵티마이저
파라미터를 조정하는 알고리즘입니다.
from torch.optim import SGD, Adam, RMSProp
# SGD (Stochastic Gradient Descent)
optimizer = SGD(model.parameters(), lr=learning_rate)
# Adam (적응적 학습률)
optimizer = Adam(model.parameters(), lr=learning_rate)
# RMSProp
optimizer = RMSProp(model.parameters(), lr=learning_rate)최적화 3단계
각 학습 반복마다:
# 1. Gradient 초기화 (누적 방지)
optimizer.zero_grad()
# 2. 역전파 (gradient 계산)
loss.backward()
# 3. 파라미터 업데이트
optimizer.step()학습 루프
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
model.train() # 학습 모드 (dropout, batch norm 활성화)
for batch, (X, y) in enumerate(dataloader):
# 예측 및 손실 계산
pred = model(X)
loss = loss_fn(pred, y)
# 역전파 및 최적화
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss_value = loss.item()
current = (batch + 1) * len(X)
print(f"loss: {loss_value:>7f} [{current:>5d}/{size:>5d}]")검증 루프
def test_loop(dataloader, model, loss_fn):
model.eval() # 평가 모드 (dropout, batch norm 비활성화)
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
with torch.no_grad(): # Gradient 계산 비활성화
for X, y in dataloader:
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")전체 학습 파이프라인
# 모델, 손실, 옵티마이저 초기화
model = NeuralNetwork().to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
# 학습 실행
for epoch in range(epochs):
print(f"Epoch {epoch+1}\n-------------------------------")
train_loop(train_dataloader, model, loss_fn, optimizer)
test_loop(test_dataloader, model, loss_fn)
print("Done!")FashionMNIST 결과 예시 (10 에폭):
- 초기: Accuracy 42.9%, Loss 2.155
- 최종: Accuracy 70.9%, Loss 0.785
7. 모델 저장 및 로드
state_dict
PyTorch 모델은 학습된 파라미터를 state_dict라는 내부 구조에 저장합니다.
모델 가중치 저장 (권장)
# 가중치만 저장
torch.save(model.state_dict(), 'model_weights.pth')장점:
- 파일 크기가 작음
- 이식성이 높음
- 보안상 안전
모델 가중치 로드
# 1. 동일한 아키텍처의 모델 생성
model = NeuralNetwork()
# 2. 가중치 로드
model.load_state_dict(
torch.load('model_weights.pth', weights_only=True)
)
# 3. 평가 모드로 전환 (필수!)
model.eval()weights_only=True: 보안을 위해 가중치만 언피클링
model.eval() 필수 이유:
- Dropout 비활성화
- Batch Normalization을 평가 모드로 전환
- 일관된 예측 결과 보장
전체 모델 저장 (대안)
# 아키텍처 + 가중치 함께 저장
torch.save(model, 'model.pth')
# 로드
model = torch.load('model.pth', weights_only=False)
model.eval()단점:
- Python pickle 모듈 의존
- 원본 클래스 정의가 필요 (이식성 떨어짐)
- 보안 위험
권장: state_dict 방식 사용
전체 워크플로우 예제
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
# ===== 1. 데이터 준비 =====
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64)
# ===== 2. 디바이스 설정 =====
device = (
torch.accelerator.current_accelerator().type
if torch.accelerator.is_available()
else "cpu"
)
print(f"Using {device} device")
# ===== 3. 모델 정의 =====
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28 * 28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10)
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork().to(device)
print(model)
# ===== 4. 손실 함수 및 옵티마이저 =====
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
# ===== 5. 학습 및 검증 함수 =====
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
model.train()
for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)
pred = model(X)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss_value, current = loss.item(), (batch + 1) * len(X)
print(f"loss: {loss_value:>7f} [{current:>5d}/{size:>5d}]")
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval()
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
# ===== 6. 학습 실행 =====
epochs = 10
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model, loss_fn)
print("Done!")
# ===== 7. 모델 저장 =====
torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")
# ===== 8. 모델 로드 및 추론 =====
model = NeuralNetwork().to(device)
model.load_state_dict(torch.load("model.pth", weights_only=True))
model.eval()
classes = [
"T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"
]
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
x = x.to(device)
pred = model(x.unsqueeze(0)) # 배치 차원 추가
predicted_class = classes[pred[0].argmax(0)]
actual_class = classes[y]
print(f"Predicted: {predicted_class}, Actual: {actual_class}")핵심 요약
데이터 흐름
원본 데이터
↓ (Dataset + Transform)
Tensor 데이터
↓ (DataLoader)
배치 데이터
↓ (Model)
예측값
↓ (Loss Function)
손실값
↓ (Autograd)
Gradient
↓ (Optimizer)
파라미터 업데이트
주요 개념 맵핑
| 개념 | PyTorch 구성 요소 | 역할 |
|---|---|---|
| 데이터 구조 | torch.Tensor | NumPy + GPU + Autograd |
| 데이터 로딩 | Dataset, DataLoader | 배치 처리, 셔플링 |
| 전처리 | transforms | 정규화, 증강 |
| 모델 | nn.Module | 레이어 조합 |
| 순전파 | forward() | 입력 → 출력 |
| 역전파 | autograd, .backward() | Gradient 계산 |
| 손실 | nn.CrossEntropyLoss 등 | 오차 측정 |
| 최적화 | torch.optim.SGD 등 | 파라미터 조정 |
| 저장/로드 | state_dict | 모델 영속성 |
모범 사례
-
학습 전:
model.train()호출optimizer.zero_grad()호출
-
추론 전:
model.eval()호출with torch.no_grad():사용
-
저장:
state_dict방식 사용weights_only=True옵션
-
GPU 사용:
- 모델과 데이터를 같은 디바이스로 이동
참고 자료
생성일: 2025-11-11 대상: FashionMNIST 분류 작업