과적합(오버피팅): 딥러닝 기초 시리즈 9

과적합(Overfitting)은 머신 러닝과 딥 러닝에서 모델이 훈련 데이터에 너무 맞춰져서 새로운, 이전에 보지 못한 데이터에 대한 일반화(generalization) 능력이 떨어지는 현상을 가리킵니다.

즉, 모델이 훈련 데이터에 너무 딱 맞아서 훈련 데이터에 대한 예측은 정확하지만, 새로운 데이터에 대한 예측은 부정확하게 되는 문제입니다.

이 글에서는 과적합에 대해 알아보고 어떻게 과적합을 감소시키기 위한 기법들을 소개합니다.


목차

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

참고 문헌 및 참고자료: 밑바닥부터 시작하는 딥러닝


1. 과적합(오버피팅)

과적합(Overfitting)은 머신 러닝과 딥 러닝에서 모델이 훈련 데이터에 너무 맞춰져서 새로운, 이전에 보지 못한 데이터에 대한 일반화(generalization) 능력이 떨어지는 현상을 가리킵니다. 즉, 모델이 훈련 데이터에 너무 딱 맞아서 훈련 데이터에 대한 예측은 정확하지만, 새로운 데이터에 대한 예측은 부정확하게 되는 문제입니다.

아래는 과적합에 대한 상세한 설명입니다.

1. 원인:

  • 복잡한 모델: 모델이 너무 복잡하면, 데이터에 있는 잡음(noise)이나 불필요한 세부 사항을 학습하려고 하게 됩니다.
  • 훈련 데이터 부족: 훈련 데이터가 적으면 모델은 제한된 정보로 학습하며, 이로 인해 훈련 데이터에 과도하게 적합화될 수 있습니다.
  • 학습 시간과 에포크 수: 학습 시간이 길고 에포크 수가 많으면 모델이 훈련 데이터에 과적합하기 쉬워집니다.

2. 특징:

  • 훈련 데이터에 대한 예측 정확도 높음: 모델은 훈련 데이터에 대한 예측 정확도가 높지만, 테스트 데이터나 새로운 데이터에 대한 예측 정확도가 낮습니다.
  • 노이즈 감수성 부족: 모델은 훈련 데이터의 노이즈 까지 학습하므로, 노이즈가 있는 데이터에는 민감하게 반응할 수 있습니다.
  • 일반화 능력 하락: 모델의 일반화 능력이 떨어져 새로운 데이터에 대한 예측이 부정확하게 됩니다.

3. 해결 방법:

  • 더 많은 데이터 수집: 더 많은 다양한 데이터를 수집하여 모델이 더 일반적인 특성을 학습하게 할 수 있습니다.
  • 모델 단순화: 모델의 복잡성을 줄이는 방법을 고려합니다. 예를 들어, 모델의 파라미터 수를 줄이거나, 특징 선택(feature selection)을 통해 중요한 특징만 사용할 수 있습니다.
  • 규제(Regularization): L1 또는 L2 규제를 사용하여 가중치를 제한하고, 드롭아웃과 같은 규제 기법을 적용할 수 있습니다.
  • 교차 검증(Cross-validation): 교차 검증을 사용하여 모델의 일반화 성능을 평가하고 조정합니다.

과적합은 머신러닝 모델을 개발할 때 주의해야 하는 중요한 문제 중 하나이며, 적절한 모델 선택과 규제 기법 적용을 통해 이를 해결할 수 있습니다.


2. 가중치 감소

과적합은 매개변수의 값이 커서 발생하는 경우가 많습니다. 그래서 큰 가중치에 대한 패널티를 증가시켜 가중치가 커지는 것을 억제합니다.

가중치 감소는 학습 중에 모델의 가중치 업데이트 과정에서 적용됩니다. 학습이 끝난 후에는 가중치가 최종적으로 학습되고 저장됩니다. 따라서 가중치 감소는 모델을 학습하는 동안 가중치를 제한하고 규제하는 프로세스입니다.

가중치 감소는 훈련 중에 가중치를 조절하므로 학습 과정 동안 적용됩니다. 학습이 끝나면 모델의 가중치는 최종값을 가지며, 이를 사용하여 새로운 데이터에 대한 예측을 수행할 수 있습니다.

가중치 감소는 학습 중에 모델의 가중치 업데이트 과정에서 적용됩니다. 학습이 끝난 후에는 가중치가 최종적으로 학습되고 저장됩니다. 따라서 가중치 감소는 모델을 학습하는 동안 가중치를 제한하고 규제하는 프로세스입니다.

가중치 감소는 주로 다음과 같은 방식으로 적용됩니다:

  1. 손실 함수에 가중치 감소 항 추가: 모델의 손실 함수에 가중치 감소 항(L1 또는 L2 규제)을 추가합니다. 이 항은 가중치를 훈련 중에 제한하고 규제하는 데 사용됩니다.
  2. 전체 손실 계산: 손실 함수는 훈련 데이터에 대한 예측 오차와 가중치 감소 항으로 이루어집니다. 전체 손실은 이 두 부분을 합산한 값입니다.
  3. 역전파 및 경사 하강법: 모델은 전체 손실을 최소화하기 위해 역전파(backpropagation) 알고리즘을 사용하여 가중치를 업데이트합니다. 이때 가중치 감소 항이 추가됩니다.
  4. 가중치 업데이트: 가중치 업데이트 단계에서 가중치 감소 항이 고려되어 가중치가 더 작은 값을 가지도록 조절됩니다. 이렇게 하면 가중치가 너무 커지는 것을 방지하고 일반화 능력을 향상시킵니다.

가중치 감소는 훈련 중에 가중치를 조절하므로 학습 과정 동안 적용됩니다. 학습이 끝나면 모델의 가중치는 최종값을 가지며, 이를 사용하여 새로운 데이터에 대한 예측을 수행할 수 있습니다.

1. 손실함수

최종적인 목적은 손실함수에 가중치 감소값 더하는 것입니다.

L = Data Loss + L1 또는 L = Data Loss + L2 또는 L = Data Loss + L1 + L2

2. 규제 항 (Regulation Loss)

이러한 가중치 감소에는 두가지 종류, L1 규제, L2 규제 가 있고 동시에 쓰는 경우도 있습니다.

L1 규제 (Lasso): L1 = \lambda_1 \sum_{i=0}^{n} |w_i| 

L2 규제 (RIdge): L2 = \lambda_2 \sum_{i=0} w_i^2 

Elastic Net 규제:  \text{Elastic Net Regulation} = L1 + L2

3. 경사 하강법 (Gradient Descent)

이후 경사하강법을 적용 합니다.

w = w - \text{LearningRate} \frac{\partial L}{\partial w} 

3-1. 규제 항 그레디언트 (Regularization Loss Gradient)

  1. L1 규제 항의 그래디언트 = \frac{\partial L1}{\partial W} = \lambda_1 \cdot \text{sign}(W)
    여기서 Sign(W)는 가중치 W 에 대한 부호 (양수, 또는 음수)
  2. L2 규제 항의 그래디언트 = \frac{\partial L2}{\partial W} = 2 \cdot \lambda_2 \cdot W

3-2. 가중치 업데이트 (Weight Update)

이제 사용하고 싶은 가중치 감소 (L1 규제, L2 규제) 를 선택하여 적용 훈련을 합니다.
w = w - \text{LearningRate} \cdot \frac{\partial L}{\partial w}
여기서 \frac{\partial L}{\partial w} = \frac{\partial \text{DataLoss}}{\partial w} + \frac{\partial L1}{\partial w} + \frac{\partial L2}{\partial w} 파이썬 코드로 구현

Python
import numpy as np

class RegularizedWeightUpdate:
    def __init__(self, weights, lambda_l1, lambda_l2):
        self.lambda_l1 = lambda_l1
        self.lambda_l2 = lambda_l2
        self.weights = weights

    def l1_regularization(self): # L1규제의 핵심식
        l1_regularization_per_channel = self.lambda_l1 * np.sum(np.abs(self.weights), axis=(1, 2))
        total_l1_regularization = np.sum(l1_regularization_per_channel)
        return total_l1_regularization

    def l2_regularization(self): # L2규제의 핵심식
        l2_regularization_per_channel = self.lambda_l2 * np.sum(self.weights**2, axis=(1, 2))
        total_l2_regularization = np.sum(l2_regularization_per_channel)
        return total_l2_regularization

    def forward(self):
        # 순전파를 수행 (다른 코드와 통일성을 유지)
        return self.weights

    def backward(self):
        # 역전파를 수행하는 메서드
        l1_grad = self.lambda_l1 * np.sign(self.weights)
        l2_grad = 2 * self.lambda_l2 * self.weights
        total_grad = l1_grad + l2_grad
        return total_grad, l1_grad, l2_grad
        
#결괏값을 고정시키기 위해 시드값 0으로 고정
#가중치 초기화( -2 부터 2 까지 12개의 랜덤한 요소를 만든 후에 3채널 2 x 2 로 재배
np.random.seed(0)
weights = np.random.randint(-2, 3, 12).reshape(3, 2, 2)

# RegularizedWeightUpdate 클래스의 인스턴스 생성
lambda_l1 = 0.4
lambda_l2 = 0.4
model = RegularizedWeightUpdate(weights, lambda_l1, lambda_l2)

# 순전파 수행
forwarded_weights = model.forward()

# 역전파 수행
gradients0, gradients1, gradients2 = model.backward()

print("\n역전파 결과 (L1 + L2규제 그래디언트):")
print(gradients0)

print("\n역전파 결과 (L1 규제 그래디언트):")
print(gradients1)

print("\n역전파 결과 (L2 규제 그래디언트):")
print(gradients2)

print(f"\nL1 규제 값: {model.l1_regularization()}")
print(f"L2 규제 값: {model.l2_regularization()}")

'''
역전파 결과 (L1 + L2규제 그래디언트):
[[[ 2.  -2. ]
  [ 1.2  1.2]]

 [[ 1.2 -1.2]
  [ 1.2  0. ]]

 [[ 2.  -2. ]
  [-2.   2. ]]]

역전파 결과 (L1 규제 그래디언트):
[[[ 0.4 -0.4]
  [ 0.4  0.4]]

 [[ 0.4 -0.4]
  [ 0.4  0. ]]

 [[ 0.4 -0.4]
  [-0.4  0.4]]]

역전파 결과 (L2 규제 그래디언트):
[[[ 1.6 -1.6]
  [ 0.8  0.8]]

 [[ 0.8 -0.8]
  [ 0.8  0. ]]

 [[ 1.6 -1.6]
  [-1.6  1.6]]]

L1 규제 값: 6.800000000000001
L2 규제 값: 11.600000000000001
'''

여기서 나온 역전파 결괏값 을 학습시 적용시키면 됩니다.


3. 드랍 아웃

과적합방지를 위한 드랍 아웃 예시 그림

드롭아웃은 훈련 중 일부 뉴런을 무작위로 제외하여 과적합을 방지합니다.

드롭아웃은 훈련 중에만 적용되며, 테스트나 추론 단계에서는 비활성화 됩니다. 따라서 훈련 중에 드롭아웃을 적용한 경우, 테스트 시에는 동일한 입력 데이터에 대해 동일한 결과를 얻어야 합니다. 그렇기 때문에 드롭아웃 레이어를 통과한 출력 값을 조절하는 것이 필요합니다.

드랍아웃 메서드 코드 에서 return x * (1.0 - self.dropout_ratio) 부분은 테스트 시에 드롭아웃을 적용하지 않은 경우의 출력을 계산합니다.

Python
### 생략 ###
    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            # 순전파 과정에서 활성화 함수를 통과하기 전에는 각 뉴런의 출력을 스케일링하여 활성화합니다.
            # 이렇게 하면 테스트 과정에서는 드롭아웃을 적용하지 않고 예측을 할 수 있습니다.
            return x * (1.0 - self.dropout_ratio)
### 생략 ### 하단부에 있습니다.

self.dropout_ratio는 드롭아웃 확률로, 예를 들어 0.5로 설정된 경우 50%의 뉴런이 비활성화되고 50%만 남는 것을 의미합니다. 따라서 이 코드는 훈련 시에 비활성화된 뉴런의 값에 해당 비율을 곱하여 조절한 것입니다. 이렇게 하면 테스트 시에도 훈련 시와 유사한 결과를 얻을 수 있습니다.

  1. 드롭아웃의 작동 원리: 드롭아웃은 학습 중에 신경망의 일부 뉴런을 임의로 비활성화시키는 것입니다. 이를 통해 네트워크가 각 뉴런에 너무 의존하지 않도록 하고, 다양한 특징을 학습할 수 있도록 도와줍니다. 각 뉴런을 비 활성화시킬 확률은 Dropout ratio 로 설정됩니다.
  2. 순전파 과정에서 드롭아웃: 드롭아웃은 주로 훈련(training) 중에만 사용되며, 순전파 시에는 비활성화된 뉴런의 출력을 0으로 만들어야 합니다. 하지만 순전파 과정에서 활성화 함수를 통과하기 전에는 각 뉴런의 출력을 X = X (1 - \text{Dropout ratio})로 스케일링하여 활성화합니다.
    이렇게 하면 테스트(test) 과정에서는 드롭아웃을 적용하지 않고 예측을 할 수 있습니다. 즉, X = X (1 - \text{Dropout ratio}) 를 거치는 것은 , 테스트나 추론 시에는 뉴런의 출력을 적절히 조절하여 일관된 예측 결과를 얻기 위한 것입니다.
  3. 드롭아웃의 역전파: 드롭아웃을 통한 뉴런의 무작위 비활성화를 고려하여 역전파를 수행해야 합니다.
    • 훈련(training) 중 드롭아웃 적용: 훈련 중에 드롭아웃을 적용하면 각 뉴런은 확률적으로 비활성화됩니다. 이때, 비활성화된 뉴런의 출력은 0으로 설정됩니다.
    • 역전파 시작: 역전파는 손실 함수를 미분하여 모델의 가중치와 편향을 업데이트하는 과정입니다. 드롭아웃을 사용할 때, 역전파는 순전파 시에 비 활성화된 뉴런에 대한 그래디언트(기울기)를 고려해야 합니다.
    • 비 활성화된 뉴런의 그래디언트: 비 활성화된 뉴런의 출력은 0이므로 해당 뉴런에 대한 그래디언트도 0입니다. 따라서 역 전파 시에는 비 활성화된 뉴런에 대한 그래디언트를 전파하지 않아야 합니다.
    • 활성화된 뉴런의 그래디언트: 활성화된 뉴런에 대한 그래디언트는 일반적인 역전파 방법을 사용하여 계산합니다.
    • 가중치 업데이트: 계산된 그래디언트를 사용하여 가중치와 편향을 업데이트합니다.
Python
import numpy as np
import matplotlib.pyplot as plt

class Dropout:
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None
        
    #순전파
    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            # 순전파 과정에서 활성화 함수를 통과하기 전에는 각 뉴런의 출력을 스케일링하여 활성화합니다.
            # 이렇게 하면 테스트 과정에서는 드롭아웃을 적용하지 않고 예측을 할 수 있습니다.
            return x * (1.0 - self.dropout_ratio)
            
    #역전파 계산
    def backward(self, dout):
        return dout * self.mask
# 시드고정
np.random.seed(0)

# 랜덤 인풋
x_input = np.random.randn(1,1,3,3)

# 드랍아웃이 0.3일때 드랍아웃 훈련 flg 및 미훈련 flg
# 여기서 훈련을 하지 않을 때는 드랍 아웃을 꺼주는데 최종적으로 드랍아웃의 비율만큼 가중치를 보정시켜 준다.
do_test = Dropout(0.3)
forwarded1 = do_test.forward(x_input, train_flg=True)
forwarded2 = do_test.forward(x_input, train_flg=False)

print('x_input: \n', x_input, '\n')
print('True flag: \n', forwarded1,'\n \n' ,'False flag: \n', forwarded2 ,'\n')


plt.subplot(1, 3, 1)
plt.imshow(x_input[0][0], cmap='gray')
plt.title('x_input')
plt.xticks([])  # 가로 축 숫자 제거
plt.yticks([])  # 세로 축 숫자 제거

plt.subplot(1, 3, 2)
plt.imshow(forwarded1[0][0], cmap='gray')
plt.title('True')
plt.xticks([])  # 가로 축 숫자 제거
plt.yticks([])  # 세로 축 숫자 제거

plt.subplot(1, 3, 3)
plt.imshow(forwarded2[0][0], cmap='gray')
plt.title('False')
plt.xticks([])  # 가로 축 숫자 제거
plt.yticks([])  # 세로 축 숫자 제거

plt.show()

'''
x_input: 
 [[[[ 1.76405235  0.40015721  0.97873798]
   [ 2.2408932   1.86755799 -0.97727788]
   [ 0.95008842 -0.15135721 -0.10321885]]]] 

True flag: 
 [[[[ 1.76405235  0.40015721  0.97873798]
   [ 2.2408932   0.         -0.        ]
   [ 0.         -0.15135721 -0.10321885]]]] 
 
 False flag: 
 [[[[ 1.23483664  0.28011005  0.68511659]
   [ 1.56862524  1.30729059 -0.68409452]
   [ 0.66506189 -0.10595005 -0.0722532 ]]]]
'''
과적합방지를 위한 드랍아웃 결과사진

그림을 그려보면 True 적용시 몇몇 뉴런은 사라져 있는 것을 알 수 있습니다.


결론:

“이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.”

과적합(Overfitting)은 모델이 훈련 데이터에 너무 맞춰져서 새로운 데이터에 대한 일반화 능력이 떨어지는 문제를 가리키며, 가중치 감소(Weight Decay)와 드롭아웃(Dropout)은 과적합을 줄이고 모델의 성능을 향상시키는 데 도움을 주는 규제 기법입니다.

가중치 감소는 가중치 값이 커지는 것을 제한하여 모델의 복잡성을 줄이는 방법이며, L1 규제와 L2 규제가 있습니다. 이 규제를 통해 모델이 훈련 데이터에 과적합하는 것을 방지하고 일반화 능력을 향상시킵니다.

드롭아웃은 훈련 중에 무작위로 일부 뉴런을 비활성화시켜 모델이 각 뉴런에 너무 의존하지 않도록 하며, 테스트나 추론 시에는 비활성화된 뉴런의 출력을 적절히 조절하여 일관된 예측 결과를 얻을 수 있도록 합니다.

두 규제 기법은 모델의 일반화 능력을 향상시키고 과적합을 줄이는 데 도움을 주며, 모델 개발 시에 고려해야 할 중요한 요소입니다.


함께 참고하면 좋은 글

합성곱 신경망: 딥러닝 기초 시리즈 8

배치 정규화: 딥러닝 기초 시리즈 7

가중치 초기화 이론: 딥러닝 기초 시리즈 6

역전파를 이용한 학습: 딥러닝 기초 시리즈 5

경사 하강법: 딥러닝 기초 시리즈 4

손실 함수: 딥러닝 기초 시리즈 3

활성화 함수: 딥러닝 기초 시리즈 2

퍼셉트론: 딥러닝 기초 시리즈 1

1 thought on “과적합(오버피팅): 딥러닝 기초 시리즈 9”

Leave a Comment