[딥러닝] 심층신경망, 옵티마이저

5 분 소요



from tensorflow import keras

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()

from sklearn.model_selection import train_test_split

train_scaled = train_input / 255.0
train_scaled = train_scaled.reshape(-1, 28*28)

train_scaled, val_scaled, train_target, val_target = train_test_split(
    train_scaled, train_target, test_size=0.2, random_state=42)

dense1 = keras.layers.Dense(100, activation='sigmoid', input_shape=(784,))
dense2 = keras.layers.Dense(10, activation='softmax')

이전에 만든 신경망 모델과 다른점은 입력층과 출력층 사이에 밀집층이 추가되었다. 이런 모든층을 은닉층(hidden layer)이라고 한다.

출력층에 적용하는 활성화 함수는 이진 분류일 경우 시그모이드, 다중 분류일 경우 소프트맥스로 제한되어 있다. 이에 비해 은닉층의 활성화 함수는 비교적 자유롭다. 대표적으로 시그모이드 함수와 렐루함수 등을 사용한다.

은닉층 dense1의 뉴런 개수를 정하는데는 특별한 기준이 없다. 몇 개의 뉴런을 두어야 하는지는 상당한 경험이 필요하다.

여기에 한가지 제약 사항이 있다면 출력층의 뉴런보다는 많게 만들어야한다. 여기서는 클래스 10개에 대한 확률을 예측해야 하는데 이전 은닉층의 뉴런이 10개보다 적다면 부족한 정보가 전달될 것이다.



심층 신경망 만들기

model = keras.Sequential([dense1, dense2])

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 100)               78500     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1010      
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________

심층 신경망(deep neural network, DNN)을 만들었다.

인공신경망의 강력한 성능은 층을 추가해 입력 데이터에 대해 연속적인 학습을 진행하는 능력에서 나온다.



층을 추가하는 다른 방법

model = keras.Sequential([
    keras.layers.Dense(100, activation='sigmoid', input_shape=(784,), name='hidden'),
    keras.layers.Dense(10, activation='softmax', name='output')
], name='패션 MNIST 모델')

model.summary()
Model: "패션 MNIST 모델"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
hidden (Dense)               (None, 100)               78500     
_________________________________________________________________
output (Dense)               (None, 10)                1010      
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________

위 코드처럼 층을 추가할 수 있다.

이 방법의 경우 편리하지만 아주 많은 층을 추가하려면 Sequential 클래스 생성자가 매우 길어진다. 또 조건에 따라 층을 추가할 수도 없다.

model = keras.Sequential()
model.add(keras.layers.Dense(100, activation='sigmoid', input_shape=(784,)))
model.add(keras.layers.Dense(10, activation='softmax'))

model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_2 (Dense)              (None, 100)               78500     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                1010      
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________

Sequential 클래스에서 층을 추가할 때 가장 널리 사용하는 방법은 모델의 add 메서드이다.

model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')

model.fit(train_scaled, train_target, epochs=5)
Epoch 1/5
1500/1500 [==============================] - 6s 2ms/step - loss: 0.5642 - accuracy: 0.8087
Epoch 2/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.4091 - accuracy: 0.8526
Epoch 3/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3740 - accuracy: 0.8657
Epoch 4/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3515 - accuracy: 0.8722
Epoch 5/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3340 - accuracy: 0.8804
<keras.callbacks.History at 0x7f1210023c90>

모델 훈련은 compile, fit을 통해 한다.



렐루 함수로 훈련

초창기 인공 신경망의 은닉층에 많이 사용된 활성화 함수는 시그모이드 함수였다. 하지만 이 함수는 단점이 있다. 이 함수는 오른쪽에서 왼쪽 끝으로 갈수록 그래프가 누워있기 때문에 올바른 출력을 만드는데 신속하게 대응하지 못한다.

특히 층이 많은 심층 신경망일수록 그 효과가 누적되어 학습을 더 어렵게 만든다.

이를 개선하기 위해 렐루(ReLU)함수를 사용한다. 입력이 양수일 경우 마치 활성화 함수가 없는 것처럼 그냥 입력을 통과시키고 음수일 경우에는 0으로 만든다. 렐루 함수는 max(0, z)와 같이 쓸 수 있다.

렐루 함수는 특히 이미지 처리에 좋은 성능을 낸다고 알려져 있다.

model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28, 28)))
model.add(keras.layers.Dense(100, activation='relu'))
model.add(keras.layers.Dense(10, activation='softmax'))

model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 100)               78500     
_________________________________________________________________
dense_5 (Dense)              (None, 10)                1010      
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________

케라스는 Flatten 층을 제공한다. 이 층을 사용하면 넘파이 배열의 reshape 함수를 사용하지 않아도 된다.

Flatten 클래스는 배치 차원을 제외하고 나머지 입력 차원을 모두 일렬로 펼치는 역할을 한다. 입력에 곱해지는 가중치나 절편이 없다. 따라서 인공 신경망의 성능을 기여하는 바는 없다. 하지만 Flatten 클래스를 층처럼 입력층과 은닉층 사이에 추가하기 때문에 이를 층이라고 부른다.

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()

train_scaled = train_input / 255.0

train_scaled, val_scaled, train_target, val_target = train_test_split(
    train_scaled, train_target, test_size=0.2, random_state=42)

훈련 데이터를 준비한다. 여기서 reshape 함수를 사용할 필요가 없다.

model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')

model.fit(train_scaled, train_target, epochs=5)
Epoch 1/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.5236 - accuracy: 0.8155
Epoch 2/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3890 - accuracy: 0.8602
Epoch 3/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3527 - accuracy: 0.8722
Epoch 4/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3322 - accuracy: 0.8810
Epoch 5/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3175 - accuracy: 0.8863
<keras.callbacks.History at 0x7f11cffbcc50>
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 2ms/step - loss: 0.3669 - accuracy: 0.8782
[0.3669053614139557, 0.878166675567627]

컴파일하고 훈련한다.

시그모이드 함수를 사용했을 때와 비교해 성능이 조금 향상되었다.



옵티마이저

신경망에는 하이퍼파라미터가 많다. 은닉층의 개수, 활성화 함수, 층의 종류, batch_size, epochs 등이 있다.

여기서 compile 메서드에서는 케라스의 기본 경사 하강법 알고리즘인 RMSprop을 사용했다. 케라스는 다양한 종류의 경사 하강법 알고리즘을 제공한다. 이들을 옵티마이저(optimizer)라고 부른다. 여기서 RMSprop 학습률 또한 조정할 하이퍼파리미터이다.

model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy', metrics='accuracy')
sgd = keras.optimizers.SGD()
model.compile(optimizer=sgd, loss='sparse_categorical_crossentropy', metrics='accuracy')

SGD 옵티마이저를 사용하려면 optimizer 매개변수를 ‘sgd’로 지정한다. 혹은 tensorflow.keras.optimizers 패키지 아래 SGD 클래스로 구현된거로 구현이 가능하다.

여기서 sgd와 ‘sgd’의 차이는 없다. 원래 sgd = keras.optimizers.SGD() 이런 식으로 사용해야 하는데 번거로움을 피하고자 ‘sgd’라 지정하면 자동으로 SGD 객체를 만들어 준다.

sgd = keras.optimizers.SGD(learning_rate=0.1)

만약 SGD 클래스의 학습률 기본값이 0.01일 때 이를 바꾸고 싶다면 원하는 학습률을 learning_rate 매개변수에 지정하여 사용한다.

sgd = keras.optimizers.SGD(momentum=0.9, nesterov=True)

기본 경사 하강법 옵티마이저는 모두 SGD 클래스에서 제공한다. SGD 클래스의 momentum 매개변수의 기본값은 0이다. 이를 0보다 큰 값으로 지정하면 마치 이전의 그레이디언트를 가속도처럼 사용하는 모멘텀 최적화를 사용한다. 보통 momentum 매개변수는 0.9이상을 지정한다.

위 코드처럼 nesterov 매개변수를 기본값 False에서 True로 바꾸면 네스테로프 모멘텀 최적화(또는 네스테로프 가속 경사)를 사용한다.

네스테로프 모멘텀은 모멘텀 최적화를 2번 반복해 구현한다. 대부분의 경우 네스체로프 모멘텀 최적화가 기본 확률적 경사 하강법보다 더 나은 성능을 제공한다.

모델이 최적점에 가까이 갈수록 학습률이 낮츨 수 있다. 이렇게 하면 안정적으로 최적점에 수렴할 가능성이 높다. 이런 학습률을 적응적 학습률(adaptive learning rate)이라고 한다. 이런 방식들은 학습률 매개변수를 튜닝하고 수고를 덜 수 있는 것이 장점이다.

적응적 학습률을 사용하는 대표적인 옵티마이저는 Adagrad와 RMSprop이다.

참고로 optimizer 매개변수의 기본값은 ‘rmsprop’이다.

adagrad = keras.optimizers.Adagrad()
model.compile(optimizer=adagrad, loss='sparse_categorical_crossentropy', metrics='accuracy')
rmsprop = keras.optimizers.RMSprop()
model.compile(optimizer=rmsprop, loss='sparse_categorical_crossentropy', metrics='accuracy')

위 코드처럼 옵티마이저를 설정할 수 있다.

모멘텀 최적화와 RMSprop의 장점을 접목한 것이 Adam이다. Adam은 RMSprop과 함께 맨처음 시도해 볼 수 있는 좋은 알고리즘이다. Adam 클래스도 keras.optimizers 패키지 아래에 있다. 적응적 학습률을 사용하는 이 3개의 클래스는 lerarning_rate 매개변수의 기본값으로 모두 0.001을 사용한다.

model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28, 28)))
model.add(keras.layers.Dense(100, activation='relu'))
model.add(keras.layers.Dense(10, activation='softmax'))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')

model.fit(train_scaled, train_target, epochs=5)
Epoch 1/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.5249 - accuracy: 0.8155
Epoch 2/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3929 - accuracy: 0.8589
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3529 - accuracy: 0.8726
Epoch 4/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3259 - accuracy: 0.8793
Epoch 5/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3076 - accuracy: 0.8876
<keras.callbacks.History at 0x7f11d1915f90>
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 2ms/step - loss: 0.3345 - accuracy: 0.8805
[0.3345348834991455, 0.8805000185966492]

옵티마이저를 adam으로 설정하고 훈련한다.

결과를 보면 기본 RMSprop을 사용했을 때와 거의 같은 성능을 보여준다. 검증 세트 역시 기본 RMSprop보다 조금 나은 성능을 낸다.