인공지능 모델을 파인튜닝할 때 Gradient Clipping이라는 기술을 자주 사용합니다. 이 기술은 새로운 데이터를 학습할 때 모델의 가중치가 급격하게 변화하는 것을 방지하는 역할을 합니다. 이번 포스팅에서는 Gradient Clipping의 원리와 작동 방식을 좀 더 자세히 알아보겠습니다.
그래디언트 클래핑이란?
신경망 학습 과정에서 발생하는 그래디언트 폭주(Gradient Exploding) 문제를 해결하는 중요한 기법입니다. 그래디언트 폭주는 역전파 과정에서 그래디언트가 지나치게 커져 가중치 업데이트가 불안정해지는 현상을 말합니다. 이는 학습을 크게 방해할 수 있습니다. 그래디언트 클리핑은 그래디언트의 크기에 상한선을 설정하여 이를 초과하는 경우 그래디언트를 줄여주는 기술입니다.
그래디언트 클래핑이 발생하는 이유는?
- 학습률이 너무 높은 경우: 한 번의 업데이트로 파라미터가 크게 변하면서 그래디언트가 급격히 증가할 수 있습니다.
- 데이터의 특성이 극단적인 경우: 입력 데이터의 값이 매우 크거나 작으면 이로 인해 그래디언트가 증폭될 수 있습니다.
- 신경망의 구조가 복잡한 경우: 층이 깊거나 뉴런 수가 많으면 그래디언트가 누적되어 커질 가능성이 높아집니다.
- 활성화 함수의 특성: ReLU 함수 사용 시 일부 뉴런이 지속적으로 활성화되어 그래디언트가 누적되는 현상이 발생할 수 있습니다.
- 순환 신경망(RNN)에서 긴 시퀀스 처리: 시퀀스가 길어질수록 그래디언트가 폭발적으로 증가할 수 있습니다.
- 파인튜닝 과정에서의 문제:
- 모델이 특정 도메인에 과적합되어 새로운 데이터에 대해 큰 오차가 발생할 수 있습니다.
- 학습 데이터와 새로운 데이터의 특징 공간 차이로 인해 극단적인 활성화 값이 생성될 수 있습니다.
- 모델이 익숙하지 않은 데이터에 대해 불안정하거나 극단적인 예측을 하여 손실 함수 값이 크게 증가할 수 있습니다.
이러한 요인들이 단독으로 또는 복합적으로 작용하여 그래디언트 폭발 문제를 일으킬 수 있습니다.
그래디언트 클래핑 작동 방식
이 그래프는 그래디언트 클리핑(Gradient Clipping)의 원리를 시각적으로 나타낸 것입니다.
- X축은 원래 그래디언트의 크기 (||∇L||)를 나타냅니다.
- Y축은 클리핑 후의 그래디언트 크기 (||∇L||clipped)를 나타냅니다.
- C는 클리핑 임계값(threshold)을 나타냅니다.
그래프의 동작은 다음과 같습니다:
- 그래디언트의 크기가 C보다 작거나 같을 때 (||∇L|| ≤ C):
- 클리핑이 적용되지 않습니다.
- 그래프에서 45도 각도의 직선으로 표현됩니다.
- 그래디언트의 크기가 C보다 클 때 (||∇L|| > C):
- 그래디언트는 C 값으로 클리핑됩니다.
- 그래프에서 수평선으로 표현됩니다.
이 문제를 해결하기 위해 그래디언트 클리핑에서는 그래디언트 값에 상한선 C를 설정합니다.
계산된 그래디언트가 이 상한선을 초과하면 더 작은 값으로 ‘잘라냅니다’.
구체적으로, 전체 그래디언트의 L2 노름(크기)을 계산하고, 이 값이 C보다 크면 그래디언트의 각 요소를 일정 비율로 축소합니다.
이는 원래 그래디언트에 (C / L2 노름)을 곱하는 방식으로 이루어집니다.
예를 들어, 모델의 그래디언트가 [10, -20]이고 우리가 설정한 상한선 C가 5라고 가정해 봅시다.
이 때 그래디언트 클리핑은 다음과 같은 과정으로 진행됩니다:
먼저, 그래디언트의 L2 노름을 계산합니다.
- sqrt(10^2 + (-20)^2) = sqrt(100 + 400) = sqrt(500) ≈ 22.36입니다.
이 L2 노름 값 22.36은 우리가 설정한 상한선 5를 초과합니다.
따라서 그래디언트를 조정해야 합니다.
조정 비율을 계산하기 위해 상한선 C를 L2 노름으로 나눕니다. 즉, 5 / 22.36 ≈ 0.22입니다.
이제 원래 그래디언트의 각 요소에 이 비율 0.22를 곱합니다:
- 10 * 0.22 ≈ 2.24
- -20 * 0.22 ≈ -4.47
결과적으로, 클리핑된 그래디언트는 [2.24, -4.47]이 됩니다.
이 방법을 통해 그래디언트의 방향은 그대로 유지하면서 크기만 줄일 수 있습니다.
클리핑된 그래디언트의 L2 노름을 다시 계산해 보면 sqrt(2.24^2 + (-4.47)^2) ≈ 5로,
우리가 설정한 상한선과 같아집니다.
이러한 과정을 통해 그래디언트의 크기를 제한하면서도 학습의 방향성은 유지할 수 있어, 안정적인 학습이 가능해집니다.
Gradient Clipping 코드로 살펴보기
# Assuming a neural network model `model` and a loss function `loss_fn`
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for batch_data, batch_labels in dataloader:
optimizer.zero_grad() # Reset gradients
# Forward pass
outputs = model(batch_data)
loss = loss_fn(outputs, batch_labels)
# Backward pass to compute gradients
loss.backward()
# Clip gradients (e.g., C = 5)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5)
# Update weights
optimizer.step()
이 코드는 PyTorch로 작성된 신경망 훈련 과정에서 gradient clipping을 적용하는 예시를 준비했습니다.
각 부분을 순서대로 자세히 설명해 드리겠습니다.
1. optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
optimizer
정의:- 여기서는
Adam
옵티마이저를 사용하여 모델의 파라미터를 업데이트합니다. Adam
은 적응적 학습률을 사용하는 알고리즘으로, 학습 과정에서 각각의 파라미터에 대해 학습률을 개별적으로 조정합니다.model.parameters()
는 신경망 모델의 학습 가능한 파라미터들을 반환하며, 옵티마이저가 이 파라미터들을 업데이트합니다.lr=1e-3
은 학습률을 의미하며, 여기서는 0.001로 설정했습니다. 학습률은 파라미터가 업데이트되는 속도를 결정하는 중요한 하이퍼파라미터입니다.
- 여기서는
2. for batch_data, batch_labels in dataloader:
- 배치 단위 학습:
dataloader
는 데이터셋을 배치(batch) 단위로 나누어 네트워크에 공급하는 역할을 합니다.- 각
batch_data
는 입력 데이터의 한 배치를,batch_labels
는 그에 대응하는 정답 레이블(batch labels)을 나타냅니다. - 이 코드 블록에서는 전체 데이터를 작은 배치로 나누어 신경망에 입력하고, 각 배치마다 훈련을 진행합니다.
3. optimizer.zero_grad()
- 기울기 초기화:
- PyTorch에서는 이전 배치에서 계산된 기울기가 계속 누적되기 때문에, 새로운 배치에 대해 기울기를 계산하기 전에 기존의 기울기를 0으로 초기화해야 합니다.
optimizer.zero_grad()
를 호출함으로써 이전의 기울기 값을 모두 0으로 설정하여 이전 배치의 영향이 이번 배치의 기울기 계산에 영향을 미치지 않도록 합니다.
4. outputs = model(batch_data)
- 순전파(Forward pass):
batch_data
를 모델에 입력하고, 모델이 이 데이터를 사용하여 출력을 생성합니다.outputs
은 모델이 예측한 값들을 나타내며, 이 값을 실제 레이블과 비교하여 손실을 계산하게 됩니다.- 모델은 신경망 레이어를 거쳐 데이터를 처리하며 예측값을 산출합니다.
5. loss = loss_fn(outputs, batch_labels)
- 손실(Loss) 계산:
loss_fn
은 손실 함수로, 모델의 예측값(outputs
)과 실제 값(batch_labels
)을 비교하여 모델의 성능을 평가합니다.- 손실 함수의 결과는 모델이 얼마나 잘못 예측했는지의 정도를 나타내며, 이 값이 작을수록 모델의 예측이 실제와 가까워진다는 뜻입니다.
- 여기서는
loss_fn
이 구체적으로 정의되어 있지 않지만, 일반적으로CrossEntropyLoss
,MSELoss
등이 사용됩니다.
6. loss.backward()
- 역전파(Backward pass):
loss.backward()
는 손실에 대한 기울기(gradient)를 계산하는 과정입니다.- 이 과정에서 모델의 각 파라미터에 대해 손실 함수의 기울기를 계산하여, 그 기울기를 통해 파라미터를 업데이트할 수 있게 합니다.
- 즉,
backward()
호출로 네트워크의 각 파라미터가 얼마나 변해야 손실이 줄어드는지를 계산하는 것입니다.
7. torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5)
- Gradient Clipping 적용:
- 이 줄에서 gradient clipping을 수행하여 기울기의 크기가 너무 커지는 문제를 방지합니다.
clip_grad_norm_
함수는 각 파라미터의 기울기 크기를 제한합니다.max_norm=5
는 기울기의 L2 노름(norm)이 5를 넘지 않도록 제한하는 역할을 합니다. 즉, 계산된 기울기의 크기가 5보다 크면 그 크기를 5로 맞춥니다.- 기울기가 너무 크면 폭발적 기울기(exploding gradients) 문제를 일으킬 수 있어 학습이 불안정해질 수 있는데, 이를 방지하기 위해 기울기 클리핑을 적용합니다.
8. optimizer.step()
- 파라미터 업데이트:
optimizer.step()
는 기울기를 이용하여 모델의 파라미터를 업데이트합니다.- 앞서 계산한 기울기를 기반으로, 옵티마이저는 각 파라미터를 손실을 줄이는 방향으로 조금씩 변경합니다.
- 이 과정이 반복되면서 모델이 데이터를 점점 더 잘 예측할 수 있게 됩니다.
C값은 어떻게 결정해야할까?
그래디언트 클리핑에 적절한 임계값 C를 결정하는 것은 모델 구조, 데이터셋, 사용된 최적화 방법 등 여러 요인에 따라 달라집니다. 안타깝게도 모든 모델과 데이터셋에 통용되는 C 값은 없습니다. 대신, 실험과 훈련 과정에서 자세한 관찰이 필요합니다.
- 그래디언트 값 모니터링
- 그래디언트 클리핑을 적용하기 전에, 훈련 중 그래디언트 값의 범위를 모니터링하는 것이 도움이 될 수 있습니다. 여러 배치에 걸쳐 그래디언트의 노름(즉, ∥∇L∥)을 추적함으로써 일반적인 범위와 극단적인 값의 빈도를 파악할 수 있습니다.
- 이러한 모니터링은 그래디언트 폭주가 문제인지 확인하고 적절한 임계값을 설정하는 데 필요한 정보를 찾을 수 있습니다. PyTorch나 TensorFlow 같은 라이브러리를 사용하여 그래디언트 노름을 기록할 수 있습니다.
- 합리적인 추측으로 시작하기
- C에 대한 일반적인 시작점은 그래디언트를 1에서 5 사이로 클리핑하는 것입니다. 이는 많은 모델과 데이터셋에 효과적인 경우가 많습니다.
- C를 작은 값으로 설정하고 훈련 과정이 어떻게 진행되는지 관찰하며 실험할 수 있습니다.
- 그래디언트가 자주 클리핑된다면, 임계값이 너무 작다는 것을 의미할 수 있습니다.
- 그래디언트가 전혀 클리핑되지 않고 손실 함수가 불안정하다면, 임계값을 낮춰야 할 수 있습니다.
- 훈련 동작에 따라 점진적으로 조정하기
- C가 너무 작은 경우: C 값이 너무 작으면 그래디언트가 너무 자주 클리핑되어 모델이 가중치를 효과적으로 업데이트하지 못하고, 수렴이 느리거나 성능이 저하될 수 있습니다.
- C가 너무 큰 경우: C 값이 너무 크면 그래디언트 클리핑이 거의 또는 전혀 효과가 없어, 여전히 그래디언트 폭주나 훈련의 불안정성을 겪을 수 있습니다.
- 손실 곡선의 안정성과 모델의 성능을 기반으로 C를 점진적으로 조정합니다.
- 검증 손실을 피드백으로 사용하기
- 그래디언트 클리핑의 효과를 평가하기 위해 검증 손실이나 다른 평가 지표를 모니터링할 수 있습니다. 클리핑이 검증 손실을 줄이고 더 안정적인 훈련으로 이어진다면, 선택한 C가 적절할 수 있습니다.
- 그러나 모델이 과소적합(낮은 훈련 성능)으로 어려움을 겪거나 손실이 불안정하다면, 그에 따라 C를 조정합니다.
- 자동화 방법
- 경우에 따라 Adam과 같은 적응형 최적화 알고리즘은 학습률을 동적으로 조정하여 그래디언트 문제를 관리하는 데 도움이 될 수 있습니다. Adam을 사용할 때도 그래디언트 클리핑은 여전히 유용하지만, 최적화 알고리즘이 일부 그래디언트 문제를 완화하는 데 도움이 될 수 있습니다.
- 또는 일부 프레임워크에서는 그래디언트 클리핑 임계값을 자동으로 조정하는 방법을 제공하지만, 수동 조정만큼 일반적이지는 않습니다.
- 모델별 휴리스틱
- 순환 신경망(RNN), 트랜스포머, 깊은 합성곱 신경망과 같은 다양한 유형의 모델은 서로 다른 C 범위가 필요할 수 있습니다. 예를 들어:
- RNN은 그래디언트 폭주에 더 취약하며 일반적으로 그래디언트 클리핑의 혜택을 더 많이 받습니다.
- BERT나 GPT 같은 트랜스포머 기반 모델은 더 정교한 구조 덕분에 극단적인 그래디언트 변동에 덜 취약하지만, 클리핑은 여전히 유용할 수 있습니다.
- 데이터셋 크기와 복잡성
- 더 크고 복잡한 데이터셋은 더 큰 그래디언트를 생성할 수 있으므로, 적절한 C 값은 데이터셋에 따라 달라질 수 있습니다. 더 작고 단순한 데이터셋의 경우 더 작은 C로도 충분할 수 있습니다.
- 또한 더 복잡한 모델(예: 많은 층을 가진 깊은 모델)은 더 큰 그래디언트를 가지는 경향이 있어 더 적극적인 클리핑이 필요할 수 있습니다.
Gradient Clipping 과 관련된 논문들
- “On the difficulty of training Recurrent Neural Networks” (2013)
- 저자: Razvan Pascanu, Tomas Mikolov, Yoshua Bengio
- 이 논문에서 그래디언트 클리핑 기법이 처음으로 제안되었습니다.
- RNN 훈련의 어려움을 다루며, 그래디언트 폭주 문제를 해결하기 위한 방법으로 그래디언트 클리핑을 소개했습니다.
- “Identifying and attacking the saddle point problem in high-dimensional non-convex optimization” (2014)
- 저자: Yann N. Dauphin, Razvan Pascanu, Caglar Gulcehre, Kyunghyun Cho, Surya Ganguli, Yoshua Bengio
- 이 논문은 그래디언트 클리핑의 효과를 심층적으로 분석하고, 최적화 과정에서의 안장점 문제를 다룹니다.
- “Deep learning with elastic averaging SGD” (2015)
- 저자: Sixin Zhang, Anna E. Choromanska, Yann LeCun
- 이 논문은 분산 학습 맥락에서 그래디언트 클리핑의 사용을 탐구합니다.
- “Gradient Descent with Clipping and its Application in Model Aggregation” (2021)
- 저자: Cong Xie, Oluwasanmi Koyejo, Indranil Gupta
- 이 최근 논문은 그래디언트 클리핑의 이론적 특성을 분석하고, 모델 집계에서의 응용을 탐구합니다.