Python для НЛП: Создание модели TF-IDF с нуля
Это 14 – я статья в моей серии статей по Python для НЛП. В моей предыдущей статье я объяснил , как преобразовать предложения в числовые векторы, используя подход “мешок слов”. Чтобы лучше понять подход “мешок слов”, мы внедрили
В этой статье мы будем опираться на концепцию, которую мы изучили в предыдущей статье, и реализуем схему TF-IDF с нуля в Python. Термин TF расшифровывается как “частота термина”, в то время как термин IDF
Проблема с моделью мешка слов
Прежде чем мы действительно увидим модель TF-IDF, давайте сначала обсудим несколько проблем, связанных с моделью мешка слов.
В предыдущей статье у нас были следующие три примера предложений:
- В предыдущей статье у нас были следующие три примера предложений:
- – Вы вышли на улицу поиграть в теннис?”
- – Мы с Джоном играем в теннис.”
– Мы с Джоном играем в теннис.”
1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | Получившаяся модель мешка слов выглядела так: |
0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | Предложение 1 |
1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | Предложение 3 |
Одна из главных проблем, связанных с моделью мешка слов, заключается в том, что она придает одинаковое значение словам, независимо от их важности. Например, слово “игра” появляется во всех трех предложениях, поэтому это слово очень распространено, с другой стороны, слово “футбол” появляется только в одном предложении. Редкие слова обладают большей классификационной силой по сравнению с обычными.
Идея, лежащая в основе подхода TF-IDF, заключается в том, что слова, которые более распространены в одном предложении и менее распространены в других предложениях, должны быть даны
Теория, лежащая в основе TF-IDF
Прежде чем реализовать схему TF-IDF в Python, давайте сначала изучим теорию. В нашем примере мы будем использовать те же три предложения, что и в модели “мешок слов”.
- “Я люблю играть в футбол”
- – Вы вышли на улицу поиграть в теннис?”
- – Вы вышли на улицу поиграть в теннис?”
– Мы с Джоном играем в теннис.”
Как и мешок слов, первым шагом к реализации модели TF-IDF является токенизация.
I | Сделал | Джон |
Как и мешок слов, первым шагом к реализации модели TF-IDF является токенизация. | ты | Джон |
любить | идти | I |
играть | снаружи | играть |
играть | к | теннис |
к | ||
теннис |
Шаг 2: Найдите значения TF-IDF
После того как вы маркировали предложения, следующий шаг-найти значение TF-IDF для каждого слова в предложении.
Как обсуждалось ранее, значение TF относится к частоте термина и может быть вычислено следующим образом:
TF = (Frequency of the word in the sentence) / (Total number of words in the sentence)
Например, посмотрите на слово “игра” в первом предложении. Его частота термина будет равна 0,20, так как слово “игра” встречается в предложении только один раз, а общее количество слов в предложении равно 5, следовательно, 20.
IDF относится к обратной частоте документов и может быть рассчитана следующим образом:
IDF: (Total number of sentences (documents))/(Number of sentences (documents) containing the word)
Важно отметить, что значение IDF для слова остается одинаковым во всех документах, поскольку оно зависит от общего количества документов. С другой стороны, значения TF слова отличаются от документа к документу.
Давайте найдем частоту IDF слова “играть”. Поскольку у нас есть три документа и слово “игра” встречается во всех трех из них, следовательно, значение IDF слова “игра” равно.
Наконец, значения TF-IDF вычисляются путем умножения значений TF на соответствующие им значения IDF.
Чтобы найти значение TF-IDF, нам сначала нужно создать словарь частот слов, как показано ниже:
I | 2 |
любить | 1 |
к | 2 |
играть | 3 |
футбол | 1 |
Сделал | 1 |
ты | 1 |
идти | 1 |
снаружи | 1 |
теннис | 2 |
Джон | 1 |
и | 1 |
Далее давайте отсортируем словарь в порядке убывания частоты, как показано в следующей таблице.
играть | 3 |
теннис | 2 |
к | 2 |
I | 2 |
футбол | 1 |
Сделал | 1 |
ты | 1 |
идти | 1 |
снаружи | 1 |
любить | 1 |
Джон | 1 |
и | 1 |
Наконец, мы отфильтруем 8 наиболее часто встречающихся слов.
Как я уже говорил ранее, поскольку значения IDF вычисляются с использованием всего корпуса. Теперь мы можем вычислить значение IDF для каждого слова. Следующая таблица содержит значения IDF для каждой таблицы.
играть | 3 | |
.5 | теннис | 2 |
.5 | к | 2 |
.5 | I | 2 |
футбол | 1 | |
Сделал | 1 | |
ты | 1 | |
идти | 1 |
Вы можете ясно видеть, что редкие слова имеют более высокие значения IDF по сравнению со словами, которые являются более распространенными.
Теперь давайте найдем значения TF-IDF для всех слов в каждом предложении.
играть | 0.20 x.20 | 0,14 x.14 | 0.20 x.20 |
теннис | 0 x | 0.14 x.21 | 0.20 x.30 |
к | 0.20 x.30 | 0.14 x.21 | 0 x |
I | 0.20 x.30 | 0.14 x.21 | 0.20 x.30 |
футбол | 0.20 x.6 | 0 x | 0 x |
сделал | 0 x | 0.14 x.42 | 0 x |
ты | 0 | 0.14 x.42 | 0 x |
идти | 0x | 0.14 x.42 | 0 x |
Значения в столбцах для предложений 1, 2 и 3 являются соответствующими векторами TF-IDF для каждого слова в соответствующих предложениях.
Значения в столбцах для предложений 1, 2 и 3 являются соответствующими векторами TF-IDF для каждого слова в соответствующих предложениях.
Важно отметить, что для смягчения влияния очень редких и очень распространенных слов на корпус можно вычислить логарифм значения IDF, прежде чем умножать его на значение TF-IDF. В таком случае формула IDF становится:
IDF: log((Total number of sentences (documents))/(Number of sentences (documents) containing the word))
Однако, поскольку в нашем корпусе было всего три предложения, мы для простоты не использовали лог. В разделе реализация мы будем использовать функцию log для вычисления конечного значения TF-IDF.
Модель TF-IDF с нуля в Python
Как объяснено в разделе теории, шаги по созданию отсортированного словаря частот слов аналогичны между bag of words и моделью TF-IDF. Чтобы понять, как мы создаем сортированный словарь частот слов, пожалуйста, обратитесь к моей последней статье . Здесь я просто напишу код. Модель TF-IDF будет построена на этом коде.
# -*- coding: utf-8 -*- """ Created on Sat Jul 6 14:21:00 2019 @author: usman """ import nltk import numpy as np import random import string import bs4 as bs import urllib.request import re raw_html = urllib.request.urlopen('https://en.wikipedia.org/wiki/Natural_language_processing') raw_html = raw_html.read() article_html = bs.BeautifulSoup(raw_html, 'lxml') article_paragraphs = article_html.find_all('p') article_text = '' for para in article_paragraphs: article_text += para.text corpus = nltk.sent_tokenize(article_text) for i in range(len(corpus )): corpus [i] = corpus [i].lower() corpus [i] = re.sub(r'\W',' ',corpus [i]) corpus [i] = re.sub(r'\s+',' ',corpus [i]) wordfreq = {} for sentence in corpus: tokens = nltk.word_tokenize(sentence) for token in tokens: if token not in wordfreq.keys(): wordfreq[token] = 1 else: wordfreq[token] += 1 import heapq most_freq = heapq.nlargest(200, wordfreq, key=wordfreq.get)
В приведенном выше сценарии мы сначала соскребаем статью Википедии о Обработке естественного языка . Затем мы предварительно обрабатываем его, чтобы удалить все специальные символы и несколько пустых пробелов. Наконец, мы создаем словарь частот слов
Следующим шагом является поиск значений IDF для наиболее часто встречающихся слов в корпусе. Это делает следующий сценарий:
word_idf_values = {} for token in most_freq: doc_containing_word = 0 for document in corpus: if token in nltk.word_tokenize(document): doc_containing_word += 1 word_idf_values[token] = np.log(len(corpus)/(1 + doc_containing_word))
В приведенном выше скрипте мы создаем пустой словарь word_idf_values
. Этот словарь будет хранить наиболее часто встречающиеся слова в виде ключей, а соответствующие им значения IDF-в виде словарных значений. Далее мы повторяем список наиболее часто встречающихся слов. Во время каждой итерации мы создаем переменную doc_containing_word
. Эта переменная будет хранить количество документов, в которых появляется слово. Далее мы перебираем все предложения в нашем корпусе. Предложение маркируется, а затем мы проверяем, существует ли слово в предложении или нет, если слово существует, мы увеличиваем переменную doc_containing_word
. Наконец, чтобы вычислить значение IDF, мы делим общее количество предложений на общее количество документов, содержащих это слово.
Следующим шагом является создание словаря TF для каждого слова. В словаре TF ключом будут наиболее часто встречающиеся слова, а значениями-49 размерных векторов, поскольку наш документ содержит 49 предложений. Каждое значение в векторе будет принадлежать значению TF слова для соответствующего предложения. Посмотрите на следующий сценарий:
word_tf_values = {} for token in most_freq: sent_tf_vector = [] for document in corpus: doc_freq = 0 for word in nltk.word_tokenize(document): if token == word: doc_freq += 1 word_tf = doc_freq/len(nltk.word_tokenize(document)) sent_tf_vector.append(word_tf) word_tf_values[token] = sent_tf_vector
В приведенном выше сценарии мы создаем словарь, который содержит слово в качестве ключа и список из 49 элементов в качестве значения, поскольку у нас есть 49 предложений в нашем корпусе. Каждый элемент в списке хранит значение TF слова для соответствующего предложения. В приведенном выше скрипте word_tf_values
находится наш словарь. Для каждого слова мы создаем список sent_tf_vector
.
Затем мы перебираем каждое предложение в корпусе и маркируем его. Слово из внешнего цикла сопоставляется с каждым словом в предложении. Если совпадение найдено, переменная doc_freq
увеличивается на 1. Как только все слова в предложении повторяются, doc_freq
делится на общую длину предложения, чтобы найти значение TF слова для этого предложения. Этот процесс повторяется для всех слов в списке наиболее часто встречающихся слов. Окончательный словарь word_tf_values
будет содержать 200 слов в качестве ключей. Для каждого слова будет список из 49 элементов в качестве значения.
Если вы посмотрите на словарь word_tf_values
, то он выглядит следующим образом:
Вы можете видеть, что слово
является ключом, тогда как список из 49 элементов является значением для каждого ключа.
Теперь у нас есть значения IDF всех слов, а также значения TF каждого слова в предложениях. Следующий шаг-просто умножить значения IDF на значения TF.
tfidf_values = [] for token in word_tf_values.keys(): tfidf_sentences = [] for tf_sentence in word_tf_values[token]: tf_idf_score = tf_sentence * word_idf_values[token] tfidf_sentences.append(tf_idf_score) tfidf_values.append(tfidf_sentences)
В приведенном выше скрипте мы создаем список с именем tfidf_values
. Затем мы перебрали все ключи в словаре word_tf_values
. Эти ключи в основном являются наиболее часто встречающимися словами. Используя эти слова, мы получаем 49-мерный список, содержащий значения TF для слова, соответствующего каждому предложению. Затем значение TF умножается на значение IDF слова и сохраняется в переменной tf_idf_score
. Затем переменная добавляется в список tf_idf_sentences
. Наконец, список tf_idf_sentences
добавляется к списку tfidf_values
.
Теперь на данный момент времени tfidf_values
представляет собой список списков. Где каждый элемент представляет собой 49-мерный список, содержащий значения TFIDF конкретного слова для всех предложений. Нам нужно преобразовать двумерный список в массив numpy. Посмотрите на следующий сценарий:
tf_idf_model = np.asarray(tfidf_values)
Теперь наш массив numpy выглядит следующим образом:
Однако есть еще одна проблема с этой моделью TF-IDF. Размер массива равен 200 x 49, что означает, что каждый столбец представляет вектор TF-IDF для соответствующего предложения. Мы хотим, чтобы строки представляли векторы TF-IDF. Мы можем сделать это, просто транспонировав наш массив numpy следующим образом:
tf_idf_model = np.transpose(tf_idf_model)
Теперь у нас есть 49 x 200-мерный массив numpy, где строки соответствуют векторам TF-IDF, как показано ниже:
Вывод
Модель TF-IDF является одной из наиболее широко используемых моделей преобразования текста в цифры. В этой статье мы кратко рассмотрели теорию, лежащую в основе модели TF-IDF. Наконец, мы реализовали модель TF-IDF с нуля в Python. В следующей статье мы увидим, как реализовать модель N-Gram с нуля в Python.