페이지

2022년 7월 23일 토요일

3.5.5 새로운 데이터에 대해 예측하기

 모델 객체의 predict 메서드는 46개의 토픽에 대한 확률 분포를 반환합니다. 테스트 데이터 전체에 대한 토픽을 예측해 보겠습니다.

    predictions = model.predict(x_test)

predictions의 각 항목은 길이가 46인 벡터입니다.

>>> predictions[0].shape

(46,)

이 벡터의 원소 합은 1입니다.

>>> np.sum(predictions[0])

1.0

가장 큰 값이 예측 클래스가 됩니다. 즉 가장 확률이 높은 클래스입니다.

>>> np.argmax(predictions[0])

3


3.5.4 훈련 검증

 훈련 데이터에서 1,000개의 샘플을 따로 떼어서 검증 세트로 사용하겠습니다.

    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:]

이제 20번의 에포크로 모델을 훈련시킵니다.

    history = model.fit(partial_x_train,

                            partial_y_train,

                            epochs=20,

                            batch_size=512,

                            validation_data=(x_val, y_val))

마지막으로 손실과 정확도 곡선을 그립니다.

    import matplotlib.pyplot as plt

    loss = history.history]['loss']

    val_loss = history.history['val_loss']


    epochs = range(1, len(loss) + 1)

    plt.plot(epochs, loss, 'bo', label='Training loss')

    plt.plot(epochs, val_loss, 'b', label='Validation loss')

    plt.title('Training and validation loss')

    plt.xlabel('Epochs')

    plt.ylabel('Loss')

    plt.legend()

    plt.show()


    plt.clf()  ............... 그래프를 초기화합니다.


    acc = history.history['acc']

    val_acc = history.history['val_acc']


    plt.plot(epochs, acc, 'bo', label='Training acc')

    plt.plot(epochs, val_acc, 'b', label='Validation acc')

    plt.title('Training and validation accuracy')

    plt.xlabel('Epochs')

    plt.ylabel('Accuracy')

    plt.legend()

    plt.show()


이 모델은 아홉 번째 에포크 이후에 과대 적합이 시작됩니다. 아홉 번의 엨포크로 새로운 모델을 훈련하고 테스트 세트에서 평가하겠습니다.


    model = modles.Sequential()

    model.add(layers.Dense(64, activation='relu', input_shape=(1000, )))

    model.add(layers.Dense(64, activation='relu'))

    model.add(layers.Dense(46, activation='softmax'))

    

    model.compile(optimizer='rmsprop',

                    loss='categorical_crossentropy',

                    metrics=['accuracy']

    model.fit(partial_x_train,

                paartial_y_train,

                epochs=9,

                batch_size=512,

                validation_data=(x_val, y_val))

    results = model.evaluate(x_test, on_hot_test_labels)

최종 결과는 다음과 같습니다

>>> results

[1.022498257544459, 0.7756010686194165]

대략 78%의 정확도를 달성했씁니다. 균형 잡힌 이진 분류 문제에서 완전히 무작위로 분류하면 50%의 정확도를 달성합니다. 이 문제는 불균형한 데이터셋을 사용하므로 무작위로 분류하면 18%정도를 달성합니다. 여기에 비하면 이 결과는 꽤 좋은 편입니다.

>>> import copy

>>> test_labels_copy = copy.copy(test_labels)

>>> np.random.shuffle(test_labels_copy)

>>> hits_array = np.array(test_labels) == np.array(test.labels_copy)

>>> float(np.sum(hits_array)) / len(test_labels)

0.182546749777382

 

3.5.3 모델구성

 이 토픽 분류문제는 이전의 영화 리뷰 분류 문제와 비슷해 보입니다. 두 경우 모두 잛은 텍스트를 분류하는 것이죠. 여기에서는 새로운 제약 사항이 추가되었습니다. 출력 클래스의 개수가 2에서 46개로 늘어난 점입니다. 출력 공간의 차원이 훨씬 커졌습니다.

이전에 사용했던 것처럼 Dense층을 쌓으면 각 층은 이전 층의 출력에서 제공한 정보만 사용할 수 있습니다. 한 층이 분류 문제에 필요한 일부 정보를 누락하면 그 다음 층에서 이를 복원할 방법이 없습니다. 각 층은 잠재적으로 정보의 병목(information bottleneck)이 될 수 있습니다. 이전 예제에서 16차원을 가진 중간층을 사용했지만 16차원 공간은 46개의 클래슬르 구분하기에 너무 제약이 많을 것 같ㅅ브니다. 이렇게 규모가 작은 층은 유용한 정보를 완전히 잃게 되는 정보의 병목 지점처럼 동작할 수 있습니다.

이런 이유로 좀 더 규모가 큰 층을 사용하겠습니다. 64개의 유닛을 사용해 보죠.

    from keras import models

    from keras import layers

    

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

이 구조에서 주목해야 할 점이 두 가지 있습니다.

1) 마지막 Dense 층의 크기가 46입니다. 각 입력 샘플에 대해서 46차원의 벡터를 출력한다는 뜻입니다. 이 벡터의 각 원소(각 차원)는 각기 다른 출력 클래스가 인코딩된 것입니다.

2) 마지막 층에 softmax활성화 함수가 사용되었습니다. MNIST예제에서 이런 방식을 보았습니다. 각 입력 샘플마다 46개의 출력 클래스에 대한 확률 분포를 출력합니다. 즉 46차원의 출력 벡터를 만들며 output[i]는 어떤 샘플이 클래스 i에 속할 확률입니다. 46개의 값을 모두 더하면 1이 됩니다.

이런 문제에 사용할 최선의 손실 함수는 categorical_crossentropy입니다. 이 함수는 두 확률 분포 사이의 거리르 측정합니다. 여기에서는 네트워크가 출력한 확률 분포와 진짜 레이블의 분포 사이의 거리입니다. 두 분포 사이의 거리를 최소화하면 진짜 레이블에 가능한 가까운 출력을 내도록 모델을 훈련하게 됩니다.

    model.compile(optimizer='rmsprop',

                        loss='categorical_crossentropy',

                        metrics=['accuracy'])

3.5.2 데이터 준비

 이전에 예제와 동일한 코드를 사용해서 데이터를 벡터로 변환합니다.

    import numpy as np

    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)  ................ 텍스트 데이터 벡터 변환

레이블을 벡터로 바꾸는 방법은 두 가지입니다. 레이블의 리스트를 정수 텐서로 변환하는 것과 원-핫 인코딩을 사용하는 것입니다. 원-핫 인코딩이 범주형 데이터에 널리 사용되지 때문에 범주형 인코딩(catetorical encoding)이라고 부릅니다. 원-핫 인코딩에 대한 자세한 설명은 6.1절을 참고하세요. 이 경우 레이블의 원-핫 인코딩은 각 레이블의 인텍스 자리는 1이고 나머지는 모두 0인 벡터입니다. 다음과 같습니다.

    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)..............테스트 레이블 벡터 변환

MNIST예제에서 이미 보았듯이 케라스에는 이를 위한 내장 함수가 있습니다.

    from keras.utils.np_utils import to_categorical

    one_hot_train_labels = to_categorical(train_labels)

    ont_hot_test_labels = to_categorical(test_labels)

3.5.1 로이터 데이터셋

 1986년에 로이터에서 공개한 짧은 뉴스 기사와 토픽의 집합인 로이터 데이터셋을 사용하겠습니다. 이 데이터셋은 텍스트 분류를 위해 널리 사용되는 간단한 데이터셋입니다. 46개의 토픽이 있으며 어떤 토픽은 다른 것에 비해 데이터가 많습니다. 각 토픽은 훈련 세트에 최소한 10개의 샘플을 가지고 있습니다.

IMDB, MNIST와 마찬가지로 로이터 데이터셋은 케라스에 포함되어 있습니다. 한번 살펴보죠.

    from keras.datasets import reuters

    (train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000)

 IMDB 데이터셋처럼 num_words=10000 매개변수는 데이터에서 가장 자주 등장하는 단어 1만개로 제한합니다.

여기에는 8,982개의 훈련 샘플과 2,246개의 테스트 샘플이 있습니다.

>>> len(train_data)

8982

>>> len(test_data)

2246

IMDB 리뷰처럼 각 샘플은 정수 리스트입니다(단어 인덱스).

>>> train_data[10]

[1, 245, 273, 207, 156, 53, 74, 160, 26, 14, 46, 296, 26, 39, 74, 2979, 3554, 14, 46, 4689, 4389, 86, 61, 3499, 4795, 14, 61, 451, 4329, 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, 1, 2는 '패딩', '문서 시작', '사전에 없음'을 위한 인덱스이므로 3을 뺍니다.

샘릉에 연결된 레이블은 토픽의 인텍스로 0과 45 사이의 정수입니다.

>>>  train_lagbels[10]

3



3.5 뉴스 기사 분류: 다중 분류 문제

 이전 절에서 완전 연결된 신경망을 사용하여 벡터 입력을 어떻게 2개의 클래스로 분류하는지 보았습니다. 2개 이상의 클래스가 있을 때는 어떻게 해야 할까요?

이 절에서 로이터(Reuter)뉴스를 46개의 상호 배타적인 토픽으로 분류하는 신경망을 만들어 보겠습니다. 클래스가 많기 때문에 이 문제는 다중 분류(multiclass classification)의 예입니다. 각 데이터 포인트가 정확히 하나의 범주로 분류되기 때문에 좀 더 정확히 말하면 단일 레이블 다중 분류(single-label, multiclass classification)문제입니다. 각 데이터 포인트가 여러 개의 범주(예를 들어 토픽)에 속할 수 있다면 이것은 다중 레이블 다중 분류(multi-label, multiclass classification)문제가 됩니다.

3.4.7 정리

다음은 이 예제에서 배운 것들 입니다.

1) 원본 데이터를 신경망에 턴서로 주입하기 위해서는 꽤 많은 전처리가 필요합니다. 단어 시퀀스는 이진 벡터로 인코딩될 수 있고 다른 인코딩 방식도 있습니다.

2) relu 활성화 함수와 함께 Dense층을 쌓은 네트워크는 (감성 분류를 ㅍ초함하여) 여러 종류의 문제에 적용할 수 있어 앞으로 자주 사용하게 될 것입니다.

3) (출력 클래스가 2개인) 이진 분류 문제에서 네트워크는 하나의 유닛과 sigmoid 활성화 함수를 가진 Dense 층으로 끝나야 합니다. 이 신경망의 출력은 확률을 나타내는 0과 1 사이의 스칼라 값입니다.

4) 이진 분류 문제에서 이런 스칼라 시그모이드 출력에 대해 사용할 손실 함수는 binary_crossentropy입니다.

5) rmsprop 옵티마이전느 문제에 상관없이 일반적으로 충분히 좋은 선택입니다. 걱정할 거리가 하나 줄은 셈입니다.

6) 훈련 데이터에 대해 성능이 향사됨에 따라 신경망은 과대적합되기 시작하고 이전에 본적 없는 데이터에서는 결과가 점점 나빠지게 됩니다. 항상 훈련 세트 이외의 데이터에서 성능을 모니터링해야 합니다.