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

基于朴素贝叶斯的分类器


我们都知道在概率论中有个大名鼎鼎的公式——贝叶斯公式(Bayes),主要是存在一个先验概率的思想,公式如下的:

p(A|B)=p(AB)p(B)=p(A)p(B|A)p(B) p ( A | B ) = p ( A B ) p ( B ) = p ( A ) ∗ p ( B | A ) p ( B )

基于这个公式,我们就可以用来进行分类,对公式不熟悉翻一下书就回忆起来了吧。很有名的一个例子就是用Bayes来判断一封邮件是不是垃圾邮件,这个分类是基于单词出现概率的,也就是说,分类器已经有一个词库,并且统计好了各种词出现的概率和是垃圾邮件的先验概率。

我们对上面的公式稍作修改,记C i i 为第i个类别, w⃗  w → 表示一个向量,它由多个条件组成(我自己演算时的一种记录方式,不一定严谨),也就是垃圾邮件的条件下各单词的出现概率,于是公式表示如下:

p(Ci|w⃗ )=p(Ci)p(w⃗ |Ci)p(w⃗ ) p ( C i | w → ) = p ( C i ) ∗ p ( w → | C i ) p ( w → )

OK,到了这里,我们会发现 p(Ci) p ( C i ) 是个定值,而 p(w⃗ |Ci)p(w⃗ ) p ( w → | C i ) , p ( w → ) 就有点不好求了。的确,这就是我们为什么叫朴素贝叶斯的这个“朴素”的含义了,我们假设各变量之间相互独立,相互独立嘛。。。这下就好办了,直接将各变量对应的概率值相乘就好了。于是乎;

p(Ci|w⃗ )=p(Ci)p(w⃗ |Ci)p(w⃗ )=p(Ci)nj=0p(wj|Ci)nj=0p(wj) p ( C i | w → ) = p ( C i ) ∗ p ( w → | C i ) p ( w → ) = p ( C i ) ∗ ∏ j = 0 n p ( w j | C i ) ∏ j = 0 n p ( w j )

我们要作的工作大致分为以下几步:
- 构建一个词库
- 统计出各词分别在是垃圾邮件和不是垃圾邮件条件下的概率

说明: p(Ci),nj=0p(wj) p ( C i ) , ∏ j = 0 n p ( w j ) ,是个定值,无需求出确切值,比较分子大小即可。

可以开始写代码了。


1. 先从一个简单的例子入手

数据集就6个sample,是某些人在宠物社区留言板的评论。有些喷子,爱好说脏话;有些人比较文明,和本人一样。

#  词表到向量的转化函数
def loadDatatSet():
    # 评论列表
    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'],
        ['my', '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
    ```
然后,根据这些评论生成一个词库(估计20几个词吧),同时定义一个函数,功能是将一句话转变为一个对应和词库维度相等的向量
```python
def createVocabList(dataset):
    vocabSet = set()
    for item in dataset:
        # 集合求并集
        vocabSet = vocabSet | set(item)
    vocabList = list(vocabSet)
    # 返回结果排序
    return sorted(vocabList, key=operator.itemgetter(0))


def setOfWords2vec(vocabList, input):
    resultVec = np.zeros((len(vocabList),))
    for word in input:
        #  如果词库中有这个词,就对应index位置置为1。
        if word in vocabList:
            resultVec[vocabList.index(word)] = 1
        # 词库中不存在该次暂时忽略
        else:
            print(word, "is not in the vocabulary list")
    return resultVec




class="se-preview-section-delimiter">div>

然后根据朴素贝叶斯求概率。说明一下,pAbusiveWordspGoodWords就是在求 p(wj|Ci) p ( w j | C i ) 。防止出现因为词库中没统计到某个单词的出现频率而导致概率为0的情况(实际上不可能为0),用到了拉普拉斯平滑处理(Laplace Smoothing)。防止概率太小,用了log函数对最终结果处理,对数函数并不会影响单调性。

def trainNaiveBayes(trainMatrix, trainLabels):
    # 朴素贝叶斯
    m = len(trainMatrix)  # 训练数据总数
    n = len(trainMatrix[0])  # 词库词汇总数

    # 下面是做统计工作
    pAbusive = sum(trainLabels) / float(m)  # 1代表是脏话,利用了列表加法
    pGood = 1 - pAbusive

    # 初始化时利用了拉普拉斯平滑处理,分子加1,分母加单词总数

    abusiveWordsNum = np.ones((n,))  # 脏话条件下,各词汇出现的次数。 初始化为1,放置出现很多单词出现次数为0
    abusiveWordsTotal = n  # 脏话里面所有词总数。 初始化为n

    goodWordsNum = np.ones((n,))  # 好话条件下,各词汇出现的次数
    goodWordsTotal = n  # 好话里面所有词总数。 初始化为n
    for i in range(m):
        if trainLabels[i] == 1:
            abusiveWordsNum += trainMatrix[i]
            abusiveWordsTotal += sum(trainMatrix[i])
        elif trainLabels[i] == 0:
            goodWordsNum += trainMatrix[i]
            goodWordsTotal += sum(trainMatrix[i])

    pAbusiveWords = np.log(abusiveWordsNum / abusiveWordsTotal)  # 脏话条件下,各词汇出现频率,对数处理,防止下溢
    pGoodWords = np.log(goodWordsNum / goodWordsTotal)  # 好话条件下,各词汇出现频率
    return pAbusive, pGood, pAbusiveWords, pGoodWords





class="se-preview-section-delimiter">div>

然后就是测试了,代码思路也很简单清晰。

def test():
    # 训练的过程
    postingList, classVec = loadDatatSet()
    vocabList = createVocabList(postingList)
    print("所有的词汇库为", vocabList)
    trainMatrix = []
    for item in postingList:
        trainMatrix.append(setOfWords2vec(vocabList, item))
    pAbusive, pGood, pAbusiveWords, pGoodWords = trainNaiveBayes(trainMatrix, classVec)

    print(pAbusiveWords)
    print(pGoodWords)

    # 预测的过程
    testPostingList = [
        ['love', 'my', 'dalmation'],
        ['stupid', 'garbage']
    ]
    testMatrix = []
    for index, item in enumerate(testPostingList):
        testMatrix.append(setOfWords2vec(vocabList, testPostingList[index]))

    for i in range(len(testMatrix)):
        if classify(testMatrix[i], pAbusive, pGood, pAbusiveWords, pGoodWords) == 1:
            print(testPostingList[i], "是一句脏话")
        else:
            print(testPostingList[i], "是一句好话")




class="se-preview-section-delimiter">div>

2. 真正处理文本文件

说明:提取词库的时候,我们采取的办法是字符大于2的都保留在词库中,用一个简单的正则处理一下即可。

def textParse(bigStr):
    listOfTokens = re.split(r'\\w*', bigStr)
    # 返回字长大于等于2的所有单词
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]


def spamTest():
    emailList = []
    classLabels = []
    fullWords = []

    # 读取垃圾邮件
    for i in range(1,26):
        wordList = textParse(open("email/spam/%d.txt" % i,encoding='gbk').read())
        fullWords.extend(wordList)
        classLabels.append(1)
        emailList.append(wordList)

    # 读取非垃圾邮件
    for i in range(1,26):
        wordList = textParse(open("email/ham/%d.txt" % i,encoding='gbk').read())
        fullWords.extend(wordList)
        classLabels.append(0)
        emailList.append(wordList)

    # 构建词库(Token库)
    vocabulary = createVocabList(emailList)
    print(vocabulary)
    trainingSetIndexList = list(range(50))
    testSetIndexList = []

    # 构建训练集和测试集,随机抽10个作为测试集
    for i in range(10):
        randIndex = int(random.uniform(0, len(trainingSetIndexList)))
        testSetIndexList.append(randIndex)
        del(trainingSetIndexList[randIndex])


    trainMatrix = []
    trainClassLabels = []

    for index in trainingSetIndexList:
        trainMatrix.append(setOfWords2vec(vocabulary, emailList[index]))
        trainClassLabels.append(classLabels[index])

    #  训练集训练
    pSpam, pNotSpam, pSpamWords, pNotSpamWords = trainNaiveBayes(trainMatrix, trainClassLabels)

    # 测试数据
    errorCount=0
    errorEmails=[]
    for index in testSetIndexList:
        wordVect=setOfWords2vec(vocabulary,emailList[index])
        predit=classify(wordVect, pSpam, pNotSpam, pSpamWords, pNotSpamWords)
        if predit!=classLabels[index]:
            errorCount +=1
            errorEmails.append({"email":emailList[index],"errorClass":predit})

    return float(errorCount)/len(testSetIndexList),errorEmails


if __name__ == '__main__':
    #test()
    for 
    errorRate, errorEmails=spamTest()
    print("测试集分类错误率为:",errorRate*100,"%")
    print("错误分类的邮件和对应的分类为:")
    for i in range(len(errorEmails)):
        print(errorEmails[i]["email"])
        print(errorEmails[i]["errorClass"])

运行结果:
第 1 次测试集分类错误率为: 20.0 %
第 2 次测试集分类错误率为: 10.0 %
第 3 次测试集分类错误率为: 10.0 %
第 4 次测试集分类错误率为: 0.0 %
第 5 次测试集分类错误率为: 30.0 %
第 6 次测试集分类错误率为: 10.0 %
第 7 次测试集分类错误率为: 10.0 %
第 8 次测试集分类错误率为: 0.0 %
第 9 次测试集分类错误率为: 10.0 %
第 10 次测试集分类错误率为: 20.0 %

因为在选择测试集的时候是随机选取的,每次的错误结果可以能略有差别,我们跑10次,总体上来说,结果还是挺不错的(毕竟所有数据也只有50个邮件,邮件次数也比较少),谁叫机器都是数据喂出来的呢。

溜了溜了 :)

你可能感兴趣的:(机器学习)