机器学习实战 -- 朴素贝叶斯

     朴素贝叶斯是一种基于概率论的分类方法,贝叶斯规则被用于很多分类的理论分析中,首先从实战的代码看一下朴素贝叶斯的操作方法,最后做理论分析,分析一下代码中为什么要这样做。

一、算法原理

    贝叶斯规则的公式如下


    w的类别可以表示为,在ci类别下w发生的概率乘以ci发生的概率再除以w发生的概率,想要理解书中的代码这一句话就够了。

二、代码实现

    先做一个小实验,针对侮辱性和非侮辱性的留言进行分类,代码如下:

    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]                                                                  
    return postingList,classVec

函数输入:所有的文本集合

函数输出:词汇表

def createVocabList(dataSet):
    vocabSet = set([])                      
    for document in dataSet:               
        vocabSet = vocabSet | set(document)   #将完全不重复的dataSet,通过并集赋值到vocabSet
    return list(vocabSet)

函数输入:词汇表,测试文本或训练文本

函数输出:词向量

def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                                    
    for word in inputSet:                                            
        if word in vocabList:                                            #如果词条存在于词汇表中,则置1
            returnVec[vocabList.index(word)] = 1
        else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec    

    这里,我们将vocabSet称为词汇表,inputset称为测试或者训练文本,函数setofwoed2vec()的输出称为词向量。

    createVocabList()函数的功能是将全有文本中含有的词汇都提取出来,然后写入一个大的列表中,这个列表中的所有元素都是不重复的。setOfWord2Vec()函数的功能是,对于输入的测试或者训练文本,将文本中的词汇都映射到词向量中,文本中出现的词,在词向量的相应位置就置1。

    有了上述两个函数就可以对输入的文本进行处理。

     2. 训练函数

训练函数清单:

函数输入:由词向量组成的列表,相应的标签

函数输出:p0vec 侮辱类词条对应的条件概率(p0vec是一个列表,其长度和词向量相同)

                p1vec 非侮辱类词条对应的条件概率

                pABusive  文本属于侮辱类词条的概率

def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                           
    numWords = len(trainMatrix[0])                           
    pAbusive = sum(trainCategory)/float(numTrainDocs)        
    p0Num = np.zeros(numWords); p1Num = np.zeros(numWords)    
    p0Denom = 0.0; p1Denom = 0.0                          
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                            #P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                                                #P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num/p1Denom                                      
    p0Vect = p0Num/p0Denom         
    return p0Vect,p1Vect,pAbusive                            #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

     首先得到文本集合中文本的个数,然后得到词向量的元素个数,因为trainCategory中属于侮辱性文档的的文本对应的值为1,所以由pAbusive = sum(trainCategory)/float(numTrainDocs) 可以得到p(ci)的概率。

     进入循环,每当文本文档是侮辱性的时候,就将该文本对应的词向量加到p1num对应的词向量上,然后计算侮辱性文档中的总词数,相除后得到的就是p( w | ci )。这是因为出现几率越高的词汇,在p1num中对应的数字就越大,侮辱性文本中就越有可能含有它。

     但实际能够应用的代码稍有修改,因为上述代码中,如果P(w0|1),P(w1|1),P(w2|1)中有一项为0,那么最终的p( w | ci )全部为0,为了解决这个问题,对上述代码做一下修改:

def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            
    numWords = len(trainMatrix[0])                          
    pAbusive = sum(trainCategory)/float(numTrainDocs)       
    p0Num = ones(numWords); p1Num = ones(numWords)          #初始化为1
    p0Denom = 2.0; p1Denom = 2.0                            #分母初始化为2
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                            #P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                                                #即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom)                            #取对数,防止下溢         
    p0Vect = np.log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive                            #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

     修改的部分是将分子初始化为1,分母初始化为2,这种做法叫Laplace Smoothing。具体的原理如下

    经过修改后,条件概率不会出现为0的情况,同时函数的变化更加平滑。

     3. 分类和测试函数

函数输入:待分类的词向量,p0Vec , p1Vec , p(ci)

函数输出:分类结果

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)        #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

     这个函数很简单,就不解释了,只是需要注意这里用log的加法代替了原来的乘法。

 测试函数:

def testingNB():
    listOPosts,listClasses = loadDataSet()                                 
    myVocabList = createVocabList(listOPosts)                            
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))             #将实验样本向量化
    p0V,p1V,pAb = trainNB0(np.array(trainMat),np.array(listClasses))        #训练朴素贝叶斯分类器
    testEntry = ['love', 'my', 'dalmation']                                 #测试样本1
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))          
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')                                    
    else:
        print(testEntry,'属于非侮辱类')                                       #执行分类并打印分类结果
    testEntry = ['stupid', 'garbage']                                       #测试样本2

    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))          
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')                                        
    else:
        print(testEntry,'属于非侮辱类')   

     首先训练分类器,用的数据集就是第一个函数中创建的数据。然后写一个测试的样本,将其向量化后丢到分类器中,输出分类结果。

    以上就是整个朴素贝叶斯分类器的工作过程,接下来看它的实际运用的例程。

三、实际运用(用朴素贝叶斯做邮件过滤)

     想要对邮件进行分类,首先要获取邮件中的文本信息(即 将邮件中的文本切分成词汇)。所以就需要写一个切分文本的函数:

函数输入:一个大的字符串

函数输出:切分好的词汇文本

def textParse(bigString):                                                 
    listOfTokens = re.split(r'\W*', bigString)                              # 把特殊符号作为切分标志,进行字符串切分
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]            # 除了单个字母,例如大写的I,其它单词变成小写

邮件分类测试函数

函数输入:空

函数输出:分类结果,错误率

def spamTest():
    docList = []; classList = []; fullText = []
    for i in range(1, 26):                                                  #遍历25个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())     #读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(1)                                                 #标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())      #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(0)                                                 #标记非垃圾邮件,1表示垃圾文件   
    vocabList = createVocabList(docList)                                   
    trainingSet = list(range(50)); testSet = []                             #创建存储训练集的索引值的列表和测试集的索引值的列表                       
    for i in range(10):                                                     #从50个邮件中,选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                                                 #错误数加1
            print("分类错误的测试集:",docList[docIndex])
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))

     这段程序就是在不停地调用之前的几个函数,并不难读,注释也很清楚,运行后显示的是分类的错误率。

     贝叶斯分类器的数学推导,会在之后补充上。

猜你喜欢

转载自blog.csdn.net/weixin_39749553/article/details/79733091