机器学习入门(三)

学习《机器学习实战》(朴素贝叶斯)

第四章原理挺好理解的,但是很多代码让我很不解,感觉和给出的公式并不一样,希望有大佬能给我指明一下

先上概率论学过的贝叶斯公式:
在这里插入图片描述
大致理解,设Ai代表一系列事物特征,Bi代表某一结果,若已知B发生的情况下,Ai的概率,现在想求某种A发生时Bi的概率,就用这个公式。

朴素贝叶斯的适用范围,像垃圾邮件分类、分本分类这样二分类或多分类的情况效果较好, 核心思想是选择高概率对应的类别 ,所以不用特别准确的求出各自的概率,能比较出来结果即可

开始确定已知的B和A, 也就是训练集数据,书上用分类言论为辱骂类还是正常类的数据举的例子。

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

postingList中每一行相当于一个向量A, classVec中是其对应的结果B, 有了这些数据,根据上面的公式,我们可以求出结果B下,A中每个特征发生的概率,再乘于结果B发生的概率,就可以把贝叶斯公式中的分子求出来了。

然后考虑怎么求,把分子的公式拿下来在这里插入图片描述

P(Bi)发生的概率,就让它发生的数量除以classVec的长度即可
P(A|Bi)就需要,让结果为Bi的那些行,按列累加,得到每个特征发生的数量,再除以Bi的总行数, 这样可以得到每个特征在Bi下发生的概率,但是这和P(A|Bi)还有点不同,因为它们可能是相关的,所以无法直接概率相乘,为了把这个步骤简化,这个算法就假设每个单词的出现没有半毛钱的关系即独立,那就可以写成P(A|Bi) = P(A1|Bi)*P(A2|Bi)…这样, 这也是朴素贝叶斯中”朴素“的含义。当然这是我理解的,书上讲的我感觉就是这样,但是代码里的计算就看不太懂了。

这时候突然意识到一个问题,刚才按列累加,默认每列是一个特征了, 并且1表示存在0表示不存在,事实上数据是没有规律的字符串数组,所以需要写一个函数转化一下,将字符串数组变成标识特征的向量。

# 从数据集中抽取词汇表 输入:[][]   输出:[]
def createVocabList(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

这样就满足了
可以看一下用最开始的数据,得到的向量
在这里插入图片描述因为创建词汇表时,每个单词的位置是随机的,所以只能通过数1的方式大致判断一下
接下来就可以训练分类器了

import numpy as np

# 朴素贝叶斯分类器训练函数 输入:[][] [] 输出:[] [] float
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) # 数据的个数
    numWords = len(trainMatrix[0]) # 特征个数
    pAbusive = sum(trainCategory)/float(numTrainDocs) # 好巧妙 求辱骂言论的概率
    p0Num = np.ones(numWords) # 初始为1 
    p1Num = np.ones(numWords) # 初始为1
    p0Denom = 2.0 # p(0)发生的总词条数 # 初始为2
    p1Denom = 2.0 # p(1)发生的总词条数 # 初始为2
    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 
    return p0Vect, p1Vect, pAbusive

上面代码中的初始为1,初始为2,在公式中相当于分子加1,分母加2是为了防止某一概率为0,导致相乘后结果都为0了。这里面分母的求法让我很费解,难道不应该是直接除以行数吗, 也就是每次循环+1,这里为什么要求和?暂时还想不明白…
这个函数可以求得各个特征在结果是辱骂类或正常类的时候的概率,以及邮件中辱骂类的比例,正常类就等于1减去辱骂类的比例
有了这个结果就可以带入贝叶斯公式,完成分类了

书上又做了一步优化,考虑到了下溢出,因为向量只有0或者1,而词汇表中的总数却很多,所以每个特征的概率都很小,再相乘,结果小的不得了,很可能就下溢出了,所以通过取对数的方式,把小数变成大数,而且这个函数本身递增,不影响最后的概率比较。

接下来就是分类函数了

import math
# 朴素贝叶斯分类函数 vec2Classify:要分类的向量[] p0Vec:0时各个词条的概率[] p1Vec:1时各个词条的概率[] pClass1:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + math.log(pClass1) # ???   
    p0 = sum(vec2Classify * p0Vec) + math.log(1.0 - pClass1) # ???
    if p1 > p0:
        return 1
    else: 
        return 0
    
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']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))

输出
在这里插入图片描述整段代码最难理解的就算p0、p1的计算,这个我也不明白,就代码而言,公式中的乘变成了加,是因为求对数,为什么前面不先求个log再求和,而是直接求和,之后再与结果概率的对数相加。我暂且理解为,求不求log对结果的比较无影响,这样可以简化计算。

词袋模型

上面的特征向量,是通过0和1来表示有还是没有,这样的描述被称为词集模型, 事实上单词出现的频率也很能说明文本的主题,所以想把0和1改成对应单词出现的次数,这样的描述称为词袋模型

词袋模型只需要改一下转换向量的函数即可,每次加1而不是取1

# 词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1 # 每次加1
    return returnVec

下面是案例

使用朴素贝叶斯过滤垃圾邮件
这是朴素贝叶斯最著名的应用

准备数据阶段,将文本切分成字符串数组
交叉验证,也是一段神奇的代码

# 文件解析及完整的垃圾邮件测试函数
def textParse(bigString):    #input is big string, #output is word list
    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):
        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 = list(range(50)); testSet=[]
    for i in range(10): # 10个测试数据
        randIndex = int(np.random.uniform(0,len(trainingSet))) # 0-len 的随机数字
        testSet.append(trainingSet[randIndex]) # 保存对应下标
        del(trainingSet[randIndex])  # 从训练集中移除
    trainMat=[]; trainClasses = []
    for docIndex in trainingSet: # train the classifier (get probs) trainNB0
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex])) # 转化成向量
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNBO(np.array(trainMat),np.array(trainClasses))
    errorCount = 0
    for docIndex in testSet:        # classify the remaining items
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex]) # 测试向量
        if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]: # 分类不正确
            errorCount += 1
            print("classification error",docList[docIndex])
    print('the error rate is: ',float(errorCount)/len(testSet))
    return vocabList,fullText

在这里插入图片描述
书上说平均为6%
接着后面讲了一些用RSS源的数据,我访问得到的数据都为空就不记录了。

第四章完结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值