G
N
I
D
A
O
L

【机器学习】朴素贝叶斯分类器

源代码文件请点击此处

条件概率的定义和公式

  • 条件概率:事件 \(B\) 已发生条件下事件 \(A\) 发生的概率,记为 \(P(A|B)\),即

\[P(A|B) = \frac{P(AB)}{P(B)} \]

  • 乘法定理:

\[P(AB) = P(A) P(B|A) \]

  • 全概率公式:

\[\begin{aligned} P(B) &= P(A_1)P(B|A_1) + P(A_2)P(B|A_2) + ... + P(A_n)P(B|A_n) \\ &= \sum_{i=1}^{n} P(A_i)P(B|A_i) \end{aligned} \]

  • 贝叶斯(Bayes)公式(逆概率公式):

\[\begin{aligned} P(A_i|B) &= \frac{P(A_iB)}{P(B)} \\ &= \frac{P(A_i)P(B|A_i)}{P(A_1)P(B|A_1) + P(A_2)P(B|A_2) + ... + P(A_n)P(B|A_n)} \\ &= \frac{P(A_i)P(B|A_i)}{\sum_{i=1}^{n} P(A_i)P(B|A_i)} \end{aligned} \]

  • 独立概率的乘法规则:事件 \(A\) 和事件 \(B\) 必须相互独立,即一个事件的发生不会影响另一个事件的发生,此时有

\[P(AB) = P(A) P(B) \\ P(A|B) = P(A|\overline{B}) = P(A) \\ P(B|A) = P(B|\overline{A}) = P(B) \]

  • 一些小技巧:
    • \(P(A) = P(A|B) + P(A|\overline{B})\)
    • \(P(AB|C) = P(A|C) P(B|C)\)

先验概率和后验概率

定义:

  • 先验概率:开始时,我们手中没有任何信息,只能计算一个初始概率。
  • 事件:新的事件发生,有了新的信息。
  • 后验概率:根据这些新信息和先验概率可以得到更好的概率估计。

例如,我们想得知今天下雨的概率:

  • 先验概率:开始时,我们手中没有任何信息,只能粗略估计今天下雨的概率为 20%。
  • 事件:我们在亚马逊雨林中。
  • 后验概率:因此,今天下雨的概率被修正为 70%。

让我们再看以下例子。

【例】某种诊断癌症的试验具有如下效果:被诊断者有癌症,试验反应为阳性的概率为0.95;被诊断者没有癌症,试验反应为阴性的概率为 0.95。现对自然人群进行普查,设被试验的人群中患有癌症的概率为0.005,求:已知试验反应为阳性,该被诊断者确有癌症的概率。

【解】概率树如下:

graph LR A[所有患者] --0.005--> B[患有癌症] A[所有患者] --0.995--> C[没有癌症] B[患有癌症] --0.95--> D[试验反应为阳性] B[患有癌症] --0.05--> E[试验反应为阴性] C[没有癌症] --0.05--> F[试验反应为阳性] C[没有癌症] --0.95--> G[试验反应为阴性]

\(A\) 表示“患有癌症”,\(\overline{A}\) 表示“患有癌症”;\(B\) 表示“试验反应为阳性”,\(\overline{B}\) 表示“试验反应为阴性”。由题意得

\[P(A) =0.005, P(\overline{A}) = 0.995 \\ P(B|A) = 0.95, P(\overline{B}|\overline{A}) = 0.05 \\ P(B|\overline{A}) = 1 - P(\overline{B}|\overline{A}) = 0.95 \]

由贝叶斯公式得:

\[\begin{aligned} P(A|B) &= \frac{P(AB)}{P(B)} \\ &= \frac{P(A)P(B|A)}{P(A)P(B|A) + P(\overline{A})P(B|\overline{A})} \\ &= 0.087 \end{aligned} \]

通过以上例子可知:

  • 先验概率:根据对自然人群的普查,被试验的人群中患有癌症的概率为0.005(同样,没有癌症的概率为0.995)。
  • 事件:患者接受了试验,且结果为阳性。
  • 后验概率:在得到试验反应为阳性后,该被诊断者确有癌症的概率被修正为 0.087。

使用朴素贝叶斯(Naive Bayes)算法检测垃圾邮件

设电子邮件有 \(n\) 个单词 \(x_1, x_2, ..., x_n\),则邮件是垃圾邮件的概率为:

\[P(垃圾邮件 | x_1, x_2, ..., x_n) = \frac{P(x_1, x_2, ..., x_n | 垃圾邮件) P(垃圾邮件)}{P(x_1, x_2, ..., x_n | 垃圾邮件) P(垃圾邮件) + P(x_1, x_2, ..., x_n | 非垃圾邮件) P(非垃圾邮件)} \]

假设所有单词的出现都是独立的,则有:

\[P(x_1, x_2, ..., x_n | 垃圾邮件) = P(x_1 | 垃圾邮件) P(x_2 | 垃圾邮件) ... P(x_n | 垃圾邮件) = \prod_i^n P(x_i | 垃圾邮件) \\ P(x_1, x_2, ..., x_n | 非垃圾邮件) = P(x_1 | 非垃圾邮件) P(x_2 | 非垃圾邮件) ... P(x_n | 非垃圾邮件) = \prod_i^n P(x_i | 非垃圾邮件) \]

根据条件概率公式有:

\[P(x_i | 垃圾邮件) = \frac{P(出现 x_i 的垃圾邮件)}{P(垃圾邮件)} = \frac{出现 x_i 的垃圾邮件数}{电子邮件总数} \cdot \frac{电子邮件总数}{垃圾邮件总数} = \frac{出现 x_i 的垃圾邮件数}{垃圾邮件总数} \\ 同理,P(x_i | 非垃圾邮件) = \frac{出现 x_i 的非垃圾邮件数}{非垃圾邮件总数} \\ \]

所以:

\[P(垃圾邮件 | x_1, x_2, ..., x_n) = \frac{\frac{垃圾邮件总数}{电子邮件总数} \cdot \prod_i^n \frac{出现 x_i 的垃圾邮件数}{垃圾邮件总数} }{\frac{垃圾邮件总数}{电子邮件总数} \cdot \prod_i^n \frac{出现 x_i 的垃圾邮件数}{垃圾邮件总数} + \frac{垃圾邮件总数}{电子邮件总数} \cdot \prod_i^n \frac{出现 x_i 的非垃圾邮件数}{非垃圾邮件总数}} \]

化简得:

\[P(垃圾邮件 | x_1, x_2, ..., x_n) = \frac{垃圾邮件总数 \cdot \prod_i^n \frac{出现 x_i 的垃圾邮件数}{垃圾邮件总数} }{垃圾邮件总数 \cdot \prod_i^n \frac{出现 x_i 的垃圾邮件数}{垃圾邮件总数} + 非垃圾邮件总数 \cdot \prod_i^n \frac{出现 x_i 的非垃圾邮件数}{非垃圾邮件总数}} \]

代码如下:

import pandas as pd
import numpy as np
import pickle
import os

pkl_dir = os.path.dirname(os.path.abspath(__file__))
pkl_file = 'emails_words_count.pkl'

'''
该函数用于将邮件中所有单词变为小写,并将出现过的单词转换为列表
输出如下所示:左边为邮件文本,右边为出现过的单词的列表
 text  ...                                              words
0  Subject: naturally irresistible your corporate...  ...  [love, are, 100, even, isguite, %, to, and, yo...
1  Subject: the stock trading gunslinger  fanny i...  ...  [group, penultimate, tanzania, bedtime, edt, s...
2  Subject: unbelievable new homes made easy  im ...  ...  [complete, loan, rate, this, subject:, 3, adva...
'''
def process_email(text):
    text = text.lower()
    return list(set(text.split()))


'''
【建立模型】记录每个单词分别在垃圾邮件和非垃圾邮件中的出现次数
输入:
  - emails:电子邮件数据集
输出:
  - model:二维数组,大小为 (每个邮件的不重复单词个数, 2)
    - [word]['spam']:记录该单词出现在垃圾邮件中的个数
    - [word]['ham']:记录该单词出现在非垃圾邮件中的个数
'''
def words_count(emails):
    pkl_path = pkl_dir + '/' + pkl_file
    if os.path.exists(pkl_path):
        with open(pkl_path, 'rb') as f:
            model = pickle.load(f)
        return model

    model = {} # 该字典记录了每个单词分别在垃圾邮件和非垃圾邮件中的出现次数

    for index, email in emails.iterrows(): # 遍历所有的邮件
        for word in email['words']: # 检测该邮件中的每一个单词
            if word not in model:  # 如果字典中没有这个单词
                model[word] = {'spam': 1, 'ham': 1}  # 初始化为 1,防止后面计算概率时除数为零
            else:                  # 如果字典中已有这个单词
                if email['spam'] == 1:  # 如果该邮件是垃圾邮件
                    model[word]['spam'] += 1
                else:               # 如果该邮件是非垃圾邮件
                    model[word]['ham'] += 1

    with open(pkl_path, 'wb') as f:
        pickle.dump(model, f)

    return model


'''
【推理预测】预测指定邮件是否为垃圾邮件
输入:
  - email:待预测的电子邮件文本
  - model:
  - num_spam:数据集中垃圾邮件的总数
  - num_ham:数据集中非垃圾邮件的总数
'''
def predict_naive_bayes(email, model, num_spam, num_ham):
    email = email.lower() # 待预测邮件文本全部小写化
    words = set(email.split()) # 生成单词列表

    # 计算邮件的总数
    # total = num_ham + num_spam

    # 计算概率
    spams, hams = [1.0], [1.0]
    for word in words:
        if word in model:
            spams.append(model[word]['spam'] / num_spam)
            hams.append(model[word]['ham'] / num_ham)
        # np.prod():计算数组中所有元素的乘积
        prod_spams = np.prod(spams) * num_spam
        prod_hams = np.prod(hams) * num_ham

    return prod_spams / (prod_hams + prod_spams)

# ============================= 主程序 main ===================================
if __name__ == '__main__':
    emails = pd.read_csv('emails.csv')
    emails['words'] = emails['text'].apply(process_email)

    num_emails = len(emails)
    num_spam = sum(emails['spam'])
    num_ham = num_emails - num_spam

    print("邮件总数:", num_emails)
    print("垃圾邮件数:", num_spam)
    print("邮件是垃圾邮件的先验概率:", num_spam / num_emails) # 计算先验概率
    print()

    # 记录每个单词分别在垃圾邮件和非垃圾邮件中的出现次数
    model = words_count(emails)

    # 出现单词 lottery 的邮件是垃圾邮件的概率?
    sum = model['lottery']['spam'] + model['lottery']['ham']
    print("单词 lottery 出现在邮件的次数:", model['lottery'])
    print("出现单词 lottery 的邮件是垃圾邮件的概率:", model['lottery']['spam'] / sum)

    # 出现单词 sale 的邮件是垃圾邮件的概率?
    sum = model['sale']['spam'] + model['sale']['ham']
    print("单词 sale 出现在邮件的次数:", model['sale'])
    print("出现单词 sale 的邮件是垃圾邮件的概率:", model['sale']['spam'] / sum)

    email1 = "Hi mom how are you"
    email2 = "buy cheap lottery easy money now"
    email3 = "meet me at the lobby of the hotel at nine am"
    email4 = "asdfgh"  # 不包含字典中任何一个单词,其为垃圾邮件的概率等于先验概率
    email5 = "As the sun rose, I packed my bags with essentials like a camera, a water bottle, and a few snacks. Excited and ready for the adventure, I hopped into my car and set the GPS to Laojun Mountain. The drive was a pleasant one, with lush green fields and rolling hills passing by the window."

    result = predict_naive_bayes(email5, model, num_spam, num_ham)
    print(result)
posted @ 2024-06-06 11:04  漫舞八月(Mount256)  阅读(6)  评论(0编辑  收藏  举报