机器学习实战-基于概率论的分类方法:


条件概率:

如果各个特征属性是条件独立的,则根据贝叶斯定理有如下推导:

计算条件概率的方法:贝叶斯准则
可以交换条件概率中的条件与结果


使用条件概率来分类:

规则:

  • 如果p1(x,y) > p2(x,y),那么属于类别1
  • 如果p1(x,y) < p2(x,y),那么属于类别2

使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。


使用朴素贝叶斯进行文档分类:

朴素贝叶斯是贝叶斯分类器的一个拓展,是用于文档分类的常用算法。

我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。

朴素贝叶斯的一般过程:

  1. 收集数据:可以使用任何方法
  2. 准备数据:数字型或布尔型
  3. 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好
  4. 训练算法:计算不同的独立特征的条件概率
  5. 测试算法:计算错误率
  6. 使用算法:可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本

样本数:

由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要N^10 个样本,对于包含1000个特征的词汇表将需要N^1000 个样本。

所需要的样本数会随着特征数目增大而迅速增长。

如果特征之间相互独立,那么样本数就可以从N^1000 个样本减少到1000N。

所谓独立(independence)指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。

两个假设:

  • 朴素(naive)的含义:特征之间独立
  • 每个特征同等重要

使用python进行文本分类:

要从文本中获取特征,需要先拆分文本。
每一个文本片段表示为一个词条向量,1表示词条出现在文档中,0表示词条未出现

  • 特征:来自文本的词条(token)
  • 一个词条是字符的任意组合
  • 词条可以为单词,也可以为非单词(如URL、ip地址等)

准备数据:从文本中构建词向量

'''
词表到向量的转换函数
'''
# 创建了一些实验样本
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 1为侮辱性文字
    return postingList,classVec

def createVocabList(dataSet):
    vocabSet = set([])  #create empty set
    for document in dataSet:
        vocabSet = vocabSet | set(document) #union of the two sets 创建并集,即获得一个去重词汇列表
    return list(vocabSet)

def setOfWords2Vec(vocabList, inputSet): # vocabList词汇表,inputSet需要检查的所有单词
    returnVec = [0]*len(vocabList) # 与词汇表相同的0值列表
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1 # 如果需要检查的单词列表出现在vocabList词汇表中,0值列表对应的位置设为1
        else: print "the word: %s is not in my Vocabulary!" % word
    return returnVec

if __name__ == '__main__':
    postingList, classVec = loadDataSet()
    print postingList
    print '---------'
    print classVec
    print '---------'
    myVocabList = createVocabList(postingList)
    print myVocabList
    print '--------'
    print setOfWords2Vec(myVocabList, postingList[0])
    # [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1] 

训练算法:从词向量到计算概率

重写贝叶斯准则,将之前的 x, y 替换为 w. 粗体的 w 表示这是一个向量,即它由多个值组成。
在这个例子中,数值个数与词汇表中的词个数相同。

我们使用上述公式,对每个类计算该值,然后比较这两个概率值的大小。

计算p(ci):

  • 首先可以通过类别 i (侮辱性留言或者非侮辱性留言)中的文档数除以总的文档数来计算概率 p(ci) 。

计算 p(w | ci):

  • 朴素贝叶斯假设

  • 如果将 w 展开为一个个独立特征,那么就可以将上述概率写作 p(w0, w1, w2…wn | ci) 。

  • 这里假设所有词都互相独立,该假设也称作条件独立性假设(例如 A 和 B 两个人抛骰子,概率是互不影响的,也就是相互独立的,A 抛 2点的同时 B 抛 3 点的概率就是 1/6 * 1/6),

  • 它意味着可以使用 p(w0 | ci)p(w1 | ci)p(w2 | ci)…p(wn | ci) 来计算上述概率,这样就极大地简化了计算的过程。

from numpy import *
from machinelearninginaction.Ch04.bayes import loadDataSet,createVocabList,setOfWords2Vec

def trainNB0(trainMatrix,trainCategory): #trainMatrix文档矩阵,trainCategory每篇文档类别标签所构成的向量
    numTrainDocs = len(trainMatrix) # 训练样本集长度
    numWords = len(trainMatrix[0]) # 每个样本的词数量
    pAbusive = sum(trainCategory)/float(numTrainDocs) # 计算文档属于侮辱性文档的概率
    p0Num = zeros(numWords); p1Num = zeros(numWords)      # 初始化分子
    p0Denom = 0.0; p1Denom = 0.0                        # 初始化分母
    for i in range(numTrainDocs): # 对每篇训练文档
        if trainCategory[i] == 1: # 词向量值为1 -> 侮辱性
            p1Num += trainMatrix[i] # 矩阵,对应位置,出现侮辱性词条 累加矩阵
            p1Denom += sum(trainMatrix[i]) # 计算所有侮辱性词条的总数
        else: # 词向量值为0 -> 非侮辱性
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num/p1Denom          #change to log() 词汇表对应位置上有侮辱性词条的出现的总数 除以 总词条数目得到条件概率 = 词汇表每个位置上出现侮辱性词条的概率
    p0Vect = p0Num/p0Denom          #change to log()
    return p0Vect,p1Vect,pAbusive
    # p0Vect 非侮辱性词语在词汇表每个位置出现的条件概率


if __name__ == '__main__':
    postingList, classVec = loadDataSet()
    myVocabList = createVocabList(postingList) # 获得一个去重的词汇表
    trainMat = []
    for postinDodc in postingList:
        trainMat.append(setOfWords2Vec(myVocabList,postinDodc))
    print  'trainMat: '
    print trainMat
    print '----------'
    p0V,p1V,pAb = trainNB0(trainMat,classVec)
    print p0V
    print '---------'
    print p1V
    print '---------'
    print pAb

p0Num :

  • 矩阵,对应位置,出现非侮辱性词条的话,则累加起来。
  • 最后每个位置的数字为该位置(某个单词)出现过多少次非侮辱性词条

p0Denom:

  • 侮辱性词条的总数

p0Vect = p0Num/p0Denom

  • 计算:词汇表每个位置上出现侮辱性词条的概率

测试算法:根据现实情况修改分类器

在利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算 p(w0|1) * p(w1|1) * p(w2|1)。如果其中一个概率值为 0,那么最后的乘积也为 0。

为降低这种影响,可以将所有词的出现数初始化为 1,并将分母初始化为 2 (取1 或 2 的目的主要是为了保证分子和分母不为0,大家可以根据业务需求进行更改)。

另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积 p(w0|ci) * p(w1|ci) * p(w2|ci)… p(wn|ci) 时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(用 Python 尝试相乘许多很小的数,最后四舍五入后会得到 0)。一种解决办法是对乘积取自然对数。在代数中有 ln(a * b) = ln(a) + ln(b), 于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。

下图给出了函数 f(x) 与 ln(f(x)) 的曲线。可以看出,它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。

from numpy import *
from machinelearninginaction.Ch04.bayes import loadDataSet,createVocabList,setOfWords2Vec

def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    p0Num = ones(numWords)
    p1Num = ones(numWords)  # change to ones()
    p0Denom = 2.0
    p1Denom = 2.0  # change to 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])
    p1Vect = log(p1Num / p1Denom)  # change to log()
    p0Vect = log(p0Num / p0Denom)  # change to log()
    return p0Vect, p1Vect, pAbusive


def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): # p0Vec非侮辱性的条件概率、p1Vec侮辱性的条件概率, pClass1侮辱性文档的概率
    temp = vec2Classify * p1Vec
    temp1 = sum(vec2Classify * p1Vec)
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  # element-wise mult 
    # 测试向量矩阵 * p1条件概率矩阵 * pClass1侮辱性文档的概率
    print '\n'
    print 'p1: ' + str(p1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    print 'p0: ' + str(p0)
    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(array(trainMat), array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print thisDoc
    print '--------'
    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)


if __name__ == '__main__':
    testingNB()


准备数据:文档词袋模型

我们将每个词的出现与否作为一个特征,这可以被描述为 词集模型(set-of-words model)
如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为 词袋模型(bag-of-words model)

  • 词袋中,每个单词可以出现多次,
  • 而在词集中,每个词只能出现一次。

为适应词袋模型,需要对函数 setOfWords2Vec() 稍加修改,修改后的函数为 bagOfWords2Vec() 。

如下给出了基于词袋模型的朴素贝叶斯代码。它与函数 setOfWords2Vec() 几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为 1 。


示例1:使用朴素贝叶斯过滤垃圾邮件

步骤:

  • (1)收集数据:提供文本文件。
  • (2)准备数据:将文本文件解析成词条向量。
  • (3)分析数据:检查词条确保解析的正确性。
  • (4)训练算法:使用我们之前建立的trainNBO()函数。
  • (5)测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率。
  • (6)使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。

准备数据:切分文本

测试算法:使用朴素贝叶斯进行交叉验证

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


示例2:使用朴素贝叶斯分类器从个人广告中获取区域倾向

使用朴素贝叶斯来发现地域相关的用词

  • (1)收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口。
  • (2)准备数据:将文本文件解析成词条向量。
  • (3)分析数据:检查词条确保解析的正确性。
  • (4)训练算法:使用我们之前建立的trainNBO()函数。
  • (5)测试算法:观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高
    分类结果。
  • (6)使用算法:构建一个完整的程序,封装所有内容。给定两个RSS源,该程序会显示最
    常用的公共词。

参考:

猜你喜欢

转载自blog.csdn.net/qq_28921653/article/details/80560462