Seed(시드)는 난수 생성기의 '시작점'을 의미합니다. 마치 주사위를 던지는 게임에서 항상 같은 결과를 얻고 싶을 때 사용하는 "치트키"와 같습니다.
딥러닝에는 많은 랜덤 요소가 있습니다:
신경망의 시작 가중치를 랜덤하게 설정합니다.
예시 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 고정 안 하면:
학습 데이터를 섞는 순서가 매번 달라집니다.
예시 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가지:
# 예시 1: 기본 Dropout
dropout = nn.Dropout(p=0.5)
# → 매번 다른 50%의 뉴런을 끔
# 예시 2: Spatial Dropout (이미지용)
dropout2d = nn.Dropout2d(p=0.3)
# → 매번 다른 채널을 드롭
# 예시 3: DropConnect
# → 가중치 연결을 랜덤하게 끊음
비유: 연습할 때마다 다른 선수를 벤치에 앉히는 것
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) # 항상 같은 숫자
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) # 같은 분할
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) # 항상 같은 값
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: 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
🎓 핵심 요약
- Seed = 난수의 시작점 → 같은 시작점 = 같은 결과
- 4가지 seed 모두 고정 → 완벽한 재현성
- cuDNN 고정은 선택사항 → 속도 vs 정확성 트레이드오프
- 실무에서는 상황에 맞게 → 개발/논문/대회 별로 다르게 설정
재현 가능한 실험은 과학적 연구의 기본입니다! 🔬