一,概念
朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同。比如决策树,KNN,逻辑回归,支持向量机等,他们都是判别方法,也就是直接学习出特征输出Y和特征X之间的关系,要么是决策函数,要么是条件分布。但是朴素贝叶斯却是生成方法,该算法原理简单,也易于实现。
1.先验概率
P(cj)代表还没有训练模型之前,根据历史数据/经验估算cj拥有的初始概率。P(cj)常被称为cj的先验概率(prior probability) ,它反映了cj的概率分布,该分布独立于样本。
通常可以用样例中属于cj的样例数|cj|比上总样例数|D|来近似,即:
2.后验概率
给定数据样本x时cj成立的概率P(cj | x )被称为后验概率(posterior probability),因为它反映了在看到数据样本 x后 cj 成立的置信度。
大部分机器学习模型尝试得到后验概率。
贝叶斯定理
已知两个独立事件A和B,事件B发生的前提下,事件A发生的概率可以表示为P(A|B),即上图中橙色部分占红色部分的比例,即:
公式:
3.朴素贝叶斯分类器
MAP 分类准则 MAP: Maximum A Posterior x 属于类别 c* 的概率:
利用 MAP 准则 利用贝叶斯准则转化为:
朴素贝叶斯分类器(Naïve Bayes Classifier)采用了“属性条件独立性假设” ,即每个属性独立地对分类结果发生影响。为方便公式标记,不妨记P(C=c|X=x)为P(c|x),基于属性条件独立性假设,贝叶斯公式可重写为:
朴素贝叶斯分类器的训练器的训练过程就是基于训练集D估计类先验概率 ,并为每个属性估计条件概率 。
令 表示训练集D中第c类样本组合的集合,则类先验概率:
4.拉普拉斯修正
在概率估计中,当对某个事件的概率进行估计时,如果该事件在样本中没有出现过,传统的频率估计方法会将其概率估计为零。然而,这可能导致在实际应用中遇到问题,在贝叶斯分类器中,如果一个特征在训练数据中没有出现过,那么该特征对分类结果的影响就会被忽略掉。
为了避免其他属性携带的信息,被训练集中未出现的属性值“抹去”,在估计概率值时通常要进行“拉普拉斯修正”: 令 N 表示训练集 D 中可能的类别数,N_i表示第i个属性可能的取值数,则贝叶斯公式可修正为:
5.防溢出策略
条件概率乘法计算过程中,因子一般较小(均是小于1的实数)。当属性数量增多时候,会导致累乘结果下溢出的现象。 在代数中有ln(a*b) = ln(a)+ln(b),因此可以把条件概率累乘转化成对数累加。分类结果仅需对比概率的对数累加法运算后的数值,以确定划分的类别。
二,朴素贝叶斯的简单实现
垃圾邮件的分类
import numpy as np
# 创建不重复词的列表 ———— 词汇表
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 vocabList: # 如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
returnVec[vocabList.index(word)] = 1
else:
print("单词 %s 不在词汇表中!" % word)
return returnVec
# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 获得训练的文档总数
numWords = len(trainMatrix[0]) # 获得每篇文档的词总数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算文档是侮辱类的概率
p0Num = np.ones(numWords) # 创建numpy.ones数组,初始化概率
p1Num = np.ones(numWords) # 创建numpy.ones数组,初始化概率
p0Denom = 2.0 # 初始化为2.0
p1Denom = 2.0 # 初始化为2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # 向量相加,统计侮辱类的条件概率的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i] # 向量相加,统计非侮辱类的条件概率的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Denom += sum(trainMatrix[i])
p1Vect = np.log(p1Num / p1Denom) # 侮辱类,每个元素除以该类别中的总词数
p0Vect = np.log(p0Num / p0Denom) # 非侮辱类,每个元素除以该类别中的总词数
return p0Vect, p1Vect, pAbusive # p0Vect非侮辱类的条件概率数组、p1Vect侮辱类的条件概率数组、pAbusive文档属于侮辱类的概率
# 朴素贝叶斯分类器分类函数
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
# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
# 文件解析
def textParse(bigString): # 输入字符串, 输出单词列表
import re
listOfTokens = re.split(r'\W+', bigString) # 字符串切分,去掉除单词、数字外的任意字符串
return [str(tok).lower() for tok in listOfTokens if len(str(tok)) > 2] # 除了单个字母外,其他字符串全部转换成小写
# 完整的垃圾邮件测试函数
def spamTest():
docList = [] # 文档列表
classList = [] # 文档标签
fullText = [] # 全部文档内容集合
for i in range(1, 26): # 遍历垃圾邮件和非垃圾邮件各25个
wordList = textParse(open('email/spam/%d.txt' % i).read()) # 读取垃圾邮件,将大字符串并将其解析为字符串列表
docList.append(wordList) # 垃圾邮件加入文档列表
fullText.extend(wordList) # 把当前垃圾邮件加入文档内容集合
classList.append(1) # 1表示垃圾邮件,标记垃圾邮件
wordList = textParse(open('email/ham/%d.txt' % i).read()) # 读非垃圾邮件,将大字符串并将其解析为字符串列表
docList.append(wordList) # 非垃邮件加入文档列表
fullText.extend(wordList) # 把当前非垃圾邮件加入文档内容集合
classList.append(0) # 0表示非垃圾邮件,标记非垃圾邮件
vocabList = createVocabList(docList) # 创建不重复的词汇表
trainingSet = list(range(50)) # 为训练集添加索引
testSet = [] # 创建测试集
for i in range(10): # 目的为了从50个邮件中,随机挑选出40个作为训练集,10个做测试集
randIndex = int(np.random.uniform(0, len(trainingSet))) # 随机产生索引
testSet.append(trainingSet[randIndex]) # 添加测试集的索引值
del (trainingSet[randIndex]) # 在训练集中,把加入测试集的索引删除
trainMat = [] # 创建训练集矩阵训练集类别标签系向量
trainClasses = [] # 训练集类别标签
for docIndex in trainingSet: # for循环使用词向量来填充trainMat列表t
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex])) # 把词集模型添加到训练矩阵中
trainClasses.append(classList[docIndex]) # 把类别添加到训练集类别标签中
p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses)) # 朴素贝叶斯分类器训练函数
print('词表:\n', vocabList)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('pSpam:\n', pSpam)
errorCount = 0 # 用于计数错误分类
for docIndex in testSet: # 循环遍历训练集
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex]) # 获得测试集的词集模型
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1 # 预测值和真值不一致,则错误分类计数加1
print("分类错误集", docList[docIndex])
print('错误率: ', float(errorCount) / len(testSet))
if __name__ == '__main__':
spamTest()
这段代码实现了一个基于朴素贝叶斯算法的垃圾邮件分类器。具体来说,代码中包括以下几个函数:
- createVocabList(dataSet):创建不重复词的列表,即词汇表。
- setOfWords2Vec(vocabList, inputSet):将输入的文本转换成词向量。
- trainNB0(trainMatrix, trainCategory):训练朴素贝叶斯分类器。
- classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):使用训练好的模型对新文本进行分类。
- bagOfWords2VecMN(vocabList, inputSet):朴素贝叶斯词袋模型。
- textParse(bigString):对原始文本进行解析,返回单词列表。
- spamTest():完整的垃圾邮件测试函数。
代码中的主要流程如下:
- 遍历垃圾邮件和非垃圾邮件各25个,将其解析为单词列表,并将垃圾邮件标记为1,非垃邮件标记为0。
- 创建词汇表,用于将文本转换成词向量。
- 随机选择10封邮件作为测试集,将剩下的40封邮件作为训练集。
- 将训练集转换成词向量,并使用训练集训练朴素贝叶斯分类器。
- 对测试集中的邮件进行分类,并计算分类错误率。
测试结果:
p0V给出了每个词汇在非垃圾邮件中出现的概率,而p1V给出了每个词汇在垃圾邮件中出现的概率。pSpam是指在整个数据集中垃圾邮件的比例。错误率为0.1,即10%,这表示有10%的测试集中的样本被错误地分类。
参考自【精选】机器学习 —— 朴素贝叶斯_机器学习贝叶斯_DreamWendy的博客-CSDN博客
三,小结
- 调整分类器参数:针对单词"scifinance"的错误分类,可以尝试调整分类器的参数,以提高分类的准确性。
- 数据集分析:在对数据集中具体单词的分布情况可以进行更深入的分析,特别是那些容易导致错误分类的单词。
- 考虑其他特征:除了单词出现的概率外,还可以考虑其他特征,像邮件的长度、特殊字符的使用频率等,来提高分类器的性能。
在对垃圾邮件分类的代码实现中,运用了垃圾邮件的代码简单修改后进行操作,理解了大部分内容,也学习到了朴素贝叶斯的简单实现。