贝叶斯
贝叶斯算法
引言
机器学习中,如KNN,逻辑回归,决策树等模型都是判别方法, 也就是直接学习出输出特征y和输入特征x之间的关系(决策函数:y = f(x) 或者条件分布P(Y|X))。
朴素贝叶斯是生成方法,直接找出输出特征y和输入特征x的联合分布P(X, Y),进而通过P(Y|X)=P(X, Y)/P(x)计算出结果判定。
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。而朴素贝叶斯(Naive Bayes)分类是贝叶斯分类中最简单,也是常见的一种分类方法
原理
贝叶斯公式:
简单来说,贝叶斯定理(Bayes Theorem,也称贝叶斯公式)是基于假设的先验概率、给定假设下观察到不同数据的概率,提供了一种计算后验概率的方法。在人工智能领域,有一些概率型模型会依托于贝叶斯定理,比如我们今天的主角「朴素贝叶斯模型」。
P(A|B) = (P(B|A)/P(A))/P(B)
P(A)是先验概率,一般都是人主观给出的。贝叶斯中的先验概率一般特指它。
P(B)是先验概率,在贝叶斯的很多应用中不重要(因为只要最大后验不求绝对值),需要时往往用全概率公式计算得到。
P(B∣A)是条件概率,又叫似然概率,一般是通过历史数据统计得到。
P(A∣B)是后验概率,一般是我们求解的目标
-
先验概率: 事件发生前的预判概率。可以是基于历史数据的统计,可以是背景常识得出,也可以是人主观观点给出,一般是单独事件概率。
-
后验概率:事件发生后求的反向条件概率。或者说,基于先验概率求得的反向条件概率。概率形式和条件概率相同。
-
条件概率: 一个事件发生后,另一个事件发生的概率, 一般形式P(B|A), 表示A发生条件下B发生的概率。P(B|A) = P(AB)/P(A)
朴素贝叶斯
之所以命名朴素贝叶斯原因:
- 假设样本之间是独立互不相干的
- 样本所属类别是受特征属性取值影响的
核心思想:
通过考虑特征概率来预测分类,即对于给出的待分类样本,求解在此样本出现的条件下各个类别出现的概率,哪个最大,就认为此待分类样本属于哪个类别
直接利用贝叶斯公式,计算当x给定时,y取值的概率值;
选择最大的取值作为最终的预测类型
训练过程
- 训练中,计算各个类别的先验概率p(y=k)
- 计算各个类别中,各个特征属性的取值概率,也就是当类别给定时,x的条件概率p(x=i|y=k)
预测过程
直接把类别的先验概率以及属性的条件概率做一个累乘,选择累乘结果最大的类别作为最终输出预测类别
朴素贝叶斯的类别
高斯贝叶斯
假定特征属性取值是服从高斯贝叶斯, 模型比较适合连续的特征属性
模型训练条件概率时,会对背个类别,每个特征属性都分别的均值和方差,得到该类别该特征属性所满足的高斯概率密度函数。
伯努利贝叶斯
假设特征属性是稀疏的,也就是将有值的属性认为是1,没有值的数星星认为是0,所以认为特征属性服从伯努利分布,训练条件的时候,也就是计算伯努利概率密度函数,该算法比较适合高度稀疏的特征矩阵。
多项式贝叶斯
假设特征属性取值服从多项式分布,所以模型比较适合离散型特征属性
模型训练条件概率时,直接计算每个类别的每个特征属性的取值样本数目占类别总样本数目的概率最为条件概率
Note:
为了防止概率为0,需要做一个平滑转换,一般为拉普拉斯变换。
零概率问题,指的是在计算实例的概率时,如果某个量x,在观察样本库(训练集)中没有出现过,会导致整个实例的概率结果是0。
在文本分类的问题中,当「一个词语没有在训练样本中出现」时,这个词基于公式统计计算得到的条件概率为0,使用连乘计算文本出现概率时也为0。这是不合理的,不能因为一个事件没有观察到就武断的认为该事件的概率是0
解决方法:
拉普拉斯变换:
假定训练样本很大时,每个分量x的计数加1造成的估计概率变化可以忽略不计,但可以方便有效的避免零概率问题
对应到文本分类的场景中,如果使用多项式朴素贝叶斯,假定特征xi,表示某个词在样本中出现的次数(当然用TF-IDF表示也可以)。拉普拉斯平滑处理后的条件概率计算公式为:
实践案例:
利用贝叶斯实现文本分类和垃圾邮件的过滤:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
'''
此部分代码使用朴素贝叶斯,来进行文本分类的功能
'''
import numpy as np
def loadDataSet():
"""
来生成数据:文本列表和标签分类
:return:
"""
#文本集:每个[]是一个文本,里面是分割开的单词
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']]
#0-非侮辱性,1-侮辱性
classVec = [0,1,0,1,0,1]
return postingList, classVec
def createVocableList(dataSet):
"""
获取词汇表:把文本集装换成一个不含重复词的列表
:param dataSet:
:return:
"""
vocabSet = set([]) # #创建一个空集,可以进行集合操作
for document in dataSet:
vocabSet = vocabSet | set(document) # 集合求并集
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet):
"""
转化成向量:参数是词汇表和某个文档, 使用one-hot编码的方式实现词向量
:param vocabList:
:param inputSet:
:return:
"""
# 构建vocabList长度的0向量
returnVec = [0]*len(vocabList)
for word in inputSet: #把该文档中在词汇表中有该单词的位置标位1(one-hot向量)
if word in vocabList:
returnVec[vocabList.index(word)]=1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
def trainNB0(trainMatrix, trainGategory):
numTrainDocs = len(trainMatrix) #返回训练的文档数目
numWords = len(trainMatrix[0]) #返回每一篇文档的词条数
pAbusive = sum(trainGategory)/float(numTrainDocs) #文档属于侮辱类的概率,对list求和得到侮辱性文档的个数
#用拉普拉斯修正”来平滑数据
p0Num = np.ones(numWords)
p1Num = np.ones(numWords) #创建numpy.zeros数组,词条出现数初始化为0
p0Denom = 2.0; p1Denom = 2.0 #分母初始化为2
for i in range(numTrainDocs):
if trainGategory[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = np.log(p1Num/p1Denom)#log防止下溢出
p0Vect = np.log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive #返回属于非侮辱类的条件概率数组,属于侮辱类的条件概率数组,文档属于侮辱类的概率
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
利用贝叶斯进行分类的输出
:param vec2Classify:
:param p0Vec:
:param p1Vec:
:param pClass1:
:return:
"""
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
def testingNB():
"""
对朴素贝叶斯分类,进行测试
:return:
"""
listOPosts, listClasses = loadDataSet()
myVocabList = createVocableList(listOPosts)
trainMat = []
# 对每个一行文本转化为词向量,进行计算
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(np.array(trainMat), np.array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as:', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as:', classifyNB(thisDoc, p0V, p1V, pAb))
#词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
"""
朴素贝叶斯词袋模型,每当遇到一个单词时会增加向量中的对应值,不只是赋值为1
:param vocabList:
:param inputSet:
:return:
"""
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
"""
垃圾邮件的过滤
"""
def textParse(bigString):
"""
输入很长的字符串,转换为向量
参数:
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 = []
# 遍历垃圾邮件和正常邮件,各25个
for i in range(1, 26):
# 读取垃圾邮件
wordList = textParse(open("email/spam/{}.txt".format(i), errors='ignore').read())
# 添加到列表
docList.append(wordList)
fullText.extend(wordList)
# 添加到类
classList.append(1)
# 读取正常邮件
# ham中的23.txt总是报错有不能解读的字节,选择忽略该错误
wordList = textParse(open("email/ham/{}.txt".format(i), errors='ignore').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
# 创建词汇表
vocabList = createVocableList(docList)
# 训练集和测试集序号集
trainingSet = list(range(50))
testSet = []
# 随机抽取训练集中的10个序号,放入测试集
for i in range(10):
# 生成随机序号
randIndex = np.int(np.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(np.array(trainMat), np.array(trainClasses))
# 错误计数器初始化为0
errorCount = 0
# 对于测试集
for docIndex in testSet:
# 得到词袋向量
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
# 判断结果
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
# 统计错误
errorCount += 1
# 打印错误信息
print("错误序号为:{}".format(docList[docIndex]))
print("总准确率为:{}".format(1 - np.float(errorCount) / len(testSet)))
def stopWord():
wordList = open(r'D:\\workplace\\project\\machine-project\\thinking_code\\stopword.txt').read()
listOfTokens = re.split(r'\W+',wordList)
return [tok.lower() for tok in listOfTokens]
def localWords(feed1,feed0):#参数为两个RSS源
docList = []
classList = []
fullText = []
minLen = min(len(feed1['entries']),len(feed0['entries']))
#选取两者较短的长度
for i in range(minLen):
wordList = textParse(feed1['entries'][i]['summary'])
#提取RSS源中的字符串文本并切分为字符串列表
docList.append(wordList)
#将每个字符串列表放入一个总的列表里
fullText.extend(wordList)
#将所有字符都存储在一个列表里
classList.append(1)
#标记为垃圾广告,标签为1
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
#同上,这里标记为非垃圾广告
vocabList = createVocabList(docList)
stopWordList = stopWord()
for stopWord_ in stopWordList:
if stopWord_ in vocabList:
vocabList.remove(stopWord_)
#创建不重复的词汇表
# top30Words = calcMostFreq(vocabList,fullText)
# #存储出现次数最多的前30个单词
# for pairW in top30Words:
# if pairW[0] in vocabList:
# vocabList.remove(pairW[0])
#遍历单词,在词汇表中去掉出现次数最高的那些单词
trainingSet = list(range(2*minLen))
#创建训练集的索引列表
testSet = []
#创建测试集的索引列表
for i in range(5):#随机选取20个作为测试用例
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 = trainNBO(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
#返回值
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**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**")
for item in sortedSF:
print(item[0])
#遍历每个列表,先输出概率值大的单词
sortedNY = sorted(topNY,key=lambda pair:pair[1],reverse=True)
print("NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**")
for item in sortedNY:
print(item[0])
if __name__ == '__main__':
# ress源的实践
sf = feedparser.parse('http://sports.yahoo.com/nba/teams/hou/rss.xml')
ny = feedparser.parse('http://www.nasa.gov/rss/dyn/image_of_the_day.rss')
print(len(sf))
print(len(ny))
# vocabList, pSF, pNY = localWords(ny,sf)
getTopWords(ny, sf)
# 分类测试
testingNB()
参考博客:
https://www.showmeai.tech/article-detail/189
https://blog.csdn.net/ShowMeAI/article/details/123398851