一.贝叶斯公式
1.1先验概率
官话定义:
1)P(cj)代表还没有训练模型之前,根据历史数据/经验估算cj拥有的初始概率。P(cj)常被称为cj的先验概率(prior probability) ,它反映了cj的概率分布,该分布独立于样本。
2)通常可以用样例中属于cj的样例数|cj|比上总样例数|D|来近似,即:
举个例子:
假设要猜测西瓜的好坏问题,如何判断正反概率?
根据自己以往购买西瓜的经验,好瓜的概率是p(好瓜)=0.6,坏瓜概率是p(坏瓜)=0.4 则可将p(好瓜)=0.6,p(坏瓜)=0.4作为先验概率,也就是观测新样本前就已知的先验分布p(y)。
即根据以往经验和分析。在实验或采样前就可以得到的概率称为先验概率。
我觉得这个例子比较通俗易懂
1.2后验概率
难懂官话:
1)给定数据样本x时cj成立的概率P(cj | x )被称为后验概率(posterior probability),因为它反映了在看到数据样本 x后 cj 成立的置信度。
2)大部分机器学习模型尝试得到都是后验概率
翻译一下:
就是某件事已经发生,想要计算这件事发生的原因是由某个因素引起的概率。
就比如已经知道是坏瓜,想要计算出是什么因素引起的(色泽,蜷缩等),就称为后验概率
1.3贝叶斯公式
贝叶斯公式如下:
通俗来讲是以下形式:
而不同类别之间进行大小比较,其分母是相同的,因此正比关系如下
由于朴素贝叶斯的朴素代表属性独立性的假设因此式子也可以改写为这样
1.4朴素贝叶斯应用
假设有一个数据集,包括了10个水果的特征和它们被分类为“苹果”或“橙子”的标签。水果的特征包括颜色(红色、橙色)、直径(大、小)和重量(轻、重)。数据集如下:
现在,要求使用朴素贝叶斯算法来预测一个新水果的类别。假设这个新水果的颜色是红色,直径是大,重量是重。我们可以使用朴素贝叶斯算法来计算出这个水果被分类为“苹果”和“橙子”的概率,然后选择概率较高的类别作为预测结果。
1)首先,我们需要计算出“苹果”和“橙子”的先验概率。在这个数据集中,共有6个苹果和4个橙子,因此“苹果”先验概率为6/10=0.6,“橙子”先验概率为4/10=0.4。
2)接着,我们需要计算出在给定水果颜色、直径和重量的条件下,水果属于“苹果”和“橙子”类别的概率。p(红/苹果) = 6/6 ,p(大/苹果) = 4/6 p(重/苹果)= 3/6
p(i/苹果) = 0.6 x p(红/苹果) x p(大/苹果) x p(重/苹果)= 0.2
p(红/橙子) = 0/4 ,p(大/橙子) = 2/4 p(重/橙子)= 2/4
p(i/橙子) = 0.4 x p(红/橙子) x p(大/橙子) x p(重/橙子)= 0
因为p(i/苹果)>p(i/橙子) 所以我们可以将这个新水果分类为“苹果”。
二.拉普拉斯修正
观察上述的应用,发现有一个问题,当判别橙子时,如果要判别的颜色是红色时,其概率为0,因为是概率连乘,有一个为0就都为0了,这样会出现过拟合问题
所以就引出了拉普拉斯修正,其目的是为了避免其他属性携带的信息,被训练集中未出现的属性值“抹去”,在估计概率值时通常要进行“拉普拉斯修正”
令 N 表示训练集 D 中可能的类别数,N_i表示第i个属性可能的取值数,则贝叶斯公式可修正为:
三.防溢出策略
问题:
1)条件概率乘法计算过程中,因子一般较小(均是小于1的实数)。当属性数量增多时候,会导致累乘结果下溢出的现象。(即无限趋近于0)
解决策略:
在代数中有ln(a*b) = ln(a)+ln(b),因此可以把条件概率累乘转化成对数累加。分类结果仅需对比概率的对数累加法运算后的数值,以确定划分的类别。
四.朴素贝叶斯的代码实现
4.1数据准备
将提供的文本文件构建成词条向量
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) # 取并集
return list(vocabSet)
# createVocabList:根据输入的数据集创建词汇表,返回一个不重复的词条列表。
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) # 取并集
return list(vocabSet)
# setOfWords2Vec:根据词汇表将输入的词条列表转换为向量,向量的每个元素为 1 或 0。
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
# bagOfWords2VecMN:根据词汇表构建词袋模型,即将每个词条的出现次数转换为一个向量。
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
4.2训练算法
从词向量计算概率,根据实际情况修改分类器
# trainNB0:训练朴素贝叶斯分类器,输入为训练文档矩阵和训练类别标签向量,
# 返回正常邮件类和垃圾邮件类的条件概率数组以及文档属于垃圾邮件类的概率。
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
print("文档数目:", numTrainDocs)
numWords = len(trainMatrix[0])
# 形状[40,37]
pAbusive = sum(trainCategory) / float(numTrainDocs)
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 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])
print("1:",p1Num / p1Denom)
print("0:",p0Num / p1Denom)
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
# classifyNB:朴素贝叶斯分类器的分类函数,根据输入的词条向量和条件概率数组判断文档属于正常邮件还是垃圾邮件。
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
p0=sum(vec2Classify*p0Vec)+np.log(1.0-pClass1)
if p1 > p0:
return 1
else:
return 0
# textParse:将大字符串解析为字符串列表。
def textParse(bigString):
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 0]
4.3测试算法
使用朴素贝叶斯进行交叉验证
# spamTest:主函数,测试朴素贝叶斯分类器的性能。
# 首先读取垃圾邮件和正常邮件,然后创建词汇表,训练朴素贝叶斯分类器,最后进行交叉验证测试。
def spamTest():
docList = []
classList = []
fullText = []
listOfTokens1 = []
spam_dir = r'C:\Users\86158\Desktop\机器学习\第五讲 朴素贝叶斯\贝叶斯实验\email\spam'
ham_dir = r'C:\Users\86158\Desktop\机器学习\第五讲 朴素贝叶斯\贝叶斯实验\email\ham'
# 读取垃圾邮件和正常邮件
for i in range(1, 26):
spam_file = os.path.join(spam_dir, f'{i}.txt')
ham_file = os.path.join(ham_dir, f'{i}.txt')
wordList = textParse(open(spam_file, 'r', encoding='iso8859-1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(1)
wordList = textParse(open(ham_file, 'r', encoding='iso8859-1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(0)
vocabList = createVocabList(docList)
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(np.array(trainMat), np.array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print("分类错误的测试集:", docList[docIndex])
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
4.4完整代码展示:
import numpy as np
import random
import re
import os
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) # 取并集
return list(vocabSet)
# createVocabList:根据输入的数据集创建词汇表,返回一个不重复的词条列表。
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) # 取并集
return list(vocabSet)
# setOfWords2Vec:根据词汇表将输入的词条列表转换为向量,向量的每个元素为 1 或 0。
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
# trainNB0:训练朴素贝叶斯分类器,输入为训练文档矩阵和训练类别标签向量,
# 返回正常邮件类和垃圾邮件类的条件概率数组以及文档属于垃圾邮件类的概率。
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
print("文档数目:", numTrainDocs)
numWords = len(trainMatrix[0])
# 形状[40,37]
pAbusive = sum(trainCategory) / float(numTrainDocs)
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 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])
print("1:",p1Num / p1Denom)
print("0:",p0Num / p1Denom)
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
# classifyNB:朴素贝叶斯分类器的分类函数,根据输入的词条向量和条件概率数组判断文档属于正常邮件还是垃圾邮件。
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
p0=sum(vec2Classify*p0Vec)+np.log(1.0-pClass1)
if p1 > p0:
return 1
else:
return 0
# bagOfWords2VecMN:根据词汇表构建词袋模型,即将每个词条的出现次数转换为一个向量。
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
# textParse:将大字符串解析为字符串列表。
def textParse(bigString):
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 0]
# spamTest:主函数,测试朴素贝叶斯分类器的性能。
# 首先读取垃圾邮件和正常邮件,然后创建词汇表,训练朴素贝叶斯分类器,最后进行交叉验证测试。
def spamTest():
docList = []
classList = []
fullText = []
listOfTokens1 = []
spam_dir = r'C:\Users\86158\Desktop\机器学习\第五讲 朴素贝叶斯\贝叶斯实验\email\spam'
ham_dir = r'C:\Users\86158\Desktop\机器学习\第五讲 朴素贝叶斯\贝叶斯实验\email\ham'
# 读取垃圾邮件和正常邮件
for i in range(1, 26):
spam_file = os.path.join(spam_dir, f'{i}.txt')
ham_file = os.path.join(ham_dir, f'{i}.txt')
wordList = textParse(open(spam_file, 'r', encoding='iso8859-1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(1)
wordList = textParse(open(ham_file, 'r', encoding='iso8859-1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(0)
vocabList = createVocabList(docList)
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(np.array(trainMat), np.array(trainClasses))
errorCount = 0
#for docIndex in testSet:
# wordVector = setOfWords2Vec(vocabList, docList[docIndex])
# if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
# errorCount += 1
# print("分类错误的测试集:", docList[docIndex])
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
4.5结果展示
五.小结
朴素贝叶斯是一种简单而有效的分类算法,它既有优点也有缺点:
优点:
1) 算法简单,易于实现和理解。
2) 在处理大规模数据集时具有较高的效率。
3) 对于小规模数据表现良好,适用于文本分类等领域。
4) 对于缺失数据不敏感,能够处理缺失值的情况。
缺点:
1)假设特征之间相互独立,这在实际情况中往往并不成立,可能导致分类性能下降。
2) 需要计算先验概率,分类决策存在错误率。
3)朴素贝叶斯算法通常被认为是一种较为简单的分类器,对于复杂的分类问题可能表现不佳。
4) 会带来一些准确率上的损失。