Problem Solving with Algorithms

728x90
반응형

[AI 인공지능 머신러닝 딥러닝/Python | PyTorch] - 인스톨! 파이토치 강의 소개

 

인스톨! 파이토치 강의 소개

혁펜하임 PyTorch 강의 오리엔테이션 요약혁펜하임 채널의 '[PyTorch] 0강. 오리엔테이션' 영상은 채널 5주년 기념으로 '인스톨! 파이토치' 강의를 소개하는 내용입니다. 강의자는 최근 출간한 '이론

inner-game.tistory.com

 

 

 

 

인스톨! 파이토치

 

딥러닝에서 분류(Classification)는 회귀와 함께 가장 중요한 작업 유형입니다. 특히 이진 분류(Binary Classification)는 스팸 메일 필터링, 질병 진단, 고객 이탈 예측 등 실생활의 수많은 문제를 해결하는 데 사용됩니다. 4강에서는 파이토치로 이진 분류 모델을 구현하는 전체 과정과, 모델 성능을 극대화하기 위한 하이퍼파라미터 튜닝 전략을 마스터할 수 있습니다.[^1][^2]

4-1강. 파이토치 튜토리얼 코드 | 이진 분류 예제 코드 15분 컷!

이진 분류의 기본 개념

이진 분류는 입력 데이터를 두 개의 클래스(0 또는 1, True 또는 False) 중 하나로 분류하는 작업입니다. 선형 회귀와 달리 이진 분류는 연속적인 값이 아닌 이산적인 범주를 예측하므로, 활성화 함수와 손실 함수가 달라집니다.[^2][^3][^1]

이진 분류의 핵심은 모델의 출력을 확률값(0과 1 사이)으로 변환하는 것입니다. 예를 들어, 출력이 0.8이면 "클래스 1에 속할 확률이 80%"라는 의미이며, 일반적으로 0.5를 기준(threshold)으로 0 또는 1로 최종 분류합니다.[^4][^2]

시그모이드(Sigmoid) 활성화 함수

선형 레이어의 출력은 -∞부터 +∞까지의 임의의 실수값을 가질 수 있습니다. 이를 0과 1 사이의 확률로 변환하기 위해 시그모이드 함수(Sigmoid Function)를 사용합니다:[^5][^4][^2]

σ(x) = 1 / (1 + e^(-x))

시그모이드 함수는 다음과 같은 특성을 가집니다:[^2]

  • 입력이 매우 큰 양수일 때 → 출력은 1에 근접
  • 입력이 매우 큰 음수일 때 → 출력은 0에 근접
  • 입력이 0일 때 → 출력은 정확히 0.5
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])

이진 교차 엔트로피(Binary Cross-Entropy) 손실 함수

이진 분류에서는 평균 제곱 오차(MSE) 대신 이진 교차 엔트로피(Binary Cross-Entropy, BCE)를 손실 함수로 사용합니다. MSE는 예측값과 실제값의 차이가 작을 때 오차도 작아져 학습이 제대로 진행되지 않지만, BCE는 로그 함수를 활용해 이 문제를 해결합니다.[^3][^1]

BCE 손실 함수의 수식은 다음과 같습니다:[^1]

BCE = -[y·log(ŷ) + (1-y)·log(1-ŷ)]

여기서 y는 실제 레이블(0 또는 1), ŷ는 모델이 예측한 확률값입니다. 이 손실 함수는 모델이 확신을 가지고 잘못된 예측을 할 때 큰 패널티를 부여합니다.[^1]

BCELoss vs BCEWithLogitsLoss

파이토치는 두 가지 BCE 손실 함수를 제공합니다:[^6][^7]

  1. nn.BCELoss(): 이미 시그모이드가 적용된 확률값을 입력으로 받음[^8]
  2. 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개 vs 2개

이진 분류에서 출력 노드를 1개로 할지 2개로 할지 고민될 수 있습니다. 권장 방식은 출력 1개입니다:[^4]

  • 출력 1개: 시그모이드 사용, BCE 손실 함수 사용
  • 출력 2개: 소프트맥스 사용, CrossEntropy 손실 함수 사용

출력 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]

4-2강. 파이토치 하이퍼파라미터 튜닝 기본 규칙 & 팁들

하이퍼파라미터란?

하이퍼파라미터(Hyperparameter)는 학습 전에 사람이 설정해야 하는 값으로, 모델의 성능과 학습 속도에 큰 영향을 미칩니다. 가중치나 편향과 달리 학습 과정에서 자동으로 업데이트되지 않습니다.[^12]

주요 하이퍼파라미터는 다음과 같습니다:[^13]

  • 학습률(Learning Rate): 매개변수 업데이트 크기
  • 배치 크기(Batch Size): 한 번에 처리하는 샘플 수
  • 에포크 수(Epochs): 전체 데이터를 몇 번 반복할지
  • 레이어 수와 노드 수: 모델의 복잡도
  • 옵티마이저 종류: SGD, Adam, RMSprop 등

학습률(Learning Rate) 튜닝

학습률은 가장 중요한 하이퍼파라미터로, 기울기를 얼마나 반영하여 매개변수를 업데이트할지 결정합니다.[^12]

학습률이 너무 큰 경우:[^12]

  • 손실이 발산하거나 진동함
  • 최적점을 뛰어넘어 수렴하지 못함
  • 학습이 불안정함

학습률이 너무 작은 경우:[^12]

  • 학습 속도가 매우 느림
  • 지역 최소점(local minimum)에 갇힐 위험 증가
  • 학습 시간이 과도하게 길어짐

권장 초기값: 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)

배치 크기(Batch Size) 선택

배치 크기는 한 번의 순전파와 역전파에 사용되는 샘플 개수입니다.[^13][^12]

큰 배치 크기의 장단점:[^12]

  • 장점: GPU 활용률 높음, 학습 속도 빠름, 기울기가 안정적
  • 단점: 메모리 많이 사용, 일반화 성능 저하 가능, 지역 최소점에 갇히기 쉬움

작은 배치 크기의 장단점:[^12]

  • 장점: 메모리 효율적, 일반화 성능 좋음, 지역 최소점 탈출 용이
  • 단점: 기울기가 불안정, 학습 시간 오래 걸림, GPU 활용률 낮음

권장값: 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]

에포크 수(Epochs) 설정

에포크는 전체 훈련 데이터를 한 번 완전히 학습하는 것을 의미합니다.[^13][^12]

에포크가 너무 적으면:[^12]

  • 모델이 데이터의 패턴을 충분히 학습하지 못함
  • 과소적합(underfitting) 발생

에포크가 너무 많으면:[^12]

  • 훈련 데이터에 과도하게 최적화됨
  • 과적합(overfitting) 발생
  • 검증 손실이 다시 증가하기 시작

조기 종료(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):

  • 가장 기본적인 옵티마이저
  • 모멘텀(momentum) 추가 시 성능 향상
  • 학습률 튜닝이 중요

Adam (Adaptive Moment Estimation):

  • 학습률을 자동으로 조정
  • 대부분의 경우 잘 작동
  • 기본 선택으로 권장

RMSprop:

  • RNN에 적합
  • 학습률이 급격히 변하는 문제 해결
# 다양한 옵티마이저 비교
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]

실전 튜닝 팁

  1. 학습률 먼저 튜닝: 가장 영향력이 큰 하이퍼파라미터부터 시작[^12]
  2. 로그 스케일 사용: 학습률은 로그 스케일(0.0001, 0.001, 0.01, 0.1)로 탐색[^14]
  3. 검증 세트 활용: 테스트 세트는 최종 평가에만 사용[^12]
  4. 학습 곡선 관찰: 손실과 정확도 그래프를 통해 과적합/과소적합 진단
  5. 재현성 확보: 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

 

 

728x90
반응형
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
250x250