详解TF-IDF

1. TF-IDF概念

TF-IDF是NLP中常用的方法,也比较经典,基本思想是:如果一个词在文档中出现了很多次,但是这个词在其它文档中出现的次数很少,则这个词对这篇文档很重要。在一定程度上这个词可以表达这篇文档的关键信息,所以在网页搜索、关键词提取中常用到TF-IDF。

TF-IDF就是 t f i d f ( t , d ) = t f ( t , d ) × i d f ( t ) tf-idf(t,d)=tf(t,d) \times idf(t) ,公式中t代表词term,d代表文档document。其实就是TF与IDF相乘。

TF是词频term frequency ,idf是逆文档频率inverse document-frequency。频率与频数不同,这里的词频是指词的频数,也就是一个词在文档中出现的个数,比如“的”在一个文档中出现了128次数,则 t f ( t , d ) = 128 tf(t,d)=128 ,这个比较好理解。

逆文档频率:为什么要有IDF呢?如果只用TF会有什么问题呢?比如“的”在一个文档中出现128次,是整个文档中出现次数最多的词。但是这个词并没有什么意义。所以要求有意义的词在这篇文档中出现的次数多,但是在其它的文档中出现的少。我们理解的频率的概念是频数除以总数。“逆”就是倒过来的意思,为了防止分母为0,在分母上了个1,再做log处理。所以:
i d f ( t ) = l o g n 1 + d f ( t ) idf(t)=log\frac{n}{1+df(t)}
公式中 n n 代表文档的总数, d f ( t ) df(t) 代表包含单词t的文档的个数。对于这个公式sklearn做了优化。

2. sklearn中的TF-IDF

2.1 sklearn中TF-IDF的公式及计算过程

在sklearn中 TfidfTransformerTfidfVectorizer 设置参数 smooth_idf=False时,IDF的计算公式,如下: i d f ( t ) = l o g n d f ( t ) + 1 idf(t)=log\frac{n}{df(t)}+1
把分子中的1拿到了外边。如果smooth_idf=True时分子和分母同时加了1,做平滑处理。如下:
i d f ( t ) = l o g 1 + n 1 + d f ( t ) + 1 idf(t) = log\frac{1+n}{1+df(t)}+1
然后再 l 2 l_2 正则化:
v n o r m = v v 1 2 + v 2 2 + . . . + v n 2 v_{norm}=\frac{v}{\sqrt{v_1^2+v_2^2+...+v_n^2}}
这个向量其实是每个词的权重(weight),一开始是用于信息检索(information retrieval),后来发现这个词向量在文本分类与文本聚类中也很有效果。或许会问,为什么要做正则化?有没有发现正则化后,两个向量的点乘就是这两个向量的余弦相似度了。

举个例子:

>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> transformer = TfidfTransformer(smooth_idf=False)
>>> transformer
# 未做smooth处理
TfidfTransformer(smooth_idf=False)
# 有6个文档,每个文档有三个特征词,一行是一个文档。
>>> counts = [[3, 0, 1],
...[2, 0, 0],
...[3, 0, 0],
...[4, 0, 0],
...[3, 2, 0],
...[3, 0, 2]]
...
# 使用sklearn的
>>> tfidf = transformer.fit_transform(counts)
>>> tfidf                         
<6x3 sparse matrix of type '<... 'numpy.float64'>'with 9 stored elements in Compressed Sparse ... format>

>>> tfidf.toarray()                        
array([[0.81940995, 0.        , 0.57320793],[1.        , 0.        , 0.        ],[1.        , 0.        , 0.        ],[1.        , 0.        , 0.        ],[0.47330339, 0.88089948, 0.        ],

# 做smooth处理的
>>> transformer = TfidfTransformer()
>>> transformer.fit_transform(counts).toarray()
array([[0.85151335, 0.        , 0.52433293],
       [1.        , 0.        , 0.        ],
       [1.        , 0.        , 0.        ],
       [1.        , 0.        , 0.        ],
       [0.55422893, 0.83236428, 0.        ],
       [0.63035731, 0.        , 0.77630514]])

TfidfVectorizer

>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> vectorizer = TfidfVectorizer()
# 直接把语料转换为if-idf的向量
>>> vectorizer.fit_transform(corpus)
<4x9 sparse matrix of type '<... 'numpy.float64'>'
    with 19 stored elements in Compressed Sparse ... format>

这个例子中6篇文档3个特征的tf-idf的计算过程:

一共有6个文档,n=6,特征词有三个,对于term1在所有的文档中都出现过,所以, d f ( t ) t e r m 1 = 6 df(t)_{term1}=6

对于doc1的第一个term的tf = 3,所以 t f i d f t e r m 1 = t f i d f ( t ) t e r m 1 = 3 × ( l o g 6 6 + 1 ) = 3 tf-idf_{term1}= tf * idf(t)_{term1} = 3 \times (log\frac{6}{6}+1)=3

对于doc2的第二个term的tf = 0,所以 t f i d f t e r m 1 = t f i d f ( t ) t e r m 1 = 0 × ( l o g 6 1 + 1 ) = 0 tf-idf_{term1}= tf * idf(t)_{term1} = 0 \times (log\frac{6}{1}+1)=0

对于doc1的第三个term的tf = 1,所以 t f i d f t e r m 1 = t f i d f ( t ) t e r m 1 = 1 × ( l o g 6 2 + 1 ) = 2.0986 tf-idf_{term1}= tf * idf(t)_{term1} = 1 \times (log\frac{6}{2}+1)=2.0986

原始的tf-idf向量:[3, 0, 2.0986]
正则化:
[ 3 , 0 , 2.0986 ] 3 2 + 0 2 + 2.098 6 2 = [ 0.819 , 0 , 0.573 ] \frac{[3,0,2.0986]}{\sqrt{3^2+0^2+2.0986^2}}=[0.819,0,0.573]
在sklearn中TfidfVecotrizerCountVectorizerTfidfTransformer的结合,可以直接把文本的语料转化为以词为特征的向量。其实这个向量就是一个文本转化为向量后的结果,如果不限定的话,是所有文本组成的词典的长度,但是可以根据max_dfmin_df来选择,超过max_df或低于min_df的词是不被选入词典的。max_df的默认值是1.0,也就是最高文档频率是没有限制的,min_df的默认值是1,也就是说单词要在所有的文档中有出现。特征是所有的转化为后向量可以用于文本分类或文本聚类。

2.2 使用LightGBM进行文本分类

使用的是sklearn中自带的文本样本20newsgroups数据集,一共有11314篇文档,20个类别。对样本向量化,再使用LightGBM对样本进行分类。对比了sklearn中两种tf-idf向量化的方式,向量化的结果是一样的。但是向量化后,数据比较稀疏,训练有点慢。不过仅仅使用这么稀疏的数据,而且使用的lgb的baseline模型,效果还是不错的了,最好的类别f1 score能到94%。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 小结

  • 文本需要切词,构建所有文本的词典;
  • 把词典的词作为样本的特征,特征值是每个词在文档中出现的次数;
  • 根据次数及tf-idf公式计算每个词的tf-idf值,然后l2正则化;经过tf-idf处理后的向量也可以作为文本的向量;
  • 样本的tf-id向量中权重比较大的词就可以作为样本的关键词;
  • 可以通过限制max_dfmin_df来限制样本的tf-idf向量的维度;
  • 所有样本的tf-idf matrix是一个稀疏矩阵,对于传统的机器学习算法不友好,训练时间比较长;所以后来word embedding就是把稀疏矩阵降维的;
  • 这种文本向量化的模型其实基于词袋模型(Bag of Word model)的。

3. jieba使用tf-idf提取关键词

3.1 一行代码提取关键词

from jieba import analyse
sentence  = "人工智能(Artificial Intelligence),英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器,该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧的“容器”。人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考、也可能超过人的智能。人工智能是一门极富挑战性的科学,从事这项工作的人必须懂得计算机知识,心理学和哲学。人工智能是包括十分广泛的科学,它由不同的领域组成,如机器学习,计算机视觉等等,总的说来,人工智能研究的一个主要目标是使机器能够胜任一些通常需要人类智能才能完成的复杂工作。但不同的时代、不同的人对这种“复杂工作”的理解是不同的。2017年12月,人工智能入选“2017年度中国媒体十大流行语“。"
keywords = analyse.extract_tags(sentence, topK=10, withWeight=False, allowPOS=(), withFlag=False)
print(keywords)
# ['人工智能', '智能', '2017', '机器', '不同', '人类', '科学', '模拟', '一门', '技术']

输入是一段字符串,输出关键词的list。
其中的参数:

  • sentence:需要提取关键词的字符串
  • topK:返回权重前top K个单词
  • withWeight:返回结果是否需要带tf-idf值
  • allowPOS:关键词的词性
  • withFlag:返回结果是否要带词性

使用起来确实简单,直接输入一段话就可以提取其中的关键词了。但是有一个问题是,idf的计算是需要看出现这个词的文档的个数,及总的文档数量的,jieba难道不需要么?

3.2 具体实现

jieba中有一个自带的idf.txt,第一列单词,第二列是idf。这个idf是通过离线训练好的。
在这里插入图片描述
jieba的tf-idf计算比较简单,源码如下:
在这里插入图片描述
有三步:

  • 第一步:对sentence分词,分词使用的HMM算法,当然,也是内置的转移矩阵、发射矩阵及初始概率。分词这个有时间可以写一下。
  • 第二步:统计sentence中每个单词的个数。具体实现是用一个dict放的,当然也根据参数allowPOS、stop_words,单词的长度(大于2)进行了过滤。
  • 第三步:计算tf-idf。idf是通过内置的idf.txt得到的,类初始化的时候会加载idf.txt文件为一个diict,即self.idf_freq。如果单词不在self.freq中时,返回的是平均的idf值self.median_idf。然后用第二步的tf值与得到idf相乘。至于为什么要除以一个total,我觉得是为了把tf-idf值变的小一些,比较容易比较大小,毕竟所有的tf-idf都除以一个正数对大小的比较没有影响。

3.3 小结

  • jieba的tf-idf值的计算,不需要输入多篇文档,只需要输入需要提取关键词的文档即可
  • 关键词可以有词性的限制
  • jieba是通过内置的idf来计算单词的tf-idf值的
  • 缺点:对于专业性比较强的文档,可能会有问题。毕竟jieba使用的训练样本是不清楚的。

4 总结

  • tf-idf的计算公式及优化
  • tf-idf的一个应用是基于词袋模型为文本提取特征向量,然后用于文本分类或聚类。优点是比较好理解 ,缺点是matrix太稀疏,向量与向量之间的相似性不太靠谱。试想一下两个文档,里面所用的词不同,但是意思差不多,直观上两个向量应该是相似的。但是其实两个向量相乘的结果为零向量。词不相同,向量正交。
  • tf-idf的另一个应用是提取关键词,jieba的实现中是利用已经训练好的idf来作的,对于比较专业的领域可能会有偏差。如果有小伙伴儿知道jieba是用什么语料训练的,欢迎留言。

参考:

声明:原创文章,转载请注明出处。

发布了62 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/real_ilin/article/details/103935369
今日推荐