目录
背景
一、适用数据集
1. 数据集选择
1.1 适用领域
1.2 数据集维度(特征数)
1.3 数据行数
2. 本文数据集介绍
2.1 数据集特征
2.2 数据格式
3. 数据集下载
二、算法原理
1. 朴素贝叶斯定理
2. 算法逻辑
3. 运行步骤
4. 更多延申模型
三、代码
1. 导入所需包&数据
2. 数据预处理
3. 数据探索
3.1 特征工程
3.2 异常值处理
4. 数据准备
4.1 文本处理
4.2 矢量化
5. 模型建构
6. 模型评价
背景
朴素贝叶斯(Naive Bayes)是一种基于贝叶斯定理(即条件概率)的简单高效的分类算法,广泛应用于文本分类和其他监督学习任务。朴素贝叶斯算法假设特征之间相互独立,这一假设使得算法运算简洁,适合处理高维度数据。
本文将使用朴素贝叶斯对垃圾邮件进行分类,数据集以及完整跑通代码见文首绑定资源。
一、适用数据集
朴素贝叶斯算法具有高效性和良好的扩展性,适合处理高维度(数千到数万特征)和大规模(数千到百万级数据行)的数据集,特别是在特征相对独立的情况下,性能会更加优越。在实际应用中,具体的维度和数据行数量还需根据可用的计算资源和任务复杂性进行评估。
1. 数据集选择
朴素贝叶斯(NB)适用于文本分类问题,练习时在选择数据集是可参考以下 3 个方面。反过来,如果在为项目选择合适的算法模型时,数据集符合以下条件时,可考虑选用朴素贝叶斯进行分析处理。
1.1 适用领域
文本分类
- 垃圾邮件过滤:朴素贝叶斯是邮件分类中的经典方法,可以有效区分垃圾邮件和正常邮件。其假设每个词语独立贡献信息,有助于快速判断邮件的类别。
- 情感分析:可用于情感分析任务,特别是判断评论或社交媒体上的文本情绪(如积极或消极)。这种任务的文本数据特征可以独立统计,因此朴素贝叶斯通常表现较好。
- 文档主题识别:朴素贝叶斯广泛应用于文档主题识别任务中,通过统计每个类别的关键词出现概率,帮助区分文档的主要主题。比如,将一批文档区分为“技术”、“金融”、“健康”等类别。
内容推荐:在简单推荐系统中用于根据用户历史行为对内容进行分类和推荐,尤其是文本型内容。例如,对一组产品描述或影评进行分类,为用户推荐相关产品。
医疗诊断:在医学应用中,用于对症状组合的分类和预测,例如根据病人症状预测疾病类型。其优势在于能通过独立假设快速处理较多的特征,适合于诊断大量症状的医学数据。
欺诈检测:用于检测金融交易的欺诈行为,通过朴素贝叶斯快速学习欺诈模式并做出分类,例如区分正常交易和可疑交易。
语言识别和拼写纠正:用于语言识别任务中,区分文本语言类型(如英语和法语)。在拼写纠正中,基于历史数据统计拼写错误的发生概率,识别最可能的正确拼写。
1.2 数据集维度(特征数)
朴素贝叶斯能够有效处理数千到数万甚至更多的特征。尤其在文本分类中,特征数通常较高(如词汇表中的单词数)。
1.3 数据行数
朴素贝叶斯能够处理千级、万级到百万级的数据行,许多文本分类任务中的数据集(如电子邮件、社交媒体数据等)都能在此范围内良好工作。
由于其基于概率的计算方式,朴素贝叶斯的训练时间复杂度为 ,其中 n 是样本数量,d 是特征数量。这使得它在内存和计算资源有限的情况下依然能高效运行。
2. 本文数据集介绍
SMS Spam Collection Dataset 包含来自真实用户的短信,是一个广泛用于文本分类和机器学习研究的经典数据集,专门用于垃圾短信(spam)和正常短信(ham)的分类。常被用于测试各种机器学习算法,尤其是朴素贝叶斯算法。
2.1 数据集特征
- 样本数量:数据集中包含 5,572 条短信记录。
- 特征描述:
- Target:短信的类别,取值为 "spam" 或 "ham"。
- Text:短信的文本内容。
2.2 数据格式
数据集通常以 CSV 格式存储,每行对应一条短信记录。具体的列结构如下:
Target | Text |
---|---|
ham | Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat... |
spam | Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's |
3. 数据集下载
数据集下载地址:https://www.kaggle.com/datasets/uciml/sms-spam-collection-dataset
也可以在文章绑定资源中直接下载获取。
二、算法原理
1. 朴素贝叶斯定理
朴素贝叶斯基于贝叶斯定理:
其中,P(A∣B) 是在已知条件 B 下事件 A 的后验概率。对于朴素贝叶斯算法,算法通过最大化后验概率来判断类别标签。朴素假设意味着各特征之间独立,因此可以将联合概率简化为各个特征的独立概率的乘积,这样大大减少了计算复杂度。得到联合概率分布后,概率估计方法可以是极大似然估计或贝叶斯估计。
2. 算法逻辑
朴素贝叶斯法的基本假设是条件独立性,
这是一个较强的假设。由于这一假设,模型包含的条件概率的数量大为减少,朴素贝叶斯法的学习与预测大为简化。因而朴素贝叶斯法高效,且易于实现。其缺点是分类的性能不一定很高。
朴素贝叶斯法利用贝叶斯定理与学到的联合概率模型进行分类预测。
将输入 x 分到后验概率最大的类 y。
后验概率最大等价于0-1损失函数时的期望风险最小化。
3. 运行步骤
朴素贝叶斯算法的步骤如下:
- 计算各类别的先验概率:统计每个类别在训练数据中出现的频率。
- 计算每个特征在各类别下的条件概率:假设特征间独立,计算每个特征在特定类别下出现的概率。
- 应用贝叶斯定理:将样本数据代入公式,计算后验概率,并选择具有最大后验概率的类别作为分类结果。
4. 更多延申模型
朴素贝叶斯对应的包名称为 naive_bayes,在其下可以选用所种模型:
模型名称 | 可导入包名称 |
---|---|
高斯贝叶斯 | GaussianNB |
多项式贝叶斯 | MultinomialNB |
伯努利贝叶斯 | BernoulliNB |
如选用多项式贝叶斯模型时,代码如下:
from sklearn.naive_bayes import MultinomialNB
三、代码
本篇代码包含多张图片以及完整注释,建议在文章绑定资源中下载完整代码查看。
1. 导入所需包&数据
## 数据处理类
import numpy as np
import pandas as pd
import warnings## 数据可视化类
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline## 自然语言处理类
import re #处理文本字符串,如查找、替换、分割和匹配
import nltk #文本预处理(如分词、词干提取、停用词过滤)、词性标注、命名实体识别、情感分析等各种自然语言处理任务
from nltk.corpus import stopwords #文本预处理
from nltk.tokenize import word_tokenize #分割文本
from nltk.stem.porter import PorterStemmer #词干提取算法
from nltk.stem import WordNetLemmatizer #去标点符号from sklearn.preprocessing import MinMaxScaler,LabelEncoder
# MinMaxScaler 能将类别型数据转换为数字
# LabelEncoder 进行特征放缩
from sklearn.feature_extraction.text import TfidfVectorizer
# TfidfVectorizer 将原生文档转化为txt矩阵## 分析算法类
from sklearn.naive_bayes import MultinomialNB #朴素贝叶斯## 算法优化类
from sklearn.pipeline import Pipeline #流水线操作
from sklearn.model_selection import GridSearchCV,cross_val_score,train_test_split
# GridSearchCV 网格搜索,cross_val_score 交叉验证,train_test_split 数据划分## 算法评分类
from sklearn import metrics
from sklearn.metrics import precision_score, recall_score, plot_confusion_matrix, classification_report, accuracy_score, f1_score#自行加载数据完成
#查看数据集基本结构信息
data.info()
2. 数据预处理
# 去除后面三列无意义列
to_drop = ["Unnamed: 2","Unnamed: 3","Unnamed: 4"]
data = data.drop(data[to_drop], axis=1)# 重命名前两列
data.rename(columns = {"v1":"Target", "v2":"Text"}, inplace = True)
data.head()
3. 数据探索
plt.figure(figsize=(6,4))
fg = sns.countplot(x= data["Target"])
fg.set_title("Count Plot of Classes")
fg.set_xlabel("Classes")
fg.set_ylabel("Number of Data points")
3.1 特征工程
#添加列:字符数、单词数和句子数
data["No_of_Characters"] = data["Text"].apply(len)data["No_of_Words"]=data.apply(lambda row: nltk.word_tokenize(row["Text"]), axis=1).apply(len)
# 创建新列"No_of_Words",apply 将一个函数应用到 DataFrame 的每一行(axis=1),定义了一个匿名函数lambda接受行作为输入
# 使用 word_tokenize 函数对该行中的 Text 列进行分词,返回一个单词列表,使用 apply(len) 计算分词结果的长度,即单词的数量data["No_of_sentence"]=data.apply(lambda row: nltk.sent_tokenize(row["Text"]), axis=1).apply(len)
# sent_tokenize 函数返回的是句子列表data.describe().T # .T 是 DataFrame 的转置操作,将行和列互换plt.figure(figsize=(12,8))
fg = sns.pairplot(data=data, hue="Target")
plt.show(fg)
3.2 异常值处理
data = data[(data["No_of_Characters"]<350)]
data.shapeplt.figure(figsize=(12,8))
fg = sns.pairplot(data=data, hue="Target")
plt.show(fg)
4. 数据准备
由于本篇数据为文本处理,故需要先对长文本格式进行批量处理,才能使用算法模型进行预测评估。
4.1 文本处理
# 查看数据
print("\033[1m\u001b[45;1m The First 5 Texts:\033[0m",*data["Text"][:5], sep = "\n")# 编写数据清理函数
def Clean(Text):sms = re.sub('[^a-zA-Z]', ' ', Text) #Replacing all non-alphabetic characters with a spacesms = sms.lower() #converting to lowecasesms = sms.split()sms = ' '.join(sms)return smsdata["Clean_Text"] = data["Text"].apply(Clean)# 查看清理后数据
print("\033[1m\u001b[45;1m The First 5 Texts after cleaning:\033[0m",*data["Clean_Text"][:5], sep = "\n")data["Tokenize_Text"]=data.apply(lambda row: nltk.word_tokenize(row["Clean_Text"]), axis=1)print("\033[1m\u001b[45;1m The First 5 Texts after Tokenizing:\033[0m",*data["Tokenize_Text"][:5], sep = "\n")# 删除停用词
def remove_stopwords(text):stop_words = set(stopwords.words("english"))filtered_text = [word for word in text if word not in stop_words]return filtered_textdata["Nostopword_Text"] = data["Tokenize_Text"].apply(remove_stopwords)print("\033[1m\u001b[45;1m The First 5 Texts after removing the stopwords:\033[0m",*data["Nostopword_Text"][:5], sep = "\n")lemmatizer = WordNetLemmatizer()
# lemmatize string
def lemmatize_word(text):#word_tokens = word_tokenize(text)# provide context i.e. part-of-speechlemmas = [lemmatizer.lemmatize(word, pos ='v') for word in text]return lemmasdata["Lemmatized_Text"] = data["Nostopword_Text"].apply(lemmatize_word)
print("\033[1m\u001b[45;1m The First 5 Texts after lemitization:\033[0m",*data["Lemmatized_Text"][:5], sep = "\n")
4.2 矢量化
由于文字其实无法被读取,需要将文本内容进行向量化转换,便于
#词频 (TF) = (文档中某个词的频率)/(文档中词的总数)
#逆文档频率 (IDF) = log( (文档总数)/(包含词 t 的文档数)
#I将使用 TfidfVectorizer() 对预处理后的数据进行矢量化。#创建文本特征语料库
corpus= []
for i in data["Lemmatized_Text"]:msg = ' '.join([row for row in i])corpus.append(msg)corpus[:5]
print("\033[1m\u001b[45;1m The First 5 lines in corpus :\033[0m",*corpus[:5], sep = "\n")# 矢量化
tfidf = TfidfVectorizer()
X = tfidf.fit_transform(corpus).toarray()
# 查看特征
X.dtype# 对目标进行标签编码并将其用作 y
label_encoder = LabelEncoder()
data["Target"] = label_encoder.fit_transform(data["Target"])
5. 模型建构
y = data["Target"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)nb_model = MultinomialNB()
nb_model.fit(X_train, y_train)
y_pred_nb = nb_model.predict(X_test)
accuracy_nb = accuracy_score(y_test, y_pred_nb)
print(f"朴素贝叶斯分类准确率: {accuracy_nb:.4f}")#交叉验证
# 使用准确率作为评估标准
scores = cross_val_score(MultinomialNB(),X_train, y_train, cv=5, scoring='accuracy')# 输出交叉验证的平均准确率和标准差
print(f"朴素贝叶斯 - 交叉验证准确率: {scores.mean():.2f} ± {scores.std():.2f}")# 使用加权F1得分作为评估标准
scores = cross_val_score(MultinomialNB(), X_train, y_train, cv=5, scoring='f1_weighted')# 输出加权F1得分的平均值和标准差
print(f"朴素贝叶斯 - 加权F1得分: {scores.mean():.2f} ± {scores.std():.2f}")
6. 模型评价
precision =[]
recall =[]
f1_score = []
trainset_accuracy = []
testset_accuracy = []pred_train = nb_model.predict(X_train)
pred_test = nb_model.predict(X_test)
prec = metrics.precision_score(y_test, pred_test)
recal = metrics.recall_score(y_test, pred_test)
f1_s = metrics.f1_score(y_test, pred_test)
train_accuracy = nb_model.score(X_train,y_train)
test_accuracy = nb_model.score(X_test,y_test)#评分
precision.append(prec)
recall.append(recal)
f1_score.append(f1_s)
trainset_accuracy.append(train_accuracy)
testset_accuracy.append(test_accuracy)# 初始化列表的数据
data = {'Precision':precision,
'Recall':recall,
'F1score':f1_score,
'Accuracy on Testset':testset_accuracy,
'Accuracy on Trainset':trainset_accuracy}#创建表格
Results = pd.DataFrame(data, index =["NaiveBayes"])Results.style.background_gradient()# 创建混淆矩阵
fig, ax = plt.subplots() # 创建一个单独的子图
plot_confusion_matrix(nb_model, X_test, y_test, ax=ax)
ax.title.set_text(type(nb_model).__name__) # 设置标题为模型的类名
plt.tight_layout()
plt.show()
准确性报告返回表格如下:
分析可以得到以下结论:
-
Precision(准确率):模型在测试集上预测正类的准确率是100%,表示被模型识别为【正类】的样本 100% 的确是【正类】。
-
Recall(召回率):召回率为 0.705882,说明是【正类】的所有样本中,模型只识别出了大约 70.59%。这相对较低,表明模型有较高的漏报率。
-
F1 Score:F1分数是 0.827586,它结合了准确率和召回率,是一个综合度量指标。尽管模型在准确率上表现出色,但因为召回率较低,F1分数中等。
-
测试集上的准确率:模型在测试集上的总体准确率为 0.963964,说明在所有测试样本中,大约96.4%的样本被正确分类。这个值较高,表明模型在未见数据上的表现不错。
-
训练集上的准确率:训练集的准确率为 0.976341,略高于测试集准确率,但差距不大。训练和测试集之间的准确率差异较小,表明模型没有明显的过拟合,性能在训练和测试中较为一致。
综合分析:
总体准确率显示出模型的整体分类能力较强,没有明显过拟合,如果目标是减少误报(例如在精确识别很重要的场景下),模型的表现已经非常出色。如果更关注全面识别正类样本(如在健康诊断中),则召回率需要进一步优化。