Изучите основы TF-IDF и как реализовать его с нуля на Python.

Введение

Извлечение признаков — важный начальный шаг в НЛП, который включает преобразование текстовых данных в математическое представление, часто в виде векторов, известных как встраивания слов. Существуют различные подходы к встраиванию слов, начиная от классических подходов, таких как word2vec и GloVe, до более современных, таких как встраивания BERT. Хотя сегодня в области НЛП преобладают встраивания на основе трансформеров, понимание эволюции предыдущих подходов может быть полезным.

В этой статье мы рассмотрим более традиционный подход к извлечению признаков, известный как TF-IDF, основанный на статистическом анализе. Мы углубимся в детали TF-IDF и его реализации, а также предоставим бонусное приложение, которое поможет укрепить ваше понимание. Итак, оставайтесь с нами до конца, пока мы раскрываем все тонкости TF-IDF!

Что такое TF-IDF?

TF-IDF, сокращенно от термина частота, обратная частоте документа, является широко используемой техникой НЛП для определения значимости слов в документе или корпусе. Чтобы дать некоторый фоновый контекст, опрос, проведенный в 2015 году, показал, что 83% текстовых рекомендательных систем в цифровых библиотеках используют TF-IDF для извлечения текстовых функций. Вот такая популярная техника. По сути, он измеряет важность слова, сравнивая его частоту в конкретном документе с частотой его частоты во всем корпусе. Основное предположение состоит в том, что слово, которое чаще встречается в документе, но редко встречается в корпусе, особенно важно в этом документе.

Теперь давайте посмотрим на математическую формулу для расчета TF-IDF:

TF (частота терминов) определяется путем вычисления частоты слова в документе и деления ее на общее количество слов в документе.

TF = (Number of times the word appears in the document) / (Total number of words in the document)

IDF (обратная частота документа), с другой стороны, измеряет важность слова в корпусе в целом. Он рассчитывается как:

IDF = log((Total number of documents in the corpus) / (Number of documents containing the word))

Наконец, оценка TF-IDF для слова в данном документе является произведением оценок TF и ​​IDF слова. Чем выше итоговый балл TF-IDF, тем более значимым считается слово в контексте этого документа по сравнению с другими словами.

Реализация TF-IDF в Python

Теперь, когда мы рассмотрели математический расчет TF-IDF, давайте реализуем его с помощью Python. Несмотря на то, что существуют библиотеки для более быстрого вычисления функций TF-IDF, в этой статье основное внимание будет уделено ее созданию с нуля.

Настройка и предварительная обработка

Для начала импортируем необходимые пакеты, которые нам понадобятся в дальнейшем, например, класс Счетчик из модуля коллекции.

import re
import math
from collections import Counter
import numpy as np

Далее мы определим список документов/корпусов, которые будем использовать в качестве примера. Возьмем недавнее увлечение ChatGPT и генеративным ИИ.

docs = ["ChatGPT is a AI chatbot developed by OpenAI.",
        "ChatGPT is built on top of the GPT family of large language models.",
        "Generative AI is rising in popularity and has started to transform businesses in various ways possible."]

Перед вычислением текстовых характеристик стандартной практикой является предварительная обработка документов в любой задаче НЛП, такой как строчные буквы, лемматизация, выделение корней, удаление стоп-слов и т. д. В этом примере мы приведем документы к нижнему регистру и удалим все знаки препинания. Однако в зависимости от поставленной задачи можно выполнить дополнительную предварительную обработку, и эти шаги можно выполнить с использованием библиотек НЛП, таких как NLTK или SpaCy. Мы также будем отслеживать уникальный набор токенов в корпусе.

p_docs = []
tok_set = []
for doc in docs:
    p_doc = re.sub(r'[^\w\s]', '', doc.lower())
    p_docs.append(p_doc)
    tok_set.extend(p_doc.split())

tok_set = set(tok_set)
print(p_docs)
print(tok_set)

Расчет IDF уникальных токенов в корпусе

После того, как у нас есть набор токенов, мы можем вычислить IDF каждого токена в корпусе, используя приведенную выше формулу.

def calculate_idf(p_docs, tok_set):
    idf = {}
    for tok in tok_set:
        N = len(p_docs)
        df = 0
        for doc in p_docs:
            if tok in doc.split():
                df += 1
        idf[tok] = math.log(N/df)
    return idf

idf = calculate_idf(p_docs, tok_set)
print(idf)

Обратите внимание, что такие слова, как «openai» и «gpt», имеют более высокий IDF, чем такие слова, как «chatgpt» или «ai», поскольку первые термины встречаются в корпусе реже.

Расчет TF каждого токена для каждого документа

В то время как IDF рассчитывается для каждого токена во всем корпусе, TF рассчитывается для каждого токена отдельно для каждого документа. Используя формулу для TF, мы можем быстро получить количество токенов в документе и вычислить их относительную частоту, используя класс Counter.

def calculate_tf(tok, p_doc):
    toks = p_doc.split()
    tok_freq = Counter(toks)
    if tok in tok_freq:
        return tok_freq[tok]/len(toks)
    return 0

Вот пример частоты использования термина «chatgpt» в каждом документе.

print(calculate_tf("chatgpt", p_docs[0]))
print(calculate_tf("chatgpt", p_docs[1]))
print(calculate_tf("chatgpt", p_docs[2]))

Наконец, на TF-IDF

Теперь пришло время реализовать функцию TF-IDF. Сначала мы можем обернуть предыдущий код предварительной обработки в функцию, чтобы мы могли вызывать ее при вычислении TF-IDF.

def prepare_docs(docs):
    p_docs = []
    tok_set = []
    for doc in docs:
        p_doc = re.sub(r'[^\w\s]', '', doc.lower())
        p_docs.append(p_doc)
        tok_set.extend(p_doc.split())

    tok_set = set(tok_set)
    return p_docs, tok_set

def tf_idf(tok, docs):
    p_docs, tok_set = prepare_docs(docs)
    print(f"calculating tf-idf for {tok} in all docs...")
    idf_dict = calculate_idf(p_docs, tok_set)
    idf = idf_dict[tok] if tok in idf_dict else 0.0
    print(f"idf for {tok}: {round(idf, 4)}")
    for i, doc in enumerate(p_docs):
        tf = calculate_tf(tok, doc)
        tf_idf = tf * idf
        print(f"Doc {i+1}: {doc}, tf: {round(tf, 4)}, tf-idf: {round(tf_idf, 4)}")

Давайте попробуем несколько примеров.

print(tf_idf("chatgpt", docs))

print(tf_idf("gpt", docs))

print(tf_idf("generative", docs))

print(tf_idf("is", docs))

print(tf_idf("gpt4", docs))

Из приведенных выше примеров мы можем видеть, что у «chatgpt» более высокий TF-IDF в документах 1 и 2, чем в документе 3, поскольку этот термин не встречается в документе 3. Хотя «chatgpt» встречается в документах 1 и 2 только один раз, первый имеет немного более высокий TF, поскольку у него меньше токенов, что приводит к более высокому TF-IDF.

TF-IDF «gpt» равен 0 в документах 1 и 3, так как ни один из них не содержит слова. «gpt» в документе 2 присутствует; однако TF-IDF выше, чем TF-IDF «chatgpt» в документе 1, поскольку его редкое появление в корпусе перевешивает большую длину второго документа.

«генеративный» имеет TF-IDF 0 в документах 1 и 2 из-за его отсутствия. «is» имеет 0 TF-IDF для всех документов, поскольку он присутствует во всех них. Токены вне словаря, такие как «gpt4», также имеют TF-IDF, равный 0.

И вот вы знаете, как реализовать TF-IDF в Python.

Бонус: создание поисковой системы MVP с TF-IDF

У TF-IDF много потенциальных применений, и одно из них — создание поисковой системы. В этом разделе мы рассмотрим, как мы можем использовать TF-IDF для создания простой MVP поисковой системы. Подход, который мы выберем, заключается в ранжировании документов по запросу по сумме их терминов TF-IDF, при этом более высокая сумма приводит к более высокому ранжированию документа.

Для начала давайте изменим функцию tf_idf, чтобы добавить результаты TF-IDF в список и установить TF-IDF слов, не входящих в словарь, равным 0.

def tf_idf(tok, docs):
    p_docs, tok_set = prepare_docs(docs)
    idf_dict = calculate_idf(p_docs, tok_set)
    idf = idf_dict[tok] if tok in idf_dict else 0
    tf_idfs = []
    for i, doc in enumerate(p_docs):
        tf = calculate_tf(tok, doc)
        tf_idf = tf * idf
        tf_idfs.append(tf_idf)
    return tf_idfs

С этой модификацией мы теперь можем реализовать поисковую систему. Приведенный ниже код вычисляет значения TF-IDF для каждого термина запроса по корпусу и ранжирует документы в порядке их суммирования.

def search_query(query, docs):
    print(f"searching for: {query}")
    terms = query.lower().split()
    score = 0
    tf_idfs = []
    for tok in terms:
        tf_idfs.append(tf_idf(tok, docs))
    tf_idfs = np.array(tf_idfs)
    print(tf_idfs)
    doc_scores = np.sum(tf_idfs, axis=0) # summation of tf_idfs of all query terms for each doc
    print(doc_scores)
    rank_doc = np.argsort(doc_scores)[::-1]
    print("docs ranked in order of relevance:")
    for i in rank_doc:
        print(f"Doc {i+1}: {docs[i]}, score: {doc_scores[i]}")

Давайте попробуем найти запрос «ChatGPT AI»:

print(search_query("ChatGPT AI", docs))

Мы видим, что «ChatGPT AI» больше всего подходит для документа 1 из-за его самого высокого TF-IDF. Это имеет смысл, поскольку «ChatGPT» и «AI» включены в документ, в то время как каждый из двух других документов содержит только один из терминов.

Что произойдет, если мы попробуем «языковые модели ИИ»?

print(search_query("AI language models", docs))

Документ 2 имеет наивысший рейтинг, так как он содержит два токена «язык» и «модели», а два других содержат только «ИИ». В этом случае документ 1 имеет более высокий рейтинг, чем 3, поскольку он короче, что приводит к несколько более высокой частоте терминов.

И вот он, предельно простой поисковик на основе TF-IDF!

Заключение

В этой статье мы узнали о TF-IDF, что это такое, как работает и, самое главное, зачем он нужен в современных NLP-приложениях. Мы также реализовали TF-IDF и показали, что можем построить из него даже минималистичный поисковик. Если вы не хотите реализовывать его с нуля, вы можете использовать sklearn.feature_extraction.text.TfidfVectorizer, чтобы реализовать его быстрее. Вот две замечательные статьи в блоге, в которых рассказывается, как его использовать: 1 и 2.

TF-IDF — не единственный метод извлечения признаков для текста. Есть много других методов, которые могут работать лучше, чем он, и стоит разобраться в них. Тем не менее, это по-прежнему популярный подход к работе с текстовыми функциями, и в будущем мы, возможно, рассмотрим более современные.

Спасибо за прочтение. Если это поможет, не стесняйтесь следовать за мной и подписаться на новые статьи.

Надеюсь, вы прекрасно провели время.

Ваше здоровье.

Рекомендации

[1] Миколов, Томас и др. «Распределенные представления слов и фраз и их композиционность». Достижения в области нейронных систем обработки информации 26 (2013 г.).

[2] Пеннингтон, Джеффри, Ричард Сочер и Кристофер Д. Мэннинг. «Перчатка: глобальные векторы для представления слов». Материалы конференции 2014 года по эмпирическим методам обработки естественного языка (EMNLP). 2014.

[3] Девлин, Джейкоб и др. «Берт: предварительная подготовка глубоких двунаправленных преобразователей для понимания языка». препринт arXiv arXiv:1810.04805 (2018 г.).

[4] Васвани, Ашиш и др. «Внимание — это все, что вам нужно». Достижения в области нейронных систем обработки информации 30 (2017 г.).

[5] Бил, Джоран и др. «Бумажные рекомендательные системы: обзор литературы». Международный журнал цифровых библиотек 17 (2016): 305–338.

[6] Как Tfidfvectorizer от sklearn вычисляет значения tf-idf, Analytics Vidhya, https://www.analyticsvidhya.com/blog/2021/11/how-sklearns-tfidfvectorizer-calculates-tf-idf-values/

[7] Векторизация текста с использованием Python: TF-IDF, Окан Булут, https://okan.cloud/posts/2022-01-16-text-vectorization-using-python-tf-idf/

[8] Понимание TF-IDF для машинного обучения, Capital One, https://www.capitalone.com/tech/machine-learning/understanding-tf-idf/

[9] Понимание TF-ID: простое введение, MonkeyLearn, https://monkeylearn.com/blog/what-is-tf-idf/