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

源代码文件请点击此处

条件概率的定义和公式

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

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

  • 乘法定理:

P ( A B ) = P ( A ) P ( B ∣ A ) P(AB) = P(A) P(B|A) P(AB)=P(A)P(BA)

  • 全概率公式:

P ( B ) = P ( A 1 ) P ( B ∣ A 1 ) + P ( A 2 ) P ( B ∣ A 2 ) + . . . + P ( A n ) P ( B ∣ A n ) = ∑ i = 1 n P ( A i ) P ( B ∣ A i ) \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} P(B)=P(A1)P(BA1)+P(A2)P(BA2)+...+P(An)P(BAn)=i=1nP(Ai)P(BAi)

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

P ( A i ∣ B ) = P ( A i B ) P ( B ) = 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 ) = P ( A i ) P ( B ∣ A i ) ∑ i = 1 n P ( A i ) P ( B ∣ A i ) \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} P(AiB)=P(B)P(AiB)=P(A1)P(BA1)+P(A2)P(BA2)+...+P(An)P(BAn)P(Ai)P(BAi)=i=1nP(Ai)P(BAi)P(Ai)P(BAi)

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

P ( A B ) = P ( A ) P ( B ) P ( A ∣ B ) = P ( A ∣ B ‾ ) = P ( A ) P ( B ∣ A ) = P ( B ∣ A ‾ ) = P ( 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(AB)=P(A)P(B)P(AB)=P(AB)=P(A)P(BA)=P(BA)=P(B)

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

先验概率和后验概率

定义:

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

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

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

让我们再看以下例子。

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

【解】概率树如下:

0.005
0.995
0.95
0.05
0.05
0.95
所有患者
患有癌症
没有癌症
试验反应为阳性
试验反应为阴性
试验反应为阳性
试验反应为阴性

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

P ( A ) = 0.005 , P ( A ‾ ) = 0.995 P ( B ∣ A ) = 0.95 , P ( B ‾ ∣ A ‾ ) = 0.05 P ( B ∣ A ‾ ) = 1 − P ( B ‾ ∣ A ‾ ) = 0.95 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 P(A)=0.005,P(A)=0.995P(BA)=0.95,P(BA)=0.05P(BA)=1P(BA)=0.95

由贝叶斯公式得:

P ( A ∣ B ) = P ( A B ) P ( B ) = P ( A ) P ( B ∣ A ) P ( A ) P ( B ∣ A ) + P ( A ‾ ) P ( B ∣ A ‾ ) = 0.087 \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} P(AB)=P(B)P(AB)=P(A)P(BA)+P(A)P(BA)P(A)P(BA)=0.087

通过以上例子可知:

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

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

设电子邮件有 n n n 个单词 x 1 , x 2 , . . . , x n x_1, x_2, ..., x_n x1,x2,...,xn,则邮件是垃圾邮件的概率为:

P ( 垃圾邮件 ∣ x 1 , x 2 , . . . , x n ) = 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) = \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(垃圾邮件x1,x2,...,xn)=P(x1,x2,...,xn垃圾邮件)P(垃圾邮件)+P(x1,x2,...,xn非垃圾邮件)P(非垃圾邮件)P(x1,x2,...,xn垃圾邮件)P(垃圾邮件)

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

P ( x 1 , x 2 , . . . , x n ∣ 垃圾邮件 ) = P ( x 1 ∣ 垃圾邮件 ) P ( x 2 ∣ 垃圾邮件 ) . . . P ( x n ∣ 垃圾邮件 ) = ∏ i n P ( x i ∣ 垃圾邮件 ) P ( x 1 , x 2 , . . . , x n ∣ 非垃圾邮件 ) = P ( x 1 ∣ 非垃圾邮件 ) P ( x 2 ∣ 非垃圾邮件 ) . . . P ( x n ∣ 非垃圾邮件 ) = ∏ 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_1, x_2, ..., x_n | 非垃圾邮件) = P(x_1 | 非垃圾邮件) P(x_2 | 非垃圾邮件) ... P(x_n | 非垃圾邮件) = \prod_i^n P(x_i | 非垃圾邮件) P(x1,x2,...,xn垃圾邮件)=P(x1垃圾邮件)P(x2垃圾邮件)...P(xn垃圾邮件)=inP(xi垃圾邮件)P(x1,x2,...,xn非垃圾邮件)=P(x1非垃圾邮件)P(x2非垃圾邮件)...P(xn非垃圾邮件)=inP(xi非垃圾邮件)

根据条件概率公式有:

P ( x i ∣ 垃圾邮件 ) = P ( 出现 x i 的垃圾邮件 ) P ( 垃圾邮件 ) = 出现 x i 的垃圾邮件数 电子邮件总数 ⋅ 电子邮件总数 垃圾邮件总数 = 出现 x i 的垃圾邮件数 垃圾邮件总数 同理, P ( x i ∣ 非垃圾邮件 ) = 出现 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(xi垃圾邮件)=P(垃圾邮件)P(出现xi的垃圾邮件)=电子邮件总数出现xi的垃圾邮件数垃圾邮件总数电子邮件总数=垃圾邮件总数出现xi的垃圾邮件数同理,P(xi非垃圾邮件)=非垃圾邮件总数出现xi的非垃圾邮件数

所以:

P ( 垃圾邮件 ∣ x 1 , x 2 , . . . , x n ) = 垃圾邮件总数 电子邮件总数 ⋅ ∏ i n 出现 x i 的垃圾邮件数 垃圾邮件总数 垃圾邮件总数 电子邮件总数 ⋅ ∏ i n 出现 x i 的垃圾邮件数 垃圾邮件总数 + 垃圾邮件总数 电子邮件总数 ⋅ ∏ i n 出现 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(垃圾邮件x1,x2,...,xn)=电子邮件总数垃圾邮件总数in垃圾邮件总数出现xi的垃圾邮件数+电子邮件总数垃圾邮件总数in非垃圾邮件总数出现xi的非垃圾邮件数电子邮件总数垃圾邮件总数in垃圾邮件总数出现xi的垃圾邮件数

化简得:

P ( 垃圾邮件 ∣ x 1 , x 2 , . . . , x n ) = 垃圾邮件总数 ⋅ ∏ i n 出现 x i 的垃圾邮件数 垃圾邮件总数 垃圾邮件总数 ⋅ ∏ i n 出现 x i 的垃圾邮件数 垃圾邮件总数 + 非垃圾邮件总数 ⋅ ∏ i n 出现 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 的非垃圾邮件数}{非垃圾邮件总数}} P(垃圾邮件x1,x2,...,xn)=垃圾邮件总数in垃圾邮件总数出现xi的垃圾邮件数+非垃圾邮件总数in非垃圾邮件总数出现xi的非垃圾邮件数垃圾邮件总数in垃圾邮件总数出现xi的垃圾邮件数

代码如下:

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)
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值