基于多项式朴素贝叶斯实现垃圾短信识别
一、实验内容
使用贝叶斯网络模型基于短信数据集实现垃圾短信识别。
二、实验目标
- 掌握贝叶斯网络模型
- 熟悉将文本转换为可处理向量的方法
- 熟练使用分词框架jieba分词相关API
- 熟悉数据集划分方法K折交叉验证
三、实验环境
- 操作系统:Ubuntu16
- 工具软件:jupyter notebook、Python 3.6.13
- 硬件环境:无特殊要求
- 核心库:
- jieba 0.42.1
- pandas 1.1.5
- numpy 1.19.4
- scikit-learn 0.24.2
四、实验原理
1 贝叶斯网络
贝叶斯方法源域它生前为解决一个“逆概”问题写的一篇文章。其要解决的问题:
正向概率:假设袋子里面有N个白球,M个黑球,你伸手进去摸一把,摸出黑球的概率是多大
逆向概率:如果我们事先不知道袋子里面黑白球的比例,而是闭着眼睛摸出一个(或者好几个)球,观察这些取出来的球的颜色之后,那么我们可以就此对袋子里面的黑白球的比例做出什么样的推测。
那么什么是贝叶斯呢?
- 现实世界本身就是不确定的,人类的观察能力是有局限性的
- 我们日常观察到的只是事物表明上的结果,因此我们需要提供一个猜测
NaiveBayes算法,又称朴素贝叶斯算法。朴素:特征条件独立;贝叶斯:基于贝叶斯定理。属于监督学习的生成模型,实现监督,没有迭代,并有坚实的数学理论(即贝叶斯定理)作为支撑。在大量样本下会有较好的表现,不适用于输入向量的特征条件有关联的场景。
朴素贝叶斯会单独考量每一维独立特征被分类的条件概率,进而综合这些概率并对其所在的特征向量做出分类预测。因此,朴素贝叶斯的基本数据假设是:各个维度上的特征被分类的条件概率之间是相互独立的。它经常被用于文本分类中,包括互联网新闻的分类,垃圾邮件的筛选。
朴素贝叶斯的思想基础是这样的:对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,哪个最大,即认为此待分类项属于哪个类别。
三种常见的贝叶斯网络
- 多项式模型(MultinomialNB)
- 多项式朴素贝叶斯常用语文本分类,特征是单词,值时单词出现的次数。
from sklearn.naive_bayes import MultinomialNB
- 高斯模型(GaussianNB)
- 当特征是连续变量的时候,假设特征分布为正态分布,根据样本算出均值和方差,再求得概率。
from sklearn.naive_bayes import GaussianNB
- 伯努利模型(BernoulliNB)
- 伯努利模型适用于离散特征的情况,伯努利模型中每个特征的取值只能是1和0。
2 结巴分词
"结巴"中文分词:做最好的Python中文分词组件 “Jieba”
- git地址
https://github.com/rainforest32/jieba - 安装
pip install jieba
特征
- 支持三种分词模式:
- 精确模式,试图将句子最精确地切开,适合文本分析;
- 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
- 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
- 支持繁体分词
- 支持自定义词典
功能分词
jieba.cut方法接受两个输入参数: 1) 第一个参数为需要分词的字符串; 2)cut_all参数用来控制是否采用全模式jieba.cut_for_search方法接受一个参数:需要分词的字符串,该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细- 注意:待分词的字符串可以是gbk字符串、utf-8字符串或者unicode
jieba.cut以及jieba.cut_for_search返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode),也可以用 list(jieba.cut(…)) 转化为 list
代码示例
#encoding=utf-8
import jieba
seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
print "Full Mode:", "/ ".join(seg_list) # 全模式
seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print "Default Mode:", "/ ".join(seg_list) # 精确模式
seg_list = jieba.cut("他来到了网易杭研大厦")
print ", ".join(seg_list) # 默认是精确模式
seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")
print ", ".join(seg_list) # 搜索引擎模式
输出结果:
【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
【精确模式】: 我/ 来到/ 北京/ 清华大学
【新词识别】:他, 来到, 了, 网易, 杭研, 大厦 (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)
【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造
更多功能参考对应git项目地址。
3 文本特征提取
本特征提取是将文本数据转化成特征向量的过程比较常用的文本特征表示法为词袋法。
词袋法:
- 不考虑词语出现的顺序,每个出现过的词汇单独作为一列特征
- 这些不重复的特征词汇集合为词表
- 每一个文本都可以在很长的词表上统计出一个很多列的特征向量
- 如果每个文本都出现的词汇,一般被标记为 停用词 不计入特征向量
sklearn主要有两个API来实现 CountVectorizer 和 TfidfVectorizer
- CountVectorizer:只考虑词汇在文本中出现的频率
- TfidfVectorizer:
from sklearn.feature_extraction.text import- TfidfVectorizer 除了考量某词汇在文本出现的频率,还关注包含这个词汇的所有文本的数量 能够削减高频没有意义的词汇出现带来的影响, 挖掘更有意义的特征
相比之下,文本条目越多,Tfid的效果会越显著
4 K折交叉验证
交叉验证的基本思想是把在某种意义下将原始数据集(dataset)进行分组,一部分作为训练集(trainset),另一部分作为验证集(validation set or test set),首先用训练对分类器进行训练,再利用验证集来测试训练得到的模型(model),以此来作为评判分类器的性能指标
- 优点:能比较鲁棒性地评估模型在未知数据上的性能.
- 缺点:计算复杂度较大.因此,在数据集较大,模型复杂度较高,或者计算资源不是很充沛的情况下,可能不适用,尤其是在训练深度学习模型的时候.
- 所在包sklearn.model_selection提供了KFold以及RepeatedKFold, LeaveOneOut, LeavePOut, ShuffleSplit, StratifiedKFold, GroupKFold, TimeSeriesSplit等变体,常用的 有:
KFold和StratifiedKFold
KFold()方法
KFlod : KFold 将所有的样例分为k个组,称为折叠(fold),每组数据都具有相同的大小。每一次分割会将其中的K-1组作为训练数据,剩下的一组用作测试数据,一共会分割K次。
可以通过这张图直观的提现出来。(这是四折交叉验证,既K取值为4)

StratifiedKFold()方法
StratifiedKFold: 是KFold()的变种,采用分层分组的形式(有点类似分层抽样), 使每个分组中各类别的比例 同整体数据中各类别的比例尽可能的相同。(它相对于KFold()方法更完善)
所在包: from sklearn.model_selection import StratifiedKFold
示例代码
import numpy as np
from sklearn.model_selection import StratifiedKFold
X=np.array([[1,2],[3,4],[5,6],[7,8],[9,10],[11,12]])
y=np.array([1,1,1,2,2,2])
skf=StratifiedKFold(n_splits=2)
print(skf.get_n_splits(X,y)) # 返回交叉验证器中拆分迭代的次数
#for循环中的train_index与test_index是索引而并非我们的训练数据
for train_index,test_index in skf.split(X,y): # split生成索引以将数据拆分为训练集和测试集。
print("Train Index:",train_index,",Test Index:",test_index)
X_train,X_test=X[train_index],X[test_index]
y_train,y_test=y[train_index],y[test_index]
输出结果:
2
Train Index: [2 4 5] ,Test Index: [0 1 3]
Train Index: [0 1 3] ,Test Index: [2 4 5]
五、实验步骤
1 导入数据
import pandas as pd
data=pd.read_table('../data/noteData.txt',sep='\t', header=None, nrows=10000, names=["标签","短信内容"])
data.head()
我们的数据分为两部分,一部分是数据标签0代表正常短信,1代表垃圾短信。另外一部分为短信内容。

看一下数据形状,一共有7万多条数据,这条我仅取用其中1万条
data.shape
# 结果
(10000, 2)
2 进行分词
import jieba
data['分词后数据']=data["短信内容"].apply(lambda x:' '.join(jieba.cut(x)))
data.head()

这里我采用结巴分词,将句子切分为单词。
结巴分词的作用是什么呢?如下
分词前:我来到北京清华大学
分词后:我 / 来到 / 北京 / 清华大学
3 导入停用词
f = open('../dataset/my_stop_words.txt','r')
my_stop_words_data = f.readlines()
f.close()
my_stop_words_list=[]
for each in my_stop_words_data:
my_stop_words_list.append(each.strip('\n'))
什么是停用词呢?简单来讲就是对我们理解语意无关紧要的字或者词(例如下面这些),为了防止这些字词对后面的训练造成影响,需要先剔除这些字词。
:$ 0 1 2 3 4 5 6 7 8 9 ? _ “ ” 、 。 《 》 一 一些 一何 一切 一则 一方面 一旦 一来 一样
一般 一转眼 万一 上 上下 下 不 不仅 不但 不光 不单 不只 不外乎 不如 不妨 不尽 不尽然 不得 不
怕 不惟 不成 不拘 不料 呃 呕 呗 呜 呜呼...
4 提取特征和目标数据
X = data['分词后数据']
y = data['标签']
5 模型训练&预测打分
from sklearn.model_selection import StratifiedKFold
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
skf = StratifiedKFold(n_splits=10, random_state=1, shuffle=True)
for train_index, test_index in skf.split(X, y):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# 使用朴素贝叶斯分类器 ,采用TfidfVectorizer提取文本特征向量,默认配置不去除停用词
pipeline = Pipeline([('vect',TfidfVectorizer(stop_words=my_stop_words_list)),
('clf', MultinomialNB(alpha=1.0))])
pipeline.fit(X_train,y_train)
#进行预测
predict = pipeline.predict(X_test)
score = pipeline.score(X_test,y_test)
print(score)
"""
输出:
0.948051948051948
0.949050949050949
0.955044955044955
0.954045954045954
0.951048951048951
0.9469469469469469
0.950950950950951
0.948948948948949
0.9419419419419419
0.944944944944945
"""
代码进行一些补充说明:
TfidfVectorizer相当于先后调用CountVectorizer和TfidfTransformer两种方法。CountVectorizer用于将文本从标量转换为向量,TfidfTransformer则将向量文本转换为tf-idf矩阵。如何简单来理解这个方法:一句话,让我们的预测更加准确的一种数据预处理方法。
看一下我们最后的预测效果
data["数据类型"] = pipeline.predict(X) #lambda x:x+1 if not 2==1 else 0
data['数据类型']=data["数据类型"].apply(lambda x:"垃圾短信" if x==1 else "正常短信")
data.head()

申明一下,这个里面是有垃圾短信的啊,它们只是没显示出。
六、实验总结
朴素贝叶斯模型被广泛应用于海量互联网文本分类任务。由于其较强的特征条件独立假设,使得模型预测所需要顾及的参数规模从幂指数数量级向线性量级减少,极大的节约了内存消耗和计算时间。但是,也正是受这种强假设的限制,模型训练时无法将各个特征之间的联系考量在内,使得该模型在其他数据特征关联性较强的分类任务上性能表现不佳。
在scikit-learn中,提供了三种朴素贝叶斯分类算法:GaussianNB(高斯分布的朴素贝叶斯),MultinomailNB(先验为多项式分布的朴素贝叶斯),BernoulliNB(先验为伯努利分布的朴素贝叶斯)。
这三个类适用的分类场景各不相同,一般来说,如果样本特征的分布大部分是连续值,适用 GaussianNB 会比较好。如果样本特征的分布大部分是多元离散值,使用 MultinomialNB 比较合适,而且 MultinomialNB 常用语文本分类。而如果样本特征是二元离散值或者很稀疏的多元离散值,应该使用 BernoulliNB 。

1万+

被折叠的 条评论
为什么被折叠?



