如何手动计算SKLearn的TfidfVectorizer中TF-IDF得分

4
我一直在使用SKLearn中的TF-IDF向量化器,但是我无法手动重现值(以了解发生了什么)。为了添加一些上下文,我有一个从文档中提取出来的命名实体列表(在我的实际数据中,这些可以增加到5个元组,但在此处我将其限制为二元组)。我只想知道这些值的TF-IDF分数,并认为通过参数传递这些术语可以做到这一点。以下是我正在处理的类似于虚拟数据:
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示例 以第一列为例,它只有一个命名实体'Boston',根据上述代码,在第一个文档中具有1的TF-IDF。然而,当我手动计算时,得到以下结果:
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的内容,但我无法找出我的错误所在。此外,根据以上计算,在第一列和第二列中,波士顿的得分应该没有任何区别,因为该术语在每个文档中只出现一次。

编辑 发帖后,我想到也许术语频率是与文档中的unigrams数量或命名实体数量之比计算的。例如,在第二个文档中,SKlearn生成了波士顿的得分为0.627914。如果我将TF计算为标记='boston'(1):所有单元标记(4)的比率,则得到0.25的TF。将其应用于TF-IDF后,返回刚刚超过0.147的分数。

同样地,当我使用标记='boston'(1):所有NE标记(2)的比率,并应用TF-IDF时,我得到一个分数为0.846。因此,显然我做错了什么。


一个可能有帮助的方法是按步骤分解。例如,从tfidfvectorizer文档中可以看到:“相当于CountVectorizer后跟TfidfTransformer。”您可以尝试重新创建这些步骤并比较中间输出。 - G. Anderson
您缺少规范化步骤。请参考以下示例。 - Sergey Bushmanov
1个回答

4

我将逐步为您完成这个数学练习。

步骤1:获取boston令牌的tfidf分数

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.        ]])

我们目前所拥有的是关于boston标记(词汇表中排名第3)的tfidf分数。
第二步:计算未经规范化的boston标记的tfidf值。
公式如下:
tf-idf(t, d) = tf(t, d) * idf(t) idf(t) = log((n+1)/(df(t)+1))+1 其中: - tf(t,d) -- 文档d中朴素术语t的频率 - idf(t) -- 平滑逆文档频率(因为有smooth_idf=True参数)
统计第0个文档中的boston标记出现次数及其出现文档的数量:
tfidf_boston_wo_norm = ((1/5) * (np.log((1+5)/(1+2))+1))
tfidf_boston_wo_norm
0.3386294361119891

注意,根据内置的标记化方案,i 不被视为令牌。
第三步:标准化
首先进行l1标准化,即所有计算出的非标准化的 tfdid 应该按行总和为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

这仍然与您可能看到的相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接