[혼자공부하는 머신러닝 + 딥러닝] 17_심층 신경망
16. 심층 신경망
- 이전 시간에 한 인공 신경망을 좀더 심층적으로 만들어 보자.
- 케라스 API를 사용해서 패션 MNIST 데이터셋을 불러오자.
from tensorflow import keras
import numpy as np
from sklearn.model_selection import train_test_split
(train_input, train_target), (test_input, test_target) =\
keras.datasets.fashion_mnist.load_data()
- 픽셀값을 0에서 255범위에서 0~1 사이로 변환하고 28x28 크기의 2차원 배열을 784크기의 1차원배열로 펼친다.
- 마지막으로 검증 세트를 분리해준다.
train_scaled = train_input / 255
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)
- 이제 인공 신경망 모델에 층을 2개 추가해 보자. 지난 시간과 다른점은 만든 신경망 모델에 입력층과 출력층 사이에 밀집층이 추가된 것이다. 이렇게 입력층과 출력층 사이에 있는 모든 층을 은닉층이라 한다.
- 은닉층에도 활성화 함수를 사용하는데, 은닉층에 사용하는 함수는 출력층 보다 비교적 자유롭다. 대표적으로는 시그모이드 함수와 볼 렐루 함수 등을 사용한다.
- 분류 문제는 클래스에 대한 확률을 출력하기 위해 활성화 함수를 사용한다. 회귀의 출력은 임의의 어떤 숫자이므로 활성화 함수를 적용할 필요가 없다. 즉, 출력층의 선형 방정식의 계산을 그대로 출력한다. Dense층의 activation을 지정하지 않으면 된다.
1. 인공 신경망 은닉층에 활성화 함수를 사용하는 이유
- 은닉층에서 선형적인 산술 계산만 수행한다면 수행 역할이 없는 셈이다. 선형 계산을 적당하게 비선형적으로 비틀어 주어야 한다. 그래야 다음 층의 계산이 단순히 합쳐지지 않고 독립적인 역할을 할 수 있다.
- 많이 사용하는 함수는 시그모이드 함수이다. 이 함수는 출력 z값을 0과 1사이로 압축한다.
- 시그모이드 활성화 함수를 사용한 은닉층과 소프트맥수 함수를 사용한 출력층을 케라스의 Dense 클래스로 만들어보자.
dense1 = keras.layers.Dense(100, activation='sigmoid', input_shape=(784,))
dense2 = keras.layers.Dense(10, activation='softmax')
- dense1이 은닉층이고 100개의 뉴런을 가진 밀집층이다. 활성화 함수를 ‘sigmoid’로 하고 input_shape 매개변수에서 입력의 크기를 784로 지정했다.
- 뉴런의 개수를 정할 때 단 한가지 제약사항이 있다면 적어도 출력층의 뉴런보다는 많게 만들어야 한다.
- dense2는 출력층이다. 10개의 클래스를 분류하므로 10개의 뉴런을 두었고 활성화 함수는 소프트맥스로 지정했다.
2. 심층 신경망 만들기
- 앞에서 만든 dense1, dense2 객체를 Sequential 클래스에 추가해 심층 신경망을 만들어 보겠다.
model = keras.Sequential([dense1, dense2])
2021-10-09 14:48:48.308850: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
- Sequential 클래스의 객체를 만들 때 여러 개의 층을 추가하려면 이와 같이 dense1, dense2를 리스트로 만들어 전달한다. 출력층은 항상 맨 마지막에 두어야 한다.
- 인공 신경망의 강력한 성능은 바로 이렇게 층을 추가해 입력 데이터에 대해 연속적인 학습을 진행하는 능력에서 나온다.
- 케라스는 모델의 summary() 메서드를 호출하면 층에 대한 유용한 정보를 얻을 수 있다.
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
_________________________________________________________________
- 맨 첫줄에는 모델의 이름이 나온다. 그 다음 모델이 들어 있는 층이 순서대로 나열된다. 이 순서는 맨 처음 추가한 은닉층에서 출력층의 순서대로 나열된다.
- 각 층마다 층의 이름, 클래스, 출력크기, 파라미터 개수가 출력된다. 층을 만들 때 name 매개변수로 이름을 지정할 수 있다.
- 출력 크기를 보면 (None, 100)과 (None, 10)이다. 첫 번째 차원은 샘플의 개수를 나타낸다. 샘플의 개수가 아직 정의되어 있지 않아 None으로 표현된 것이다. 이는 샘플 개수를 고정하지 않고 어떤 배치 크기에도 유연하게 대응할 수 있도록 None으로 설정한 것이다.
- 두 번째 100과 출력층의 10은 은닉층의 뉴런 개수를 말한다. 셈플마다 784개의 픽셀값이 은닉층을 지나가면서 100개의 특성으로 압축된 것이다. 마지막으로 모델 파라미터 개수가 출력된다.
- 마지막에는 총 모델 파라미터 개수와 훈련되는 파라미터 개수가 동일하게 나타나고 있다. 이는 은닉층과 출력층의 파라미터 개수를 합친 값을 말한다. 그 아래에는 훈련되지 않는 파라미터를 말한다. 간혹 경사 하강법으로 훈련되지 않는 파라미터를 가진 층이 존재하기도 한다. 이는 맨 아래 표시된다.
3. 층을 추가하는 다른 방법
- 모델을 훈련하기 전에 Sequential 클래스에 층을 추가하는 다른 방법을 알아보자. 앞에서는 Dense 클래스의 dense1, dense2를 만들어 Sequential 클래스에 전달했다. 이 두 객체를 따로 저장해서 쓰지않기 때문에 아래 처럼 생성자 안에서 바로 Dense클래스의 객체를 만드는 경우가 많다.
- 아래와 같이 작업하면 한눈에 쉽게 알아볼 수 있다는 장점이 있다. 그리고 이번에는 모델과 각 층에 이름을 지정했다. 모델의 이름과 달리 층의 이름은 영문으로 작성되어야 한다.
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
_________________________________________________________________
- 2개의 Dense 층이 이전과 동일하게 추가되었고 파라미터 개수도 동일하다. 모델이름도 제대로 적용이 되었다.
- 여러 모델과 많은 층을 사용할 때 name 매개변수를 사용하면 구분하기 쉽다.
- 이 방법이 편리하긴 하지만 많은 층을 추가할 때는 길이가 매우 길어지는 단덥이 있다. 또 조건에 따라 층을 추가할 수도 없다. 그래서 Sequential 클래스에서 층을 추가할 때 가장 널리 사용하는 방법은 add() 메서드 이다.
model = keras.Sequential()
model.add(keras.layers.Dense(100, activation='sigmoid', input_shape=(784,)))
model.add(keras.layers.Dense(10, activation='softmax'))
- 여기서도 Dense 클래스의 객체를 따로 변수에 담지 않고 바로 add() 메서드로 전달한다. 이 방법은 한눈에 추가되는 층을 볼 수 있고 프로그램 실행 시 동적으로 층을 선택하여 추가할 수 있다.
- summary()의 결과도 동일할 것이다.
- 이제 모델을 훈련해 보도록 하자.
print(train_scaled.shape, train_target.shape)
(48000, 784) (48000,)
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
2021-10-09 14:48:48.991557: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
Epoch 1/5
1500/1500 [==============================] - 3s 1ms/step - loss: 0.5632 - accuracy: 0.8078
Epoch 2/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.4083 - accuracy: 0.8532
Epoch 3/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3739 - accuracy: 0.8652
Epoch 4/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3514 - accuracy: 0.8731
Epoch 5/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3344 - accuracy: 0.8800
<keras.callbacks.History at 0x1854e7c40>
- 훈련 세트에 대한 성능을 보면 추가된 층이 성능을 향상시켰다는 것을 알 수 있다.
- 인공 신경망에 몇 개의 층을 추가하더라도 compile() 메서드와 fit() 메서드의 사용법은 동일하다.
- 다음은 이미지 분류 문제에서 높은 성능을 낼 수 있는 활성화 함수에 대해 알아보자.
4. 렐루 함수
- 초기 인공 신경망 은닉층에 가장 많이 사용된 활성화 함수는 시그모이드 함수였다. 하지만 시그모드 함수는 올바른 출력을 만드는데 신속하게 대응하지 못한다는 장점이 있고, 이는 심층 신경망일수록 그 효과가 누적 되어 학습을 어렵게 만든다. 그래서 다른 종류의 활성화 함수가 제안되었는데 그것이 렐루함수이다.
- 렐루 함수는 아주 간단하다. 입력이 양수일 경우 마치 활성화 함수가 없는 것처럼 그냥 입력을 통과시키고 음수일 경우에는 0으로 만든다.
- 렐루 함수는 max(0,z)와 같이 쓸 수 있다. 이 함수는 z가 0보다 크면 z를 출력하고 z가 0보다 작으면 0을 출력한다. 렐루 함수는 특히 이미지 처리에서 좋은 성능을 낸다.
- 이번 패션 MNIST 데이터를 살펴보면 28x28 크기이기 때문에 인공 신경망에 주입하기 위해 넘파이 배열의 reshape() 메서드를 사용해 1차원으로 펼쳤다. 하지만 케라스에서는 이를 위한 Flatten 층을 제공한다.
- Flatten 클래스는 배치 차원을 제외하고 나머지 입력 차원을 모두 일렬로 펼치는 역할만 한다. 입력에 곱해지는 가중치나 절편이 없다. 즉, 인공 신경망에 있어서 성능에 기여하는 부분이 존재하지 않는다.
- 하지만 Flatten 클래스는 층처럼 입력층과 은닉층 사이에 추가하기 때문에 이를 층이라 부른다. 아래처럼 입력층 바로 뒤에 추가해준다.
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 층이 추가 됨으로써 입력값의 차원을 짐작해 볼 수 있게 되었다. 앞의 출력에서 784개의 입력이 첫 번째 은닉층에 전달된다는 것을 알 수 있었다. 이는 이전에 만들었던 모델에서는 쉽게 눈치채기 어려웠다. 입력 데이터에 대한 전처리 과정을 가능한 모델에 포함시키는 것이 케라스API의 철학 중 하나이다.
- 훈련데이터를 다시 준비해서 모델을 훈련해 보자. 이번 데이터셋은 이전과 동일하지만 reshape()을 적용하지 않았다.
(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)
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
Epoch 1/5
1500/1500 [==============================] - 3s 1ms/step - loss: 0.5366 - accuracy: 0.8113
Epoch 2/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3946 - accuracy: 0.8589
Epoch 3/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3563 - accuracy: 0.8708
Epoch 4/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3342 - accuracy: 0.8803
Epoch 5/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3180 - accuracy: 0.8869
<keras.callbacks.History at 0x188962e80>
- 시그모이드 함수를 사용했을 때보다 성능이 향상되었다. 그렇다면 검증세트는 어떨까 확인해보자.
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 974us/step - loss: 0.3692 - accuracy: 0.8753
[0.36915475130081177, 0.875333309173584]
- 저번 시간에 은닉층을 추가하지 않은 경우보다 몇 퍼센트 성능이 향상되었다. 지금까지는 5번의 에포크 동안 훈련을 진행했다.
- 마지막으로 인공 신경망의 하이퍼파라미터에 대해 잠시 알아보고 마무리 하도록 하자.
5. 옵티마이저
- 하이퍼파라미터는 모델이 학습하지 않아 사람이 지정해 주어야 하는 파라미터를 말한다. 신경망에는 특히 하이퍼 파라미터가 많이 존재한다.
- 은닉층의 개수, 뉴런의 개수, 활성화 함수 선택, 케라스의 batch_size 매개변수, fit()메서드의 에포크 횟수도 하이퍼 파라미터 이다. 마지막으로 compile() 메서드에서는 케라스의 기본 경사 하강법 알고리즘인 RMSprop을 사용했다. 이 또한 하이퍼 파라미터 중 하나이다. 이들을 옵티마이저라고 부른다.
- 처음부터 모델을 구성하고 각종 하이퍼파라미터의 최적값을 찾는 것을 어려운 작업이다.
- 가장 기본적인 옵티마이저인 확률적 경사하강법이다. 이름이 확률적 경사 하강법이지만 1개의 샘플을 뽑아서 훈련하지 않고 기본적으로 미니배치를 사용한다. SGD 옵티마이저를 사용하려면 compile() 메서드의 optimizer 매개변수를 ‘sgd’로 지정한다.
model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy', metrics='accuracy')
- 만약 SGD 클래스의 학습률 기본값이 0.01일 때 이를 바꾸고 싶다면 다음과 같이 원하는 학습률을 learning_rate 매개변수에 지정하여 사용합니다.
- 다양한 옵티마이저들이 존재한다. 기본 경사 하강법 옵티마이저는 모두 SGD 클래스에서 제공합니다. SGD 클래스의 momentum 매개변수의 기본값은 0이다. 이를 0보다 큰 값으로 지정하면 마치 이전의 그레이디언트를 가속도처럼 사용하는 모멘텀 최적화를 사용한다. 보통 momentum 매개변수는 0.9이상을 지정한다.
- 다음처럼 SGD 클래스의 nesterov 매개변수를 기본값 False에서 True로 바꾸면 네스테로프 모멘텀 최적화를 사용한다.
- 모델이 최적점에 가까이 갈수록 학습률을 낮출 수 있습니다. 이렇게 하면 안정적으로 최적점에 수렴할 가능성이 높다. 이런 학습률을 적응적 학습률이라고 한다. 이런 방식들은 학습률 매개변수를 튜닝하는 수고를 덜 수 있는 것이 장점이다.
- 적응적 학습률을 사용하는 대표적인 옵티마이저는 Adagrad와 RMSprop이다. 각각 compile() 메서드의 optimizer 매개변수에 ‘adagrad’와 ‘rmsprop’으로 지정할 수 있다. ‘rmsprop’는 기본 매개변수이다.
- 모멘텀 최적화와 RMSprop의 장점을 접목한 것이 Adam이다. RMSprop와 함께 맨 처음 시도해 볼 수 있는 좋은 알고리즘이다. Adam클래스도 keras.optimizers 패키지 아래 있다. 적응적 학습률을 사용하는 이 3개의 클래스는 learning_rate 매개변수의 기본값으로 모두 0.001을 사용한다.
- Adam을 이용해 패션 MNIST 모델을 훈련해보자.
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 [==============================] - 3s 1ms/step - loss: 0.5254 - accuracy: 0.8164
Epoch 2/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3949 - accuracy: 0.8591
Epoch 3/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3561 - accuracy: 0.8720
Epoch 4/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3259 - accuracy: 0.8807: 0s - loss: 0.3266
Epoch 5/5
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3085 - accuracy: 0.8864
<keras.callbacks.History at 0x18b7a8430>
- 출력 결과를 살펴보면 기본 RMSProp을 사용한 것과 거의 동일하게 나타난다. 마지막으로 검증 세트로 확인해보자.
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 0s 994us/step - loss: 0.3581 - accuracy: 0.8690
[0.35809123516082764, 0.8690000176429749]
- 매번 같지는 않지만 기본 RMSprop보다 조금 나은 성능을 낸다.
Leave a comment