딥러닝 Seed 고정

김동준·2025년 10월 14일

딥러닝 Seed 고정 완벽 가이드

🎲 Seed란 무엇인가?

Seed(시드)는 난수 생성기의 '시작점'을 의미합니다. 마치 주사위를 던지는 게임에서 항상 같은 결과를 얻고 싶을 때 사용하는 "치트키"와 같습니다.

🎰 실생활 비유

  • 슬롯머신: 같은 시간, 같은 방법으로 레버를 당기면 같은 결과가 나오도록 설정
  • 게임 맵 생성: 마인크래프트에서 Seed 번호를 입력하면 항상 같은 지형이 생성됨
  • 복권 번호: 같은 알고리즘으로 "랜덤" 번호를 뽑지만, 시작점을 고정하면 항상 같은 번호 생성

🤔 왜 딥러닝에서 Seed를 고정해야 하나?

딥러닝에는 많은 랜덤 요소가 있습니다:

1️⃣ 가중치 초기화 (Weight Initialization)

신경망의 시작 가중치를 랜덤하게 설정합니다.

예시 3가지:

# 예시 1: He 초기화
torch.nn.init.kaiming_normal_(layer.weight)
# → 매번 다른 값으로 시작!

# 예시 2: Xavier 초기화  
torch.nn.init.xavier_uniform_(layer.weight)
# → 실행할 때마다 다른 초기 가중치

# 예시 3: 단순 랜덤 초기화
layer.weight = torch.randn(10, 5)
# → 완전히 다른 시작점

Seed 고정 안 하면:

  • 실험 1: 정확도 85.3%
  • 실험 2: 정확도 84.7%
  • 실험 3: 정확도 85.9%
  • → 어떤 결과가 진짜인지 모름! 😵

2️⃣ 데이터 셔플링 (Data Shuffling)

학습 데이터를 섞는 순서가 매번 달라집니다.

예시 3가지:

# 예시 1: DataLoader 셔플
train_loader = DataLoader(dataset, shuffle=True)
# → 매 epoch마다 다른 순서로 데이터 공급

# 예시 2: Train/Valid 분할
train_data, valid_data = train_test_split(data, test_size=0.2)
# → 매번 다른 데이터가 검증 세트에 포함

# 예시 3: 데이터 증강 순서
random.shuffle(augmentation_list)
# → 증강 기법이 적용되는 순서가 매번 변경

비유: 카드 게임에서 매번 다르게 섞으면 같은 전략을 테스트할 수 없음

3️⃣ Dropout (드롭아웃)

학습 중 일부 뉴런을 랜덤하게 꺼서 과적합 방지합니다.

예시 3가지:

# 예시 1: 기본 Dropout
dropout = nn.Dropout(p=0.5)
# → 매번 다른 50%의 뉴런을 끔

# 예시 2: Spatial Dropout (이미지용)
dropout2d = nn.Dropout2d(p=0.3)
# → 매번 다른 채널을 드롭

# 예시 3: DropConnect
# → 가중치 연결을 랜덤하게 끊음

비유: 연습할 때마다 다른 선수를 벤치에 앉히는 것


🔧 각 라이브러리별 Seed 고정 상세 설명

1. Python 기본 Random 모듈

import random
random.seed(2021)

영향을 받는 것들:

예시 1: 리스트 셔플

# Seed 고정 전
my_list = [1, 2, 3, 4, 5]
random.shuffle(my_list)  # 실행 1: [3, 1, 5, 2, 4]
random.shuffle(my_list)  # 실행 2: [2, 5, 1, 4, 3]

# Seed 고정 후
random.seed(2021)
my_list = [1, 2, 3, 4, 5]
random.shuffle(my_list)  # 항상: [3, 1, 5, 2, 4]

예시 2: 랜덤 선택

# Seed 고정 전
colors = ['red', 'blue', 'green']
random.choice(colors)  # 매번 다른 색

# Seed 고정 후
random.seed(2021)
random.choice(colors)  # 항상 같은 색

예시 3: 랜덤 숫자 생성

# Seed 고정 전
random.randint(1, 100)  # 매번 다른 숫자

# Seed 고정 후
random.seed(2021)
random.randint(1, 100)  # 항상 같은 숫자

2. NumPy Random 모듈

import numpy as np
np.random.seed(2021)

영향을 받는 것들:

예시 1: 배열 셔플

# Seed 고정 전
arr = np.array([1, 2, 3, 4, 5])
np.random.shuffle(arr)  # 매번 다른 순서

# Seed 고정 후
np.random.seed(2021)
arr = np.array([1, 2, 3, 4, 5])
np.random.shuffle(arr)  # 항상 같은 순서

예시 2: 정규분포 샘플링

# Seed 고정 전
samples = np.random.normal(0, 1, size=100)  # 매번 다른 값들

# Seed 고정 후
np.random.seed(2021)
samples = np.random.normal(0, 1, size=100)  # 항상 같은 값들

예시 3: Train/Test 분할

# Seed 고정 전
from sklearn.model_selection import train_test_split
X_train, X_test = train_test_split(X, test_size=0.2)  # 매번 다른 분할

# Seed 고정 후
np.random.seed(2021)
X_train, X_test = train_test_split(X, test_size=0.2, random_state=2021)  # 같은 분할

3. PyTorch Random

import torch
torch.manual_seed(2021)
torch.cuda.manual_seed_all(2021)

영향을 받는 것들:

예시 1: 가중치 초기화

# Seed 고정 전
layer = nn.Linear(10, 5)
print(layer.weight)  # 매번 다른 초기 가중치

# Seed 고정 후
torch.manual_seed(2021)
layer = nn.Linear(10, 5)
print(layer.weight)  # 항상 같은 초기 가중치

예시 2: Dropout 동작

# Seed 고정 전
dropout = nn.Dropout(0.5)
x = torch.ones(10)
output = dropout(x)  # 매번 다른 뉴런이 꺼짐

# Seed 고정 후
torch.manual_seed(2021)
dropout = nn.Dropout(0.5)
x = torch.ones(10)
output = dropout(x)  # 항상 같은 뉴런이 꺼짐

예시 3: 랜덤 텐서 생성

# Seed 고정 전
tensor = torch.randn(3, 3)  # 매번 다른 값

# Seed 고정 후
torch.manual_seed(2021)
tensor = torch.randn(3, 3)  # 항상 같은 값

4. cuDNN Random (GPU 연산)

torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

영향을 받는 것들:

예시 1: 합성곱 연산

# deterministic = False (기본값)
conv = nn.Conv2d(3, 64, 3).cuda()
output = conv(input)  # GPU에서 미세하게 다른 결과

# deterministic = True
torch.backends.cudnn.deterministic = True
output = conv(input)  # 항상 정확히 같은 결과

예시 2: 배치 정규화

# deterministic = False
bn = nn.BatchNorm2d(64).cuda()
output = bn(input)  # 부동소수점 오차로 미세한 차이

# deterministic = True
torch.backends.cudnn.deterministic = True
output = bn(input)  # 완벽히 동일한 결과

예시 3: RNN/LSTM 연산

# deterministic = False
lstm = nn.LSTM(100, 50).cuda()
output, hidden = lstm(input)  # 미세한 차이 발생

# deterministic = True
torch.backends.cudnn.deterministic = True
output, hidden = lstm(input)  # 완전히 동일한 결과

📊 Seed 고정의 효과 비교

시나리오 1: 논문 재현 실험

Seed 고정 안 함:

실험 1: Loss 0.234, Accuracy 87.3%
실험 2: Loss 0.229, Accuracy 87.8%
실험 3: Loss 0.241, Accuracy 86.9%
→ 어떤 결과를 논문에 쓸까? 🤔

Seed 고정함:

실험 1: Loss 0.234, Accuracy 87.3%
실험 2: Loss 0.234, Accuracy 87.3%
실험 3: Loss 0.234, Accuracy 87.3%
→ 신뢰할 수 있는 결과! ✅

시나리오 2: 하이퍼파라미터 튜닝

Seed 고정 안 함:

Learning Rate 0.001: Accuracy 85.2%
Learning Rate 0.01:  Accuracy 84.9%
→ 0.001이 더 좋은가? 아니면 운이 좋았나? 🎲

Seed 고정함:

Learning Rate 0.001: Accuracy 85.2%
Learning Rate 0.01:  Accuracy 87.1%
→ 0.01이 명확히 더 좋음! 📈

시나리오 3: 디버깅

Seed 고정 안 함:

1차 실행: 에러 발생 없음
2차 실행: NaN 에러 발생!
3차 실행: 에러 발생 없음
→ 버그를 찾을 수 없음 😰

Seed 고정함:

1차 실행: NaN 에러 발생!
2차 실행: NaN 에러 발생!
3차 실행: NaN 에러 발생!
→ 버그를 정확히 재현하고 수정 가능! 🔧

⚡ 성능 vs 재현성 트레이드오프

cuDNN Benchmark의 영향

# 🚀 빠른 학습 (재현성 낮음)
torch.backends.cudnn.benchmark = True
torch.backends.cudnn.deterministic = False
# → 학습 시간: 100초
# → 매 실행마다 0.5% 차이

# ✅ 느린 학습 (완벽한 재현성)
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
# → 학습 시간: 115초 (15% 느림)
# → 매 실행마다 완전히 동일

실무 권장사항

1. 개발/실험 단계 (빠른 속도 우선)

# cuDNN 최적화 ON
torch.backends.cudnn.benchmark = True
torch.backends.cudnn.deterministic = False

# 기본 seed만 고정
random.seed(2021)
np.random.seed(2021)
torch.manual_seed(2021)

2. 논문/발표용 (완벽한 재현성)

# cuDNN 최적화 OFF
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True

# 모든 seed 고정
random.seed(2021)
np.random.seed(2021)
torch.manual_seed(2021)
torch.cuda.manual_seed_all(2021)

3. 대회/제출용 (균형)

# 적당한 재현성
torch.backends.cudnn.benchmark = False  # 알고리즘 고정
torch.backends.cudnn.deterministic = False  # 약간의 비결정성 허용

# 기본 seed 고정
random.seed(2021)
np.random.seed(2021)
torch.manual_seed(2021)

🎯 완벽한 Seed 고정 체크리스트

✅ 필수 항목 (항상 해야 함)

  • random.seed() 설정
  • np.random.seed() 설정
  • torch.manual_seed() 설정
  • GPU 사용 시 torch.cuda.manual_seed_all() 설정

⚠️ 선택 항목 (상황에 따라)

  • torch.backends.cudnn.deterministic = True (재현성 필요시)
  • torch.backends.cudnn.benchmark = False (재현성 필요시)
  • DataLoader에 worker_init_fn 설정 (멀티프로세싱 사용시)

📝 추가 고려사항

  • random_state 파라미터를 지원하는 함수들에 일관된 seed 전달
  • 데이터 증강 라이브러리의 seed 설정
  • 외부 라이브러리(OpenCV 등)의 random seed 확인

💡 실전 팁

Tip 1: Worker Init Function (멀티프로세싱)

def seed_worker(worker_id):
    worker_seed = torch.initial_seed() % 2**32
    np.random.seed(worker_seed)
    random.seed(worker_seed)

train_loader = DataLoader(
    dataset, 
    batch_size=32,
    num_workers=4,
    worker_init_fn=seed_worker  # 각 워커의 seed 고정
)

Tip 2: 함수로 만들어서 재사용

def set_seed(seed=2021):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    
# 매 실험마다 호출
set_seed(2021)

Tip 3: 실험별로 다른 Seed 사용

# 5번 반복 실험으로 평균 성능 측정
results = []
for seed in [2021, 2022, 2023, 2024, 2025]:
    set_seed(seed)
    accuracy = train_and_evaluate()
    results.append(accuracy)

print(f"평균 정확도: {np.mean(results):.2f} ± {np.std(results):.2f}")
# 출력: 평균 정확도: 87.23 ± 0.45

🎓 핵심 요약

  1. Seed = 난수의 시작점 → 같은 시작점 = 같은 결과
  2. 4가지 seed 모두 고정 → 완벽한 재현성
  3. cuDNN 고정은 선택사항 → 속도 vs 정확성 트레이드오프
  4. 실무에서는 상황에 맞게 → 개발/논문/대회 별로 다르게 설정

재현 가능한 실험은 과학적 연구의 기본입니다! 🔬

profile
Story Engineer

0개의 댓글