“딥러닝 알아듣기” 시리즈는 딥러닝의 기초 지식을 저만의 방식으로 쉽게 풀어내는 시리즈입니다. 이번 챕터에서는 모델의 효과적인 학습과 일반화를 위해 사용되는 여러 기법들에 대해 간략히 알아봅니다.
앞선 챕터에서 MNIST 데이터셋을 이용해 숫자 손글씨를 분류하는 딥 러닝 모델을 만들었고, 모델과 데이터셋의 특성이 모델의 __일반화__에 어떤 영향을 끼치는 지 알아보았다. 딥 러닝 모델을 실제 문제 해결에 사용하려면 적절한 일반화는 필수적이다. 실제 환경에서 모델에 입력되는 데이터들이 학습 데이터셋과 비슷한 경향을 가진다고 장담할 수 없기 때문이다. 다양한 입력 데이터에 대해 모델의 성능을 확보하는 동시에, 모델을 최대한 일반화할 필요가 있다.
최대한 다양한 데이터에 대해 모델의 성능을 확보하는 궁극적인 방법은 학습 데이터셋을 최대한 늘리는 것이다. 그러나 많은 상황에서 학습 데이터를 늘리는 것은 곧 시간과 자본의 추가적인 투입을 의미한다. 추가적으로 데이터를 모으려면 당연히 더 많은 시간과 비용이 필요할 것이다. 그러나 더 좋은 성능의 모델이 필요하다고 해서 무한정으로 학습 데이터를 늘릴 수는 없다. 그래서 학습 데이터를 직접적으로 늘리지 않으면서 모델을 더욱 일반화시키는 방법들이 다수 고안되었다.
이번 챕터에서 살펴볼 기술들은, 자원을 추가적으로 투입하지 않으면서 딥 러닝 모델의 성능을 효과적으로 향상시킬 수 있는 방법들이다. 딥 러닝의 반복 학습 효율을 높이거나, 역전파를 통한 모델의 학습 과정에서 생기는 문제를 해결할 수 있도록 돕는다. 나아가서 학습한 모델을 실제 환경에 적용했을 때도 효과적이도록 모델을 더욱 일반화시키는 방법도 제공한다. 다음의 다섯 가지 학습 기술들을 같이 알아보도록 하자.
- 파라미터의 효과적인 초기화 (Weight / Bias Initialize)
- 가중치 감쇠 (Weight Decay)
- 모델 앙상블 (Model Ensemble)
- 드롭아웃 (Dropout)
- 배치 정규화 (Batch Normalization)
3.6.1. 가중치의 효과적인 초기화 (Weight Initialize)
가장 먼저, 모델 파라미터의 초깃값을 제대로 설정하는 것부터 시작해보자. 편향은 대부분의 경우 모든 원소를 0으로 초기화하는 것이 좋다고 경험적으로 입증되었다. 중요한 것은 가중치를 어떻게 초기화하느냐이다.
제일 쉽게 떠올릴 수 있는 가중치의 초기화 방법은 모두 0으로 초기화하는 것이다. 그러나 모든 파라미터를 동일하게 초기화하면, 각 레이어별 모든 파라미터들에 대해 그래디언트가 동일하게 계산될 것이다. 그래디언트의 값이 같으면 당연히 갱신 후의 파라미터 값도 모두 동일해진다. 최적화 반복이 아무리 진행되어도 각 레이어별로 파라미터의 값들이 모두 동일하므로, 다양한 데이터의 특성을 이해할 수 없게 된다. 따라서 모든 파라미터를 0으로 초기화하는 것은 좋은 생각이 아니다.
Normal Initialize
다음으로 생각해볼 수 있는 것은 랜덤 초기화(Random Initialize) 이다. 단어 그대로 범위 내 임의의 값들로 파라미터들의 초깃값을 설정해주는 것이다. 보통 임의의 값은 균등분포(Uniform Distribution)나 정규분포(Normal Distribution) 내에서 확률적으로 추출한다. 다양한 값들로 초기화될 수 있으니 모든 값을 동일하게 초기화하는 것보다 당연히 좋을 것이다. 실제로 파라미터의 초기화 방법이 깊이 연구되기 전에는, 평균이 0이고 표준편차가 1인 정규분포에서 값을 추출해 파라미터를 초기화하는 방법을 많이 사용했다.
그래프의 가로축이 추출되는 값, 세로축을 해당 값이 추출될 확률로써 해석할 수 있다. 초기화 시점에서 파라미터를 다양한 값으로 설정할 수 있으니 좋아보인다. 그러나 시그모이드 함수를 적용해서 모델을 학습시킬 때 이 초기화 방법의 문제가 발견되었다. 그 이유는 시그모이드 함수의 출력 값에 따른 그래디언트에 있었다. 알다시피 시그모이드 함수의 출력은 0 또는 1의 극한값을 가진다. 시그모이드 함수를 미분하면, 0 또는 1의 극한값에 가까워질수록 미분값이 0에 가까워진다(그래디언트가 작아진다). 다음의 그래프는 시그모이드 함수와 그 도함수를 나타낸 그래프이다.
시그모이드 함수는 입력이 0에 가까워질수록 상대적으로 큰 그래디언트를 가진다. 모델 파라미터의 초기화에 균등분포나 정규분포를 이용할 경우, 낮지 않은 확률로 파라미터들이 0에서 먼 값으로 초기화된다. 가중치와 편향의 값이 0에서 멀어지니 당연히 각 레이어의 출력도 0에서 멀어지게 된다. 이렇게 되면 역전파 과정에서 시그모이드 함수의 그래디언트가 작아져 모델 전체적으로 그래디언트가 사라지는 결과를 얻는다. 그래디언트 소실이 일어나는 것이다.
가중치 초기화로 인한 그래디언트 소실 문제를 해결하려면, 표준편차가 작은 정규분포에서 가중치를 초기화하는 방법 외에는 없다. 표준편차가 작아지면 0에 가까운 값으로 파라미터를 초기화할 확률이 커지기 때문이다. 다음은 표준편차가 약 0.4인 정규분포의 그래프이다.
그러나 아직 약간 부족하다. 시그모이드 활성 함수가 사용되는 모델에서 그래디언트 소실 문제를 해결하려면, 근본적으로 각 레이어의 출력 값들이 0에 가까울수록 좋다. 다만 모든 값이 0에 가까우면 출력 값의 다양성이 떨어져 오히려 최적화가 잘 되지 않을 것이므로, 모델의 출력이 적당히 표준 정규분포의 형태를 가지도록 하면 좋을 것이다. 0에 가까운 출력이 다수이도록 유지하면서 각 레이어 출력에 다양성을 두어, 모델 최적화를 다양한 방향으로 시도할 수 있도록 만들어줄 수 있다.
Xavier Initialize
이렇게 모델의 출력 형태를 변화시키기 위한 파라미터의 초기화 방법으로 Xavier 초기화 방법이 고안되었다. 구글 딥마인드의 Xavier Glorot 박사가 고안해낸 파라미터 초기화 방법으로, 모든 레이어의 출력을 표준 정규분포 에 가깝게 만들어준다.
각각의 레이어 출력은 행렬곱으로 계산된다. 즉 레이어의 출력은 입력 텐서와 가중치 텐서의 행렬곱을 구하는 과정이므로, 가중치와 편향 파라미터의 갯수가 많아질수록 레이어의 출력에서 각 원소의 절댓값이 자연스럽게 커진다. 이 출력을 시그모이드 활성 함수에 입력하면 시그모이드의 출력 또한 0 또는 1의 극한값으로 벌어질 가능성이 크다. 이렇게 출력값이 양 극한으로 벌어지면 그 다음 레이어의 연산 결과 또한 더 큰 범위의 출력을 보일 수밖에 없다.
앞서 살펴보았듯이 시그모이드의 출력이 양 극한으로 벌어지면 그래디언트는 0에 수렴한다. 결국 레이어가 조금만 넓어져도 레이어를 몇 층 쌓지 못할것이라는 이야기다. 서너층의 레이어만 지나면 시그모이드의 출력이 이 양 극단으로 벌어질 대로 벌어져, 대부분의 그래디언트가 0으로 수렴할 것이기 때문이다. 이는 초기화 방법의 문제라기보다, 시그모이드 활성 함수를 사용하는 딥 러닝 모델의 고질적인 문제점이다. 이 문제를 해결하려면, 레이어의 출력이 양 극한으로 벌어지지 않고 전체적으로 정규분포의 모습을 띄도록 하면 될 것이다.
레이어 출력에서 모든 노드의 값 분포가 대강 정규분포를 따르게 하려면, 레이어의 파라미터 개수가 많을수록 파라미터의 초깃값을 작게 가져가야 한다. 그래야 레이어 출력의 절댓값 범위가 일정해진다. 행렬곱의 원리에 따라 레이어에 입력되는 벡터의 원소 갯수가 많을수록 출력의 절댓값은 커진다. 따라서 파라미터를 초기화할 때 입력 텐서의 원소 갯수를 고려해주어야 한다.
반대로 역전파의 관점에서 보면 출력 레이어의 노드 수도 문제가 된다. 출력 레이어의 노드 수가 많아질수록 같은 그래디언트 값이라도 파라미터의 갱신 폭이 커진다. 뒷 레이어의 노드 수가 많아질수록 앞 레이어의 파라미터 업데이트에 영향을 미치는 그래디언트의 수도 많아지기 때문이다. 입력과 출력 노드의 갯수가 많아질수록 절대적인 계산 결과값이 증가하니 그래디언트가 사라지는 것은 당연한 수순이다. 입력과 출력 노드의 갯수를 동시에 고려하여 가중치를 초기화할 필요가 있다.
Xavier Glorot 박사가 입출력 노드의 수에 맞게 가중치 초깃값의 범위를 조정하기 위해 제안한 초기화 방법은 간단하다. \(j\)번째 레이어의 가중치를 초기화하기 위해, 기존에 하던 것처럼 정규분포에서 임의의 초깃값을 추출한 후 \(\sqrt{\frac{2}{n_{j} + n_{j+1}}}\)를 곱한다. \(n_j\)는 \(j\)번째 레이어의 노드 수다. 이렇게 하면 레이어의 초깃값이 입력 레이어의 노드 수에 비례해 작게 초기화된다.
Xavier 초기화의 최종 목적은 모든 레이어의 출력이 비슷한 평균과 표준편차를 가지는 분포를 따르도록 만드는 것이다. 정규분포로 레이어의 파라미터를 초기화해도 레이어를 쌓을수록 출력 값이 벌어지는 주된 이유는 레이어 파라미터의 갯수에 있다. 그러므로 각 레이어의 파라미터를 초기화할 때 파라미터의 갯수에 맞춰 초깃값의 범위를 작게 조정해서, 모든 레이어 출력의 분포와 범위를 일정하게 맞추겠다는 이야기다. 레이어가 넓을수록 파라미터의 초깃값이 작아지므로, 레이어를 지날수록 출력값이 커지는 일 없이 모델의 출력까지 일정한 출력 값의 분포를 유지할 수 있다. 그래서 Xavier 초기화는 신경망의 레이어를 무한히 쌓을 수 있게 만드는 가장 기초적인 기반을 다졌다는 점에 의의가 있다.
PyTorch에서 Xavier 초기화를 사용하려면 torch.nn.init.xavier_normal_
또는 torch.nn.init.xavier_uniform_
함수를 사용한다. 각각 정규분포와 균등분포에서 값을 추출해 초기화를 수행한다.
layer1 = torch.nn.Linear(256, 256) # 레이어 선언
layer2 = torch.nn.Linear(256, 256) # 레이어 선언
torch.nn.init.xavier_normal_(layer1.weight) # 레이어의 가중치 파라미터를 정규분포 Xavier 초기화
torch.nn.init.xavier_uniform_(layer2.weight) # 레이어의 가중치 파라미터를 균등분포 Xavier 초기화
Kaiming Initialize
Kaiming He 박사가 제안한 초기화 방법으로, Xavier 초기화를 약간 변형한 방법이다. Xavier 초기화는 근본적으로 시그모이드 형태의 할성 함수를 사용하는 레이어를 위해 디자인되었다. 그래서 ReLU 활성 함수를 사용하는 모델에 Xavier 초기화 방법을 사용하면 최적화가 잘 되지 않는다. ReLU가 음수인 레이어 출력을 모두 0으로 죽여서 그래디언트가 사라지는 경우가 많기 때문이다. 역전파 과정에서 노드가 많아져 생기는 문제를 해결하려고 Xavier 초기화를 적용했지만, ReLU를 사용하면 오히려 그래디언트가 0인 경우가 많아지므로 역으로 학습이 되지 않는 것이다. Kaiming 초기화는 단순히 가중치 초깃값의 범위를 두 배 늘려 문제를 극복하고자 하는 방법이다. ReLU 함수의 미분으로 인해 많은 그래디언트가 사라지므로, 전체적인 레이어 출력의 범위 자체를 늘려 이를 극복하고자 한다.
Xavier 초기화에서는 입력과 출력 노드의 갯수를 모두 고려하지만, Kaiming 초기화는 입력 노드의 갯수만을 고려한다. 정규분포에서 임의의 초깃값을 추출한 후, Xavier 초기화의 \(\sqrt{\frac{2}{n_{j} + n_{j+1}}}\) 대신 \(\sqrt{\frac{2}{n_{j}}}\) 만을 나누어 준다. 확실히 가중치 초깃값의 범위가 크게 늘어나는 결과를 얻을 수 있을 것이다.
PyTorch에서 Kaiming 초기화를 사용하려면 torch.nn.init.kaiming_normal_
또는 torch.nn.init.kaiming_uniform_
함수를 사용한다.
layer1 = torch.nn.Linear(256, 256) # 레이어 선언
layer2 = torch.nn.Linear(256, 256) # 레이어 선언
torch.nn.init.kaiming_normal_(layer1.weight) # 레이어의 가중치 파라미터를 정규분포 He 초기화
torch.nn.init.kaiming_uniform_(layer2.weight) # 레이어의 가중치 파라미터를 균등분포 He 초기화
3.6.2. 가중치 감쇠 (Weight Decay)
가중치 감쇠(Weight Decay)는 딥 러닝 모델의 일반화를 돕기 위한 기술이다. 어떤 모델이 충분히 큰 용량을 가지고 있지만 학습 데이터셋이 상대적으로 적어 오버피팅이 일어나고 있는 상황을 가정해보자. 오버피팅이 일어난 모델에서는 많은 경우 일부 가중치 파라미터의 값만 크게 뛰는 현상이 발생한다. 오버피팅된 모델의 모습을 다시 확인해보자.
앞선 챕터에서 오버피팅된 모델의 예시로 들었던 그림이다. 손글씨 0과 1의 그림이 매우 복잡하게 분포되었음에도 불구하고 빨간 선으로 나타나는 모델이 어떻게든 구분해내고 있다. 이렇게 급격한 변곡점을 수없이 가지고 있는 결정 경계를 가질 수 있었던 것은 분명히 일부 가중치의 값만 매우 크기 때문 일 것이다. 특정 가중치의 절댓값이 매우 커지지 않고서는 모델이 저렇게 급격한 커브를 가지는 결정 경계를 표현할 수 없다. 선형 함수의 기울기 값이 클수록 값이 급격하게 변화하는 것을 떠올리면 되겠다. 결국 이렇게 튀는 일부 가중치의 절댓값을 조절해주지 않으면, 오버피팅 문제에 쉽게 노출된다는 이야기다.
이 문제를 해결하려면 가중치를 최적화하는 동시에 전체적으로 값이 튀지 않도록 계속 조정해주어야 한다. 가중치의 절댓값을 인위적으로 줄이는 것이다. 이러한 작업을 가중치 감쇠(Weight decay) 라고 한다. 또한 이렇게 학습 도중 특정한 테크닉을 이용해서 모델의 일반화를 가속하는 작업을 정규화(Regularization) 이라고 말하기도 한다.
가중치 감쇠의 원리는 간단하다. 손실 함수에 가중치 감쇠를 위한 항을 하나 더해서, 모델이 학습 데이터를 최대한 이해하면서도 가중치가 최대한 작아지는 방향으로 학습시킨다. 여기서 가중치 감쇠를 위한 항을 정규화 항(Regularization Term) 이라고 부르기로 하자. 가중치 감쇠 정규화 방법은 정규화 항을 어떻게 만드는지에 따라 그 이름이 달라진다. 대표적인 두 가지 방법을 소개하겠다.
첫 번째는 L1 정규화이다. 가중치 텐서에 대한 L1 Norm을 구해 정규화를 수행한다. Norm은 간단히 말해 선형대수학에서 특정 벡터의 크기를 측정하는 방법이다. 가중치 텐서에 대해 Norm을 구한다는 것은, 그 가중치 텐서 내 원소들의 전체적인 절댓값 평균이 얼마나 큰지 확인하고 싶다는 것이다. 가중치 텐서의 L1 Norm은 다음과 같이 구할 수 있다.
식만 보아도 직관적으로 느낌이 올 것이다. 가중치 텐서의 각각 원소들의 값이 클수록 Norm의 값도 증가한다. 가중치 텐서에 대해 Norm을 구해 보면 얼마나 정규화가 덜 되어 있는지 수치로 파악 가능할 것이다. L1 정규화는 이 점을 이용한다. 딥 러닝 모델은 손실을 어떻게라도 줄이는 방향으로 학습하게 되어 있다. 만약 모델의 손실 함수 식에 가중치 텐서의 Norm을 더해준다면, 모델은 가중치 텐서의 Norm을 동시에 줄여나가는 방향으로 학습할 것이다. 이것이 가중치 감쇠의 아이디어다.
기존 손실 함수에 L1 Norm을 활용한 정규화 항을 더해주고 있다. Norm 앞의 \(\lambda\)는 정규화의 강도를 조절하는 하이퍼 파라미터이다. \(\lambda\)가 커질수록 가중치 정규화 정도로 인한 손실이 커지므로, 정규화가 더 강하게 적용된다.
두 번째는 L2 정규화이다. 원리는 L1 정규화와 동일하다. 원래의 L2 Norm은 다음과 같이 구한다.
그러나 L2 정규화에서는 L2 Norm에 제곱근을 씌우지 않고 사용한다. 가중치 벡터 안에서 값이 튄 원소에 대한 손실을 더 크게 부여하기 위해서이다.
L1 정규화는 값이 얼마나 크게 튀는지에 상관 없이 모두 동일한 수준의 손실을 부여한다. 그래서 L1 정규화를 계속 진행하면 원래 값이 작았던 원소들은 대부분 0으로 값이 수렴하고, 특징적인 원소 일부만 남아 가중치를 구성하게 된다. 가중치의 특정 원소가 0이면 그 원소와 연결된 입력 특징은 출력에 아무런 영향을 미치지 못한다. 즉, 레이어 출력에 영향을 주는 특징의 갯수를 일부러 줄여가며 정규화한다고 볼 수 있다.
이와 다르게 L2 정규화는 모든 원소에 제곱을 하기 때문에 특정 원소의 값이 상대적으로 작아져도 잘 사라지지는 않는다. 대신 값이 튀는 원소들에 대한 손실이 매우 커지므로, 가중치의 값을 전체적으로 일정 범위 안에 머물게 한다. L1 정규화가 일부 입력 특징만 이용하도록 강제해 정규화한다면, L2 정규화는 모든 입력 특징을 이용하지만 각각의 특징이 모두 비슷한 영향을 미치도록 하여 정규화한다는 차이가 있다.
PyTorch는 기본적으로 옵티마이저에 L2 정규화가 구현되어 있다. 옵티마이저의 weight_decay
하이퍼 파라미터는 수식에서 \(\lambda\)이다. 이 값을 조정하여 정규화의 정도를 조절할 수 있으며, 0으로 정규화를 수행하지 않는 것이 기본 값이다.
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=0.005)
3.6.3. 모델 앙상블(Model Ensemble)
모델 앙상블
모델 앙상블은 모델 결합(Model Combination) 이라고도 한다. 단순한 아이디어지만 나름 강력한 성능을 발휘하는 모델의 정규화 방법이다. 아이디어는 간단하다. 같은 문제를 푸는 여러 개의 약한 모델(Weak model) 을 각각 따로 학습시켜 동시에 사용하겠다는 것이다. 한 사람의 생각보다 여러 사람이 모인 집단 지성이 더 강력한 힘을 발휘하듯이, 여러 모델을 동시에 사용해 더욱 강력한 성능을 내고자 한다.
따로 학습시킨 여러 모델에 같은 입력 데이터를 넣으면 각각의 출력이 나올 것이다. 가장 대표적인 투표(Voting) 기반의 앙상블은 가장 많이 나온 출력 값을 최종 출력 값으로 삼는다. 동물과 식물을 이중 분류하는 모델 10개를 학습하여 앙상블했다고 가정해보자. 어떤 입력 데이터에 대해 동물로 판단한 모델이 7개, 식물로 판단한 모델이 3개라면 최종 출력은 더 많은 모델이 선택한 동물로 결정된다. 분류 문제는 투표로 정하고, 회귀 문제와 같이 출력 데이터가 연속적(Continuous)인 경우 출력값의 평균을 최종 출력으로 한다.
그러나 모든 약한 모델을 동일한 데이터셋과 동일한 세팅으로 학습하면 당연히 앙상블의 의미가 사라질 것이다. 각 모델들에게 최대한 데이터셋을 다양하게 나누어 학습시켜서 일반화 성능을 높이는 것이 모델 앙상블의 목적이므로, 모델마다 데이터셋을 일부러 조금씩 다르게 보여줘야 한다. 전체 학습 데이터셋에서 일부를 추출해 각 모델의 학습 데이터셋으로 사용하는데, 이 때 추출한 데이터셋을 서브셋(Subset) 이라고 부른다.
서브셋을 추출하는 방법에는 배깅(Bagging) 과 페이스팅(Pasting) 의 두 가지 방법이 있다. 둘 다 원본 학습 데이터셋에서 일부를 샘플링하여 각 모델의 학습 데이터셋으로 사용한다는 점은 동일하다. 그러나 배깅은 같은 학습 데이터 샘플이 여러 모델 학습에 중복 사용되는 것을 허용하고, 페이스팅은 중복을 허용하지 않는다.
부스팅
단순히 데이터셋을 여러 서브셋으로 나누어 학습시킨 모델들을 앙상블하는 것은 분명히 성능의 한계가 존재한다. 때문에 모델 앙상블 기법은 이후에도 계속 발전하였다. 여러개의 모델을 독립적으로 학습시켜 단순 투표로 앙상블하는 기법을 넘어서, 모델들 자체의 성능을 강화하여 성능을 얻어내고자 하는 부스팅(Boosting) 방법이 등장했다.
기존의 모델 앙상블은 각각의 약한 모델을 따로 학습하였다. 학습 데이터셋의 차이는 있었으나 모델들이 서로 학습에 영향을 미치지 않았다. 부스팅은 이 점을 공략해서, 다른 모델들이 어려워한 데이터셋에 대해 더 많이 학습하도록 하여 모든 모델의 전체적인 성능을 높인다. 간단히 이중 분류 모델을 만든다고 가정하고, 부스팅이 어떻게 모델의 성능을 높였는지 이해해보자.
먼저 원본 학습 데이터셋으로 약한 모델 하나를 학습시켜본다. 모델이 학습하면서 분류에 성공한 데이터도 있을 것이고, 실패한 데이터도 있을 것이다. 분류에 실패했다는 것은 모델의 결정 경계가 해당 데이터를 구분하지 못한다는 것이다.
앞의 그림은 학습 데이터셋의 분포 사이에 학습된 모델의 결정 경계를 그린 그림이다. 어떠한 추가적인 장치 없이 약한 모델 하나만을 학습시켰으므로, 분류 성능이 크게 좋지 않을 것이다. 이 때, 학습을 중지한 시점에서 모델이 틀린 데이터를 기억한다. 앞의 그림에서 상대적으로 크게 그려진 데이터들이 틀린 데이터들이다.
다음 모델을 학습시킬 때는 이전 모델이 틀렸던 데이터들에 대해 더 큰 가중치를 주어 학습시킨다. 가중치가 큰 데이터의 분류를 실패할 경우 손실에 더욱 강력한 패널티를 받는다. 따라서 모델은 가중치가 큰 데이터들을 더 잘 분류할 수 있는 방향으로 강하게 학습될 것이다. 모델의 구조와 용량이 동일하다고 해도, 학습 데이터마다 손실에 미치는 가중치가 달라지면 학습의 양상 또한 당연히 변화할 것이다. 이렇게 두 번째 모델도 학습하고, 두 번째 모델이 분류에 실패한 데이터를 모아서 다음 모델 학습시에 더 큰 가중치를 준다. 이런 방식을 반복하여 여러 개의 약한 모델을 순차적으로 학습시킨다.
두번째 모델은 첫 번째 모델이 어려워했던 데이터에 더욱 큰 비중을 두어 학습했으므로 상대적으로 성능이 더 나아졌을 것이다. 그러나 두 번째 모델 또한 완벽하지 않다. 몇 개의 모델을 이어서 학습하더라도 모든 데이터에 대해 완벽한 모델을 만들어내기는 힘들다. 일부 데이터셋에 더 집중하여 학습하는 만큼, 다른 데이터에 대해 다시 성능이 떨어질 수도 있기 때문이다. 때문에 최대한 많은 모델을 앙상블해야 한다. 각각의 약한 모델을 학습할 때마다 이전 모델이 어려워했던 부분을 보완하면서 학습하기 때문에, 결국에는 대부분의 데이터에 대해 안정적인 성능을 확보할 가능성이 커지기 때문이다.
어려운 데이터들에 대해 계속 보완학습을 하면서 여러 개의 모델 학습을 완료한다. 이렇게 만들어진 모델들을 앙상블하면, 각 모델들의 성능을 더 강력하게 만들면서도 여러 모델을 동시 사용하여 정규화하는 앙상블의 장점을 그대로 가져올 수 있다. 여러 개의 모델들이 서로 어려워하는 부분을 보완하여 학습했기 때문에 대부분의 데이터에 강력한 성능을 가질 수 있는 것이다. 마치 학생들이 서로 모르는 부분을 알려주며 같이 공부해서, 집단 지성으로 다같이 높은 시험 점수를 받아가는 것과 같은 모양이다.
이론적으로만 보아도 강력하고 안정적인 예측 모델을 만들 수 있을 것처럼 보인다. 그래서 딥 러닝 모델이 본격적으로 깊어지기 직전에는 모델 앙상블, 특히 부스팅 방법이 대부분의 머신 러닝 알고리즘을 평정했다.
그러나 여러 개의 모델을 동시에 학습해야 한다는 점에서 예상할 수 있듯이, 학습 시간이 상대적으로 오래 걸린다는 문제점이 존재한다. 또한 여러 개의 하이퍼 파라미터를 동시에 조정해야하기 때문에 학습을 진행하고 평가하기가 더 어렵다는 단점도 가지고 있다.
3.6.4. 드롭아웃(Dropout)
드롭아웃도 대표적인 정규화 방법 중 하나이다. 간단한 아이디어지만 많은 상황에서 강력한 정규화 성능을 보여준다.
드롭아웃은 모델 앙상블을 통한 정규화에서 아이디어를 착안했다. 모델 앙상블은 상대적으로 성능이 약한 모델 여러 개를 모아 동시에 사용함으로써 모델의 추론 성능을 올렸다. 모델 여러 개를 동시에 학습하면서 데이터를 여러 방면에서 이해하도록 하는 것이다. 여기서 생각을 더 발전시켜서, 하나의 모델만을 학습하면서 앙상블의 아이디어를 차용해 정규화 성능을 높일 수 있는 방법은 없을까?
이와 같은 질문에서 나온 것이 드롭아웃이다. 드롭아웃은 단순히 순전파 과정에서 레이어의 일부 출력 노드를 무작위로 선정해 무시 한다.
실제로 구현할 때는 각 레이어의 출력 텐서에서 일부 원소를 0으로 만들어주는 방식으로 구현한다. 무시되는 노드는 매 반복마다 바뀐다. 분명히 같은 모델이지만 학습 반복마다 매번 다른 약한 모델을 학습하는 셈이 된다.
같은 학습 데이터셋을 입력받아도 어떤 출력 값이 무시되고 살아남는지에 따라 모델의 최종 출력과 손실이 달라진다. 이렇게 구한 손실은 무시된 출력 값들과 전혀 관련 없는 값이 된다. 따라서 역전파로 모델 파라미터를 갱신할 때도 무시된 출력 값에 관여하는 가중치들은 갱신되지 않는다. 생각해보면, 오버피팅이 일어나는 주요한 원인이 일부 가중치 값들에만 그래디언트가 몰려 가중치가 큰 값으로 튀는게 문제라고 했었다. 같은 모델과 데이터셋으로 계속 동일한 반복을 수행하므로 이런 현상이 나타나는 것은 피할 수 없는 일이었다. 그러나 반복마다 무작위로 일부 레이어 출력을 무시하면, 역전파시에도 특정 가중치에 큰 그래디언트가 몰리는 현상이 줄어든다. 같은 데이터를 봐도 각각 가중치의 그래디언트가 판이하게 달라지기 때문이다. 이전 반복에서 갱신된 가중치가 다음 반복에서는 갱신되지 않을 수도 있다. 결국 매 반복마다 조금씩 다른 모델로 학습시키는 꼴이 되므로, 전체적으로 튀는 가중치 값 없이 정규화된 모델을 얻어낼 수 있다.
약간 다른 관점에서 드롭아웃을 이해할 수도 있다. 무작위로 레이어의 출력 일부를 무시하며 학습하므로 매 반복마다의 모델은 필연적으로 성능이 약할 수밖에 없다. 그러나 성능이 낮은 대신 한편으로는 오버피팅의 가능성이 적은 것이기도 하다. 학습 과정에서 드롭아웃을 통해, 성능이 낮은 모델을 수없이 학습시켜 그 모든 결과를 합친 꼴의 모델을 만들어낼 수 있다. 오버피팅되지 않은 적당한 성능의 모델 여러 개를 합쳐 잘 정규화된 최종 모델을 얻어낼 수 있는 것이다. 여러 모델의 앙상블에서 투표함으로써 잘 정규화된 모델 결과를 얻어내는 것과 같은 이치다.
또한 드롭아웃은 모델의 성능 자체를 향상시켜주기도 한다. 딥 러닝 모델을 학습할 때 자주 부딫히는 문제 중 하나가 가중치 원소끼리의 상호 적응(Co-adaptation) 이다. 모델을 최적화하면 할수록 여러 가중치 원소들이 서로 영향을 주고받는 현상이다. 모델의 오버피팅이 진행되기 시작하면 값이 튀는 가중치의 영향만 커지면서 다른 가중치들은 상대적으로 영향이 줄어든다. 특정 노드의 값 변화로 인해 다른 가중치 노드들까지 영향을 받는 것이다. 모든 가중치의 영향을 골고루 사용하지 않기 때문에 모델의 성능이 떨어지는 동시에 오버피팅의 가능성이 늘어난다. 그러나 드롭아웃을 사용하면 그래디언트가 큰 노드가 있더라도 반복마다 유지되지는 않으므로 상호 적응 문제를 해결할 수 있다. 각각의 가중치 노드가 모델 출력에 고루 영향을 미치도록 만드는 것이다. 이는 곧 드롭아웃의 목표인 모델의 정규화와도 직접 연결된다.
드롭아웃은 적용만으로 확실하게 효과를 볼 수 있는 알고리즘이다. 그러나 최근 들어 모든 경우에 드롭아웃이 모델 성능을 높여주는 것은 아니라는 사실이 밝혀지기 시작했다. 앞으로 이어지는 챕터들에서 어떤 경우 드롭아웃을 조심해서 사용해야 하는지 알아볼 것이다.
PyTorch에서 드롭아웃은 torch.nn.Dropout
레이어를 추가함으로써 적용할 수 있다. Dropout
레이어는 바로 이전 레이어의 출력 값 일부를 무작위로 선택해 0으로 만든다. 3.4챕터에서 학습했던 MNIST 분류 모델에 드롭아웃을 적용해보자. 중간 레이어에서 ReLU 활성 함수를 거친 출력을 드롭아웃 레이어에 입력하면, 드롭아웃 레이어는 정해진 확률로 출력 텐서의 일부 원소를 0으로 만든다.
import torch.nn as nn
model = nn.Sequential(
nn.Linear(784, 20),
nn.ReLU(),
nn.Dropout(p=0.2), # 0.2의 확률로 일부 출력을 0으로 만든다.
nn.Linear(20, 20),
nn.ReLU(),
nn.Dropout(p=0.2), # 0.2의 확률로 일부 출력을 0으로 만든다.
nn.Linear(20, 10)
)
3.6.5. 배치 정규화 (Batch Normalization)
마지막으로 살펴볼 것은 많은 상황에서 강력한 성능을 보이는 배치 정규화(Batch Normalization) 알고리즘이다. 지금까지 역전파시 오버피팅을 막기 위해 다양한 방법들을 사용했다. Xavier 초기화 방법 등으로 가중치를 잘 초기화해서 가중치 텐서의 원소들이 전체적으로 적절히 분포하도록 만들었으며, 가중치 감쇠를 적용해 가중치의 특정 원소가 튀지 않도록 강제하였다.
이러한 방법들이 해결하고자 하는 문제 상황을 처음부터 다시 상기해보자. 오버피팅이 발생하는 첫 번째 이유는 학습 데이터셋의 특징 값 분포가 불균형하기 때문이었다. 딥 러닝 모델은 학습시에 더 많이 등장한 데이터에 대해서 더욱 강한 성능을 보인다. 손실 함수를 구하고 모델 파라미터를 갱신하는 일에 상대적으로 많은 영향을 주었기 때문이다. 학습 데이터셋에서 어떤 특징 값의 분포가 고르지 않을 경우, 모델은 더 많이 등장한 값에 대해서만 좋은 성능을 보이도록 학습된다. 최적화 과정에서 많이 등장한 특징 값에 따른 그래디언트 갱신이 주로 이루어졌기 때문이다. 때문에 데이터셋 전체에 대해 특징 값의 분포가 정규분포를 이루도록 입력 데이터를 정규화했다. 특정한 값에 모델이 오버피팅되는 것을 막고, 다양한 특징 값에 대해 모델이 고루 최적화될 수 있도록 만들어준 것이다.
그러나 입력 데이터의 정규화만으로는 오버피팅 문제를 해결할 수 없었다. 오버피팅이 일어나는 이유는 근본적으로 각 레이어의 출력이 앞선 레이어들의 출력에 영향을 받기 때문이다. Xavier 초기화 등으로 모든 가중치를 정규분포에 가깝게 초기화했어도, 손실을 줄이는 방향으로 가중치의 업데이트가 반복되기 때문에 가중치의 전체적인 분포가 변하는 것은 피할 수 없다. 역전파를 반복하면서 각 레이어의 가중치들이 갱신되면 다음 그림처럼 레이어 출력의 분포가 변화하게 된다. 입력 데이터의 각 특징 값들이 항상 동일한 정규분포를 이루고 있어도, 레이어들의 출력 분포는 가중치에 따라 변화한다.
이러한 현상을 내부 공변량 변화(Internal Covariate Shift) 라고 한다. 용어만 보면 쉽게 감이 오지 않지만, 간단히 레이어의 출력은 앞선 레이어 출력에 큰 영향을 받는 현상이라고 이해하면 된다. 바로 이 현상때문에 특정 가중치 값이 크게 튀는 오버피팅 문제가 발생했다. 순전파와 역전파를 반복하며 파라미터를 갱신하는 모델의 학습 방식 자체에서 기인하는 필연적인 문제였던 것이다. 이 현상을 최소화하기 위해 가중치를 적절히 초기화하고, 가중치 감쇠와 드롭아웃을 적용했다.
그러나 이 방법들이 문제를 직접 해결해주는 것은 아니다. 가중치의 적절한 초기화는 학습 초반에 그래디언트가 사라지지 않도록 막아주는 것이고, 가중치 감쇠는 특정 가중치 값이 튀지 않도록 강제하는 것 뿐이다. 앞 레이어들의 영향을 최대한 안 받는것처럼 만들 뿐이지, 내부 공변량 변화 현상은 그대로 존재한다. 어찌됐던 손실 함수를 최소화하도록 파라미터가 계속 갱신될 것이기 때문이다. 모든 레이어 가중치를 각각 정규분포를 따르도록 초기화해도 같은 현상이 일어나는 이유이다.
이 문제가 해결되려면 이상적으로 모든 레이어 출력의 분포가 일정해야 한다. 어떤 하나의 레이어 출력 분포가 변화하기 시작하는 순간 다른 레이어들도 동시에 영향을 받기 때문이다. 다음 사진에서 주황색 입력 데이터 배치의 각 특징 값들은 정규분포를 따르도록 초기화되었다.
학습이 진행되어 중간 레이어의 가중치가 변화하면, 레이어 출력 노드 각각의 값이 배치 내에서 가지는 분포가 달라질 것이다. 입력된 학습 데이터 배치에 따라 모델이 최적화되는 중이기 때문이다. 다음 그림처럼 배치 내에서 출력 값의 분포가 달라지는 것은, 다음 레이어의 입력 값 분포도 변화한다는 것을 의미한다.
분포가 불균형한 특징 값을 입력받은 다음 레이어는, 배치 내에서 더 많이 등장한 특징 값에 맞추어 가중치가 갱신된다. 이렇게 가중치 갱신이 반복되면 레이어 입출력에서 각각 특징 값의 분포 불균형이 더욱 심해지고, 레이어들은 상대적으로 많이 관찰된 입력 특징 값에 대해 편향되어 최적화된다. 결국 모델이 특정한 데이터에 대해서만 강한 성능을 보이는 오버피팅 문제를 피할 수 없다.
지금까지 살펴보았던 방법들과는 다르게, 배치 정규화 는 이 문제를 상당히 능동적으로 해결한다. 중간 레이어 출력 특징 값 분포의 변화가 문제라면, 아예 분포의 변화가 없도록 강제하면 되지 않을까 하는 생각에서 출발했다.
레이어의 출력 특징 값들을 정규화하면 분포가 일정해질 것이다. 입력 데이터에서 각각의 특징 값을 정규화했을 때와 같다. 다만 이때는 입력 데이터를 정규화 할때처럼 전체 학습 데이터셋을 사용할 수 없으므로, 현재 배치 내에서만 입력 특징 값의 정규화를 수행 해야 한다. 그래서 배치 정규화라는 이름이 붙었다. 현재 레이어의 출력 배치에서 같은 특징 값끼리 정규화해서, 다음 레이어에 입력되는 각각의 특징 값 분포가 고르도록 만든다. 이렇게 최적화 반복마다 정규화를 수행하면 오버피팅을 막을 수 있을 것이다.
단순히 생각해보자. 레이어의 출력 배치를 정규화하는 가장 쉬운 방법은, 이전 레이어 출력에서 각각 특징 값의 분포를 평균이 0이고 분산이 1인(단위 분산) 정규분포로 바꾸면 된다. 그러면 레이어의 입력 배치에서 각 특징 값의 분포가 비슷하게 맞춰질 것이다. 이 방법을 화이트닝(Whitening) 이라고 한다.
그러나 단순히 레이어의 출력 특징 값을 화이트닝하는 것은 해결책이 되지 못한다. 출력 특징 값의 화이트닝과 레이어 파라미터의 최적화는 완전히 별개로 동작하기 때문이다. 모델의 역전파 과정에서는 각 레이어의 출력 특징 값이 화이트닝되었다는 사실을 알 방법이 없으므로, 화이트닝된 출력 자체에 맞추어 파라미터의 갱신을 반복한다. 그런데 이 과정에서 갱신된 파라미터의 영향이 매우 적어지는 현상이 발생한다. 모델의 손실을 줄이기 위해서 기껏 레이어의 파라미터를 갱신해놓았는데, 다음 순전파 과정에서 그 레이어의 출력 특징 값들이 이전 반복에서와 같은 분포를 가지도록 화이트닝되면 결국 파라미터 갱신의 영향이 희미해지는 것이다.
수학적으로도 간단히 이해할 수 있다. 가중치와 편향 파라미터가 각각 \(w, b\)인 레이어 \(y = wx + b\)의 파라미터를 한 번 업데이트했다고 가정하자. 역전파 과정에서 가중치와 편향이 약간 증가하였다.
화이트닝이 없었다면 다음 순전파 과정에서는 레이어의 출력 특징 값들이 약간 더 커졌을 것이다. 그러나 화이트닝의 과정에서 레이어 출력 특징 값들의 평균을 각각 빼므로, 결국 이전의 출력 특징 값과 거의 차이가 없게 된다.
각각의 레이어 출력 특징 값에서 평균 \(\mu\)를 빼 평균을 0으로 맞추는 과정에서 갱신된 파라미터의 영향으로 생긴 출력 특징 값들의 증가분이 사라지는 것이다. 화이트닝으로 만들어진 출력 특징 값이 이전에 갱신된 파라미터의 영향을 무시하니, 최적화를 아무리 반복해도 파라미터가 전체적으로 크게 갱신되지 않는다. 당연히 최적화에 실패할 가능성이 크다.
그렇다면 단순 화이트닝이 아니라, 역전파 과정에서 갱신되는 파라미터의 영향을 그대로 유지하면서 출력 특징 값들의 분포를 조정하는 방법이 필요하다. 배치 정규화가 이에 대한 해답을 제공한다.
배치 정규화는 화이트닝한 출력 배치가 항상 동일한 분포를 가지지 않도록 Scale과 Shift 파라미터를 도입한다. 출력 배치에서 각각의 특징 값을 화이트닝한 후, 출력값에 Scale 파라미터를 곱하고 Shift 파라미터를 더해서 분포를 다르게 만든다.
수식으로 살펴보자. 모델의 학습을 \(n\)개 크기의 배치로 수행할 때, 레이어 출력의 \(k\)번째 특징 값들을 \(\mathbf{B}_k = \lbrace {x_{k1}, \ x_{k2}, \ ..., \ x_{kn}} \rbrace\)로 표현할 수 있다. \(k\)번째 특징 값들의 평균 \(\mu_k\)와 분산 \(\sigma_k\)를 구하면 화이트닝을 수행할 수 있다.
화이트닝 된 출력 특징 값 \(\lbrace \widehat{x}_{k1}, \widehat{x}_{k2}, ... \widehat{x}_{kn} \rbrace\)들은 정규분포에 가깝게 분포되어 있는 상태이다. 이 특징 값들에 Scale 파라미터 \(\gamma\)를 곱하고 Shift 파라미터 \(\beta\)를 더하면, 출력 특징 값의 분포가 \(\gamma\)에 따라 전체적으로 넓어지고 \(\beta\)에 따라 이동하면서 새로운 분포를 가진다.
이 때, Scale과 Shift 파라미터는 역전파 과정에서 레이어 파라미터와 같이 학습된다. 손실 함수를 줄이는 방향으로 Scale과 Shift 파라미터가 계속 갱신되다보면, 모델의 성능이 가장 좋아지는 레이어의 출력 특징 값 분포가 무엇인지 찾을 수 있게 된다. 그리고 Scale과 Shift 파라미터가 매 반복마다 갱신되기 때문에, 단순 화이트닝만 사용해서 레이어의 출력 배치를 정규화했을 때의 문제가 사라진다. 매번 출력 배치의 특징 값 분포가 바뀌는 동시에 최대한으로 다양한 특징 값들의 영향을 그래디언트에 반영할 수 있기 때문이다. 출력 배치의 분포가 매번 달라지니 파라미터 갱신의 영향이 사라지던 문제가 해결되어 레이어 파라미터들을 정상적으로 갱신할 수 있게 된 것이다. Scale과 Shift 파라미터 \(\gamma, \beta\) 가 모델의 가중치와 편향을 보조하는 것처럼 동작한다.
모델을 학습할 때 배치 정규화를 사용했으면 당연히 모델로 실제 추론을 시도할 때도 배치 정규화를 수행해야 한다. 그러나 추론시에는 모델 학습 때와 같이 중간 배치의 특징 값들을 정규화할 수 없다. 추론은 미니 배치 단위로 이루어지지 않기 때문이다. 그래서 추론시에는 레이어마다 고정된 평균과 분산 값을 이용해 정규화해주는 것이 좋다. 레이어 파라미터를 학습할 때와 같은 경향의 정규화를 보장하기 위해서이다. 때문에 모델을 학습할 때, 매 반복에서 각 레이어별로 나타난 특징 값 평균과 분산의 이동 평균 을 저장해놓는다. 모든 학습 데이터셋에 대한 중간 레이어 출력 특징 값의 평균과 분산을 저장해놓는 것이다. 학습 데이터셋에서 나타날 수 있는 해당 레이어 출력의 모든 특징 값에 대한 평균이므로, 추론시에 해당 레이어 출력을 저장해놓은 평균과 분산으로 정규화하면 학습 데이터에서의 전체적인 경향과 일치하게 정규화될 것이다. 동일한 경향으로 정규화되어야 학습된 Scale과 Shift 파라미터를 적용했을 때 최상의 모델 성능을 얻어낼 수 있다. 정규화를 위한 이동평균들은 보통 학습 종료 이전 일정량의 배치에서 나타난 평균과 분산의 단순 이동평균을 사용한다. 지수이동평균을 사용하기도 한다.
배치 정규화는 입력과 출력 배치가 존재하고 학습해야 할 Scale과 Shift 파라미터가 존재하므로 하나의 레이어로 구현할 수 있다. 앞서 설명했다시피 보통의 경우 레이어 출력 이후 활성 함수를 거치기 전에 배치 정규화 레이어가 구현된다. 레이어 출력 배치를 입력받아 정규화된 배치를 출력하는 레이어다.
앞에서 레이어의 출력 배치를 정규화한다고 가정하고 설명했지만, 사실 배치 정규화 레이어는 어떠한 입력 배치던지 받아 특징 값별로 정규화한 출력 배치를 만들어낼 수 있으므로 어느 위치에 구현해도 상관은 없다. 다음 그림처럼 레이어에 배치가 입력되기 직전에 배치 정규화를 수행하기도 한다. 이 경우 이전 레이어 출력이 비선형 활성 함수를 거친 후에 정규화된다.
보통의 경우 활성 함수에 입력하기 전에 배치 정규화 레이어를 놓는 것이 더 좋은 결과를 보인다고 알려져 있다.
배치 정규화를 적용하면 가중치 감쇠 등으로 얻어냈던 파라미터의 정규화 효과를 보다 직접적으로 구현할 수 있다. 중간 레이어 입출력의 특징 값들이 튀는 일 없이 고르게 분포하도록 강제하여 그래디언트가 특정한 파라미터에만 몰리는 현상이 사라진다. 또한 배치 정규화 레이어의 Scale과 Shift 파라미터의 학습으로 출력 특징 값이 커지는 현상도 동시에 막을 수 있다. 즉, 내부 공변량 변화로 인해 레이어의 파라미터가 오버피팅되는 현상을 막을 수 있게 된다.
같은 이유로 배치 정규화 계층은 그래디언트가 폭발하거나 사라지는 문제도 해결할 수 있다. 그래디언트가 특정 파라미터에 몰리지 않게 되니, 맨 앞쪽의 레이어까지 역전파를 진행해도 그래디언트의 값이 폭발적으로 커지지 않는다. 또한, Scale과 Shift 파라미터의 학습으로 출력 특징 값들이 그래디언트가 0이 되는 지점으로 몰리는 일을 피해, 그래디언트 소실 문제 또한 해결할 수 있다. 이 모든 문제들이 내부 공변량 변화라는 같은 맥락에서 발생한 문제들이고, 배치 정규화는 이 문제를 직접적으로 해결했으므로 모델 학습시에 발생할 수 있는 다양한 문제에 대한 해법을 동시에 제공해주는 것이다.
그래디언트가 커져도 어차피 배치 정규화 레이어가 출력 특징 값들이 커지지 않도록 강제하므로 파라미터의 갱신 폭을 일정하게 유지할 수 있다. 그래서 배치 정규화를 이용하면 모델의 학습률을 크게 가져갈 수 있다. 학습률이 커지면 지역 최적점으로 모델 파라미터가 수렴하는 일을 막을 수 있다. 배치 정규화를 적용하지 않았을 때보다 상대적으로 더 빠르면서 효율적으로 모델을 최적화할 수 있게 되는 것이다. 이것이 배치 정규화가 강력한 성능을 보이는 주된 이유이고, 딥 러닝 모델에 필수적인 요소가 된 이유이다. 더 깊고 넓은 모델의 학습을 상대적으로 더 빠르게 진행할 수 있게 되었기 때문이다.