机器学习实战读书笔记(3)--朴素贝叶斯

基于贝叶斯决策理论的分类方法

优点:在数据较少的情况下仍然有效,可以处理多类别问题

缺点:对输入数据的准备方式比较敏感,需要标称数据.确定贝叶斯最优假设的计算代价较大

朴素贝叶斯是贝叶斯决策理论的一部分.贝叶斯决策理论的核心思想:一个数据集包括2类(或两类以上)数据,这些数据有一些维度,如果已知一个数据的特征,由该特征得到其属于第一类的可能性 p1(x,y) p 1 ( x , y ) 和其属于第二类的可能性 p2(x,y) p 2 ( x , y ) ,当 p1(x,y)>p2(x,y) p 1 ( x , y ) > p 2 ( x , y ) 时,认为该数据属于第一类而不属于第二类.

为了计算上文中提到的概率,需要引入条件概率和贝叶斯变换.

条件概率:

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

贝叶斯变换:

P(A|B)=P(B|A)P(A)P(B) P ( A | B ) = P ( B | A ) P ( A ) P ( B )

通过贝叶斯变换,可以将求P(c1|x,y)转化为求P(x,y|c1)P(c1),进而通过训练数据集的带有标签的特征训练,在c先验概率下求x,y的概率,求得P(c1|x,y),对未知标签的(x,y)进行判断.

朴素贝叶斯的假设:
- 一个特征或单词的出现的可能性与其他邻近单词没有关系.(不准确但是有效,朴素的含义)
- 每个特征同等重要

使用贝叶斯进行文本分类

从文本获取特征,即需要拆分文本,特征来自词条(token),一个词条是字符的任意组合,一个文本可以表示为一个词向量,其中为1表示这个词条存在,为0表示未出现.

from numpy import *
import feedparser

# 准备数据
def loadDataSet():
    postingList = [['my', 'dog', 'has', 'flea', \
                    'problem', '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):
    '''
    从数据集提取所有出现过的词作为词表
    dataSet可以是一个二维数组
    '''
    vocabSet = set([])  # 创建一个空集
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 创建两个集合的并集
    return list(vocabSet)


def setOfWords2Vec(vocabList, inputSet):
    '''词集模型, 将数据集中的词汇列表转换为词向量'''
    returnVec = [0]*len(vocabList) # 创建一个和vocabList等长的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1 # 如果word在vocabList中,其索引对应位置的特征维度修改为1
        else:
            print("The word:%s is not in my Vocabulary" %word)
    return returnVec


def bagOfWords2Vec(vocabList, inputSet):
    '''词袋模型'''
    returnVec = [0]*len(vocabList) # 创建一个和vocabList等长的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1 # 如果word在vocabList中,其索引对应位置的特征维度加1
        else:
            print("The word:%s is not in my Vocabulary" %word)
    return returnVec

接下来是真正的训练部分.我们知道要计算 P(c|ω) P ( c | ω ) ,就需要计算 P(ω|c)P(c)P(ω) P ( ω | c ) P ( c ) P ( ω ) ,其中w为一个文本,文本独一无二,用来计算概率没有意义,所以其实是计算构建文本的词条的概率,那么词条和文本之间的关系是什么呢,根据朴素贝叶斯的第一个假设,各词条之间独立,则有

P(ω|c)=P(ω1|c)P(ω2|c)...P(ωm|c) P ( ω | c ) = P ( ω 1 | c ) P ( ω 2 | c ) . . . P ( ω m | c )

训练部分做的就是计算每个词条的 P(ωi|c) P ( ω i | c ) ,累计某个分类中的文本内各词条出现的次数,去除以该类文本的词条总量即是所求的概率.这个概率去乘以P(c),即该类中文本数量在文本总数中的出现的频数,就是我们需要的 P(c|ωi) P ( c | ω i ) .对于一个未知的 ω ω ,求其 P(c|ω) P ( c | ω ) 即是求上式.

def trainNB0(trainMatrix, trainCategory):
    '''
    计算词表/词袋中的词在有辱骂性句子/没有辱骂性句子的前提下出现的条件概率
    trainMatrix--训练矩阵,注意!!!是ndarray格式
    trainCategory--训练向量的标签列表 注意!!!也是ndarray格式
    '''
    numofDoc = len(trainMatrix) # 用于训练的句子数
    numofWord = len(trainMatrix[0]) # 用于分类的词汇数量
    pAbusive = sum(trainCategory)/numofDoc # p(c1)为有辱骂性的句子的总数除以句子总数
    # p1Num = zeros(numofWord); p0Num = zeros(numofWord) # 初始化各单词的计数
    # p1Cal = 0.0; p0Cal = 0.0
    p1Num = ones(numofWord); p0Num = ones(numofWord) # 初始化各单词的计数
    p1Cal = 2.0; p0Cal = 2.0
    # 这里的修改解决了一个问题,在第一版情况下,有大量的词的条件概率为0,
    # 在利用贝叶斯变换相乘时会导致结果的错误.所以把每个词的初始频率为
    # 0.5,即在初始情况下,一个词出现在两边的概率相等.
    for i in range(numofDoc):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i] # p1Num是一个向量,对有辱骂性句子中的各词出现次数累加
            p1Cal += sum(trainMatrix[i]) # p0Num同样是向量~
        else:
            p0Num += trainMatrix[i] # 计算有辱骂性句子中词汇总数
            p0Cal += sum(trainMatrix[i]) #计算无辱骂性~
    # p1 = p1Num/p1Cal # 计算各词的p(w_i | c1)
    # p0 = p0Num/p0Cal # 计算各词的p(w_i | c0)
    p1 = log(p1Num / p1Cal)
    p0 = log(p0Num/p0Cal)
    # 第二版解决了另一个问题,即使每个词的初始概率为0.5,大量词的概率相乘后,得到的数非常小,可能
    # 导致下溢出,即变成0,将小数相乘转化为对数相加,能够有效避免下溢出并且结果的数值虽然发生变化,
    # 但趋势不变
    return p0, p1, pAbusive
def classifyNB(vec2Classify,p0Vec, p1Vec, pClass1):
    '''根据词表中提供的条件概率,及侮辱性句子的在数据集中出现的概率,计算一个测试句子是/不是侮辱性句子的概率
    由于vec2classify和p两个向量需要做元素相乘,
    故vec2classify也需要是ndarray
    但是list和一个array相乘时,numpy会把list转为array然后做相乘,这是一个trick
    但是最好还是在使用函数的时候加上array()
    '''
    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 line in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, line))
    p0V, p1V, pAb = trainNB0(trainMat, listClasses)
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) # 注意转化为ndarray数组
    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))

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

利用朴素贝叶斯过滤垃圾邮件

过滤垃圾邮件同样是对垃圾邮件和非垃圾邮件的文本进行词表制作,然后计算词表中的各词的概率.
谈一谈这里面测试集和训练集的问题,从数据集中随机选取一部分作为训练集,而剩余部分作为测试集的过程称为留存交叉验证,分类器所需要的概率计算只能从训练集中的文档来完成.

def splitParse(bigString):
    '''split the phrase to words longer than 2 characters'''
    import re
    regEx = re.compile('\\W*')
    listOfToken = regEx.split(bigString)
    tokenList = [tok.lower() for tok in listOfToken if len(tok) > 2]
    return tokenList


def spamTest():
    docList = [] # 文档列表,每个元素是该文档的划分词后的列表
    fullText = [] # 包含所有文档的词汇,用于制作词表 这个是干什么的呢?
    classList = [] # 即上文的label,用于标记数据集的分类标签
    for i in range(1,26):
        wordList = splitParse(open('MLIA_self/ch_04/email/spam/%d.txt' % i,encoding='ISO-8859-1').read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1) #类别标签为1,即是垃圾邮件
        wordList = splitParse(open('MLIA_self/ch_04/email/ham/%d.txt' % i, encoding='ISO-8859-1').read())

        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)  # 类别标签为1,即是垃圾邮件
    vocabList = createVocabList(docList)
    trainSet = list(range(50)); testSet = [] # 初始化训练集和测试集的index
    for i in range(10):
        randIndex = int(random.uniform(0,len(trainSet)))
        testSet.append(trainSet[randIndex])
        del(trainSet[randIndex]) #  这样删除不会导致删了一个之后index就变了么? 上一行要改
    trainMat = []; trainClasses = []
    for i in trainSet: # 把训练集和训练集的标签导入
        trainMat.append(setOfWords2Vec(vocabList,docList[i]))
        trainClasses.append(classList[i])
    p0V, p1V, pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0.0
    for i in testSet:
        result = classifyNB(array(setOfWords2Vec(vocabList, docList[i])),p0V,p1V,pSpam)
        if result != classList[i]:
            errorCount += 1
    print('The error rate is: %.3f' % float(errorCount / len(testSet)))


spamTest()
The error rate is: 0.000


/home/caid/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:5: FutureWarning: split() requires a non-empty pattern match.
  """

在本章的利用贝叶斯处理文本的例子中,词条切分器和停用词的设置都值得深入研究,如果遇到文本处理问题时,这一块还要继续深入.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值