900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 机器学习实战之朴素贝叶斯与垃圾邮件分类

机器学习实战之朴素贝叶斯与垃圾邮件分类

时间:2020-05-12 09:22:27

相关推荐

机器学习实战之朴素贝叶斯与垃圾邮件分类

文章目录

一、实现原理1.1 贝叶斯理论与公式1.2 朴素贝叶斯分类器1.3 拉普拉斯修正1.4 分类小案例 二、代码实现2.1 数据准备与处理2.2 创建词汇表2.3 构建词袋/词集模型2.4 朴素贝叶斯分类器实现及结果 三、总结

一、实现原理

1.1 贝叶斯理论与公式

朴素贝叶斯是基于概率的一种推断,先展示公式:

其中,P(A)是先验概率,就是在事件B发生之前,我们对A事件概率的一个判断;

P(A|B)是后验概率,是在B事件发生之后,我们对A事件概率的重新评估;

P(B|A)/P(B)是可能性函数,这是一个调整因子,使得预估概率更接近真实概率。

于是条件概率就是:后验概率=先验概率 x 调整因子

根据一个样本的多种属性,判断它是正例还是负例,只要计算在这两个情况下的概率就行。即:p(y=1|x), p(y=0|x),比较他们的大小来确定样本类别。

因为要计算两次概率,关于它们的分母,是这个样本的属性在全部样本中的概率。而这两次计算,它们的分母是不变的,所以我们只要计算分子就行。于是有了下面的结论:

即:

1.2 朴素贝叶斯分类器

朴素贝叶斯分类器的训练器的训练过程就是基于训练集D估计类

先验概率P ( c ),并为每个属性估计条件概率

样本的属性分为离散与连续,先给出两个公式:

后面重点还是在于讲述离散属性的朴素贝叶斯分类。

朴素贝叶斯分类器(Naïve Bayes Classifier)采用了“属性条件独立性

假设”,即每个属性独立地对分类结果发生影响。

为方便公式标记,不妨记P(C=c|X=x)为P(c|x)。在假设每个属性都独立的情况下,贝叶斯公式可以修改为:

因为在1.1中,已经说明过,分母是相同的,于是去掉分母,得:

最终我们利用这个公式,在代码中实现概率的计算来对样本进行分类。

1.3 拉普拉斯修正

在用朴素贝叶斯分类判断文本类别的时候,要计算多个概率的乘积。如果样本中的某些单词不在词汇表中出现,则连乘后概率为0,无法进行判断。因此我们在计算概率的时要用拉普拉斯修正,公式如下:

1.4 分类小案例

在写代码前,先用一个小案例,通过贝叶斯分类器来判断文档类别。

我们已知训练集如下,每条文本都已经打上标签

经过处理,变成下面这样(顺带说一下,这里用到的是词袋模型,还有一种词集模型将在代码实现中细说):

问题:I love song是哪种类别?

省略单词 I

最终得到结果

二、代码实现

2.1 数据准备与处理

分别有25条被标记上ham与spam的邮件如下:

共50条邮件。将随机选择40条做训练,剩下10条用于测试。

在这些邮件中,要将符号去除,并且把单词一个个分割开,变成字符串列表

def textParse(bigString): # 将字符串转换为字符列表# * 会匹配0个或多个规则,split会将字符串分割成单个字符【python3.5+】; 这里使用\W 或者\W+ 都可以将字符数字串分割开,产生的空字符将会在后面的列表推导式中过滤掉listOfTokens = re.split(r'\W+', bigString) # 将特殊符号作为切分标志进行字符串切分,即非字母、非数字return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 除了单个字母,例如大写的I,其它单词变成小写

效果如下:

2.2 创建词汇表

将ham与spam里的单词全部拿出,创建一个不重复的词汇表,将来计算单词概率就是以这个词汇表为基准

def createVocabList(dataSet):vocabSet = set([]) # 创建一个空的不重复列表for document in dataSet:vocabSet = vocabSet | set(document) # 取并集return list(vocabSet)

这样就得到了一个字符串列表

2.3 构建词袋/词集模型

词袋模型和词集模型的区别在于,在统计一个文本里单词是否出现在词汇表里时,前者是统计个数,后者则是出现置1。而接下来用的方法是利用词袋模型计算。

def bagOfWords2VecMN(vocabList, inputSet):returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量# print(inputSet)# print(inputSet)for word in inputSet: # 遍历每个词条if word in vocabList: # 如果词条存在于词汇表中,则计数加一returnVec[vocabList.index(word)] += 1return returnVec # 返回词袋模型

def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量for word in inputSet: # 遍历每个词条if word in vocabList: # 如果词条存在于词汇表中,则置1returnVec[vocabList.index(word)] = 1else:print("the word: %s is not in my Vocabulary!" % word)return returnVec # 返回文档向量

每一个训练集样本的文档向量都是长度为词汇表长度的列表,里面包含的是该邮件单词出现在词汇表的次数。

2.4 朴素贝叶斯分类器实现及结果

首先要做的是拉普拉斯平滑。在这里令分母等于2,分子为1。然后遍历训练集,统计侮辱类与非侮辱类的分子。

这是侮辱类的分子,长度是词汇表长度。它的含义是:在训练集中,这些单词在对应的词汇表里出现的次数。

因此可以根据这个算概率:如,在侮辱类的情况下,每个单词在总侮辱类样本的单词数的比例。得到不取对数得结果:

可以看到数字太小。取对数的目的是防止下溢出。取完对数结果如下:

贴上代码

def trainNB0(trainMatrix, trainCategory):numTrainDocs = len(trainMatrix) # 计算训练的文档数目# trainCategory 文本类别# print(numTrainDocs)# print(trainCategory)numWords = len(trainMatrix[0]) # 计算每篇文档的词条数pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱类的概率# print((pAbusive))p0Num = np.ones(numWords)p1Num = np.ones(numWords) # 创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑p0Denom = 2.0p1Denom = 2.0 # 分母初始化为2,拉普拉斯平滑for i in range(numTrainDocs):if trainCategory[i] == 1: # 统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···p1Num += trainMatrix[i]# print(trainMatrix[i])# print(i, sum(trainMatrix[i]), p1Denom)p1Denom += sum(trainMatrix[i])# print(i, p1Denom)else: # 统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Num += trainMatrix[i]p0Denom += sum(trainMatrix[i])# print(p1Num / p1Denom)# 分母:一共有多少词# 分子:是个数组,表示每个索引下对应的词出现次数# np.log 默认以e为底# print(p1Num)# print(p1Denom, p0Denom)# print(p1Num)# print('不取对数:\n', p1Num / p1Denom)# p1Num / p1Denom 就是每个单词在总单词出现数中的概率p1Vect = np.log(p1Num / p1Denom) # 取对数,防止下溢出(数字太小导致)p0Vect = np.log(p0Num / p0Denom)# print(p1Vect == np.log(p1Num / p1Denom))# print("侮辱类:", p0Vect)# print("非侮辱类:", p1Vect)# print("文档属于侮辱类的概率:", pAbusive)return p0Vect, p1Vect, pAbusive # 返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

然后根据得到的数据计算两种情况下的概率,比较大小,实现分类。

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):# print(p1Vec)# print(vec2Classify * p1Vec)# print(len(vec2Classify))# print(pClass1)# print('class:')# print(np.log((pClass1)))# vec2Classify * p1Vec 表示待判断的文本在这个概率下的数组,其中vec2Classify是0,1组成的,结果就是0,或是对应的p1Vec的值# print(vec2Classify)# print(p1Vec)# print(vec2Classify * p1Vec)# print(sum(vec2Classify * p1Vec)) 统计这个数组里所有值# p1Vect = np.log(p1Num / p1Denom)已经取过一次对数,这里的sum(vec2Classify * p1Vec)就表示是对数p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # 对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)# print(p1)# print(p0)# print('----------------------------------------------------------------------------------------------------------')if p1 > p0:return 1else:return 0

多次运行,得到以下结果:

错误率并不算高。

三、总结

这次实验相比于逻辑斯蒂回归在数学上没那么难,代码实现也挺容易。但是对于连续型的变量在该实例里并没有体现,要深入研究的话在课后还是要多花时间的。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。