【机器学习】贝叶斯算法详解 + 公式推导 + 垃圾邮件过滤实战 + Python代码实现

news2025/4/8 6:23:42

文章目录

  • 一、贝叶斯简介
  • 二、贝叶斯公式推导
  • 三、拼写纠正案例
  • 四、垃圾邮件过滤案例
    • 4.1 问题描述
    • 4.2 朴素贝叶斯引入
  • 五、基于朴素贝叶斯的垃圾邮件过滤实战
    • 5.1 导入相关库
    • 5.2 邮件数据读取
    • 5.3 构建语料表(字典)
    • 5.4 构建训练集的特征向量
    • 5.5 朴素贝叶斯算法计算概率
    • 5.6 贝叶斯公式的对数变换 + 邮件类别预测
    • 5.7 完整代码 + 运行输出


一、贝叶斯简介

在这里插入图片描述

贝叶斯主要解决的问题是“逆概”问题,那么什么是正向概率什么是逆向概率呢,下面给出解释

在这里插入图片描述


二、贝叶斯公式推导

贝叶斯定理由英国数学家贝叶斯 ( Thomas Bayes 1702-1761 ) 发展,用来描述两个条件概率之间的关系,比如 P(A|B) 和 P(B|A)。按照乘法法则,可以立刻导出:P(A∩B) = P(A)*P(B|A)=P(B)*P(A|B)。如上公式也可变形为:P(A|B)=P(B|A)*P(A)/P(B)。

下面举一个例子来推导贝叶斯公式:

在这里插入图片描述

解答如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最终得到贝叶斯公式如下

P ( A ∣ B ) = P ( A ) × P ( B ∣ A ) P ( B ) P(A|B)=\frac{P(A)×P(B|A)}{P(B)} P(AB)=P(B)P(A)×P(BA)


三、拼写纠正案例

问题描述如下:
例如,用户拼写 thr ,我们猜测他可能想拼写 the

在这里插入图片描述

根据贝叶斯公式,我们可以得到如下结论:

在这里插入图片描述

由于用户实际输入的单词是已知的,所以P(D)是一个常数,所以上式的P(D)其实可以忽略。
P(h)是我们猜测词可能出现的概率,这个概率可以通过从一个庞大的语料库中统计获取
P(D|h)是当用户实际像输入的词是h时,用户输入D的概率。假设D是thr,h是the,那么P(D|h)我们可以通过计算thr要通过多少步的增删改操作才能变为the,所需要的步数越少,P(D|h)概率越大。

在这里插入图片描述

其中,P(h)又叫先验概率,是我们从庞大语料库中可以获取到的已知概率

在这里插入图片描述

贝叶斯和最大似然的区别在于,最大似然的结果由数据决定,而贝叶斯的结果由先验概率决定。例如,抛硬币游戏,前一百次都抛了正面,最大似然会认为下一次抛肯定还是正面。但是贝叶斯由于有先验概率的存在,无论如何他都认为下一次抛出正面的概率是0.5。

在这里插入图片描述


四、垃圾邮件过滤案例

4.1 问题描述

问题的相关描述如下:

在这里插入图片描述

4.2 朴素贝叶斯引入

一封邮件里有很多单词,从实际出发,第二个词出现的概率其实是受第一个词的影响的。所以将P(d1,d2,…,dn|h+)扩展之后的公式如下所示:

在这里插入图片描述

像上面那样展开的话就会导致计算非常复杂,为了简化计算,我们可以假设相邻词之间是独立无关的,这样就可以将展开后的式子简化为下图所示的式子(朴素贝叶斯就是比贝叶斯多了独立无关这个假设):

在这里插入图片描述


五、基于朴素贝叶斯的垃圾邮件过滤实战

本章节的完整代码和邮件数据集的链接为:Python代码实现基于朴素贝叶斯算法的垃圾邮件分类

5.1 导入相关库

import numpy as np
import re
import random

5.2 邮件数据读取

下面是数据集的截图(每一行代表一封邮件),格式为:邮件类别\t邮件内容

在这里插入图片描述

# 数据预处理操作(词的切分、词转化为小写)
def text_parse(input_str):
    word_list = re.split(r"\W+", input_str)
    return [word.lower() for word in word_list if len(word_list) > 2 and len(word) > 0]


# 获取数据
def read_data():
    doc_list = []
    class_list = []
    with open("./data/SMS.txt", "r", encoding="utf-8") as file:
        datas = file.read()
        # print(data)
        datas = datas.split("\n")
        for data in datas:
            # label = ham 代表 正常邮件 , label = spam 代表垃圾邮件
            label, text = data.split("\t")
            doc_list.append(text_parse(text))
            # 0:正常邮件,1:垃圾邮件
            class_list.append(0 if label == "ham" else 1)
    return doc_list, class_list

5.3 构建语料表(字典)

# 构建语料表
def create_vocabulary_list(doc_list):
    vocabulary_set = set([])
    for document in doc_list:
        vocabulary_set = vocabulary_set | set(document)
    return list(vocabulary_set)

5.4 构建训练集的特征向量

# 将一篇邮件转化为 类似 One-Hot 的向量,长度和 vocabulary_list 一样,为 1 的位置代表该单词在该邮件中出现了
def set_of_word2vector(vocabulary_list, document):
    vec = [0 for _ in range(len(vocabulary_list))]
    for word in document:
        index = vocabulary_list.index(word)
        if index >= 0:
            vec[index] = 1
    return vec
    
train_matrix = []
train_class = []
for train_index in train_index_set:
    train_matrix.append(set_of_word2vector(vocabulary_list, doc_list[train_index]))
    train_class.append(class_list[train_index])

5.5 朴素贝叶斯算法计算概率

回顾一下,我们用贝叶斯算法进行垃圾邮件分类时,其实就是比较 P ( h + ∣ D ) P(h_+|D) P(h+D) P ( h − ∣ D ) P(h_-|D) P(hD) 的大小,如果 P ( h + ∣ D ) P(h_+|D) P(h+D) 大,则表示该邮件更有可能是垃圾邮件,否则更有可能是正常邮件。

P ( h + ∣ D ) = P ( h + ) × P ( D ∣ h + ) ÷ P ( D ) P ( h − ∣ D ) = P ( h − ) × P ( D ∣ h − ) ÷ P ( D ) P(h_+|D)=P(h_+)×P(D|h_+)\div P(D) \\ P(h_-|D)=P(h_-)×P(D|h_-)\div P(D) P(h+D)=P(h+)×P(Dh+)÷P(D)P(hD)=P(h)×P(Dh)÷P(D)

又因为,在针对某封邮件做预测时, P ( D ) P(D) P(D) 可以看作常数,因而可以忽略,从而得到简化后的公式如下(其实下面式子写等号不严谨,应该是正比于,但是为了方便,后面都将正比于简化为等号):

P ( h + ∣ D ) = P ( h + ) × P ( D ∣ h + ) P ( h − ∣ D ) = P ( h − ) × P ( D ∣ h − ) P(h_+|D)=P(h_+)×P(D|h_+) \\ P(h_-|D)=P(h_-)×P(D|h_-) P(h+D)=P(h+)×P(Dh+)P(hD)=P(h)×P(Dh)

其中, P ( h + ) P(h_+) P(h+) 为训练集中垃圾邮件的比率, P ( h − ) P(h_-) P(h)为训练集中正常邮件的比率,它们两个就是先验概率。

显然, P ( h + ) + P ( h − ) = 1 P(h_+) + P(h_-) = 1 P(h+)+P(h)=1,因此,下面我们可以只计算 P ( h + ) P(h_+) P(h+),用变量 p_spam 表示, P ( h + ) P(h_+) P(h+) 可以根据训练集很轻易地得到

又因为朴素贝叶斯假设任意两个词之间是独立无关的,所以可以将 P ( D ∣ h + ) P(D|h_+) P(Dh+) P ( D ∣ h − ) P(D|h_-) P(Dh) 展开如下:

P ( D ∣ h + ) = P ( d 1 ∣ h + ) × P ( d 2 ∣ h + ) × . . . × P ( d n ∣ h + ) P ( D ∣ h − ) = P ( d 1 ∣ h − ) × P ( d 2 ∣ h − ) × . . . × P ( d n ∣ h − ) P(D|h_+) = P(d_1|h_+)×P(d_2|h_+)×...×P(d_n|h_+)\\ P(D|h_-) = P(d_1|h_-)×P(d_2|h_-)×...×P(d_n|h_-) P(Dh+)=P(d1h+)×P(d2h+)×...×P(dnh+)P(Dh)=P(d1h)×P(d2h)×...×P(dnh)

其中 d n d_n dn 代表邮件 D D D中的第 n n n 个单词。 P ( d n ∣ h + ) P(d_n|h_+) P(dnh+) 表示邮件 D D D中的第 n n n 个单词在垃圾邮件中出现的概率。所以有:

P ( d n ∣ h + ) = C ( d n ∣ h + ) C ( h + ) P ( d n ∣ h − ) = C ( d n ∣ h − ) C ( h − ) P(d_n|h_+)=\frac{C(d_n|h_+)}{C(h_+)} \\ P(d_n|h_-)=\frac{C(d_n|h_-)}{C(h_-)} P(dnh+)=C(h+)C(dnh+)P(dnh)=C(h)C(dnh)

其中 C ( d n ∣ h + ) C(d_n|h_+) C(dnh+) 表示邮件 D D D中的第 n n n 个单词在垃圾邮件中出现的次数; C ( d n ∣ h − ) C(d_n|h_-) C(dnh) 表示表示邮件 D D D中的第 n n n 个单词在正常邮件中出现的次数; C ( h + ) C(h_+) C(h+) 表示垃圾邮件中的总单词数, C ( h − ) C(h_-) C(h) 表示正常邮件中的总单词数。根据上式,可以推导出:

P ( D ∣ h + ) = C ( d 1 ∣ h + ) C ( h + ) × C ( d 2 ∣ h + ) C ( h + ) × . . . × C ( d n ∣ h + ) C ( h + ) P ( D ∣ h − ) = C ( d 1 ∣ h − ) C ( h − ) × C ( d 2 ∣ h − ) C ( h − ) × . . . × C ( d n ∣ h − ) C ( h − ) P(D|h_+) = \frac{C(d_1|h_+)}{C(h_+)}×\frac{C(d_2|h_+)}{C(h_+)}×...×\frac{C(d_n|h_+)}{C(h_+)}\\ P(D|h_-) = \frac{C(d_1|h_-)}{C(h_-)}×\frac{C(d_2|h_-)}{C(h_-)}×...×\frac{C(d_n|h_-)}{C(h_-)} P(Dh+)=C(h+)C(d1h+)×C(h+)C(d2h+)×...×C(h+)C(dnh+)P(Dh)=C(h)C(d1h)×C(h)C(d2h)×...×C(h)C(dnh)

下面的代码中,我并没有直接计算出 P ( D ∣ h + ) P(D|h_+) P(Dh+) P ( D ∣ h − ) P(D|h_-) P(Dh),而是计算出了 C ( d 1 ∣ h + ) C ( h + ) 、 C ( d 2 ∣ h + ) C ( h + ) 、 . . . 、 C ( d n ∣ h + ) C ( h + ) \frac{C(d_1|h_+)}{C(h_+)}、\frac{C(d_2|h_+)}{C(h_+)}、...、\frac{C(d_n|h_+)}{C(h_+)} C(h+)C(d1h+)C(h+)C(d2h+)...C(h+)C(dnh+) C ( d 1 ∣ h − ) C ( h − ) 、 C ( d 2 ∣ h − ) C ( h − ) 、 . . . 、 C ( d n ∣ h − ) C ( h − ) \frac{C(d_1|h_-)}{C(h_-)}、\frac{C(d_2|h_-)}{C(h_-)}、...、\frac{C(d_n|h_-)}{C(h_-)} C(h)C(d1h)C(h)C(d2h)...C(h)C(dnh),并把它们分别存在了两个列表中(p_spam_vecp_ham_vec

以计算 C ( d 1 ∣ h + ) C ( h + ) 、 C ( d 2 ∣ h + ) C ( h + ) 、 . . . 、 C ( d n ∣ h + ) C ( h + ) \frac{C(d_1|h_+)}{C(h_+)}、\frac{C(d_2|h_+)}{C(h_+)}、...、\frac{C(d_n|h_+)}{C(h_+)} C(h+)C(d1h+)C(h+)C(d2h+)...C(h+)C(dnh+) 为例,在计算它们的过程中用到了两个辅助变量 p_spam_moleculep_spam_denominator,分别代表 C ( d n ∣ h + ) C ( h + ) \frac{C(d_n|h_+)}{C(h_+)} C(h+)C(dnh+) 的分子和分母。

但是按照上面的公式,有一些时候会出问题。下面进行问题说明和解决方案的阐述:

问题一:假设某个单词没有出现在垃圾邮件中,意味着 C ( d n ∣ h + ) = 0 C(d_n|h_+)=0 C(dnh+)=0,即 P ( d n ∣ h + ) = 0 P(d_n|h_+)=0 P(dnh+)=0。又因为 P ( D ∣ h + ) = P ( d 1 ∣ h + ) × P ( d 2 ∣ h + ) × . . . × P ( d n ∣ h + ) P(D|h_+) = P(d_1|h_+)×P(d_2|h_+)×...×P(d_n|h_+) P(Dh+)=P(d1h+)×P(d2h+)×...×P(dnh+) 是连乘的,所以只要一个元素为0,那么最终 P ( D ∣ h + ) P(D|h_+) P(Dh+) 就为0。

问题二:假设训练集中没有垃圾邮件,意味着 C ( h + ) = 0 C(h_+)=0 C(h+)=0,即公式 P ( d n ∣ h + ) = C ( d n ∣ h + ) C ( h + ) P(d_n|h_+)=\frac{C(d_n|h_+)}{C(h_+)} P(dnh+)=C(h+)C(dnh+) 的分母为0,此时作除法是会引发异常的

为了解决上述两个问题,我们在下面的代码中做了平滑处理,又叫做拉普拉斯平滑

针对问题一:我们假设 C ( d n ∣ h + ) C(d_n|h_+) C(dnh+) 至少为 1,所以下面代码中用了 np.ones() 函数对分子进行初始化,而不是用 np.zeros()

针对问题二:我们假设 C ( h + ) C(h_+) C(h+) 至少为 m(m通常为该分类任务中的类别数量,例如在垃圾邮件分类中,只有垃圾和正常两种邮件,所以m为2)

以此类推,对于 C ( d n ∣ h − ) C(d_n|h_-) C(dnh) C ( h − ) C(h_-) C(h) 也是一样。

# 用朴素贝叶斯算法进行计算
def naive_bayes(train_matrix, train_class):
    # 样本个数
    train_data_size = len(train_class)
    # 语料库大小
    vocabulary_size = len(train_matrix[0])
    # 计算垃圾邮件的概率值
    p_spam = sum(train_class) / train_data_size
    # 初始化分子,做了一个平滑处理(拉普拉斯平滑)
    p_ham_molecule = np.ones(vocabulary_size)
    p_spam_molecule = np.ones(vocabulary_size)
    # 初始化分母(通常初始化为类别个数,在垃圾邮件分类中,只有垃圾和正常两种邮件,所以类别数为2)
    p_ham_denominator = 2
    p_spam_denominator = 2
    # 循环计算分子和分母
    for i in range(train_data_size):
        if train_class[i] == 1:
            p_spam_molecule += train_matrix[i]
            p_spam_denominator += sum(train_matrix[i])
        else:
            p_ham_molecule += train_matrix[i]
            p_ham_denominator += sum(train_matrix[i])
    # 计算概率
    p_ham_vec = p_ham_molecule / p_ham_denominator
    p_spam_vec = p_spam_molecule / p_spam_denominator
    # 返回
    return p_ham_vec, p_spam_vec, p_spam

5.6 贝叶斯公式的对数变换 + 邮件类别预测

回顾上面介绍过的公式:

P ( h + ∣ D ) = P ( h + ) × P ( D ∣ h + ) P ( h − ∣ D ) = P ( h − ) × P ( D ∣ h − ) P(h_+|D)=P(h_+)×P(D|h_+) \\ P(h_-|D)=P(h_-)×P(D|h_-) P(h+D)=P(h+)×P(Dh+)P(hD)=P(h)×P(Dh)

根据上一节的函数,我们已经能够求得 P ( h + ) 、 P ( D ∣ h + ) 、 P ( h − ) 、 P ( D ∣ h − ) P(h_+)、P(D|h_+)、P(h_-)、P(D|h_-) P(h+)P(Dh+)P(h)P(Dh)了。但是 P ( D ∣ h + ) = P ( d 1 ∣ h + ) × P ( d 2 ∣ h + ) × . . . × P ( d n ∣ h + ) P(D|h_+) = P(d_1|h_+)×P(d_2|h_+)×...×P(d_n|h_+) P(Dh+)=P(d1h+)×P(d2h+)×...×P(dnh+) 是连乘的,且每一项都是在 [0,1] 区间内的,这也就意味着经过连乘之后, P ( D ∣ h + ) P(D|h_+) P(Dh+) P ( D ∣ h − ) P(D|h_-) P(Dh) 的值往往非常小。这是不利于它们之间大小比较的。

所以我们通常采用取对数(通常是取自然对数 ln ⁡ \ln ln)的方式,将它们“放大”。下面是自然对数的图像:

在这里插入图片描述
取自然对数之后,公式变为:

ln ⁡ ( P ( h + ∣ D ) ) = ln ⁡ ( P ( h + ) × P ( D ∣ h + ) ) = ln ⁡ ( P ( h + ) ) + ln ⁡ ( P ( D ∣ h + ) ) ln ⁡ ( P ( h − ∣ D ) ) = ln ⁡ ( P ( h − ) × P ( D ∣ h − ) ) = ln ⁡ ( P ( h − ) ) + ln ⁡ ( P ( D ∣ h − ) ) \ln(P(h_+|D))=\ln(P(h_+)×P(D|h_+))=\ln(P(h_+))+\ln(P(D|h_+)) \\ \ln(P(h_-|D))=\ln(P(h_-)×P(D|h_-))=\ln(P(h_-))+\ln(P(D|h_-)) ln(P(h+D))=ln(P(h+)×P(Dh+))=ln(P(h+))+ln(P(Dh+))ln(P(hD))=ln(P(h)×P(Dh))=ln(P(h))+ln(P(Dh))

其中:

ln ⁡ ( P ( D ∣ h + ) ) = ln ⁡ ( P ( d 1 ∣ h + ) × P ( d 2 ∣ h + ) × . . . × P ( d n ∣ h + ) ) = ln ⁡ ( P ( d 1 ∣ h + ) ) + ln ⁡ ( P ( d 2 ∣ h + ) ) + . . . + ln ⁡ ( P ( d n ∣ h + ) ) ln ⁡ ( P ( D ∣ h − ) ) = ln ⁡ ( P ( d 1 ∣ h − ) × P ( d 2 ∣ h − ) × . . . × P ( d n ∣ h − ) ) = ln ⁡ ( P ( d 1 ∣ h − ) ) + ln ⁡ ( P ( d 2 ∣ h − ) ) + . . . + ln ⁡ ( P ( d n ∣ h − ) ) \ln(P(D|h_+))=\ln(P(d_1|h_+)×P(d_2|h_+)×...×P(d_n|h_+))=\ln(P(d_1|h_+))+\ln(P(d_2|h_+))+...+\ln(P(d_n|h_+)) \\ \ln(P(D|h_-))=\ln(P(d_1|h_-)×P(d_2|h_-)×...×P(d_n|h_-))=\ln(P(d_1|h_-))+\ln(P(d_2|h_-))+...+\ln(P(d_n|h_-)) ln(P(Dh+))=ln(P(d1h+)×P(d2h+)×...×P(dnh+))=ln(P(d1h+))+ln(P(d2h+))+...+ln(P(dnh+))ln(P(Dh))=ln(P(d1h)×P(d2h)×...×P(dnh))=ln(P(d1h))+ln(P(d2h))+...+ln(P(dnh))

综上可得:

ln ⁡ ( P ( h + ∣ D ) ) = ln ⁡ ( P ( h + ) ) + [ ln ⁡ ( P ( d 1 ∣ h + ) ) + ln ⁡ ( P ( d 2 ∣ h + ) ) + . . . + ln ⁡ ( P ( d n ∣ h + ) ) ] ln ⁡ ( P ( h − ∣ D ) ) = ln ⁡ ( P ( h − ) ) + [ ln ⁡ ( P ( d 1 ∣ h − ) ) + ln ⁡ ( P ( d 2 ∣ h − ) ) + . . . + ln ⁡ ( P ( d n ∣ h − ) ) ] \ln(P(h_+|D))=\ln(P(h_+))+[\ln(P(d_1|h_+))+\ln(P(d_2|h_+))+...+\ln(P(d_n|h_+))] \\ \ln(P(h_-|D))=\ln(P(h_-))+[\ln(P(d_1|h_-))+\ln(P(d_2|h_-))+...+\ln(P(d_n|h_-))] ln(P(h+D))=ln(P(h+))+[ln(P(d1h+))+ln(P(d2h+))+...+ln(P(dnh+))]ln(P(hD))=ln(P(h))+[ln(P(d1h))+ln(P(d2h))+...+ln(P(dnh))]

# 预测,返回预测的类别
def predict(vec, p_ham_vec, p_spam_vec, p_spam):
    # 由于计算出来的概率通常很接近0,所以我们通常取对数将它“放大”,这也基于我们在做贝叶斯的时候,不需要知道他们确切的概率,只需要比较他们概率大小即可
    p_spam = np.log(p_spam) + sum(vec * np.log(p_spam_vec))
    p_ham = np.log(1 - p_spam) + sum(vec * np.log(p_ham_vec))
    return 1 if p_spam >= p_ham else 0

5.7 完整代码 + 运行输出

完整代码

import numpy as np
import re
import random


# 数据预处理操作(词的切分、词转化为小写)
def text_parse(input_str):
    word_list = re.split(r"\W+", input_str)
    return [word.lower() for word in word_list if len(word_list) > 2 and len(word) > 0]


# 获取数据
def read_data():
    doc_list = []
    class_list = []
    with open("./data/SMS.txt", "r", encoding="utf-8") as file:
        datas = file.read()
        # print(data)
        datas = datas.split("\n")
        for data in datas:
            # label = ham 代表 正常邮件 , label = spam 代表垃圾邮件
            label, text = data.split("\t")
            doc_list.append(text_parse(text))
            # 0:正常邮件,1:垃圾邮件
            class_list.append(0 if label == "ham" else 1)
    return doc_list, class_list


# 构建语料表
def create_vocabulary_list(doc_list):
    vocabulary_set = set([])
    for document in doc_list:
        vocabulary_set = vocabulary_set | set(document)
    return list(vocabulary_set)


# 将一篇邮件转化为 类似 One-Hot 的向量,长度和 vocabulary_list 一样,为 1 的位置代表该单词在该邮件中出现了
def set_of_word2vector(vocabulary_list, document):
    vec = [0 for _ in range(len(vocabulary_list))]
    for word in document:
        index = vocabulary_list.index(word)
        if index >= 0:
            vec[index] = 1
    return vec


# 用朴素贝叶斯算法进行计算
def naive_bayes(train_matrix, train_class):
    # 样本个数
    train_data_size = len(train_class)
    # 语料库大小
    vocabulary_size = len(train_matrix[0])
    # 计算垃圾邮件的概率值
    p_spam = sum(train_class) / train_data_size
    # 初始化分子,做了一个平滑处理(拉普拉斯平滑)
    p_ham_molecule = np.ones(vocabulary_size)
    p_spam_molecule = np.ones(vocabulary_size)
    # 初始化分母(通常初始化为类别个数,在垃圾邮件分类中,只有垃圾和正常两种邮件,所以类别数为2)
    p_ham_denominator = 2
    p_spam_denominator = 2
    # 循环计算分子和分母
    for i in range(train_data_size):
        if train_class[i] == 1:
            p_spam_molecule += train_matrix[i]
            p_spam_denominator += sum(train_matrix[i])
        else:
            p_ham_molecule += train_matrix[i]
            p_ham_denominator += sum(train_matrix[i])
    # 计算概率
    p_ham_vec = p_ham_molecule / p_ham_denominator
    p_spam_vec = p_spam_molecule / p_spam_denominator
    # 返回
    return p_ham_vec, p_spam_vec, p_spam


# 预测,返回预测的类别
def predict(vec, p_ham_vec, p_spam_vec, p_spam):
    # 由于计算出来的概率通常很接近0,所以我们通常取对数将它“放大”,这也基于我们在做贝叶斯的时候,不需要知道他们确切的概率,只需要比较他们概率大小即可
    p_spam = np.log(p_spam) + sum(vec * np.log(p_spam_vec))
    p_ham = np.log(1 - p_spam) + sum(vec * np.log(p_ham_vec))
    return 1 if p_spam >= p_ham else 0


if __name__ == '__main__':
    # 读取数据
    doc_list, class_list = read_data()
    print(f"A total of {len(class_list)} email data were read, including {sum(class_list)} spam")

    # 构建语料表
    vocabulary_list = create_vocabulary_list(doc_list)

    # 划分训练集和测试集
    test_ratio = 0.3
    train_index_set = [i for i in range(len(doc_list))]
    test_index_set = []
    for _ in range(int(len(doc_list) * test_ratio)):
        index = random.randint(0, len(train_index_set) - 1)
        test_index_set.append(train_index_set[index])
        del train_index_set[index]
    print(f"test_ratio: {test_ratio} , train_data_size: {len(train_index_set)} , test_data_size: {len(test_index_set)}")

    # 将邮件转化为向量
    train_matrix = []
    train_class = []
    for train_index in train_index_set:
        train_matrix.append(set_of_word2vector(vocabulary_list, doc_list[train_index]))
        train_class.append(class_list[train_index])

    # 用朴素贝叶斯算法进行计算
    p_ham_vec, p_spam_vec, p_spam = naive_bayes(np.array(train_matrix), np.array(train_class))

    # 测试部分
    # 记录预测正确的数量
    acc_cnt = 0
    for test_index in test_index_set:
        vec = set_of_word2vector(vocabulary_list, doc_list[test_index])
        predict_class = predict(vec, p_ham_vec, p_spam_vec, p_spam)
        if predict_class == class_list[test_index]:
            acc_cnt += 1
    print(f"test accuracy: {acc_cnt / len(test_index_set)}")

运行输出

A total of 5574 email data were read, including 747 spam
test_ratio: 0.3 , train_data_size: 3902 , test_data_size: 1672
test accuracy: 0.9826555023923444

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149044.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

web服务器的相关配置

一:简单介绍web服务 1、什么是www www是world wide web的缩写,也就是全球信息广播的意思。通常说的上网就是使用www来查询用户所需要的信息。www可以结合文字、图形、影像以及声音等多媒体,并通过可以让鼠标单击超链接的方式将信息以Interne…

数字化时代,企业如何培养员工的数据思维意识

很多的企业,他们都有意识想通过大数据或者商业智能BI来推动一些数据信息化的项目建设,比如企业的财务分析、经营管理分析、供应链分析、运营、市场分析等等,这都是很好的表现。 但在实际跟进和深入沟通的时候就发现很多分析的想法是没有办法…

OpenCV(9)-图像轮廓

图像轮廓 图像轮廓概述 图像轮廓是具有相同颜色或强度的连续点的曲线 作用: 可以用于图形分析物体的识别与检测 注意: 为了检测的准确性,需要先对图像进行二值化或Canny操作画轮廓时会修改输入的图像 轮廓查找API: findConto…

FastDFS简介/架构/安装/代码

1简介/架构 1.1简单介绍 分布式文件系统,看名字就知道,它可以存储在不同的机器上。肯定也要有路由功能。 特点 备份容错,负载均衡,动态扩容 场景 0-500MB之间大小的文件存储(图片,短视频,文…

LeetCode[703]数据流中的第K大元素

难度:简单题目:设计一个找到数据流中第 k大元素的类(class)。注意是排序后的第 k大元素,不是第 k个不同的元素。描述:请实现 KthLargest类:KthLargest(int k, int[] nums) 使用整数 k 和整数流 …

【北邮果园大三上】数据挖掘

数据挖掘 大数据 定义: 体积时效性种类值 数据挖掘模型 1.归纳已知 2.预测未来 1. 数据的质量处理和度量方法 1.1数据 1.1.1数据属性 属性类型: 标称(nominal)序数(ordinal)区间(interval)比率(ratio) 非对称的属性(asymmetric attribute): 对于非对称的…

airserver怎么下载使用?IOS设备无线传送到Mac电脑的屏幕上教程

AirSever是一种通用的镜像接收器,非常适合镜像iPhone或Android视频文件。 它可以将简单的大屏幕或投影仪变成通用的屏幕镜像接收器。 使用启用了AirSever的设备,您可以用户拥有iPhone,iPad,Mac,Android,Nex…

Proxy详解

Proxy Proxy(代理),首先理解代理是什么意思,才能更便于了解Proxy的作用。 Proxy是一个代理,可以这么理解,就相当于它是一个快递代理点,快递会运输到该代理点,我们取件只需要去对应的代理点取件…

2023年宝塔面板快速部署Rocket.Chat,安卓,PC,ios端使用

官方网站快速部署代码:Docker & Docker Compose - Rocket.Chat Docs 环境:centos 服务器必须大于2核2G,否则docker占用资源太多会起不起来 安装宝塔面板 一 . 宝塔面板安装docker 当然也可以手动安装:如果您还没有,请确保您已安装并运行Docker和Docker-comp…

Docker 数据卷

问题:通过镜像创建一个容器。容器一旦被销毁,则容器内的数据将一并被删除。但有些情况下,通过服务器上传的图片出会丢失。容器中的数据不是持久化状态的。 那有没有一种独立于容器、提供持久化并能服务于多个容器的东西呢? 什么是…

SpringCloud组件之Gateway网关详细教程

目录 一:概念 1.1:什么是微服务网关? 1.2: 为什么需要使用网关,网关的作用? 1.3网关的好处? 二:Gateway网关的快速入门 1.快速创建 2.网关路由的流程 3:Gateway断言工厂 &…

Java复习—运算符

运算符 运算符:对字面量或者变量进行操作的符号 表达式:用运算符把字面量或者变量连接起来,符合Java语法的式子就可以称为表达式。 算数运算符 符号作用加法作用-减法作用*乘法作用/除法法作用%取模、取余 在代码中,如果有小数…

Qt OpenGL(05)标准化设备坐标(NDC)

文章目录OpenGL中的坐标简介标准化设备坐标标准化设备坐标绘制 x y z 三个轴线完整代码顶点着色器片段着色器Widget.hWidget.cpp总结OpenGL中的坐标简介 OpenGL 基于绘制流水线模型,而且绘制流水线的第一个步骤是对顶点进行一 系列的操作, 其中大部分属于几何操作。…

3-2内存管理-虚拟内存

文章目录一.虚拟内存的基本概念二.请求分页管理方式(一)页表机制(二)缺页中断机构(三)地址变换机构三.页面置换算法(一)最佳置换算法OPT(二)先进先出页面置换…

flowable 简介

flowable 简介目录概述需求:设计思路实现思路分析1.管理2.二、初识Flowable五大引擎3.2.通过编写程序的方式来构造ProcessEngineConfiguration对象4.流程引擎API架构图5.flowable 表结构说明参考资料和推荐阅读Survive by day and develop by night. talk for impor…

Leetcode_单周赛_327

6283. 正整数和负整数的最大计数 代码 直接遍历统计即可 class Solution {public int maximumCount(int[] nums) {int a 0, b 0;for (int i 0; i < nums.length; i) {if (nums[i] > 0) a;else if (nums[i] < 0) b;}return Math.max(a, b);} }6285. 执行 K 次操作…

Python代码实现:坐标轮换法求解多维最优化问题

文章目录多维最优化问题坐标轮换法原理代码实现坐标轮换法坐标轮换法优缺点多维最优化问题 此前介绍的黄金分割法和切线法都是针对一维最优化问题的解决方案。本文开始&#xff0c;我们将最优化问题从一维扩展到多维&#xff0c;暂时仍考虑无约束的优化场景。 坐标轮换法原理…

ArrayList | 简单的洗牌算法

一个洗牌程序需要包含&#xff1a; 创建一副扑克牌&#xff08;除去大小王剩下52张&#xff0c;每种花色13张&#xff09;。洗牌&#xff0c;打乱牌的顺序。揭牌&#xff0c;每位玩家轮流揭牌&#xff0c;从洗完后的牌组中获得自己的牌。因此&#xff0c;我们可以依照以下思路来…

R 语言 4.2.2安装 WGCNA

文章目录1 WGCNA库介绍2 安装踩坑还得是官方文档这样安装我出现的问题参考AppendixA. 安装RB. 配置环境C. 修改镜像1 WGCNA库介绍 WGCNA是用于加权相关网络分析的R包&#xff0c; 相关网络越来越多地用于生物信息学应用 加权基因共表达网络分析是一种系统生物学方法&#xff0…

按键控制电源通断,实现各种设备/电脑开关机低功耗IC

前言 今天记录一下一些硬件开关电的低功耗控制ic&#xff0c;代替物理机械开关&#xff0c;后续有新的更好用的芯片会继续更新此博。 环境 every machine 参考文档 正文 一版我们选择ic&#xff0c;除了功能之外还要看一些性能&#xff0c;这里我暂时录入的功能就是一个按…