朴素贝叶斯(Naive Bayes) | 算法实现

本文从朴素贝叶斯原理出发,详细介绍如何构建一个简易的垃圾邮件过滤器。通过分析邮件中的词语,利用朴素贝叶斯算法进行训练,最终实现对个人邮箱中垃圾邮件的有效过滤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

01 起

大数据时代,我们的“隐私”早已不再是隐私,一个特别直接的证据是什么呢?

我们的邮箱也好、手机也好,经常收到恼人的垃圾邮件、垃圾短信

被这些东西烦的不行,怎么办呢?网上有很多垃圾邮件过滤软件,可以拿来直接用的,其中的原理是什么呢?

今天我们自己造个轮子来过滤邮箱里的垃圾邮件吧!

系好安全带,我要开车了!


02 过滤原理

垃圾邮件过滤的原理其实很简单:朴素贝叶斯(Naive Bayes)

NB的详细原理我们在这一文中讲过:统计学习方法 | 朴素贝叶斯法,大家可以点击学习

简单点,利用朴素贝叶斯原理过滤垃圾邮件,内在逻辑是这样的:

  • 朴素贝叶斯公式:P(Y|X)=P(X|Y)*P(Y)/P(X)

  • 我们把Y看作邮件分类结果(0不是垃圾邮件、1是垃圾邮件),X看作邮件中的各个词语

  • 于是,P(Y)表示训练集中各类邮件出现的概率**(条件概率)**(大数定理,即各类邮件出现次数/总邮件数),若训练样本较少,大数定理不适用,本文旨在深刻理解NB原理,因此即使本文中训练样本较少,但仍然使用了大数定理。

  • P(X)表示邮件某个词语出现的概率,P(X|Y)表示某类邮件中词语X出现的概率**(先验概率)**

  • P(Y|X)表示邮件包含X词语时,该邮件为Y类的概率**(后验概率)**,是垃圾邮件过滤的结果,我们会将测试样本X归类到使得P(Y|X)最大的那个Y类

这就是利用NB进行垃圾邮件分类的原理


03 算法实现

知道了过滤原理,我们就来写个算法过滤垃圾邮件吧!

本次邮件过滤使用的样本为我qq邮箱中的74条邮件,人工标注了是否为垃圾邮件,其中有26条垃圾邮件,样本数据长这样

可以看到,样本数据中有较多符号等非文字内容,于是我们需要对样本进行清洗,总得来说,造轮子主要是一下几个步骤:

  1. 读取样本数据(自己qq邮箱74条邮件)
  2. 数据清洗(去除样本中的符号等非文字内容)
  3. 数据处理(训练集测试集分割、文本向量化)
  4. 调用sklearn中的NB模型训练,评估,优化模型(本文重点不在此,略去)
  5. 自己写个NB模型分辨垃圾邮件

数据清洗

刚才我们已经读取了样本数据,下面我们进行数据清洗,清洗思路如下:

  1. 去除待处理文本每条文本中的符号、数字、字母,仅剩中文字符(即以符号、数字、字母为分隔符,隔开每条文本作文item,构成一个list)
  2. 去除文本list中长度小于等于1的item
  3. 将文本list(text[i])每个item分词(可加载自定义词典),并放入新的对应index的文本list中(text_word[i])
  4. 将新的文本list(text_word[i])用空格连接起来并返回

基于此思路,给出以下清洗函数

def text_format():
    import jieba
    import re
    import pandas as pd
    
    print ("待处理文本格式要求:utf-8编码格式,仅包含待处理文本,每行为一条文本")
    text_path=input("请输入待清洗文本路径+名字:")
    
    #加载用户自定义词典用于分词
    userdict_path=input("请输入自定义分词词典路径+名字(可不输入):")
    if userdict_path !="":
        jieba.load_userdict(userdict_path)
        
    #根据用户输入地址,读取文件
    with open(text_path,"r",encoding="utf-8") as file:
        text=file.readlines()
    for i in range(len(text)):
        text[i]=text[i].strip()
    
    #定义一个空列表,用于存放分词后的文本,长度和text一致
    text_word=[[] for i in range(len(text))]
    
    splitter=re.compile(r"\W+|\d+|[a-z]+") #正则匹配,去除文本中的符号、数字、字母等非中文字符的元素
    for i in range(len(text)):
        text[i]=splitter.split(text[i].lower())
        text[i]=[word for word in text[i] if len(word)>1] #每条文本已经被分为一段一段的句子,每条文本此时是一个list,先去除其中字段长度小于等于1的单词
        for word in text[i]:
            text_word[i].extend(jieba.lcut(word))
        text_word[i]=" ".join(text_word[i]) #为了便于TfidfVectorizer等文本向量化处理,将每条标题用元素用空格连起来
    
    return text_word

清洗前后效果如下
清洗前

清洗后

数据处理

接下来就是数据处理,包括以下步骤:

  1. 将每条邮件对应的标签提取出来成为一个list
  2. 利用train_test_split将清洗后的数据与其对应的标签切分为训练集、测试集(这个分割函数也可以自己写,本文略去)
  3. 构成词袋模型,记录各个词出现的频率
    • CountVectorizer(stop_words=).fit_transform(训练集)
    • CountVectorizer(stop_words=).transform(测试集)
#建立训练集、测试集
label=emails.type.tolist()
X_train,X_test,Y_train,Y_test=train_test_split(emails_format,label,test_size=0.15,random_state=5)
#加载并处理停用词典
with open(r"E:\python\data\stopwords.txt","r",encoding="utf-8") as file:
    stop_words=file.readlines()
for i in range(len(stop_words)):
    stop_words[i]=stop_words[i].strip("\n")
    
#构成词袋模型,记录各个词出现的频率
cv=CountVectorizer(stop_words=stop_words)
X_train_count=cv.fit_transform(X_train)
X_test_count=cv.transform(X_test)

处理后的结果如下,可以看到,训练集62条邮件、测试集12条邮件,共提取出386个文本特征

朴素贝叶斯算法实现

接下来就是重头戏了,自定义NB模型,思路如下:

  1. 利用各词出现的频率和对应标签概率,训练NB模型各概率参数
  2. 将测试集各特征在训练集对应的先验概率带入计算,得到测试集各样本后验概率,取后验概率最大的标签类别为该测试样本类别
1. 利用训练集,训练概率参数(拉普拉斯平滑)[类似mnb.fit()]
#先将训练集的内容和标签合为一个dataframe
d={"content":X_train_count.toarray().tolist(),"label":Y_train}
emails_train=pd.DataFrame(data=d)

#将正常邮件(Y=0)和垃圾邮件(Y=1)分为两个子集
normal=emails_train[emails_train.label==0]
normal.reset_index(inplace=True,drop=True) #重置normal索引,作用于原表,丢弃之前的索引
spam=emails_train[emails_train.label==1]
spam.reset_index(inplace=True,drop=True) #重置spam索引,作用于原表,丢弃之前的索引

"""计算Y_train=0、1的条件概率(拉普拉斯平滑)"""
Py0=(len(normal)+1)/(len(emails_train)+2)
Py1=(len(spam)+1)/(len(emails_train)+2)

"""计算X_train各特征向量取各特征值时的先验概率(拉普拉斯平滑)"""
"""计算垃圾邮件中,各特征向量的先验概率"""
vd=len(spam.content[0]) #特征向量的维度
spam_count_dict={} #用于保存content特征向量按列累加的结果
spam_count_prob={} #用于保存垃圾邮件中各特征向量出现的概率

#求content各特征向量按列累加的结果,用于计算各向量在训练集中出现的概率
for i in range(len(spam)):
    for j in range(vd):
        spam_count_dict[j]=spam_count_dict.get(j,0)+spam.content[i][j] #计算垃圾邮件中各特征向量出现的次数,即,求content各特征向量count按列累加的结果

for j in range(vd):
    spam_count_prob[j]=(spam_count_dict.get(j,0)+1)/(len(spam)+2)#计算垃圾邮件中各特征向量出现的概率(拉普拉斯平滑)

"""计算正常邮件中,各特征向量的先验概率"""
normal_count_dict={} #用于保存content特征向量按列累加的结果
normal_count_prob={} #用于保存正常邮件中各特征向量出现的概率

#求content各特征向量按列累加的结果,用于计算各向量在训练集中出现的概率
for i in range(len(normal)):
    for j in range(vd):
        normal_count_dict[j]=normal_count_dict.get(j,0)+normal.content[i][j] #计算垃圾邮件中各特征向量出现的次数,即,求content各特征向量count按列累加的结果

for j in range(vd):
    normal_count_prob[j]=(normal_count_dict.get(j,0)+1)/(len(normal)+2)#计算垃圾邮件中各特征向量出现的概率(拉普拉斯平滑)
2. 将测试集各特征向量值带入训练的概率参数中,计算后验概率,取使后验概率最大的Y=ck为测试样本的分类[类似mnb.predict(), mnb.predict_proba()]
"""计算各测试样本的后验概率"""
test_classify={} #用于保存测试集各样本的后验概率 P(Y|X)=P(Y)*P(X|Y)/P(X)
Px_spam={} #用于保存测试集各样本在垃圾邮件下的先验概率 P(X|Y)
Px_normal={} #用于保存测试集各样本在正常邮件下的先验概率 P(X|Y)

for i in range(X_test_array.shape[0]):
    for j in range(X_test_array.shape[1]):
        if X_test_array[i][j]!=0:
            Px_spam[i]=Px_spam.get(i,1)*spam_count_prob.get(j)#计算垃圾邮件下,各测试样本的后验概率
            Px_normal[i]=Px_normal.get(i,1)*normal_count_prob.get(j)#计算正常邮件下,各测试样本的后验概率

    test_classify[i]=Py0*Px_normal.get(i,0),Py1*Px_spam.get(i,0) #后验概率P(Y|X)=P(Y)*P(X|Y)/P(X)

#比较各样本属于不同分类时(正常/垃圾)的后验概率,去后验概率大的为样本分类结果
results={} #用于存放邮件判定结果
for key,value in test_classify.items():
    if value[0]<=value[1]:
        results[key]=1
    else:
        results[key]=0

测试集12条邮件的分类结果如下,其中第二列为该邮件不是垃圾邮件的概率,第三列为是垃圾邮件的概率:

因此得到分类结果:

3. 计算分类准确率 [类似mnb.score()]
#计算分类准确率
count=0 #计数,统计被正确分类的邮件数量
for key,value in results.items():
    if value==Y_test[key]:
        count+=1
score=count/len(Y_test)
print ("NB模型分类准确率为:{0}%".format(score*100))

输出结果为:


04 总结

本文从朴素贝叶斯原理出发,自己写了一个NB模型,用于滤除个人邮箱中的垃圾邮件,抛砖引玉,希望可以启发大家。

另外,本文设计的NB模型不足之处在于,训练样本太少,分类不够客观准确。解法其实很简单:增加样本量。

当然,本文重点在于理解NB算法原理,下期实操预告:决策树实现。


05 参考

《统计学习方法》 李航 Chapter4

<think>好的,我现在需要帮助用户理解朴素贝叶斯算法的原理和实现。首先,用户的问题是关于Naive Bayes实现和应用场景,所以我得从基础开始解释,确保结构清晰。 首先,我应该回顾朴素贝叶斯的基本原理。记得它是基于贝叶斯定理,假设特征之间独立。这点很重要,虽然实际中特征可能不独立,但算法还是有效。需要提到数学公式,比如后验概率的计算,这里需要用LaTeX格式,根据用户的系统级指令,行内公式用$...$,独立公式用$$...$$。比如,贝叶斯定理可以写成$P(y|X) = \frac{P(X|y)P(y)}{P(X)}$,然后分解条件概率。 接下来,实现步骤需要分点说明。数据准备、计算先验概率、条件概率等。可能还需要提到拉普拉斯平滑,避免零概率问题。这里可以引用用户提供的引用[2],指出概率输出的问题,说明朴素贝叶斯作为估计器的局限性。 然后,代码示例部分。用户给的示例里有Python代码,所以我也要提供一个简单的实现,比如高斯朴素贝叶斯。同时,要提到scikit-learn中的实现,这样用户知道实际应用时可以用库。需要确保代码正确,符合语法要求,并解释关键步骤。 应用场景方面,文本分类、垃圾邮件过滤、情感分析都是常见用途。这部分需要结合引用[1]提到的分类效率和参数少的特点,说明为什么在这些场景适用。同时,也要提到假设特征独立的限制,可能影响结果,呼应引用[1]中的内容。 最后,相关问题部分,根据用户之前的例子,生成三个相关问题。比如优化方法、与逻辑回归的比较、处理连续特征的方法。这些问题可以帮助用户进一步学习。 还要注意引用标记,比如在提到错误率时引用[^1],概率估计问题引用[^2],交叉验证引用[^3]。确保引用自然,不破坏段落流畅。 检查是否有遗漏,比如是否覆盖了原理、实现步骤、代码、应用场景和优缺点。确保数学公式正确,代码格式正确,使用中文回答,结构清晰。可能还需要提到模型验证方法,如交叉验证,引用[3]中的内容。 总结一下,我需要分步骤解释原理,给出代码示例,讨论应用和限制,并生成相关问题。同时,正确使用引用,确保符合用户的所有格式要求。</think>### 朴素贝叶斯算法原理与实现 #### 1. 核心原理 朴素贝叶斯基于贝叶斯定理$P(y|X) = \frac{P(X|y)P(y)}{P(X)}$,并假设特征之间条件独立("朴素"假设)。对于输入特征$X=(x_1,x_2,...,x_n)$,分类目标为: $$ \hat{y} = \arg\max_{y} P(y) \prod_{i=1}^n P(x_i|y) $$ 这种独立性假设简化了计算,但也可能影响准确性[^1]。 #### 2. 实现步骤 1. **数据准备**:将特征离散化(对连续特征需使用高斯朴素贝叶斯) 2. **计算先验概率**:$P(y)$ 3. **计算条件概率**:$P(x_i|y)$ 4. **拉普拉斯平滑**:避免零概率问题 5. **预测计算**:通过最大后验概率决策 #### 3. Python代码示例 ```python import numpy as np from sklearn.naive_bayes import GaussianNB # 手动实现高斯朴素贝叶斯 class GaussianNaiveBayes: def fit(self, X, y): self.classes = np.unique(y) self.mean = {} self.var = {} self.priors = {} for c in self.classes: X_c = X[y == c] self.mean[c] = X_c.mean(axis=0) self.var[c] = X_c.var(axis=0) self.priors[c] = X_c.shape[0] / X.shape[0] def predict(self, X): posteriors = [] for c in self.classes: prior = np.log(self.priors[c]) likelihood = -0.5 * np.sum(np.log(2 * np.pi * self.var[c]) + ((X - self.mean[c])**2) / self.var[c], axis=1) posterior = prior + likelihood posteriors.append(posterior) return self.classes[np.argmax(posteriors, axis=0)] ``` #### 4. 典型应用场景 - **文本分类**:垃圾邮件检测(词频作为特征) - **推荐系统**:用户行为预测 - **医疗诊断**:症状与疾病关系建模 - **实时系统**:低计算资源场景[^1] #### 5. 优缺点分析 | 优势 | 局限性 | |------|--------| | 训练速度快(时间复杂度$O(np)$) | 独立性假设可能导致信息损失 | | 可处理高维数据 | 需要离散化处理连续特征 | | 对缺失数据鲁棒 | 概率估计可能不准确[^2] | | 适合增量学习 | 特征关联场景表现下降 | 实际应用中常通过以下方法改进: - 使用核密度估计处理连续特征 - 集成特征选择方法 - 结合交叉验证优化超参数[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值