朴素贝叶斯--过滤垃圾邮件实例

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/GXSeveryday/article/details/88068020

使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。

数据集下载

1. 准备数据

import re

"""
函数说明:接收一个大字符串并将其解析为字符串列表

Parameters:
    无
Returns:
    无
"""
def textParse(bigString):                                                   #将字符串转换为字符列表
    listOfTokens = re.split(r'\W*', bigString)                              #将特殊符号作为切分标志进行字符串切分,即非字母、非数字
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]            #除了单个字母,例如大写的I,其它单词变成小写

"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表

Parameters:
    dataSet - 整理的样本数据集
Returns:
    vocabSet - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

if __name__ == '__main__':
    docList = []; classList = []
    for i in range(1, 26):                                                  #遍历25个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())     #读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(1)                                                 #标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())      #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(0)                                                 #标记非垃圾邮件,1表示垃圾文件   
    vocabList = createVocabList(docList)                                    #创建词汇表,不重复
    print(vocabList)

运行结果:

['eugene', 'biggerpenis', 'was', 'endorsed', 'starting', 'improving', 'life', 'ideas', 'drunk', 'door', 'wednesday', 'docs', 'genuine', 'inform', '588', 'welcome',
 'location', 'scenic', 'try', 'assigning', 'incredib1e', 'order', 'survive', 'fine', 'ups', 'view', 'know', 'monte', 'phentermin', 'julius', 'located', 'either', '300x', 
 'shape', 'food', 'gas', 'program', 'permanantly', 'may', 'now', 'borders', 'based', 'for', 'brand', 'inside', 'money', 'trusted', 'both', 'cold', 'ferguson', 'should', 
 'dozen', 'magazine', 'must', 'attaching', 'past', 'jpgs', 'watchesstore', 'bin', 'scifinance', '562', 'questions', 'said', 'reply', 'worldwide', 'jocelyn', 'serial', 
 'don', 'link', 'management', 'about', 'freeviagra', 'visa', 'night', 'hours', 'uses', 'wallets', 'does', 'cannot', 'would', 'your', '100', 'designed', 'pharmacy', 
 'grow', 'been', 'products', 'warranty', 'reliever', 'earn', 'but', 'noprescription', 'groups', 'inconvenience', 'runs', 'are', 'windows', 'being', 'recieve', 
 'intenseorgasns', 'zolpidem', 'phone', 'lunch', 'express', 'today', 'guy', 'cost', 'time', 'announcement', 'hope', 'linkedin', 'want', '2010', 'drugs', '138', 
 '86152', 'learn', 'wilmott', 'opioid', 'arolexbvlgari', 'town', 'rain', 'launch', 'such', 'changes', 'business', 'keep', 'ma1eenhancement', 'having', 'safe', 
 'mailing', 'address', '325', 'web', 'tickets', 'development', 'aged', 'selected', 'tabs', 'regards', 'rent', 'jay', 'girl', 'this', 'spaying', 'couple', 'cartier', 'work', 
 'source', 'needed', 'significantly', 'far', 'moderate', 'creation', 'turd', 'series', 'huge', 'amazing', 'softwares', 'full', 'gucci', 'cheers', 'bathroom', 'high', 
 'what', 'ordercializviagra', 'dhl', 'will', 'louis', 'job', 'museum', 'buyviagra', 'individual', 'jewerly', 'online', 'logged', 'certified', '5mg', 'others', 'whybrew', 
 'they', 'credit', '174623', 'natural', 'out', 'one', 'increase', 'february', 'required', 'top', 'once', 'safest', '30mg', 'courier', 'discount', 'network', 'went', 
 'sophisticated', 'bettererections', 'you', 'sorry', 'those', 'moderately', 'decision', 'update', 'pro', 'received', 'art', 'told', 'forum', 'thanks', 'financial', 'edit', 
 'computing', 'millions', 'code', 'had', 'school', 'haloney', 'way', 'how', '195', 'severepain', 'pick', 'yesterday', 'create', 'well', 'cat', 'vicodin', 'station', 'ones', 
 'chapter', 'competitive', '291', 'site', 'concise', 'strategy', 'york', 'tour', 'who', '15mg', 'inches', 'jar', 'status', 'oem', 'storage', 'good', 'private', 'team', 
 'supporting', 'tool', 'office', 'butt', 'email', 'accepted', 'below', 'quantitative', 'coast', 'inspired', 'knocking', 'cuda', '66343', '129', 'running', 'finder', 'ryan', 
 'that', 'exhibit', 'horn', '366', 'requested', 'explosive', 'right', 'shipping', 'another', 'incoming', 'answer', 'codeine', 'supplement', 'strategic', 'computer', 
 'think', 'works', 'discussions', 'service', '10mg', 'experts', 'wilson', 'generation', 'mom', 'issues', '203', 'father', 'instead', '292', 'arvind', 'bags', '2007', 
 'notification', 'python', 'there', 'assistance', 'longer', 'yourpenis', 'interesting', 'follow', 'mandelbrot', 'heard', 'enjoy', 'could', 'automatic', 'cards', 'check', 
 'nvidia', 'file', 'things', 'carlo', 'color', 'level', 'message', '625', 'come', 'please', 'functionalities', 'store', 'pavilion', 'viagranoprescription', 'each', 
 'generates', 'mandatory', 'control', 'thank', 'used', 'dior', 'copy', 'capabilities', 'find', 'commented', '2011', 'care', 'them', 'superb', 'enough', 'invitation', 
 'let', 'take', 'game', 'sure', 'expo', 'great', 'focus', 'wrote', 'watches', 'talked', 'approved', 'can', 'plane', 'chance', 'shipment', 'bad', 'writing', 'winter', 'treat', 
 'hommies', 'sounds', 'brands', 'help', 'behind', 'doing', 'methods', 'google', 'chinese', 'focusing', 'analgesic', 'possible', 'oris', 'window', 'items', 'ready', 
 'important', 'buy', 'information', '50092', 'amex', 'hermes', 'lined', 'speedpost', 'jqplot', 'prototype', 'thickness', 'back', 'differ', 'automatically', 'book', 
 'pages', 'bargains', 'hydrocodone', 'some', 'pictures', 'thirumalai', 'design', 'since', 'foaming', 'trip', 'knew', 'much', 'modelling', '1924', 'dusty', 'gain', 
 'germany', 'wholesale', 'effective', 'john', 'yeah', 'just', 'accept', 'ambiem', 'meet', 'quality', 'hotels', 'everything', 'tesla', 'kerry', 'favorite', 'pricing', 
 'thread', 'volume', 'titles', 'experience', 'gpu', 'troy', 'made', 'website', 'stuff', 'major', '430', 'saw', 'free', 'holiday', 'specifically', 'blue', '50mg', 'page', 
 'lists', 'contact', 'jose', 'doctor', 'discreet', 'customized', 'ultimate', '130', 'got', 'fractal', 'length', 'stepp', 'close', 'add', 'fedex', 'has', 'doors', 'nature', 'see', 
 'storedetailview_98', '14th', 'pretty', 'don抰', 'new', '180', 'rude', 'use', 'signed', 'creative', 'brandviagra', 'jquery', 'model', 'car', 'also', 'more', 'rock', 
 'example', '513', 'cheap', 'per', '119', 'using', 'riding', 'sky', '492', 'sent', 'expertise', 'photoshop', 'might', 'pills', '90563', 'price', 'mba', 'tokyo', 'advocate', 
 'enabled', 'fans', '199', 'class', 'because', 'held', 'acrobat', 'you抮e', 'hangzhou', 'died', 'thing', 'success', 'placed', '570', 'support', 'thought', 'hotel', 'low', 
 'glimpse', 'fermi', 'of_penisen1argement', 'members', 'have', 'definitely', 'style', 'mathematician', 'hold', 'via', 'get', 'upload', 'china', 'files', 'articles', 
 'place', 'need', 'easily', 'giants', 'all', 'group', 'going', 'working', 'through', 'benoit', 'his', '322', 'retirement', 'often', 'vuitton', 'net', 'the', 'approach', 'from', 
 'party', 'insights', 'doggy', 'peter', 'reservation', 'tiffany', 'canadian', '200', 'connection', 'ofejacu1ate', 'forward', 'thailand', 'wasn', 'grounds', 'income', 
 'includes', 'watson', 'access', '120', 'changing', 'days', 'not', '0nline', 'item', 'year', 'core', 'when', 'programming', 'over', '100m', 'then', 'specifications', 
 'gains', 'looking', 'cs5', 'any', 'name', 'day', 'featured', '750', 'call', 'cats', 'guaranteeed', 'harderecetions', 'faster', 'came', 'mathematics', 'pls', 'fast', 'off', 
 'moneyback', 'transformed', 'release', 'proven', 'done', 'naturalpenisenhancement', 'risk', '396', 'bike', 'note', 'leaves', 'zach', 'adobe', 
 'betterejacu1ation', 'most', 'femaleviagra', 'home', 'opportunity', 'fundamental', 'encourage', 'yay', 'parallel', 'province', 'plus', 'same', 'vivek', 'listed', 
 'sliding', 'cca', 'withoutprescription', 'while', 'too', 'prices', 'methylmorphine', 'plugin', '156', 'brained', 'hamm', 'download', 'prepared', 'extended', 
 'features', 'mandarin', 'tent', 'roofer', 'october', 'www', 'sites', 'fbi', 'only', 'famous', 'away', 'number', 'train', 'where', '225', 'two', 'reputable', 'pill', 'share', 
 'herbal', 'ems', 'http', 'mail', 'least', '100mg', 'and', 'latest', 'narcotic', 'percocet', 'hello', 'than', 'professional', 'comment', 'thousand', 'like', 'suggest', 
 'pain', 'derivatives', '25mg', 'com', '219', 'finance', 'perhaps', 'with', 'fda', 'these', 'here', 'microsoft', 'save', 'owner', 'delivery', '385']

2. 根据词汇表,我们就可以将每个文本向量化。我们将数据集分为训练集和测试集,使用交叉验证的方式测试朴素贝叶斯分类器的准确性。编写代码如下:

import numpy as np
import random
import re

"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表

Parameters:
    dataSet - 整理的样本数据集
Returns:
    vocabSet - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0

Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词集模型
"""
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                                    #创建一个其中所含元素都为0的向量
    for word in inputSet:                                                #遍历每个词条
        if word in vocabList:                                            #如果词条存在于词汇表中,则置1
            returnVec[vocabList.index(word)] = 1
        else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec                                                    #返回文档向量


"""
函数说明:根据vocabList词汇表,构建词袋模型

Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词袋模型
"""
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)                                        #创建一个其中所含元素都为0的向量
    for word in inputSet:                                                #遍历每个词条
        if word in vocabList:                                            #如果词条存在于词汇表中,则计数加一
            returnVec[vocabList.index(word)] += 1
    return returnVec                                                    #返回词袋模型

"""
函数说明:朴素贝叶斯分类器训练函数

Parameters:
    trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
    trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱类的条件概率数组
    p1Vect - 非侮辱类的条件概率数组
    pAbusive - 文档属于侮辱类的概率
"""
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #计算训练的文档数目
    numWords = len(trainMatrix[0])                            #计算每篇文档的词条数
    pAbusive = sum(trainCategory)/float(numTrainDocs)        #文档属于侮辱类的概率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)    #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
    p0Denom = 2.0; p1Denom = 2.0                            #分母初始化为2,拉普拉斯平滑
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                            #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                                                #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom)                            #取对数,防止下溢出         
    p0Vect = np.log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive                            #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

"""
函数说明:朴素贝叶斯分类器分类函数

Parameters:
    vec2Classify - 待分类的词条数组
    p0Vec - 侮辱类的条件概率数组
    p1Vec -非侮辱类的条件概率数组
    pClass1 - 文档属于侮辱类的概率
Returns:
    0 - 属于非侮辱类
    1 - 属于侮辱类
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)        #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

"""
函数说明:朴素贝叶斯分类器训练函数

Parameters:
    trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
    trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱类的条件概率数组
    p1Vect - 非侮辱类的条件概率数组
    pAbusive - 文档属于侮辱类的概率
"""
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #计算训练的文档数目
    numWords = len(trainMatrix[0])                            #计算每篇文档的词条数
    pAbusive = sum(trainCategory)/float(numTrainDocs)        #文档属于侮辱类的概率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)    #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
    p0Denom = 2.0; p1Denom = 2.0                            #分母初始化为2,拉普拉斯平滑
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                            #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                                                #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom)                            #取对数,防止下溢出         
    p0Vect = np.log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive                            #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率


"""
函数说明:接收一个大字符串并将其解析为字符串列表

Parameters:
    无
Returns:
    无
"""
def textParse(bigString):                                                   #将字符串转换为字符列表
    listOfTokens = re.split(r'\W*', bigString)                              #将特殊符号作为切分标志进行字符串切分,即非字母、非数字
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]            #除了单个字母,例如大写的I,其它单词变成小写

"""
函数说明:测试朴素贝叶斯分类器

Parameters:
    无
Returns:
    无
"""
def spamTest():
    docList = []; classList = []; fullText = []
    for i in range(1, 26):                                                  #遍历25个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())     #读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(1)                                                 #标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())      #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(0)                                                 #标记非垃圾邮件,1表示垃圾文件   
    vocabList = createVocabList(docList)                                    #创建词汇表,不重复
    trainingSet = list(range(50)); testSet = []                             #创建存储训练集的索引值的列表和测试集的索引值的列表                       
    for i in range(10):                                                     #从50个邮件中,随机挑选出40个作为训练集,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(np.array(trainMat), np.array(trainClasses))  #训练朴素贝叶斯模型
    errorCount = 0                                                          #错误分类计数
    for docIndex in testSet:                                                #遍历测试集
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])           #测试集的词集模型
        if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:    #如果分类错误
            errorCount += 1                                                 #错误计数加1
            print("分类错误的测试集:",docList[docIndex])
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))


if __name__ == '__main__':
    spamTest()

运行结果1:

分类错误的测试集: ['oem', 'adobe', 'microsoft', 'softwares', 'fast', 'order', 'and', 'download', 'microsoft', 'office', 'professional', 'plus', '2007', '2010', '129', 'microsoft', 'windows', 'ultimate', '119', 'adobe', 'photoshop', 'cs5', 'extended', 'adobe', 'acrobat', 'pro', 'extended', 'windows', 'professional', 'thousand', 'more', 'titles']
分类错误的测试集: ['home', 'based', 'business', 'opportunity', 'knocking', 'your', 'door', 'don抰', 'rude', 'and', 'let', 'this', 'chance', 'you', 'can', 'earn', 'great', 'income', 'and', 'find', 'your', 'financial', 'life', 'transformed', 'learn', 'more', 'here', 'your', 'success', 'work', 'from', 'home', 'finder', 'experts']
错误率:20.00%

运行结果2:

分类错误的测试集: ['home', 'based', 'business', 'opportunity', 'knocking', 'your', 'door', 'don抰', 'rude', 'and', 'let', 'this', 'chance', 'you', 'can', 'earn', 'great', 'income', 'and', 'find', 'your', 'financial', 'life', 'transformed', 'learn', 'more', 'here', 'your', 'success', 'work', 'from', 'home', 'finder', 'experts']
错误率:10.00%

运行结果3:

错误率:0.00%

函数spamTest()会输出在10封随机选择的电子邮件上的分类错误概率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错误的文档的此表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归为垃圾邮件好。为了避免错误,有多种方式可以用来修正分类器。

参考博客:https://cuijiahua.com/

猜你喜欢

转载自blog.csdn.net/GXSeveryday/article/details/88068020