TF-IDF算法(原理+python代码实现)

目录

前言

一、TF-IDF的由来

二、什么是TF-IDF?

2.1 TF(Term Frequency)

2.2 IDF(Inverse Document Frequency)

2.3 TF-IDF(Term Frequency-Inverse Document Frequency)

三、TF-IDF应用

四、代码实现

4.1 常规Python实现TF-IDF

4.2 NLTK实现TF-IDF

4.3 Jieba实现TF-IDF算法

五、TF-IDF不足之处

六、TF-IDF变种

5.1 通过对数函数避免 TF 线性增长

5.2 标准化解决长文档、短文档问题

5.3 对数函数处理 IDF

5.4 查询词及文档向量标准化

六、参考文章


前言

最近在读一篇论文《Mining Quality Phrases from Massive Text Corpora》,其中涉及到了TF-IDF和随机森林算法(Random Forest),顾对两个算法进行巩固加深记忆,这篇文章主要是讲TF-IDF算法,行文过程中参考了几篇博文,做了如下总结。

一、TF-IDF的由来

把查询关键字(Query)和文档(Document)都转换成 “向量”,并且尝试用线性代数等数学工具来解决信息检索问题,这样的努力至少可以追溯到 20 世纪 70 年代。

1971 年,美国康奈尔大学教授杰拉德 · 索尔顿(Gerard Salton)发表了《SMART 检索系统:自动文档处理实验》(The SMART Retrieval System—Experiments in Automatic Document Processing)一文,文中首次提到了把查询关键字和文档都转换成 “向量”,并且给这些向量中的元素赋予不同的值。这篇论文中描述的 SMART 检索系统,特别是其中对 TF-IDF 及其变种的描述成了后续很多工业级系统的重要参考。

1972 年,英国的计算机科学家卡伦 · 琼斯(Karen Spärck Jones)在《从统计的观点看词的特殊性及其在文档检索中的应用》(A Statistical Interpretation of Term Specificity and Its Application in Retrieval) 一文中第一次详细地阐述了 IDF 的应用。其后卡伦又在《检索目录中的词赋值权重》(Index Term Weighting)一文中对 TF 和 IDF 的结合进行了论述。可以说,卡伦是第一位从理论上对 TF-IDF 进行完整论证的计算机科学家,因此后世也有很多人把 TF-IDF 的发明归结于卡伦。

杰拉德本人被认为是 “信息检索之父”。他 1927 年出生于德国的纽伦堡,并与 1950 年和 1952 年先后从纽约的布鲁克林学院获得数学学士和硕士学位,1958 年从哈佛大学获得应用数学博士学位,之后来到康奈尔大学参与组建计算机系。为了致敬杰拉德本人对现代信息检索技术的卓越贡献,现在,美国计算机协会 ACM(Association of Computing Machinery)每三年颁发一次“杰拉德 · 索尔顿奖”(Gerard Salton Award),用于表彰对信息检索技术有突出贡献的研究人员。卡伦 · 琼斯在 1988 年获得了第二届“杰拉德 · 索尔顿奖” 的殊荣。

二、什么是TF-IDF?

TF-IDF(Term Frequency-Inverse Document Frequency, 词频-逆文件频率)是一种用于资讯检索与资讯探勘的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。

简单来说就是:一个词语在一篇文章中出现次数越多, 同时在所有文档中出现次数越少, 越能够代表该文章。这也就是TF-IDF的含义。

TF-IDF算法(原理+python代码实现)_第1张图片

2.1 TF(Term Frequency)

词频(TF)表示词条(关键字)在文本中出现的频率

这个数字通常会被归一化(一般是词频除以文章总词数), 以防止它偏向长的文件。

公式 tf_{ij}=\frac{n_{i,j}}{\sum_{k}^{n_{k,j}}}  (术语 t 在文档中出现的次数) / (文档中的术语总数)

但是,需要注意, 一些通用的词语对于主题并没有太大的作用, 反倒是一些出现频率较少的词才能够表达文章的主题, 所以单纯使用是TF不合适的。权重的设计必须满足:一个词预测主题的能力越强,权重越大,反之,权重越小。所有统计的文章中,一些词只是在其中很少几篇文章中出现,那么这样的词对文章的主题的作用很大,这些词的权重应该设计的较大。IDF就是在完成这样的工作。 

2.2 IDF(Inverse Document Frequency)

逆向文件频率 (IDF) :某一特定词语的IDF,可以由总文件数目除以包含该词语的文件的数目再将得到的商取对数得到

如果包含词条t的文档越少, IDF越大,则说明词条具有很好的类别区分能力。

idf_i=log\frac{|D|}{|\begin{Bmatrix} {j:t_j\in d_j} \end{Bmatrix}|}     log_e(文档总数/包含术语 t 的文档数)

其中,|D| 是语料库中的文件总数 |{j:ti∈dj}| 表示包含词语 ti 的文件数目(即 ni,j≠0 的文件数目)。如果该词语不在语料库中,就会导致分母为零,因此一般情况下使用 1+|{j:ti∈dj}|

IDF用于衡量一个术语的重要性。在计算 TF 时,所有项都被认为同样重要。然而,众所周知,某些术语,如"是","的"和"那个",可能会出现很多次,但并不重要。因此,我们需要通过计算以下内容来权衡常用术语,同时放大稀有术语:IDF(t) 

2.3 TF-IDF(Term Frequency-Inverse Document Frequency)

某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语,表达为 

TF-IDF=TF\ast IDF

注:  TF-IDF算法非常容易理解,并且很容易实现,但是其简单结构并没有考虑词语的语义信息,无法处理一词多义与一义多词的情况。 

三、TF-IDF应用

(1)搜索引擎;(2)关键词提取;(3)文本相似性;(4)文本摘要

四、代码实现

4.1 常规Python实现TF-IDF

# -*- coding: utf-8 -*-
from collections import defaultdict
import math
import operator
 
"""
函数说明:创建数据样本
Returns:
    dataset - 实验样本切分的词条
    classVec - 类别标签向量
"""
def loadDataSet():
    dataset = [ ['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],    # 切分的词条
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
                   ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
                   ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
                   ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid'] ]
    classVec = [0, 1, 0, 1, 0, 1]  # 类别标签向量,1代表好,0代表不好
    return dataset, classVec
 
 
"""
函数说明:特征选择TF-IDF算法
Parameters:
     list_words:词列表
Returns:
     dict_feature_select:特征选择词字典
"""
def feature_select(list_words):
    #总词频统计
    doc_frequency=defaultdict(int)
    for word_list in list_words:
        for i in word_list:
            doc_frequency[i]+=1
 
    #计算每个词的TF值
    word_tf={}  #存储没个词的tf值
    for i in doc_frequency:
        word_tf[i]=doc_frequency[i]/sum(doc_frequency.values())
 
    #计算每个词的IDF值
    doc_num=len(list_words)
    word_idf={} #存储每个词的idf值
    word_doc=defaultdict(int) #存储包含该词的文档数
    for i in doc_frequency:
        for j in list_words:
            if i in j:
                word_doc[i]+=1
    for i in doc_frequency:
        word_idf[i]=math.log(doc_num/(word_doc[i]+1))
 
    #计算每个词的TF*IDF的值
    word_tf_idf={}
    for i in doc_frequency:
        word_tf_idf[i]=word_tf[i]*word_idf[i]
 
    # 对字典按值由大到小排序
    dict_feature_select=sorted(word_tf_idf.items(),key=operator.itemgetter(1),reverse=True)
    return dict_feature_select
 
if __name__=='__main__':
    data_list,label_list=loadDataSet() #加载数据
    features=feature_select(data_list) #所有词的TF-IDF值
    print(features)
    print(len(features))

打印结果如下 

[('to', 0.0322394037469742), ('stop', 0.0322394037469742), ('worthless', 0.0322394037469742), ('my', 0.028288263356383563), ('dog', 0.028288263356383563), ('him', 0.028288263356383563), ('stupid', 0.028288263356383563), ('has', 0.025549122992281622), ('flea', 0.025549122992281622), ('problems', 0.025549122992281622), ('help', 0.025549122992281622), ('please', 0.025549122992281622), ('maybe', 0.025549122992281622), ('not', 0.025549122992281622), ('take', 0.025549122992281622), ('park', 0.025549122992281622), ('dalmation', 0.025549122992281622), ('is', 0.025549122992281622), ('so', 0.025549122992281622), ('cute', 0.025549122992281622), ('I', 0.025549122992281622), ('love', 0.025549122992281622), ('posting', 0.025549122992281622), ('garbage', 0.025549122992281622), ('mr', 0.025549122992281622), ('licks', 0.025549122992281622), ('ate', 0.025549122992281622), ('steak', 0.025549122992281622), ('how', 0.025549122992281622), ('quit', 0.025549122992281622), ('buying', 0.025549122992281622), ('food', 0.025549122992281622)]
32

4.2 NLTK实现TF-IDF

from nltk.text import TextCollection
from nltk.tokenize import word_tokenize
 
#首先,构建语料库corpus
sents=['this is sentence one','this is sentence two','this is sentence three']
sents=[word_tokenize(sent) for sent in sents] #对每个句子进行分词
print(sents)  #输出分词后的结果
corpus=TextCollection(sents)  #构建语料库
print(corpus)  #输出语料库
 
#计算语料库中"one"的tf值
tf=corpus.tf('one',corpus)    # 1/12
print(tf)
 
#计算语料库中"one"的idf值
idf=corpus.idf('one')      #log(3/1)
print(idf)
 
#计算语料库中"one"的tf-idf值
tf_idf=corpus.tf_idf('one',corpus)
print(tf_idf)

打印结果如下

[['this', 'is', 'sentence', 'one'], ['this', 'is', 'sentence', 'two'], ['this', 'is', 'sentence', 'three']]

0.08333333333333333
1.0986122886681098
0.0915510240556758

注:代码运行过程中可能报错,解决方法参考下文

NLTK:Resource punkt not found. Please use the NLTK Downloader to obtain the resource_massive_jiang的博客-CSDN博客

4.3 Jieba实现TF-IDF算法

import jieba.analyse
 
text='关键词是能够表达文档中心内容的词语,常用于计算机系统标引论文内容特征、信息检索、系统汇集以供读者检阅。关键词提取是文本挖掘领域的一个分支,是文本检索、文档比较、摘要生成、文档分类和聚类等文本挖掘研究的基础性工作'
 
keywords=jieba.analyse.extract_tags(text, topK=5, withWeight=False, allowPOS=())
print(keywords)

打印结果如下 

['文档', '文本', '关键词', '挖掘', '文本检索']

注:

jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
sentence 为待提取的文本
topK 为返回几个 TF/IDF 权重最大的关键词,默认值为 20
withWeight 为是否一并返回关键词权重值,默认值为 False
allowPOS 仅包括指定词性的词,默认值为空,即不筛选

五、TF-IDF不足之处

TF-IDF算法是建立在这样一个假设之上的:对区别文档最有意义的词语应该是那些在文档中出现频率高,而在整个文档集合的其他文档中出现频率少的词语,所以如果特征空间坐标系取TF词频作为测度,就可以体现同类文本的特点。另外考虑到单词区别不同类别的能力,TF-IDF法认为一个单词出现的文本频数(即包含某个单词的文本数)越小,它区别不同类别文本的能力就越大。因此引入了逆文本频度IDF的概念,以TF和IDF的乘积作为特征空间坐标系的取值测度,并用它完成对权值TF的调整,调整权值的目的在于突出重要单词,抑制次要单词。但是在本质上IDF是一种试图抑制噪声的加权,并且单纯地认为文本频率小的单词就越重要,文本频率大的单词就越无用,显然这并不是完全正确的。IDF的简单结构并不能有效地反映单词的重要程度和特征词的分布情况,使其无法很好地完成对权值调整的功能,所以TF-IDF法的精度并不是很高。

此外,在TFIDF算法中并没有体现出单词的位置信息,对于Web文档而言,权重的计算方法应该体现出HTML的结构特征。特征词在不同的标记符中对文章内容的反映程度不同,其权重的计算方法也应不同。因此应该对于处于网页不同位置的特征词分别赋予不同的系数,然后乘以特征词的词频,以提高文本表示的效果。

注:TF-IDF 算法主要适用于英文,中文首先要分词,分词后要解决多词一义,以及一词多义问题,这两个问题通过简单的tf-idf方法不能很好的解决。于是就有了后来的词嵌入方法,用向量来表征一个词。

六、TF-IDF变种

TF-IDF算法(原理+python代码实现)_第2张图片

5.1 通过对数函数避免 TF 线性增长

很多人注意到 TF 的值在原始的定义中没有任何上限。虽然我们一般认为一个文档包含查询关键词多次相对来说表达了某种相关度,但这样的关系很难说是线性的。拿我们刚才举过的关于 “Car Insurance” 的例子来说,文档 A 可能包含 “Car” 这个词 100 次,而文档 B 可能包含 200 次,是不是说文档 B 的相关度就是文档 A 的 2 倍呢?其实,很多人意识到,超过了某个阈值之后,这个 TF 也就没那么有区分度了。

用 Log,也就是对数函数,对 TF 进行变换,就是一个不让 TF 线性增长的技巧。具体来说,人们常常用 1+Log(TF) 这个值来代替原来的 TF 取值。在这样新的计算下,假设 “Car” 出现一次,新的值是 1,出现 100 次,新的值是 5.6,而出现 200 次,新的值是 6.3。很明显,这样的计算保持了一个平衡,既有区分度,但也不至于完全线性增长。

5.2 标准化解决长文档、短文档问题

经典的计算并没有考虑 “长文档” 和“短文档”的区别。一个文档 A 有 3,000 个单词,一个文档 B 有 250 个单词,很明显,即便 “Car” 在这两个文档中都同样出现过 20 次,也不能说这两个文档都同等相关。对 TF 进行 “标准化”(Normalization),特别是根据文档的最大 TF 值进行的标准化,成了另外一个比较常用的技巧

5.3 对数函数处理 IDF

第三个常用的技巧,也是利用了对数函数进行变换的,是对 IDF 进行处理。相对于直接使用 IDF 来作为 “惩罚因素”,我们可以使用 N+1 然后除以 DF 作为一个新的 DF 的倒数,并且再在这个基础上通过一个对数变化。这里的 N 是所有文档的总数。这样做的好处就是,第一,使用了文档总数来做标准化,很类似上面提到的标准化的思路;第二,利用对数来达到非线性增长的目的。

5.4 查询词及文档向量标准化

还有一个重要的 TF-IDF 变种,则是对查询关键字向量,以及文档向量进行标准化,使得这些向量能够不受向量里有效元素多少的影响,也就是不同的文档可能有不同的长度。在线性代数里,可以把向量都标准化为一个单位向量的长度。这个时候再进行点积运算,就相当于在原来的向量上进行余弦相似度的运算。所以,另外一个角度利用这个规则就是直接在多数时候进行余弦相似度运算,以代替点积运算。

六、参考文章

1. TF-IDF 原理与实现 - 知乎 (zhihu.com)

2. 通俗理解TF-IDF - 简书 (jianshu.com)

3. Tf-idf :: 单页教程 - 信息检索和文本挖掘 (tfidf.com)

4. 一文看懂 TF-IDF (概念图解+4大变种+发展历史) (easyai.tech) 

5. TF-IDF算法介绍及实现_Asia-Lee-CSDN博客_tf-idf

6. 机器学习:生动理解TF-IDF算法 - 知乎 (zhihu.com)

你可能感兴趣的:(NLP,算法,自然语言处理,人工智能,nlp,python)