[AI 인공지능 머신러닝 딥러닝/Python | PyTorch] - 인스톨! 파이토치 강의 소개
인스톨! 파이토치 강의 소개
혁펜하임 PyTorch 강의 오리엔테이션 요약혁펜하임 채널의 '[PyTorch] 0강. 오리엔테이션' 영상은 채널 5주년 기념으로 '인스톨! 파이토치' 강의를 소개하는 내용입니다. 강의자는 최근 출간한 '이론
inner-game.tistory.com

딥러닝에서 분류(Classification)는 회귀와 함께 가장 중요한 작업 유형입니다. 특히 이진 분류(Binary Classification)는 스팸 메일 필터링, 질병 진단, 고객 이탈 예측 등 실생활의 수많은 문제를 해결하는 데 사용됩니다. 4강에서는 파이토치로 이진 분류 모델을 구현하는 전체 과정과, 모델 성능을 극대화하기 위한 하이퍼파라미터 튜닝 전략을 마스터할 수 있습니다.[^1][^2]
이진 분류는 입력 데이터를 두 개의 클래스(0 또는 1, True 또는 False) 중 하나로 분류하는 작업입니다. 선형 회귀와 달리 이진 분류는 연속적인 값이 아닌 이산적인 범주를 예측하므로, 활성화 함수와 손실 함수가 달라집니다.[^2][^3][^1]
이진 분류의 핵심은 모델의 출력을 확률값(0과 1 사이)으로 변환하는 것입니다. 예를 들어, 출력이 0.8이면 "클래스 1에 속할 확률이 80%"라는 의미이며, 일반적으로 0.5를 기준(threshold)으로 0 또는 1로 최종 분류합니다.[^4][^2]
선형 레이어의 출력은 -∞부터 +∞까지의 임의의 실수값을 가질 수 있습니다. 이를 0과 1 사이의 확률로 변환하기 위해 시그모이드 함수(Sigmoid Function)를 사용합니다:[^5][^4][^2]
σ(x) = 1 / (1 + e^(-x))
시그모이드 함수는 다음과 같은 특성을 가집니다:[^2]
import torch
import torch.nn as nn
# 시그모이드 함수 적용
logits = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])
sigmoid = nn.Sigmoid()
probabilities = sigmoid(logits)
print(probabilities)
# tensor([0.1192, 0.2689, 0.5000, 0.7311, 0.8808])
이진 분류에서는 평균 제곱 오차(MSE) 대신 이진 교차 엔트로피(Binary Cross-Entropy, BCE)를 손실 함수로 사용합니다. MSE는 예측값과 실제값의 차이가 작을 때 오차도 작아져 학습이 제대로 진행되지 않지만, BCE는 로그 함수를 활용해 이 문제를 해결합니다.[^3][^1]
BCE 손실 함수의 수식은 다음과 같습니다:[^1]
BCE = -[y·log(ŷ) + (1-y)·log(1-ŷ)]
여기서 y는 실제 레이블(0 또는 1), ŷ는 모델이 예측한 확률값입니다. 이 손실 함수는 모델이 확신을 가지고 잘못된 예측을 할 때 큰 패널티를 부여합니다.[^1]
파이토치는 두 가지 BCE 손실 함수를 제공합니다:[^6][^7]
nn.BCELoss(): 이미 시그모이드가 적용된 확률값을 입력으로 받음[^8]nn.BCEWithLogitsLoss(): 시그모이드와 BCE를 결합한 함수[^9][^5]두 번째 옵션이 더 권장됩니다. 그 이유는 수치적 안정성(numerical stability) 때문입니다. 시그모이드와 로그 연산을 분리하면 작은 값에서 정밀도 손실이 발생할 수 있지만, BCEWithLogitsLoss는 내부적으로 최적화된 방식으로 계산하여 이를 방지합니다.[^10][^6][^5]
# 방법 1: BCELoss (덜 권장)
model_output = model(x)
prediction = torch.sigmoid(model_output)
loss = nn.BCELoss()(prediction, target)
# 방법 2: BCEWithLogitsLoss (권장)
model_output = model(x) # 시그모이드 적용 안 함
loss = nn.BCEWithLogitsLoss()(model_output, target)
15분 안에 완성하는 이진 분류 전체 코드는 다음과 같습니다:[^11][^2]
import torch
import torch.nn as nn
import torch.optim as optim
# 1. 데이터 준비 (XOR 문제 예시)
X_train = torch.FloatTensor([[0, 0], [0, 1], [1, 0], [1, 1]])
y_train = torch.FloatTensor([[^0], [^1], [^1], [^0]])
# 2. 모델 정의
class BinaryClassifier(nn.Module):
def __init__(self):
super(BinaryClassifier, self).__init__()
self.layer1 = nn.Linear(2, 4) # 입력층 → 은닉층
self.layer2 = nn.Linear(4, 1) # 은닉층 → 출력층
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.sigmoid(self.layer1(x))
x = self.layer2(x) # BCEWithLogitsLoss 사용 시 sigmoid 제거
return x
model = BinaryClassifier()
# 3. 손실 함수와 옵티마이저
criterion = nn.BCEWithLogitsLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)
# 4. 학습 루프
epochs = 10000
for epoch in range(epochs):
# 순전파
y_pred = model(X_train)
loss = criterion(y_pred, y_train)
# 역전파
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 1000 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
# 5. 예측
with torch.no_grad():
predictions = torch.sigmoid(model(X_train))
predicted_classes = (predictions > 0.5).float()
print(f'\n예측 확률:\n{predictions}')
print(f'\n예측 클래스:\n{predicted_classes}')
이진 분류에서 출력 노드를 1개로 할지 2개로 할지 고민될 수 있습니다. 권장 방식은 출력 1개입니다:[^4]
출력 1개 방식이 더 간단하고 직관적이며, 계산 효율도 좋습니다. 레이블도 0/1의 스칼라 값으로 사용하면 됩니다.[^4]
손실 함수 외에도 모델의 정확도(Accuracy)를 계산하여 성능을 평가합니다:[^2]
with torch.no_grad():
predictions = torch.sigmoid(model(X_train))
predicted_classes = (predictions > 0.5).float()
accuracy = (predicted_classes == y_train).float().mean()
print(f'정확도: {accuracy.item() * 100:.2f}%')
임계값(threshold) 0.5를 조정하여 정밀도(precision)와 재현율(recall)의 균형을 맞출 수도 있습니다.[^2]
하이퍼파라미터(Hyperparameter)는 학습 전에 사람이 설정해야 하는 값으로, 모델의 성능과 학습 속도에 큰 영향을 미칩니다. 가중치나 편향과 달리 학습 과정에서 자동으로 업데이트되지 않습니다.[^12]
주요 하이퍼파라미터는 다음과 같습니다:[^13]
학습률은 가장 중요한 하이퍼파라미터로, 기울기를 얼마나 반영하여 매개변수를 업데이트할지 결정합니다.[^12]
학습률이 너무 큰 경우:[^12]
학습률이 너무 작은 경우:[^12]
권장 초기값: 0.001 ~ 0.01 범위에서 시작[^14]
# 학습률 범위 탐색
learning_rates = [0.0001, 0.001, 0.01, 0.1, 1.0]
for lr in learning_rates:
model = MyModel()
optimizer = optim.Adam(model.parameters(), lr=lr)
# 학습 후 검증 손실 비교
고정된 학습률 대신 학습률 스케줄러(Learning Rate Scheduler)를 사용하면 학습이 진행됨에 따라 학습률을 동적으로 조정할 수 있습니다:[^15]
# StepLR: 일정 에포크마다 학습률 감소
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(100):
train(...)
scheduler.step() # 30 에포크마다 lr = lr * 0.1
# ReduceLROnPlateau: 손실이 개선되지 않으면 학습률 감소
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=5)
for epoch in range(100):
val_loss = validate(...)
scheduler.step(val_loss)
배치 크기는 한 번의 순전파와 역전파에 사용되는 샘플 개수입니다.[^13][^12]
큰 배치 크기의 장단점:[^12]
작은 배치 크기의 장단점:[^12]
권장값: 16, 32, 64, 128 중 선택[^14]
# 배치 크기별 DataLoader 생성
batch_sizes = [16, 32, 64, 128]
for bs in batch_sizes:
train_loader = DataLoader(dataset, batch_size=bs, shuffle=True)
# 학습 후 성능 비교
선형 스케일링 규칙(Linear Scaling Rule): 배치 크기를 2배 증가시키면 학습률도 2배 증가시켜야 학습 효율을 유지할 수 있습니다:[^16]
η_new = η_old × (B_new / B_old)
예를 들어, 배치 크기 32에서 학습률 0.01로 학습했다면, 배치 크기를 64로 증가시킬 때 학습률도 0.02로 조정합니다.[^16]
# 배치 크기에 따른 학습률 자동 조정
base_batch_size = 32
base_lr = 0.01
current_batch_size = 128
adjusted_lr = base_lr * (current_batch_size / base_batch_size)
print(f'조정된 학습률: {adjusted_lr}') # 0.04
이 규칙은 대규모 데이터셋과 분산 학습에서 특히 중요합니다.[^16]
에포크는 전체 훈련 데이터를 한 번 완전히 학습하는 것을 의미합니다.[^13][^12]
에포크가 너무 적으면:[^12]
에포크가 너무 많으면:[^12]
조기 종료(Early Stopping) 기법을 사용하면 검증 손실이 개선되지 않을 때 자동으로 학습을 중단할 수 있습니다:[^12]
best_val_loss = float('inf')
patience = 10
trigger_times = 0
for epoch in range(1000):
train_loss = train(...)
val_loss = validate(...)
if val_loss < best_val_loss:
best_val_loss = val_loss
trigger_times = 0
# 모델 저장
torch.save(model.state_dict(), 'best_model.pth')
else:
trigger_times += 1
if trigger_times >= patience:
print('조기 종료!')
break
각 옵티마이저는 장단점이 있습니다:
SGD (Stochastic Gradient Descent):
Adam (Adaptive Moment Estimation):
RMSprop:
# 다양한 옵티마이저 비교
optimizers = {
'SGD': optim.SGD(model.parameters(), lr=0.01),
'SGD+Momentum': optim.SGD(model.parameters(), lr=0.01, momentum=0.9),
'Adam': optim.Adam(model.parameters(), lr=0.001),
'RMSprop': optim.RMSprop(model.parameters(), lr=0.001)
}
그리드 서치(Grid Search): 모든 조합을 시도[^12]
learning_rates = [0.001, 0.01, 0.1]
batch_sizes = [16, 32, 64]
for lr in learning_rates:
for bs in batch_sizes:
# 학습 및 평가
랜덤 서치(Random Search): 무작위로 샘플링하여 효율적으로 탐색[^14]
import random
for trial in range(20):
lr = random.uniform(0.0001, 0.1)
bs = random.choice([16, 32, 64, 128])
# 학습 및 평가
베이지안 최적화: 이전 결과를 바탕으로 다음 탐색 지점 결정 (Ray Tune 등 도구 활용)[^14]
torch.manual_seed() 설정으로 실험 재현 가능하게 만들기
# 재현성을 위한 시드 고정
import random
import numpy as np
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
set_seed(42)
```
핵심 정리: 이진 분류는 시그모이드 활성화 함수와 이진 교차 엔트로피 손실 함수를 사용하며, BCEWithLogitsLoss가 수치적 안정성 면에서 권장됩니다. 하이퍼파라미터 튜닝에서는 학습률과 배치 크기가 가장 중요하며, 조기 종료와 학습률 스케줄러를 활용하여 효율적인 학습을 달성할 수 있습니다. 체계적인 실험과 검증을 통해 최적의 설정을 찾는 것이 모델 성능 향상의 핵심입니다.
[AI 인공지능 머신러닝 딥러닝/Python | PyTorch] - 인스톨! 파이토치 5강
인스톨! 파이토치 5강
[AI 인공지능 머신러닝 딥러닝/Python | PyTorch] - 인스톨! 파이토치 강의 소개 인스톨! 파이토치 강의 소개혁펜하임 PyTorch 강의 오리엔테이션 요약혁펜하임 채널의 '[PyTorch] 0강. 오리엔테이션' 영상
inner-game.tistory.com