Рубрики
Без рубрики

Python для НЛП: Создание модели TF-IDF с нуля

Автор оригинала: Usman Malik.

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.