본문 바로가기

KHUDA 심화트랙 NLP

[KHUDA_NLP] 혼공머신 Chapter 07. 딥러닝을 시작합니다

Chapter 07. 딥러닝을 시작합니다

07-1 인공 신경망

✅ 패션 MNIST

더보기

MNIST가 뭐죠?

머신러닝과 딥러닝을 처음 배울 때 많이 사용하는 데이터셋이 있습니다. 머신러닝에서는 붓꽃 데이터셋이 유명하죠. 딥러닝에서는 MNIST 데이터셋이 유명합니다. 이 데이터는 손으로 쓴 0~9까지의 숫자로 이루어져 있습니다. MNIST와 크기, 개수가 동일하지만 숫자 대신 패션 아이템으로 이루어진 데이터가 바로 패션 MNIST입니다.

from tensorflow import keras
(train_input, train_target), (test_input, test_target) =\
keras.datasets.fashion_mnist.load_data()

keras.datasets.fashion_mnist 모듈 아래 load_data() 함수는 친절하게 훈련 데이터와 테스트 데이터를 나누어 반환합니다. 이 데이터는 각각 입력과 타깃의 쌍으로 구성되어 있습니다.

print(train_input.shape, train_target.shape)
-> (60000, 28, 28) (60000,)

훈련 데이터는 60,000개로, 각 이미지는 28 * 28 크기입니다. 타깃도 60,000개의 원소가 있는 1차원 배열입니다.

import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 10, figsize=(10,10))
for i in range(10):
  axs[i].imshow(train_input[i], cmap='gray_r')
  axs[i].axis('off')
plt.show()

 

이 샘플들의 타깃값을 확인해 보죠.

print([train_target[i] for i in range(10)])
-> [9, 0, 0, 3, 0, 2, 7, 2, 5, 5]

패션 MNIST의 타깃은 0~9까지의 숫자 레이블로 구성됩니다. 패션 MNIST에 포함된 10개 레이블의 의미는 다음과 같습니다.

✅ 로지스틱 회귀로 패션 아이템 분류하기

더보기

넘파이 배열의 바이트 크기를 알 수 있나요?

넘파이 배열의 nbytes 속성에 실제 해당 배열이 차지하는 바이트 용량이 저장되어 있습니다.

확률적 경사 하강법은 여러 특성 중 기울기가 가장 가파른 방향을 따라 이동합니다. 만약 특성마다 값의 범위가 많이 다르면 올바르게 손실 함수의 경사를 내려올 수 없겠죠. 패션 MNIST의 경우 각 픽셀은 0~255 사이의 정숫값을 가집니다. 이런 이미지의 경우 보통 255로 나누어 0~1 사이의 값으로 정규화합니다. 이는 표준화는 아니지만 양수 값으로 이루어진 이미지를 전처리할 때 널리 사용하는 방법입니다.

train_scaled = train_input / 255.0
train_scaled = train_scaled.reshape(-1, 28*28)
print(train_scaled.shape)
-> (60000, 784)

reshape() 메서드의 두 번째 매개변수를 28 * 28 이미지 크기에 맞게 지정하면 첫 번째 차원(샘플 개수)은 변하지 않고 원본 데이터의 두 번째, 세 번째 차원이 1차원으로 합쳐집니다.

from sklearn.model_selection import cross_validate
from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
scores = cross_validate(sc, train_scaled, train_target, n_jobs=-1)
print(np.mean(scores['test_score']))
-> 0.8312999999999999

SGDClassifier 클래스와 cross_validate 함수를 사용해 이 데이터에서 교차 검증으로 성능을 확인해보았습니다.

위 로지스틱 회귀 공식을 패션 MNIST 데이터에 맞게 변형하면 다음과 같습니다.

이런 식으로 나머지 클래스에 대한 선형 방정식을 모두 생각해 볼 수 있습니다. SGDClassifier 모델은 패션 MNIST 데이터의 클래스를 가능한 잘 구분할 수 있도록 이 10개의 방정식에 대한 모델 파라미터(가중치와 절편)를 찾습니다.

여기에서 중요한 점은 티셔츠를 계산하기 위해 픽셀 784개와 곱하는 가중치 784개(w1~w784)와 절편(b)이 바지를 계산하기 위해 픽셀 784개와 곱하는 가중치 784개(w1'~w784'), 절편(b')과 다르다는 것입니다.

✅ 인공 신경망

가장 기본적인 인공 신경망은 확률적 경사 하강법을 사용하는 로지스틱 회귀와 같습니다. 먼저 패션 아이템 분류 문제를 인공 신경망으로 표현해 보겠습니다.

앞서 로지스틱 회귀를 표현한 그림과 매우 비슷합니다. z1~z10을 계산하고 이를 바탕으로 클래스를 예측하기 때문에 신경망의 최종 값을 만든다는 의미에서 출력층이라고 부릅니다. 인공 신경망에서는 z 값을 계산하는 단위를 뉴런이라고 부릅니다. 하지만 뉴런에서 일어나는 일은 선형 계산이 전부입니다! 이제는 뉴런이란 표현 대신에 유닛이라고 부르는 사람이 더 많아지고 있습니다. 인공 신경망은 x1~x784까지를 입력층이라고 부릅니다. 즉 입력층은 픽셀값 자체이고 특별한 계산을 수행하지 않습니다.

 

인공 신경망은 1943년 워런 매컬러와 월터 피츠가 제안한 뉴런 모델로 거슬러 올라갑니다. 이를 매컬러-피츠 뉴런이라고 부릅니다. 인공 뉴런은 다음과 같은 생물학적 뉴런에서 영감을 얻어 만들어졌습니다.

인공 뉴런은 생물학적 뉴런의 모양을 본뜬 수학 모델에 불과합니다. 인공 신경망은 기존의 머신러닝 알고리즘이 잘 해결하지 못했던 문제에서 높은 성능을 발휘하는 새로운 종류의 머신러닝 알고리즘일 뿐입니다.

더보기

그럼 딥러닝은 무엇인가요?

딥러닝은 인공 신경망과 거의 동의어로 사용되는 경우가 많습니다. 혹은 심층 신경망을 딥러닝이라고 부릅니다.

텐서플로와 케라스

텐서플로는 구글이 2015년 11월 오픈소스로 공개한 딥러닝 라이브러리입니다. 이때부터 딥러닝에 대한 개발자의 관심이 늘어났습니다.

텐서플로에는 저수준 API와 고수준 API가 있습니다. 바로 케라스가 텐서플로의 고수준 API입니다.

 

딥러닝 라이브러리가 다른 머신러닝 라이브러리와 다른 점 중 하나는 그래픽 처리 장치인 GPU를 사용하여 인공 신경망을 훈련한다는 것입니다. GPU는 벡터와 행렬 연산에 매우 최적화되어 있기 때문에 곱셈과 덧셈이 많이 수행되는 인공 신경망에 큰 도움이 됩니다.

더보기

그럼 이 장의 예제를 실행할 때 GPU를 사용해야 하나요?

네. GPU를 사용하면 텐서플로로 만든 딥러닝 모델을 훨씬 빠르게 훈련시킬 수 있습니다.

케라스 라이브러리는 직접 GPU 연산을 수행하지 않습니다. 대신 GPU 연산을 수행하는 다른 라이브러리를 백엔드로 사용합니다. 텐서플로가 케라스의 백엔드 중 하나입니다. 이런 케라스를 멀티-백엔드 케라스라고 부릅니다. 케라스 API만 익히면 다양한 딥러닝 라이브러리를 입맛대로 골라서 쓸 수 있는 셈이죠.

 

프랑소와가 구글에 합류한 뒤 텐서플로 라이브러리에 케라스 API가 내장되었습니다. 텐서플로 2.0부터는 케라스 API를 남기고 나머지 고수준 API를 모두 정리했고, 멀티-백엔드 케라스는 2.3.1 버전 이후로 더 이상 개발되지 않습니다. 이제는 케라스와 텐서플로가 거의 동의어가 된 셈입니다.

✅ 인공 신경망으로 모델 만들기

로지스틱 회귀에서는 교차 검증을 사용해 모델을 평가했지만, 인공 신경망에서는 교차 검증을 잘 사용하지 않고 검증 세트를 별도로 덜어내어 사용합니다.

 

이렇게 하는 이유는

1. 딥러닝 분야의 데이터셋은 충분히 크기 때문에 검증 점수가 안정적이고,

2. 교차 검증을 수행하기에는 훈련 시간이 너무 오래 걸리기 때문입니다.

 

어떤 딥러닝 모델은 훈련하는 데 몇 시간, 심지어 며칠이 걸릴 수도 있습니다.

from sklearn.model_selection import train_test_split
train_scaled, val_scaled, train_target, val_target = train_test_split(
    train_scaled, train_target, test_size=0.2, random_state=42
)

print(train_scaled.shape, train_target.shape)
->(48000, 784) (48000,)

print(val_scaled.shape, val_target.shape)
-> (12000, 784) (12000,)

60,000개 중에 12,000개가 검증 세트로 분리되었습니다. 먼저 훈련 세트(train_scaled, train_target)로 모델을 만듭니다. 그다음 검증 세트(val_scaled, val_target)로 훈련한 모델을 평가해 보겠습니다.

 

먼저 인공 신경망 그림의 오른쪽에 놓인 층을 만들어 보겠습니다.

케라스의 레이어(keras.layers) 패키지 안에는 다양한 층이 준비되어 있습니다. 가장 기본이 되는 층은 밀집층입니다. 이런 층을 양쪽의 뉴런이 모두 연결하고 있기 때문에 완전 연결층이라고도 부릅니다. 그럼 케라스의 Dense 클래스를 사용해 밀집층을 만들어 보죠. 필요한 매개변수는 뉴런 개수, 뉴런의 출력에 적용할 함수, 입력의 크기입니다.

dense = keras.layers.Dense(10, activation='softmax', input_shape=(784,))

첫 번째 매개변수로 뉴런 개수를 10개로 지정합니다. 10개의 패션 아이템을 분류하기 때문이죠. 10개의 뉴런에서 출력되는 값을 확률로 바꾸기 위해서는 소프트맥스 함수를 사용합니다. 케라스 층에서는 activation 매개변수에 이 함수를 지정합니다. 마지막으로 세 번째 매개변수는 입력값의 크기입니다. 10개의 뉴런이 각각 몇 개의 입력을 받는지 튜플로 지정합니다.

model = keras.Sequential(dense)

Sequential 클래스의 객체를 만들 때 앞에서 만든 밀집층의 객체 dense를 전달했습니다. 여기서 만든 model 객체가 바로 신경망 모델입니다.

소프트맥스와 같이 뉴런의 선형 방정식 계산 결과에 적용되는 함수를 활성화 함수라고 부릅니다.

더보기

소프트맥스 함수는 별도의 층인가요?

시그모이드 함수나 소프트맥스와 같은 활성화 함수는 뉴런의 출력에 바로 적용되기 때문에 보통 층의 일부로 나타냅니다. 하지만 종종 "소프트맥스 층을 적용했어"와 같이 따로 부르는 경우도 많습니다. 가중치와 절편으로 선형 계산을 수행하는 층을 좁은 개념의 신경망 층으로 생각한다면 소프트맥스 층은 넓은 의미의 층이라 볼 수 있습니다.

✅ 인공 신경망으로 패션 아이템 분류하기

케라스 모델은 훈련하기 전에 설정 단게가 있습니다. 이런 설정을 model 객체의 compile() 메서드에서 수행합니다. 꼭 지정해야 할 것은 손실 함수의 종류입니다.

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

 

이진 분류에서는 이진 크로스 엔트로피 손실 함수를 사용하고 다중 분류에서는 크로스 엔트로피 손실 함수를 사용합니다.

- 이진 분류: loss = 'binary_crossentropy'

- 다중 분류: loss = 'categorical_crossentropy'

 

혼공머신 4장에서 이진 크로스 엔트로피 손실을 위해 -log(예측 확률)에 타깃값(정답)을 곱했습니다.

이진 분류에서는 출력층의 뉴런이 하나입니다. 이 뉴런이 출력하는 확률값 a(시그모이드 함수의 출력값)를 사용해 양성 클래스와 음성 클래스에 대한 크로스 엔트로피를 계산합니다. 이진 분류의 출력 뉴런은 오직 양성 클래스에 대한 확률(a)만 출력하기 때문에 음성 클래스에 대한 확률은 간단히 1-a로 구할 수 있습니다. 역시 이진 분류의 타깃값은 양성 샘플일 경우에는 1, 음성 샘플일 경우에는 0으로 되어 있죠. 0을 곱하면 어떤 계산이든지 모두 0이 되기 때문에 특별히 음성 샘플일 경우 1로 바꾸어(1-타깃값) 계산합니다.

 

그럼 패션 MNIST 데이터셋과 같이 다중 분류일 경우 어떻게 계산할 수 있을까요?

출력층은 10개의 뉴런이 있고 10개의 클래스에 대한 확률을 출력합니다. 첫 번째 뉴런은 티셔츠일 확률이고 두 번째 뉴런은 바지일 확률을 출력하죠. 이진 분류와 달리 각 클래스에 대한 확률이 모두 출력되기 때문에 타깃에 해당하는 확률만 남겨 놓기 위해서 나머지 확률에는 모두 0을 곱합니다.

 

예를 들어 샘플이 티셔츠일 경우 첫 번째 뉴런의 활성화 함수 출력인 a1에 크로스 엔트로피 손실 함수를 적용하고 나머지 활성화 함수 출력 a2~a10까지는 모두 0으로 만듭니다. 이렇게 하기 위해 티셔츠 샘플의 타깃값은 첫 번째 원소만 1이고 나머지는 모두 0인 배열로 만들 수 있습니다.

이 배열과 출력층의 활성화 값의 배열과 곱하면 됩니다.

결국 다른 원소는 모두 0이 되고 a1만 남습니다. 결국 신경망은 티셔츠 샘플에서 손실을 낮추려면 첫 번째 뉴런의 활성화 출력 a1의 값을 가능한 1에 가깝게 만들어야 합니다.

 

이와 같이 타깃값을 해당 클래스만 1이고 나머지는 모두 0인 배열로 만드는 것을 원-핫 인코딩이라고 부릅니다. 따라서 다중 분류에서 크로스 엔트로피 손실 함수를 사용하려면 0, 1, 2와 같이 정수로 된 타깃값을 원-핫 인코딩으로 변환해야 합니다.

하지만 텐서플로에서는 정수로 된 타깃값을 원-핫 인코딩으로 바꾸지 않고 그냥 사용할 수 있습니다. 정수로된 타깃값을 사용해 크로스 엔트로피 손실을 계산하는 것이 바로 'sparse_categorical_crossentropy'입니다. 타깃값을 원-핫 인코딩으로 준비했다면 compile() 메서드에 손실 함수를 loss='categorical_crossentropy'로 지정합니다.

 

케라스는 모델이 훈련할 때 기본으로 에포크마다 손실 값을 출력해 줍니다. 손실이 줄어드는 것을 보고 훈련이 잘되어있다는 것을 알 수 있지만 정확도를 출력하면 더 좋겠죠. 이를 위해 metrics 매개변수에 정확도 지표를 의미하는 'accuracy'를 지정했습니다.

 

자 이제 모델을 훈련해 보죠. 훈련하는 fit() 메서드는 사이킷런과 매우 비슷합니다. 처음 두 매개변수에 입력(train_scaled)과 타깃(train_target)을 지정합니다. 그다음 반복할 에포크 횟수를 epochs 매개변수로 지정합니다. 사이킷런의 로지스틱 모델과 동일하게 5번 반복해 보겠습니다.

model.fit(train_scaled, train_target, epochs=5)
##################################################
Epoch 1/5
1500/1500 [==============================] - 5s 2ms/step - loss: 0.6044 - accuracy: 0.7933
Epoch 2/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.4743 - accuracy: 0.8395
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4497 - accuracy: 0.8472
Epoch 4/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4379 - accuracy: 0.8529
Epoch 5/5
1500/1500 [==============================] - 4s 3ms/step - loss: 0.4300 - accuracy: 0.8555
<keras.src.callbacks.History at 0x7de619bdeec0>

 

그럼 앞서 따로 떼어놓은 검증 세트(val_scaled, val_target)에서 모델의 성능을 확인해 보겠습니다.

model.evaluate(val_scaled, val_target)
-> 375/375 [==============================] - 2s 4ms/step - loss: 0.4412 - accuracy: 0.8493
[0.4412451684474945, 0.8493333458900452]

evaluate() 메서드도 fit() 메서드와 비슷한 출력을 보여 줍니다. 검증 세트의 점수는 훈련 세트 점수보다 조금 낮은 것이 일반적입니다.

07-2 심층 신경망

✅ 2개의 층

이제 인공 신경망 모델에 층을 2개 추가해 보겠습니다. 여기서 만들 모델의 대략적인 구조는 다음 그림과 같습니다.

1절 신경망 모델과 다른 점은 입력층과 출력층 사이에 밀집층이 추가된 것입니다. 이렇게 입력층과 출력층 사이에 있는 모든 층을 은닉층이라고 부릅니다. 활성화 함수는 신경망 층의 선형 방정식의 계산 값에 적용하는 함수입니다. 출력층에 적용했던 소프트맥스 함수도 활성화 함수입니다.

 

출력층에 적용하는 활성화 함수는 종류가 제한되어 있습니다. 이진 분류일 경우 시그모이드 함수를 사용하고 다중 분류일 경우 소프트맥스 함수를 사용하죠. 이에 비해 은닉층의 활성화 함수는 비교적 자유롭습니다.

더보기

회귀를 위한 신경망의 출력층에서는 어떤 활성화 함수를 사용하나요?

분류 문제는 클래스에 대한 확률을 출력하기 위해 활성화 함수를 사용합니다. 회귀의 출력은 임의의 어떤 숫자이므로 활성화 함수를 적용할 필요가 없습니다. 즉 출력층의 선형 방정식의 계산을 그대로 출력합니다.

은닉층에서 선형적인 산술 계산만 수행한다면 수행 역할이 없는 셈입니다. 선형 계산을 적당하게 비선형적으로 비틀어 주어야 합니다. 마치 다음과 같습니다.

그럼 시그모이드 활성화 함수를 사용한 은닉층과 소프트맥스 함수를 사용한 출력층을 케라스의 Dense 클래스로 만들어 보겠습니다. 케라스에서 신경망의 첫 번째 층은 input_shape 매개변수로 입력의 크기를 꼭 지정해 주어야 합니다.

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

dense1이 은닉층이고 100개의 뉴런을 가진 밀집층입니다. 활성화 함수를 'sigmoid'로 지정했고 input_shape 매개변수에서 입력의 크기를 (784,)로 지정했습니다. 은닉층의 뉴런 개수를 정하는 데는 특별한 기준이 없습니다. 몇 개의 뉴런을 두어야 할지 판단하기 위해서는 상당한 경험이 필요합니다.

✅ 심층 신경망 만들기

이제 앞에서 만든 dense1과 dense2 객체를 Sequential 클래스에 추가하여 심층 신경망을 만들어 보겠습니다.

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

이 리스트는 가장 처음 등장하는 은닉층에서 마지막 출력층의 순서로 나열해야 합니다.

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

케라스는 모델의 summary() 메서드를 호출하면 층에 대한 유용한 정보를 얻을 수 있습니다.

층마다 층 이름, 클래스, 출력 크기, 모델 파라미터 개수가 출력됩니다. 층을 만들 때 층 이름을 지정하지 않으면 케라스가 자동으로 'dense'라고 이름을 붙입니다.

 

출력 크기를 보면 (None, 100)입니다. 첫 번째 차원은 샘플의 개수를 나타냅니다. 샘플 개수가 아직 정의되어 있지 않기 때문에 None입니다. 왜 그럴까요? 케라스 모델의 fit() 메서드에 훈련 데이터를 주입하면 이 데이터를 한 번에 모두 사용하지 않고 잘게 나누어 여러 번에 걸쳐 경사 하강법 단계를 수행합니다. 바로 미니배치 경사 하강법을 사용하는 거죠. 이렇게 신경망 층에 입력되거나 출력되는 배열의 첫 번째 차원을 배치 차원이라고 부릅니다.

 

두 번째 100은 쉽습니다. 은닉층의 뉴런 개수를 100개로 두었으니 100개의 출력이 나오겠죠.

 

마지막으로 모델 파라미터 개수가 출력됩니다. 이 층은 Dense 층이므로 입력 픽셀 784개와 100개의 모든 조합에 대한 가중치가 있습니다. 그리고 뉴런마다 1개의 절편이 있습니다.

두 번째 층의 출력 크기는 (None, 10)입니다. 배치 차원은 동일하게 None이고 출력 뉴런 개수가 10개이기 때문입니다.

summary() 메서드의 마지막에는 총 모델 파라미터 개수와 훈련되는 파라미터 개수가 동일하게 79,510개로 나옵니다. 은닉층과 출력층의 파라미터 개수를 합친 값입니다. 그 아래 훈련되지 않는 파라미터(Non-trainable params)는 0으로 나옵니다. 간혹 경사 하강법으로 훈련되지 않는 파라미터를 가진 층이 있습니다.

✅ 층을 추가하는 다른 방법

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 모델')

이렇게 작업하면 추가되는 층을 한눈에 쉽게 알아보는 장점이 있습니다.

 

이 방법이 편리하지만 아주 많은 층을 추가하려면 Sequential 클래스 생성자가 매우 길어집니다. 또 조건에 따라 층을 추가할 수도 없습니다. Sequential 클래스에서 층을 추가할 때 가장 널리 사용하는 방법은 모델의 add() 메서드입니다.

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

 

이제 모델을 훈련해 보겠습니다.

✅ 렐루 함수

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

특히 층이 많은 심층 신경망일수록 그 효과가 누적되어 학습을 더 어렵게 만듭니다. 이를 개선하기 위해 렐루 함수가 제안되었습니다. 렐루 함수는 입력이 양수일 경우 마치 활성화 함수가 없는 것처럼 그냥 입력을 통과시키고 음수일 경우에는 0으로 만듭니다.

렐루 함수는 max(0, z)와 같이 쓸 수 있습니다. 이 함수는 z가 0보다 크면 z를 출력하고 z가 0보다 작으면 0을 출력합니다. 렐루 함수는 특히 이미지 처리에서 좋은 성능을 낸다고 알려져 있습니다.

 

케라스는 28 * 28 크기의 데이터를 위한 Flatten 층을 제공합니다. 사실 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'))

하지만 이 신경망을 깊이가 3인 신경망이라고 부르지는 않습니다. Flatten 클래스는 학습하는 층이 아니니까요.

 

모델의 summary() 메서드를 호출해보면 이런 점을 더 확실히 알 수 있습니다.

784개의 입력이 첫 번째 은닉층에 전달된다는 것을 알 수 있는데요. 이는 이전에 만들었던 모델에서는 쉽게 눈치채기 어려웠습니다. 입력 데이터에 대한 전처리 과정을 가능한 모델에 포함시키는 것이 케라스 API의 철학 중 하나입니다.

✅ 옵티마이저

추가할 은닉층의 개수는 모델이 학습하는 것이 아니라 우리가 지정해 주어야 할 하이퍼파라미터입니다. 은닉층의 뉴런 개수도 하이퍼파라미터입니다. 활성화 함수, 층의 종류도 마찬가지로 하이퍼파라미터입니다.

 

케라스는 다양한 종류의 경사 하강법 알고리즘을 제공합니다. 이들을 옵티마이저라고 부릅니다.

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

 

SGD 클래스의 nesterov 매개변수를 기본값 False에서 True로 바꾸면 네스테로프 모멘텀 최적화를 사용합니다.

 

모델이 최적점에 가까이 갈수록 학습률을 낮출 수 있습니다. 이렇게 하면 안정적으로 최적점에 수렴할 가능성이 높습니다. 이런 학습률을 적응적 학습률이라고 합니다. 적응적 학습률을 사용하는 대표적인 옵티마이저는 Adagrad와 RMSprop입니다.

07-3 신경망 모델 훈련

✅ 손실 곡선

케라스의 fit() 메서드는 History 클래스 객체를 반환합니다. History 객체에는 훈련 과정에서 계산한 지표, 즉 손실과 정확도 값이 저장되어 있습니다. 이 값을 사용하면 그래프를 그릴 수 있습니다.

 

✅ 검증 손실

4장에서 확률적 경사 하강법을 사용했을 때 과대/과소적합과 에포크 사이의 관계를 알아봤습니다. 인공 신경망은 모두 일종의 경사 하강법을 사용하기 때문에 동일한 개념이 여기에도 적용됩니다.

 

에포크에 따른 과대적합과 과소적합을 파악하려면 훈련 세트에 대한 점수뿐만 아니라 검증 세트에 대한 점수도 필요합니다.

더보기

손실을 사용하는 것과 정확도를 사용하는 것은 어떤 차이가 있나요?

인공 신경망 모델이 최적화하는 대상은 정확도가 아니라 손실 함수입니다. 모델이 잘 훈련되었는지 판단하려면 정확도보다는 손실 함수의 값을 확인하는 것이 더 낫습니다.

훈련 손실과 검증 손실을 한 그래프에 그리면 다음과 같습니다. 초기에 검증 손실이 감소하다가 다섯 번째 에포크 만에 다시 상승하기 시작합니다. 훈련 손실은 꾸준히 감소하기 때문에 전형적인 과대적합 모델이 만들어집니다. 검증 손실이 상승하는 시점을 가능한 뒤로 늦추면 검증 세트에 대한 손실이 줄어들 뿐만 아니라 검증 세트에 대한 정확도도 증가할 것입니다.

 

기본 RMSprop 옵티마이저는 많은 문제에서 잘 동작합니다. 만약 이 옵티마이저 대신 다른 옵티마이저를 테스트해 본다면 Adam이 좋은 선택입니다. Adam은 적응적 학습률을 사용하기 때문에 에포크가 진행되면서 학습률의 크기를 조정할 수 있습니다.

✅ 드롭아웃

드롭아웃은 딥러닝의 아버지로 불리는 제프리 힌턴이 소개했습니다. 이 방식은 다음 그림처럼 훈련 과정에서 층에 있는 일부 뉴런을 랜덤하게 꺼서(즉 뉴런의 출력을 0으로 만들어) 과대적합을 막습니다.

뉴런은 랜덤하게 드롭아웃되고 얼마나 많은 뉴런을 드롭할지는 우리가 정해야 할 또 다른 하이퍼파라미터입니다.

 

드롭아웃이 왜 과대적합을 막을까요?

1. ❓이전 층의 일부 뉴런이 랜덤하게 꺼지면 특정 뉴런에 과대하게 의존하는 것을 줄일 수 있고 모든 입력에 대해 주의를 기울여야 합니다.

2. 또 다른 해석은 앞의 드롭아웃이 적용된 2개의 신경망 그림을 보면 드롭아웃을 적용해 훈련하는 것은 마치 2개의 신경망을 앙상블하는 것처럼 상상할 수 있습니다.

 

물론 훈련이 끝난 뒤에 평가나 예측을 수행할 때는 드롭아웃을 적용하지 말아야 합니다. 하지만 똑똑하게도 텐서플로와 케라스는 모델을 평가와 예측에 사용할 때는 자동으로 드롭아웃을 적용하지 않습니다.

✅ 모델 저장과 복원

패션 MNIST 데이터셋에서 덜어낸 검증 세트의 샘플 개수는 12,000개이기 때문에 predict() 메서드는 (12000, 10) 크기의 배열을 반환합니다.

따라서 조금 번거롭지만 10개 확률 중에 가장 큰 값을 골라 타깃 레이블과 비교하여 정확도를 계산해 보겠습니다.

 

넘파이 argmax() 함수는 배열에서 가장 큰 값의 인덱스를 반환합니다.

axis=1이면 열을 따라 각 행의 최댓값의 인덱스를 선택하고, axis=0이면 행을 따라 각 열의 최댓값의 인덱스를 선택합니다.

✅ 콜백

콜백은 훈련 과정 중간에 어떤 작업을 수행할 수 있게 하는 객체로 keras.callbacks 패키지 아래에 있는 클래스들입니다. fit() 메서드의 callbacks 매개변수에 리스트로 전달하여 사용합니다.

 

검증 점수가 상승하기 시작하면 그 이후에는 과대적합이 더 커지기 때문에 훈련을 계속할 필요가 없습니다. 이때 훈련을 중지하면 컴퓨터 자원과 시간을 아낄 수 있습니다. 이렇게 과대적합이 시작되기 전에 훈련을 미리 중지하는 것을 조기 종료라고 부르며, 딥러닝 분야에서 널리 사용합니다.