【机器学习实战笔记(3-2)】朴素贝叶斯法及应用的python实现
文章目录
1.朴素贝叶斯法的Python实现
本小节将以文本分类为例,介绍朴素贝叶斯实现的整个过程。
朴素贝叶斯法相关概念及原理中提到,朴素贝叶斯法是典型的生成学习方法。
生成方法:
由训练数据学习联合概率分布P(X,Y)。
利用训练数据学习P(X|Y)和P(Y)的估计,得到联合概率分布:P(X,Y)=P(Y)P(X|Y);
概率估计方法:可以是极大似然估计或贝叶斯估计。
然后利用贝叶斯定理与学到的联合概率模型求得后验概率分布P(Y|X),进行分类预测。
P(Y∣X)=P(Y)P(X∣Y)∑YP(Y)P(X∣Y)P(Y|X)=\frac{P(Y)P(X|Y)}{\sum_{Y}P(Y)P(X|Y)}P(Y∣X)=∑YP(Y)P(X∣Y)P(Y)P(X∣Y)
朴素贝叶斯法的基本假设是条件独立性。
- 分类预测:将输入x分到后验概率最大的类y。y=argmaxckP(Y=ck)∏jP(X(j)=x(j)∣Y=ck)y=argmax_{c_k}P(Y=c_k)\prod_jP(X^{(j)}=x^{(j)}|Y=c_k)y=argmaxckP(Y=ck)j∏P(X(j)=x(j)∣Y=ck)
下面我们就依照这个步骤框架来一步步实现朴素贝叶斯法。
1.1 准备数据:从文本中构建词向量
这算是一个预处理的部分。
要从文本中获取特征,需要先拆分文本。具体来说,这里的特征是来自文本的词条(token),一个词条是字符的任意组合。可以把词条想象为单词,也可以使用非单词词条,如URL、IP地址或者任意其他字符串。然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0表示词条未出现。(即one-hot编码)
本节例子是以在线社区额留言板为例,将侮辱类、非侮辱类发言进行分类,使用1和0分别表示。
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
# 创建一个dataSet的所有文档中出现的不重复词的列表
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空集
for document in dataSet:
vocabSet = vocabSet | set(document) # 创建两个集合的并集
return list(vocabSet)
# 为输入的某个文档的单词集合构造词向量
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList) # 创建一个0向量
for word in inputSet:
if word in inputSet:
returnVec[vocabList.index(word)] = 1 # 贝努利模型
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
注释:
朴素贝叶斯分类器通常有两种实现方式:
- 基于贝努利模型实现:该实现方式中不考虑词在文档中出现的次数,只考虑出不出现。因此,在这个意义上相当于假设,词是等权重的。(本节采用了此种实现方式)
- 基于多项式模型实现:考虑词在文档中出现的次数。
1.2 训练算法:从词向量计算概率
假设我们用w⃗\vec{w}w表示词向量,cic_ici表示某一类别标签,那么我们最终的目标就是:
y=argmaxciP(Y=ci)∏jP(wj⃗∣Y=ci)y=argmax_{c_i}P(Y=c_i)\prod_jP(\vec{w_j}|Y=c_i)y=argmaxciP(Y=ci)j∏P(wj∣Y=ci)
现在我们有了可以用的训练数据:特征向量、类标签,下一步就是利用训练数据集来学习模型,也就是联合概率分布P(w⃗,ci)P(\vec{w},c_i)P(w,ci),进一步地就是:
- 先验概率P(Y=ci)P(Y=c_i)P(Y=ci)
- 条件概率P(w⃗∣ci)P(\vec{w}|c_i)P(w∣ci),加上条件独立性假设,P(w⃗∣ci)=∏jP(wj⃗∣Y=ci)P(\vec{w}|c_i)=\prod_{j}P(\vec{w_j}|Y=c_i)P(w∣ci)=∏jP(wj∣Y=ci)
对于以上两个概率的估计,我们这里使用极大似然估计。于是我们有训练函数:
# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱性文档的概率
# 初始化概率
p0Num = zeros(numWords); p1Num = zeros(numWords) # 分子
p0Denom = 0.0; p1Denom = 0.0 # 分母
for i in range(numTrainDocs):
if trainCategory[i] == 1:
# 向量相加
p1Num += trainMatrix[i]
# p1Denom += sum(trainMatrix[i]) 原书中的错误写法
p1Denom += 1
else:
p0Num += trainMatrix[i]
# p0Denom += sum(trainMatrix[i]) 原书中的错误写法
p0Denom += 1
# 对每个元素做除法
p1Vect = p1Num/p1Denom
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive
1.3 测试算法:根据现实情况修改分类器
trainNB0(trainMatrix, trainCategory)完成了对上面先验概率、条件概率的概率估计,但是就像我们在朴素贝叶斯法相关概念及原理中提到的,现在我们的训练函数有两个问题:
- 可能出现某一个特征的条件概率P(wj⃗∣Y=ci)P(\vec{w_j}|Y=c_i)P(wj∣Y=ci)为0的情况,进而影响后验概率的计算结果,使分类产生偏差,所以我们改用贝叶斯估计。
将所有词的出现次数初始化为1,并将分母初始化为2:
# 根据现实情况修改分类器
p0Num = ones(numWords); p1Num = ones(numWords) # 分子
p0Denom = 2.0; p1Denom = 2.0 # 分母
- 另一个问题就是下溢出,我们通过对乘积取自然对数来解决这个问题。
# 根据现实情况修改分类器
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
到目前为止,我们已经构建了一个比较完整的训练函数了,下面我们就利用训练函数得到的先验概率及条件概率去构建我们完整的分类器。
# 朴素贝叶斯分类函数
# 根据训练得到的三个概率对要分类的向量vec2Classify进行分类的朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # 对应元素相乘
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
# 便利函数(convenience function),该函数封装所有操作
def testingNB():
listOPosts,listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
关于向量/矩阵乘法与对应元素相乘的注释:
- np.dot(a,b):表示矩阵/向量乘法。特别地,a,b均为向量的时候,.dot实现内积,不用考虑a,b具体是行向量还是列向量;
- a*b或np.multiply(a,b):表示矩阵/向量按对应位置相乘。特别地,当a,b分别为行向量和列向量时,不管a,b谁在前边都是一样的结果。
来看一下算法运行效果如何:
testingNB()
-->Out:['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
到目前为止,我们已经完整实现了一个朴素贝叶斯分类器,并且可以成功用它来对留言进行分类。是不是瞬间感觉朴素贝叶斯非常easy!下面两节我们将再用两个经典的应用实例来巩固我们对朴素贝叶斯的掌握。
1.4 准备数据:文档词袋模型
我们前面提到,朴素贝叶斯分类器通常有两种实现方式:
基于贝努利模型实现:将每个词出现与否作为一个特征,这可以描述为词集模型(set-of-words model)
基于多项式模型实现:若一个词在文档中出现不止一次,这可能意味着包含/该词是否出现在文档中/(即词集模型)所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。
也就是说:
在词集中,每个词只能出现一次;
在词袋中,每个单词可以出现多次
为了适应词袋模型,需要对函数setOfWords2Vec()稍加修改,修改后的函数名称为bagOfWords2VecMN(),如下:
# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1 # 词袋模型
return returnVec
2.示例1:使用朴素贝叶斯过滤垃圾邮件
2.1 准备数据:切分文本
在第1节的例子中,我们直接引入了字符串列表,但是在现实生活中,我们需要先从文本内容得到字符串列表,然后生成词向量。
# 文本解析,将一个大字符串解析为字符串列表
def textParse(bigString):
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
2.2 测试算法:使用朴素贝叶斯进行交叉验证
# 完整的垃圾邮件测试函数,对朴素贝叶斯垃圾邮件分类器进行自动化处理
def spamTest():
docList=[]; classList=[]; fullText=[]
for i in range(1,26):
# 导入并解析文本文件
wordList = textParse(open("email/spam/%d.txt" % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(open("email/ham/%d.txt" % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
# trainingSet = range(50) 原书中的错误写法
trainingSet = list(range(50))
testSet=[]
# 随机构建训练集
for i in range(10):
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("classification error",docList[docIndex])
errorRate = float(errorCount)/len(testSet)
print("the error rate is: ", errorRate)
return (float(errorCount)/len(testSet))
注释:
留存交叉验证(hold-out cross validation):随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程。
正则表达式:
\w:用于匹配字母,数字或下划线字符;
\W:用于匹配所有与\w不匹配的字符;
*:前一个字符0次或多次扩展;
+:前一个字符1次或多次扩展;
re.compile(pattern, flags=0) Compile a regular expression pattern, returning a pattern object.返回一个匹配对象,它单独使用就没有任何意义,需要和findall(), search(), match()等搭配使用。
带有条件判断的列表推导式,共有两种形式:
[x for x in data if condition]
[exp1 if condition else exp2 for x in data]
random.uniform(a,b):随机生成一个在[a,b)的实数
运行一下看看效果:
spamTest()
-->Out:classification error ['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']
the error rate is: 0.1
为了更精确的估计分类器的错误率,下面将spamTest()迭代10词,求平均错误率:
# 将spamTest()重复10次,求平均错误率
errorRateList = []
for i in range(10):
errorRateList.append(spamTest())
print("the average errorrate is: ",average(errorRateList))
3.示例2:使用贝叶斯分类器从个人广告中获取区域倾向
书中作者的意思是以来自源 http://newyork.craigslist.org/stp/index.rss 中的文章作为分类为1的文章,以来自源 http://sfbay.craigslist.org/stp/index.rss 中的文章作为分类为0的文章,但是由于FW的原因,访问不到作者所说的数据,所以在网上找了两个替代源:
- NASA Image of the Day:http://www.nasa.gov/rss/dyn/image_of_the_day.rss
- Yahoo Sports - NBA - Houston Rockets News:http://sports.yahoo.com/nba/teams/hou/rss.xml
import operator
import feedparser
# RSS源分类器及高频词去除函数
# 统计词频,并降序排序,返回词频最高的top-k
def calcMostFreq(vocabList,fullText):
freqDict = {}
# 计算出现频率
for token in vocabList:
freqDict[token] = fullText.count(token)
sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
return sortedFreq[:30]
# 与spam_Test()函数几乎相同
def localWords(feed1,feed0):
docList=[]; classList=[]; fullText=[]
minLen = min(len(feed1['entries']),len(feed0['entries']))
for i in range(minLen):
# 每次访问一条RSS源
wordList = textParse(feed1['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
top30Words = calcMostFreq(vocabList, fullText)
# 从词汇表中,去掉出现次数最高的那些词
for pairW in top30Words:
if pairW[0] in vocabList:
vocabList.remove(pairW[0])
# 去除停用词
stopwords=open("stop_words.utf8", encoding="utf-8").readlines()
stopwords = [st.replace('\n', '') for st in stopwords]
vocabList = [pairW for pairW in vocabList if pairW not in stopwords]
trainingSet = list(range(2*minLen)); testSet=[]
for i in range(4):
randIndex = int(random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat=[]; trainClasses=[]
for docIndex in trainingSet:
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print("the error rate is: ",float(errorCount)/len(testSet))
return vocabList,p0V,p1V
nasa = feedparser.parse('http://www.nasa.gov/rss/dyn/image_of_the_day.rss')
yahoo = feedparser.parse('http://sports.yahoo.com/nba/teams/hou/rss.xml')
vocabList,pYahoo,pNasa=localWords(nasa,yahoo)
-->Out:the error rate is: 0.25
注释:
localWords与spamTest几乎相同,唯一的区别就是:这里访问的是RSS源,而不是文件。
值得注意的是,RSS源要在函数外导入,这样做的原因是RSS源会随时间而改变。
上面函数中不仅移除了高频词,也加载了停用词表(stop word list)去除停用词。
operator.itemgetter(1):operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号。
del,list.remove(),list.pop()
list.remove():删除单个元素,删除首个符合条件的元素,按值删除,返回值为空;
list.pop():删除索引位置元素,无参情况下删除最后一个元素,返回删除的元素值;
del:根据索引位置来删除单个值或指定范围内的值
同样地,为了得到错误率的精确估计,应该多次进行上述实验,然后取平均值。可以发现,这里的错误率要远高于垃圾邮件分类中的错误率,由于这里关注的是单词概率而不是实际分类,所以问题不大。
下面就来显示地域相关的用词。可以先对向量pSF与pNY进行排序,然后按照顺序将词打印出来。
# 最具表征性的词汇显示函数
def getTopWords(ny,sf):
vocabList,p0V,p1V=localWords(ny,sf)
topNY=[]; topSF=[]
for i in range(len(p0V)):
if p0V[i] > -6.0:
topSF.append((vocabList[i],p0V[i]))
if p1V[i] > -6.0:
topNY.append((vocabList[i],p1V[i]))
sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True)
print("SF**"*15)
for item in sortedSF:
print(item[0])
sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True)
print("NY**"*15)
for item in sortedNY:
print(item[0])
注释:
lambda表达式:通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。
例子:将列表中的元素按照绝对值大小进行升序排列
list1 = [3,5,-4,-1,0,-2,-6]
sorted(list1, key=lambda x: abs(x))
参考资料:
1.《机器学习实战》第4章
2.另外的RSS源
【机器学习实战笔记(3-2)】朴素贝叶斯法及应用的python实现的更多相关文章
- 朴素贝叶斯法(naive Bayes algorithm)
对于给定的训练数据集,朴素贝叶斯法首先基于iid假设学习输入/输出的联合分布:然后基于此模型,对给定的输入x,利用贝叶斯定理求出后验概率最大的输出y. 一.目标 设输入空间是n维向量的集合,输出空间为 ...
- 朴素贝叶斯法(naive Bayes)
<统计学习方法>(第二版)第4章 4 朴素贝叶斯法 生成模型 4.1 学习与分类 基于特征条件独立假设学习输入输出的联合概率分布 基于联合概率分布,利用贝叶斯定理求出后验概率最大的输出 条 ...
- 第四章 朴素贝叶斯法(naive_Bayes)
总结 朴素贝叶斯法实质上是概率估计. 由于加上了输入变量的各个参量条件独立性的强假设,使得条件分布中的参数大大减少.同时准确率也降低. 概率论上比较反直觉的一个问题:三门问题:由于主持人已经限定了他打 ...
- 统计学习方法与Python实现(三)——朴素贝叶斯法
统计学习方法与Python实现(三)——朴素贝叶斯法 iwehdio的博客园:https://www.cnblogs.com/iwehdio/ 1.定义 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设 ...
- [机器学习] 分类 --- Naive Bayes(朴素贝叶斯)
Naive Bayes-朴素贝叶斯 Bayes' theorem(贝叶斯法则) 在概率论和统计学中,Bayes' theorem(贝叶斯法则)根据事件的先验知识描述事件的概率.贝叶斯法则表达式如下所示 ...
- 详解基于朴素贝叶斯的情感分析及 Python 实现
相对于「 基于词典的分析 」,「 基于机器学习 」的就不需要大量标注的词典,但是需要大量标记的数据,比如: 还是下面这句话,如果它的标签是: 服务质量 - 中 (共有三个级别,好.中.差) ╮(╯-╰ ...
- 朴素贝叶斯原理、实例与Python实现
初步理解一下:对于一组输入,根据这个输入,输出有多种可能性,需要计算每一种输出的可能性,以可能性最大的那个输出作为这个输入对应的输出. 那么,如何来解决这个问题呢? 贝叶斯给出了另一个思路.根据历史记 ...
- <机器学习实战>读书笔记--朴素贝叶斯
1.朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法, 最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model, ...
- Python机器学习笔记:朴素贝叶斯算法
朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法.对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同.比如决策树,KNN,逻辑回归,支持向 ...
随机推荐
- iOS性能优化-内存优化
https://blog.csdn.net/a184251289/article/details/82589128 2018年09月10日 14:25:31 xingshao1990 阅读数:328 ...
- Windows Server 2008 R2 SP1 中IIS7.5 和 TOMCAT7 整合笔记
Windows Server 2008 R2 SP1 中IIS7.5 和 TOMCAT7 整合笔记 来源:www.roak.com 整合文件在百度网盘或博客盘 配置了N次,64位操作系统真坑爹~~~下 ...
- 产品原型 UI 设计工具
产品原型设计工具 Balsamiq Mockups Axure RP 图像处理.绘制工具 ps,AI 跨平台 UI开发工具 QT , Unity3D
- Django博客开发-数据建模与样式设定
开发流程介绍 之前Django的学习过程当中已经把基本Django开发学完了,现在以Django 的博客项目完成一遍课程的回顾和总结.同时来一次完整开发的Django体验. 一个产品从研究到编码我们要 ...
- 编程题目: 两个队列实现栈(Python)
感觉两个队列实现栈 比 两个栈实现队列 麻烦 1.栈为空:当两个队列都为空的时候,栈为空 2.入栈操作:当队列2为空的时候,将元素入队到队列1:当队列1位空的时候,将元素入队到队列2: 如果队列1 和 ...
- express 配置 https 服务 ( 以阿里云服务器为例), 探索一周终于搞定
首先最重要的是 你要明白 https接口的接收或者发送 的形式 是 https://域名:端口号 而不是 https://ip:端口号 一,首先,去阿里云注册免费ssl证书 1,在搜索框 ...
- Windows使用Nexus搭建Maven私服
简介 Maven私服是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构件,有了私服之后,当 Maven 需要下载构件时,直接请求私服,私服上存在则下载到本地仓库,否则,私服请求外部的 ...
- 04.swoole学习笔记--webSocket服务器
<?php //创建webSocket服务器 $serv=); //获取请求 //on //open 建立连接 $serv:服务器 $request:客户端信息 $serv->on('op ...
- 7.7 Varnishadm命令
- python反序列化漏洞
原理在网页源码中如果出现将用户输入数据进行反序列化当成参数输出时,出现漏洞,可造成任意命令执行例如网页源码try: become = self.get_argument('become') ...