[케라스(keras) 이해] 3장. 신경망 시작하기 (2)
본 글은 [케라스 창시자에게 배우는 딥러닝] - (박해선 옮김) 책을 개인공부하기 위해 요약, 정리한 내용입니다.
전체 코드는 https://github.com/ingu627/deep-learning-with-python-notebooks에 기재했습니다.(원본 코드 fork)
뿐만 아니라 책에서 설명이 부족하거나 이해가 안 되는 것들은 외부 자료들을 토대로 메꿔 놓았습니다. 즉, 딥러닝은 이걸로 끝을 내보는 겁니다.
오타나 오류는 알려주시길 바라며, 도움이 되길 바랍니다.
3_5. 뉴스 기사 분류: 다중 분류 문제
- 로이터(Reuter) 뉴스를 46개의 상호 배타적인 토픽으로 분류하는 신경망을 만들어 본다.
- 클래스가 많기 때문에 이 문제는 다중 분류(multiclass classification)의 예이다.
-
단일 레이블 다중 분류(single-label, multicalss classification) : 각 데이터 포인트가 정확히 하나의 범주로 분류
-
다중 레이블 다중 분류(multi-label, multiclass classification) : 각 데이터 포인트가 여러 개의 범주에 속할 때 분류
레이블을 벡터로 바꾸는 방법
- 레이블의 리스트를 정수 텐서로 변환하는 것
- 원-핫 인코딩을 사용 =범주형 인코딩(categorical encoding)
from tensorflow.keras.datasets import reuters
import numpy as np
from tensorflow.keras.utils import to_categorical
import matplotlib.pyplot as plt
from tensorflow.keras import models, layers
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(
num_words=10000
)
# 8982개의 훈련 샘플과 2246개의 테스트 샘플이 있다.
print(len(train_data))
print(len(test_data))
# IMDB 리뷰처럼 각 샘플은 정수 리스트이다. (단어 인덱스)
print(train_data[0])
# 결과 값
# [1, 2, 2, 8, 43, 10, 447, 5, 25, 207, 270, 5, 3095, 111, 16, 369, 186, 90, 67, 7, 89, 5, 19,
# 102, 6, 19, 124, 15, 90, 67, 84, 22, 482, 26, 7, 48, 4, 49, 8, 864, 39, 209, 154, 6, 151, 6, 83,
# 11, 15, 22, 155, 11, 15, 7, 48, 9, 4579, 1005, 504, 6, 258, 6, 272, 11, 15, 22, 134, 44, 11, 15, 16,
# 8, 197, 1245, 90, 67, 52, 29, 209, 30, 32, 132, 6, 109, 15, 17, 12]
# 로이터 데이터셋을 텍스트로 디코딩하기
word_index = reuters.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoded_newswire = ' '.join([reverse_word_index.get(i-3, '?') for i in train_data[0]])
# 샘플에 연결된 레이블은 토픽의 인덱스로 0과 45 사이의 정수이다.
print(train_labels[10]) # 3
# 데이터 준비
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1
return results
x_train = vectorize_sequences(train_data) # 훈련 데이터 벡터 변환
x_test = vectorize_sequences(test_data)
# def to_one_hot(labels, dimension=46):
# results = np.zeros((len(labels), dimension))
# for i, label in enumerate(labels):
# results[i, label] = 1
# return results
# one_hot_train_labels = to_one_hot(train_labels) # 훈련 레이블 벡터 변환
# one_hot_test_labels = to_one_hot(test_labels)
one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
# 출력 클래스의 개수가 46개로 늘어났다. 출력 공간의 차원이 훨씬 커졌다.
# 16차원 공간은 46개의 클래스를 구분하기에는 너무 제약이 많기 때문에 64개의 유닛을 이용한다.
# 모델 정의하기
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))
# 마지막 Dense 층의 크기가 46이다. 각 입력 샘플에 대해서 46차원의 벡터를 출력한다는 뜻이다.
# 이 벡터의 각 원소(각 차원)는 각기 다른 출력 클래스가 인코딩된 것이다.
# softmax -> 각 입력 샘플마다 46개의 출력 클래스에 대한 확률 분포를 출력한다.
# 즉, 46차원의 출력 벡터를 만들며 output[i]는 어떤 샘플이 클래스 i에 속할 확률이다.
# 46개의 값을 모두 더하면 1이 된다.
# 손실 함수 : categorical_crossentropy
# 이 함수는 두 확률 분포 사이의 거리를 측정한다.
# 여기에서는 네트워크가 출력한 확률 분포와 진짜 레이블의 분포 사이의 거리이다.
# 두 분포 사이의 거리를 최소화하면 진짜 레이블에 가능한 가까운 출력을 내도록 모델을 훈련하게 된다.
# 모델 컴파일하기
model.compile(
optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
# 훈련 데이터에서 1000개의 샘플을 따로 떼어서 검증 세트로 사용한다.
# 검증 세트 준비하기
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = one_hot_train_labels[:1000]
partial_y_train = one_hot_train_labels[1000:]
# 모델 훈련하기
history = model.fit(
partial_x_train,
partial_y_train,
batch_size=512,
epochs=9, # epochs가 9부터 과대적합이 일어나므로 9로 설정
validation_data=(x_val, y_val)
)
results = model.evaluate(x_test, one_hot_test_labels)
print(results) # 약 78%의 정확도
# [1.0235412120819092, 0.7849510312080383]
history.history.keys()
# dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])
accuracy = history.history['accuracy']
val_accuracy = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)
fig = plt.figure(figsize=(15,5))
ax1 = fig.add_subplot(1, 2, 1)
plt.plot(epochs, accuracy, 'bo', label='Training accuracy')
plt.plot(epochs, val_accuracy, 'b', label='Validaion accuracy')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('accuracy')
plt.legend()
ax1 = fig.add_subplot(1, 2, 2)
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validaion loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
# 새로운 데이터에 대해 예측하기
predictions = model.predict(x_test)
print(predictions[0].shape) # (46,)
print(np.sum(predictions[0])) # 0.99999994
print(np.argmax(predictions[0])) # 3
레이블과 손실을 다루는 다른 방법
- 정수 텐서로 변환하기
- 정수 레이블을 사용할 때는
sparse_categorical_corssentropy
를 사용하면 된다.
정리
- N개의 클래스로 데이터 포인트를 분류하려면 네트워크의 마지막 Dense 층의 크기는 N이어야 한다.
- 단일 레이블, 다중 분류 문제에서는 N개의 클래스에 대한 확률 분포를 출력하기 위해
softmax
활성화 함수를 사용해야 한다. - 이런 문제에서는 항상 범주형 크로스엔트로피를 사용해야 한다. 이 함수는 모델이 출력한 확률 분포와 타깃 분포 사이의 거리를 최소화 한다.
- 많은 수의 범주를 분류할 때 중간층의 크기가 너무 작아 네트워크에 정보의 병목이 생기지 않도록 해야 한다.
3_6. 주택 가격 예측: 회귀 문제
- (boston_housing)보스턴 주택 가격 데이터셋
- 1970년 중반 보스턴 외곽 지역의 범죄율, 지방세율 등의 데이터가 주어졌을 때 주택 가격의 중간 값을 예측해 본다.
보스턴 주택 데이터셋 로드하기
- 13개의 수치 특성이 있다.
from tensorflow.keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
print(train_data.shape)
print(test_data.shape)
print(train_targets)
# 결과
# (404, 13)
# (102, 13)
# [15.2 42.3 50. 21.1 17.7 18.5 11.3 15.6 15.6 14.4 12.1 17.9 23.1 19.9
# 15.7 8.8 50. 22.5 24.1 27.5 10.9 30.8 32.9 24. 18.5 13.3 22.9 34.7
# 16.6 17.5 22.3 16.1 14.9 23.1 34.9 25. 13.9 13.1 20.4 20. 15.2 24.7
# 22.2 16.7 12.7 15.6 18.4 21. 30.1 15.1 18.7 9.6 31.5 24.8 19.1 22.
# 14.5 11. 32. 29.4 20.3 24.4 14.6 19.5 14.1 14.3 15.6 10.5 6.3 19.3
# 19.3 13.4 36.4 17.8 13.5 16.5 8.3 14.3 16. 13.4 28.6 43.5 20.2 22.
# 23. 20.7 12.5 48.5 14.6 13.4 23.7 50. 21.7 39.8 38.7 22.2 34.9 22.5
# 31.1 28.7 46. 41.7 21. 26.6 15. 24.4 13.3 21.2 11.7 21.7 19.4 50.
# 22.8 19.7 24.7 36.2 14.2 18.9 18.3 20.6 24.6 18.2 8.7 44. 10.4 13.2
# 21.2 37. 30.7 22.9 20. 19.3 31.7 32. 23.1 18.8 10.9 50. 19.6 5.
# 14.4 19.8 13.8 19.6 23.9 24.5 25. 19.9 17.2 24.6 13.5 26.6 21.4 11.9
# 22.6 19.6 8.5 23.7 23.1 22.4 20.5 23.6 18.4 35.2 23.1 27.9 20.6 23
# ...
3_6_2. 데이터 준비
- 특성별로 정규화를 한다.
- 주의할 점은 정규화할 때 사용한 값이 훈련 데이터에서 계산한 값이라는 것.
- 머신 러닝 작업 과정에서 절대로 테스트 데이터에서 어떤 값도 사용해서는 안된다.
# 데이터 정규화하기
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std
3_6_3. 모델 구성
- 샘플 개수가 적기 때문에 64개의 유닛을 가진 2개의 은닉 층으로 작은 네트워크를 구성하여 사용.
from tensorflow.keras import models, layers
def build_model():
model = models.Sequential()
model.add(layers.Dense(
64, activation='relu',
input_shape=(train_data.shape[1],)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
return model
- 이 네트워크의 마지막 층은 하나의 유닛을 가지고 있고 활성화 함수가 없다. (선형 층)
- 마지막 층이 순수한 선형이므로 네트워크가 어떤 범위의 값이라도 예측하도록 자유롭게 학습된다.
- MAE(mean absolute error) : 예측과 타깃 사이 거리의 절댓값
3_6_4. K-겹 검증을 사용한 훈련 검증
- K-겹 교차 검증(K-fold cross-validation) : 데이터를 K개의 분할(즉 폴드(fold))로 나누고(일반적으로 K=4, K=5), K개의 모델을 각각 만들어 K-1개의 분할에서 훈련하고 나머지 분할에서 평가하는 방법
- 모델의 검증 점수는 K개의 검증 점수 평균이 된다.
# K-겹 검증하기
import numpy as np
k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []
for i in range(k):
print('처리중인 폴드 #', i)
val_data = train_data[i * num_val_samples: (i+1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i+1) * num_val_samples]
partial_train_data = np.concatenate(
[train_data[:i * num_val_samples],
train_data[(i+1) * num_val_samples:]],
axis=0)
partial_train_targets = np.concatenate(
[train_targets[:i * num_val_samples],
train_targets[(i+1) * num_val_samples:]],
axis=0)
model = build_model()
model.fit(
partial_train_data, partial_train_targets,
batch_size=1, epochs=1, verbose=0
)
val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
all_scores.append(val_mae)
# 결과
# 처리중인 폴드 # 0
# 처리중인 폴드 # 1
# 처리중인 폴드 # 2
# 처리중인 폴드 # 3
build_model()
: 케라스 모델 구성(컴파일 포함)verbose=0
이므로 훈련 과정이 출력되지 않는다.
print(all_scores)
print(np.mean(all_scores))
# 결과
# [4.616418361663818, 4.768833160400391, 4.236837387084961, 5.337116718292236]
# 4.739801406860352
정리
- 입력 데이터의 특성이 서로 다른 범위를 가지면 전처리 단계에서 각 특성을 개별적으로 스케일 조정해야 한다.
- 가용한 데이터가 적다면 K-겹 검증을 사용하는 것이 신뢰할 수 있는 모델 평가 방법이다.
- 가용한 훈련 데이터가 적다면 과대적합을 피하기 위해 은닉 층의 수를 줄인 모델이 좋다. (일반적으로 1개 또는 2개)
댓글남기기