Как вручную рассчитать оценку TF-IDF из SKLearn's TfidfVectorizer - PullRequest
3 голосов
/ 21 февраля 2020

Я запускаю TF-IDF Vectorizer от SKLearn, но у меня возникают проблемы при воссоздании значений вручную (чтобы понять, что происходит).

Чтобы добавить некоторый контекст, у меня есть список документов, из которых я извлек именованные объекты (в моих реальных данных это go до 5 граммов, но здесь я ограничил это биграммами). Я только хочу знать оценки TF-IDF для этих значений и думал, что передача этих терминов через параметр vocabulary сделает это.

Вот некоторые фиктивные данные, аналогичные тем, с которыми я работаю:

from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd    


# list of named entities I want to generate TF-IDF scores for
named_ents = ['boston','america','france','paris','san francisco']

# my list of documents
docs = ['i have never been to boston',
    'boston is in america',
    'paris is the capitol city of france',
    'this sentence has no named entities included',
    'i have been to san francisco and paris']

# find the max nGram in the named entity vocabulary
ne_vocab_split = [len(word.split()) for word in named_ents]
max_ngram = max(ne_vocab_split)

tfidf = TfidfVectorizer(vocabulary = named_ents, stop_words = None, ngram_range=(1,max_ngram))
tfidf_vector = tfidf.fit_transform(docs)

output = pd.DataFrame(tfidf_vector.T.todense(), index=named_ents, columns=docs)

Примечание: Я знаю, что стоп-слова удаляются по умолчанию, но некоторые из названных сущностей в моем фактическом наборе данных включают фразы, такие как «государственный департамент». Таким образом, они были сохранены здесь.

Здесь мне нужна помощь. Я понимаю, что мы рассчитываем TF-IDF следующим образом:

TF: термин частоты: который согласно рекомендациям SKlearn является "числом раз в данном документе встречается термин "

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

Затем мы умножаем TF на IDF , чтобы получить общее TF-IDF для данного термина в данном документе.

Пример

Давайте возьмем первый столбец в качестве примера, который имеет только одну именованную сущность 'Бостон', и согласно приведенному выше коду имеет TF-IDF для первого документа 1. Однако, когда я работаю с этим вручную, я получаю следующее:

TF = 1

IDF = log-e(1+total docs / 1+docs with 'boston') + 1
' ' = log-e(1+5 / 1+2) + 1
' ' = log-e(6 / 3) + 1
' ' = log-e(2) + 1
' ' = 0.69314 + 1
' ' = 1.69314

TF-IDF = 1 * 1.69314 = 1.69314 (not 1)

Возможно, я что-то упускаю в документации, где говорится, что оценки ограничены 1, но я не могу понять, где я ошибся. Кроме того, с учетом приведенных выше расчетов не должно быть различий между оценкой Бостона в первом столбце и во втором столбце, поскольку термин появляется в каждом документе только один раз.

Редактировать После публикации вопроса я подумал, что, возможно, термин «частота» рассчитывался как отношение либо количества символов в документе, либо количества именованных объектов в документе. Например, во втором документе SKlearn генерирует для Бостона оценку 0.627914. Если я вычисляю TF как отношение токенов = 'boston' (1): все униграммовые токены (4), я получаю TF 0.25, который при подаче заявки на TF-IDF возвращает чуть более 0.147 ,

Точно так же, когда я использую соотношение токенов = 'boston' (1): все токены NE (2) и применяю TF-IDF, я получаю оценку 0.846. Ясно, что я где-то ошибаюсь.

1 Ответ

2 голосов
/ 22 февраля 2020

Давайте выполним это математическое упражнение по одному шагу за раз.

Шаг 1. Получите оценки tfidf для boston токена

docs = ['i have never been to boston',
        'boston is in america',
        'paris is the capitol city of france',
        'this sentence has no named entities included',
        'i have been to san francisco and paris']

from sklearn.feature_extraction.text import TfidfVectorizer

# I did not include your named_ents here but did for a full vocab 
tfidf = TfidfVectorizer(smooth_idf=True,norm='l1')

Обратите внимание на параметры в TfidfVectorizer они важны для последующего сглаживания и нормализации.

docs_tfidf = tfidf.fit_transform(docs).todense()
n = tfidf.vocabulary_["boston"]
docs_tfidf[:,n]
matrix([[0.19085885],
        [0.22326669],
        [0.        ],
        [0.        ],
        [0.        ]])

То, что мы получили, tfidf набирает за boston токен (# 3 в вокабе).

Шаг 2. Рассчитать tfidf для boston токена без нормы.

Формулы:

tf-idf (t, d) = tf (t, d) * idf (t)
idf (t) = log ((n + 1) / (df (t) +1)) + 1
где:
- tf (t , d) - простой термин t частота в документе d
- idf (t) - сглаженная инвертированная частота документа (из-за smooth_idf=True param)

Подсчет токена boston в 0-й документ и количество документов, в которых он отображается:

tfidf_boston_wo_norm = ((1/5) * (np.log((1+5)/(1+2))+1))
tfidf_boston_wo_norm
0.3386294361119891

Примечание. i не считается токеном согласно встроенной схеме токенизации.

Шаг 3. Нормализация

Сначала сделаем l1 нормализацию, т.е. все вычисленные ненормированные tf Значение did должно составлять до 1 по строке:

l1_norm = ((1/5) * (np.log((1+5)/(1+2))+1) +
         (1/5) * (np.log((1+5)/(1+1))+1) +
         (1/5) * (np.log((1+5)/(1+2))+1) +
         (1/5) * (np.log((1+5)/(1+2))+1) +
         (1/5) * (np.log((1+5)/(1+2))+1))
tfidf_boston_w_l1_norm = tfidf_boston_wo_norm/l1_norm
tfidf_boston_w_l1_norm 
0.19085884520912985

Как видите, мы получаем тот же показатель tfidf, что и выше.

Давайте теперь выполним ту же математику для нормы l2.

Контрольный показатель:

tfidf = TfidfVectorizer(sublinear_tf=True,norm='l2')
docs_tfidf = tfidf.fit_transform(docs).todense()
docs_tfidf[:,n]
matrix([[0.42500138],
        [0.44400208],
        [0.        ],
        [0.        ],
        [0.        ]])

Исчисление:

l2_norm = np.sqrt(((1/5) * (np.log((1+5)/(1+2))+1))**2 +
                  ((1/5) * (np.log((1+5)/(1+1))+1))**2 +
                  ((1/5) * (np.log((1+5)/(1+2))+1))**2 +
                  ((1/5) * (np.log((1+5)/(1+2))+1))**2 +
                  ((1/5) * (np.log((1+5)/(1+2))+1))**2                
                 )

tfidf_boston_w_l2_norm = tfidf_boston_wo_norm/l2_norm
tfidf_boston_w_l2_norm 
0.42500137513291814

Это все равно, что может видеть.

...