当然前者是老师写的。
训练数据
i
n
d
e
x
index
index是具体数据的文件位置。
s
p
a
m
spam
spam是垃圾邮件
s
t
o
p
stop
stop是停用词,即去掉了一些非实义动词(啊,比如,其实等)
数据预处理
创造一个 d a t a f r a m e dataframe dataframe结构,存储类别和训练数据所在位置。
def load_data():
df = pd.read_csv('index', sep=' ', names=['spam', 'path'])
df.spam = df.spam.apply(lambda x: 1 if x == 'spam' else 0)
df.path = df.path.apply(lambda x: x[1:])#这里是为了去除../的.因为在同一工作目录下
return df
加载停用词。
def load_stop_list():
with open('stop', 'r') as f:
lines = f.readlines()
stop_list = [i.strip() for i in lines]#移除指定字符,默认是空格或者换行符,这里移除了换行符
return stop_list
获取邮件的内容
def get_content(path):
# 邮件数据是 gbk 编码, 忽略无法解码的内容
with open(path, 'r', encoding='gbk', errors='ignore') as f:
lines = f.readlines()
for i in range(len(lines)):
if lines[i] == '\n':
#从空行后的第一行开始,因为一开始都是收件人等的信息,第一个空行后才是邮件数据
lines = lines[i:]
break
content = ''.join(''.join(lines).strip().split())#用空格分开
#''.join()也可以用来转化成字符串
#.strip()用来移除字符串首位指定字符,去除了空格,\n \t \r字符
return content
训练
创建词汇字典,为什么要创建这个呢。
因为我们的做法,是把邮件中的每个词汇当做特征,来进行朴素贝叶斯分类。
最后得到每个邮件的词汇字典。
def create_string_word_dict(string, stop_list):
string = re.findall(u'[\u4E00-\u9FD5]', string)#寻找中文语句
string = ''.join(string)
word_list = []
seg_list = jieba.cut(string)#结巴分词,创建词汇
for word in seg_list:
if word != '' and word not in stop_list:
word_list.append(word)#没有被禁用就加入
word_dict = dict([(word, 1) for word in word_list])#创建字典
return word_dict
进行训练,得到具体每个词汇对应具体分类的数量。
def train(df):
train_word_dict = {}
for word_dict, spam in zip(df.word_dict, df.spam):
# 统计测试邮件中词语在垃圾邮件和正常邮件中出现的总数
for w in word_dict:
train_word_dict.setdefault(w, {0: 0, 1: 0})
train_word_dict[w][spam] += 1
ham_count = df.spam.value_counts()[0]
spam_count = df.spam.value_counts()[1]
return train_word_dict, spam_count, ham_count
预测
预测就是计算文章内多个词汇对于某个类别的概率,然后相乘。
为了防止溢出,我们用的是
l
o
g
log
log。
def predict(train_word_dict, spam_count, ham_count, row):
total_count = ham_count + spam_count
word_dict = row['word_dict']
# 正常邮件概率
hp = math.log(float(ham_count) / total_count)
# 垃圾邮件概率
sp = math.log(float(spam_count) / total_count)
for w in word_dict:
w = w.strip()
# 给该词在词典中设定一个默认数0
train_word_dict.setdefault(w, {0: 0, 1: 0})
# 该词在词典中正常邮件出现的次数
pih = train_word_dict[w][0]
# 平滑处理, 每个词汇基数+1,正常邮件数+2
hp += math.log((float(pih) + 1) / (ham_count + 2))
pis = train_word_dict[w][1]
sp += math.log((float(pis) + 1) / (spam_count + 2))
# 预测结果
predict_spam = 1 if sp > hp else 0
# 返回预测是否准确,用于统计
return 1 if predict_spam == row['spam'] else 0
主程序调用
if __name__ == '__main__':
df=load_data()#加载邮件数据
stop_list = load_stop_list()#加载停用词列表
df['content'] = df.path.apply(lambda x: get_content(x))#提取邮件文本内容
df['word_dict'] = df.content.apply(lambda x: create_string_word_dict(x, stop_list))#创建邮件字典
print(df['word_dict'])
train_mails = df.loc[:int(len(df) * 0.8)]
test_mails = df.loc[int(len(df) * 0.8):]#训练集和测试集
train_word_dict, spam_count, ham_count = train(train_mails)
train_word_dict
test_mails_predict = test_mails.apply(
lambda x: predict(train_word_dict, spam_count, ham_count, x), axis=1)
result = 1 - float(test_mails_predict.value_counts().get(0, 0)) / test_mails_predict.value_counts().sum()
print('预测准确率:', result)
结果
使用sklearn
之前我们使用结巴分词,留下中文,去掉停用词。
# -*- coding: utf-8 -*-
"""
Created on Sat May 2 17:20:05 2020
@author: DELL
"""
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer # 从sklearn.feature_extraction.text里导入文本特征向量化模块
from sklearn.naive_bayes import MultinomialNB # 从sklean.naive_bayes里导入朴素贝叶斯模型
from sklearn.metrics import classification_report
import pandas as pd
stop_list=[]
def load_data():
df = pd.read_csv('index', sep=' ', names=['spam', 'path'])
df.spam = df.spam.apply(lambda x: 1 if x == 'spam' else 0)
df.path = df.path.apply(lambda x: x[1:])#这里是为了去除../的.因为不是在同一工作目录下
return df
def load_stop_list():
with open('stop', 'r') as f:
lines = f.readlines()
stop_list = [i.strip() for i in lines]#移除指定字符,默认是空格或者换行符,这里移除了换行符
return stop_list
def get_content(path):
# 邮件数据是 gbk 编码, 忽略无法解码的内容
with open(path, 'r', encoding='gbk', errors='ignore') as f:
lines = f.readlines()
for i in range(len(lines)):
if lines[i] == '\n':
#从空行后的第一行开始,因为一开始都是收件人等的信息,第一个空行后才是邮件数据
lines = lines[i:]
break
content = ''.join(''.join(lines).strip().split())
return content
if __name__ == '__main__':
df = load_data()#加载邮件数据
stop_list = load_stop_list()#加载停用词列表
df['content'] = df.path.apply(lambda x: get_content(x))#提取邮件文本内容
X_train,X_test,y_train,y_test = train_test_split(df['content'],df['spam'],test_size=0.2,random_state=33) # 随机采样25%的数据样本作为测试集
#文本特征向量化
vec = CountVectorizer(stop_words=stop_list)#设置停用词
X_train = vec.fit_transform(X_train)
X_test = vec.transform(X_test)
#使用朴素贝叶斯进行训练
mnb = MultinomialNB() # 使用默认配置初始化朴素贝叶斯
mnb.fit(X_train,y_train) # 利用训练数据对模型参数进行估计
y_predict = mnb.predict(X_test) # 对参数进行预测
#获取结果报告
print ('The Accuracy of Naive Bayes Classifier is:', mnb.score(X_test,y_test))
结果如下:
可以发现,相比较而言,后者略微好一点点。