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-그램을 머신러닝 모델이 처리할 수 있는 숫자 벡터로 변환해야 합니다. 아래 예시는 두 텍스트에 대해 생성된 유니그램 및 Bigram에 할당된 색인을 보여줍니다.

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]

카운트 인코딩: 모든 샘플 텍스트는 텍스트에서 토큰 수를 나타내는 벡터로 표시됩니다. 유니그램에 해당하는 요소(아래 굵게 표시됨)는 이제 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] (See Scikit-learn TfidfTransformer)

다른 벡터 표현도 많지만, 위 세 가지가 가장 흔히 사용됩니다.

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

특성 선택

데이터 세트의 모든 텍스트를 유니+바이그램 토큰으로 변환하면 수만 개의 토큰이 생성될 수 있습니다. 이러한 토큰/특성 중 일부는 라벨 예측에 영향을 주지 않습니다. 따라서 특정 토큰을 데이터 세트 전체에서 거의 사용하지 않는 토큰을 삭제할 수 있습니다. 또한 특성 중요도 (각 토큰이 라벨 예측에 기여하는 정도)를 측정하고 가장 유용한 토큰만 포함할 수 있습니다.

특성 및 해당 라벨을 사용하여 특성 중요도 점수를 출력하는 많은 통계 함수가 있습니다. 일반적으로 사용되는 두 가지 함수는 f_classifchi2입니다. 실험 결과, 두 함수 모두 동등한 성능을 보이는 것으로 나타났습니다.

무엇보다도 정확성은 많은 데이터 세트에서 약 20,000개의 특성에 도달합니다 (그림 6 참고). 이 임계값보다 많은 특성을 추가하면 매우 적게 기여하며 과적합으로 이어져 성능이 저하되는 경우도 있습니다.

상위 K 및 정확도

그림 6: 주요 K 특성과 정확도 데이터 세트 전반에 걸쳐 상위 20, 000개 정도의 특성에서 정확성이 떨어집니다.

정규화

정규화는 모든 특성/샘플 값을 작고 유사한 값으로 변환합니다. 이렇게 하면 학습 알고리즘에서 경사하강 수렴이 간소화됩니다. 살펴본 바와 같이 데이터 사전 처리 중 정규화는 텍스트 분류 문제에 큰 가치를 더하지 않는 것으로 보입니다. 이 단계를 건너뛰는 것이 좋습니다.

다음 코드는 위의 모든 단계를 통합합니다.

  • 텍스트 샘플을 유니+비그램으로 토큰화
  • tf-idf 인코딩을 사용하여 벡터화
  • 토큰 벡터에서 상위 20,000개의 특성만 선택합니다. 2회 미만으로 표시되는 토큰을 삭제하고 f_classif를 사용하여 특성 중요도를 계산합니다.
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'
Index assigned for every token: {'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}.
NOTE: 'the' occurs most frequently, so the index value of 1 is assigned to it.
Some libraries reserve index 0 for unknown tokens, as is the case here.
Sequence of token indexes: '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 값만 사용하여 클래스를 나타낼 수 있습니다. 내부적으로 네트워크는 원-핫 벡터를 사용하여 이러한 값을 표현합니다 (라벨 간의 잘못된 관계 추론을 방지하기 위해). 이러한 표현은 손실 함수와 신경망에서 사용하는 마지막 레이어 활성화 함수에 따라 달라집니다. 다음 섹션에서는 이에 대해 자세히 알아봅니다.