机器学习第二周-朴素贝叶斯

机器学习第二周-朴素贝叶斯

本周学习内容

  • 分类问题:朴素贝叶斯
  • 分类问题:逻辑回归

第四章:基于概率论的分类方法: 朴素贝叶斯

关于贝叶斯

贝叶斯分类是一类分类算法的总称,这类算法是以贝叶斯定理为基础的。朴素贝叶斯中的朴素,是在整个形式化过程只做最原始最简单的假设。朴素贝叶斯可以用于分档分类领域。

贝叶斯理论

贝叶斯决策理论的核心思想是最高概率决策。我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:

  • 如果p1(x,y)> p2(x,y),那么类别为1
  • 如果p2(x,y)> p1(x,y),那么类别为2
使用条件概率来分类

应用贝叶斯规范

使用上面公式,可以定义贝叶斯分类规则为:

  • 如果P(c1 | x,y)> P(c2 | x,y),那么属于类别c1;
  • 如果P(c2 | x,y)> P(c1 | x,y),那么属于类别c2。

其中 p(c1 | x,y) 表示在给定x,y表示的数据点,那么该数据点来自类别c1的概率是多少

统计上的独立是指一个特征的出现和其他特征出现没有关系。在该模型中我们将所有特征看做相互独立,实际和假设误差其实很小,可以忽略。

朴素贝叶斯开发流程
收集数据: 可以使用任何方法。
准备数据: 需要数值型或者布尔型数据。
分析数据: 有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
训练算法: 计算不同的独立特征的条件概率。
测试算法: 计算错误率。
使用算法: 一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
朴素贝叶斯工作原理
提取所有文档中的词条并进行去重
获取文档的所有类别
计算每个类别中的文档数目
对每篇训练文档: 
    对每个类别: 
        如果词条出现在文档中-->增加该词条的计数值(for循环或者矩阵相加)
        增加所有词条的计数值(此类别下词条总数)
对每个类别: 
    对每个词条: 
        将该词条的数目除以总词条数目得到的条件概率(P(词条|类别))
返回该文档属于每个类别的条件概率(P(类别|文档的所有词条))

代码实战

留言板分类

特征:切分后的单词

类别:侮辱性和非侮辱性

初始化数据集

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

创建单词表

def createVocabList(dataSet):
    #创建单词表
    vocabSet = set([])  #初始化一个集合,目的是获取不重复的单词列表
    for document in dataSet:     #遍历属性集上的每一行
        vocabSet = vocabSet | set(document) #集合可以使用并运算,用来添加每一行未出现过的单词
    return list(vocabSet)    #返回单词表

将每一个文件转换为对应的词向量,词向量中每一个位置对应于单词表中的一个单词。1表示出现,0表示未出现

def setOfWords2Vec(vocabList, inputSet):
    '''
    函数的输入参数为词汇表及某个文档,输出的是文档向量,
    向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现
    '''
    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

输出的单词表为:

['food','please','has','so','take','maybe','flea','not','I','worthless','cute','my','mr','dalmation',...]

输出的词向量示例:

[0,1,1,0,0,0,1,0,0,0,0,1,0,0,1,0,0,...]

训练模型,从词向量中计算概率

def trainNB0(trainMatrix,trainCategory):
    '''
    输入分别为文件单词向量[[0,1,1,0...],[0,1,...],...]
    单词类别[0,1,0...]其中0表示非侮辱性,1表示侮辱性
    '''
    numTrainDocs = len(trainMatrix)   #总文件数,即文件行数
    numWords = len(trainMatrix[0])   #词向量长度,即单词数
    pAbusive = sum(trainCategory)/float(numTrainDocs)    #侮辱性文章概率
    p0Num = ones(numWords); p1Num = ones(numWords)      
    #构造在分别在p0和p1类别中各个单词出现次数的列表,使用1初始化是为了避免后面出现0乘和除0的情况
    p0Denom = 2.0; p1Denom = 2.0                        
    #p0,p1类别中出现的各种单词的总数,初始化
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:       #对于侮辱性文章
            p1Num += trainMatrix[i]      #累加侮辱词次数
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = log(p1Num/p1Denom)          
    #[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表
    p0Vect = log(p0Num/p0Denom)          
    #[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表
    return p0Vect,p1Vect,pAbusive   #

实际训练上面的算法

trainMat=[]   #将所有文件转换为对应的词向量,构造词向量矩阵
for postinDoc in listoposts:
    trainMat.append(setOfWords2Vec(myvocablist,postinDoc))
pov,p1v,pab=trainNB0(trainMat,listclasses)

结果

pab   #0.5
plv  
'''
array([-2.35137526, -3.04452244, -3.04452244, -3.04452244, -2.35137526,
       -2.35137526, -3.04452244, -2.35137526, -3.04452244, -1.94591015,
       -3.04452244, -3.04452244, -3.04452244, -3.04452244, -1.94591015,
       -3.04452244, -3.04452244, -3.04452244, -2.35137526, -2.35137526,
       -2.35137526, -2.35137526, -2.35137526, -2.35137526, -2.35137526,
       -3.04452244, -3.04452244, -3.04452244, -3.04452244, -2.35137526,
       -3.04452244, -1.65822808])
 '''
 

至此得到了每一类中每种单词出现的概率,以及每一类的概率

接下来就可以开始利用朴素贝叶斯原理进行分类了

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    '''
    vec2Classify是待分类的词向量
    '''
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)   
    #注意因为我们对所有概率结构都做了log运算,所以乘法变加法,除法变减法。并且,对于P1P2来说pw是一样的,所以没有必要求pw
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else: 
        return 0
使用朴素贝叶斯过滤垃圾邮件

在第一个例子中,数据是直接以单词形式给出,实际情况下是以文章形式给出,因此在本例子中,我主要学习了如何分割文件,提取单词。

切分文本

如果使用string.split()方法,无法成功分离出标点符号。因此选择正则表达式

import re
mySent = 'This book is the best book on Python or M.L. I have ever laid eyes upon.'
regEx = re.compile('\\W+')   
#书中这里的+是*,但是在实际环境运行了一下,发现使用*会把所有按照字符逐个切分,得不到单词,查阅资料后,换成了+,应该是python3已近取消了该语法
listOfTokens = regEx.split(mySent)
>>> listOfTokens
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon', '']

了解了正则表达式原理后我们就可以进行实际文本的切分了。

切分文本函数

def textParse(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 = [] #
    #导入并解析文本文件
    for i in range(1, 26):
        # 对于类别1的文件
        wordList = textParse(open('./email/spam/%d.txt' % i).read()) #打开文件并切分出单词
        docList.append(wordList)       #添加到文件列表
        classList.append(1)            #归类到1类别
        # 对于类别0的文件
        wordList = textParse(open('./email/ham/%d.txt' % i).read())#打开文件并切分出单词
        docList.append(wordList)     #添加到文件列表
        fullText.extend(wordList)   
        classList.append(0)          #归类到1类别
    # 创建词汇表    
    vocabList = createVocabList(docList)
    trainingSet = list(range(50))  #python3语法,range返回range对象,不是列表
    testSet = []     #这里保存测试集的索引值
    # 随机取 10 个邮件用来测试
    for i in range(10):
        # random.uniform(x, y) 随机生成一个范围为 x ~ y 的实数
        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('the errorCount is: ', errorCount )
    print('the testSet length is :', len(testSet)) 
    print( 'the error rate is :', float(errorCount)/len(testSet))

结果

the errorCount is:  1
the testSet length is : 10
the error rate is : 0.1

需要注意的是,如果使用python3,书中的部分代码可能已经可能不再适用,具体的我在代码注释中有说明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值