关于TF-IDF值,首先简单地提一下:
TF(Term Frequency),也就是词频,这个定义很直观,也就是某个词语地频数除以整个文档的总词数:
T F = 某 个 词 语 出 现 次 数 文 档 总 词 语 数 TF=\frac{某个词语出现次数}{文档总词语数} TF=文档总词语数某个词语出现次数
对于IDF(Inverse Document Frequency)也就是“逆文本频率”,这个值主要是用来限制单纯使用TF值的缺陷,根据TF的计算方式,“的”,“我”等等词语很可能会在文本中大量出现,但是显然,这样的词语对于文本的分类没有用处,因此定义了IDF,计算方式如下:
I D F = l o g 总 文 档 数 目 出 现 过 某 个 词 的 文 档 数 目 IDF = log\frac{总文档数目}{出现过某个词的文档数目} IDF=log出现过某个词的文档数目总文档数目
log函数对于原来值的大小趋势没有影响,从公式来看,如果一个词在很多文档中都出现了,那么IDF值会比较小,反之,如果一个词语在很少的文档中出现了,那么它的IDF值就会很大,这个也很好理解,如果一个词语只在一类文档中出现,那么这个词就可以作为这类文档的区分手段。
最终的TF-IDF值就是将这两个值相乘。
实际上,这只是TF-IDF的基本的计算方式,定义不同,计算的方式也不同,例如在计算IDF值的时候,有些时候为了防止出现坟墓为0的情况,往往会加入平滑的手段,其实它们本质上还是一样的。
那么,在sklearn库中,这个值是如何计算的呢?
首先,sklearn中有几个类与之有关:
如果直接计算TF-IDF值的话,直接使用TfidfVectorizer会比较方便,功能相当于其余的两个类的综合。
来看一个具体的例子:
from sklearn.feature_extraction.text import TfidfVectorizer
data = ["I have a pen", "I have an apple"]
tfidf = TfidfVectorizer(data)
res = tfidf.fit_transform(data)
print(res)
print(res.toarray())
# print(res.todense()) # 功能相当于toarray()
print(tfidf.vocabulary_)
输出的结果是这样:
(0, 2) 0.5797386715376657
(0, 3) 0.8148024746671689
(1, 2) 0.4494364165239821
(1, 0) 0.6316672017376245
(1, 1) 0.6316672017376245
[[0. 0. 0.57973867 0.81480247]
[0.6316672 0.6316672 0.44943642 0. ]]
{'have': 2, 'pen': 3, 'an': 0, 'apple': 1}
直接打印计算TF-IDF的话,它会以稠密的方式给出结果,因为TD-IDF矩阵的维数是字典长度的平方,使用toarray()
方法可以展开成稀疏矩阵的形式,通过最后字典的内容可以看到,在统计的过程中,sklearn实际上会删除一些常用的词语,例如I
和 a
。
对于sklearn计算的TF-IDF矩阵,如果我们手算一个例子,就会发现sklearn计算的结果与我们在上面的定义的结果并不同,注意上面的TF-IDF矩阵的元素是按照字典中词语的顺序排列的,例如(0,2)是have对应的TF-IDF值,(0,3)是pen对应的TF-IDF值,举个例子来说,计算pen这个单词的TD-IDF值:
# pen TF-IDF
tf = 1/4
idf = np.log(2/1) # 这就是按照之前定义的方式计算的结果(直接人工统计=_=||)
tf_idf = tf * idf
print("TF-IDF: ", tf_idf)
idf = np.log(3/2)+1 # 之前提到有些时候回对原始形式进行平滑 这就是平滑的方式之一
tf_idf = tf * idf
print("Smooth TF-IDF: ", tf_idf)
两次输出的结果:
TF-IDF: 0.17328679513998632
Smooth TF-IDF: 0.3513662770270411
可以看到,无论是否采用平滑的方式(也有可能是平滑的方式不对,根据源代码,还真不对),所计算的TF-IDF值都与sklearn所计算的 0.81480247 有较大的误差,根据之前写的一些代码,我想到是不是sklearn计算的过程中进行了某种标准化呢?在网上查询未果的情况下,我找到了这一部分的源代码。(我使用的是Jupyter Notebook,可以光标放在函数上(随便一个属于TfidfVectorizer的函数都可以),然后 shift
+tad
键,这时候会提示函数信息,然后拉到最下面会找到函数文件所在的位置,复制到文件资源管理器就可以打开文件了,如果是Pycharm中的话就更方便了,直接光标定位到那个函数,然后 ctrl
+ 鼠标左键即可)
首先,定位到与计算TF-IDF值直接相关的 fit_transform()
函数,这时候可以看到,这个函数非常简单:
self._check_params()
X = super(TfidfVectorizer, self).fit_transform(raw_documents)
self._tfidf.fit(X)
实际上也就是调用了父类(CountVectorizer)的fit_transform()
方法,在上面的函数中,还可以看到类似调用TfidfTransform的方法,这也就是为什么实际上使用 TfidfVectorizer 等同于同时使用CountVectorizer 和 TfidfTransformer。那这样我们继续寻找在CountVectorizer和TfidfTransformer中寻找有关IDF值的计算方法,在TfidfTransformer中,可以看到这样一大段注释:
The formula that is used to compute the tf-idf for a term t of a document d
in a document set is tf-idf(t, d) = tf(t, d) * idf(t), and the idf is
computed as idf(t) = log [ n / df(t) ] + 1 (if ``smooth_idf=False``), where
n is the total number of documents in the document set and df(t) is the
document frequency of t; the document frequency is the number of documents
in the document set that contain the term t. The effect of adding "1" to
the idf in the equation above is that terms with zero idf, i.e., terms
that occur in all documents in a training set, will not be entirely
ignored.
(Note that the idf formula above differs from the standard textbook
notation that defines the idf as
idf(t) = log [ n / (df(t) + 1) ]).
这段注释解释了TF-IDF值的计算方式,可以看到对于IDF值的计算,这里提供了一个参数,可以选择是否进行平滑处理,上面的公式是不进行平滑处理,在下面的注释中,还有进行平滑处理的公式,也就是:
idf(d, t) = log [ (1 + n) / (1 + df(d, t)) ] + 1
在下面对于Parameter的注释中,提到了关于标准化的处理:
norm : 'l1', 'l2' or None, optional (default='l2')
Each output row will have unit norm, either:
* 'l2': Sum of squares of vector elements is 1. The cosine
similarity between two vectors is their dot product when l2 norm has
been applied.
* 'l1': Sum of absolute values of vector elements is 1.
See :func:`preprocessing.normalize`
可以看到,sklearn中提供了L1与L2标准化可供选择,默认状态下是进行L2标准化的,我们尝试对之前的结果进行L2标准化处理,这里是对每一篇文档(使用一个行向量表示)进行标准化,所以我们也需要计算另一个单词 have 的原始TF-IDF值(由于I 和 a 是停用词未算入词典,所以只计算 have 和 pen 即可):
tf_have = 1/ 4
idf_have = np.log(2/2) + 1
tf_idf_have = tf_have * idf_have
tf_pen = 1/4
idf_pen = np.log(3/2)+1
tf_idf_pen = tf_pen * idf_pen
norm = np.sqrt(tf_idf_have**2 + tf_idf_pen**2)
print([tf_idf_have, tf_idf_pen] / norm)
输出结果:
[0.57973867 0.81480247]
可以看到,这样结果与之前sklearn输出的结果一致了,真相大白。
总结一下,sklearn中计算TF-IDF值的方法是,首先剔除数据中的常见停用词,如果没有指明 smooth 和 norm 的话,按照公式:
idf(d, t) = log [ (1 + n) / (1 + df(d, t)) ] + 1
计算IDF值,并进行L2标准化处理。