5 분 소요

본 글은 [케라스 창시자에게 배우는 딥러닝] - (박해선 옮김) 책을 개인공부하기 위해 요약, 정리한 내용입니다.
전체 코드는 https://github.com/ingu627/deep-learning-with-python-notebooks에 기재했습니다.(원본 코드 fork)
뿐만 아니라 책에서 설명이 부족하거나 이해가 안 되는 것들은 외부 자료들을 토대로 메꿔 놓았습니다. 즉, 딥러닝은 이걸로 끝을 내보는 겁니다.
오타나 오류는 알려주시길 바라며, 도움이 되길 바랍니다.





3_5. 뉴스 기사 분류: 다중 분류 문제

  • 로이터(Reuter) 뉴스를 46개의 상호 배타적인 토픽으로 분류하는 신경망을 만들어 본다.
  • 클래스가 많기 때문에 이 문제는 다중 분류(multiclass classification)의 예이다.



  • 단일 레이블 다중 분류(single-label, multicalss classification) : 각 데이터 포인트가 정확히 하나의 범주로 분류

  • 다중 레이블 다중 분류(multi-label, multiclass classification) : 각 데이터 포인트가 여러 개의 범주에 속할 때 분류



레이블을 벡터로 바꾸는 방법

  1. 레이블의 리스트를 정수 텐서로 변환하는 것
  2. 원-핫 인코딩을 사용 =범주형 인코딩(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


output



레이블과 손실을 다루는 다른 방법

  • 정수 텐서로 변환하기
  • 정수 레이블을 사용할 때는 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개의 검증 점수 평균이 된다.

image

# 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개)



References

댓글남기기