페이지

2022년 7월 25일 월요일

3.5.7 충분히 큰 중간층을 두어야 하는 이유

 앞서 언급한 것 처럼 마지막 출력이 46차원이기 때문에 중간층의 히든 유닛이 46개보다 많이 적어선느 안 됩니다. 46차원보다 훨씬 작은 중간층(예를 들어 4차원)을 두면 정보의 병목이 어떻게 나타나는지 확인해 보겠습니다.

    model = models.Sequential()

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

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

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


    model.compile(optimizer='rmsprop',

                        loss='categorical_crossentropy',

                        metrics=['accuracy'])

    model.fit(partial_x_train,

                partial_y_train,

                epochs=20,

                batch_size=128,

                validation_data=(x_val, y_val))

검증 정확도의 최고 값은 약 71%로 8% 정도 감소되었습니다. 이런 손실의 원인 대부분은 많은 정보(클래스 46개의 분할 초평면을 복원하기에 충분한 정보)를 중간층의 저 차원 표현 공간으로 압축하려고 했기 때문입니다. 이 네트워크는 필요한 정보 대부분을 4차원 표현 안에 구겨 넣었지만 전부는 넣지 못했습니다.

2022년 7월 23일 토요일

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

 앞서 언급한 것처럼 레이블을 인코딩하는 다른 방법은 다음과 같이 정수 텐서로 변환하는 것입니다.

    y_train = np.array(train_labels)

    y_test = np.array(test_labels)

이 방식을 사용하려면 손실 함수 하나만 바꾸면 됩니다. 코드 3-21에 사용된 손실 함수 categorical_crossentropy는 레이블이 범주형 인코딩되어 있을 것이라고 기대합니다. 정수 레이블을 사용할때는 sparse_categorical_crossentropy 를 사용해야 합니다.

    model.comile(optimizer='rmsprop',

                    loss='sparse_categorical_crossentropy',

                    metrics=['acc'])

이 손실 함수는 인터페이스만 다를 뿐이고 수학적으로 categorical_crossentropy와 동일합니다.

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