【机器学习】朴素贝叶斯分类器

基于贝叶斯决策理论的分类方法。核心思想:选择具有最高概率的决策

优缺点:

  • 优点:在数据量少的情况下依然有效,可处理多类别问题
  • 缺点:对于输入数据的准备方式比较敏感
  • 适用数据类型:标称型

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

一般步骤:

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

朴素的理念

  1. 假设词汇表有1000个单词,如果每个特征需要N个样本,包含1000个特征的词汇表需要N的1000次方个样本,指数型增长

  2. 假设特征之间相互独立(一个特征或者单词出现的可能性与它与其他单词相邻没有关系,概率相同),那么样本数就可以从N^1000减少到1000*N(朴素Naive的含义)

  3. 另外一个假设是,每个特征同等重要

二、使用Python进行文本分类

  • 拆分文本获取特征。将每一个文本片段表示为一个词条向量,值为1表示该词条在文档中出现,0表示未出现

  • 构建一个快速过滤器,屏蔽掉侮辱性的言论。

  • 建立两个类别,侮辱类和非侮辱类,使用0和1表示

  • 逻辑:将文本转换为数字向量,基于这些向量计算条件概率,并在这基础上构建分类器。

2.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] #1:侮辱性;0:正常言论
    return postingList, classVec

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

运行:

>>> import bayes
>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> myVocabList
['stupid', 'maybe', 'so', 'ate', 'not', 'dog', 'dalmation', 'worthless', 'how', 'food', 'help', 'steak', 'quit', 'mr', 'please', 'love', 'buying', 'my', 'him', 'take', 'I', 'park', 'posting', 'problems', 'garbage', 'flea', 'has', 'stop', 'is', 'to', 'licks', 'cute']
>>> bayes.setOfWords2Vec(myVocabList, listOPosts[0])
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0]
>>> bayes.setOfWords2Vec(myVocabList, listOPosts[3])
[1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0]
>>> listOPosts[0]
['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']

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



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

计算过程:

  • 通过类别i中的文档数除以总的文档数,计算概率p(Ci)
  • 使用朴素贝叶斯假设计算p(w|Ci)
    • w展开为一个个独立特征,上述的概率可写作:p(W0, W1, W2..WN|Ci)
    • 假设所有词都相互独立(条件独立性假设),所以上述概率等于p(W0|Ci)p(W1|Ci)p(W2|Ci)..p(WN|Ci)

伪代码:

计算每个类别中的文档数目
对于每篇训练文档:
    如果词条出现在该文档中:
        增加该词条的计数值
   增加所有词条的计数值

对于每个类别:
    对于每个词条:
        将该词条的数目 除以 总词条数目 得到条件概率

返回每个类别的条件概率

在开头加上:

from numpy import *

计算概率向量

def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) #计算文档数
    numWords = len(trainMatrix[0])      #计算特征向量的长度
    pAbusive = sum(trainCategory) / float(numTrainDocs)   #侮辱性文档的概率P(1), P0=1-P(1)
    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   #change to log()
    p0Vect = p0Num / p0Denom   #change to log()
    return p0Vect, p1Vect, pAbusive

运行:

>>> from importlib import reload
>>> reload(bayes)

>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> trainMat = []
>>> from importlib import reload
>>> for postinDoc in listOPosts:
...     trainMat.append(bayes.setOfWords2Vec(myVocabList, postinDoc))
...
>>> trainMat
[[0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1], [0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 1, 0, 0, 1, 0], [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]]
>>> p0V, p1V, pAb = bayes.trainNB0(trainMat, listClasses)
>>> pAb
0.5
>>> p0V
array([0.        , 0.00775194, 0.00775194, 0.        , 0.00775194,
       0.        , 0.        , 0.00775194, 0.00775194, 0.00775194,
       0.        , 0.01550388, 0.00775194, 0.00775194, 0.00775194,
       0.        , 0.00775194, 0.00775194, 0.00775194, 0.        ,
       0.        , 0.        , 0.02325581, 0.00775194, 0.        ,
       0.00775194, 0.00775194, 0.00775194, 0.00775194, 0.        ,
       0.00775194, 0.00775194])
>>> p1V
array([0.15789474, 0.        , 0.        , 0.05263158, 0.        ,
       0.05263158, 0.05263158, 0.        , 0.        , 0.        ,
       0.10526316, 0.05263158, 0.        , 0.        , 0.05263158,
       0.05263158, 0.10526316, 0.        , 0.        , 0.05263158,
       0.05263158, 0.05263158, 0.        , 0.        , 0.05263158,
       0.        , 0.        , 0.        , 0.        , 0.05263158,
       0.        , 0.05263158])

出现概率最大的单词,最能表征该类别

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

要计算多个概率的乘积,为了避免其中一个概率为0,导致最后的乘积也是0

可以将所有词的出现数初始化为1,并将分母初始化为2,刚才的函数对应的修改如下:

   p0Num = ones(numWords); p1Num = ones(numWords) 
   p0Denom = 2.0; p1Denom = 2.0

另一个要解决的问题是下溢出或者得不到正确答案,因为大部分因子都很小。可以对乘积取自然对数,避免下溢出或者取浮点数舍入导致的错误。

自然对数处理不会有任何损失

ln(a*b) = ln(a) + ln(b)

对应修改return:

 p1Vect = log(p1Num / p1Denom)   #change to log()
 p0Vect = log(p0Num / p0Denom)   #change to log()
#朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  #对应元素相乘
    p0 = sum(vec2Classify * p0Vec) + 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(array(trainMat), array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print('{} classified as: {}'.format(testEntry, classifyNB(thisDoc, p0V, p1V, pAb)))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print('{} classified as {}'.format(testEntry, classifyNB(thisDoc, p0V ,p1V, pAb)))
>>> reload(bayes)

>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as 1

2.4 准备数据:文档词袋模型

  • 词集模型(set-of-words model):每个词是否出现作为一个特征
  • 词袋模型(bag-of-words model):记录每个单词出现的次数

三、示例:使用朴素贝叶斯分类器过滤垃圾邮件

从文本内容得到字符串列表,再生成词向量

步骤:

  • 收集数据:提供文本文件
  • 准备数据:将文本文件解析成词条向量
  • 分析数据:检查词条确保解析的正确性
  • 训练算法:trainNB0()
  • 测试数据:构建新的测试函数来计算文档集的错误率

3.1 准备数据:切分文本

  • 正则
  • 文本解析规则:可以使用更高级的过滤器,如HTML和URL对象进行处理

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

  • 随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为留存交叉验证(hold-out cross validation)
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

def spamTest():
    import random
    docList = []; classList = []; fullText = []
    #读取邮件,解析为词列表
    for i in range(1, 26):
        #垃圾邮件
        emailText = open('email/spam/%d.txt'%i, 'rt').readlines()
        emailText = ''.join(emailText)
        wordList = textParse(emailText)
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        #正常邮件
        emailText = open('email/ham/%d.txt'%i, 'rt').readlines()
        emailText = ''.join(emailText)
        wordList = textParse(emailText)
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    #构建词汇表    
    vocabList = createVocabList(docList)
    trainingSet = list(range(50)); testSet = []
    #构建随机训练集和测试集
    for i in range(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(array(trainMat), array(trainClasses))
    #测试错误率
    errorCount = 0
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            print('classified error:{}'.format(docList[docIndex]))
            errorCount += 1
    print('the error rate is: {}'.format(float(errorCount)/len(testSet)))

运行:

>>> reload(bayes)

>>> bayes.spamTest()
the error rate is: 0.0
>>> bayes.spamTest()
classified error:['scifinance', 'now', 'automatically', 'generates', 'gpu', 'enabled', 'pricing', 'risk', 'model', 'source', 'code', 'that', 'runs', '300x', 'faster', 'than', 'serial', 'code', 'using', 'new', 'nvidia', 'fermi', 'class', 'tesla', 'series', 'gpu', 'scifinance', 'derivatives', 'pricing', 'and', 'risk', 'model', 'development', 'tool', 'that', 'automatically', 'generates', 'and', 'gpu', 'enabled', 'source', 'code', 'from', 'concise', 'high', 'level', 'model', 'specifications', 'parallel', 'computing', 'cuda', 'programming', 'expertise', 'required', 'scifinance', 'automatic', 'gpu', 'enabled', 'monte', 'carlo', 'pricing', 'model', 'source', 'code', 'generation', 'capabilities', 'have', 'been', 'significantly', 'extended', 'the', 'latest', 'release', 'this', 'includes']
the error rate is: 0.1

为避免错误,可以用多种方式修改分类器

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

目的:

  • 分别从美国的两个城市中选取一些人,通过分析这些人发布的征婚广告信息,来比较这两个城市的人们在广告用词上是否不同
  • 如果不同,他们各自常用哪些词
  • 能否对不同城市的人所关系的内容有所了解
  • 通过单词和条件概率值来发现特定城市相关的内容

步骤:

  • 收集数据:RSS源
  • 准备数据:解析成词向量
  • 分析数据:检查词条确保解析的正确性
  • 训练算法:trainNB0()
  • 测试算法: 修改切分程序,降低错误率
  • 使用算法:封装程序,给定2个RSS源,程序显示最常用的公共词

4.1 收集数据:导入RSS源

pip install feedparser
# RSS源分类器以及高频词过滤器
def calcMostFreq(vocabList, fullText):
    #计算出现频率
    import operator
    freqDict = {}
    for token in vocabList:
        freqDict[token] = fullText.count(token)
    sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
    return sortedFreq[:30]

def localWords(feed1, feed0):
    import feedparser
    import random
    docList = []; classList = []; fullText = []
    minLen = min(len(feed1['entries']), len(feed0['entries']))
    for i in range(minLen):
        wordList = textParse(feed1['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)       
        wordList = textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0) 
    vocabList = createVocabList(docList)
    top30Words = calcMostFreq(vocabList, fullText)
    for pairW in top30Words: #去除出现次数最高的前30个词
        if pairW[0] in vocabList:
            vocabList.remove(pairW[0])
    # # 移除停用词
    # stopWordList = stopWords()
    # for stopWord in stopWordList:
    #     if stopWord in vocabList:
    #         vocabList.remove(stopWord)
    trainingSet = list(range(2*minLen)); testSet=[]
    for i in range(20):
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex]) 
        del(trainingSet[randIndex])
    trainMat = []; trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
    print('the value rate is: {}'.format(float(errorCount)/len(testSet)))
    return vocabList, p0V, p1V
def localWords(feed1, feed0):
    import feedparser
    docList = []; classList = []; fullText = []
    minLen = min(len(feed1['entries']), len(feed0['entries']))
    for i in range(minLen):
        # 每次访问一条RSS源
        wordList = textParse(feed1['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)
    # 去掉出现次数最高的那些词
    top10Words = calcMostFreq(vocabList, fullText)
    for pairW in top10Words:
        if pairW[0] in vocabList: 
            vocabList.remove(pairW[0])
    # # 移除停用词
    # stopWordList = stopWords()
    # for stopWord in stopWordList:
    #     if stopWord in vocabList:
    #         vocabList.remove(stopWord)
    trainingSet = list(range(2*minLen)); testSet = []
    for i in range(10):
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat = []; trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
    print('the error rate is: ', float(errorCount)/len(testSet))
    return vocabList, p1V, p0V

运行:

>>> import bayes, feedparser
>>> ny=feedparser.parse('https://newyork.craigslist.org/search/res?format=rss')
>>> sf=feedparser.parse('https://sfbay.craigslist.org/search/apa?format=rss')
>>> print(len(ny['entries']))
25
>>> print(len(sf['entries']))
25
>>> vocabList, pNY, pSF = bayes.localWords(ny, sf)
D:\App\Python3\Anaconda\Anaconda\lib\re.py:212: FutureWarning: split() requires a non-empty pattern match.
  return _compile(pattern, flags).split(string, maxsplit)
the value rate is: 0.55
>>> vocabList, pNY, pSF = bayes.localWords(ny, sf)
the value rate is: 0.5
>>> vocabList, pNY, pSF = bayes.localWords(ny, sf)
the value rate is: 0.4

移除高频词,是因为语言中大部分都是冗余和结构辅助性内容。

另一个常用的方法是不仅移除高频词,同时从某个预定词表中移除结构上的辅助词。该词表称为停用词表。https://www.ranks.nl/stopwords

def stopWords():
    import re
    wordList =  open('stopwords.txt').readlines() 
    listOfTokens = re.split(r'\W+', wordList)
    listOfTokens = [tok.lower() for tok in listOfTokens] 
    return listOfTokens

对应的localWords函数内加上代码:

    # 移除停用词
    stopWordList = stopWords()
    for stopWord in stopWordList:
        if stopWord in vocabList:
            vocabList.remove(stopWord)

参考自:https://my.oschina.net/u/4004713/blog/3031845

多次运行上述实验,取平均值获得错误率的精确估计

4.2 分析数据:显示地域相关的用词

先向向量pSF,pNY进行排序,然后按照顺序将词打印出来

#具有表征性的词汇显示函数
def getTopWords(ny, sf):
    import operator
    vocabList, p0V, p1V = localWords(ny, sf)
    topNY=[]; topSF=[]
    for i in range(len(p0V)):
        if p0V[i] > -6.0: 
            topSF.append((vocabList[i], p0V[i]))
        if p1V[i] > -6.0: 
            topNY.append((vocabList[i], p1V[i]))
        sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True)
        print("SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**")
        for item in sortedSF:
            print(item[0])
        sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True)
        print('NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**')
        for item in sortedNY:
            print(item[0])

运行:

>>> bayes.getTopWords(ny, sf)

总结

  1. 就分类而言,使用概率有时候比硬规则更有效
  2. 贝叶斯概率机贝叶斯准则提供了一种利用已知值来估计位置概率的有效方法。
  3. 通过独立性假设,降低数据量的需求
  4. 独立性假设虽然不正确,但是分类器有效
  5. 概率对数化解决下溢问题
  6. 可以使用词袋模型、溢出停用词、优化分类器进行优化

学习自

  • 《机器学习实战》
  • https://my.oschina.net/u/4004713/blog/3031845

你可能感兴趣的:(【机器学习】朴素贝叶斯分类器)