Шаг 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'] , биграммы слов (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 . Проблема с двумя вышеуказанными подходами заключается в том, что общие слова, встречающиеся с одинаковой частотой во всех документах (т. е. слова, которые не особенно уникальны для образцов текста в наборе данных), не наказываются. Например, такие слова, как «а», будут очень часто встречаться во всех текстах. Таким образом, большее количество токенов для «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)

Существует множество других векторных представлений, но чаще всего используются три приведенных выше.

Мы заметили, что кодирование tf-idf немного лучше, чем два других с точки зрения точности (в среднем: на 0,25-15% выше), и рекомендуем использовать этот метод для векторизации n-грамм. Однако имейте в виду, что он занимает больше памяти (поскольку использует представление с плавающей запятой) и требует больше времени для вычислений, особенно для больших наборов данных (в некоторых случаях может занять вдвое больше времени).

Выбор функции

Когда мы преобразуем все тексты в наборе данных в токены uni+bigram слов, мы можем получить десятки тысяч токенов. Не все эти токены/функции способствуют прогнозированию меток. Таким образом, мы можем отбрасывать определенные токены, например те, которые крайне редко встречаются в наборе данных. Мы также можем измерить важность функции (насколько каждый токен способствует прогнозированию меток) и включить только наиболее информативные токены.

Существует множество статистических функций, которые принимают функции и соответствующие метки и выводят оценку важности функций. Две часто используемые функции — это f_classif и chi2 . Наши эксперименты показывают, что обе эти функции выполняются одинаково хорошо.

Что еще более важно, мы увидели, что пик точности для многих наборов данных составляет около 20 000 признаков (см. рис. 6 ). Добавление дополнительных функций сверх этого порога дает очень мало, а иногда даже приводит к переоснащению и снижению производительности.

Top 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: Вложения Word

Модели последовательности часто имеют такой слой внедрения в качестве первого слоя. Этот слой учится превращать последовательности индексов слов в векторы встраивания слов в процессе обучения, так что каждый индекс слова сопоставляется с плотным вектором реальных значений, представляющих местоположение этого слова в семантическом пространстве (см. рис. 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 для их представления. Внутри сеть будет использовать горячие векторы для представления этих значений (чтобы избежать вывода о неправильной связи между метками). Это представление зависит от функции потерь и функции активации последнего слоя, которую мы используем в нашей нейронной сети. Мы узнаем больше об этом в следующем разделе.