이미지 로딩 중...
AI Generated
2025. 11. 19. · 4 Views
Hyperparameter 튜닝 및 학습 설정 완벽 가이드
AI 모델 학습의 핵심인 하이퍼파라미터 튜닝을 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. Learning Rate, Batch Size부터 Mixed Precision Training까지, 실무에서 바로 활용할 수 있는 구체적인 설정 방법과 팁을 제공합니다.
목차
- Learning_Rate_설정
- Batch_Size와_Gradient_Accumulation
- Warmup_Ratio_및_Cosine_Scheduler
- Mixed_Precision_Training_BF16
- Gradient_Clipping_및_Weight_Decay
- Early_Stopping_기준_설정
1. Learning_Rate_설정
시작하며
여러분이 AI 모델을 학습시킬 때 학습이 너무 느리거나, 반대로 loss가 갑자기 발산하는 상황을 겪어본 적 있나요? 몇 시간을 기다렸는데 모델 성능이 전혀 개선되지 않거나, 갑자기 loss 값이 NaN으로 바뀌어버리는 경험 말이죠.
이런 문제는 대부분 Learning Rate(학습률) 설정이 잘못되어서 발생합니다. Learning Rate는 마치 자동차의 액셀러레이터와 같습니다.
너무 세게 밟으면 사고가 나고, 너무 약하게 밟으면 목적지에 도착하지 못하죠. 모델 학습에서 가장 중요한 하이퍼파라미터 중 하나입니다.
바로 이럴 때 필요한 것이 적절한 Learning Rate 설정입니다. TTS나 Voice Cloning 모델의 경우, 2e-4에서 5e-4 사이의 값이 가장 안정적이고 효과적인 것으로 알려져 있습니다.
개요
간단히 말해서, Learning Rate는 모델이 한 번 학습할 때 파라미터를 얼마나 크게 업데이트할지를 결정하는 숫자입니다. AI 모델 학습은 마치 안개 낀 산에서 가장 낮은 곳을 찾아가는 것과 같습니다.
Learning Rate는 한 걸음의 크기를 의미하죠. 너무 큰 걸음으로 가면 가장 낮은 곳을 지나쳐버리고, 너무 작은 걸음으로 가면 평생 걸어도 목적지에 도착하지 못합니다.
TTS나 LLM 같은 복잡한 모델에서는 이 '적절한 걸음 크기'를 찾는 것이 매우 중요합니다. 전통적으로는 0.01이나 0.001 같은 고정된 Learning Rate를 사용했습니다.
하지만 최신 딥러닝 모델에서는 2e-4(0.0002)부터 5e-4(0.0005) 사이의 더 작은 값을 사용합니다. 이는 모델이 수백만, 수억 개의 파라미터를 가지고 있어서 조심스럽게 업데이트해야 하기 때문입니다.
2e-4는 안정적인 학습을 원할 때, 5e-4는 조금 더 빠른 수렴을 원할 때 사용합니다. Voice Cloning처럼 섬세한 조정이 필요한 작업에서는 2e-4가 더 적합하고, 대규모 LLM 파인튜닝에서는 3e-4에서 5e-4 사이가 효과적입니다.
이러한 작은 차이가 최종 모델의 품질을 크게 좌우합니다.
코드 예제
from transformers import TrainingArguments
# TTS/Voice Cloning을 위한 학습 설정
training_args = TrainingArguments(
output_dir="./results",
# 안정적인 학습을 위한 Learning Rate 설정
learning_rate=2e-4, # 0.0002, 보수적인 시작값
# learning_rate=5e-4, # 0.0005, 빠른 수렴이 필요할 때
# Learning Rate의 최솟값 설정 (Cosine 스케줄러와 함께 사용)
learning_rate_min=1e-6, # 너무 작아지지 않도록 제한
num_train_epochs=10,
per_device_train_batch_size=8,
)
설명
이것이 하는 일: Learning Rate는 모델의 가중치(weight)를 얼마나 빠르게 조정할지를 결정합니다. 모델이 데이터를 보고 '아, 이렇게 해야 더 좋겠구나'라고 판단했을 때, 그 판단을 얼마나 강하게 반영할지를 조절하는 것이죠.
첫 번째로, learning_rate=2e-4 설정은 모델이 매우 조심스럽게 학습하도록 만듭니다. 2e-4는 과학적 표기법으로 0.0002를 의미하는데, 이는 모델이 한 번 업데이트할 때 가중치를 0.02%만 변경한다는 뜻입니다.
왜 이렇게 작은 값을 쓸까요? TTS 모델은 수천만 개의 파라미터를 가지고 있어서, 조금만 크게 변경해도 학습이 불안정해지기 때문입니다.
그 다음으로, learning_rate_min=1e-6 설정이 실행되면서 학습 후반부에 Learning Rate가 너무 작아지는 것을 방지합니다. 학습이 진행되면서 Learning Rate를 점점 줄이는데(이걸 Learning Rate Scheduling이라고 합니다), 너무 작아지면 모델이 아예 학습을 멈춥니다.
1e-6(0.000001)이 하한선 역할을 하는 거죠. 마지막으로, 주석 처리된 learning_rate=5e-4 옵션은 더 빠른 수렴이 필요할 때 사용합니다.
이미 pre-trained된 모델을 fine-tuning하거나, 데이터셋이 크고 안정적일 때는 5e-4를 써도 괜찮습니다. 하지만 처음 시작할 때는 2e-4가 더 안전합니다.
여러분이 이 코드를 사용하면 학습 초기에 loss가 발산하는 것을 방지하고, 안정적으로 모델을 수렴시킬 수 있습니다. 특히 음성 합성처럼 미세한 조정이 필요한 작업에서는 작은 Learning Rate가 훨씬 좋은 결과를 만들어냅니다.
또한 GPU 메모리나 배치 크기를 바꿔도 학습이 안정적으로 유지됩니다.
실전 팁
💡 처음에는 항상 2e-4로 시작하세요. 학습이 너무 느리다고 느껴지면 3e-4, 4e-4로 점진적으로 올리는 것이 안전합니다. 한 번에 10배씩 올리면 학습이 망가집니다.
💡 Loss 그래프를 항상 확인하세요. Loss가 지그재그로 흔들리면 Learning Rate가 너무 큰 것이고, 전혀 줄어들지 않으면 너무 작은 것입니다. 부드럽게 감소하는 곡선이 이상적입니다.
💡 배치 크기를 2배로 늘리면 Learning Rate도 1.5배 정도 늘려주는 것이 좋습니다. 예를 들어 batch size 8에서 2e-4를 쓰다가 batch size 16으로 늘리면 3e-4 정도로 조정하세요.
💡 Voice Cloning처럼 개인화가 중요한 작업은 낮은 Learning Rate(2e-4)를, LLM fine-tuning처럼 빠른 적응이 필요한 작업은 높은 Learning Rate(5e-4)를 사용하세요.
💡 학습 초반 몇 에포크의 로그를 저장해두세요. Learning Rate를 바꿔가며 실험한 결과를 비교하면 여러분의 데이터셋에 가장 맞는 값을 찾을 수 있습니다.
2. Batch_Size와_Gradient_Accumulation
시작하며
여러분이 GPU 메모리가 부족해서 "CUDA out of memory" 에러를 본 적 있나요? 논문에서는 batch size 32를 쓰라고 하는데, 여러분의 GPU는 batch size 4만 해도 메모리가 터져버리는 상황 말이죠.
이런 문제는 AI 모델 학습에서 가장 흔하게 발생하는 좌절감 중 하나입니다. 특히 TTS나 LLM처럼 큰 모델을 학습할 때는 더욱 심각합니다.
GPU 메모리는 한정되어 있는데 모델은 점점 커지고, 어떻게 해야 할지 막막하죠. 바로 이럴 때 필요한 것이 Batch Size와 Gradient Accumulation의 조합입니다.
작은 배치 크기로 여러 번 나눠서 계산한 뒤 합치면, 큰 배치 크기와 동일한 효과를 낼 수 있습니다.
개요
간단히 말해서, Batch Size는 한 번에 모델에 넣는 데이터의 개수이고, Gradient Accumulation은 여러 번의 작은 배치를 모아서 마치 큰 배치처럼 학습하는 기법입니다. 식당에서 요리를 한다고 생각해보세요.
Batch Size는 한 번에 요리하는 접시의 개수입니다. 큰 주방(큰 GPU)에서는 한 번에 32개 접시를 만들 수 있지만, 작은 주방(작은 GPU)에서는 8개밖에 못 만듭니다.
Gradient Accumulation은 8개씩 4번 요리해서, 결국 32개를 만드는 것과 같은 효과를 내는 방법이죠. 손님들은 32개를 한 번에 받는 것과 동일한 경험을 하게 됩니다.
전통적으로는 GPU 메모리가 허용하는 최대 Batch Size를 사용했습니다. 하지만 최신 방법에서는 per_device_train_batch_size를 작게 유지하고, gradient_accumulation_steps로 effective batch size를 키웁니다.
예를 들어 batch size 8 × accumulation 4 = effective batch size 32가 됩니다. Batch Size가 클수록 학습이 안정적이지만 메모리를 많이 씁니다.
Gradient Accumulation을 사용하면 메모리는 적게 쓰면서도 큰 배치의 이점을 누릴 수 있습니다. 단, 학습 속도는 약간 느려질 수 있습니다.
이러한 트레이드오프를 이해하고 적절히 조절하는 것이 중요합니다.
코드 예제
from transformers import TrainingArguments
# GPU 메모리 효율적인 학습 설정
training_args = TrainingArguments(
output_dir="./results",
# 실제 GPU에 한 번에 올라가는 배치 크기
per_device_train_batch_size=8, # GPU 메모리에 맞춰 조정
per_device_eval_batch_size=8,
# Gradient Accumulation으로 효과적인 배치 크기 증가
# effective_batch_size = 8 × 4 = 32
gradient_accumulation_steps=4,
# 전체 배치 크기 = 8 × 4 × GPU개수
# 1개 GPU 사용시: 32
# 2개 GPU 사용시: 64
learning_rate=2e-4,
num_train_epochs=10,
)
설명
이것이 하는 일: Batch Size와 Gradient Accumulation의 조합은 제한된 GPU 메모리로도 대규모 배치 학습의 이점을 얻을 수 있게 해줍니다. 작은 조각으로 나눠서 계산한 뒤 합치는 방식이죠.
첫 번째로, per_device_train_batch_size=8 설정은 GPU에 실제로 한 번에 올라가는 데이터의 개수를 지정합니다. 만약 여러분의 GPU가 16GB VRAM을 가지고 있다면, TTS 모델의 경우 batch size 8 정도가 적당합니다.
이 값은 GPU 사양에 맞춰 조정해야 하며, 메모리 에러가 나면 4나 6으로 줄여야 합니다. 그 다음으로, gradient_accumulation_steps=4 설정이 실행되면서 마법이 일어납니다.
모델은 8개 데이터로 gradient를 계산하고, 이를 메모리에 '저장'만 합니다. 그리고 다시 8개를 처리하고, 또 저장하고...
이렇게 4번을 반복합니다. 4번째가 끝나면 저장된 모든 gradient를 평균 내서 한 번에 모델을 업데이트합니다.
결과적으로 32개 데이터를 한 번에 처리한 것과 동일한 효과가 나타나죠. 마지막으로, 주석에 설명된 것처럼 여러 GPU를 사용하면 effective batch size는 더 커집니다.
2개의 GPU를 쓰면 8 × 4 × 2 = 64가 되는 거죠. 이렇게 하면 논문에서 사용하는 큰 배치 크기를 여러분도 사용할 수 있습니다.
여러분이 이 코드를 사용하면 비싼 고성능 GPU 없이도 최신 AI 모델을 효과적으로 학습시킬 수 있습니다. 메모리 에러 없이 안정적으로 학습이 진행되고, 큰 배치 크기의 이점(안정적인 gradient, 더 나은 일반화)도 함께 얻을 수 있습니다.
특히 Voice Cloning처럼 품질이 중요한 작업에서 배치 크기가 클수록 더 좋은 결과가 나옵니다.
실전 팁
💡 GPU 메모리의 80-90%를 사용하는 batch size를 찾으세요. nvidia-smi 명령어로 메모리 사용량을 실시간으로 확인하면서 batch size를 하나씩 늘려보세요. 메모리가 남는 건 낭비입니다.
💡 논문에서 권장하는 effective batch size가 있다면 그것을 목표로 하세요. 예를 들어 논문에서 batch size 64를 쓴다면, 8×8, 16×4, 32×2 등 여러 조합을 시도해보고 가장 빠른 것을 선택하세요.
💡 Gradient Accumulation은 학습 속도를 약간 늦춥니다. 시간이 중요하다면 batch size를 줄이고 accumulation을 줄이는 것도 고려하세요. 하지만 품질은 조금 떨어질 수 있습니다.
💡 평가(evaluation)할 때는 batch size를 크게 해도 됩니다. per_device_eval_batch_size=16이나 32로 설정하면 평가 속도가 훨씬 빨라집니다. 평가는 gradient를 계산하지 않아서 메모리를 덜 씁니다.
💡 Batch size와 Learning Rate는 함께 조정해야 합니다. Effective batch size를 2배로 늘렸다면 Learning Rate도 1.5배 정도 올려주세요. 아니면 학습이 너무 느려집니다.
3. Warmup_Ratio_및_Cosine_Scheduler
시작하며
여러분이 AI 모델 학습을 시작했는데, 처음 몇 스텝에서 loss가 폭발하거나 NaN이 되는 경험을 한 적 있나요? 또는 학습 후반부에 loss가 더 이상 줄어들지 않아서 답답한 상황 말이죠.
이런 문제는 Learning Rate를 처음부터 끝까지 똑같은 값으로 고정했을 때 자주 발생합니다. 모델은 학습 초기와 후기에 서로 다른 학습 전략이 필요합니다.
초기에는 조심스럽게 시작해야 하고, 중간에는 적극적으로 학습하고, 후기에는 미세하게 조정해야 하죠. 바로 이럴 때 필요한 것이 Warmup과 Cosine Scheduler입니다.
Learning Rate를 학습 단계에 따라 똑똑하게 조절하여, 안정적이면서도 효율적인 학습을 가능하게 합니다.
개요
간단히 말해서, Warmup은 학습 초기에 Learning Rate를 천천히 증가시키는 것이고, Cosine Scheduler는 학습 과정에서 Learning Rate를 코사인 곡선 형태로 부드럽게 감소시키는 방법입니다. 자동차 운전을 생각해보세요.
처음 출발할 때 갑자기 액셀을 밟으면 차가 튀거나 엔진이 고장 납니다. 천천히 가속하다가(Warmup), 고속도로에서 속도를 유지하고(학습 중반), 목적지에 가까워지면 부드럽게 감속합니다(Cosine Decay).
Learning Rate Scheduling도 정확히 같은 원리입니다. 전통적으로는 Learning Rate를 고정하거나 계단식으로 줄이는 Step Decay를 사용했습니다.
하지만 최신 방법에서는 warmup_ratio로 초기 몇 %를 Warmup 기간으로 설정하고, lr_scheduler_type="cosine"으로 부드러운 감소를 적용합니다. Transformer 기반 모델(BERT, GPT, TTS)에서 거의 표준이 되었습니다.
Warmup Ratio는 보통 0.050.1(전체 학습의 510%)을 사용합니다. 이 기간 동안 Learning Rate가 0에서 설정한 최댓값까지 선형적으로 증가합니다.
그 이후에는 Cosine 함수 형태로 부드럽게 감소하여 학습 마지막에는 아주 작은 값이 됩니다. 이런 스케줄이 모델이 섬세한 최적점을 찾는 데 도움을 줍니다.
코드 예제
from transformers import TrainingArguments
# Warmup과 Cosine Scheduler를 사용한 학습 설정
training_args = TrainingArguments(
output_dir="./results",
learning_rate=5e-4, # 최대 Learning Rate
# Warmup: 전체 학습의 5%를 Warmup 기간으로 설정
warmup_ratio=0.05, # 0에서 5e-4까지 천천히 증가
# warmup_steps=100, # 또는 직접 스텝 수로 지정 가능
# Cosine Scheduler: 부드러운 감소
lr_scheduler_type="cosine", # cosine, linear, constant 등
# Cosine Scheduler의 최솟값 (너무 작아지지 않도록)
lr_scheduler_kwargs={"eta_min": 1e-6},
num_train_epochs=10,
per_device_train_batch_size=8,
)
설명
이것이 하는 일: Warmup과 Cosine Scheduler는 Learning Rate를 동적으로 조절하여 학습의 각 단계에서 최적의 업데이트 크기를 유지합니다. 초기 불안정성을 방지하고 후기 수렴을 개선하죠.
첫 번째로, warmup_ratio=0.05 설정은 전체 학습 스텝의 5%를 Warmup 기간으로 만듭니다. 예를 들어 총 10,000 스텝을 학습한다면 처음 500 스텝 동안 Learning Rate가 0에서 5e-4까지 천천히 증가합니다.
왜 이렇게 할까요? 모델의 초기 가중치는 랜덤이거나 pre-trained 모델의 것인데, 갑자기 큰 Learning Rate로 업데이트하면 좋은 방향을 찾기 전에 망가질 수 있기 때문입니다.
그 다음으로, lr_scheduler_type="cosine" 설정이 실행되면서 Warmup 이후의 Learning Rate 변화가 결정됩니다. Cosine 함수는 처음에는 천천히 감소하다가 중간쯤에서 빠르게 감소하고, 마지막에는 다시 천천히 감소하는 부드러운 곡선을 그립니다.
이것이 Step Decay(계단식 감소)보다 훨씬 안정적이고 좋은 결과를 만듭니다. 모델이 갑자기 혼란스러워하지 않고 자연스럽게 미세 조정 단계로 들어가는 거죠.
마지막으로, lr_scheduler_kwargs={"eta_min": 1e-6} 설정은 Learning Rate가 너무 작아지는 것을 방지합니다. Cosine Scheduler는 이론적으로 0까지 감소하는데, 너무 작으면 모델이 아예 학습을 멈춥니다.
1e-6을 최솟값으로 설정하면 학습 마지막까지 조금씩이라도 개선이 일어납니다. 여러분이 이 코드를 사용하면 학습 초기 불안정성이 크게 줄어들고, 최종 모델 성능도 2-5% 향상됩니다.
TTS 모델에서는 음질이 더 자연스러워지고, LLM에서는 perplexity가 낮아집니다. 또한 하이퍼파라미터에 덜 민감해져서 여러 실험을 할 때 더 일관된 결과를 얻을 수 있습니다.
실전 팁
💡 큰 모델일수록 Warmup이 중요합니다. LLM이나 TTS처럼 수억 개 파라미터를 가진 모델은 warmup_ratio=0.1(10%)을 사용하고, 작은 모델은 0.03(3%)만 해도 충분합니다.
💡 Warmup과 Cosine을 같이 쓰면 Learning Rate 그래프가 '산' 모양이 됩니다. TensorBoard나 wandb로 이 그래프를 꼭 확인하세요. 예쁜 곡선이 그려지면 제대로 설정된 겁니다.
💡 Transfer Learning(사전 학습된 모델 fine-tuning)에서는 Warmup을 더 길게 하세요. warmup_ratio=0.1~0.2가 좋습니다. 새로운 데이터에 적응하는 데 시간이 필요하기 때문입니다.
💡 Cosine 대신 "cosine_with_restarts"를 쓰면 주기적으로 Learning Rate가 다시 올라갔다 내려갑니다. 학습이 정체될 때 탈출구를 제공하지만, 불안정할 수도 있으니 신중히 선택하세요.
💡 Learning Rate Scheduler를 바꾸면 최적 Learning Rate도 바뀝니다. Cosine Scheduler는 평균 Learning Rate가 낮아서, 고정 Learning Rate보다 1.5배 정도 높게 설정해도 괜찮습니다.
4. Mixed_Precision_Training_BF16
시작하며
여러분이 큰 AI 모델을 학습할 때 GPU 메모리가 부족하거나, 학습 속도가 너무 느려서 며칠씩 기다려야 하는 상황을 겪어본 적 있나요? 최신 LLM이나 TTS 모델은 크기가 수 기가바이트에 달해서, 일반적인 방법으로는 학습 자체가 불가능한 경우도 많습니다.
이런 문제는 모든 숫자를 32비트 실수(float32)로 저장하기 때문에 발생합니다. 높은 정밀도는 좋지만, 메모리를 2배로 쓰고 계산도 느립니다.
특히 요즘처럼 모델 크기가 기하급수적으로 커지는 시대에는 비효율적이죠. 바로 이럴 때 필요한 것이 Mixed Precision Training, 특히 BF16(BFloat16) 형식입니다.
절반 크기의 숫자로 계산하면서도 품질은 거의 그대로 유지하여, 메모리와 속도 문제를 동시에 해결합니다.
개요
간단히 말해서, Mixed Precision Training은 일부 계산을 16비트 실수로 하고 중요한 부분만 32비트로 하는 기법이며, BF16은 범위가 넓어서 딥러닝에 최적화된 16비트 형식입니다. 사진을 생각해보세요.
4K 초고화질 사진은 용량이 크지만, 대부분의 경우 Full HD로 줄여도 사람 눈에는 거의 차이가 없습니다. Mixed Precision도 마찬가지입니다.
모든 숫자를 최고 정밀도로 저장할 필요 없이, '적당한' 정밀도로도 충분히 좋은 결과를 얻을 수 있습니다. 특히 BF16은 FP16보다 표현할 수 있는 범위가 넓어서 딥러닝에서 더 안전합니다.
전통적으로는 모든 계산을 FP32(32비트 실수)로 했습니다. 그 다음에는 FP16(16비트 실수)이 나왔지만 범위가 좁아서 오버플로우가 자주 발생했죠.
최신 방법인 BF16은 FP32와 같은 범위를 가지면서 크기는 절반이라, NVIDIA A100, H100 같은 최신 GPU에서 표준이 되었습니다. BF16을 사용하면 메모리 사용량이 거의 절반으로 줄어들고, 계산 속도는 2~3배 빨라집니다.
품질 손실은 거의 없습니다. NVIDIA Ampere 이후 GPU(A100, RTX 30/40 시리즈)에서만 지원되지만, 지원하는 GPU라면 항상 켜두는 것이 좋습니다.
PyTorch와 Hugging Face Transformers는 이를 자동으로 처리해줍니다.
코드 예제
from transformers import TrainingArguments
# Mixed Precision Training 설정
training_args = TrainingArguments(
output_dir="./results",
# BF16 활성화 (NVIDIA A100, RTX 30/40 시리즈 등)
bf16=True, # BFloat16 사용
# fp16=True, # 구형 GPU는 FP16 사용 (V100, RTX 20 시리즈)
# BF16 완전 평가 모드 (메모리 추가 절약)
bf16_full_eval=True, # 평가시에도 BF16 사용
# 자동 Mixed Precision 최적화 레벨
# "O1": 보수적 (대부분 FP32, 일부만 BF16)
# "O2": 적극적 (대부분 BF16, 일부만 FP32) - 권장
dataloader_num_workers=4, # 데이터 로딩 병렬화
learning_rate=2e-4,
per_device_train_batch_size=16, # BF16으로 배치 크기 2배 증가 가능
)
설명
이것이 하는 일: Mixed Precision Training은 모델의 대부분을 16비트로 계산하고, 정밀도가 중요한 부분(예: gradient 누적, loss 계산)만 32비트로 유지하여 효율성과 정확성을 동시에 확보합니다. 첫 번째로, bf16=True 설정은 모든 forward pass(순전파)와 backward pass(역전파) 계산을 BFloat16 형식으로 수행하도록 만듭니다.
BF16은 8비트 지수와 7비트 가수로 구성되어 있어, FP32(8비트 지수, 23비트 가수)와 같은 범위를 표현할 수 있습니다. 정밀도는 조금 떨어지지만, 딥러닝에서는 이 정도 정밀도로도 충분합니다.
왜냐하면 딥러닝은 수많은 데이터의 평균적인 패턴을 학습하기 때문에, 개별 숫자의 미세한 차이는 크게 중요하지 않기 때문입니다. 그 다음으로, bf16_full_eval=True 설정이 실행되면서 평가(evaluation) 단계에서도 BF16을 사용합니다.
평가는 학습보다 더 많은 데이터를 한 번에 처리할 수 있어서, 메모리를 절약하면 배치 크기를 더 키울 수 있습니다. 이렇게 하면 평가 속도가 훨씬 빨라져서 개발 사이클이 단축됩니다.
마지막으로, 주석에 나온 것처럼 구형 GPU(V100, RTX 20 시리즈)에서는 bf16=False, fp16=True로 설정해야 합니다. FP16은 범위가 좁아서 가끔 오버플로우가 발생하지만, loss_scale을 자동으로 조정하는 기능 덕분에 대부분 잘 작동합니다.
하지만 최신 GPU를 쓴다면 무조건 BF16을 선택하세요. 여러분이 이 코드를 사용하면 같은 GPU로 2배 큰 배치 크기를 사용할 수 있거나, 2배 큰 모델을 학습할 수 있습니다.
학습 속도도 2~3배 빨라져서 실험 주기가 단축됩니다. TTS 모델 학습이 3일 걸렸다면 하루로 줄어들고, LLM fine-tuning이 1주일 걸렸다면 2-3일로 줄어듭니다.
품질은 거의 동일하거나 오히려 약간 나아지는 경우도 있습니다(regularization 효과).
실전 팁
💡 GPU가 BF16을 지원하는지 확인하세요. Python에서 torch.cuda.is_bf16_supported()로 확인할 수 있습니다. False면 fp16=True를 쓰거나 Mixed Precision을 포기해야 합니다.
💡 BF16을 켜면 배치 크기를 2배로 늘려보세요. 메모리가 절반으로 줄어들기 때문에 가능합니다. 더 큰 배치는 더 안정적인 학습을 의미합니다.
💡 Loss가 NaN이 되면 bf16을 의심하세요. 드물지만 BF16의 낮은 정밀도 때문에 문제가 생길 수 있습니다. 이럴 때는 gradient clipping을 더 강하게 하거나(max_grad_norm=0.5), BF16을 끄고 FP32로 돌려보세요.
💡 Inference(추론)에도 BF16을 사용하세요. 학습뿐 아니라 모델을 실제로 사용할 때도 메모리와 속도 이점이 있습니다. model.half() 또는 model.to(torch.bfloat16)으로 변환하세요.
💡 Hugging Face 모델을 로드할 때 torch_dtype=torch.bfloat16을 지정하면 처음부터 BF16으로 로드되어 메모리를 절약합니다. 예: model = AutoModel.from_pretrained("model_name", torch_dtype=torch.bfloat16).
5. Gradient_Clipping_및_Weight_Decay
시작하며
여러분이 AI 모델을 학습하다가 loss가 갑자기 급증하거나, 모델이 학습 데이터만 외워서 새로운 데이터에서는 형편없는 성능을 보이는 경험을 한 적 있나요? 학습 그래프를 보면 loss가 0에 가까운데, 실제로 테스트해보면 쓸모없는 결과가 나오는 상황 말이죠.
이런 문제는 두 가지 원인에서 발생합니다. 첫째는 gradient explosion(기울기 폭발)으로 일부 가중치가 너무 크게 업데이트되는 것이고, 둘째는 overfitting(과적합)으로 모델이 데이터를 지나치게 외워버리는 것입니다.
둘 다 모델의 일반화 능력을 망가뜨립니다. 바로 이럴 때 필요한 것이 Gradient Clipping과 Weight Decay입니다.
Gradient Clipping은 너무 큰 업데이트를 방지하고, Weight Decay는 가중치가 불필요하게 커지는 것을 억제하여, 안정적이고 일반화된 모델을 만듭니다.
개요
간단히 말해서, Gradient Clipping은 gradient의 크기를 제한하여 폭발적인 업데이트를 방지하는 것이고, Weight Decay는 가중치에 작은 페널티를 부여하여 과적합을 방지하는 정규화 기법입니다. 자전거를 탈 때 안전장치를 생각해보세요.
Gradient Clipping은 속도 제한 장치입니다. 아무리 급한 내리막길이어도 위험한 속도 이상으로는 빨라지지 않게 막아줍니다.
Weight Decay는 자전거에 적당한 마찰을 추가하는 것과 같습니다. 페달을 밟지 않으면 자연스럽게 속도가 줄어들어, 불필요하게 과속하는 것을 방지합니다.
전통적으로는 gradient clipping 없이 학습하거나, L2 regularization만 사용했습니다. 하지만 최신 Transformer 모델에서는 max_grad_norm=1.0(gradient를 1.0 이하로 제한)과 weight_decay=0.01(1% 페널티)을 표준으로 사용합니다.
이 조합이 가장 안정적이고 좋은 일반화 성능을 보입니다. Gradient Clipping의 값이 클수록(예: 5.0) 더 자유로운 학습이 가능하지만 불안정할 수 있고, 작을수록(예: 0.5) 안정적이지만 학습이 느립니다.
Weight Decay도 마찬가지로, 클수록(예: 0.1) 더 강한 정규화 효과가 있지만 언더피팅 위험이 있고, 작을수록(예: 0.001) 약한 정규화로 과적합 위험이 있습니다. 데이터와 모델에 맞게 조절해야 합니다.
코드 예제
from transformers import TrainingArguments
# 안정적이고 일반화된 학습을 위한 설정
training_args = TrainingArguments(
output_dir="./results",
learning_rate=2e-4,
# Gradient Clipping: gradient의 norm을 1.0으로 제한
max_grad_norm=1.0, # RNN/Transformer 표준값
# max_grad_norm=0.5, # 더 안정적인 학습이 필요할 때
# max_grad_norm=5.0, # 더 빠른 학습이 필요할 때
# Weight Decay: L2 정규화로 과적합 방지
weight_decay=0.01, # 1% 페널티, Transformer 표준값
# weight_decay=0.1, # 강한 정규화 (데이터가 적을 때)
# weight_decay=0.0, # 정규화 없음 (데이터가 매우 많을 때)
per_device_train_batch_size=8,
num_train_epochs=10,
)
설명
이것이 하는 일: Gradient Clipping과 Weight Decay는 학습 과정을 안정화하고 모델이 훈련 데이터에 과적합되는 것을 방지하여, 실제 환경에서도 잘 작동하는 모델을 만듭니다. 첫 번째로, max_grad_norm=1.0 설정은 모든 gradient의 L2 norm(벡터 크기)을 1.0 이하로 제한합니다.
어떻게 작동할까요? 각 학습 스텝에서 모든 파라미터의 gradient를 계산한 뒤, 그 전체 크기를 측정합니다.
만약 1.5라면 모든 gradient를 1.0/1.5 = 0.67배로 스케일 다운합니다. 이렇게 하면 어떤 이상한 데이터 때문에 gradient가 폭발하더라도, 모델이 망가지지 않습니다.
RNN이나 Transformer처럼 깊은 네트워크에서는 필수입니다. 그 다음으로, weight_decay=0.01 설정이 실행되면서 모든 가중치가 매 스텝마다 0.99배로 조금씩 줄어듭니다.
정확히는 optimizer가 가중치를 업데이트할 때 w = w - lr * (gradient + 0.01 * w)를 계산합니다. 이 0.01 * w 항이 Weight Decay입니다.
가중치가 크면 클수록 더 큰 페널티를 받아서, 모델이 복잡한 패턴을 외우는 대신 간단하고 일반적인 패턴을 학습하게 됩니다. 마지막으로, 주석에 나온 대로 상황에 맞게 값을 조정할 수 있습니다.
학습이 불안정하면 max_grad_norm=0.5로 줄이고, 과적합이 심하면 weight_decay=0.1로 올리세요. 반대로 언더피팅(모델이 충분히 학습하지 못함) 문제가 있으면 둘 다 줄이거나 끄면 됩니다.
여러분이 이 코드를 사용하면 학습 중 loss가 NaN이 되는 현상이 거의 사라집니다. 또한 validation loss와 training loss의 차이(generalization gap)가 줄어들어, 실제 환경에서도 훈련 때와 비슷한 성능을 발휘합니다.
TTS 모델은 더 자연스러운 음성을 생성하고, LLM은 더 일관성 있는 텍스트를 만듭니다.
실전 팁
💡 Loss가 NaN이 되면 max_grad_norm을 더 낮춰보세요(0.5나 0.3). Gradient가 여전히 폭발하고 있다는 신호입니다. 또는 Learning Rate가 너무 높을 수도 있으니 함께 확인하세요.
💡 Validation loss가 training loss보다 훨씬 높으면 과적합입니다. weight_decay를 0.01에서 0.05나 0.1로 올려보세요. 동시에 dropout이나 data augmentation도 고려하세요.
💡 Adam optimizer를 사용할 때 주의하세요. AdamW는 Weight Decay를 올바르게 적용하지만, 일반 Adam은 L2 regularization과 Weight Decay가 다릅니다. Hugging Face는 기본적으로 AdamW를 쓰니까 괜찮습니다.
💡 Pre-trained 모델을 fine-tuning할 때는 weight_decay를 줄이세요(0.001이나 0.0). 이미 일반화되어 있는 가중치를 너무 강하게 regularize하면 성능이 떨어집니다.
💡 Gradient norm을 로깅하여 모니터링하세요. TensorBoard나 wandb에서 grad_norm 그래프를 보면, max_grad_norm이 실제로 적용되는지(gradient가 제한되는지) 확인할 수 있습니다.
6. Early_Stopping_기준_설정
시작하며
여러분이 AI 모델을 며칠 동안 학습시켰는데, 나중에 확인해보니 처음 몇 시간 후부터는 성능이 전혀 개선되지 않았거나 오히려 나빠진 경험이 있나요? GPU 비용과 시간을 낭비한 데다, 과적합된 모델까지 얻게 되는 최악의 상황이죠.
이런 문제는 학습을 '언제 멈춰야 할지' 모르고 무작정 끝까지 돌리기 때문에 발생합니다. 모델은 항상 더 오래 학습한다고 좋아지는 게 아닙니다.
어느 순간부터는 훈련 데이터를 외우기 시작해서, 새로운 데이터에 대한 성능(validation performance)이 떨어집니다. 바로 이럴 때 필요한 것이 Early Stopping(조기 종료)입니다.
Validation loss나 정확도를 모니터링하다가, 더 이상 개선되지 않으면 자동으로 학습을 멈추고 가장 좋았던 모델을 저장합니다.
개요
간단히 말해서, Early Stopping은 validation metric이 일정 기간 동안 개선되지 않으면 학습을 자동으로 중단하는 기법으로, 과적합을 방지하고 리소스를 절약합니다. 마라톤을 생각해보세요.
여러분의 최고 기록이 3시간인데, 어느 대회에서 3시간 10분이 걸렸다고 칩시다. 다음 대회에서도 3시간 15분, 그 다음에도 3시간 20분...
계속 기록이 나빠진다면 뭔가 문제가 있는 거죠. 더 달리기보다는 멈추고 원인을 찾아야 합니다.
Early Stopping도 같은 원리입니다. 성적이 계속 나빠지면 더 학습하지 말고 멈춰야 합니다.
전통적으로는 고정된 epoch 수(예: 100 epoch)를 정해두고 끝까지 학습했습니다. 하지만 최신 방법에서는 EarlyStoppingCallback을 사용하여 validation loss를 모니터링하고, patience(인내심) 기간 동안 개선이 없으면 자동으로 멈춥니다.
이렇게 하면 최적의 시점에서 학습을 종료할 수 있습니다. Early Stopping의 핵심 파라미터는 patience(인내심)입니다.
Patience=3이면 3번의 evaluation 동안 개선이 없으면 멈춥니다. Patience가 너무 작으면(1~2) 너무 일찍 멈출 수 있고, 너무 크면(10 이상) Early Stopping의 의미가 없어집니다.
보통 3~5가 적당합니다. 또한 minimum delta(최소 개선폭)를 설정하여 아주 미세한 개선은 무시할 수도 있습니다.
코드 예제
from transformers import TrainingArguments, EarlyStoppingCallback, Trainer
# Early Stopping을 위한 학습 설정
training_args = TrainingArguments(
output_dir="./results",
learning_rate=2e-4,
num_train_epochs=50, # 최대 epoch 수 (Early Stopping으로 일찍 끝날 수 있음)
# Evaluation 설정 (Early Stopping을 위해 필수)
evaluation_strategy="steps", # 또는 "epoch"
eval_steps=500, # 500 스텝마다 평가
save_strategy="steps", # 평가할 때마다 저장
save_steps=500,
# 최고 성능 모델만 저장
load_best_model_at_end=True, # 학습 종료 시 최고 모델 로드
metric_for_best_model="eval_loss", # 또는 "accuracy", "f1" 등
greater_is_better=False, # loss는 작을수록 좋음 (accuracy는 True)
save_total_limit=3, # 최근 3개 체크포인트만 유지 (디스크 절약)
)
# Early Stopping Callback 설정
early_stopping = EarlyStoppingCallback(
early_stopping_patience=3, # 3번 개선 없으면 중단
early_stopping_threshold=0.001, # 최소 0.1% 개선 필요
)
# Trainer에 콜백 추가
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
callbacks=[early_stopping], # Early Stopping 활성화
)
설명
이것이 하는 일: Early Stopping은 validation dataset에서의 성능을 주기적으로 측정하고, 성능이 정체되거나 나빠지면 학습을 멈춰서 시간과 자원을 절약하며 최적의 모델을 얻게 합니다. 첫 번째로, evaluation_strategy="steps"와 eval_steps=500 설정은 500 스텝마다 validation dataset에서 모델을 평가하도록 만듭니다.
이 평가 결과(eval_loss, eval_accuracy 등)가 Early Stopping의 판단 기준이 됩니다. "epoch" 전략을 쓰면 매 epoch마다 평가하는데, epoch가 길면 너무 드물게 체크하게 되므로 "steps"가 더 세밀합니다.
그 다음으로, load_best_model_at_end=True와 metric_for_best_model="eval_loss" 설정이 실행되면서 학습 과정에서 가장 좋았던 모델을 자동으로 찾아 저장합니다. 예를 들어 10번째 evaluation에서 eval_loss가 0.5로 최저였는데, 이후 계속 올라갔다면 학습 종료 시 10번째 체크포인트를 최종 모델로 사용합니다.
마지막 모델이 아니라 '최고' 모델을 얻는 거죠. 마지막으로, EarlyStoppingCallback 설정이 실제 Early Stopping을 수행합니다.
early_stopping_patience=3은 3번의 evaluation 동안 개선이 없으면 학습을 중단합니다. early_stopping_threshold=0.001은 최소 0.1% 개선을 요구하므로, eval_loss가 0.500에서 0.499로 줄어든 것은 너무 미세해서 개선으로 인정하지 않습니다.
이렇게 하면 노이즈 때문에 잘못된 판단을 내리는 것을 방지합니다. 여러분이 이 코드를 사용하면 학습 시간이 평균 30-50% 절약됩니다.
50 epoch를 설정했지만 실제로는 20-30 epoch에서 멈추는 경우가 많습니다. GPU 비용도 그만큼 줄고, 과적합도 방지되어 실제 사용 환경에서 더 좋은 성능을 발휘합니다.
또한 여러 실험을 동시에 돌릴 때, 쓸모없는 실험을 일찍 중단하여 리소스를 효율적으로 배분할 수 있습니다.
실전 팁
💡 Patience는 데이터셋 크기에 맞춰 조정하세요. 작은 데이터셋(수천 개)은 patience=3, 큰 데이터셋(수백만 개)은 patience=5~10이 적당합니다. 큰 데이터셋은 변동성이 커서 더 기다려야 진짜 개선인지 알 수 있습니다.
💡 여러 metric을 함께 보세요. eval_loss만 보지 말고 eval_accuracy, eval_f1 등도 로깅하세요. Loss는 줄어드는데 accuracy는 안 올라가면 뭔가 문제가 있는 겁니다.
💡 Learning Rate Scheduler와 함께 쓸 때 주의하세요. Cosine Scheduler는 후반에 Learning Rate가 작아서 개선이 느립니다. Patience를 조금 더 크게(5~7) 설정하여 충분한 기회를 주세요.
💡 save_total_limit=3으로 디스크 공간을 절약하세요. TTS나 LLM 모델은 체크포인트 하나가 수 GB라서, 모든 체크포인트를 저장하면 금방 디스크가 찹니다. 최근 3개만 유지하면 충분합니다.
💡 첫 번째 실험에서는 Early Stopping을 끄고 끝까지 돌려보세요. 학습 곡선 전체를 보면 적절한 patience와 threshold 값을 찾을 수 있습니다. 그 다음 실험부터는 Early Stopping을 켜서 시간을 절약하세요.