[TOC]
一、朴素贝叶斯基础理论
这部分内容转载自:https://www.kesci.com/home/project/5cededa67a818c002b6ec64a
博主讲得很清晰,并介绍了scikit-learn中封装的库如何调用
1. 前言
机器学习中常见的一个问题就是如何把未知的数据分到先前已经知晓的类别中去。
比如我们想对一个未知的水果进行分类,而我们已经知道的分类特征如下:
如图所示,我们有三个现有的水果类别:苹果,蓝莓和椰子。
这些水果中的每一种都有三个我们关心的特征:大小,重量和颜色。
通过观察未知水果我们发现,这个水果大小适中(moderate),但是很重(heavy),并且还是红色(red)的。
我们可以将这些特征与我们已经知道的特征进行比较,由此判断它是什么类型的水果。
若未知水果像椰子一样重,但另外两个特征与苹果相同,我们将猜测它是苹果。
这个例子比较简单,但是它体现了在分类问题中的一种思想:在分类问题中,我们会将未知标签的数据集特征与我们已知标签的特征进行比较。
朴素贝叶斯,就是这样一种比较的方法。
2. 简介
朴素贝叶斯是一种分类技术,它会使用我们已知的数据概率来对未知的数据点进行分类。
这种概率和现有的数据分布以及数据的标签有关。
正如上面例子所言,我们将最类似于未知数据的分类,作为未知数据的标签。
Naive Bayes(朴素贝叶斯)技术的理论依据是贝叶斯定理(Bayes’ Theorem),在下面我们将详细阐述。
3. 贝叶斯定理
首先我们引入两个个概念:概率、条件概率
概率:事件A发生的可能性就被称为A发生的概率,用P(A)来表示
条件概率:当事件B已经发生的时候,事件A发生的可能性,称为在B条件下A发生的条件概率,用P(A|B)表示
那么针对于事件A、B同时发生的概率,就可以用P(AB)来表示
因此我们可以得出以下公式:
举个例子:
一起汽车撞人逃跑事件,已知只有两种颜色的车,比例为蓝色20% 绿色80%,目击者指证是蓝车。
但根据现场分析,当时那种条件下,目击者看车的颜色正确的可能性是60%。
那么,肇事的车是蓝车的概率到底是多少?
首先,事件B = {目击者看到车为蓝色},事件A = {车本来就是蓝色}
4. 朴素贝叶斯理论
朴素贝叶斯的基本方法:在统计数据的基础上,依据条件概率公式,计算当前特征的样本属于某个分类的概率,选最大的概率分类
5. 算法
下面讲解一些朴素贝叶斯的基本算法,我们可以根据算法的特征类型将其分成两类。
- 连续:这意味着最终的标签为实值(可以存在小数)
- 离散:这以为着最终的结果为分类的类别值(只能为整数)
5.1 高斯模型(连续)
高斯模型假设特征的分布是属于正态分布的。
在处理连续的特征变量时,我们应该选择使用高斯模型。
正态分布的概率密度函数如图所示:
#导入相应的包
import numpy as np
#导入高斯模型
from sklearn.naive_bayes import GaussianNB
#样本X包含三个特征,分别是Red的百分比,Green的百分比,Blue的百分比
#每个特征的值都是(0,1)之间的小数
#首先我们创建一个训练集
X = np.array([[.5, 0, .5], [1, 1, 0], [0, 0, 0]])
#给定我们训练集的分类标签
y = np.array(['Purple', 'Yellow', 'Black'])
#运用高斯模型去训练数据
clf = GaussianNB()
#训练数据集
clf.fit(X, y)
#下面我们运用我们的模型进行测试
#比如我们试一下,red 0.5,green 0.5,blue 0.5
print(clf.predict([[0.5, 0.5, 0.5]]))
#同学们可以使用不同的占比来做出预测,练习如何使用高斯模型
['Purple']
5.2 多项式模型(离散)
当我们的特征都是分类型特征的时候,可以使用多项式模型。
我们可以用它来计算特征中分类的出现频率。
特别的是,当特征只有两种的时候,我们将会使用多项式模型中的伯努利模型。
#导入相关的包
import numpy as np
#导入多项式模型
from sklearn.naive_bayes import MultinomialNB
#我们使用文章最开始的水果的数据集作为示例
#水果数据集的样本X具有三个特征[Size, Weight, Color]
#每个特征共有三种分类
#由于python不能直接识别文字
#所以将这个三个特征的不同分类重新编码如下
# Size: 0 = Small, 1 = Moderate, 2 = Large
# Weight: 0 = Light, 1 = Moderate, 2 = Heavy
# Color: 0 = Red, 1 = Blue, 2 = Brown
#用编码好的数据创建训练集
X = np.array([[1, 1, 0], [0, 0, 1], [2, 2, 2]])
#给训练集的数据创建标签
y = np.array(['Apple', 'Blueberry', 'Coconut'])
#运用多项式模型训练数据
clf = MultinomialNB()
#训练水果数据集
clf.fit(X, y)
#预测数据集
#比如我们试一下 size = 1,weight = 2,color = 0
print(clf.predict([[1, 2, 0]]))
#同学们可以使用不同的size、weight,color来做出预测,练习如何使用多项式模型
['Apple']
5.3 伯努利模型(离散)
上文中我们提到,当特征只有两种的时候,我们可以使用伯努利模型。
与多项式模型不同,我们在这里只计算一个特征是否发生。
比如,本页面是否存在【多项式】这三个字,答案只有两种,存在or不存在。
伯努利模型的条件概率计算方法与多项式模型一致。
下图展示了我们可能与伯努利模型一起使用的数据类别:
#导入包
import numpy as np
#导入伯努利模型
from sklearn.naive_bayes import BernoulliNB
#数据集X的特征有三个,分别是
# Walks like a duck
# Talks like a duck
# Is small]
#这三个特征分别有两种分布,是or否
# Walks like a duck: 0 = False, 1 = True
# Talks like a duck: 0 = False, 1 = True
# Is small: 0 = False, 1 = True
#创建训练集
X = np.array([[1, 1, 0], [0, 0, 1], [1, 0, 0]])
#给训练集创建标签
#是鸭子or不是鸭子
y = np.array(['Duck', 'Not a Duck', 'Not a Duck'])
#使用伯努利模型训练数据
clf = BernoulliNB()
#训练数据集
clf.fit(X, y)
#预测数据集
#比如我们试一下 三个特征都为true的时候,到底是不是鸭子
print( clf.predict([[1, 1, 1]]))
6. 总结
在本节内容中,我们学习了朴素贝叶斯的相关知识。
朴素贝叶斯可以让我们依据现有的数据特征和标签,对于预测数据进行分类。
正如前面代码所展现的那样,我们不需要很多数据就可以训练朴素贝叶斯模型。
朴素贝叶斯模型有另外一个优点,就是运行速度特别快,可以进行实时预测。
我们用朴素贝叶斯做了很多假设,所以要对结果要持保留态度,不能完全信任。
当我们没有太多数据,又需要迅速产生结果的时候,可以选择朴素贝叶斯模型。
二、使用朴素贝叶斯算法进行文本分类
任务描述为:
给定已经拆分成词表的评论组成的数据集,及其标签(侮辱性、非侮辱性)
构建朴素贝叶斯分类器
实现:判断某条评论是否是带有侮辱性的
1. 将词表转换为向量
相关的函数处理(代码使用jupyter notebook编写)
#encoding=utf-8
import numpy as np
%matplotlib inline
# 获得词表和这条评论的标签
def loadDataSet():
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0,1,0,1,0,1] #1 is abusive, 0 not
return postingList,classVec
- 构建词典
#将所有的评论词表合起来,求一个并集
#将所有评论中都出现过的单词组成一个总的词典(set去除重复)
def createVocabList(dataSet):
vocabSet = set([]) #create empty set
for document in dataSet:
vocabSet = vocabSet | set(document) #union of the two sets
return list(vocabSet)
- 单个词表转化为向量
# 将 单条 评论词表,转化为 向量
# 向量长度为总字典的长度
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else: print ("the word: %s is not in my Vocabulary!" % word)
return returnVec
- 测试上述三个函数
comments, comments_types = loadDataSet()
words_dict = createVocabList(comments)
words_dict
['maybe',
'stupid',
'park',
'problems',
'posting',
'how',
'food',
'my',
'him',
'quit',
'garbage',
'buying',
'love',
'I',
'is',
'flea',
'mr',
'dalmation',
'steak',
'licks',
'worthless',
'not',
'so',
'has',
'take',
'to',
'cute',
'ate',
'dog',
'please',
'stop',
'help']
setOfWords2Vec(words_dict, comments[2])
[0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
1,
1,
1,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
0,
1,
0,
0,
0,
0,
0]
2. 朴素贝叶斯分类器训练函数
# 给定所有训练样本向量组成的训练集矩阵 trainMatrix
# 以及分类标签向量 trianCategory
def trainNB0(trainMatrix,trainCategory):
"""
输入:
trainMatrix:训练集,每条评论向量组成的矩阵
trainCategory:训练集的标签
输出:
p0Vect:是一个长度为字典长度的向量,每一位代表概率,即这一位上的单词,在0这个类别中出现的概率
p1Vect:是一个长度为字典长度的向量,每一位代表概率,即这一位上的单词,在1这个类别中出现的概率
pAbusive: 1 这个类别占总类别的概率(比例)
"""
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = np.sum(trainCategory)/float(numTrainDocs)
# 为了防止多个概率的乘积为0,将所有词出现次数初始化为1,而不是0
p0Num = np.ones(numWords); p1Num = np.ones(numWords) #change to ones()
# 将分母初始化为2
p0Denom = 2.0; p1Denom = 2.0 #change to 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
#为了防止后续多个很小的概率相乘造成舍入误差过大,
#将概率值取log函数
p1Vect = np.log(p1Num/p1Denom) #change to log()
p0Vect = np.log(p0Num/p0Denom) #change to log()
return p0Vect,p1Vect,pAbusive
- 测试上述函数
trainMat = []
for comment in comments:
trainMat.append(setOfWords2Vec(words_dict, comment))
p0V, p1V, p1_probablity = trainNB0(trainMat, comments_types)
p0V
array([-3.25809654, -3.25809654, -3.25809654, -2.56494936, -3.25809654,
-2.56494936, -3.25809654, -1.87180218, -2.15948425, -3.25809654,
-3.25809654, -3.25809654, -2.56494936, -2.56494936, -2.56494936,
-2.56494936, -2.56494936, -2.56494936, -2.56494936, -2.56494936,
-3.25809654, -3.25809654, -2.56494936, -2.56494936, -3.25809654,
-2.56494936, -2.56494936, -2.56494936, -2.56494936, -2.56494936,
-2.56494936, -2.56494936])
p1V
array([-2.35137526, -1.65822808, -2.35137526, -3.04452244, -2.35137526,
-3.04452244, -2.35137526, -3.04452244, -2.35137526, -2.35137526,
-2.35137526, -2.35137526, -3.04452244, -3.04452244, -3.04452244,
-3.04452244, -3.04452244, -3.04452244, -3.04452244, -3.04452244,
-1.94591015, -2.35137526, -3.04452244, -3.04452244, -2.35137526,
-2.35137526, -3.04452244, -3.04452244, -1.94591015, -3.04452244,
-2.35137526, -3.04452244])
p1_probablity
0.5
3. 朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
输入:
vec2Classify:需要分类的向量
p0Vec,p1Vec, pClass1:0类所以词的概率向量,1类所有词的概率向量, 1类占所有类别的概率
"""
p1 = np.sum(vec2Classify * p1Vec) + np.log(pClass1) #element-wise mult
p0 = np.sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
将上文提到的训练,分类函数整合在一个函数中进行测试
def testingNB():
comments,comments_types = loadDataSet()
words_dict = createVocabList(comments)
trainMat=[]
for comment in comments:
trainMat.append(setOfWords2Vec(words_dict, comment))
p0V,p1V,p1_probablity = trainNB0(np.array(trainMat),np.array(comments_types))
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(words_dict, testEntry))
print (testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,p1_probablity))
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(words_dict, testEntry))
print (testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,p1_probablity))
testingNB()
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
4. 词集模型 VS 词袋模型
即,将某条评论词表
如:
['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
转化为向量 的规则
- 词集模型为,在词表中遇到某个词,则将这条评论对应的词表向量的对应单词位置 置为1
- 词袋模型,在词表中遇到某个词,则将这条评论对应的词表向量的对应单词位置 +1
4.1 词集模型
# 将 单条 评论词表,转化为 向量
# 向量长度为总字典的长度
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else: print ("the word: %s is not in my Vocabulary!" % word)
return returnVec
4.2 词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
"""
输入:
vocabList:词典
inputSet:单条的评论 词表
输出:
输入的评论的数值向量
"""
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
三、过滤垃圾邮件
1. 准备数据,切分文本
从文本文档构建自己的词列表
import re
# 以任意的非单词字符作为分隔符号
re_express = re.compile(r'\W*')
- 测试分词功能
mysentence = 'this book is the best book on the Python or M.L. I have ever laid eyes upon.'
listOfTokens = re_express.split(mysentence)
listOfTokens[-5:]
['ever', 'laid', 'eyes', 'upon', '']
# 将所有单词变为小写,并且筛除空的字符len()>0
[tok.lower() for tok in listOfTokens if len(tok)>0][-5:]
['have', 'ever', 'laid', 'eyes', 'upon']
- 观察上述函数对一份实际的电子邮件的拆分效果
代码中使用的数据连接地址: https://www.manning.com/books/machine-learning-in-action
with open(r'MachineLearningInAction/Ch04/email/ham/6.txt') as f:
emailText = f.read()
emailText
'Hello,\n\nSince you are an owner of at least one Google Groups group that uses the customized welcome message, pages or files, we are writing to inform you that we will no longer be supporting these features starting February 2011. We made this decision so that we can focus on improving the core functionalities of Google Groups -- mailing lists and forum discussions. Instead of these features, we encourage you to use products that are designed specifically for file storage and page creation, such as Google Docs and Google Sites.\n\nFor example, you can easily create your pages on Google Sites and share the site (http://www.google.com/support/sites/bin/answer.py?hl=en&answer=174623) with the members of your group. You can also store your files on the site by attaching files to pages (http://www.google.com/support/sites/bin/answer.py?hl=en&answer=90563) on the site. If you抮e just looking for a place to upload your files so that your group members can download them, we suggest you try Google Docs. You can upload files (http://docs.google.com/support/bin/answer.py?hl=en&answer=50092) and share access with either a group (http://docs.google.com/support/bin/answer.py?hl=en&answer=66343) or an individual (http://docs.google.com/support/bin/answer.py?hl=en&answer=86152), assigning either edit or download only access to the files.\n\nyou have received this mandatory email service announcement to update you about important changes to Google Groups.'
listOftokenss = re_express.split(emailText)
listOftokenss[-5:]
['changes', 'to', 'Google', 'Groups', '']
[tok.lower() for tok in listOftokenss if len(tok)>0][-5:]
['important', 'changes', 'to', 'google', 'groups']
2. 测试算法,使用朴素贝叶斯进行交叉验证
读取txt文件的时候有可能会出现:
'gbk' codec can't decode byte 0xbf in position 2: illegal multibyte sequence
参考解决办法:https://blog.csdn.net/lqzdreamer/article/details/76549256
def textParse(bigString):
# input is big string, #output is word list
import re
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
docList = []
classList = []
fullText = []
# 左闭右开,1~25
# 导入并解析文本文件
for i in range(1, 26):
with open(r'MachineLearningInAction/Ch04/email/spam/%d.txt' % i, encoding='gb18030', errors='ignore') as f:
wordList = textParse(f.read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
with open(r'MachineLearningInAction/Ch04/email/ham/%d.txt' % i, encoding='gb18030', errors='ignore') as ff:
wordList = textParse(ff.read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
# 将评论中所有出现过的词,组合成总词典
vocabList = createVocabList(docList) # create vocabulary
trainingSet = list(range(50))
testSet = [] # create test set
# 创建测试集,并选定的评论从训练集删除
for i in range(10):
randIndex = int(np.random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del trainingSet[randIndex]
# 创建训练集,并且将训练集转化为词向量矩阵
trainMat = []
trainClasses = []
for docIndex in trainingSet: # train the classifier (get probs) trainNB0
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
# 得到每一类别的每个词的概率向量,以及1类别占总类别的概率
p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))
errorCount = 0
for docIndex in testSet: # classify the remaining items
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
result = classifyNB(np.array(wordVector), p0V, p1V, pSpam)
if result != classList[docIndex]:
errorCount += 1
print("classified error sentence: \n", docList[docIndex])
print('classifyNB=', result)
print('real_result=', classList[docIndex], '\n')
print('the error rate is: ', float(errorCount) / len(testSet))
# return vocabList,fullText
spamTest()
classified error sentence:
['home', 'based', 'business', 'opportunity', 'knocking', 'your', 'door', 'don抰', 'rude', 'and', 'let', 'this', 'chance', 'you', 'can', 'earn', 'great', 'income', 'and', 'find', 'your', 'financial', 'life', 'transformed', 'learn', 'more', 'here', 'your', 'success', 'work', 'from', 'home', 'finder', 'experts']
classifyNB= 0
real_result= 1
the error rate is: 0.1