[딥.한.끝] 8. 가중치 초기화와 배치 정규화

PurinYun·2023년 12월 19일
0

AIffel

목록 보기
62/75

1. 가중치 초기화

Weights Initialization: 가중치 초기화는 신경망의 성능에 큰 영향을 주는 요소 중 하나.
만약 가중치의 값이 일부 값으로 치우치게 되면,
활성화 함수를 통과한 값들도 치우치게 되고,
결국 표현할 수 있는 신경망의 수가 적어지는 문제가 발생합니다.

보통 가중치 초기값으로 0에 가까운 무작위 값을 사용하더라도
초기값에 따라서 모델의 성능 차이가 발생할 수 있습니다.
즉, 모델의 활성화 값이 골고루 분포 되는 것이 중요합니다.

가중치 초기화 방법에 따라서 어떻게 달라지는지 살펴보기 위해 지정된 활성화 함수 activation과 초기화 initializer에 따라 모델을 생성하는 build_model 함수를 정의합니다.

from tensorflow.keras import models, layers, optimizers

def build_model(activation, initializer):
  model = models.Sequential()
  model.add(layers.Input(shape=(400, 20), name='input'))
  model.add(layers.Dense(20, activation=activation, name='hidden1', 
                        kernel_initializer=initializer))
  model.add(layers.Dense(20, activation=activation, name='hidden2', 
                        kernel_initializer=initializer))
  model.add(layers.Dense(20, activation=activation, name='hidden3', 
                        kernel_initializer=initializer))
  model.add(layers.Dense(20, activation=activation, name='hidden4', 
                        kernel_initializer=initializer))
  model.add(layers.Dense(20, activation=activation, name='hidden5', 
                        kernel_initializer=initializer))
  model.compile(loss='sparse_categorical_crossentropy',
                optimizer=optimizers.SGD(),
                metrics=['accuracy'])
  return model

각 레이어마다 분포값을 히스토그램으로 출력하기 위한 show_layer 함수를 정의합니다.

import numpy as np 
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')

def show_layer(model):
  input = np.random.randn(400, 20)

  plt.figure(figsize=(12, 6))
  for i in range(1, 6):
    name = 'hidden' + str(i)
    layer = model.get_layer(name)
    input = layer(input)
    plt.subplot(1, 6, i)
    plt.title(name)
    plt.hist(input, 20, range=(-1, 1))
    plt.subplots_adjust(wspace=0.5, hspace=0.5)
  plt.show()

여러 활성화 함수들이 있지만, 크게 선형 함수와 비선형 함수로 구분하여
가중치 초기화 방법에 따라 어떻게 분포가 달라지는지 살펴보도록 하겠습니다.

1-1. 선형 함수 가중치 초기화

활성화 함수 중에서 activations.sigmoid를 사용하여 초기화 방법들을 비교해봅니다.
sigmoid 함수는 전체적으로는 볼 때 비선형 함수이지만
특정 범위(-1, 1)에서는 선형 함수입니다.

(1) 제로 초기화

initializers.Zeros()를 이용해 가중치를 제로값으로 초기화해 봅시다.

from tensorflow.keras import initializers, activations

model = build_model(activations.sigmoid, initializers.Zeros())
show_layer(model)

각 레이어의 값이 0.5에 몰려 있는 이유는 가중치 값이 활성화 함수로 사용한 sigmoid 함수를 통과하였기 때문입니다. (sigmoid(0)=0.5)

가중치를 제로값으로 초기화할 경우
각 레이어의 가중치 값 분포가 특정한 값으로만 몰려 있는 것을 알 수 있습니다.
가중치 값이 0일 경우에는
오차역전파에서 모든 가중치 값이 똑같이 갱신되어
학습이 올바르게 진행될 수 없습니다.

(2) 정규분포 초기화

initializers.RandomNormal()를 통해 정규분포를 따르도록 무작위 값을 초기화해 봅시다.

model = build_model(activations.sigmoid, initializers.RandomNormal())
show_layer(model)

정규분포를 따르도록 무작위 값을 초기화하면
제로 초기화보다는 분포가 퍼져있는 것을 알 수 있습니다.
그러나 한 곳에 치우쳐 있기 때문에
여전히 신경망의 표현을 제한한다는 문제가 있습니다.

(3) 균일분포 초기화

initializers.RandomUniform()를 통해 균일분포를 따르는 무작위 값으로 초기화헤 봅시다.

model = build_model(activations.sigmoid, initializers.RandomUniform())
show_layer(model)

균일분포를 따르는 무작위 값으로 초기화한 결과도
제로 초기화보다는 분포가 퍼져있는 것을 알 수 있습니다.
그러나 활성화 값이 균일하지 않으므로
역전파로 전해지는 기울기 값이 사라질 수 있습니다.

(4) Xavier(Glorot) 정규분포 초기화

Xavier(Glorot) 방법은 은닉층의 노드의 수가 n이라면
표준편차가 아래와 같은 분포로 초기화 를 수행합니다.

initializers.GlorotNormal()를 적용한 모델을 시각화해 봅시다.

model = build_model(activations.sigmoid, initializers.GlorotNormal())
show_layer(model)

initializers.GlorotNormal()를 적용한 모델을 시각화한 결과를 보면
비교적 분포로 고르고 레이어마다 표현이 잘 되고 있는 것을 알 수 있습니다.
즉, 더 많은 가중치에 역전파가 전달이 가능하게 됩니다.
일반적으로 Xavier(Glorot) 방법은 활성화 함수가 선형함수인 경우에 매우 적합 합니다.

(5) Xavier(Glorot) 균일분포 초기화

initializers.GlorotUniform()를 통해
Xavier(Glorot) 균일분포 초기화를 이용한 결과도 마찬가지로 잘 분포되어 있습니다.

model = build_model(activations.sigmoid, initializers.GlorotUniform())
show_layer(model)

활성화 함수를 sigmoid 대신 tanh를 적용하면

model = build_model(activations.tanh, initializers.GlorotUniform())
show_layer(model)

좀 더 균일한 분포의 모습을 보이는 것을 알 수 있습니다.

(6) He 정규분포 초기화

He 초기화 방법은 표준편차가

위와 같은 분포를 가지도록 초기화합니다.
시각화한 결과를 살펴보면, 활성화값 분포가 균일하게 분포되어 있습니다.
참고로 ReLU와 같은 비선형함수 일 때 더 적합하다고 알려진 초기화 방법입니다.

model = build_model(activations.sigmoid, initializers.HeNormal())
show_layer(model)

(7) He 균일분포 초기화

He 균일분포도 정규분포와 유사하게 가중치 값들이 잘 분포되어 있음을 알 수 있습니다.

model = build_model(activations.sigmoid, initializers.HeUniform())
show_layer(model)

1-2. 비선형 함수 가중치 초기화

대표적인 비선형 활성화 함수인 activations.relu를 사용하여 초기화 방법들을 비교해봅시다.

(1) 제로 초기화

model = build_model(activations.relu, initializers.Zeros())
show_layer(model)

가중치를 제로값으로 초기화한 경우,
시각화 결과를 보면 레이어의 가중치 분포가 0에만 몰려 있는 것을 알 수 있습니다.
활성화 함수로 relu를 사용하였기 때문에
0인 값은 그대로 0에만 머무는 것을 알 수 있습니다.

(2) 정규분포 초기화

model = build_model(activations.relu, initializers.RandomNormal())
show_layer(model)

제로 초기화보다는 정규분포 초기화의 경우 분포가 퍼져있는 것을 알 수 있습니다.
그러나 마찬가지로 hidden1 레이어를 제외하고는 0에 치우쳐 있는 문제가 있습니다.

(3) 균일분포 초기화

model = build_model(activations.relu, initializers.RandomUniform())
show_layer(model)

균일분포도 마찬가지로 처음 레이어를 제외하고는
가중치 값이 0에 치우쳐 있는 것을 알 수 있습니다.

(4) Xavier(Glorot) 정규분포 초기화

model = build_model(activations.relu, initializers.GlorotNormal())
show_layer(model)

Xavier 초기화 방법은 relu 특성상 0인 값이 많지만,
전체 레이어를 보더라도 어느정도 분포가 퍼져있는 것을 알 수 있습니다.

(5) Xavier(Glorot) 균일분포 초기화

model = build_model(activations.relu, initializers.GlorotUniform())
show_layer(model)

균일분포도 정규분포와 유사한 형태의 분포를 보여주고 있습니다.

(6) He 정규분포 초기화

model = build_model(activations.relu, initializers.HeNormal())
show_layer(model)

He 초기화는 일반적으로 비선형 함수에 더 적합하다고 알려져있으며,
시각화된 결과도 어느정도 분포가 고르게 형성되어 있음을 알 수 있습니다.

(7) He 균일분포 초기화

model = build_model(activations.relu, initializers.HeUniform())
show_layer(model)

균일분포도 마찬가지로 정규분포와 유사한 모습을 보여주고 있습니다.

2. Reuters 딥러닝 모델 예제

데이터 로드 및 전처리

케라스에서는 Reuters 뉴스 기사 데이터를 reuters.load_data() 함수를 통해 다운로드가 가능합니다.
본 예제에서는 num_words를 10000개로 제한하여 데이터를 로드합니다.
8,982개의 훈련 데이터셋과
2,246개의 테스트 데이터셋으로 구분되어 있습니다.
레이블을 통해 뉴스 기사의 주제로 46개가 있는 것을 알 수 있습니다.

from keras.datasets import reuters
import numpy as np

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

print(len(train_data))
print(len(test_data))
print(len(set(train_labels)))

Reuters 데이터셋도 단어 인덱스로만 구성되어 있기 때문에 reuters.get_word_index() 함수를 통해 얻은 단어 인덱스 딕셔너리를 이용해 인덱스를 단어들로 변환이 가능합니다.

word_index 딕셔너리를 확인해보자 (스크롤의 압박😅)

word_index = reuters.get_word_index()
word_index

인덱스 단어 딕셔너리 형태로 만들기 위해서
단어 인덱스 딕셔너리를 역으로 변환시킵니다. (스크롤의 압박😅)

index_word = dict([(value, key) for (key, value) in word_index.items()])
index_word

train_data[0]의 각 인덱스에 매핑되는 단어들로 연결하여 하나의 리뷰를 만들어 줍니다.

news = ' '.join([str(i) for i in train_data[0]])
news

단어 인덱스에서 0은 패딩을 의미하는 토큰인 pad,
1은 문장의 시작을 의미하는 토큰인 sos,
2는 OOV(Out Of Vocabulary)를 의미하는 토큰인 unk으로 지정되어 있습니다. 그러므로 i-3으로 인덱스를 맞춰주고, 해당 토큰들은 ?로 대체합니다.

news = ' '.join([index_word.get(i-3, '?') for i in train_data[0]])
news

딥러닝 모델의 학습에 사용하기 위해서는 텍스트 데이터를 벡터로 변환해야 합니다. 일반적으로 자주 사용되는 원-핫 인코딩(one-hot encoding) 을 이용해 텍스트를 0과 1의 벡터로 변환합니다.

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

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

print(x_train[0])

Reuters 데이터셋에서 레이블은 46개의 주제를 구분하기 위한 인덱스로 되어 있습니다. 몇 개의 레이블을 확인해 봅시다.

print(train_labels[5])
print(train_labels[15])
print(train_labels[25])
print(train_labels[35])
print(train_labels[45])

뉴스 주제인 레이블도 원-핫 인코딩(범주형 인코딩) 을 이용해 변환을 수행해 주어야 합니다. 케라스에서는 utils.to_categorical() 을 이용해 쉽게 변환할 수 있습니다.

from tensorflow.keras import utils

y_train = utils.to_categorical(train_labels)
y_test = utils.to_categorical(test_labels)

print(train_labels[5], y_train[5])
print(train_labels[15], y_train[15])
print(train_labels[25], y_train[25])

모델 구성

Reuters 데이터셋을 주제별로 분류하기 위한 딥러닝 모델을 정의합니다.
순차적으로 레이어를 추가하여 단순한 모델을 생성하기 위해
Sequential() 함수를 사용합니다.

첫번째는 유닛수 256개,
활성화 함수는 'relu',
입력 차원은 10000,
이름은 input인 Dense 레이어를 추가합니다.

두번째도 마찬가지로 유닛수 256개를 가지고,
relu 활성화 함수를 사용하며
이름은 hidden인 Dense 레이어를 추가합니다.

세번째는 뉴스 주제 갯수로 유닛수를 46개로 지정하고,
다중 분류이므로 활성화 함수는 softmax 를 사용하고,
이름은 output으로 지정한 Dense 레이어를 추가합니다.

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(46, activation='softmax', name='output'))

모델 컴파일 및 학습

정의한 모델에 사용할 옵티마이저는 rmsprop를 사용하고,
손실 함수는 다중 분류이므로 categorical_crossentropy를 지정합니다.
그리고 지표는 accuracy를 사용하고,
모델의 구조를 summary() 함수를 통해 살펴봅니다.

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

생성한 딥러닝 모델을 이용해 Reuters 데이터셋을 학습하기 위해
학습용 데이터셋으로 x_train과 y_train을 지정하고,
에폭(epochs)은 40으로 지정합니다.
배치 사이즈는 512로 지정하고,
검증을 위해서 테스트 데이터인 x_test와 y_test를 지정하여
fit() 함수를 실행시킵니다.

history = model.fit(x_train, y_train,
                    epochs=40,
                    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, color='blue', label='train_loss')
ax1.plot(epochs, val_loss, color='red', 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, color='blue', label='train_accuracy')
ax2.plot(epochs, val_accuracy, color='red', label='val_accuracy')
ax2.set_title('Train and Validation Accuracy')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('Accuracy')
ax2.grid()
ax2.legend()

plt.show()

모델의 학습 결과를 살펴보면 에폭이 진행될수록 val_loss는 계속 증가하고,
val_accuracy는 조금씩 감소하는 것을 알 수 있습니다.
모델은 과대적합된 상태인 것을 알 수 있습니다.

가중치 초기화

이제 실제 Reuters 딥러닝 모델에 가중치 초기화를 적용한 결과를 살펴보기 위해
초기화 initializer에 따라 모델을 생성하는 build_model 함수를 정의합니다.
케라스에서는 기본적으로 레이어에 균일분포 초기화를 수행하지만,
다른 초기화 방법과 비교해보도록 하겠습니다.

def build_model(initializer):
  model = models.Sequential()
  model.add(layers.Dense(128,
                         activation='relu', 
                         kernel_initializer=initializer,
                         input_shape=(10000, ), 
                         name='input'))
  model.add(layers.Dense(128, 
                         activation='relu', 
                         kernel_initializer=initializer,
                         name='hidden'))
  model.add(layers.Dense(46, 
                         activation='softmax', 
                         name='output'))
  model.compile(optimizer='rmsprop',
                loss='categorical_crossentropy',
                metrics=['accuracy'])
  history = model.fit(x_train, y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_test, y_test))
  return history

모델의 학습 히스토리 결과를 비교해보기 위해서 시각화하는 diff_history 함수를 정의합니다.

def diff_history(history1, history2):
  history1_dict = history1.history
  h1_loss = history1_dict['loss']
  h1_val_loss = history1_dict['val_loss']

  history2_dict = history2.history
  h2_loss = history2_dict['loss']
  h2_val_loss = history2_dict['val_loss']

  epochs = range(1, len(h1_loss) + 1)
  fig = plt.figure(figsize=(12, 5))
  plt.subplots_adjust(wspace=0.3, hspace=0.3)

  ax1 = fig.add_subplot(1, 2, 1)
  ax1.plot(epochs, h1_loss, 'b-', label='train_loss')
  ax1.plot(epochs, h1_val_loss, 'r-', label='val_loss')
  ax1.plot(epochs, h2_loss, 'b--', label='train_loss')
  ax1.plot(epochs, h2_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()

  h1_accuracy = history1_dict['accuracy']
  h1_val_accuracy = history1_dict['val_accuracy']

  h2_accuracy = history2_dict['accuracy']
  h2_val_accuracy = history2_dict['val_accuracy']

  ax2 = fig.add_subplot(1, 2, 2)
  ax2.plot(epochs, h1_accuracy, 'b-', label='train_accuracy')
  ax2.plot(epochs, h1_val_accuracy, 'r-', label='val_accuracy')
  ax2.plot(epochs, h2_accuracy, 'b--', label='train_accuracy')
  ax2.plot(epochs, h2_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()

제로 초기화

가중치 값을 0으로 초기화하는 모델을 생성하여 학습합니다.

zero_history = build_model(initializers.Zeros())

기본 모델과 제로 초기화 모델 학습 결과를 비교해보면,
제로 초기화 모델은 학습이 제대로 되고 있지 않다는 것을 알 수 있습니다.

diff_history(history, zero_history)

정규분포 초기화

정규분포 초기화 방법도 비교를 위해서 모델을 생성하여 학습합니다.

normal_history = build_model(initializers.RandomNormal())

정규분포로 초기화한 결과는 기본 모델과 유사하지만 조금 더디게 학습된 것을 알 수 있습니다.

diff_history(history, normal_history)

Xavier(Glorot) 초기화

이제 Xavier(Glorot) 초기화 방법 결과 비교를 위해서 모델을 생성하고 학습합니다.

glorot_history = build_model(initializers.GlorotNormal())

시각화

diff_history(history, glorot_history)

Xavier 초기화 방법은 기존 모델에서 사용하는 균일분포와 매우 유사한 형태로 학습된 것을 알 수 있습니다.

He 초기화

He 초기화 방법도 비교를 위해서 모델을 생성하고 학습합니다.

he_history = build_model(initializers.HeNormal())

시각화

diff_history(history, he_history)

He 초기화는 기존 모델과 비교하여 작지만
좀 더 빠르게 학습된 것을 알 수 있습니다.
현재 모델이 비선형 함수인 relu를 사용하여
He 초기화에 더 적합한 것을 알 수 있습니다.

3. 배치 정규화

Batch Normalization: 배치 정규화는 모델에 입력되는 샘플들을 균일하게 만드는 방법으로 가중치의 활성화값이 적당히 퍼지게끔 '강제'로 적용시키는 것을 의미합니다.

보통 미니배치 단위로 데이터의 평균이 0, 표준편차는 1로 정규화를 수행하여 학습 자체가 빨리 진행될 수 있도록 만들어주고,
학습 후에도 새로운 데이터에 일반화가 잘 될 수 있도록 도와줍니다.
또한, 초기값에 크게 의존하지 않아도 되고, 과대적합을 방지하는 역할을 수행합니다.

배치 정규화는 데이터 전처리 단계에서 진행해도 되지만
정규화가 되어서 레이어에 들어갔다는 보장이 없으므로
주로 Dense 레이어 이후, 활성화 함수 이전에 활용됩니다.

모델 구성 및 컴파일

Reuters 데이터셋에 대해 뉴스 주제를 분류하는 딥러닝 모델에서
기존에 정의했던 3개의 Dense 레이어 사이에 배치 정규화를 위해
BatchNormalization() 레이어를 추가합니다.
다만 일반적으로 Dense 레이어와 Activation 활성함수 레이어 사이에 추가해야 합니다.

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

model = models.Sequential()
model.add(layers.Dense(128, input_shape=(10000, ), name='input'))
model.add(layers.BatchNormalization())
model.add(layers.Activation('relu')) 
model.add(layers.Dense(128, name='hidden'))
model.add(layers.BatchNormalization())
model.add(layers.Activation('relu')) 
model.add(layers.Dense(46, activation='softmax', name='output'))

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

batch_norm_history = model.fit(x_train, y_train,
                               epochs=40,
                               batch_size=512,
                               validation_data=(x_test, y_test))

시각화

diff_history(history, batch_norm_history)

기본 모델과 배치 정규화를 적용한 모델을 비교해보면,
배치 정규화를 적용한 모델이 좀 더 빠르고 안정되게 학습이 되는 것을 알 수 있습니다.

profile
Fantivation

0개의 댓글