机器学习算法(3) 朴素贝叶斯

本文介绍了如何使用朴素贝叶斯算法进行垃圾邮件分类。通过构建词向量,建立词汇表,创建文档向量并计算概率,最终依据概率大小实现分类。实验结果显示,算法能有效识别侮辱性留言和垃圾邮件。

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

基于朴素贝叶斯,实现垃圾邮件分类及相关的文本处理操作。


例子来自《Machine Learning in Action》 Peter Harrington

基本思想

这里对朴素贝叶斯概念不做过多解释。

p(ci|w)=p(w|ci)p(ci)p(w)


构建词向量

实验数据

数据描述的是一个留言板的留言信息,每条留言被标记为侮辱性or无侮辱性。

"""创建一个数据集"""
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

构建词汇表

利用set集合无重复元素的特点,创建一个包含训练集合所有字段的集合

"""获得词汇表"""
def createVocabList(dataSet):
    vocabSet = set([])              #创建一个空的set集合
    for document in dataSet:
        vocabSet = vocabSet | set(document)   #合并两个Set集合(求并集)
    return list(vocabSet)

测试

"""测试获取词汇表"""
def testCreateVocabList():
    listOposts,listClass = bayes.loadDataSet()
    myVocabList = bayes.createVocabList(listOposts)
    print(myVocabList)

结果

['stop', 'stupid', 'licks', 'park', 'my', 'has', 'not', 'love', 'quit', 'I', 'so', 'posting', 'flea', 'garbage', 'food', 'how', 'take', 'him', 'dalmation', 'worthless', 'is', 'maybe', 'help', 'ate', 'problems', 'mr', 'to', 'steak', 'cute', 'please', 'buying', 'dog']

创建文档向量

"""获取文档向量"""
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 testSetOfWords2Vec():
    listOposts,listClass = bayes.loadDataSet()
    myVocabList = bayes.createVocabList(listOposts)
    myVec = bayes.setOfWords2Vec(myVocabList,listOposts[0])
    print(myVec)

结果

输入文本对比词表,用0,1进行标注,进而转换为向量表示。

[0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1]

计算概率

基于之前的向量表示和每个向量的标签,计算相应的概率值
由于计算中存在乘积运算,防止一个为零的结果影响整个结果,所以初始化为1。同时概率是小于一的量,防止大量相乘后的数值过小,而溢出丢失精度,所以取每个概率的对数进行计算。

"""从词向量计算概率"""
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)    # 文本个数
    numWords = len(trainMatrix[0])     # 词表长度
    pAbusive = sum(trainCategory)/float(numTrainDocs)   # 所有训练文本中,是侮辱性质的所占比例。
    p0Num = ones(numWords); p1Num = ones(numWords)      #生成与词表长度一致的单位向量 (防止出现0)
    p0Denom = 2.0; p1Denom = 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)         # 防止最后的乘积过小,溢出
    p0Vect = log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive

测试

""" 测试训练函数"""
def testTrainNB0():
    listOposts,listClass = bayes.loadDataSet()
    myVocabList = bayes.createVocabList(listOposts)
    trainMat = []
    for postinDoc in listOposts:
        trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
    p0V,p1V,pAb = bayes.trainNB0(trainMat,listClass)
    print(p0V)
    print("--------------------------------------------------")
    print(p1V)
    print("--------------------------------------------------")
    print(pAb)

结果

分别计算出贝叶斯公式中需要的相应概率值

[-2.56494936 -3.25809654 -2.56494936 -3.25809654 -1.87180218 -2.56494936
 -3.25809654 -2.56494936 -3.25809654 -2.56494936 -2.56494936 -3.25809654
 -2.56494936 -3.25809654 -3.25809654 -2.56494936 -3.25809654 -2.15948425
 -2.56494936 -3.25809654 -2.56494936 -3.25809654 -2.56494936 -2.56494936
 -2.56494936 -2.56494936 -2.56494936 -2.56494936 -2.56494936 -2.56494936
 -3.25809654 -2.56494936]
--------------------------------------------------
[-2.35137526 -1.65822808 -3.04452244 -2.35137526 -3.04452244 -3.04452244
 -2.35137526 -3.04452244 -2.35137526 -3.04452244 -3.04452244 -2.35137526
 -3.04452244 -2.35137526 -2.35137526 -3.04452244 -2.35137526 -2.35137526
 -3.04452244 -1.94591015 -3.04452244 -2.35137526 -3.04452244 -3.04452244
 -3.04452244 -3.04452244 -2.35137526 -3.04452244 -3.04452244 -3.04452244
 -2.35137526 -1.94591015]
--------------------------------------------------
0.5

依据概率大小分类

有了之前的概率值,就可以利用概率大小来判断一个新数据点的类型。

"""朴素贝叶斯分类函数"""
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,listClass = bayes.loadDataSet()
    myVocabList = bayes.createVocabList(listOposts)
    trainMat = []
    for postinDoc in listOposts:
        trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
    p0V,p1V,pAb = bayes.trainNB0(trainMat,listClass)
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(bayes.setOfWords2Vec(myVocabList, testEntry))
    print (testEntry,'classified as: ',bayes.classifyNB(thisDoc,p0V,p1V,pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(bayes.setOfWords2Vec(myVocabList, testEntry))
    print (testEntry,'classified as: ',bayes.classifyNB(thisDoc,p0V,p1V,pAb))

结果

可以看到,算法可以根据新输入的句子(词组段)来判断是否是侮辱性质的内容。

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

基于朴素贝叶斯的垃圾邮件分类

垃圾邮件分类貌似是朴素贝叶斯最好的例子。(至少Andrew Y. Ng在他的课程中不止一次提起这个例子)。

词袋模型

之前的向量构建是基于词集模型的,也就是说只通过0,1的形式记录某个单词时候出现,但是出现的个数无法记录。而词袋模型可以记录每个单词出现的次数。

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

文本划分

训练数据是以txt格式存放的内容,为了方便使用之前的功能模块,需要把原始数据分割为词列表

""" 切分文本"""
def textParse(bigString):    
    import re
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]  

分类

正常邮件和垃圾邮件各有25个(ham/spam)。随机选取10个作为测试集,40个最为训练集,进行试验。

"""垃圾邮件处理"""
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):            # 随机划分训练集和测试集合 (40/10)
        randIndex = int(random.uniform(0,len(trainingSet))) # 随机生成0到50的一个整数
        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 ("classification error",docList[docIndex])
    print ('the error rate is: ',float(errorCount)/len(testSet))

结果

每次的试验结果不尽相同,但是错误率较低。

classification error ['yeah', 'ready', 'may', 'not', 'here', 'because', 'jar', 'jar', 'has', 'plane', 'tickets', 'germany', 'for']
the error rate is:  0.1

其他

需要把随书提供的邮件数据ham\23.txt的第二行SciFinance后的 改为空格。不然读取文件会报错。


以上完整代码见GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值