3단계: 데이터 준비하기

데이터를 모델에 공급하려면 먼저 모델이 이해할 수 있는 형식으로 변환해야 합니다.

첫째, 수집한 데이터 샘플이 특정 순서로 되어 있을 수 있습니다. 샘플 순서와 관련된 정보가 텍스트와 라벨의 관계에 영향을 미치길 원하지 않습니다. 예를 들어 데이터 세트를 클래스별로 정렬한 다음 학습/검증 세트로 분할할 경우 이러한 세트는 데이터의 전체 분포를 나타내지 않습니다.

모델이 데이터 순서의 영향을 받지 않도록 하는 간단한 권장사항은 다른 작업을 수행하기 전에 항상 데이터를 셔플하는 것입니다. 데이터가 이미 학습 세트와 검증 세트로 분할된 경우 학습 데이터를 변환하는 것과 동일한 방식으로 검증 데이터를 변환해야 합니다. 별도의 학습 세트와 검증 세트가 아직 없다면 셔플을 사용하여 샘플을 분할할 수 있습니다. 일반적으로 학습에 샘플의 80%, 검증에 20% 를 사용하는 것이 일반적입니다.

둘째, 머신러닝 알고리즘이 숫자를 입력으로 취합니다. 즉, 텍스트를 숫자 벡터로 변환해야 합니다. 이 프로세스에는 두 단계가 있습니다.

  1. 토큰화: 텍스트를 단어 또는 더 작은 하위 텍스트로 나눕니다. 그러면 텍스트와 라벨 간의 관계를 일반화할 수 있습니다. 이는 데이터 세트 (데이터에 있는 고유한 토큰 세트)의 '어휘'를 결정합니다.

  2. 벡터화: 이러한 텍스트를 특징짓는 적절한 숫자 측정값을 정의합니다.

N-그램 벡터와 시퀀스 벡터 모두에서 이 두 단계를 실행하는 방법과 특성 선택 및 정규화 기술을 사용하여 벡터 표현을 최적화하는 방법을 알아보겠습니다.

N-그램 벡터[옵션 A]

이어지는 단락에서는 N-그램 모델의 토큰화 및 벡터화 방법을 알아봅니다. 또한 특성 선택 및 정규화 기법을 사용하여 N-그램 표현을 최적화하는 방법도 알아봅니다

N-그램 벡터에서 텍스트는 고유한 N-그램, 즉 n개의 인접한 토큰 그룹 (일반적으로 단어)의 모음으로 표현됩니다. The mouse ran up the clock 텍스트를 살펴보겠습니다. 여기에서

  • 유니그램 (n = 1)이라는 단어는 ['the', 'mouse', 'ran', 'up', 'clock']입니다.
  • 'bigrams'(n = 2)라는 단어는 ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock']입니다.
  • 될 수 있습니다.

토큰화

단어 유니그램과 바이그램으로 토큰화하면 정확성이 우수하면서도 컴퓨팅 시간이 단축되는 것으로 나타났습니다.

벡터화

텍스트 샘플을 N-그램으로 분할한 후에는 이러한 N-그램을 머신러닝 모델이 처리할 수 있는 숫자 벡터로 변환해야 합니다. 아래 예는 두 텍스트에 대해 생성된 유니그램 및 바이그램에 할당된 색인을 보여줍니다.

Texts: 'The mouse ran up the clock' and 'The mouse ran down'
Index assigned for every token: {'the': 7, 'mouse': 2, 'ran': 4, 'up': 10,
  'clock': 0, 'the mouse': 9, 'mouse ran': 3, 'ran up': 6, 'up the': 11, 'the
clock': 8, 'down': 1, 'ran down': 5}

색인이 N-그램에 할당되면 일반적으로 다음 옵션 중 하나를 사용하여 벡터화합니다.

원-핫 인코딩: 모든 샘플 텍스트는 텍스트에 토큰의 존재 여부를 나타내는 벡터로 표현됩니다.

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]

카운트 인코딩: 모든 샘플 텍스트는 텍스트의 토큰 개수를 나타내는 벡터로 표현됩니다. 유니그램 'the'에 해당하는 요소는 이제 2로 표시됩니다. 텍스트에서 'the'라는 단어가 두 번 나오기 때문입니다.

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1]

Tf-idf 인코딩: 위 두 가지 접근 방식의 문제는 모든 문서에서 유사한 빈도로 사용되는 일반적인 단어 (즉, 데이터 세트의 텍스트 샘플에서 특별히 고유하지 않은 단어)가 불이익을 받지 않는다는 점입니다. 예를 들어 'a'와 같은 단어는 모든 텍스트에서 매우 자주 등장합니다 따라서 더 의미 있는 다른 단어보다 'the'에 대한 토큰 수가 많으면 그다지 유용하지 않습니다.

'The mouse ran up the clock' = [0.33, 0, 0.23, 0.23, 0.23, 0, 0.33, 0.47, 0.33, 0.23, 0.33, 0.33]

(scikit-learn TfidfTransformer 참조)

이 외에도 많은 벡터 표현이 있지만 앞의 세 가지 표현이 가장 일반적으로 사용됩니다.

tf-idf 인코딩의 정확성 측면에서 tf-idf 인코딩이 다른 두 인코딩보다 약간 더 우수하므로 (평균 0.25~15% 더 높음) N-그램을 벡터화하는 데 이 방법을 사용하는 것이 좋습니다. 하지만 부동 소수점 표현을 사용하기 때문에 메모리를 더 많이 차지하고 특히 대규모 데이터 세트의 경우 계산하는 데 더 많은 시간이 걸립니다(경우에 따라 두 배 더 오래 걸릴 수 있음).

특성 선택

데이터 세트의 모든 텍스트를 단어 유니+비그램 토큰으로 변환하면 수만 개의 토큰이 될 수 있습니다. 이러한 토큰/기능 중 일부가 라벨 예측에 기여하는 것은 아닙니다. 따라서 데이터 세트에서 매우 드물게 발생하는 토큰과 같은 특정 토큰을 삭제할 수 있습니다. 특성 중요도 (각 토큰이 라벨 예측에 기여하는 정도)를 측정하고 가장 유익한 토큰만 포함할 수 있습니다.

특성과 해당 라벨을 가져와서 특성 중요도 점수를 출력하는 통계 함수가 많이 있습니다. 일반적으로 사용되는 두 가지 함수는 f_classifchi2입니다. 실험에 따르면 이 두 함수의 성능은 동일합니다.

무엇보다도 많은 데이터 세트의 특성에서 약 20,000개의 정확성이 최고점을 기록했습니다 (그림 6 참고). 이 임곗값 이상으로 특성을 추가하면 거의 과적합으로 이어지고 성능이 저하되기도 합니다.

Top K 대 정확성

그림 6: Top K 특성과 정확성 비교 데이터 세트 전반에서 정확성은 상위 20, 000개 가량에 정체되어 있습니다.

정규화

정규화는 모든 특성/표본 값을 작고 유사한 값으로 변환합니다. 이를 통해 학습 알고리즘의 경사하강법 수렴이 간소화됩니다. 데이터 사전 처리 중 정규화는 텍스트 분류 문제에 많은 가치를 더하지 않으므로 이 단계를 건너뛰는 것이 좋습니다.

다음 코드는 위의 모든 단계를 하나로 모읍니다.

  • 텍스트 샘플을 단어 유니+비그램으로 토큰화
  • tf-idf 인코딩을 사용하여 벡터화
  • 2회 미만으로 나타나는 토큰을 삭제하고 f_classif를 사용하여 특성 중요도를 계산하여 토큰 벡터에서 상위 20,000개의 특성만 선택합니다.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif

# Vectorization parameters
# Range (inclusive) of n-gram sizes for tokenizing text.
NGRAM_RANGE = (1, 2)

# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Whether text should be split into word or character n-grams.
# One of 'word', 'char'.
TOKEN_MODE = 'word'

# Minimum document/corpus frequency below which a token will be discarded.
MIN_DOCUMENT_FREQUENCY = 2

def ngram_vectorize(train_texts, train_labels, val_texts):
    """Vectorizes texts as n-gram vectors.

    1 text = 1 tf-idf vector the length of vocabulary of unigrams + bigrams.

    # Arguments
        train_texts: list, training text strings.
        train_labels: np.ndarray, training labels.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val: vectorized training and validation texts
    """
    # Create keyword arguments to pass to the 'tf-idf' vectorizer.
    kwargs = {
            'ngram_range': NGRAM_RANGE,  # Use 1-grams + 2-grams.
            'dtype': 'int32',
            'strip_accents': 'unicode',
            'decode_error': 'replace',
            'analyzer': TOKEN_MODE,  # Split text into word tokens.
            'min_df': MIN_DOCUMENT_FREQUENCY,
    }
    vectorizer = TfidfVectorizer(**kwargs)

    # Learn vocabulary from training texts and vectorize training texts.
    x_train = vectorizer.fit_transform(train_texts)

    # Vectorize validation texts.
    x_val = vectorizer.transform(val_texts)

    # Select top 'k' of the vectorized features.
    selector = SelectKBest(f_classif, k=min(TOP_K, x_train.shape[1]))
    selector.fit(x_train, train_labels)
    x_train = selector.transform(x_train).astype('float32')
    x_val = selector.transform(x_val).astype('float32')
    return x_train, x_val

N-그램 벡터 표현을 사용하면 단어 순서 및 문법에 관한 많은 정보를 삭제합니다 (n > 1일 때 일부 부분 순서 정보를 유지할 수 있음). 이를 단어 집합 접근 방식이라고 합니다. 이러한 표현은 로지스틱 회귀, 멀티 레이어 퍼셉트론, 경사 부스팅 머신, 벡터 머신 지원 등 순서를 고려하지 않는 모델과 함께 사용됩니다.

시퀀스 벡터[옵션 B]

이어지는 단락에서는 시퀀스 모델의 토큰화 및 벡터화 방법을 알아봅니다. 또한 특성 선택 및 정규화 기법을 사용하여 시퀀스 표현을 최적화하는 방법도 다룹니다

일부 텍스트 샘플의 경우 단어 순서가 텍스트의 의미에 매우 중요합니다. 예를 들어 '출퇴근길이 싫어요. 새 자전거가 완전히 바뀌었어'라는 메시지는 순서대로 읽을 때만 이해할 수 있습니다. CNN/RNN과 같은 모델은 샘플의 단어 순서에서 의미를 추론할 수 있습니다. 이러한 모델의 경우 텍스트를 순서를 유지하는 토큰 시퀀스로 표현합니다.

토큰화

텍스트는 일련의 문자 또는 단어의 시퀀스로 표현될 수 있습니다. 단어 수준 표현을 사용하는 것이 문자 토큰보다 나은 성능을 제공하는 것으로 확인되었습니다. 이것은 산업이 따라가는 일반적인 표준이기도 합니다. 문자 토큰은 텍스트에 오타가 많은 경우에만 의미가 있지만 일반적으로는 그렇지 않습니다.

벡터화

텍스트 샘플을 단어 시퀀스로 변환했으면 이러한 시퀀스를 숫자 벡터로 변환해야 합니다. 아래 예시는 두 텍스트에 대해 생성된 유니그램에 할당된 색인과 첫 번째 텍스트가 변환되는 토큰 색인 시퀀스를 보여줍니다.

Texts: 'The mouse ran up the clock' and 'The mouse ran down'

모든 토큰에 할당된 색인:

{'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}

참고: 단어 'the'가 가장 자주 표시되므로 색인 값 1이 할당됩니다. 일부 라이브러리는 여기처럼 알 수 없는 토큰에 대해 색인 0을 예약합니다.

토큰 색인 시퀀스:

'The mouse ran up the clock' = [1, 2, 3, 4, 1, 5]

토큰 시퀀스를 벡터화하는 데 사용할 수 있는 옵션은 두 가지입니다.

원-핫 인코딩: n = 어휘 크기인 n차원 공간에서 단어 벡터를 사용하여 시퀀스를 표현합니다. 이러한 표현은 문자로 토큰화할 때 훌륭하며, 따라서 어휘가 작습니다. 단어로 토큰화할 때 어휘는 일반적으로 수만 개의 토큰을 갖게 되므로 원-핫 벡터가 매우 희소하고 비효율적입니다. 예:

'The mouse ran up the clock' = [
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 1, 0, 0, 0, 0],
  [0, 0, 0, 1, 0, 0, 0],
  [0, 0, 0, 0, 1, 0, 0],
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 1, 0]
]

단어 임베딩: 단어에는 연관된 의미가 있습니다. 따라서 밀집 벡터 공간 (약 수백 개의 실수)에서 단어 토큰을 표현할 수 있습니다. 여기서 단어 사이의 위치와 거리는 의미상 얼마나 유사한지를 나타냅니다 (그림 7 참고). 이러한 표현을 단어 임베딩이라고 합니다.

단어 임베딩

그림 7: 단어 임베딩

시퀀스 모델에는 이러한 임베딩 레이어가 첫 번째 레이어로 있는 경우가 많습니다. 이 레이어는 학습 프로세스 중에 단어 색인 시퀀스를 단어 임베딩 벡터로 변환하는 방법을 학습합니다. 그러면 각 단어 색인이 의미 공간에서 단어의 위치를 나타내는 실제 값의 밀집 벡터에 매핑됩니다 (그림 8 참고).

임베딩 레이어

그림 8: 임베딩 레이어

특성 선택

데이터의 모든 단어가 라벨 예측에 기여하는 것은 아닙니다. 어휘에서 드물거나 관련이 없는 단어를 삭제하여 학습 과정을 최적화할 수 있습니다. 실제로 가장 자주 사용되는 20,000개의 특성을 사용하는 것으로도 일반적으로 충분합니다. 이는 N-그램 모델에서도 마찬가지입니다 (그림 6 참고).

위의 모든 단계를 시퀀스 벡터화에 종합해 보겠습니다. 다음 코드는 이러한 작업을 수행합니다.

  • 텍스트를 단어로 토큰화
  • 상위 20,000개의 토큰을 사용해 어휘를 만듭니다.
  • 토큰을 시퀀스 벡터로 변환
  • 고정된 시퀀스 길이로 시퀀스 패딩
from tensorflow.python.keras.preprocessing import sequence
from tensorflow.python.keras.preprocessing import text

# Vectorization parameters
# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Limit on the length of text sequences. Sequences longer than this
# will be truncated.
MAX_SEQUENCE_LENGTH = 500

def sequence_vectorize(train_texts, val_texts):
    """Vectorizes texts as sequence vectors.

    1 text = 1 sequence vector with fixed length.

    # Arguments
        train_texts: list, training text strings.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val, word_index: vectorized training and validation
            texts and word index dictionary.
    """
    # Create vocabulary with training texts.
    tokenizer = text.Tokenizer(num_words=TOP_K)
    tokenizer.fit_on_texts(train_texts)

    # Vectorize training and validation texts.
    x_train = tokenizer.texts_to_sequences(train_texts)
    x_val = tokenizer.texts_to_sequences(val_texts)

    # Get max sequence length.
    max_length = len(max(x_train, key=len))
    if max_length > MAX_SEQUENCE_LENGTH:
        max_length = MAX_SEQUENCE_LENGTH

    # Fix sequence length to max value. Sequences shorter than the length are
    # padded in the beginning and sequences longer are truncated
    # at the beginning.
    x_train = sequence.pad_sequences(x_train, maxlen=max_length)
    x_val = sequence.pad_sequences(x_val, maxlen=max_length)
    return x_train, x_val, tokenizer.word_index

라벨 벡터화

샘플 텍스트 데이터를 숫자 벡터로 변환하는 방법을 살펴보았습니다. 라벨에도 비슷한 프로세스를 적용해야 합니다. 라벨을 [0, num_classes - 1] 범위의 값으로 간단히 변환할 수 있습니다. 예를 들어 클래스가 3개 있는 경우 0, 1, 2만 사용하여 클래스를 나타낼 수 있습니다. 내부적으로 네트워크는 원-핫 벡터를 사용하여 이러한 값을 표현하여 라벨 간의 잘못된 관계를 추론하지 않도록 합니다. 이러한 표현은 손실 함수와 신경망에서 사용하는 마지막 레이어 활성화 함수에 따라 달라집니다. 이에 대해서는 다음 섹션에서 자세히 알아보겠습니다.