Python 如何从SKLearn'手动计算TF-IDF分数;TFIDF矢量器

Python 如何从SKLearn'手动计算TF-IDF分数;TFIDF矢量器,python,scikit-learn,tf-idf,tfidfvectorizer,Python,Scikit Learn,Tf Idf,Tfidfvectorizer,我一直在从SKLearn运行TF-IDF矢量器,但手动重新创建值时遇到困难(作为了解发生了什么的帮助) 为了添加一些上下文,我有一个从中提取命名实体的文档列表(在我的实际数据中,这些文档高达5克,但这里我将其限制为bigrams)。我只想知道这些值的TF-IDF分数,我认为通过词汇表参数传递这些术语就可以做到这一点 下面是一些与我正在使用的类似的虚拟数据: from sklearn.feature_extraction.text import TfidfVectorizer import pan

我一直在从SKLearn运行TF-IDF矢量器,但手动重新创建值时遇到困难(作为了解发生了什么的帮助)

为了添加一些上下文,我有一个从中提取命名实体的文档列表(在我的实际数据中,这些文档高达5克,但这里我将其限制为bigrams)。我只想知道这些值的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:术语频率:根据是“一个术语在给定文档中出现的次数”

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,但我无法找出哪里出了问题。此外,通过上述计算,第一列和第二列中波士顿的得分应该没有任何差异,因为该术语在每个文档中只出现一次

编辑 在发布了这个问题之后,我认为术语频率可能是通过与文档中的单格数或文档中命名实体数的比率来计算的。例如,在第二个文档中,SKlearn为Boston生成的分数为
0.627914
。如果我将TF计算为代币的比率='boston'(1):所有单格代币(4),我得到的TF值为
0.25
,当我应用于TF-IDF时,返回的分数略高于
0.147


类似地,当我使用令牌比率='boston'(1):所有NE令牌(2)并应用TF-IDF时,我得到的分数为
0.846
。很明显,我在某个地方出错了。

让我们一步一步地做这个数学练习

第1步。获取波士顿的tfidf分数token

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在波士顿的得分为
token(#3分)

步骤2.计算波士顿
令牌的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
param)

计算第0个文档中的令牌
boston
,以及它出现在其中的文档的#:

tfidf_boston_wo_norm = ((1/5) * (np.log((1+5)/(1+2))+1))
tfidf_boston_wo_norm
0.3386294361119891
注意,
i
根据内置令牌化方案不算作令牌

第3步。正常化

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')
让我们先进行
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
norm做同样的计算

基准:

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

它仍然和一个可能看到的一样。

一个可能有用的方法是分步分解它。例如,在tfidfvectorizer文档中:“等效于后跟”。您可以尝试重新创建这些步骤,并比较缺少规范化步骤的中间输出。见下面的例子。