본문 바로가기

AIFFLE/STARTER

[DL] 7. 모델 크기 조절과 규제

728x90
학습 내용
  • 과대적합을 방지하기 위한 여러가지 방법에 대해 알아보자
  • 모델 크기조절, 규제, 드롭아웃에 대해 알아보자

 

 

1.  모델 크기 조절

  • 과대적합 문제를 해결하려면 모델의 크기를 조절해야 함
  • 모델 크기를 조절하는 방법은 크게 레이어의 유닛수와 레이어의 수를 조절하는 것이 있음.
    • 레이어의 유닛수를 증가 또는 감소 : 모델 전체 파라미터 수를 증가하거나 감소
    • 레이어의 수를 증가 : 더 깊은 신경망을 만들어 모델의 크기를 증가

 

모델 크기 조절 실험 예시

데이터 로드 및 전처리

먼저 모델의 학습에 사용하기 위해 원-핫 인코딩(one-hot encoding)을 위한 함수를 정의합니다.
그리고 imdb.load_data()를 통해 IMDB 데이터를 다운로드한 뒤,
10000 차원의 학습 데이터로 원-핫 인코딩을 수행합니다.

from keras.datasets import imdb
import numpy as np

# 아래 imdb.load_data의 num_words를 10000으로 설정할 예정이기 때문에 
# dim도 10000으로 맞춰주기

def one_hot_encoding(data, dim=10000): 
  results = np.zeros((len(data), dim))
  for i, d in enumerate(data):
    results[i, d] = 1.
  return results

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

x_train = one_hot_encoding(train_data)
x_test = one_hot_encoding(test_data)

y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

모델 구성 및 컴파일

IMDB 데이터셋에 대해 분류하는 모델로서
기존에 정의했던 3개의 Dense 레이어를 가지는 딥러닝 모델을 정의합니다.
그리고 rmsprop 옵티마이저, binary_crossentropy 손실 함수, accuracy 지표를 사용하도록 컴파일 합니다.

import tensorflow as tf
from tensorflow.keras import models, layers

model = models.Sequential()
model.add(layers.Dense(128, activation='relu', input_shape=(10000, ), name='input'))
model.add(layers.Dense(128, activation='relu', name='hidden'))
model.add(layers.Dense(1, activation='sigmoid', name='output'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])
model.summary()

summary()를 통해 확인해보면, 모델의 전체 파라미터의 수가 1,296,769개인 것을 알 수 있습니다.

모델 학습

딥러닝 모델을 fit()을 이용해 학습시킵니다.

history = model.fit(x_train, y_train,
                    epochs=30,
                    batch_size=512,
                    validation_data=(x_test, y_test))

 

모델의 지표 결과로 loss, val_loss, accuracy, val_accuracy를 차트로 살펴봅니다.

import matplotlib.pyplot as plt

history_dict = history.history

loss = history_dict['loss']
val_loss = history_dict['val_loss']
epochs = range(1, len(loss) + 1)

fig = plt.figure(figsize=(12, 5))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, loss, 'b--', label='train_loss')
ax1.plot(epochs, val_loss, 'r--', label='val_loss')
ax1.set_title('Train and Validation Loss')
ax1.set_xlabel('Epochs')
ax1.set_ylabel('Loss')
ax1.grid()
ax1.legend()

accuracy = history_dict['accuracy']
val_accuracy = history_dict['val_accuracy']

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, accuracy, 'b--', label='train_accuracy')
ax2.plot(epochs, val_accuracy, 'r--', label='val_accuracy')
ax2.set_title('Train and Validation Accuracy')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('Accuracy')
ax2.grid()
ax2.legend()

plt.show()

결과를 보면, 딥러닝 모델이 과대적합된 상태인 것을 알 수 있음

 

1-1. 모델 크기 증가

먼저 모델의 크기를 증가시키면 어떻게 될지 살펴 보겠습니다.

Dense 레이어의 유닛수를 128에서 2048로 크게 증가시켜서
모델의 전체 파라미터 수가 24,680,449개가 되었습니다.

b_model = models.Sequential()
b_model.add(layers.Dense(2048, activation='relu', input_shape=(10000, ), name='input3'))
b_model.add(layers.Dense(2048, activation='relu', name='hidden3'))
b_model.add(layers.Dense(1, activation='sigmoid', name='output3'))
b_model.compile(optimizer='rmsprop',
                loss='binary_crossentropy',
                metrics=['accuracy'])
b_model.summary()

 

b_model_history = b_model.fit(x_train, y_train,
                              epochs=30,
                              batch_size=512, 
                              validation_data=(x_test, y_test))
b_history_dict = b_model_history.history

b_loss = b_history_dict['loss']
b_val_loss = b_history_dict['val_loss']
epochs = range(1, len(b_loss) + 1)

fig = plt.figure(figsize=(12, 5))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, b_loss, 'b-', label='train_loss(large)')
ax1.plot(epochs, b_val_loss, 'r-', label='val_loss(large)')
ax1.plot(epochs, loss, 'b--', label='train_loss')
ax1.plot(epochs, val_loss, 'r--', label='val_loss')
ax1.set_title('Train and Validation Loss')
ax1.set_xlabel('Epochs')
ax1.set_ylabel('Loss')
ax1.grid()
ax1.legend()

b_accuracy = b_history_dict['accuracy']
b_val_accuracy = b_history_dict['val_accuracy']

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, b_accuracy, 'b-', label='train_accuracy(large)')
ax2.plot(epochs, b_val_accuracy, 'r-', label='val_accuracy(large)')
ax2.plot(epochs, accuracy, 'b--', label='train_accuracy')
ax2.plot(epochs, val_accuracy, 'r--', label='val_accuracy')
ax2.set_title('Train and Validation Accuracy')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('Accuracy')
ax2.grid()
ax2.legend()

plt.show()

결과를 보면, 모델의 크기가 클수록, 즉 파라미터 수가 많은 신경망일수록
빠르게 훈련 데이터 모델링이 가능하고,
학습 손실이 낮아지는 것을 알 수 있습니다.

그러나 모델이 과대적합에는 더욱 민감해지는 것을 학습-검증 데이터 손실을 통해 확인이 가능합니다.

 

 

1-2. 모델 크기 감소

이제 모델에서 사용한 Dense 레이어의 유닛수를 128대신 16으로 줄여서 사용합니다.
즉, 모델의 크기를 줄여서 전체 파라미터의 수가 160,305개로 줄어들게 됩니다.

s_model = models.Sequential()
s_model.add(layers.Dense(16, activation='relu', input_shape=(10000, ), name='input2'))
s_model.add(layers.Dense(16, activation='relu', name='hidden2'))
s_model.add(layers.Dense(1, activation='sigmoid', name='output2'))
s_model.compile(optimizer='rmsprop',
                loss='binary_crossentropy',
                metrics=['accuracy'])
s_model.summary()

s_model_history = s_model.fit(x_train, y_train,
                              epochs=30,
                              batch_size=512, 
                              validation_data=(x_test, y_test))
s_history_dict = s_model_history.history

s_loss = s_history_dict['loss']
s_val_loss = s_history_dict['val_loss']
epochs = range(1, len(s_loss) + 1)

fig = plt.figure(figsize=(12, 5))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, b_loss, 'b-', label='train_loss(large)')
ax1.plot(epochs, b_val_loss, 'r-', label='val_loss(large)')
ax1.plot(epochs, loss, 'b--', label='train_loss')
ax1.plot(epochs, val_loss, 'r--', label='val_loss')
ax1.plot(epochs, s_loss, 'b:', label='train_loss(small)')
ax1.plot(epochs, s_val_loss, 'r:', label='val_loss(small)')
ax1.set_title('Train and Validation Loss')
ax1.set_xlabel('Epochs')
ax1.set_ylabel('Loss')
ax1.grid()
ax1.legend()

s_accuracy = s_history_dict['accuracy']
s_val_accuracy = s_history_dict['val_accuracy']

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, b_accuracy, 'b-', label='train_accuracy(large)')
ax2.plot(epochs, b_val_accuracy, 'r-', label='val_accuracy(large)')
ax2.plot(epochs, accuracy, 'b--', label='train_accuracy')
ax2.plot(epochs, val_accuracy, 'r--', label='val_accuracy')
ax2.plot(epochs, s_accuracy, 'b:', label='train_accuracy(small)')
ax2.plot(epochs, s_val_accuracy, 'r:', label='val_accuracy(small)')
ax2.set_title('Train and Validation Accuracy')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('Accuracy')
ax2.grid()
ax2.legend()

plt.show()

모델의 크기를 줄였더니 과대적합 문제에 조금 덜 민감한 것을 알 수 있습니다.
즉, 모델의 최적화를 위해서는 파라미터 수를 적절하게 조절 해야 된다는 것을 알 수 있습니다.

그렇다면 어느정도 파라미터가 적절할까요?

 

2. 규제(Regularization)

  • 가중치의 파라미터 값이 커서 과대적합이 발생하는 경우가 많음
    • 이를 방지하기 위해 큰 가중치 값은 큰 규제를 가하는 방법이 규제
  • 규제를 통해 가중치의 절댓값을 가능한 작게 만들어 가중치의 모든 원소를 0에 가깝게 하여 모든 특성이 출력에 주는 영향을 최소화
  • 규제를 통해서 가중치의 분포가 더 균일하게 되고, 복잡한 네트워크일수록 네트워크의 복잡도에 제한을 두어 가중치가 작은 값을 가지도록 합니다.

규제가 모델이 과대적합 되지 않도록 강제로 제한하는 역할을 하므로
적절한 규제값을 찾는 것이 중요합니다.

모델의 손실함수에서 큰 가중치에 비용을 추가하는 형태로 규제를 적용할 수 있으며
대표적인 방법으로 L1 규제와 L2 규제가 있습니다.

L1 규제는 가중치의 절댓값에 비례하는 비용이 추가되고,
L2 규제는 가중치 감쇠(weight decay)로도 불리며, 가중치의 제곱에 비례하는 비용이 추가됩니다. 또한, L1과 L2를 둘다 합쳐서 사용하는 경우도 존재합니다.

2-1. L1 규제

  • 가중치의 절댓값 합에 비례하는 비용을 손실 함수에 추가하는 방법
  • 가중치의 절댓값은 L1 노름(norm)이라고 하며 다음과 같이 표현됨
    $$ \left| w \right|_1 = \sum{i=1}^{n}\left| w_i \right| $$
  • 전체 비용은 다음과 같이 기존의 손실 함수 $L$에 $\alpha$를 곱한 L1 규제를 적용하여 계산
    $$ Cost = \frac{1}{n} \sum_{i=1}^{n} L(y_i, \hat{y_i}) + \alpha \sum_{j=1}^{m} \left| w_j\right| $$
  • $L(y_i, \hat{y_i})$ : 손실함수(Loss Function)
  • $\alpha$ 값을 증가 : 규제가 강해져서 절댓값의 합을 줄이도록 학습하여 가중치가 0인 중요하지 않은 것들은 제외되어 일반화에 적합하게 됨
  • $\alpha$ 값을 감소 : 규제가 약해져서 가중치 값이 증가, 과대적합

 

케라스에서 L1 규제를 사용하기 위해서는
적용할 레이어에서 가중치 정규화인 kernel_regularizer를 l1으로 지정해주면 됩니다.
물론 레이어에 편향 정규화인 bias_regularizer와
출력값 정규화인 activity_regularizer에도 적용할 수 있습니다.

l1_model =  models.Sequential()
l1_model.add(layers.Dense(16, 
                          kernel_regularizer='l1',
                          activation='relu', 
                          input_shape=(10000, )))
l1_model.add(layers.Dense(16, 
                          kernel_regularizer='l1',
                          activation='relu'))
l1_model.add(layers.Dense(1, activation='sigmoid'))
l1_model.compile(optimizer='rmsprop',
                 loss='binary_crossentropy',
                 metrics=['accuracy'])
l1_model.summary()

학습

l1_model_hist = l1_model.fit(x_train, y_train,
                             epochs=30,
                             batch_size=512,
                             validation_data=(x_test, y_test))

시각화

l1_val_loss = l1_model_hist.history['val_loss']

epochs = range(1, 31)
plt.plot(epochs, val_loss, 'k--', label='Model')
plt.plot(epochs, l1_val_loss, 'b--', label='L1-regularized')
plt.xlabel('Epochs')
plt.ylabel('Validation Loss')
plt.legend()
plt.grid()
plt.show()

기존 모델의 결과와 L1 규제를 사용한 결과를 비교해보면,
기존 모델은 Loss 값이 점점 증가하며 과대적합의 모습을 보이는 반면,
L1 규제 결과는 안정적으로 Loss 값이 감소되는 것을 알 수 있습니다.

2-2. L2 규제

  • 가중치의 제곱에 비례하는 비용을 손실 함수의 일정 값에 더하는 방법
  • 가중치의 제곱은 L2 노름(norm)이라고 하며 다음과 같이 표현됨
    $$ \left| w_2 \right| = \sqrt{\sum_{i=1}^{n}\left| w_i \right|^2} $$
  • 전체 비용은 다음과 같이 기존의 손실 함수 $L$에 $\lambda$를 곱한 L1 규제를 적용하여 계산
    $$ Cost = \frac{1}{n} \sum_{i=1}^{n} L(y_i, \hat{y_i}) + \lambda \sum_{j=1}^{m} w_j^2 $$
  • $L(y_i, \hat{y_i})$ : 손실함수(Loss Function)
  • $\lambda$ 값이 크면 가중치 감소가 커지고, 작으면 가하는 규제가 적어짐
  • L2규제는 L1 보다 더 Robust한 모델을 생성하므로 많이 사용되는 방법 중 하나임

L2 규제는 가중치의 제곱에 비례하는 비용을 손실 함수의 일정 값을 더하여 과대적합을 방지합니다.

여기서 λ 값이 크면 가중치 감소가 커지고,
작으면 가하는 규제가 적어지게 됩니다.

L2 규제는 L1보다 더 Robust한 모델을 생성하므로 많이 사용되는 방법 중 하나입니다.

케라스에서 L2 규제를 사용하기 위해서는 적용할 레이어에서 kernel_regularizer를 l2로 지정해주면 됩니다.

l2_model =  models.Sequential()
l2_model.add(layers.Dense(16, 
                          kernel_regularizer='l2',
                          activation='relu', 
                          input_shape=(10000, )))
l2_model.add(layers.Dense(16, 
                          kernel_regularizer='l2',
                          activation='relu'))
l2_model.add(layers.Dense(1, activation='sigmoid'))
l2_model.compile(optimizer='rmsprop',
                 loss='binary_crossentropy',
                 metrics=['accuracy'])
l2_model.summary()

학습

l2_model_hist = l2_model.fit(x_train, y_train,
                             epochs=30,
                             batch_size=512,
                             validation_data=(x_test, y_test))

시각화

l2_val_loss = l2_model_hist.history['val_loss']

epochs = range(1, 31)
plt.plot(epochs, val_loss, 'k--', label='Model')
plt.plot(epochs, l2_val_loss, 'r--', label='L2-regularized')
plt.xlabel('Epochs')
plt.ylabel('Validation Loss')
plt.legend()
plt.grid()
plt.show()

L2 규제 방법은 기존 모델 결과와 비교해 Loss 값이 매우 낮게 감소되어
과대적합 문제를 해결하는 것을 알 수 있습니다.

 

2-3. L1 L2 규제

L1과 L2 규제를 같이 적용하는 방법도 존재하는데,

케라스에서 L1과 L2 규제를 같이 사용하기 위해서는
적용할 레이어에서 kernel_regularizer를 l1_l2로 지정해주면 됩니다.

l1_l2_model =  models.Sequential()
l1_l2_model.add(layers.Dense(16, 
                             kernel_regularizer='l1_l2',
                             activation='relu', input_shape=(10000, )))
l1_l2_model.add(layers.Dense(16, 
                             kernel_regularizer='l1_l2',
                             activation='relu'))
l1_l2_model.add(layers.Dense(1, activation='sigmoid'))
l1_l2_model.compile(optimizer='rmsprop',
                    loss='binary_crossentropy',
                    metrics=['accuracy'])
l1_l2_model.summary()

학습

l1_l2_model_hist = l1_l2_model.fit(x_train, y_train,
                                  epochs=30,
                                  batch_size=512,
                                  validation_data=(x_test, y_test))

시각화

l1_l2_val_loss = l1_l2_model_hist.history['val_loss']

epochs = range(1, 31)
plt.plot(epochs, val_loss, 'k--', label='Model')
plt.plot(epochs, l1_l2_val_loss, 'g--', label='L1_L2-regularized')
plt.xlabel('Epochs')
plt.ylabel('Validation Loss')
plt.legend()
plt.grid()
plt.show()

L1 L2 규제를 함께 사용하는 경우는 L1 규제와 큰 차이가 없는 것을 알 수 있습니다.

전체 규제 방법들을 다 비교하여 시각화

epochs = range(1, 31)
plt.plot(epochs, val_loss, 'k--', label='Model')
plt.plot(epochs, l1_val_loss, 'b--', label='L1-regularized')
plt.plot(epochs, l2_val_loss, 'r--', label='L2-regularized')
plt.plot(epochs, l1_l2_val_loss, 'g--', label='L1_L2-regularized')
plt.xlabel('Epochs')
plt.ylabel('Validation Loss')
plt.legend()
plt.grid()
plt.show()

전체 규제 방법들을 다 비교해보면, L2 규제가 가장 좋은 결과를 보여주고 있습니다.
물론 어떤 모델에 어떤 규제 방법이 좋을지는 실험을 통해 찾아가는 과정이 필요합니다.

 

3. 드롭아웃

딥러닝 모델의 과대적합을 방지하기 위한 규제 기법 중 하나.
다른 규제 기법과 달리 드롭아웃은 개념도 쉽고 효과적이며 가장 널리 사용되는 방법입니다.
드롭아웃은 모델이 학습할 때 전체 노드 중에서 일부 노드만을 사용 하는 방법입니다.

신경망에 드롭아웃을 적용하면,
학습이 진행되는 동안 무작위로 층(레이어)의 일부 특성(노드)를 제외하는 형태로 동작합니다.

예를 들어, [1.0, 3.2, 0.6, 0.8, 1.1] 라는 벡터에 대해 드롭아웃을 적용하면,
무작위로 0으로 바뀌어서 [0, 3.2, 0.6, 0.8, 0]가 됩니다.

 

일반적으로는 20% ~ 50% 사이의 비율로 지정되어 사용됩니다.
테스트 단계에서는 그 어떤 노드도 드롭아웃 되지 않고,
대신 해당 레이어의 출력 노드를 드롭아웃 비율에 맞게 줄여주게 됩니다.

Q. 위 그림처럼 각각의 레이어의 40%, 60%, 40%를 무작위로 드롭아웃하게 되면 전부 몇가지 조합이 나올 수 있나요?(각 노드는 서로 동일하지 않습니다, 조합 공식을 이용해 보세요 nCr = n!/(n-r)!r!)

10 x 10 x 10 = 1000가지

 

드롭아웃 (20%)

드롭아웃이 포함된 딥러닝 모델을 정의하기 위해
기존 모델에서 사용한 구조에서 적절한 위치에 드롭아웃 레이어를 추가해야 합니다.

먼저 첫번째 레이어와 두번째 레이어 사이에 Dropout 레이어를 추가하는데,
20% 정도만 적용하기 위해서 0.2로 지정해줍니다.
마찬가지로 기존 두번째 레이어와 세번째 레이어 사이에
20%로 적용한 Dropout 레이어를 추가합니다.

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dropout(0.2))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dropout(0.2))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])
model.summary()

기존 모델과 어떤 차이가 나는지 비교하기 위해 학습 히스토리를 별도로 저장합니다.

drop_20_history = model.fit(x_train, y_train,
                            epochs=30,
                            batch_size=512,
                            validation_data=(x_test, y_test))
drop_20_dict = drop_20_history.history

drop_20_loss = drop_20_dict['loss']
drop_20_val_loss = drop_20_dict['val_loss']
epochs = range(1, len(loss) + 1)

fig = plt.figure(figsize=(12, 5))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, loss, 'b-', label='train_loss')
ax1.plot(epochs, val_loss, 'r-', label='val_loss')
ax1.plot(epochs, drop_20_loss, 'b--', label='train_loss (dropout 20%)')
ax1.plot(epochs, drop_20_val_loss, 'r--', label='val_loss (dropout 20%)')
ax1.set_title('Train and Validation Loss')
ax1.set_xlabel('Epochs')
ax1.set_ylabel('Loss')
ax1.grid()
ax1.legend()

drop_20_accuracy = drop_20_dict['accuracy']
drop_20_val_accuracy = drop_20_dict['val_accuracy']

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, accuracy, 'b-', label='train_accuracy')
ax2.plot(epochs, val_accuracy, 'r-', label='val_accuracy')
ax2.plot(epochs, drop_20_accuracy, 'b--', label='train_accuracy (dropout 20%)')
ax2.plot(epochs, drop_20_val_accuracy, 'r--', label='val_accuracy (dropout 20%)')
ax2.set_title('Train and Validation Accuracy')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('Accuracy')
ax2.grid()
ax2.legend()

plt.show()

차트를 보면 기존 모델과 비교하여 드롭아웃을 적용한 모델이 과대적합 정도가 감소한 것을 알 수 있습니다.

 

드롭아웃 (50%)

기존 모델보다 드롭아웃 비중을 50%로 증가시키기 위해
Dropout 레이어에서 0.2로 지정된 값을 0.5로 증가시켜 줍니다.

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])
model.summary()

 

 

참고자료

아이펠

728x90

'AIFFLE > STARTER' 카테고리의 다른 글

[DL] 8. 가중치 초기화와 배치 정규화  (0) 2024.05.09
[DL] 4. 딥러닝 모델 학습  (0) 2024.05.08
[DL] 6. 모델 학습 기술  (0) 2024.05.07
[DL] 5. 모델 저장과 콜백  (0) 2024.05.07
[DL] 3. 딥러닝 구조와 모델  (0) 2024.05.02