原来大一大二那会儿上着数学课总想着学这高数、概论统计有什么用呢·······难不成我们去买菜的时候会跟人家要根号二斤豆腐不成?但是自从开始接触机器学习到现在来看,数学对于计算机系的同学还是非常重要的,因为好多分类算法中涉及到高等数学、概率统计、线性代数中的内容,数学作为一种工具来解决我们实际遇到的问题,今天的这个分类算法涉及到概论统计中的条件概论算法,使用贝叶斯准则来通过三个已知概率值计算未知概率值。

一、条件概率

首先来回顾一下概率统计中有关条件概率的知识,说起概率统计就不得不提到那个折磨了多少学生的摸球游戏:现在已知A盒子中有2个黄球2个白球,B盒子中有1个黄球2个白球,现在的问题是:在已知摸到的球来自B盒子,求取出黄球的概率是多少?

现在可以来设几个事件:

A={取到的小球来自A盒子},B={取到的小球来自B盒子},C={取到黄球}

按照题目要求我们需要求得是P(C|B),条件概率公式为:P(C|B)=P(CB)/P(B)

P(CB)的意思是取到的小球来自B盒子而且为黄色,因此P(CB)=1/7

P(B)的意思是取到的小球来自B盒子,因此P(B)=3/7

由上面可以得出P(C|B)=1/3,这是一个简单的条件概率的例子,另一种计算条件概率的方法称为贝叶斯准则,由它我们可以知道P(C|B)与P(B|C)的关系是:

二、分类方法介绍

1.贝叶斯决策理论(Bayesian decision theory)

朴素贝叶斯(Naïve Bayes)中的“朴素”意思就是最原始的意思,它是贝叶斯决策理论中的一部分,这里使用的概率解释属于贝叶斯概率理论范畴,该理论由托马斯•贝叶斯(Thomas Bayes)的名字命名。

我们通过书中的一个例子来弄明白贝叶斯决策理论的核心思想,假设我们有一个数据集,它由两类数据组成,数据分布如下图所示。

我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中用三角形表示的类别)的概率。

那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
(1)如果p1(x,y)>p2(x,y),那么类别为1。
(2)如果p2(x,y)>p1(x,y),那么类别为2。
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。要分类上图中的数据目前我们有三种方法选择:

(1)使用KNN算法,进行1000次距离计算;
(2)使用决策树,分别沿x轴、y轴划分数据;
(3)计算数据点属于每个类别的概率,并进行比较。

从计算量来看通过朴素贝叶斯概率论分类要比KNN算法计算量小的多,不幸的是决策树无法解决上述类型的分类。

2.朴素贝叶斯分类器属性

朴素贝叶斯
优点:在数据较少的情况下仍然有效,可以处理多类别问题
缺点:对于输入数据的准备方式较为敏感
适用数据类型:标称型数据

三、简单文本分类方法

要从文本中获取特征,要先拆分文本,这里的特征来自文本的词条(token),一个词条是字符的任意组合,然后将每一个文本片段表示为一个词条向量,其中值为1表示该词条在文档中出现,0表示未出现。

以留言板为例,我们需要过滤那些负面或者侮辱性言论的留言,因此我们就得到了关于留言的两个分类:侮辱类和非侮辱类,使用1和0表示。

1、从文本中构建词条向量

(1)创建一些人为模拟数据来描述基本数据

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']]
    classVec = [0, 1, 0, 1, 0, 1]  # 1表示侮辱性文字,0表示正常文字
    return postingList, classVec

(2)创建不重复的词典集向量

def createVocabList(dataSet):
    '''
    创建一个包含所有文本中出现的不重复次的词汇列表
    :param dataSet: 文本集
    :return: 不重复的词汇表
    '''
    vocabSet = set([])  # 创建空集
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 使用set函数的属性来去除重复单词
    return list(vocabSet)

(3)创建文档向量

用来统计文档中出现的单词是否在词汇表中

def setOfWords2Vec(vocabList, inputSet):
    '''
    文档向量对比函数
    :param vocabList: 词汇表向量
    :param inputSet: 文档
    :return: 对比处理后的向量结果集
    '''
    returnVec = [0] * len(vocabList)#创建一个与单词表相同长度的向量并都初始化为0
    for word in inputSet:#遍历该文档,若出现单词表中的单词则将输出文档向量对应位置设为1
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print "the word: %s is not in my Vocabulary!" % word
    return returnVec

2、训练算法

在这里我们需要应对如下公式

假设所有词都相互独立,则意味着可以使用如下算式进行计算

计算概率的思路是:

(1).计算每个类别中的文档数目

(2).对每篇训练文档:

(2.1) 对每个类别:

(2.1.1) 如果词条在文档中出现这增加该词条的计数值

(2.1.2) 增加所有词条的计数值

(3).对每个类别:

(3.1)对每个词条:

(3.1.1) 将该词条的数目除以总词条数目得到条件概率

(4).返回每个类别的概率

代码实现

def trainNB0(trainMatrix,trainCategory):
    '''
    朴素贝叶斯分类器训练函数
    :param trainMatrix: 文档矩阵
    :param trainCategory: 文档类别标签向量
    :return: 
    '''
    numTrainDocs=len(trainMatrix)
    numWords=len(trainMatrix[0])
    #初始化概率
    pAbusive=sum(trainCategory)/float(numTrainDocs)
    p0Num=ones(numWords)
    p1Num=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])
    p0Vect = log(p0Num/p0Denom)
    p1Vect = log(p1Num/p1Denom)

    return p0Vect,p1Vect,pAbusive

这里有些需要注意的地方:

(1)有关参数初始化问题

在使用以上公式计算时会出现其中某一项概率为0的情况,那么乘积最后也为0,为了避免出现这种情况我们将p0Num、p1Num初始化为1将p0Denom、p1Denom初始化为2

(2)下溢出问题

依然是上述公式带来的问题,当参与计算乘积的每一项都非常小的时候,程序将无法得出一个正确的结果,因为多项非常小的数作乘积后经过四舍五入会导致出现结果为0的情况,因此我们推荐使用对乘积取自然对数即变为一下公式不会影响计算结果

3、构建朴素贝叶斯分类器

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    '''
    朴素贝叶斯分类器
    :param vec2Classify: 
    :param p0Vec: 
    :param p1Vec: 
    :param pClass1: 
    :return: 
    '''
    p1=sum(vec2Classify*p1Vec)+log(pClass1)
    p0=sum(vec2Classify*p0Vec)+log(1.0-pClass1)
    if p1>p0:
        return 1
    else:
        return 0

4、测试算法

def testingNB():
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:  # 分割输入文本为文本向量
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0v, p1v, pAb = trainNB0(array(trainMat),array(listClasses))
    testEntry = ['love', 'my','yes']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print testEntry, 'classified as: ', classifyNB(thisDoc, p0v, p1v, pAb)
    testEntry=['stupid','garbage']
    thisDoc=array(setOfWords2Vec(myVocabList,testEntry))
    print testEntry,'classified as: ',classifyNB(thisDoc,p0v,p1v,pAb)

得到的测试结果为:

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

1、文本数据解析方法

我们希望得到的单词都是统一的因此在分割文本的时候需要使用正则表达式来进行分割,分割符为除单词、数字外的任意字符,并且单词都统一为小写。

def textParse(bigString):
    '''
    文本处理函数
    :param bigString: 文本
    :return: 处理好的单词
    '''
    import re
    #使用正则表达式来切分句子,分隔符是除单词、数字外的任意字符串
    listOfTokens=re.split(r'\W*',bigString)
    #返回时要注意去掉空字符串而且对于该项目来说我们希望所有词的形式都是统一的因此将所有字符变为小写
    return [tok.lower() for tok in listOfTokens if len(tok)>2]

2、使用朴素贝叶斯进行交叉验证

def spamTest():
    '''
    自动化处理函数,用于测试算法,使用朴素贝叶斯进行交叉验证
    :return: 
    '''
    docList=[]
    classList=[]
    fullText=[]
    for i in range(1,26):
        wordList=textParse(open("email/spam/%d.txt" %i).read())
        #注意两种添加元素的方法是不同的
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList=textParse(open("email/ham/%d.txt" %i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList=createVocabList(docList)
    #训练集
    trainingSet=range(50)
    #测试集
    testSet=[]
    '''
    随机抽取10个文件,选择10个文件到测试集,并将其从训练集中剔除,
    这种随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为存留交叉验证
    '''
    for i in range(10):
        randIndex=int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat=[]
    trainClasses=[]

    #将选好的10个样本存入训练矩阵和训练分类器中
    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 error rate is ',float(errorCount)/len(testSet)

随机计算结果:

这里使用的邮件是通过读取本地文档型邮件来实现的,我会将用到的资源放到代码中,直接下载即可。

这里需要注意的是:

(1)留存交叉验证(hold-out cross validation):随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为存留交叉验证

(2)这里只进行了一次随机迭代,想要更精确的估计分类器的错误率需要进行多次实验取平均值

点我开启源码传送门!

如有问题请与我联系喔~请发送邮件到如下邮箱,我看到后会及时回复。

bubblyyi@outlook.com

欢迎转载,转载时请标明出。

查看更多文章请访问我的个人博客 www.bubblyyi.com

请喝咖啡☕️

打赏

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注