机器学习——朴素贝叶斯

简介

朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同。比如决策树,KNN,逻辑回归,支持向量机等,他们都是判别方法,也就是直接学习出特征输出Y和特征X之间的关系,要么是决策函数,要么是条件分布。但是朴素贝叶斯却是生成方法,该算法原理简单,也易于实现。

一、朴素贝叶斯算法

朴素贝叶斯是贝叶斯分类器的一个扩展,是用于文档分类的常用算法。 贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。而朴素贝叶斯分类时贝叶斯分类中最简单,也是最常见的一种分类方法。

1.1贝叶斯定理

先验概率:即基于统计的概率,是基于以往历史经验和分析得到的结果,不需要依赖当前发生的条件。
后验概率:则是从条件概率而来,由因推果,是基于当下发生了事件之后计算的概率,依赖于当前发生的条件。

条件概率:记事件A发生的概率为P(A),事件B发生的概率为P(B),则在B事件发生的前提下,A事件发生的概率即为条件概率,记为P(A|B)。

由老师上课举的例子去,我们可以通过如下图片清晰的明白条件概率如何计算:


P(A|B)=\frac{P(AB)}{P(B)}

联合概率:表示两个事件共同发生的概率。A与B的联合概率表示为P(AB),或者P(A∩B)。

贝叶斯公式:贝叶斯公式便是基于条件概率,通过P(B|A)来求P(A|B),如下:

P(A|B)=\frac{P(B|A)*P(A)}{P(B)} 

其中,P(A|B)为后验概率,P(B|A)为条件概率,P(A)为先验概率。

全概率公式:表示若事件A_{1},A_{2},...,A_{n}构成一个完备事件组且都有正概率,则对任意一个事件B都有公式成立: P(B)=\sum_{i=1}^{n}P(B|A_{i})*P(A_{i})

将全概率公式代入贝叶斯公式中,得到:P(A|B)=\frac{P(B|A)*P(A)}{\sum_{i=1}^{n}P(B|A_{i})*P(A_{i})}

1.2朴素贝叶斯算法的原理

特征条件假设假设每个特征之间没有联系,给定训练数据集,其中每个样本x都包括n维特征,即x = ({x_1},{x_2}, \cdots ,{x_n}),类标记集合含有k种类别,即y = ({y_1},{y_2}, \cdots ,{y_k})

对于给定的新样本x,判断其属于哪个标记的类别,根据贝叶斯定理,可以得到x属于{y_k}类别的概率P({y_k}|x)P(y_{k}|x)=\frac{P(x|y_{k})*P(y_{k})}{\sum_{k}^{}P(x|y_{k})*P(y_{k})}

后验概率最大的类别记为预测类别,即:\arg_{y_{k}}maxP(y_{k}|x)

朴素贝叶斯算法对条件概率分布作出了独立性的假设,通俗地讲就是说假设各个维度的特征x1,x2,⋯,xn互相独立,在这个假设的前提上,条件概率可以转化为:

P(x|y_{k})=P(x_{1},x_{2},...x_{n}|y_{k})=\prod_{i=1}^{n}P(x_{i}|y_{k})

代入贝叶斯公式中,得到:P(y_{k}|x)=\frac{P(y_{k})*\prod_{i=1}^{n}P(x_{i}|y_{k})}{\sum_{k}^{}P(y_{k})*\prod_{i=1}^{n}P(x_{i}|y_{k})}

于是,朴素贝叶斯分类器可表示为:f(x)=\arg _{y_{k}}maxP(y_{k}|x)=\arg _{y_{k}}max\frac{P(y_{k})*\prod_{i=1}^{n}P(x_{i}|y_{k})}{\sum_{k}^{}P(y_{k})*\prod_{i=1}^{n}P(x_{i}|y_{k})}

因为对所有的y_{k},上式中的分母的值都是一样的,所以可以忽略分母部分,朴素贝叶斯分类器最终表示为: f(x)=\arg _{y_{k}}maxP(y_{k})*\prod_{i=1}^{n}P(x_{i}|y_{k})

1.3拉普拉斯平滑

为了解决零概率的问题,法国数学家拉普拉斯最早提出用加1的方法估计没有出现过的现象的概率,所以加法平滑也叫做拉普拉斯平滑。假定训练样本很大时,每个分量x的计数加1造成的估计概率变化可以忽略不计,但可以方便有效的避免零概率问题。

P(y_{k})*\prod_{i=1}^{n}P(x_{i}|y_{k})是一个多项乘法公式,其中有一项数值为0,则整个公式就为0显然不合理,避免每一项为零的做法就是在分子、分母上各加一个数值。

P(y)=\frac{|D_{y}+1|}{|D|+N},|D_{y}|表示分类y的样本数,|D|为样本总数,N是分类总数。

P(x_{i}|D_{y})=\frac{|D_{yx_{i}}|+1}{|D_{y}|+N_{i}},|D_{yx_{i}}|表示分类y的样本数,|D_{y}|表示分类y的样本数,N_{i}表示i属性的可能的取值数。

在实际的使用中也经常使用加λ(0≤λ≤1)来代替简单加1。 如果对N个计数都加上λ,这时分母也要记得加上N×λ。

1.4防溢出策略

条件概率乘法计算过程中,因子一般较小(均是小于1的实数)。当属性数量增多时候,会导致累乘结果下溢出的现象。在代数中有  \ln(a*b)=\ln (a)+\ln (b) ,因此可以把条件概率累乘转化成对数累加。分类结果仅需对比概率的对数累加法运算后的数值,以确定划分的类别。

1.5朴素贝叶斯算法的优缺点

优点

1、朴素贝叶斯模型有稳定的分类效率。
2、对小规模的数据表现很好,能处理多分类任务,适合增量式训练,尤其是数据量超出内存时,可以一批批的去增量训练。
3、对缺失数据不太敏感,算法也比较简单,常用于文本分类。

缺点

1、需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳。
2、对输入数据的表达形式很敏感(离散、连续,值极大极小之类的)。

二、使用朴素贝叶斯进行文档分类

2.1准备数据:从文本中构建词向量

# 创建实验样本
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)                    # 返回不重复的词条列表
 
 
# 输出文档向量
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
 
 
 
# 测试函数效果
 
# 创建实验样本
listPosts, listClasses = loadDataSet()
print('数据集\n', listPosts)
 
# 创建词汇表
myVocabList = createVocabList(listPosts)  
print('词汇表:\n', myVocabList)
 
# 输出文档向量
print(setOfWords2Vec(myVocabList, listPosts[5]))   

 

如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1,其余设置为0。

2.2训练算法:从词向量计算概率

# 朴素贝叶斯分类器训练函数
from gettext import npgettext
 
 
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)                      # 获得训练的文档总数
    numWords = len(trainMatrix[0])                       # 获得每篇文档的词总数
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 计算文档是侮辱类的概率
    p0Num = ones(numWords)                              # 创建numpy.ones数组,词条出现次数初始化为1,拉普拉斯平滑
    p1Num = ones(numWords)                              
    p0Denom = 2.0                                        # 分母初始化为2,拉普拉斯平滑
    p1Denom = 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文档属于侮辱类的概率
# 测试代码
listPosts, listClasses = loadDataSet()    # 创建实验样本
print('数据集\n', listPosts)
myVocabList = createVocabList(listPosts)  # 创建词汇表
print('词汇表:\n', myVocabList)
trainMat = []
for postinDoc in listPosts:               # for循环使用词向量来填充trainMat列表
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('classVec:\n',listClasses)
print('pAb:\n', pAb)

对运行结果分析,p0V存放的是属于类别0的各单词的条件概率,即各个单词属于非侮辱类的条件概率;p1V存放的是属于类别1的各单词的条件概率,即各个单词属于侮辱类的条件概率。pAb是所有侮辱类的样本占所有样本的概率,在listClasses列表[0, 1, 0, 1, 0, 1]中,1表示侮辱类文字,0表示非侮辱类文字,既有3个侮辱类,3个非侮辱类,所以侮辱类的概率是0.5,非侮辱类的概率是也0.5,pAb就是先验概率。

2.3测试算法:根据现实情况修改分类器 

使用NumPy的数组来计算两个向量相乘的结果 。这里的相乘是指对应元素相乘,即先将两个向量中的第1个元素相乘,然后将第2个元素相乘,以此类推。接下来将词汇表 中所有词的对应值相加,然后将该值加到类别的对数概率上。最后,比较类别的概率返回大概率对应的类别标签。

# 测试朴素贝叶斯分类器
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 testingNB():
    listOPosts, listClasses = loadDataSet()                           # 创建实验样本
    myVocabList = createVocabList(listOPosts)                         # 创建词汇表
    trainMat = []                                                     # 文档矩阵
    for postinDoc in listOPosts:                                      # for循环使用词向量来填充trainMat列表
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))     # 获得概率数组及先验概率
    testEntry = ['love', 'my', 'dalmation']                           # 输入测试1
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')
    else:
        print(testEntry,'属于非侮辱类')
    testEntry = ['stupid', 'garbage']                                 # 输入测试2
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')
    else:
        print(testEntry,'属于非侮辱类')
 
if __name__ == '__main__':
    testingNB()

2.4准备数据:文档词袋模型 

 将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。

# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    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

三、使用朴素贝叶斯过滤垃圾邮件 

普通邮件(ham)

垃圾邮件(spam)

 

3.1准备数据:切分文本 

# 例子
mySent = 'This book is the best book on Python or M.L. I have ever laid  eyes upon.'
print(mySent.split())

 

3.2测试算法:使用朴素贝叶斯进行交叉验证 

# 文件解析
def textParse(bigString):  # 输入字符串, 输出单词列表
    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, 22):                                           # 遍历垃圾邮件和非垃圾邮件各21个
        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(42))                                    # 为训练集添加索引
    testSet = []                                                     # 创建测试集
    for i in range(8):                                              # 目的为了从42个邮件中,随机挑选出34个作为训练集,8个做测试集
        randIndex = int(random.uniform(0, len(trainingSet)))         # 随机产生索引
        testSet.append(trainingSet[randIndex])                       # 添加测试集的索引值
        del (trainingSet[randIndex])                                 # 在训练集中,把加入测试集的索引删除
 
    trainMat = []                                                    # 创建训练集矩阵训练集类别标签系向量
    trainClasses = []    
    #print(vocabList)
    #print(docList[docIndex])                                            # 训练集类别标签
    for docIndex in trainingSet:                                     # for循环使用词向量来填充trainMat列表t
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))  # 把词集模型添加到训练矩阵中
        trainClasses.append(classList[docIndex])                     # 把类别添加到训练集类别标签中
    p0V, p1V, pSpam = trainNB0(array(trainMat), 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(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1                                          # 预测值和真值不一致,则错误分类计数加1
            print("分类错误集", docList[docIndex])
    print('错误率: ', float(errorCount) / len(testSet))

 

函数spamTest()对贝叶斯垃圾邮件分类器进行自动化处理。导入文件夹spam与ham下的文本文件,并将它们解析为词列表 。接下来构建一个测试集与一个训练集,两个集合中的 邮件都是随机选出的。有50封电子邮件,并不是很多,其中的9封电子邮件被随机选择为测试集。分类器所需要的概率计算只利用训练集中的文档来完成。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错误的文档的此表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说9次,然后求平均值。

四、问题及总结

问题

1、出现name 'log' is not defined错误无法完成防溢出策略

解决方法:导入numpy库,使用np.log可成功运行

2、读取ham和spam文件失败

解决方法:python中文件路径与之前所学编程语言略微不同,需使用r“路径”,路径用\隔开

总结

本次实验我们学习了朴素贝叶斯,并学习了它的算法,优化及代码实现。朴素贝叶斯算法对大数量训练和查询时具有较高的速度。即使使用超大规模的训练集,针对每个项目通常也只会有相对较少的特征数,并且对项目的训练和分类也仅仅是特征概率的数学运算而已;对小规模的数据表现很好,能个处理多分类任务,适合增量式训练(即可以实时的对新增的样本进行训练;对缺失数据不太敏感,算法也比较简单,常用于文本分类;此外朴素贝叶斯对结果解释容易理解。但对于输入数据的准备方式较为敏感,因此对数据的准备方式要符合条件。总的来说是一次收获满满的实验。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值