CategoryResourceRepost/极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/21丨朴素贝叶斯分类(下):如何对文档进行分类?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

324 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="21丨朴素贝叶斯分类如何对文档进行分类" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ee/e3/eec0c33f4d835287d62cc89d4c1e58e3.mp3"></audio>
我们上一节讲了朴素贝叶斯的工作原理,今天我们来讲下这些原理是如何指导实际业务的。
朴素贝叶斯分类最适合的场景就是文本分类、情感分析和垃圾邮件识别。其中情感分析和垃圾邮件识别都是通过文本来进行判断。从这里你能看出来这三个场景本质上都是文本分类这也是朴素贝叶斯最擅长的地方。所以朴素贝叶斯也常用于自然语言处理NLP的工具。
今天我带你一起使用朴素贝叶斯做下文档分类的项目最重要的工具就是sklearn这个机器学习神器。
## sklearn机器学习包
sklearn的全称叫Scikit-learn它给我们提供了3个朴素贝叶斯分类算法分别是高斯朴素贝叶斯GaussianNB、多项式朴素贝叶斯MultinomialNB和伯努利朴素贝叶斯BernoulliNB
这三种算法适合应用在不同的场景下,我们应该根据特征变量的不同选择不同的算法:
**高斯朴素贝叶斯**:特征变量是连续变量,符合高斯分布,比如说人的身高,物体的长度。
**多项式朴素贝叶斯**特征变量是离散变量符合多项分布在文档分类中特征变量体现在一个单词出现的次数或者是单词的TF-IDF值等。
**伯努利朴素贝叶斯**特征变量是布尔变量符合0/1分布在文档分类中特征是单词是否出现。
伯努利朴素贝叶斯是以文件为粒度如果该单词在某文件中出现了即为1否则为0。而多项式朴素贝叶斯是以单词为粒度会计算在某个文件中的具体次数。而高斯朴素贝叶斯适合处理特征变量是连续变量且符合正态分布高斯分布的情况。比如身高、体重这种自然界的现象就比较适合用高斯朴素贝叶斯来处理。而文本分类是使用多项式朴素贝叶斯或者伯努利朴素贝叶斯。
## 什么是TF-IDF值呢
我在多项式朴素贝叶斯中提到了“词的TF-IDF值”如何理解这个概念呢
TF-IDF是一个统计方法用来评估某个词语对于一个文件集或文档库中的其中一份文件的重要程度。
TF-IDF实际上是两个词组Term Frequency和Inverse Document Frequency的总称两者缩写为TF和IDF分别代表了词频和逆向文档频率。
**词频TF**计算了一个单词在文档中出现的次数,它认为一个单词的重要性和它在文档中出现的次数呈正比。
**逆向文档频率IDF**是指一个单词在文档中的区分度。它认为一个单词出现在的文档数越少就越能通过这个单词把该文档和其他文档区分开。IDF越大就代表该单词的区分度越大。
**所以TF-IDF实际上是词频TF和逆向文档频率IDF的乘积**。这样我们倾向于找到TF和IDF取值都高的单词作为区分即这个单词在一个文档中出现的次数多同时又很少出现在其他文档中。这样的单词适合用于分类。
## TF-IDF如何计算
首先我们看下词频TF和逆向文档概率IDF的公式。
<img src="https://static001.geekbang.org/resource/image/bc/4d/bc31ff1f31f9cd26144404221f705d4d.png" alt="">
<img src="https://static001.geekbang.org/resource/image/b7/65/b7ad53560f61407e6964e7436da14365.png" alt="">
为什么IDF的分母中单词出现的文档数要加1呢因为有些单词可能不会存在文档中为了避免分母为0统一给单词出现的文档数都加1。
**TF-IDF=TF*IDF。**
你可以看到TF-IDF值就是TF与IDF的乘积,这样可以更准确地对文档进行分类。比如“我”这样的高频单词虽然TF词频高但是IDF值很低整体的TF-IDF也不高。
我在这里举个例子。假设一个文件夹里一共有10篇文档其中一篇文档有1000个单词“this”这个单词出现20次“bayes”出现了5次。“this”在所有文档中均出现过而“bayes”只在2篇文档中出现过。我们来计算一下这两个词语的TF-IDF值。
针对“this”计算TF-IDF值
<img src="https://static001.geekbang.org/resource/image/63/12/63abe3ce8aa0ea4a78ba537b5504df12.png" alt="">
<img src="https://static001.geekbang.org/resource/image/b5/7e/b5ac88c4e2a71cc2d4ceef4c01e0ba7e.png" alt="">
所以TF-IDF=0.02*(-0.0414)=-8.28e-4。
针对“bayes”计算TF-IDF值
<img src="https://static001.geekbang.org/resource/image/3b/8d/3bbe56a7b76513604bfe6b39b890dd8d.png" alt="">
<img src="https://static001.geekbang.org/resource/image/1e/2e/1e8b7465b9949fe071e95aede172a52e.png" alt="">
TF-IDF=0.005*0.5229=2.61e-3。
很明显“bayes”的TF-IDF值要大于“this”的TF-IDF值。这就说明用“bayes”这个单词做区分比单词“this”要好。
**如何求TF-IDF**
在sklearn中我们直接使用TfidfVectorizer类它可以帮我们计算单词TF-IDF向量的值。在这个类中取sklearn计算的对数log时底数是e不是10。
下面我来讲下如何创建TfidfVectorizer类。
## TfidfVectorizer类的创建
创建TfidfVectorizer的方法是
```
TfidfVectorizer(stop_words=stop_words, token_pattern=token_pattern)
```
我们在创建的时候有两个构造参数可以自定义停用词stop_words和规律规则token_pattern。需要注意的是传递的数据结构停用词stop_words是一个列表List类型而过滤规则token_pattern是正则表达式。
什么是停用词停用词就是在分类中没有用的词这些词一般词频TF高但是IDF很低起不到分类的作用。为了节省空间和计算时间我们把这些词作为停用词stop words告诉机器这些词不需要帮我计算。
<img src="https://static001.geekbang.org/resource/image/04/e9/040723cc99b36e8ad7e45aa31e0690e9.png" alt=""><br>
当我们创建好TF-IDF向量类型时可以用fit_transform帮我们计算返回给我们文本矩阵该矩阵表示了每个单词在每个文档中的TF-IDF值。
<img src="https://static001.geekbang.org/resource/image/0d/43/0d2263fbc97beb520680382f08656b43.png" alt=""><br>
在我们进行fit_transform拟合模型后我们可以得到更多的TF-IDF向量属性比如我们可以得到词汇的对应关系字典类型和向量的IDF值当然也可以获取设置的停用词stop_words。
<img src="https://static001.geekbang.org/resource/image/a4/6b/a42780a5bca0531e75a294b4e2fe356b.png" alt=""><br>
举个例子假设我们有4个文档
文档1this is the bayes document
文档2this is the second second document
文档3and the third one
文档4is this the document。
现在想要计算文档里都有哪些单词这些单词在不同文档中的TF-IDF值是多少呢
首先我们创建TfidfVectorizer类
```
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vec = TfidfVectorizer()
```
然后我们创建4个文档的列表documents并让创建好的tfidf_vec对documents进行拟合得到TF-IDF矩阵
```
documents = [
'this is the bayes document',
'this is the second second document',
'and the third one',
'is this the document'
]
tfidf_matrix = tfidf_vec.fit_transform(documents)
```
输出文档中所有不重复的词:
```
print('不重复的词:', tfidf_vec.get_feature_names())
```
运行结果
```
不重复的词: ['and', 'bayes', 'document', 'is', 'one', 'second', 'the', 'third', 'this']
```
输出每个单词对应的id值
```
print('每个单词的ID:', tfidf_vec.vocabulary_)
```
运行结果
```
每个单词的ID: {'this': 8, 'is': 3, 'the': 6, 'bayes': 1, 'document': 2, 'second': 5, 'and': 0, 'third': 7, 'one': 4}
```
输出每个单词在每个文档中的TF-IDF值向量里的顺序是按照词语的id顺序来的
```
print('每个单词的tfidf值:', tfidf_matrix.toarray())
```
运行结果:
```
每个单词的tfidf值: [[0. 0.63314609 0.40412895 0.40412895 0. 0.
0.33040189 0. 0.40412895]
[0. 0. 0.27230147 0.27230147 0. 0.85322574
0.22262429 0. 0.27230147]
[0.55280532 0. 0. 0. 0.55280532 0.
0.28847675 0.55280532 0. ]
[0. 0. 0.52210862 0.52210862 0. 0.
0.42685801 0. 0.52210862]]
```
## 如何对文档进行分类
如果我们要对文档进行分类,有两个重要的阶段:
<img src="https://static001.geekbang.org/resource/image/25/c3/257e01f173e8bc78b37b71b2358ff7c3.jpg" alt="">
<li>
**基于分词的数据准备**,包括分词、单词权重计算、去掉停用词;
</li>
<li>
**应用朴素贝叶斯分类进行分类**,首先通过训练集得到朴素贝叶斯分类器,然后将分类器应用于测试集,并与实际结果做对比,最终得到测试集的分类准确率。
</li>
下面,我分别对这些模块进行介绍。
**模块1对文档进行分词**
在准备阶段里,最重要的就是分词。那么如果给文档进行分词呢?英文文档和中文文档所使用的分词工具不同。
在英文文档中最常用的是NTLK包。NTLK包中包含了英文的停用词stop words、分词和标注方法。
```
import nltk
word_list = nltk.word_tokenize(text) #分词
nltk.pos_tag(word_list) #标注单词的词性
```
在中文文档中最常用的是jieba包。jieba包中包含了中文的停用词stop words和分词方法。
```
import jieba
word_list = jieba.cut (text) #中文分词
```
**模块2加载停用词表**
我们需要自己读取停用词表文件从网上可以找到中文常用的停用词保存在stop_words.txt然后利用Python的文件读取函数读取文件保存在stop_words数组中。
```
stop_words = [line.strip().decode('utf-8') for line in io.open('stop_words.txt').readlines()]
```
**模块3计算单词的权重**
这里我们用到sklearn里的TfidfVectorizer类上面我们介绍过它使用的方法。
直接创建TfidfVectorizer类然后使用fit_transform方法进行拟合得到TF-IDF特征空间features你可以理解为选出来的分词就是特征。我们计算这些特征在文档上的特征向量得到特征空间features。
```
tf = TfidfVectorizer(stop_words=stop_words, max_df=0.5)
features = tf.fit_transform(train_contents)
```
这里max_df参数用来描述单词在文档中的最高出现率。假设max_df=0.5代表一个单词在50%的文档中都出现过了,那么它只携带了非常少的信息,因此就不作为分词统计。
一般很少设置min_df因为min_df通常都会很小。
**模块4生成朴素贝叶斯分类器**
我们将特征训练集的特征空间train_features以及训练集对应的分类train_labels传递给贝叶斯分类器clf它会自动生成一个符合特征空间和对应分类的分类器。
这里我们采用的是多项式贝叶斯分类器其中alpha为平滑参数。为什么要使用平滑呢因为如果一个单词在训练样本中没有出现这个单词的概率就会被计算为0。但训练集样本只是整体的抽样情况我们不能因为一个事件没有观察到就认为整个事件的概率为0。为了解决这个问题我们需要做平滑处理。
当alpha=1时使用的是Laplace平滑。Laplace平滑就是采用加1的方式来统计没有出现过的单词的概率。这样当训练样本很大的时候加1得到的概率变化可以忽略不计也同时避免了零概率的问题。
当0&lt;alpha&lt;1时使用的是Lidstone平滑。对于Lidstone平滑来说alpha 越小迭代次数越多精度越高。我们可以设置alpha为0.001。
```
# 多项式贝叶斯分类器
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB(alpha=0.001).fit(train_features, train_labels)
```
**模块5使用生成的分类器做预测**
首先我们需要得到测试集的特征矩阵。
方法是用训练集的分词创建一个TfidfVectorizer类使用同样的stop_words和max_df然后用这个TfidfVectorizer类对测试集的内容进行fit_transform拟合得到测试集的特征矩阵test_features。
```
test_tf = TfidfVectorizer(stop_words=stop_words, max_df=0.5, vocabulary=train_vocabulary)
test_features=test_tf.fit_transform(test_contents)
```
然后我们用训练好的分类器对新数据做预测。
方法是使用predict函数传入测试集的特征矩阵test_features得到分类结果predicted_labels。predict函数做的工作就是求解所有后验概率并找出最大的那个。
```
predicted_labels=clf.predict(test_features)
```
**模块6计算准确率**
计算准确率实际上是对分类模型的评估。我们可以调用sklearn中的metrics包在metrics中提供了accuracy_score函数方便我们对实际结果和预测的结果做对比给出模型的准确率。
使用方法如下:
```
from sklearn import metrics
print metrics.accuracy_score(test_labels, predicted_labels)
```
## 数据挖掘神器sklearn
从数据挖掘的流程来看,一般包括了获取数据、数据清洗、模型训练、模型评估和模型部署这几个过程。
sklearn中包含了大量的数据挖掘算法比如三种朴素贝叶斯算法我们只需要了解不同算法的适用条件以及创建时所需的参数就可以用模型帮我们进行训练。在模型评估中sklearn提供了metrics包帮我们对预测结果与实际结果进行评估。
在文档分类的项目中我们针对文档的特点给出了基于分词的准备流程。一般来说NTLK包适用于英文文档而jieba适用于中文文档。我们可以根据文档选择不同的包对文档提取分词。这些分词就是贝叶斯分类中最重要的特征属性。基于这些分词我们得到分词的权重即特征矩阵。
通过特征矩阵与分类结果,我们就可以创建出朴素贝叶斯分类器,然后用分类器进行预测,最后预测结果与实际结果做对比即可以得到分类器在测试集上的准确率。
<img src="https://static001.geekbang.org/resource/image/2e/6e/2e2962ddb7e85a71e0cecb9c6d13306e.png" alt="">
## 练习题
我已经讲了中文文档分类中的6个关键的模块最后我给你留一道对中文文档分类的练习题吧。
我将中文文档数据集上传到了GitHub上[点击这里下载](https://github.com/cystanford/text_classification)。
数据说明:
1. 文档共有4种类型女性、体育、文学、校园
<img src="https://static001.geekbang.org/resource/image/67/28/67abc1783f7c4e7cd69194fafc514328.png" alt="">
1. 训练集放到train文件夹里测试集放到test文件夹里停用词放到stop文件夹里。
<img src="https://static001.geekbang.org/resource/image/0c/0f/0c374e3501cc28a24687bc030733050f.png" alt=""><br>
请使用朴素贝叶斯分类对训练集进行训练,并对测试集进行验证,并给出测试集的准确率。
最后你不妨思考一下,假设我们要判断一个人的性别,是通过身高、体重、鞋码、外貌等属性进行判断的,如果我们用朴素贝叶斯做分类,适合使用哪种朴素贝叶斯分类器?停用词的作用又是什么?
欢迎你在评论区进行留言,与我分享你的答案。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。