关于tfidf以及文本分类中贝叶斯模型的介绍,网络上有很多。然而,大部分文章存在着讲解片面的情况,且忽视了几个非常容易产生误解的地方。例如,tfidf的取值一般不是非负整数,其对应的文档向量并不满足多项分布,那为什么许多人将tfidf特征表示作为多项式贝叶斯分类器
一、如何计算tf-idf?
二、如何将多项式/伯努利贝叶斯分类器用于文本分类?二者有什么区别?
三、为什么tfidf后可以直接接多项式/伯努利贝叶斯用于文本分类
一、如何计算tf-idf?
这里我用了一个简单的例子,分别通过sklearn以及手工方式计算了tfidf值,目的是更清晰地展示出计算的过程。
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ["good nice nice",
"good great great great",
"nice nice great",
"bad bad sad mad",
"sad sad sad bad mad mad"]
tfidf_vect = TfidfVectorizer(use_idf=True, smooth_idf=True, norm=None)
tfidf_features = tfidf_vect.fit_transform(corpus)
print(tfidf_vect.get_feature_names())
“”“
打印出来的结果如下:
['bad', 'good', 'great', 'mad', 'nice', 'sad']
“”“
print(tfidf_features.toarray())
“”“
打印出来的结果如下,每一行对应一篇文档,
每一列对应['bad', 'good', 'great', 'mad', 'nice', 'sad']中的一个单词的tfidf值:
[[0. 1.69314718 0. 0. 3.38629436 0. ]
[0. 1.69314718 5.07944154 0. 0. 0. ]
[0. 0. 1.69314718 0. 3.38629436 0. ]
[3.38629436 0. 0. 1.69314718 0. 1.69314718]
[1.69314718 0. 0. 3.38629436 0. 5.07944154]]
“”“
“”“
我们来手动计算一下tfidf是否与代码的计算结果一致。
首先是计算公式:
tf(x) = x在某文档中出现的次数
idf(x) = 1 + ln((1+文档总数)/(1+出现了x的文档总数))
tfidf(x) = tf(x) * idf(x)
以上公式有两点需要注意的地方:(1)sklearn使用e为对数log的底。(2)这里的idf采用了平滑版本。
接下来我们来计算第一篇文档中单词good与nice的tfidf值:
tf(good) = 1 [good在文档一中出现了1次]
idf(good) = 1 + ln((1+5)/(1+2)) = 1 + ln2 【文档总数:5;出现了good的文档总数:2】
tf-idf(good) = tf(good) * idf(good) = 1.69314718
tf(nice) = 2 [nice在文档一中出现了2次]
idf(nice) = 1 + ln((1+5)/(1+2)) = 1 + ln1.5 【文档总数:5;出现了nice的文档总数:2】
tf-idf(nice) = tf(nice) * idf(nice) = 3.38629436
可以看出结果是一致的。
“”“
二、如何将多项式/伯努利贝叶斯分类器用于文本分类?二者有什么区别?
接着上面的例子,首先给出代码(注意这段代码要接着上一小节中的代码运行,下同)。注意为了方便理解,这里我用的是词袋模型(bag-of-words)。下一节中我们将探讨,tfidf如何作用于多项式/伯努利贝叶斯分类器?
from sklearn.naive_bayes import MultinomialNB, BernoulliNB
from sklearn.feature_extraction.text import CountVectorizer
doc = ['great nice nice']
bow_vect = CountVectorizer()
bow_features = bow_vect.fit_transform(corpus)
bow_doc = bow_vect.transform(doc)
print(bow_doc)
"""
得到bow(词袋模型)表示的文档,输出如下:
(0, 2) 1
(0, 4) 2
"""
clf1 = MultinomialNB()
clf1.fit(bow_features, [1, 1, 1, 0, 0])
print(clf1.predict_proba(bow_doc))
"""
参数[1, 1, 1, 0, 0]对应corpus中五篇文档的标签。
MultinomialNB(多项式贝叶斯)预测概率输出如下:
[[0.00530504 0.99469496]]
"""
"""
我们来讲解一下MultinomialNB(多项式贝叶斯)的原理。
首先介绍基本的贝叶斯公式P(c|doc)= P(c)* P(doc|c)/ P(doc),其中P(doc)由于在P(c=1|doc)与P(c=0|doc)中都会出现,可以不考虑。
再介绍MultinomialNB的计算公式:
P(c)= 类别c下的单词总数/所有类别下的单词总数
P(word|c)= 单词word在类c中出现的次数/类别c下的单词总数
平滑系数取1的平滑版本:P(word|c)= (单词word在类c中出现的次数+1)/(类别c下的单词总数+V),V是词汇表大小,注意重复单词在V中只算1个
使用MultinomialNB平滑版本的公式,我们对测试文档doc进行预测:
P(c = 1|doc = ['great nice nice'])= P(1)* P(doc = ['great nice nice']|1)= P(1)* P(great|1)* P(nice|1)* P(nice|1)
P(c = 0|doc = ['great nice nice'])= P(0)* P(doc = ['great nice nice']|0)= P(0)* P(great|0)* P(nice|0)* P(nice|0)
类别1下的单词总数是10,类别0下的单词总数是10,所有类别下的单词总数是20,因此P(1)= 10 / 20, P(2)= 10 / 20
单词great在类别1中出现的次数是4,类别1下的单词总数是10,词汇表V大小是6,因此P(great|1)= (4+1)/(10+6)
其余计算同理,不再赘述。
需要注意的是,这里计算出的预测概率是没有归一化的,而sklearn给出的是归一化后的结果。
"""
clf2 = BernoulliNB()
clf2.fit(bow_features, [1, 1, 1, 0, 0])
print(clf2.predict_proba(bow_doc))
"""
BernoulliNB(伯努利贝叶斯)预测概率输出如下:
[[0.00657917 0.99342083]]
"""
"""
我们来讲解一下BernoulliNB(伯努利贝叶斯)的原理。BernoulliNB的计算公式:
P(c)= 类别c下的文档总数/所有类别下的文档总数
P(word|c)= 包含单词word的文档在类别c中出现的次数/类别c下的文档总数
平滑系数取1的平滑版本:P(word|c)= (包含单词word的文档在类别c中出现的次数+1)/(类别c下的文档总数+C),C是类别数目。
使用BernoulliNB平滑版本的公式,我们对测试文档doc进行预测:
P(c = 1|doc = ['great nice nice'])= P(1)* P(doc = ['great nice nice']|1)= P(1)* P(great|1)* P(nice|1)* P(nice|1)
P(c = 0|doc = ['great nice nice'])= P(0)* P(doc = ['great nice nice']|0)= P(0)* P(great|0)* P(nice|0)* P(nice|0)
类别1下的文档总数是3,类别0下的文档总数是2,所有类别下的文档总数是5,因此P(1)= 3/5,P(0)= 2/5
包含单词great的文档在类别1中出现的次数是2,类别c下的文档总数是3,类别数目C包括1/0两个类别所以是2,因此P(great|1)=(2+1)/(3+2)
其余计算同理,不再赘述。
同样,这里计算出的预测概率是没有归一化的,而sklearn给出的是归一化后的结果。
"""
以上就是多项式/伯努利贝叶斯分类器用于文本分类的内容。本质上,在多项分布中,有n个事件(单词),每个事件的发生次数(单词数量)是随机变量;在二项分布中,有两个事件(包含单词与不包含单词),每个事件的发生次数(包含/不包含单词的文档数量)是随机变量。用一句大白话总结,多项式贝叶斯统计的是单词,伯努利贝叶斯统计的是出现了单词的文档,一个是单词级别的贝叶斯模型,一个是文档级别的贝叶斯模型。
三、为什么tfidf后可以直接接多项式/伯努利贝叶斯用于文本分类
不知道大家有没有好奇过,为什么网上很多关于文本分类的教程里,tf-idf直接就接上了多项式贝叶斯(MultinomialNB)?根据多项式贝叶斯的定义,其输入应该是服从多项分布的随机向量X=
我开始注意到这一点的时候,也是一头雾水。因此,我特地梳理了一下文本分类任务中朴素贝叶斯的用法,希望能给困惑中的朋友一点帮助。
我们从以下几个角度讨论这个问题:
(1)tf-idf与朴素贝叶斯有什么关系?
(2)tf-idf用于多项式贝叶斯(MultinomialNB)时,发生了什么?
(3)tf-idf用于伯努利贝叶斯(BernoulliNB)时,发生了什么?
首先回答(1),tf-idf与朴素贝叶斯有什么关系?
抛出观点:tf-idf与朴素贝叶斯没有任何关系!!
因为贝叶斯分类器本质上是数数!它只在乎这个特征/单词出现了多少次(多项式贝叶斯)或有没有出现(伯努利贝叶斯),而不在乎特征值是不是什么tf-idf。
那为什么网上的代码中,铺天盖地地在tf-idf后接多项式贝叶斯呢?它们错了吗?
要回答这个问题,我们来回答问题(2):tf-idf用于多项式贝叶斯(MultinomialNB)时,发生了什么?
sklearn关于问题(2),有一段官方解释如下,【地址】:
The multinomial Naive Bayes classifier is suitable for classification with discrete features (e.g., word counts for text classification). The multinomial distribution normally requires integer feature counts. However, in practice, fractional counts such as tf-idf may also work.
也就是说,sklearn在实现中,是认为tf-idf可以作用于多项式贝叶斯的。但是具体的实现方式似乎也没有说得很清楚?
于是我又查阅了很多网络上的资料,在stackoverflow上找到了一段解释【地址】:
The (traditional) Multinomial N.B. model considers a document D as a vocabulary-sized feature vector x, where each element xi is the count of term i i document D. By definition, this vector x then follows a multinomial distribution, leading to the characteristic classification function of MNB.
When using TF-IDF weights instead of term counts, our feature vectors are (most likely) not following a multinomial distribution anymore, so the classification function is not theoretically well-founded anymore. However, it does turn out that tf-idf weights instead of counts work (much) better.
How would TFIDF values even work with this formula?
In the exact same way, except that the feature vector x is now a vector of tf-idf weights and not counts.
总结一下,就是说,使用tf-idf特征时,文档向量确实是不符合多项分布的。但是,我们可以假装其符合多项分布,照常使用多项式贝叶斯的公式来进行文本分类。唯一要注意的地方是,我们将分数形式的tf-idf权重看作单词在某文档中出现的次数。
而这样做的理由是,实践表明,使用tf-idf+多项式贝叶斯的结果,要比bow+多项式贝叶斯更好。
我们来看一段代码,分别使用tf-idf与bow训练多项式贝叶斯。(继续接着上一段代码运行的,下同)
clf1 = MultinomialNB()
clf1.fit(tfidf_features, [1, 1, 1, 0, 0])
tfidf_doc = tfidf_vect.transform(doc)
print(clf1.predict_proba(tfidf_doc))
"""
输出tf-idf权重下MultinomialNB的预测概率:
[[1.99675011e-05 9.99980032e-01]]
"""
clf2 = MultinomialNB()
clf2.fit(bow_features, [1, 1, 1, 0, 0])
print(clf2.predict_proba(bow_doc))
"""
输出bag-of-words权重下MultinomialNB的预测概率:
[[0.00530504 0.99469496]]
"""
可以看出,预测结果是不同的。
最后是问题(3):tf-idf用于伯努利贝叶斯(BernoulliNB)时,发生了什么?
直接上代码(继续接着上一段代码运行的)。
clf1 = BernoulliNB()
clf1.fit(tfidf_features, [1, 1, 1, 0, 0])
tfidf_doc = tfidf_vect.transform(doc)
print(clf1.predict_proba(tfidf_doc))
"""
输出tf-idf权重下BernoulliNB的预测概率:
[[0.00657917 0.99342083]]
"""
clf2 = BernoulliNB()
clf2.fit(bow_features, [1, 1, 1, 0, 0])
print(clf2.predict_proba(bow_doc))
"""
输出bag-of-words权重下BernoulliNB的预测概率:
[[0.00657917 0.99342083]]
"""
可以看出,预测结果相同。伯努利贝叶斯不在乎输入的是tf-idf特征还是bow特征,因为它考虑的仅仅是这个特征/单词是否存在,而不在乎它的权重是多少!
至此,关于tfidf及多项式/伯努利贝叶斯用于文本分类的介绍完毕。
如有疏漏,欢迎指正~