机器学习——朴素贝叶斯算法(垃圾邮件分类)

朴素贝叶斯算法介绍以及垃圾邮件分类实现

    • 1、一些数学知识
    • 2、贝叶斯公式
    • 3、朴素贝叶斯算法
      • (1)介绍
      • (2)核心思想
      • (3)朴素贝叶斯算法
      • (4)拉普拉斯修正
      • (5)防溢出策略
      • (6)一般过程
      • (7)优缺点
    • 4、例子实现——垃圾邮件分类

1、一些数学知识

  • 先验概率:指的是事件发生前的预判概率,可以根据历史数据/经验估算得到。例如,当我们需要判断西瓜是不是好瓜的时候,对纹理、根蒂等特征都不了解,只是平常我们买西瓜的时候买到好瓜的概率是70%,那么这个西瓜是好瓜的概率我们也可以认为是70%。这个概率70%就是先验概率。
    机器学习——朴素贝叶斯算法(垃圾邮件分类)_第1张图片

  • 后验概率:指的是事件发生后求的反向条件概率。例如,当我们买西瓜的时候,已经知道当西瓜脐部凹陷的时候,是好瓜的概率是80%。如果把脐部凹陷当作一种结果,然后去推测好瓜的概率,那么这个概率P(好瓜|纹理清晰)就是后验概率。

  • 联合概率:指的是包含多个条件,且所有条件同时成立的概率,记作P(A,B)。

  • 相互独立:如果P(A,B)=P(A)·P(B),则称事件A和事件B相互独立

  • 条件概率:指的是一个事件发生的条件下,另一个事件发生的概率,记作P(A|B)。公式:机器学习——朴素贝叶斯算法(垃圾邮件分类)_第2张图片

2、贝叶斯公式

机器学习——朴素贝叶斯算法(垃圾邮件分类)_第3张图片

  • P(A)是先验概率
  • P(B)是先验概率
  • P(B|A)是条件概率,也叫似然概率
  • P(A|B)是后验概率,一般是求解的目标

3、朴素贝叶斯算法

(1)介绍

朴素贝叶斯算法是以贝叶斯定理为基础的一种分类方法,之所以称为 “朴素” ,是假定所有输入事件之间是相互独立的。这是为了简便计算,独立事件间的概率计算更加简单。

(2)核心思想

对于给出的待分类样本,求解在此样本出现的条件下各个类别出现的概率,哪个概率最大,就认为此待分类样本属于哪个类别。
例子:当我们挑选西瓜的时候,通过西瓜的敲击声来判断西瓜的好坏,假设敲击声是“浊响”的西瓜是好瓜的概率是70%,敲击声是“沉闷”的概率是50%,敲击声是“清脆”的概率是10%,那么这个西瓜的敲击声是“浊响”,我们就认为这个西瓜是个好瓜。

(3)朴素贝叶斯算法

机器学习——朴素贝叶斯算法(垃圾邮件分类)_第4张图片
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第5张图片
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第6张图片
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第7张图片
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第8张图片
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第9张图片

机器学习——朴素贝叶斯算法(垃圾邮件分类)_第10张图片

(4)拉普拉斯修正

机器学习——朴素贝叶斯算法(垃圾邮件分类)_第11张图片
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第12张图片

(5)防溢出策略

机器学习——朴素贝叶斯算法(垃圾邮件分类)_第13张图片

(6)一般过程

  1. 收集数据:可以使用任何方法。
  2. 准备数据:需要数值型或者布尔型数据。
  3. 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果最好。
  4. 训练算法:计算不同的独立特征的条件概率。
  5. 测试算法:计算错误率。
  6. 使用算法:可以在任意的分类场景中使用朴素贝叶斯分类器,一个常见的朴素贝叶斯应用是文档分类。

(7)优缺点

  • 优点:
    · 在数据较少的情况下仍然有效,可以处理多类别问题;
    · 有稳定的分类效率;
    · 对缺失数据不太敏感,算法比较简单;
    · 分类准确率高,速度快;
  • 缺点:
    · 由于使用了样本属性独立性的假设,所以如果特征属性有关联时效果不太好;
    · 需要计算先验概率,而先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳;
  • 适用数据类型:标称型数据

4、例子实现——垃圾邮件分类

数据集说明:非垃圾邮件ham和垃圾邮件spam各25封,测试邮件5封,其中把spam中的1、2和ham中的22、23、24拿出来当测试集
ham:
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第14张图片
ham的其中一封:
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第15张图片

spam:
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第16张图片
spam中的其中一封:
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第17张图片
test:
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第18张图片

导包:

import os
import re
import string
import math
import numpy as np

数据预处理:

def get_filtered_str(category):

    email_list = []
    translator = re.compile('[%s]' % re.escape(string.punctuation))

    for curDir, dirs, files in os.walk(f'./email/{category}'):
        for file in files:
            file_name = os.path.join(curDir, file)
            with open(file_name, 'r', encoding='utf-8') as f:
                txt_str = f.read()

                # 全部小写
                txt_str = txt_str.lower()

                # 过滤掉所有符号
                txt_str = translator.sub(' ', txt_str)

                # 过滤掉全部数字
                txt_str = replace_num(txt_str)

                # 把全体的邮件文本 根据换行符把string划分成列表
                txt_str_list = txt_str.splitlines()

                # 把获取的全体单词句子列表转成字符串
                txt_str = ''.join(txt_str_list)
                # print(txt_str)
            email_list.append(txt_str)
    return email_list

数据处理阶段:

def get_dict_spam_dict_w(spam_email_list):
    '''
    :param email_list: 每个邮件过滤后形成字符串,存入email_list
    :param all_email_words: 列表。把所有的邮件内容,分词。一个邮件的词 是它的一个列表元素
    :return:
    '''

    all_email_words = []

    # 用set集合去重
    word_set = set()
    for email_str in spam_email_list:
        # 把每个邮件的文本 变成单词
        email_words = email_str.split(' ')
        # 把每个邮件去重后的列表 存入列表
        all_email_words.append(email_words)
        for word in email_words:
            if(word!=''):
                word_set.add(word)

    # 计算每个垃圾词出现的次数
    word_dict = {}
    for word in word_set:
        # 创建字典元素 并让它的值为1
        word_dict[word] = 0
        # print(f'word={word}')

        # 遍历每个邮件,看文本里面是否有该单词,匹配方法不能用正则.邮件里面也必须是分词去重后的!!! 否则 比如出现re是特征, 那么remind 也会被匹配成re
        for email_words in all_email_words:
            for email_word in email_words:
                # print(f'spam_email={email_word}')
                # 把从set中取出的word 和 每个email分词后的word对比看是否相等
                if(word==email_word):
                    word_dict[word] += 1
                    # 找到一个就行了
                    break

    # 计算垃圾词的概率
    # spam_len = len(os.listdir(f'./email/spam'))
    # print(f'spam_len={spam_len}')
    # for word in word_dict:
    #     word_dict[word]  = word_dict[word] / spam_len
    return word_dict

def get_dict_ham_dict_w(spam_email_list,ham_email_list):
    '''
    :param email_list: 每个邮件过滤后形成字符串,存入email_list
    :param all_email_words: 列表。把所有的邮件内容,分词。一个邮件的词 是它的一个列表元素
    :return:
    '''
    all_ham_email_words = []

    # 用set集合去重 得到垃圾邮件的特征w
    word_set = set()

    #获取垃圾邮件特征
    for email_str in spam_email_list:
        # 把每个邮件的文本 变成单词
        email_words = email_str.split(' ')
        for word in email_words:
            if (word != ''):
                word_set.add(word)

    for ham_email_str in ham_email_list:

        # 把每个邮件的文本 变成单词
        ham_email_words = ham_email_str.split(' ')
        # print(f'ham_email_words={ham_email_words}')

        # 把每个邮件分割成单词的 的列表 存入列表
        all_ham_email_words.append(ham_email_words)
    # print(f'all_ham_email_words={all_ham_email_words}')

    # 计算每个垃圾词出现的次数
    word_dict = {}
    for word in word_set:
        # 创建字典元素 并让它的值为1
        word_dict[word] = 0
        # print(f'word={word}')

        # 遍历每个邮件,看文本里面是否有该单词,匹配方法不能用正则.邮件里面也必须是分词去重后的!!! 否则 比如出现re是特征, 那么remind 也会被匹配成re
        for email_words in all_ham_email_words:
            # print(f'ham_email_words={email_words}')
            for email_word in email_words:
                # 把从set中取出的word 和 每个email分词后的word对比看是否相等
                # print(f'email_word={email_word}')
                if(word==email_word):
                    word_dict[word] += 1
                    # 找到一个就行了
                    break
    return word_dict

# 获取测试邮件中出现的 垃圾邮件特征
def get_X_c1(spam_w_dict,file_name):

    # 获取测试邮件
    # file_name = './email/spam/25.txt'
    # 过滤文本
    translator = re.compile('[%s]' % re.escape(string.punctuation))
    with open(file_name, 'r', encoding='utf-8') as f:
        txt_str = f.read()

        # 全部小写
        txt_str = txt_str.lower()

        # 过滤掉所有符号
        txt_str = translator.sub(' ', txt_str)

        # 过滤掉全部数字
        txt_str = replace_num(txt_str)

        # 把全体的邮件文本 根据换行符把string划分成列表
        txt_str_list = txt_str.splitlines()

        # 把获取的全体单词句子列表转成字符串
        txt_str = ''.join(txt_str_list)

    # 把句子分成词
    email_words = txt_str.split(' ')

    # 去重
    x_set = set()
    for word in email_words:
        if word!='':
            x_set.add(word)
    # print(f'\ntest_x_set={x_set}')
    spam_len = len(os.listdir(f'./email/spam'))
    # 判断测试邮件的词有哪些是垃圾邮件的特征
    spam_X_num = []
    for xi in x_set:
        for wi in spam_w_dict:
            if xi == wi:
                spam_X_num.append(spam_w_dict[wi])
    # print(f'\nspam_X_num={spam_X_num}')
    w_appear_sum_num = 1
    for num in spam_X_num:
        w_appear_sum_num += num
    # print(f'\nham_w_appear_sum_num={w_appear_sum_num}')
    # 求概率
    w_c1_p = w_appear_sum_num / (spam_len + 2)
    return w_c1_p

# 获取测试邮件中出现的非垃圾邮件特征
def get_X_c2(ham_w_dict,file_name):

    # 过滤文本
    translator = re.compile('[%s]' % re.escape(string.punctuation))
    with open(file_name, 'r', encoding='utf-8') as f:
        txt_str = f.read()

        # 全部小写
        txt_str = txt_str.lower()

        # 过滤掉所有符号
        txt_str = translator.sub(' ', txt_str)

        # 过滤掉全部数字
        txt_str = replace_num(txt_str)

        # 把全体的邮件文本 根据换行符把string划分成列表
        txt_str_list = txt_str.splitlines()

        # 把获取的全体单词句子列表转成字符串
        txt_str = ''.join(txt_str_list)

    # 把句子分成词
    email_words = txt_str.split(' ')

    # 去重
    x_set = set()
    for word in email_words:
        if word!='':
            x_set.add(word)
    # print(f'\ntest_x_set={x_set}')

    # 判断测试邮件的词有哪些是垃圾邮件的特征
    ham_X_num = []
    for xi in x_set:
        for wi in ham_w_dict:
            if xi == wi:
                ham_X_num.append(ham_w_dict[wi])
    # print(f'\nham_X_num={ham_X_num}')

    # 先求分子 所有词出现的总和
    ham_len = len(os.listdir(f'./email/ham'))
    w_appear_sum_num = 1
    for num in ham_X_num:
        w_appear_sum_num += num
    # print(f'\nspam_w_appear_sum_num={w_appear_sum_num}')
    # 求概率
    w_c2_p = w_appear_sum_num / (ham_len+2)
    return w_c2_p

测试阶段:

def email_test(spam_w_dict,ham_w_dict):
    for curDir, dirs, files in os.walk(f'./email/test'):
        for file in files:
            file_name = os.path.join(curDir, file)
            print('---------------------------------------------------------------')
            print(f'测试邮件: {file}')
            # 获取条件概率 p(X|c1)
            p_X_c1 = get_X_c1(spam_w_dict,file_name)
            # 获取条件概率 p(X|c2)
            p_X_c2 = get_X_c2(ham_w_dict,file_name)

            # print(f'\nX_c1={p_X_c1}')
            # print(f'\nX_c2={p_X_c2}')

            # #注意:Log之后全部变为负数
            A = np.log(p_X_c1) + np.log(1 / 2)
            B = np.log(p_X_c2) + np.log(1 / 2)

            # 除法会出现问题,-1 / 负分母  结果 < -2/同一个分母
            print(f'p1={A},p2={B}')

            # 因为分母一致,所以只比较 分子即可
            if A > B:
                print('p1>p2,所以是垃圾邮件.')
            if A <= B:
                print('p1)

完整代码:

import os
import re
import string
import math
import numpy as np

# 过滤数字
def replace_num(txt_str):
    txt_str = txt_str.replace(r'0', '')
    txt_str = txt_str.replace(r'1', '')
    txt_str = txt_str.replace(r'2', '')
    txt_str = txt_str.replace(r'3', '')
    txt_str = txt_str.replace(r'4', '')
    txt_str = txt_str.replace(r'5', '')
    txt_str = txt_str.replace(r'6', '')
    txt_str = txt_str.replace(r'7', '')
    txt_str = txt_str.replace(r'8', '')
    txt_str = txt_str.replace(r'9', '')
    return txt_str

def get_filtered_str(category):

    email_list = []
    translator = re.compile('[%s]' % re.escape(string.punctuation))

    for curDir, dirs, files in os.walk(f'./email/{category}'):
        for file in files:
            file_name = os.path.join(curDir, file)
            with open(file_name, 'r', encoding='utf-8') as f:
                txt_str = f.read()

                # 全部小写
                txt_str = txt_str.lower()

                # 过滤掉所有符号
                txt_str = translator.sub(' ', txt_str)

                # 过滤掉全部数字
                txt_str = replace_num(txt_str)

                # 把全体的邮件文本 根据换行符把string划分成列表
                txt_str_list = txt_str.splitlines()

                # 把获取的全体单词句子列表转成字符串
                txt_str = ''.join(txt_str_list)
                # print(txt_str)
            email_list.append(txt_str)
    return email_list

def get_dict_spam_dict_w(spam_email_list):
    '''
    :param email_list: 每个邮件过滤后形成字符串,存入email_list
    :param all_email_words: 列表。把所有的邮件内容,分词。一个邮件的词 是它的一个列表元素
    :return:
    '''

    all_email_words = []

    # 用set集合去重
    word_set = set()
    for email_str in spam_email_list:
        # 把每个邮件的文本 变成单词
        email_words = email_str.split(' ')
        # 把每个邮件去重后的列表 存入列表
        all_email_words.append(email_words)
        for word in email_words:
            if(word!=''):
                word_set.add(word)

    # 计算每个垃圾词出现的次数
    word_dict = {}
    for word in word_set:
        # 创建字典元素 并让它的值为1
        word_dict[word] = 0
        # print(f'word={word}')

        # 遍历每个邮件,看文本里面是否有该单词,匹配方法不能用正则.邮件里面也必须是分词去重后的!!! 否则 比如出现re是特征, 那么remind 也会被匹配成re
        for email_words in all_email_words:
            for email_word in email_words:
                # print(f'spam_email={email_word}')
                # 把从set中取出的word 和 每个email分词后的word对比看是否相等
                if(word==email_word):
                    word_dict[word] += 1
                    # 找到一个就行了
                    break

    # 计算垃圾词的概率
    # spam_len = len(os.listdir(f'./email/spam'))
    # print(f'spam_len={spam_len}')
    # for word in word_dict:
    #     word_dict[word]  = word_dict[word] / spam_len
    return word_dict

def get_dict_ham_dict_w(spam_email_list,ham_email_list):
    '''
    :param email_list: 每个邮件过滤后形成字符串,存入email_list
    :param all_email_words: 列表。把所有的邮件内容,分词。一个邮件的词 是它的一个列表元素
    :return:
    '''
    all_ham_email_words = []

    # 用set集合去重 得到垃圾邮件的特征w
    word_set = set()

    #获取垃圾邮件特征
    for email_str in spam_email_list:
        # 把每个邮件的文本 变成单词
        email_words = email_str.split(' ')
        for word in email_words:
            if (word != ''):
                word_set.add(word)

    for ham_email_str in ham_email_list:

        # 把每个邮件的文本 变成单词
        ham_email_words = ham_email_str.split(' ')
        # print(f'ham_email_words={ham_email_words}')

        # 把每个邮件分割成单词的 的列表 存入列表
        all_ham_email_words.append(ham_email_words)
    # print(f'all_ham_email_words={all_ham_email_words}')

    # 计算每个垃圾词出现的次数
    word_dict = {}
    for word in word_set:
        # 创建字典元素 并让它的值为1
        word_dict[word] = 0
        # print(f'word={word}')

        # 遍历每个邮件,看文本里面是否有该单词,匹配方法不能用正则.邮件里面也必须是分词去重后的!!! 否则 比如出现re是特征, 那么remind 也会被匹配成re
        for email_words in all_ham_email_words:
            # print(f'ham_email_words={email_words}')
            for email_word in email_words:
                # 把从set中取出的word 和 每个email分词后的word对比看是否相等
                # print(f'email_word={email_word}')
                if(word==email_word):
                    word_dict[word] += 1
                    # 找到一个就行了
                    break
    return word_dict

# 获取测试邮件中出现的 垃圾邮件特征
def get_X_c1(spam_w_dict,file_name):

    # 获取测试邮件
    # file_name = './email/spam/25.txt'
    # 过滤文本
    translator = re.compile('[%s]' % re.escape(string.punctuation))
    with open(file_name, 'r', encoding='utf-8') as f:
        txt_str = f.read()

        # 全部小写
        txt_str = txt_str.lower()

        # 过滤掉所有符号
        txt_str = translator.sub(' ', txt_str)

        # 过滤掉全部数字
        txt_str = replace_num(txt_str)

        # 把全体的邮件文本 根据换行符把string划分成列表
        txt_str_list = txt_str.splitlines()

        # 把获取的全体单词句子列表转成字符串
        txt_str = ''.join(txt_str_list)

    # 把句子分成词
    email_words = txt_str.split(' ')

    # 去重
    x_set = set()
    for word in email_words:
        if word!='':
            x_set.add(word)
    # print(f'\ntest_x_set={x_set}')
    spam_len = len(os.listdir(f'./email/spam'))
    # 判断测试邮件的词有哪些是垃圾邮件的特征
    spam_X_num = []
    for xi in x_set:
        for wi in spam_w_dict:
            if xi == wi:
                spam_X_num.append(spam_w_dict[wi])
    # print(f'\nspam_X_num={spam_X_num}')
    w_appear_sum_num = 1
    for num in spam_X_num:
        w_appear_sum_num += num
    # print(f'\nham_w_appear_sum_num={w_appear_sum_num}')
    # 求概率
    w_c1_p = w_appear_sum_num / (spam_len + 2)
    return w_c1_p

# 获取测试邮件中出现的 垃圾邮件特征
def get_X_c2(ham_w_dict,file_name):

    # 过滤文本
    translator = re.compile('[%s]' % re.escape(string.punctuation))
    with open(file_name, 'r', encoding='utf-8') as f:
        txt_str = f.read()

        # 全部小写
        txt_str = txt_str.lower()

        # 过滤掉所有符号
        txt_str = translator.sub(' ', txt_str)

        # 过滤掉全部数字
        txt_str = replace_num(txt_str)

        # 把全体的邮件文本 根据换行符把string划分成列表
        txt_str_list = txt_str.splitlines()

        # 把获取的全体单词句子列表转成字符串
        txt_str = ''.join(txt_str_list)

    # 把句子分成词
    email_words = txt_str.split(' ')

    # 去重
    x_set = set()
    for word in email_words:
        if word!='':
            x_set.add(word)
    # print(f'\ntest_x_set={x_set}')

    # 判断测试邮件的词有哪些是垃圾邮件的特征
    ham_X_num = []
    for xi in x_set:
        for wi in ham_w_dict:
            if xi == wi:
                ham_X_num.append(ham_w_dict[wi])
    # print(f'\nham_X_num={ham_X_num}')

    # 先求分子 所有词出现的总和
    ham_len = len(os.listdir(f'./email/ham'))
    w_appear_sum_num = 1
    for num in ham_X_num:
        w_appear_sum_num += num
    # print(f'\nspam_w_appear_sum_num={w_appear_sum_num}')
    # 求概率
    w_c2_p = w_appear_sum_num / (ham_len+2)
    return w_c2_p

def email_test(spam_w_dict,ham_w_dict):
    for curDir, dirs, files in os.walk(f'./email/test'):
        for file in files:
            file_name = os.path.join(curDir, file)
            print('---------------------------------------------------------------')
            print(f'测试邮件: {file}')
            # 获取条件概率 p(X|c1)
            p_X_c1 = get_X_c1(spam_w_dict,file_name)
            # 获取条件概率 p(X|c2)
            p_X_c2 = get_X_c2(ham_w_dict,file_name)

            # print(f'\nX_c1={p_X_c1}')
            # print(f'\nX_c2={p_X_c2}')

            # #注意:Log之后全部变为负数
            A = np.log(p_X_c1) + np.log(1 / 2)
            B = np.log(p_X_c2) + np.log(1 / 2)

            # 除法会出现问题,-1 / 负分母  结果 < -2/同一个分母
            print(f'p1={A},p2={B}')

            # 因为分母一致,所以只比较 分子即可
            if A > B:
                print('p1>p2,所以是垃圾邮件.')
            if A <= B:
                print('p1)

if __name__=='__main__':

    spam_email_list = get_filtered_str('spam')
    ham_email_list = get_filtered_str('ham')

    spam_w_dict = get_dict_spam_dict_w(spam_email_list)
    ham_w_dict = get_dict_ham_dict_w(spam_email_list,ham_email_list)

    # print(f'\n从垃圾邮件中提取的特征及每个特征出现的邮件数:')
    # print(f'spam_w_dict={spam_w_dict}')

    # print(f'\n普通邮件中垃圾邮件特征出现的邮件数为:')
    # print(f'ham_w_dict={ham_w_dict}')

    email_test(spam_w_dict, ham_w_dict)

测试结果:
机器学习——朴素贝叶斯算法(垃圾邮件分类)_第19张图片

数据集以及代码:
链接: https://pan.baidu.com/s/1cHBDd_M_B0vgUA6la6AK9Q?pwd=psk9
提取码:psk9

你可能感兴趣的:(算法,分类)