机器学习实战—朴素贝叶斯及要点注解

书籍:《机器学习实战》中文版
IDE:PyCharm Edu 4.02

环境:Adaconda3  python3.6


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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 is abusive, 0 not
    return postingList,classVec
# 创建一个包含在所有文档中出现的不重复词的列表
# 疑惑:每次运行此函数,得到的列表中,单词的顺序不同。
def createVocaList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)   #求集合并集
    return list(vocabSet)
# 构造固定长度的词集模型
# 词集模型:词集中,每个词只能出现一次
def setOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else: print("the word:%s is not in my Vocabulary!" % word)
    return returnVec
# 词袋模型:词袋中,允许每个词出现多次
def bagOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec
# 参数估计
# 两类问题
def trainNB(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    # 初始化概率(分子和分母变量),且采取拉普拉斯平滑
    # 假设所有词均出现过一次
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    # 分母加K。K代表类别数目,这里只有两类。
    p0Denom = 2.0
    p1Denom = 2.0
    # p0Num = zeros(numWords)
    # p1Num = zeros(numWords)
    # p0Denom = 0.0
    # p1Denom = 0.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])
    # p1Vect = p1Num/p1Denom
    # p0Vect = p0Num/p0Denom
    # 防止下溢出。对乘积取对数ln
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive
# 朴素贝叶斯准则
# vec2Classify:带分类向量
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  #做平滑处理后,乘积变相加
    p0 = sum(vec2Classify * p0Vec) + log(1-pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
# 将以上函数封装,测试函数的正确性
# 参数inputTest是待分类的列表
def testingNB(inputTest):
    listPosts,listClasses = loadDataSet()
    myVocabList = createVocaList(listPosts)
    trainMat = []
    for postinDoc in listPosts:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    p0V,p1V,pAb = trainNB(trainMat,listClasses) #p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
    thisDoc = setOfWords2Vec(myVocabList,inputTest)
    print(inputTest,"classified as: ",classifyNB(thisDoc,p0V,p1V,pAb))
#print(testingNB(['love','my','dalmation']))
# 例子:垃圾邮件分类
# 用正则表达式分离句子并将所有字母变为小写且过滤掉字母个数小于2的(URLd地址原因)
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\w*',bigString)
    return [word.lower() for word in listOfTokens if len(word)>2]
def spamTest():
    docList=[];classList=[];fullText=[]
    #spam和ham文件夹下各25个文本
    for i in range(1,26):
        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)
    vocabList = createVocaList(docList)
    #原文trainingSet = range(50)
    trainingSet = list(range(50)) #本例共50封邮件
    testSet = []  #随机选10封作为测试集
    for i in range(10):
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    #print(testSet,trainingSet)
    trainMat = [];trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2Vec(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pAb = trainNB(trainMat,trainClasses)
    errorCount = 0
    for docIndex in testSet:
        wordVector = bagOfWords2Vec(vocabList,docList[docIndex])
        if classifyNB(wordVector,p0V,p1V,pAb) != classList[docIndex]:
            errorCount += 1
            print("classification error",docList[docIndex])
    print('the error rate is: ',float(errorCount)/len(testSet))
print(spamTest())


注解:

1、def createVocaList(dataSet)函数中:符号“|”既可以用于集合求并集也可以是按位求或。

2、python中创建指定长度的列表

returnVec = [0]*len(vocabList)

或者 list(range(a,b,step))

3、numpy库: * / 等都是指元素间的操作

4、体会append和extend的区别:

List 的两个方法 extend 和 append 看起来类似,但实际上完全不同。extend 接受一个参数,这个参数总是一个 list,并且把这个 list 中的每个元素添加到原 list 中。append 接受一个参数,这个参数可以是任何数据类型,并且简单地追加到 list 的尾部。

5、正则表达式

正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。

基本的用字符描述字符:

\d可以匹配一个数字;\w可以匹配一个字母或数字;“.”可以匹配任意字符;

*表示任意个字符(包括0个);+表示至少一个字符;?表示0个或1个字符;

{n}表示n个字符;{n,m}表示n-m个字符;

\s可以匹配一个空格(也包括Tab等空白符);

特殊字符比如“-”需要用“\”转义;

6、re模块

Python提供re模块,包含所有正则表达式的功能。由于Python的字符串本身也用\转义,所以要特别注意。因此我们强烈建议使用Python的r前缀,就不用考虑转义的问题。

即r'abc//t123'就代表字符 abc//t123

split方法:

切分字符串:用正则表达式切分字符串比用固定的字符更灵活,还可以把不规范的输入转化成正确的数组。

注意切分后,最后一个字符可能出现一个空字符。

[word.lower() for word in listOfTokens if len(word)>2]
此语句就将分离后的单词中的空字符串剔除,同时,考虑到URL地址中有py,en等出现,为了剔除这些字符,

使用iflen(word)>2,而没有使用if len(word)>0.

7、拉普拉斯平滑

朴素贝叶斯方法有个缺点就是对数据系数问题过于敏感。

比如测试集中出现训练集中未出现的单词,则在参数估计(求p0V和p1V)时,求得的概率是零(朴素贝叶斯假设,连乘求结果),显然是不合理的。

因此,假设每个单词均出现过一次。具体操作:

初始化时,分子部分改为

p0Num = ones(numWords)  
p1Num = ones(numWords)

分母部分加k(即类别数目,本文只有两类:垃圾邮件和非垃圾邮件)

p0Denom = 2.0
p1Denom = 2.0
8、防止下溢出

根据朴素贝叶斯假设,采用连乘操作求概率,而其中有很小的值,因此最终结果可能过小,四舍五入后结果为零。

解决方法:采用取对数的操作。因为取对数操作后,f(x)与logf(x)在相同区域内同时增减且在相同点上取得极值。

9、运行问题

(1)'gbk' codec can't decode byte 0xae in position 199: illegal multibyte sequence

解决方法:ham文件夹中23.txt文件中乱码,将第二段的SciFinance?is a derivatives pricing...问好换成空格。

(2)'range' object doesn't support item deletion

解决方法:python3中range返回一个range对象而不是列表。

将trainingSet = range(50)改为trainingSet = list(range(50))

10、程序中从50个邮件中随机选取10个作为测试集,其余40为训练集。

因此,每次运行结果可能不同。可以看做是留存交叉验证,通过多次运行取平均错误率。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章