如何从 SKLearn 的 TfidfVectorizer 手动计算 TF-IDF 分数

How to manually calculate TF-IDF score from SKLearn's TfidfVectorizer

我一直是 运行 来自 SKLearn 的 TF-IDF Vectorizer,但我无法手动重新创建值(以帮助理解正在发生的事情)。

为了添加一些上下文,我有一个文档列表,我从中提取了命名实体(在我的实际数据中,它们最多为 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)

注意: 我知道默认情况下会删除停用词,但我的实际数据集中的一些命名实体包含 'the state department' 等短语。所以他们一直被保存在这里。

这是我需要帮助的地方。我的理解是我们计算 TF-IDF 如下:

TF: 词频:根据 SKlearn guidelines 是 "the number of times a term occurs in a given document"

IDF:逆文档频率:1+文档数与1+包含该词的文档数之比的自然对数。根据 link 中的相同准则,结果值添加了 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 的内容,但我无法找出哪里出错了。此外,通过上述计算,波士顿在第一列和第二列中的分数应该没有任何差异,因为该术语在每个文档中只出现一次。

编辑 发布问题后,我认为术语频率可能是计算为与文档中单字母组数或文档中命名实体数的比率。例如,在第二个文档中,SKlearn 为波士顿生成了 0.627914 的分数。如果我将 TF 计算为标记的比率 = 'boston' (1) : 所有一元标记 (4) 我得到一个 0.25 的 TF,当我将其应用于 TF-IDF returns 分数刚刚超过 0.147

同样,当我使用令牌比率 = 'boston' (1) : 所有 NE 令牌 (2) 并应用 TF-IDF 时,我得到 0.846 的分数。很明显我哪里出错了。

让我们一步一步地完成这个数学练习。

第 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 标记的 tfidf 得分(词汇中的#3)。

步骤 2.Calculate tfidf for boston token w/o norm.

公式为:

tf-idf(t, d) = tf(t, d) * idf(t)
idf(t) = log( (n+1) / (df(t)+1) ) + 1
where:
- tf(t,d) -- simple term t frequency in document d
- idf(t) -- smoothed inversed document frequency (because of smooth_idf=True param)

计算第 0 个文档中的标记 boston 以及它出现在的文档数量:

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

请注意,根据内置标记化方案,i 不算作标记。

步骤 3. 归一化

让我们先做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

还是和看到的一样。