利用朴素贝叶斯算法处理垃圾邮箱识别

一、引言

 在当今信息爆炸的时代,电子邮件成为人们日常沟通和工作中不可或缺的一部分。然而,随之而来的垃圾邮件问题也日益严重。本文将介绍如何利用朴素贝叶斯算法来识别和过滤垃圾邮箱,提高电子邮件处理效率。

二、基础知识

2.1 一些数学概率公式

(1)先验概率和后验概率:

假设我们要使用朴素贝叶斯算法来进行电子邮件的垃圾邮件分类。我们有两个类别:垃圾邮件(Spam)和非垃圾邮件(Ham)。现在我们收到了一封包含“免费优惠”这个词语的邮件,请问这封邮件属于垃圾邮件的概率是多少?

先验概率:
先验概率指的是在考虑任何证据的情况下,在我们观察到新数据之前,对事件概率的初始估计。在这个例子中,假设我们知道在所有收到的邮件中,大约有30%是垃圾邮件。那么垃圾邮件的先验概率为0.3,非垃圾邮件的先验概率为0.7。

后验概率:
后验概率是在考虑了新的数据或证据之后,根据先验概率和相应的证据来计算事件的概率。在这个例子中,我们将根据“免费优惠”这个词语在垃圾邮件和非垃圾邮件中出现的概率,来计算这封邮件属于垃圾邮件的概率,即后验概率。

综上所述,先验概率是在观察任何数据之前对事件概率的初始估计,而后验概率是在观察到新的数据或证据之后,根据先验概率和相应的证据来计算事件的概率。在朴素贝叶斯算法中,我们利用先验概率和后验概率来进行分类决策。

(2)联合概率:有多个条件同时成立的概率,记为P(A,B)

  (3)  相互独立事件:如果P(A,B)=P(A)xP(B) ,则A和B事件相互独立

(4) 指的是B事件发生的条件下,A事件发生的概率,记作P(A|B)。公式:

2.2 贝叶斯公式

贝叶斯定理是一种通过先验概率和观测数据来计算后验概率的方法,其推导过程可以通过简单的概率论知识和条件概率的定义完成。下面是贝叶斯定理的推导过程:

三、朴素贝叶斯算法

3.1 基本概念及核心思想

朴素贝叶斯算法基于贝叶斯定理,通过计算后验概率来进行分类决策。
基于朴素条件独立性假设,将样本的特征之间看作相互独立的,从而简化计算。
在训练阶段,朴素贝叶斯算法统计每个类别下各个特征的条件概率分布。
在预测阶段,根据观测到的特征值,利用贝叶斯定理计算后验概率,选择具有最大后验概率的类别作为预测结果。

用垃圾邮件的案例为例子:

3.2 MAP分类准则

朴素贝叶斯算法的MAP分类准则是一种基于贝叶斯定理和最大后验概率来进行分类的方法。在朴素贝叶斯算法中,MAP分类准则用于选择具有最大后验概率的类别作为预测结果。

因为变量相互独立,为方便公式标记,不妨记P(C=c|X=x)为P(c|x),基于属性条件独立性假设,贝叶斯公式可重写为:

其中d为属性数目,x_i 为 x 在第i个属性上的取值。 

由于对所有类别来说 P(x)相同,因此MAP判定准则可改为:

3.3 拉普拉斯修正

拉普拉斯修正是指在计算概率时对每个计数都加上一个小的修正值,通常是1,以防止概率为零的情况出现。这样做可以避免因为某个特征在训练集中未出现而导致整个类别的概率为零的问题。
在朴素贝叶斯分类器中,拉普拉斯修正通过在每个类别的计数中加上一个常数来实现,以确保所有特征的概率不会为零。

则修改可为:

3.4 防溢出策略

在朴素贝叶斯算法中,条件概率的乘积往往涉及多个小于1的概率,这样的连乘运算容易导致下溢。为了避免这种情况,可以对概率进行取对数处理,将连乘转化为连加,同时也能够减少计算复杂度。

下面是采用对数变换的防溢出策略的一般步骤:

计算先验概率和条件概率时,对其取自然对数。
在预测时,对后验概率进行计算时同样取自然对数。
对类别的后验概率进行取对数处理后,选择具有最大对数后验概率的类别作为预测结果。
通过取对数处理,可以有效避免概率连乘时出现下溢或上溢的问题,提高了计算的稳定性和准确性

四、 垃圾邮箱的分类实现

4.1 构建给出的email和sogo数据集

import os

from utils import *
from collections import defaultdict

def build_email_dataset(language,root_dir = "dataset\\email"):
    
    dataset = {}
    category_cnt = {
        'ham':0,
        'spam':0
    }
    ham_dir = os.path.join(root_dir, 'ham')
    spam_dir = os.path.join(root_dir, 'spam')
    
    ham_vocabulary = defaultdict(int)
    spam_vocabulary = defaultdict(int)
    
    # use 20 ham + 20 spam as train dataset
    # use the rest as test dataset
    for file_name in os.listdir(ham_dir)[:-5]:
        #print('processing ham file:', file_name)
        category_cnt['ham'] += 1
        words = load_text(os.path.join(ham_dir,file_name),language)
        for word in words:
            if word.startswith('http') or word.startswith('www'):
                continue
            ham_vocabulary[word]+=1
    
    for file_name in os.listdir(spam_dir)[:-5]:
        #print('processing spam file:', file_name)
        category_cnt['spam'] += 1
        words = load_text(os.path.join(spam_dir,file_name),language)
        for word in words:
            if word.startswith('http') or word.startswith('www'):
                continue
            spam_vocabulary[word]+=1
    
    dataset['ham'] = ham_vocabulary
    dataset['spam'] = spam_vocabulary
    #print(ham_vocabulary)
    #print('ham vocabulary size:',len(ham_vocabulary)) # 421
    #print('spam vocabulary size:',len(spam_vocabulary))# 264
    dataset['category_cnt'] = category_cnt
    dataset['total_cnt'] = sum(category_cnt.values())
    
    test_set = {}
    for file_name in os.listdir(ham_dir)[-5:]:
        file_name = os.path.join(ham_dir,file_name)
        test_set[file_name] = 'ham'
    for file_name in os.listdir(spam_dir)[-5:]:
        file_name = os.path.join(spam_dir,file_name)
        test_set[file_name] = 'spam'
    
    #print(test_set)
    return dataset, test_set
       

def build_sougou_dataset(language, root_dir = "dataset\\SogouC"):
    
    dataset = {}
    category_cnt = {}
    
    test_set = {}
    
    classlist_path = os.path.join(root_dir, 'ClassList.txt')
    data_path = os.path.join(root_dir,'Sample')
    with open(classlist_path,'r',encoding='utf-8') as f:
        lines = f.readlines()
        for line in lines:
            class_dir, class_name = line.strip('\n').split('\t')
            dataset[class_name] = defaultdict(int)
            category_cnt[class_name] = 0
            for file_name in os.listdir(os.path.join(data_path,class_dir))[:-1]: # use the last file as test set
                category_cnt[class_name] += 1
                words = load_text(os.path.join(data_path,class_dir,file_name),language)
                for word in words:
                    dataset[class_name][word]+=1
    
            last_file = os.listdir(os.path.join(data_path,class_dir))[-1]
            last_file = os.path.join(data_path,class_dir,last_file)
            test_set[last_file] = class_name
    
    dataset['category_cnt'] = category_cnt
    #print(category_cnt)
    dataset['total_cnt'] = sum(category_cnt.values())
    
        
    return dataset, test_set

这段代码是用于构建电子邮件和搜狗数据集的函数。

build_email_dataset(language, root_dir)函数用于构建电子邮件数据集。它接受两个参数:

  • language:指定文本语言的参数。
  • root_dir:数据集所在的根目录,默认为"dataset\email"。

函数首先创建一个空的dataset字典来存储数据集,以及一个category_cnt字典来统计每个类别的样本数量。然后,它将指定的根目录下的"ham"文件夹和"spam"文件夹的路径进行拼接。接下来,函数使用load_text函数加载每个文件的文本数据,并将单词添加到对应类别的词汇表中。其中,ham_vocabularyspam_vocabulary分别用于存储"ham"和"spam"类别的词汇表。

在处理完所有文件后,函数将最终的词汇表和样本数量信息保存到dataset字典中,并返回该字典作为训练数据集。另外一个数据集同这个。

 

4.2 定义移除常见停用词和特殊字符

# basic stop word set
stop_word_set = [',','.',':',';','(',')','[',']',
                     '{','}','<','>','/','\\','|','?'
                     ,'!','@','#','$','%','^','&','*',
                     '~','`','+','=','_','-','\n','\t','\r','&nbsp']

# chinese stop word set
stop_word_dir = 'dataset\\SogouC\\stopwords_cn.txt'
with open(stop_word_dir,'r',encoding='utf-8',errors='ignore') as f:
    stop_word_set_cn = f.read().split('\n')

def delete_stop_word(word,type='cn'):
    
    for stop_word in stop_word_set:
        word = word.replace(stop_word,'')
    for number in ['0','1','2','3','4','5','6','7','8','9']:
        word = word.replace(number,'')
   
    
    # delete the english character in chinese text
    if type == 'cn':
        for i in range(26):
            word = word.replace(chr(ord('a')+i),'')
            word = word.replace(chr(ord('A')+i),'')
        word = word.replace(' ','')
        # remove chinese character
        for c in ['。',',',':',';','(',')','【','】',
              '{','}','<','>','/','\\','|','?','!',
              '@','#','$','%','^','&','*','~','`','+','=','_','-','、']:
            word = word.replace(c,'')
        
    return word

def load_text(file_path,file_type='en',total = False):

    with open(file_path,'r',encoding='utf-8',errors='ignore') as f:
        if total:
            return f.read()
        content = f.read().replace(' ','')
        
    return delete_stop_word(content,file_type)

首先,定义了英文停用词集合stop_word_set,其中包括了常见的标点符号和空白字符等。同时,还定义了中文停用词集合stop_word_set_cn,它通过读取文件"dataset\SogouC\stopwords_cn.txt"来获取中文停用词列表。

接下来是delete_stop_word(word, type='cn')函数,用于删除停用词和特殊字符。最后是load_text(file_path, file_type='en', total=False)函数,用于加载文本数据并进行处理。

 

4.3 构建朴素贝叶斯分类器

import argparse
from time import sleep
import math
from dataset import *

def main(args):
    
    LANGUAGE = 'en'
    
    if args.email:
        LANGUAGE = 'en'
        print('naive bayes classifier for email in english')
        # dataset construction:
        # ham_vocabulary, spam_vocabulary, test_set = build_email_dataset()
        dataset,test_set = build_email_dataset(language=LANGUAGE)
    elif args.sougou:
        LANGUAGE = 'cn'
        print('naive bayes classifier for sougou in chinese')
        dataset,test_set = build_sougou_dataset(language=LANGUAGE)
        
    else :
        print('please specify the dataset')
        return

    # test
    total = 0
    correct = 0

    for file_path in test_set:
        words = load_text(file_path,LANGUAGE,total=True)
        
        # print split line
        print('-'*50)
        print(words)
        print('-'*50)
        true_label = test_set[file_path]
        naive_bayes_label = calculate_naive_bayes(dataset,load_text(file_path,LANGUAGE))
        print('true label:',true_label)
        print('naive bayes label:',naive_bayes_label)
        if naive_bayes_label == true_label:
            correct += 1
        total += 1

    print(f'accuracy: {100*correct/total} %')



def calculate_naive_bayes(dataset,words):

    possibility = {}
    for class_name,category_cnt in dataset['category_cnt'].items():
        possibility[class_name] = math.log(category_cnt/dataset['total_cnt'])
    
    #print(possibility)
    
    class_names = dataset['category_cnt'].keys()
    
    for word in words:
        for class_name in class_names:
            total_word_number = sum(dataset[class_name].values())
            # print('total word number:',total_word_number)
            # print(word)
            # exit(0)
            if word in dataset[class_name].keys():
                #print("find word:",word)
                possibility[class_name] += math.log((dataset[class_name][word]+1)/(total_word_number+len(dataset[class_name])))
                #print('possibility:',possibility)
                #sleep(3)
            else:
                possibility[class_name] += math.log(1/(total_word_number+len(dataset[class_name])+1))
    
    #possibility = np.softmax(possibility)
    print(possibility)
    label = max(possibility,key=possibility.get)
    return label


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--email',action='store_true')
    parser.add_argument('--sougou',action='store_true')
    args = parser.parse_args()
    main(args)

    

首先,通过argparse模块解析命令行参数。可以使用--email参数指定使用英文邮件数据集,或者使用--sougou参数指定使用中文搜狗数据集。

接下来,根据参数选择相应的数据集构建函数和语言类型。如果使用--email参数,则调用build_email_dataset函数构建英文邮件数据集;如果使用--sougou参数,则调用build_sougou_dataset函数构建中文搜狗数据集。同时,将测试集保存到test_set变量中。

然后,使用循环遍历测试集中的每个文件路径。通过load_text函数加载文本数据,并将整篇文本传递给calculate_naive_bayes函数进行分类。然后将真实标签和朴素贝叶斯分类结果打印出来,并统计分类准确率。

4.4 算法的用法和输出

 

 

 

 

截取重点,部分输出省略 

五、实验小结

5.1 朴素贝叶斯的优缺点

优点:

算法逻辑简单,易于实现
分类过程中时空开销小
缺点:

理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好.
而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进.

5.2 实验中的心得体会

  • 首先先验集并不是从一个大样本中筛选出来的,所以导致每一个类的初始先验概率相同.事实上垃圾邮件的数量会多于正常邮件,搜狗中也并不是这九个类的先验概率相同,这显然是不合理的.
  • 其次如果真的完全按照朴素贝叶斯公式来计算也是存在问题的,因为朴素贝叶斯公式是要求各个特征的概率值相乘,多个(0~1)且接近0的小数相乘很容易造成python的下溢出,导致概率归0.
    • 解决方法为利用 math.log 函数的性质,单调递增,内部的乘积可以转化为外部的加法

      把概率的累乘变为多个log的累加,且由于单调递增的性质不会对结果造成影响

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 基于贝叶斯的垃圾邮件过滤是一种常见的垃圾邮件过滤方法。其基本思想是通过对已知的垃圾邮件和正常邮件进行统计分析,得出每个单词在垃圾邮件和正常邮件中出现的概率,然后根据这些概率来判断一封邮件是否为垃圾邮件。 具体实现过程包括以下几个步骤: 1. 收集训练数据:收集一定数量的已知垃圾邮件和正常邮件,用于训练模型。 2. 分词:将邮件内容进行分词,得到每个单词的出现次数。 3. 计算概率:根据训练数据,计算每个单词在垃圾邮件和正常邮件中出现的概率。 4. 计算邮件概率:根据每个单词在邮件中出现的次数,计算该邮件为垃圾邮件的概率。 5. 判断邮件类型:根据计算出的概率,判断该邮件是否为垃圾邮件。 基于贝叶斯的垃圾邮件过滤方法可以有效地过滤垃圾邮件,但也存在一定的误判率。因此,在实际应用中,还需要结合其他方法来提高过滤的准确率。 ### 回答2: 随着互联网的普及和快速发展,电子邮件作为一种很重要的通信工具已经成为了人们生活中必不可少的一部分。然而,随着电子邮件的广泛传播和应用,垃圾邮件的存在也越来越得到人们的关注,它们给人们的生活带来了很多不便和干扰。为了有效地过滤垃圾邮件,基于贝叶斯的垃圾邮件过滤成为了一种非常有效的解决方案。 基于贝叶斯的垃圾邮件过滤是一种概率统计的算法,它通过对训练集进行分类学习和特征选取,计算每个特征在垃圾邮件和非垃圾邮件中的概率,从而对新邮件进行分类。 实现这一算法,需要以下步骤: (1)选择特征:通过特征选择,确定用于进行分类的特征,减小特征维度、降低复杂度、提高分类准确性。 (2)建立分类模型:贝叶斯定理中需要对类别的先验概率和条件概率进行计算。此处采用朴素贝叶斯方法,假设每个特征独立。根据样本中特征与类别的联合概率公式,计算垃圾邮件、正常邮件及其中每个特征的概率。 (3)测试分类器:使用测试集来测试分类器,得到分类器的预测准确率。可以通过调整特征选择和模型参数的方法来提高分类器的精确度和召回率。 (4)应用到实际系统中:将训练好的分类器应用到实际系统中进行垃圾邮件过滤,减少垃圾邮件对用户的干扰。 总之,基于贝叶斯的垃圾邮件过滤是一种非常有效的解决方案。但是,分类器的精度和效率是需要不断优化的。通过优化特征选择和模型参数等方法,继续提高分类器的精度和召回率,从而更好地为用户提供服务。 ### 回答3: 贝叶斯过滤器是一种用于过滤垃圾邮件算法,其基本原理是利用贝叶斯定理来计算某条邮件是垃圾邮件的概率,建立垃圾邮件和非垃圾邮件的概率模型,通过训练来预测新邮件的分类。对于已知的垃圾邮件,可以通过垃圾邮件特征(如垃圾邮件中常用的词汇、发件人、邮件主题等)来计算其在垃圾邮件和所有邮件中出现的概率,进而计算某个新邮件是否是垃圾邮件的概率。 基于贝叶斯的垃圾邮件过滤器的设计与实现可以分为以下几个步骤: 1. 数据预处理:收集训练集数据,对数据进行预处理,包括分词、停用词处理、词干化处理、词频统计等。 2. 计算概率:根据训练集数据,计算垃圾邮件和非垃圾邮件中每个词汇出现的概率,以及垃圾邮件和非垃圾邮件的先验概率。 3. 构建模型:将词汇出现的概率和先验概率组合起来,构建贝叶斯模型。 4. 邮件分类:对于一个新的邮件,将其分词处理,计算每个词汇出现的概率,然后带入贝叶斯模型中计算垃圾邮件的概率和非垃圾邮件的概率,最终将其分类为垃圾邮件或非垃圾邮件。 5. 模型优化:对分类效果较差的邮件进行分析,找出原因并进行优化,如增加新的特征、调整参数等。 基于贝叶斯的垃圾邮件过滤器可以通过机器学习算法不断优化,提高分类效果。同时,随着新的特征的引入,分类效果也会不断得到提高。因此,它是一种高效、准确、普遍使用垃圾邮件过滤器。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值