Здесь я расскажу и покажу в примерах на Python, зачем и как считать стандартный TF-IDF, а также его вариации. Примеры я буду давать по ходу объяснения. Чтобы их понять, нужно иметь представления о базовых понятиях языка программирования Python в версии 2.х либо 3.х (основные типы данных, основные структуры данных, цикл-ветвление, функция, основы генераторов списков и словарей) и понимать, как применять класс Counter из стандартной библиотеки collections
Что это вообще такое?
О том, что же такое TF-IDF, можно прочитать, например, в Википедии. Да и формулу там же можно посмотреть, поэтому теории я коснусь по минимуму, показав больше примеров.
Если кратко, то TF-IDF это term frequency-inverse document frequency или, ежели на великом и могучем, частотность терминов-обратная частотность документов. Впрочем, это я зря, непереведенное смотрелось лучше.
Зачем это нужно?
Это простой и удобный способ оценить важность термина для какого-либо документа относительно всех остальных документов. Принцип такой — если слово встречается в каком-либо документе часто, при этом встречаясь редко во всех остальных документах — это слово имеет большую значимость для того самого документа.
Чем хороша эта метрика?
1. Слова, неважные для вообще всех документов, например, предлоги или междометия — получат очень низкий вес TF-IDF (потому что часто встречаются во всех-всех документах), а важные — высокий.
2. Её просто считать
Для чего же можно применять эту метрику?
1. Выявление важных слов и стоп-слов в документах
2. Некоторые расширения этой формулы можно использовать для улучшения работы классификаторов тональности
Принцип работы
Term Frequency
TF — это частотность термина, которая измеряет, насколько часто термин встречается в документе. Логично предположить, что в длинных документах термин может встретиться в больших количествах, чем в коротких, поэтому абсолютные числа тут не катят. Поэтому применяют относительные — делят количество раз, когда нужный термин встретился в тексте, на общее количество слов в тексте.
То есть:
TF термина а = (Количество раз, когда термин а встретился в тексте / количество всех слов в тексте)
В виде кода это будет выглядеть так:
import collections def compute_tf(text): #На вход берем текст в виде списка (list) слов #Считаем частотность всех терминов во входном массиве с помощью #метода Counter библиотеки collections tf_text = collections.Counter(text) for i in tf_text: #для каждого слова в tf_text считаем TF путём деления #встречаемости слова на общее количество слов в тексте tf_text[i] = tf_text[i]/float(len(text)) #возвращаем объект типа Counter c TF всех слов текста return tf_text text = ['hasta', 'la', 'vista', 'baby', 'la', 'vista', 'la'] print compute_tf(text) Out: Counter({'la': 0.42857142857142855, 'vista': 0.2857142857142857, 'hasta': 0.14285714285714285, 'baby': 0.14285714285714285})
Inverse Document Frequency
IDF — это обратная частотность документов. Она измеряет непосредственно важность термина. То есть, когда мы считали TF, все термины считаются как бы равными по важности друг другу. Но всем известно, что, например, предлоги встречаются очень часто, хотя практически не влияют на смысл текста. И что с этим поделать? Ответ прост — посчитать IDF. Он считается как логарифм от общего количества документов, делённого на количество документов, в которых встречается термин а.
То есть:
IDF термина а = логарифм(Общее количество документов / Количество документов, в которых встречается термин а)
Логарифм, кстати, можно брать любой — потому что TF-IDF является относительной мерой; то есть веса терминов не выражаются в каких-то единицах, а существуют друг относительно друга. Я, например, обычно беру натуральный или десятичный.
В виде кода это будет выглядеть так:
import math def compute_idf(word, corpus): #на вход берется слово, для которого считаем IDF #и корпус документов в виде списка списков слов #количество документов, где встречается искомый термин #считается как генератор списков return math.log10(len(corpus)/sum([1.0 for i in corpus if word in i])) texts = [['pasta', 'la', 'vista', 'baby', 'la', 'vista'], ['hasta', 'siempre', 'comandante', 'baby', 'la', 'siempre'], ['siempre', 'comandante', 'baby', 'la', 'siempre']] print compute_idf('pasta', texts) Out: 0.47712125472
Term Frequency — Inverse Document Frequency
А затем мы умножаем TF на IDF и получаем — барабанная дробь — TF-IDF!
TF-IDF термина а = (TF термина а) * (IDF термина а)
В виде кода это будет выглядеть так:
Мы обходим каждый текст в корпусе текстов, считаем для него TF всех слов, находящихся в нем. Затем для каждого слова считаем IDF и умножаем его на уже посчитанный TF. Полученный словарь (dictionary) мы добавляем в список, чтобы сохранить такой же порядок текстов, какой был на входе. И возвращаем этот список словарей с посчитанными TF-IDF для каждого термина.
from collections import Counter
import math
def compute_tfidf(corpus):
def compute_tf(text):
tf_text = Counter(text)
for i in tf_text:
tf_text[i] = tf_text[i]/float(len(text))
return tf_text
def compute_idf(word, corpus):
return math.log10(len(corpus)/sum([1.0 for i in corpus if word in i]))
documents_list = []
for text in corpus:
tf_idf_dictionary = {}
computed_tf = compute_tf(text)
for word in computed_tf:
tf_idf_dictionary[word] = computed_tf[word] * compute_idf(word, corpus)
documents_list.append(tf_idf_dictionary)
return documents_list
corpus = [[‘pasta’, ‘la’, ‘vista’, ‘baby’, ‘la’, ‘vista’],
[‘hasta’, ‘siempre’, ‘comandante’, ‘baby’, ‘la’, ‘siempre’],
[‘siempre’, ‘comandante’, ‘baby’, ‘la’, ‘siempre’]]
print compute_tfidf(corpus)
Out: [{‘pasta’: 0.11928031367991561, ‘baby’: 0.0, ‘vista’: 0.23856062735983122, ‘la’: 0.0},
{‘hasta’: 0.09542425094393249, ‘comandante’: 0.03521825181113625, ‘siempre’: 0.0704365036222725,
‘baby’: 0.0, ‘la’: 0.0},
{‘comandante’: 0.04402281476392031, ‘baby’: 0.0, ‘siempre’: 0.08804562952784062, ‘la’: 0.0}]
Умозрительный пример (в котором мы применяем десятичный логарифм)
В некотором документе Х, содержащем 100 слов, есть слово «лингвист», которое встречается 5 раз. Таким образом, TF слова «лингвист» равняется 5 / 100 или 0.05. А теперь представим, что всего у нас есть 1000 документов (включая документ Х), и слово «лингвист» встречается в 10 из них. Таким образом, IDF слова «лингвист» равняется lg(1000/10) или 2. Таким образом, TF-IDF слова «лингвист» равняется 2 * 0.05 или 0.1
Естественно, вместо документов у нас легко могут быть какие-нибудь абстрактные категории или конкретные ящики, а вместо слов — абстрактные объекты или конкретные фрукты: TF-IDF — вполне универсальная метрика для измерения важности.
Некоторые расширения TF-IDF
В некоторых случаях TF-IDF может считаться по-иному (если вдруг не нравится работа стандартного алгоритма — можно и поэкспериментировать!)
Расширения TF
1. Можно считать TF бинарно, т.е. просто проверять наличие термина в тексте и присваивать единицу, если термин в тексте есть.
def compute_tf(text): tf_text = {i: 1 for i in text} return tf_text text = ['hasta', 'la', 'vista', 'baby', 'la', 'vista'] print compute_tf(text) Out: {'hasta': 1, 'baby': 1, 'vista': 1, 'la': 1}
2.Можно считать TF без нормализации, то есть брать просто частоту встречаемости слова в тексте без деления этой частоты на общее количество слов в тексте
from collections import Counter def compute_tf(text): tf_text = Counter(text) return tf_text text = ['hasta', 'la', 'vista', 'baby', 'la', 'vista'] print compute_tf(text) Out: Counter({'vista': 2, 'la': 2, 'hasta': 1, 'baby': 1})
3. Можно нормализовать частоту встречаемости слова с помощью логарифма таким образом, что
TF термина а = 1 + логарифм(Количество раз, когда термин а встретился в тексте)
.
from collections import Counter import math def compute_tf(text): tf_text = Counter(text) for i in tf_text: tf_text[i] = 1 + math.log10(tf_text[i]) return tf_text text = ['hasta', 'la', 'vista', 'baby', 'la', 'vista'] print compute_tf(text) Out: Counter({'vista': 1.3010299956639813, 'la': 1.3010299956639813, 'hasta': 1.0, 'baby': 1.0})
4. Можно проводить двойную нормализацию частоты встречаемости слова таким образом, что
TF термина а = 0.5 + 0.5 * (Количество раз, когда термин а встретился в тексте / Количество раз, когда встретился самый частотный термин этого текста)
.
from collections import Counter def compute_tf(text): tf_text = Counter(text) for i in tf_text: tf_text[i] = 0.5 + (0.5 * (tf_text[i]/tf_text.most_common(1)[0][1])) return tf_text text = ['hasta', 'la', 'vista', 'baby', 'la', 'vista'] print compute_tf(text) Out: Counter({'vista': 1.0, 'la': 1.0, 'hasta': 0.5, 'baby': 0.5})
Этот вариант полезен для измерения частотности терминов в корпусе текстов, где очень большие тексты соседствуют с очень маленькими — данный вид измерения TF нивелирует возможную ошибку, вызванную влиянием размера текста.
Расширения IDF
1. Можно вообще его не учитывать — считать всегда единицей и всё.
2. Можно произвести смягчение (smoothing) IDF — считать как в стандартном виде, только добавлять единицу в скобках, то есть
IDF термина а = логарифм(1 + (Общее количество документов / Количество документов, в которых встречается термин а))
.
import math def compute_idf(word, corpus): return math.log10(1 + (len(texts))/float(sum([1 for i in corpus if word in i]))) texts = [['pasta', 'la', 'vista', 'baby', 'la', 'vista'], ['hasta', 'siempre', 'comandante', 'baby', 'la', 'siempre'], ['siempre', 'comandante', 'baby', 'la', 'siempre']] print compute_idf('pasta', texts) Out: 0.602059991328
3. Можно извратиться сильнее и считать IDF так:
IDF термина а = логарифм(1 + (Количество раз, когда встретился самый частотный термин среди тех документов, в которых встречается термин а / Количество документов, в которых встречается термин а))
.
from collections import Counter import math def compute_idf(word, corpus): data = [Counter(i) for i in corpus if word in i] final_counter = Counter() for i in data: final_counter += i most_common_word = final_counter.most_common(1)[0][1] return math.log10(1 + (most_common_word/float(sum([1 for i in corpus if word in i])))) texts = [['pasta', 'la', 'vista', 'baby', 'la', 'vista'], ['hasta', 'siempre', 'comandante', 'baby', 'la', 'siempre'], ['siempre', 'comandante', 'baby', 'la', 'siempre']] print compute_idf('pasta', texts) Out: 0.47712125472
Этот вариант расчёта IDF призван снизить влияние размера документов на получающийся IDF терминов.
На практике эти расширения используют нечасто, особенно те, которые для расчёта IDF. Чаще всего используют второй, третий и четвертый варианты расчета TF со стандартным способом расчёта IDF.
В следующей статье я планирую коснуться интересных расширений стандартного алгоритма TF-IDF, который можно (а иногда даже и нужно) использовать для классификации тональности
Полезные ссылки
Очень понятная теория TF-IDF (англ)
Еще один хороший туториал с примерами (англ)
84,464 просмотров всего, 1 просмотров сегодня
Добрый день! У меня в учебной задачке 🙂 надо использовать метрику tf-idf , чтобы понизить вес спама, т.е. документ, в котором какое-то слово намеренно слишком часто повторяется, не должен взлетать в поисковой выдаче. Но вроде здесь именно так и получается. Большой tf — большой tf-idf, документ на первых позициях. Не направите, куда думать?
Отличное, четкое объяснение!) Спасибо большое, мне помогло
Спасибо большое, Мария!
Очень приятно осознавать, что такой давнишний пост до сих пор кому-то помогает 🙂
А не проще использовать кейколлектор или вордстат? зачем таким геморроем заниматься?
TF-IDF как алгоритм — очень полезная штука, которую можно применять в куче разных вещей. И в этом тексте раскрывается максимально просто именно суть алгоритма, как он в принципе работает, а не конкретная реализация или вариации на тему, которых, само собой — стопицот штук.
Доброго времени суток, прошло уже около 4 лет с момента публикации, но возможно автор увидит этот коммент), мне кажется, что в определении IDF ошибка — «IDF — это обратная частотность документов. Она измеряет непосредственно важность термина.» но ведь TF отвечает за важность термина, а IDF нужен для того, чтобы уменьшать важность всяких предлогов и т.п.
Или не так?
Привет!
Автор увидит этот коммент))
Насколько я помню, TF — это не важность термина, а просто его частота. Большой TF — много этого термина, маленький — мало.
При этом IDF — это важность термина среди всех документов, т.е. чем больше IDF, тем реже встречается термин в корпусе документов и тем он, на мой взгляд, важнее. Чем меньше IDF — тем более частотный термин в корпусе, и тем он менее важный.
Ерунда всё это, банально на примере, есть два текста
«Путин встретился с депутатами»
«Путин встретился с Медведевым»
IDF для «Путин» и «встретился» будет маленький, т.е. эти два слова будут менее важными по вашему, но на самом деле это именно более важная информация в текстах, а слова «депутатами» и «Медведевым» всего лишь дополнение не такое уж и важное. Отсеять всякие служебные слова, да подойдет, а выделить ключевые слова, т.е. важные слова в документе, вообще никак не катит.
У вас очень маленький корпус и не очень понятна задача. В вашем случае для первого документа относительно всего корпуса как раз-таки важнее то, что Путин встретился именно с депутатами. Это ключевая информация предложения относительно корпуса. TF-IDF плохо подходит для языковой модели, то есть из вашего текста TF-IDF не сможет понять смысловую нагрузку термина. Но поможет определить, на что в тексте стоит обратить внимание, какие ключевые термины есть в тексте.
Спасибо 🙂
Как раз искала инфу про tf-idf в доступном, четком формате…
Не за что! 🙂 Приятно слышать, что Вам пригодилось.