【machine learning】朴素贝叶斯分类方法

本文深入探讨朴素贝叶斯分类方法,包括贝叶斯决策理论的介绍和朴素贝叶斯的基本概念。文章阐述了如何利用条件概率进行分类,并提供了自定义测试数据、垃圾邮件过滤的应用示例,以及相关的程序设计与算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

1.1 贝叶斯决策

本文主要讲述利用朴素贝叶斯进行分类的问题。朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素负叶斯之前有必要快速了解一下贝叶斯决策理论。
我们现在用p1(x,y)表示数据点(x,y)属于类别1,用p2(x,y)表示数据点(x,y)属于类别2 的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:

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

也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。

1.2 朴素贝叶斯

我们称之为“朴素”,是因为整个形式化过程只做最原始、最简单的假设。对于朴素贝叶斯与贝叶斯理论的不同,我们引入条件概率来分类。
需要计算和比较的是p(c1|x,y)和p(c2|x,y)。这些符号所代表的具体意义是:
给定某个由x,y表示的数据点,那么该数据点来自类别c1的概率是多少?数据点来自类别c2的概率又是多少?注意这些概率与p(x,y|c1)并不一样,不过可以使用贝叶斯准则来交换概率中条件与结果。具体地,应用贝叶斯准则得到:
这里写图片描述

  • 如果p1(c1 | x,y) > p2(c2 | x,y),那么类别为1。
  • 如果p1(c1 | x,y) < p2(c2 | x,y),那么类别为2。

在利用朴素贝叶斯进行文本分类时,(x,y)表示词数值向量,如[0,1,0,…,1],则贝叶斯准则转换为:
这里写图片描述
w表示一个词数值向量,即它由多个数值组成。在这个例子中,数值个数与词汇表(文档所有的词的列表)中的词个数相同。

优缺点:

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

二、程序设计与算法简单应用

2.1 自定义测试数据

函数说明:

  • loadDataSet():加载训练样本数据
  • createVocabList():将所有文档的所有词统一到一个list中并返回,不重复
  • setOfWord2Vec():将词向量转换为词对应的数值向量
  • trainNB0():训练函数,返回三个参数
    # 类别0中词的条件概率向量
    # 类别1中词的条件概率向量
    # 0和1类别发生概率向量(二维)
  • classifyNB():贝叶斯准则的直接体现,返回输入文档词数值向量的类别值0或1

下面为loadDataSet()示例

# 加载训练样本数据
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']]
    # 对应文档的类别,1表示侮辱性言论类型,0表示正常言论类型
    classVec = [0,1,0,1,0,1]    #1 is abusive, 0 not
    return postingList,classVec

注意点说明:

  • 在某个类别下数据点出现的概率时,利用了独立假设,即词与词之间出现不相互依赖,当然我们也知道这个假设过于简单。于是求分子中的p(w0,w1,w2..wN|ci)转换求p(w0|ci)p(w1|ci)p(w2|ci)…p(wN|ci)。
  • 为防止p(w0|ci)p(w1|ci)p(w2|ci)…p(wN|ci)中一个为0导致乘积为0,将所有词的出现次数初始化为1,并将分母初始化为2,也就是说分子本质上增加了1,分母增加了2,在trainNB0中代码改为了:
    p0Num = ones(numWords); p1Num = ones(numWords)      #change to ones() 
    p0Denom = 2.0; p1Denom = 2.0                        #change to 2.0
  • 为防止p(w0|ci)p(w1|ci)p(w2|ci)…p(wN|ci)相乘导致的下溢出,采用对数ln,因为ln(a*b)=ln(a)+ln(b),在classifyNB中改为了:
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)    #element-wise mult
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)

PS:
1.这里的*号表达的是将有这个词的概率对数相加,因此在计算vec2Classify时不能用词袋模型计算向量的函数bagOfWords2VecMN,不过发现网上源代码有的用了,因为这样就变平方了,而贝叶斯准则分子的意思是出现
2.因为这是一个二类分类问题,所以通过1-P(1)得到P(0),对于多于两类的分类问题,需要对trainNB0中pAbusive的计算和classifyNB的概率计算进行修改。

运行程序:

import bayes 
if __name__ == '__main__':
    bayes.testingNB()

这里写图片描述

2.2 分类应用,过滤垃圾邮件

函数说明:

  • textParse():将字符串分成词列表
  • spamTest():将正常邮件与垃圾邮件作为数据源

注意点说明:

  • 切分文本:分隔符是处理单词、数字外的任意字符串,并转为小写
    ps:切分文本是文本解析的一个操作,但又是一个相当复杂的过程,因为要考虑诸多方面,譬如各种无用的词,如停用词等高频词,各种不应该分开的词等。
  • 在测试中,将50个文本内容(25个是垃圾邮件25个是正常邮件)作为训练集,随机抽取10个作为测试样本,采用了留存交叉验证
  • 输出结果为10个测试样本中被错分的百分比,可以选择50个样本(即全部)进行测试,也可以重复多次,然后求平均值,从而得到平均错误率,书中说是6%。

运行程序:

import bayes 
if __name__ == '__main__':
    bayes.spamTest()

这里写图片描述

2.3 完整代码

bayes.py

#! /usr/bin/env python
#coding=utf-8
'''
Created on May 19, 2015

@author: enjoyot
'''

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']]
    # 对应文档的类别,1表示侮辱性言论类型,0表示正常言论类型
    classVec = [0,1,0,1,0,1]    #1 is abusive, 0 not
    return postingList,classVec

# 将dataSet转换为一个不包含重复词组的列表,形式为上面postingList那样
def createVocabList(dataSet):
    vocabSet = set([])  #create empty set
    for document in dataSet:
        vocabSet = vocabSet | set(document) #union of the two sets
    return list(vocabSet)

# 词集模型,词在文档中至多出现一次,或表达为一次
# 将inputSet单文档词列表根据有无对应vocabList的词,转换为二值向量,如[0,0,1,1,0...]
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 bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec


# 朴素贝叶斯训练函数,两个参数分别表示
# 所有文档的二值向量组成的矩阵,形式如[[],[],[]...]
# 文档类别向量
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])
    # 计算类别1下每个词发生的条件概率,classifyNB中将进行相乘操作(独立假设)
    p1Vect = log(p1Num/p1Denom)          #change to log()
    p0Vect = log(p0Num/p0Denom)          #change to log()
    # 返回值分别代表
    # 类别0中词的条件概率向量
    # 类别1中词的条件概率向量
    # 0和1类别发生概率向量(二维)
    return p0Vect,p1Vect,pAbusive

# 最核心函数,体现贝叶斯公式,分母词向量没有参与计算是因为目的只是为了比较大小,没必要计算
# 第一个参数为接收的待分类的文档的二值向量
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)    #element-wise mult
    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))
    # 注意输入为词库已有词,否则会打印提示,见setOfWords2Vec
    testEntry = ['love', 'my', 'dalmation']
    # 注意,这里的thisDoc在应用log相加代替相乘时系数都应该为1
    # 所以不能用bagOfWords2VecMN函数,下同
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    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)


# --------------------
# 用于垃圾邮件分类测试
# --------------------

# 将字符串分成词列表
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)#create vocabulary
    trainingSet = range(50); testSet=[]           #create test set
    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:#train the classifier (get probs) trainNB0
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0
    for docIndex in testSet:        #classify the remaining items
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值