情感分析之TF-IDF算法
在这篇文章中,主要介绍的内容有:
1、将单词转换为特征向量
2、TF-IDF计算单词关联度
文本的预处理和分词。
如何将单词等分类数据转成为数值格式,以方便我们后面使用机器学习来训练模型。
一、将单词转换为特征向量
词袋模型(bag-of-words model):将文本以数值特征向量的形式来表示。主要通过两个步骤来实现词袋模型:
1、为整个文档集(包含了许多的文档)上的每个单词创建一个唯一的标记。
2、为每个文档构建一个特征向量,主要包含每个单词在文档上的出现次数。
注意:由于每个文档中出现的单词数量只是整个文档集中很少的一部分,因此会有很多的单词没有出现过,就会被标记为0。所以,特征向量中大多数的元素就会为0,就会产生稀疏矩阵。
下面通过sklearn的CountVectorizer来实现一个词袋模型,将文档转换成为特征向量
通过count.vocabulary_我们可以看出每个单词所对应的索引位置,每一个句子都是由一个6维的特征向量所组成。其中,***列的索引为0,对应单词"and","and"在***和二条句子中没有出现过,所以为0,在第三条句子中出现过一些,所以为1。特征向量中的值也被称为原始词频(raw term frequency)简写为tf(t,d),表示在文档d中词汇t的出现次数。
注意:在上面词袋模型中,我们是使用单个的单词来构建词向量,这样的序列被称为1元组(1-gram)或单元组(unigram)模型。除了一元组以外,我们还可以构建n元组(n-gram)。n元组模型中的n取值与特定的应用场景有关,如在反垃圾邮件中,n的值为3或4的n元组可以获得比较好的效果。下面举例说明一下n元组,如在"the weather is sweet"这句话中,
1元组:"the"、"weather"、"is"、"sweet"。
2元组:"the weather"、"weather is"、"is sweet"。
在sklearn中,可以设置CountVecorizer中的ngram_range参数来构建不同的n元组模型,默认ngram_range=(1,1)。
sklearn通过CountVecorizer构建2元组
二、TF-IDF计算单词关联度
在使用上面的方法来构建词向量的时候可能会遇到一个问题:一个单词在不同类型的文档中都出现,这种类型的单词其实是不具备文档类型的区分能力。我们通过TF-IDF算法来构建词向量,从而来克服这个问题。
词频-逆文档频率(TF-IDF,term frequency-inverse document frequency):tf-idf可以定义为词频×逆文档频率
其中tf(t,d)表示单词t在文档d中的出现次数,idf(t,d)为逆文档频率,计算公式如下
其中,nd表示文档的总数,df(t,d)表示包含单词t的文档d的数量。分母中加入常数1,是为了防止df(t,d)=0的情况,导致分母为0。取log的目的是保证当df(t,d)很小的时候,不会导致idf(t,d)过大。
通过sklearn的TfidfTransformer和CountVectorizer来计算tf-idf
可以发现"is"(第二列)和"the"(第六列),它们在三个句子中都出现过,它们对于文档的分类所提供的信息并不会很多,所以它们的tf-idf的值相对来说都是比较小的。
注意:sklearn中的TfidfTransformer的TF-IDF的计算与我们上面所定义TF-IDF的公式有所不同,sklearn的TF-IDF计算公式
通常在计算TF-IDF之前,会对原始词频tf(t,d)做归一化处理,TfidfTransformer是直接对tf-idf做归一化。TfidfTransformer默认使用L2归一化,它通过与一个未归一化特征向量L2范数的比值,使得返回向量的长度为1,计算公式如下:
下面通过一个例子来说明sklearn中的TfidfTransformer的tf-idf的计算过程,以上面的***句话"The sun is shining"为例子
1、计算原始词频
a、单词所对应的下标
b、计算第三句话的原始词频tf(t,d)
c、计算逆文档频率idf(t,d)
注意:其他的词在计算tf-idf都是0,因为原始词频为0,所以就不需要计算idf了,log是以自然数e为底。
d、计算tf-idf
所以,***个句子的tf-idf特征向量为[0,1,1.29,1.29,0,1,0]
e、tf-idf的L2归一化
sklearn中tfidf的计算与手工计算详解
引言:本周数据仓库与数据挖掘课程布置了word2vec的课程作业,要求是手动计算corpus中各个词的tfidf,并用sklearn验证自己计算的结果。但是博主手动计算的结果无论如何也与sklearn中的结果无法对应,在查阅大量资料无果的情况下,只好自己去阅读源码了,最后成功解决了这一问题。
作业:
(tf-idf计算这里网络上的其他文章基本都有,这里只给出基本的定义)
简介:TF-IDF(Term Frequency-Inversdocument Frequency)是一种常用于信息处理和数据挖掘的加权技术。该技术采用一种统计方法,根据字词的在文本中出现的次数和在整个语料中出现的文档频率来计算一个字词在整个语料中的重要程度。它的优点是能过滤掉一些常见的却无关紧要本的词语,同时保留影响整个文本的重要字词。
计算步骤:
1.计算TF
简介:TF,是Term Frequency的缩写,就是某个关键字出现的频率,即词库中的某个词在当前文章中出现的频率。
计算公式:
词频TF = 某个词在文章中出现的次数 / 本篇文章中词的总数
考虑到文章有长短之分,为了便于不同文章的比较,进行"词频"标准化。
其中如果一个词在文中出现的频率越多,说明这个词TF就越大。
2.计算IDF
英文全称:Inverse document Frequency,即“逆文档频率”。计算IDF需要一个语料库,用来模拟语言的使用环境。文档频率DF就是一个词在整个文库词典中出现的频率,如一个文件集中有100篇文章,共有10篇文章包含“机器学习”这个词,那么它的文档频率就是10/100=0.1,逆文档频率IDF就是这个值的倒数,即10。
计算公式:
IDF(N) = log(文档总数 / 出现N这一词汇的文档数目)
其中如果一个词越常见,那么分母就越大,逆文档频率就越小越接近0。
log表示对得到的值取对数。
3.计算TF-IDF=TF*IDF
(下面引用一段sklearn源码中的注释,可以帮助不了解的读者直接使用)
我相信绝大多数的朋友得到的也是上图的结果,这与手工计算的结果大相径庭。不止结果不一样,甚至词汇数目都对不上,这实在令人难以接受。那么究竟是怎么一回事呢?
1.更改CountVectorizer的初始化参数token_pattern=r"(?u)bw+b"
2.更改TfidfTransformer的初始化参数norm=None,***ooth_idf=False.
3.更改手工idf计算方式:由lg(以10为底)改为ln(以e为底)
经过以上步骤的处理,手工计算的tfidf和程序计算的tfidf就是相同的了,那么为什么会出现这样的问题呢?
还是从源码说起吧
源码中的注释部分这样解释token_pattern
个人 理解:CountVectorizer类在初始化时会默认词汇辨认形式为r"(?u)\b\w\w+\b",这是一个双字符以上的字符串,这样就导致了在原题目中"我","他","了","于"的丢失,这就是导致我们词汇数目不匹配的元凶!因为他们是单字符,所以我们把这个类的接受形式改为单字符即可( r"(?u)bw+b" )。
源码中的norm解释部分,以及***ooth_idf解释部分
norm很好理解,sklearn自动为我们做了l2正则化,所以我们的结果和他的不同。因此只要不使用正则化即可(norm=None)
那下面的***ooth_idf又是什么情况呢? (这里网上的各种资料简直是迷惑行为大赏,抄来抄去,说的全都含混不清)
首先我们要牢记,最基础的idf定义就是文章上面写的定义!!!
那其他的idf定义是正确的吗?比如idf = log(N + 1/ N(x) + 1),以及该式子的各种变形?
如果你使用了***ooth_idf,那么上式正确。
那么什么是***ooth_idf呢,举个栗子:
其实就是将出现过的所有词汇放入一个新生成的文章之中,确保idf初始定义中的分母不为0。
其实这是一个很搞的问题,sklearn中使用的是numpy库中的log函数,这个函数就是ln函数,在源码中所有的计算都是用的numpy.log(),这同样导致了我们的结果与程序完全不同。
虽然这个问题事后看来并不是一个非常困难的问题,但是因为这个问题需要更改好几个参数 甚至要更改自己 ,也因为某些垃圾博主只会抄袭,根本不深入研究,导致我根本找不到一个解决这个问题的博客,最后只能自己动手解决。尽管浪费了很多时间,但还是比较值得的,也希望对其他被这个问题困扰的同学有帮助吧