机器学习第二周-朴素贝叶斯
本周学习内容
- 分类问题:朴素贝叶斯
- 分类问题:逻辑回归
第四章:基于概率论的分类方法: 朴素贝叶斯
关于贝叶斯
贝叶斯分类是一类分类算法的总称,这类算法是以贝叶斯定理为基础的。朴素贝叶斯中的朴素,是在整个形式化过程只做最原始最简单的假设。朴素贝叶斯可以用于分档分类领域。
贝叶斯理论
贝叶斯决策理论的核心思想是最高概率决策。我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
- 如果p1(x,y)> p2(x,y),那么类别为1
- 如果p2(x,y)> p1(x,y),那么类别为2
使用条件概率来分类
使用上面公式,可以定义贝叶斯分类规则为:
- 如果P(c1 | x,y)> P(c2 | x,y),那么属于类别c1;
- 如果P(c2 | x,y)> P(c1 | x,y),那么属于类别c2。
其中 p(c1 | x,y) 表示在给定x,y表示的数据点,那么该数据点来自类别c1的概率是多少
统计上的独立是指一个特征的出现和其他特征出现没有关系。在该模型中我们将所有特征看做相互独立,实际和假设误差其实很小,可以忽略。
朴素贝叶斯开发流程
收集数据: 可以使用任何方法。
准备数据: 需要数值型或者布尔型数据。
分析数据: 有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
训练算法: 计算不同的独立特征的条件概率。
测试算法: 计算错误率。
使用算法: 一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
朴素贝叶斯工作原理
提取所有文档中的词条并进行去重
获取文档的所有类别
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现在文档中-->增加该词条的计数值(for循环或者矩阵相加)
增加所有词条的计数值(此类别下词条总数)
对每个类别:
对每个词条:
将该词条的数目除以总词条数目得到的条件概率(P(词条|类别))
返回该文档属于每个类别的条件概率(P(类别|文档的所有词条))
代码实战
留言板分类
特征:切分后的单词
类别:侮辱性和非侮辱性
初始化数据集
from numpy import *
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代表侮辱性,0非侮辱性
return postingList,classVec
创建单词表
def createVocabList(dataSet):
#创建单词表
vocabSet = set([]) #初始化一个集合,目的是获取不重复的单词列表
for document in dataSet: #遍历属性集上的每一行
vocabSet = vocabSet | set(document) #集合可以使用并运算,用来添加每一行未出现过的单词
return list(vocabSet) #返回单词表
将每一个文件转换为对应的词向量,词向量中每一个位置对应于单词表中的一个单词。1表示出现,0表示未出现
def setOfWords2Vec(vocabList, inputSet):
'''
函数的输入参数为词汇表及某个文档,输出的是文档向量,
向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现
'''
returnVec = [0]*len(vocabList) #初始化词向量,长度和单词表长度同
for word in inputSet: #遍历输入行的每一个单词
if word in vocabList:
returnVec[vocabList.index(word)] = 1 #在对应单词属性列打上1标签
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec
输出的单词表为:
['food','please','has','so','take','maybe','flea','not','I','worthless','cute','my','mr','dalmation',...]
输出的词向量示例:
[0,1,1,0,0,0,1,0,0,0,0,1,0,0,1,0,0,...]
训练模型,从词向量中计算概率
def trainNB0(trainMatrix,trainCategory):
'''
输入分别为文件单词向量[[0,1,1,0...],[0,1,...],...]
单词类别[0,1,0...]其中0表示非侮辱性,1表示侮辱性
'''
numTrainDocs = len(trainMatrix) #总文件数,即文件行数
numWords = len(trainMatrix[0]) #词向量长度,即单词数
pAbusive = sum(trainCategory)/float(numTrainDocs) #侮辱性文章概率
p0Num = ones(numWords); p1Num = ones(numWords)
#构造在分别在p0和p1类别中各个单词出现次数的列表,使用1初始化是为了避免后面出现0乘和除0的情况
p0Denom = 2.0; p1Denom = 2.0
#p0,p1类别中出现的各种单词的总数,初始化
for i in range(numTrainDocs):
if trainCategory[i] == 1: #对于侮辱性文章
p1Num += trainMatrix[i] #累加侮辱词次数
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num/p1Denom)
#[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表
p0Vect = log(p0Num/p0Denom)
#[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表
return p0Vect,p1Vect,pAbusive #
实际训练上面的算法
trainMat=[] #将所有文件转换为对应的词向量,构造词向量矩阵
for postinDoc in listoposts:
trainMat.append(setOfWords2Vec(myvocablist,postinDoc))
pov,p1v,pab=trainNB0(trainMat,listclasses)
结果
pab #0.5
plv
'''
array([-2.35137526, -3.04452244, -3.04452244, -3.04452244, -2.35137526,
-2.35137526, -3.04452244, -2.35137526, -3.04452244, -1.94591015,
-3.04452244, -3.04452244, -3.04452244, -3.04452244, -1.94591015,
-3.04452244, -3.04452244, -3.04452244, -2.35137526, -2.35137526,
-2.35137526, -2.35137526, -2.35137526, -2.35137526, -2.35137526,
-3.04452244, -3.04452244, -3.04452244, -3.04452244, -2.35137526,
-3.04452244, -1.65822808])
'''
至此得到了每一类中每种单词出现的概率,以及每一类的概率
接下来就可以开始利用朴素贝叶斯原理进行分类了
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
'''
vec2Classify是待分类的词向量
'''
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
#注意因为我们对所有概率结构都做了log运算,所以乘法变加法,除法变减法。并且,对于P1P2来说pw是一样的,所以没有必要求pw
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
使用朴素贝叶斯过滤垃圾邮件
在第一个例子中,数据是直接以单词形式给出,实际情况下是以文章形式给出,因此在本例子中,我主要学习了如何分割文件,提取单词。
切分文本
如果使用string.split()方法,无法成功分离出标点符号。因此选择正则表达式
import re
mySent = 'This book is the best book on Python or M.L. I have ever laid eyes upon.'
regEx = re.compile('\\W+')
#书中这里的+是*,但是在实际环境运行了一下,发现使用*会把所有按照字符逐个切分,得不到单词,查阅资料后,换成了+,应该是python3已近取消了该语法
listOfTokens = regEx.split(mySent)
>>> listOfTokens
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon', '']
了解了正则表达式原理后我们就可以进行实际文本的切分了。
切分文本函数
def textParse(bigString):
import re
# 使用正则表达式来切分句子,其中分隔符是除单词、数字外的任意字符串
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
'''
建立垃圾邮件朴素贝叶斯分类器,并返回测试结果
'''
docList = [] #文件列表
classList = [] #类别标签列表
fullText = [] #
#导入并解析文本文件
for i in range(1, 26):
# 对于类别1的文件
wordList = textParse(open('./email/spam/%d.txt' % i).read()) #打开文件并切分出单词
docList.append(wordList) #添加到文件列表
classList.append(1) #归类到1类别
# 对于类别0的文件
wordList = textParse(open('./email/ham/%d.txt' % i).read())#打开文件并切分出单词
docList.append(wordList) #添加到文件列表
fullText.extend(wordList)
classList.append(0) #归类到1类别
# 创建词汇表
vocabList = createVocabList(docList)
trainingSet = list(range(50)) #python3语法,range返回range对象,不是列表
testSet = [] #这里保存测试集的索引值
# 随机取 10 个邮件用来测试
for i in range(10):
# random.uniform(x, y) 随机生成一个范围为 x ~ y 的实数
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex]) #从训练集中剔除
trainMat = []
trainClasses = []
#生成词向量矩阵
for docIndex in trainingSet:
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
errorCount = 0
#使用测试集数据进行测试
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]: #分类结果和实际值比较
errorCount += 1
print('the errorCount is: ', errorCount )
print('the testSet length is :', len(testSet))
print( 'the error rate is :', float(errorCount)/len(testSet))
结果
the errorCount is: 1
the testSet length is : 10
the error rate is : 0.1
需要注意的是,如果使用python3,书中的部分代码可能已经可能不再适用,具体的我在代码注释中有说明。