Когда использовать встраивание слов из популярного словаря FastText, а когда придерживаться векторных представлений TF-IDF, описания с закодированными примерами.

TF-IDF и Word Embedding - два наиболее распространенных метода обработки естественного языка (NLP) для преобразования предложений в машиночитаемый код. В этой статье мы расскажем:

  • Что такое векторы TF-IDF по сравнению с векторами встраивания слов
  • Как применить оба метода к задаче классификации спама
  • Когда лучше использовать встраивание слов

Встраивание слова преобразует слово в n-мерный вектор. Связанные слова, такие как «дом» и «дом», соответствуют одинаковым n-мерным векторам, в то время как разные слова, такие как «дом» и «самолет», имеют разные векторы. Таким образом, «значение» слова может быть отражено в его вложении, модель может использовать эту информацию для изучения взаимосвязи между словами. Преимущество этого метода заключается в том, что модель, обученная слову «дом», сможет отреагировать на слово «дом», даже если она никогда не видела это слово во время обучения.

Мы будем использовать словарь встраивания слов FastText, разработанный исследовательским центром Facebook AI Research. Модель обучается путем попытки угадать пропущенное слово по другим известным словам в предложении. Как и все вложения слов, FastText был обучен с использованием очень большого текстового корпуса, в данном случае Википедии.

К счастью для нас, мы можем применить результаты без необходимости переделывать всю эту тренировку! Просто загрузив справочный словарь wiki-news-300d-1M.vec здесь, который содержит 300-мерные сопоставления 1 миллиона уникальных слов.

Вот как загрузить данные в записную книжку jupyter. Обратите внимание, что в целях экономии памяти мы сохраним только 100 000 наиболее употребительных слов и проигнорируем остальные.

# Loading the data file from local download
path_fastText = 'wiki-news-300d-1M.vec'
dictionary = open(path_fastText, 'r', encoding='utf-8',
                  newline='\n', errors='ignore')
embeds = {}
for line in dictionary:
    tokens = line.rstrip().split(' ')
    embeds[tokens[0]] = [float(x) for x in tokens[1:]]
    
    if len(embeds) == 100000:
        break
print embeds['car']
>> [-0.016, -0.0003, -0.1684, 0.0899, -0.02, -0.0093, 0.0482, -0.0308, -0.0451, 0.0006, 0.168 ... ]

Частота термина - обратная частота документов (TF-IDF) - еще один более распространенный инструмент в НЛП для преобразования списка текстовых документов в матричное представление. Каждый документ преобразуется в строку матрицы TF-IDF, и каждое слово сохраняется в векторе-столбце. Размер словаря (или количество столбцов) - это параметр, который следует указать, часто достаточно словаря из 5 000–10 000 наиболее употребительных слов. Подробнее об очистке текста читайте здесь. TF-IDF - это разреженные векторы, в которых количество ненулевых значений в векторе равно количеству уникальных слов в документе. Таким образом, если документ содержит слово дом, тогда в столбце дома будет стоять единица вместо нуля для этой строки документа.

Во время подбора функция tf-idf обнаруживает наиболее часто встречающиеся слова в корпусе и сохраняет их в словаре. Документ преобразуется путем подсчета количества появлений в нем каждого слова из словаря. Таким образом, матрица tf-idf будет иметь форму [Number_documents, Size_of_vocabulary]. Вес каждого слова нормализуется по количеству раз, которое оно встречается в корпусе, поэтому слову, которое встречается только в 10% всех документов, будет присвоено более высокое значение (и, следовательно, более важно), чем то, которое появляется, скажем, в 90 % документов.

Вкратце:

Из приведенного выше обсуждения видно, что встраивание слов явно несет гораздо больше информации, чем столбец tf-idf, но происходит за счет более интенсивного использования памяти и более сложного применения. Действительно, остается вопрос как применить встраивание слов к полному предложению, а не только к отдельному слову?

Пример прикладного кода

Давайте решим вышеуказанный вопрос на примере, допустим, мы хотим разделить текстовые сообщения на две категории: спам и не спам. Как бы мы использовали для этого два метода?

Для справедливого сравнения двух методов необходимо использовать одну и ту же модель и набор данных. Набор данных, который мы будем использовать, представляет собой набор данных для обнаружения спама, который представляет собой набор из примерно 5000 коротких текстов SMS, помеченных как ветчина или спам. Мы будем использовать модель Sklearn Классификатор линейных опорных векторов.

import pandas as pd
import numpy as np
path_to_text = '..\Datasets\SMS Spam Detection (Kaggle)\spam.csv'
data = pd.read_csv(path_to_text, encoding='latin-1')[['v1', 'v2']]
# Creating the feature set and label set
text = data['v2']
label = data['v1']
print data[10:14]

Создание таблиц функций

Метод встраивания слов:

Чтобы обучить нашу модель, используя полные предложения, а не только одно слово, мы должны найти способ передать в нашу модель несколько слов одновременно. Решение состоит в том, чтобы объединить каждый вектор слов вместе и передать объединенный вектор. Таким образом, объединение 20 слов вместе, где каждое слово является 300-мерным вложением, даст 6000-мерный вектор. Но ждать! Что делать, если не каждый текст состоит ровно из 20 слов? Для случаев, длина которых меньше 20 слов, мы дополняем конец вектора нулями, модель научится не приписывать этим значениям какое-либо значение. Для случаев, длина которых превышает 20 слов, мы будем оставлять только первые 20 слов и отбрасывать остальные.

В качестве полезной уловки воспользуемся функцией text_to_word_sequence из библиотеки предварительной обработки Keras. Эта функция автоматически преобразует строку в список токенов слов и одновременно очищает данные, удаляя знаки препинания и заглавные буквы.

from keras.preprocessing.text import text_to_word_sequence
array_length = 20 * 300
embedding_features = pd.DataFrame()
for document in text:
    # Saving the first 20 words of the document as a sequence
    words = text_to_word_sequence(document)[0:20] 
    
    # Retrieving the vector representation of each word and 
    # appending it to the feature vector 
    feature_vector = []
    for word in words:
        try:
            feature_vector = np.append(feature_vector, 
                                       np.array(embeds[word]))
        except KeyError:
            # In the event that a word is not included in our 
            # dictionary skip that word
            pass
    # If the text has less then 20 words, fill remaining vector with
    # zeros
    zeroes_to_add = array_length - len(feature_vector)
    feature_vector = np.append(feature_vector, 
                               np.zeros(zeroes_to_add)
                               ).reshape((1,-1))
    
    # Append the document feature vector to the feature table
    embedding_features = embedding_features.append( 
                                     pd.DataFrame(feature_vector))
print embedding_features.shape
>> (5572, 6000)

Теперь мы преобразовали наши данные в машиночитаемую таблицу! Как и ожидалось, размер таблицы составляет [количество документов, длина вектора признаков].

Метод TF-IDF:

Создать таблицу функций tf-idf очень просто с помощью sklearn TfidfVectorizer. Мы определяем количество слов, которое хотим сохранить (в данном случае 6000), и подгоняем векторизатор, используя полный корпус текстовых данных. Затем модель преобразует текстовые данные в представление tf-idf.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = list(text)
tfidf = TfidfVectorizer(max_features = 6000) 
tfidf.fit(corpus)
tfidf_features = tfidf.transform(corpus)
print tfidf_features.shape
>> (5572, 6000)

Обучение и тестирование модели

Мы обучим 70% данных, а оставшиеся будем использовать для тестирования. Обе таблицы функций будут обучены на идентичных классификаторах SVM, и результаты будут отображены в таблице.

Во-первых, метки должны быть преобразованы из строк в двоичные с помощью кодировщика меток sklearn.

from sklearn.preprocessing import LabelEncoder
from sklearn.svm import LinearSVC
from sklearn.metrics import precision_recall_fscore_support
# Converting the labels from strings to binary
le = LabelEncoder()
le.fit(label)
label = le.transform(label)

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

# Taking 70/30 train test split
train_percent = 0.7
train_cutoff = int(np.floor(train_percent*len(text) ) )
# Word Embedding
embeded_model = LinearSVC()
embeded_model.fit(embedding_features[0 : train_cutoff], 
                  label[0 : train_cutoff])
embeded_prediction = embeded_model.predict(
                   embedding_features[train_cutoff + 1 : len(text)])
# TF-IDF table
tfidf_model = LinearSVC()
tfidf_model.fit(tfidf_features[0 : train_cutoff], 
                  label[0 : train_cutoff])
tfidf_prediction = tfidf_model.predict(
                  tfidf_features[train_cutoff + 1 : len(text)])

Сравнение результатов.

results = pd.DataFrame(index = ['Word Embedding', 'TF-IDF'], 
          columns = ['Precision', 'Recall', 'F1 score', 'support']
          )
results.loc['Word Embedding'] = precision_recall_fscore_support(
          label[train_cutoff + 1 : len(text)], 
          embeded_prediction, 
          average = 'binary'
          )
results.loc['TF-IDF'] = precision_recall_fscore_support(
          label[train_cutoff + 1 : len(text)], 
          tfidf_prediction, 
          average = 'binary'
          )

Можно видеть, что встраивание слов и TF-IDF имеют оценки точности F1 90,5% и 93,1% соответственно. Возможно, что удивительно, лучшие результаты были получены при использовании более общего метода TF-IDF с коэффициентом примерно 3%. Эти результаты показывают, что выбор более сложных методов не всегда приводит к лучшим результатам, когда дело касается машинного обучения.

Есть несколько причин, чтобы объяснить, почему TF-IDF была лучше:

  1. Метод встраивания Word использовал только первые 20 слов, тогда как метод TF-IDF использовал все доступные слова. Поэтому метод TF-IDF получает больше информации из более длинных документов по сравнению с методом встраивания. (7% от общего количества документов длиннее 20 слов)
  2. Слово "метод встраивания" могло не соответствовать данным. Имея больший словарный запас, метод встраивания, вероятно, назначит правила для слов, которые редко встречаются при обучении. Напротив, у метода TF-IDF был меньший словарный запас, и поэтому правила могли быть сформированы только для слов, которые были замечены во многих обучающих примерах.
  3. Метод встраивания слов содержит гораздо более «шумный» сигнал по сравнению с TF-IDF. Встраивание слов - это гораздо более сложное представление слова и несет в себе гораздо больше скрытой информации. В нашем случае большая часть этой информации не нужна и создает ложные паттерны в нашей модели.

Заключение и дальнейшая работа

В статье есть:

  • Объяснил разницу между использованием встраивания слов и матриц TF-IDF.
  • Продемонстрировал, как использовать встраивание слов FastText в ваших собственных проектах.
  • Продемонстрировал, как преобразовать текст в машиночитаемый формат для использования в машинном обучении двумя разными методами.
  • Сравнили эффективность внедрения Word и TF-IDF в задаче классификации.

Будущая работа:

  • Изменения в дизайне модели или размере словаря могут уменьшить чрезмерную подгонку и улучшить производительность
  • Идентичные словари могут использоваться для обеих таблиц создания функций, что приводит к более прямому сравнению роли, которую встраивание играет в проектах НЛП.