首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >筑墙:基于朴素贝叶斯的垃圾邮件分类实践

筑墙:基于朴素贝叶斯的垃圾邮件分类实践

作者头像
herain
发布2024-11-23 10:24:38
发布2024-11-23 10:24:38
3840
举报
文章被收录于专栏:数据指象数据指象

使用sklearn包下的朴素贝叶斯算法,它包含三种模型——高斯模型、多项式模型和伯努利模型, 本文将使用贝叶斯多项式模型类来解决英文邮件分类的问题。

实践步骤:

数据集:数据来自 Spam Mails Dataset kaggle,其中正常邮件标记为ham/0,垃圾邮件为spam/1

导入包:

代码语言:javascript
复制
import nltk
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tqdm import tqdm_notebook
from wordcloud import WordCloud
from sklearn.metrics import roc_curve, auc
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize, RegexpTokenizer
%matplotlib inline

数据探查:

代码语言:javascript
复制
data = pd.read_csv('spam_ham_dataset.csv')
data = data.iloc[:, 1:]
data.head()
data.info()
代码语言:javascript
复制
plt.style.use('seaborn')
plt.figure(figsize=(6, 4), dpi=100)
data['label'].value_counts().plot(kind='bar')

数据清洗:

邮件中含有大小写,故将先单词替换为小写

使用停用词,邮件中出现的you、me、be等单词对分类没有影响,故可以将其禁用。还要注意的是所有邮件的开头中都含有单词subject(主题),我们也将其设为停用词。这里使用自然语言处理工具包nltk下的stopwords。

提取一长串句子中的每个单词,并且还要过滤掉各种符号,所以这里使用nltk下的RegexpTokenizer()函数,参数为正则表达式:RegexpTokenizer('[a-zA-Z]+').tokenize(string)。

在英语里面,一个单词有不同的时态,比如love与loves,只是时态不同,但是是同一个意思,于是就有了——词形还原与词干提取。而本文使用的词形还原方法。这里先使用nltk包下的WordNetLemmatizer()函数

代码语言:javascript
复制
new_data = data.iloc[:, 1:]
new_data['text'] = new_data['text'].str.lower()
stop_words = set(stopwords.words('english'))
stop_words.add('subject')
def text_process(text):
    tokenizer = RegexpTokenizer('[a-z]+') # 只匹配单词,由于已经全为小写,故可以只写成[a-z]+
    lemmatizer = WordNetLemmatizer()
    token = tokenizer.tokenize(text) # 分词
    token = [lemmatizer.lemmatize(w) 
             for w in token if lemmatizer.lemmatize(w) not in stop_words] # 停用词+词形还原
    return token
new_data['text'] = new_data['text'].apply(text_process)

现在我们得到了一个比较干净的数据集:

训练集与测试集:训练集中的垃圾邮件与正常邮件的数量分布

代码语言:javascript
复制
seed = 20241026 # 让实验具有重复性
X = new_data['text']
y = new_data['label_num']X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=seed) # 75%作为训练集与25%作为测试集
train = pd.concat([X_train, y_train], axis=1) # 训练集
test = pd.concat([X_test, y_test], axis=1) # 测试集

train.reset_index(drop=True, inplace=True) # 重设下标
test.reset_index(drop=True, inplace=True) # 同上

print(train['label_num'].value_counts())
plt.figure(figsize=(6, 4), dpi=100)
train['label_num'].value_counts().plot(kind='bar')

构建特征:

如果把所有的单词都拿来统计,单词表里面的单词还是比较多的,这样让我们的模型跑起来也是比较慢的,故这里随机抽取正常邮件与垃圾邮件各10封内的单词作为单词表。

代码语言:javascript
复制
ham_train = train[train['label_num'] == 0] # 正常邮件
spam_train = train[train['label_num'] == 1] # 垃圾邮件

ham_train_part = ham_train['text'].sample(10, random_state=seed) # 随机抽取的10封正常邮件
spam_train_part = spam_train['text'].sample(10, random_state=seed) # 随机抽取的10封垃圾邮件

part_words = [] # 部分的单词
for text in pd.concat([ham_train_part, spam_train_part]):
    part_words += text

接下来我们要统计每个单词出现的次数,使用sklearn的CountVectorizer()函数,如:

代码语言:javascript
复制
words = ['This is the first sentence', 'And this is the second sentence']
cv = CountVectorizer() # 参数lowercase=True,将字母转为小写,但数据已经是小写了
count = cv.fit_transform(words)
print('cv.vocabulary_:\n', cv.vocabulary_) # 返回一个字典
print('cv.get_feature_names:\n', cv.get_feature_names()) # 返回一个列表
print('count.toarray:\n', count.toarray()) # 返回序列

[0 1 1 0 1 1 1] 对应 ['and', 'first', 'is', 'second', 'sentence', 'the', 'this'],即'first'出现1次,'is'出现1次,如此类推。

接下来还要计算TF-IDF,它反映了单词在文本中的重要程度。使用sklearn包下的TfidfTransformer(),如:

代码语言:javascript
复制
tfidf = TfidfTransformer()
tfidf_matrix = tfidf.fit_transform(count)
print('idf:\n', tfidf.idf_) # 查看idf
print('tfidf:\n', tfidf_matrix.toarray()) # 查看tf-idf
代码语言:javascript
复制
# 将正常邮件与垃圾邮件的单词都整理为句子,单词间以空格相隔,CountVectorizer()的句子里,单词是以空格分隔的
train_part_texts = [' '.join(text) for text in np.concatenate((spam_train_part.values, ham_train_part.values))]
# 训练集所有的单词整理成句子
train_all_texts = [' '.join(text) for text in train['text']]
# 测试集所有的单词整理成句子
test_all_texts = [' '.join(text) for text in test['text']]

cv = CountVectorizer()
part_fit = cv.fit(train_part_texts) # 以部分句子为参考
train_all_count = cv.transform(train_all_texts) # 对训练集所有邮件统计单词个数
test_all_count = cv.transform(test_all_texts) # 对测试集所有邮件统计单词个数
tfidf = TfidfTransformer()
train_tfidf_matrix = tfidf.fit_transform(train_all_count)
test_tfidf_matrix = tfidf.fit_transform(test_all_count)

建立模型

代码语言:javascript
复制
mnb = MultinomialNB()
mnb.fit(train_tfidf_matrix, y_train)
mnb.score(test_tfidf_matrix, y_test)

模型在测试集上的正确率:0.9265

代码语言:javascript
复制
y_pred = mnb.predict_proba(test_tfidf_matrix)
fpr, tpr, thresholds = roc_curve(y_test, y_pred[:, 1])
auc = auc(fpr, tpr)

# roc 曲线
plt.figure(figsize=(6, 4), dpi=100)
plt.plot(fpr, tpr)
plt.title('roc = {:.4f}'.format(auc))
plt.xlabel('fpr')
plt.ylabel('tpr')

到此,就完成了从数据清理到建模的一整套流程了,当然其中还要许多东西可以完善的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据指象 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档