前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于jieba、TfidfVectorizer、LogisticRegression的垃圾邮件分类

基于jieba、TfidfVectorizer、LogisticRegression的垃圾邮件分类

作者头像
潇洒坤
发布2018-10-09 11:45:47
1.2K0
发布2018-10-09 11:45:47
举报
文章被收录于专栏:简书专栏简书专栏

2018年9月27日笔记

jieba中文叫做结巴,是一款中文分词工具,官方文档链接:https://github.com/fxsjy/jieba TfidfVectorizer中文叫做___ 词频逆文档频率向量化模型,是用来文章内容向量化的工具,官方文档链接:http://sklearn.apachecn.org/cn/0.19.0/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html LogisticRegression中文叫做逻辑回归模型___,是一种基础、常用的分类方法。

建议读者安装anaconda,这个集成开发环境自带了很多包。 到2018年9月27日仍为最新版本的anaconda下载链接: https://pan.baidu.com/s/1pbzVbr1ZJ-iQqJzy1wKs0A 密码: g6ex 官网下载地址:https://repo.anaconda.com/archive/Anaconda3-5.2.0-Windows-x86_64.exe 下面代码的开发环境为jupyter notebook,使用在jupyter notebook中的截图表示运行结果。

0.打开jupyter

在桌面新建文件夹命名为基于TfidfVectorizer的垃圾分类,如下图所示:

image.png

打开基于TfidfVectorizer的垃圾邮件分类文件夹,在按住Shift键的情况下,点击鼠标右键,出现如下图所示。 选择在此处打开PowerShell窗口,之后会在此路径下打开PowerShell。

image.png

在PowerShell中输入命令并运行:jupyter notebook

image.png

PowerShell运行命令后,会自动打开网页,点击如下图所示网页中的按钮:

image.png

spam中文叫做垃圾邮件 代码文件重命名为spamMailTest,重命名按钮位置如下图所示:

image.png

1.数据下载

数据文件下载链接: https://pan.baidu.com/s/1kqOFq8Ou_2D3fIKp0l62qQ 提取码: eu5x 压缩文件trec06c.zip当中含有64000多个包含邮件内容的文本文件。 使用软件Winrar无法解压,使用软件7zipBandizip可以解压,需要3分钟左右。 选择解压到trec06c,如下图所示:

image.png

解压完成后,文件夹中目录结构如下图所示:

image.png

2.数据观察

查看文件需要安装Notepad++,安装软件后鼠标右击文件,从Notepad++中打开按钮如下图所示:

image.png

trec06c文件夹中的data/000/001文件内容如下图所示。 篇幅有限,本文作者只演示其中一篇邮件的内容。 通过查看多篇邮件的内容,发现邮件头邮件内容以一个空行分隔。 在代码中找到第一个\n\n分隔成2段,第1段为邮件头,第2段为邮件内容。

image.png

trec06c文件夹中的full/index文件内容如下图所示。 每1行按照空格分隔成2段,第1段是邮件是否为垃圾邮件,标签值为spam则是垃圾邮件,标签值为ham则是正常邮件; 第2段是此邮件对应的路径,代码中通过字符串切片和拼接稍作修改成为能够读取文件的路径。

image.png

3.数据准备

3.1 预测目标值

预测目标值赋值给变量y,代码如下:

代码语言:javascript
复制
with open('./trec06c/full/index') as file:
    y = [k.split()[0] for k in file.readlines()]
print(len(y))

上面一段代码的运行结果如下:

64620

从上面的运行结果可以看出,共有64620个样本

3.2 邮件文本文件路径

邮件文本文件路径列表赋值给变量filePath_list。 本文作者使用3种方法获取文本文件路径,读者可以参考。

3.2.1 使用trec06c文件夹中的full/index文件

第1种是直接使用trec06c文件夹中的full/index文件,代码如下:

代码语言:javascript
复制
with open('./trec06c/full/index') as file:
    filePath_list = ['./trec06c' + k.strip().split()[1][2:] for k in file.readlines()]
print(filePath_list[0])
print(filePath_list[1])

上面一段代码的运行结果如下:

64620 ./trec06c/data/000/000 ./trec06c/data/000/001

3.2.2 定义getFilePathList函数

第2种是定义getFilePathList函数,函数中主要使用os.path.listdir、os.path.isdir、os.path.isdir这3个方法,获取根目录下的所有文件。 os.path.listdir方法需要1个参数,参数是路径,参数数据类型为字符串,方法可以找出路径下所有文件夹和文件。 os.path.isdir方法需要1个参数,参数是路径,参数数据类型为字符串,方法可以判断此路径是否为文件夹。 os.path.isfile方法需要1个参数,参数是路径,参数数据类型为字符串,方法可以判断此路径是否为文件

代码语言:javascript
复制
import os
import time

def getFilePathList(rootDir):    
    filePath_list = []    
    fileOrDir_list = os.listdir(rootDir) #列出文件夹下所有的目录与文件    
    for fileOrDir in fileOrDir_list:           
        path = os.path.join(rootDir, fileOrDir) 
        if os.path.isdir(path):              
            filePath_list.extend(getFilePathList(path))           
        if os.path.isfile(path):              
            filePath_list.append(path)    
    return filePath_list

startTime = time.time()
filePath_list = getFilePathList('./trec06c/data')
print(len(filePath_list))
print(filePath_list[0])
print(filePath_list[1])
print('function use %.2f seconds' %(time.time()-startTime))

上面一段代码的运行结果如下:

64620 ./trec06c/data\000\000 ./trec06c/data\000\001 function use 24.47 seconds

3.2.3 定义getFilePathList2函数

第3种是定义getFilePathList2函数,函数中主要使用os.walk方法,获取目录下所有的文件。 os.walk方法的返回结果的数据类型是列表,列表中的元素的数据类型是元组。 元组的第1个元素为表示路径的字符串; 元组的第2个元素为第1个元素所表示路径下的文件夹; 元组的第3个元素为第1个元素所表示路径下的文件;

代码语言:javascript
复制
import os
import time

def getFilePathList2(rootDir):
    filePath_list = []
    for walk in os.walk(rootDir):
        part_filePath_list = [os.path.join(walk[0], file) for file in walk[2]]
        filePath_list.extend(part_filePath_list)
    return filePath_list

startTime = time.time()
filePath_list = getFilePathList2('./trec06c/data')
print(len(filePath_list))
print(filePath_list[0])
print(filePath_list[1])
print('function use %.2f seconds' %(time.time()-startTime))

上面一段代码的运行结果如下:

64620 ./trec06c/data\000\000 ./trec06c/data\000\001 function use 0.64 seconds

3.2.4 本节小结

对比3.2.2节和3.2.3节的运行时间,使用内置的os.walk方法效率高出很多。 24.47/0.64=38.2344,在效率上,使用os.walk方法为自己实现方法的38倍。

3.3 邮件内容

3.3.1 加载邮件内容

本文作者在此项目开发中,采用快速迭代开发策略。 第1个迭代版本丢弃邮件头只使用邮件内容作为特征,就取得98%左右的准确率。 邮件内容列表赋值给变量mailContent_list,代码如下:

代码语言:javascript
复制
mailContent_list = []
for filePath in filePath_list:
    with open(filePath, errors='ignore') as file:
        file_str = file.read()
        mailContent = file_str.split('\n\n', maxsplit=1)[1] 
        mailContent_list.append(mailContent)
print(mailContent_list[1])

上面一段代码的运行结果如下:

讲的是孔子后人的故事。一个老领导回到家乡,跟儿子感情不和,跟贪财的孙子孔为本和睦。 老领导的弟弟魏宗万是赶马车的。 有个洋妞大概是考察民俗的,在他们家过年。 孔为本总想出国,被爷爷教育了。 最后,一家人基本和解。 顺便问另一类电影,北京青年电影制片厂的。中越战背景。一军人被介绍了一个对象,去相亲。女方是军队医院的护士,犹豫不决,总是在回忆战场上负伤的男友,好像还没死。最后 男方表示理解,归队了。

从上面的运行结果可以看出,获取邮件内容列表成功

3.3.2 正则表达式去除多余空格

re.sub('\s+', ' ', k)可以把变量k中多个空格或换行替换为单个空格。

代码语言:javascript
复制
import re 

mailContent_list = [re.sub('\s+', ' ', k) for k in mailContent_list]

4.分词

4.1 加载停顿词

停顿词文件下载链接: https://pan.baidu.com/s/1JWQFy84wN_jhU9H2P1Ig7g 提取码: uk4m 停顿词文件stopwords.txt文件下载完成后,放在代码文件的同级目录

代码语言:javascript
复制
with open('./stopwords.txt', encoding='utf8') as file:
    file_str = file.read()
    stopword_list = file_str.split('\n')
    stopword_set = set(stopword_list)
print(len(stopword_list))
print(len(stopword_set))

上面一段代码的运行结果如下:

1233 1231

从上面的运行结果可以看出,停顿词列表中有2个停顿词重复。

4.2 去除停顿词效率对比

jie.lcut(mail)list(jie.cut(mail))两种写法效果相同。 每篇邮件的分词结果的数据类型为列表,本文作者使用了2种方法去除分词结果中的停顿词。 不去除停顿词的代码写法cutWords = [k for k in jieba.lcut(mail)] 第1种去除停顿词的代码写法cutWords = [k for k in jieba.lcut(mail) if k not in stopword_list]

代码语言:javascript
复制
import time
import jieba 

cutWords_list = []
startTime = time.time()
i = 0
for mail in mailContent_list[:3000]:
    cutWords = [k for k in jieba.lcut(mail) if k not in stopword_list]
    cutWords_list.append(cutWords)
    i += 1
    if i % 1000 == 0:
        print('前%d篇邮件分词共花费%.2f秒' %(i, time.time()-startTime))

上面一段代码的运行结果如下:

前1000篇邮件分词共花费14.74秒 前2000篇邮件分词共花费27.88秒 前3000篇邮件分词共花费43.70秒

第2种去除停顿词的代码写法cutWords = [k for k in jieba.lcut(mail) if k not in stopword_set]

代码语言:javascript
复制
import time
import jieba 

cutWords_list = []
startTime = time.time()
i = 0
for mail in mailContent_list[:3000]:
    cutWords = [k for k in jieba.lcut(mail) if k not in stopword_set]
    cutWords_list.append(cutWords)
    i += 1
    if i % 1000 == 0:
        print('前%d篇邮件分词共花费%.2f秒' %(i, time.time()-startTime))

上面一段代码的运行结果如下:

前1000篇邮件分词共花费5.50秒 前2000篇邮件分词共花费10.37秒 前3000篇邮件分词共花费16.20秒

从上面2种方法运行时间的对比可以看出,判断1个元素是否在集合中比判断1个元素是否在列表中效率要高。 判断1个元素是否在集合中,使用hash算法,时间复杂度为O(1); 判断1个元素是否在列表中,使用循环遍历对比的方法,时间复杂度为O(n)。 在此次分词结果去除停顿词的实践中,使用判断1个元素是否在集合中的方法,效率是判断1个元素是否在列表中的3倍左右。 64000多篇邮件分词去除停顿词共花费350秒左右,即6分钟左右。

4.3 保存分词结果

第1行代码导入pickle库 第3行代码open方法中的'wb'表示文件以二进制形式写入。 第4行代码调用pickle.dump方法将python中的对象保存到文件中。

代码语言:javascript
复制
import pickle

with open('cutWords_list.pickle', 'wb') as file:
    pickle.dump(cutWords_list, file)

4.4 加载分词结果

本文作者提供已经完成的分词结果,下载链接: https://pan.baidu.com/s/1bjPgrsXKkovdgbdpzNXOmQ 提取码: x71b 压缩文件cutWords_list.zip下载完成后,其中的文件cutWords_list.pickle解压到代码文件同级目录

代码语言:javascript
复制
import pickle

with open('cutWords_list.pickle', 'rb') as file:
    cutWords_list = pickle.load(file)

5.TfidfVectorizer模型

调用sklearn.feature_extraction.text库的TfidfVectorizer方法实例化模型对象。 TfidfVectorizer方法需要3个参数。 第1个参数是分词结果,数据类型为列表,其中的元素也为列表; 第2个关键字参数min_df是词频低于此值则忽略,数据类型为int或float; 第3个关键字参数max_df是词频高于此值则忽略,数据类型为Int或float。 查看TfidfVectorizer方法的更多参数用法,官方文档链接:http://sklearn.apachecn.org/cn/0.19.0/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html

代码语言:javascript
复制
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf = TfidfVectorizer(cutWords_list, min_df=100, max_df=0.25)

6.训练数据准备

模型需要的训练数据是纯数字的特征矩阵和预测目标值。 特征矩阵通过TfidfVectorizer模型获得,预测目标值是标签编码的结果。

6.1 特征矩阵

第1行代码调用TfidfVectorizer对象的fit_transform方法获得特征矩阵; 第2行代码打印查看TfidfVectorizer对象的词表大小; 第3行代码查看特征矩阵的形状。

代码语言:javascript
复制
X = tfidf.fit_transform(mailContent_list)
print('词表大小:', len(tfidf.vocabulary_))
print(X.shape)

6.2 预测目标值

第1行代码导入sklearn.preprocessing库的LabelEncoder类; 第3行代码调用LabelEncoder()实例化标签编码对象; 第4行代码调用标签编码对象的fit_transform方法获取预测目标值。

代码语言:javascript
复制
from sklearn.preprocessing import LabelEncoder

labelEncoder = LabelEncoder()
y_encode = labelEncoder.fit_transform(y)

7.逻辑回归模型

7.1 模型训练

最后1行代码ndarray对象的round方法表示小数点保留位数。

代码语言:javascript
复制
from sklearn.linear_model import LogisticRegressionCV
from sklearn.model_selection import train_test_split

train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.2)
logistic_model = LogisticRegressionCV()
logistic_model.fit(train_X, train_y)
logistic_model.score(test_X, test_y).round(4)

上面一段代码的运行结果如下:

0.9791

7.2 模型保存

保存模型需要先安装pickle库,安装命令:pip install pickle 调用pickle库的dump方法保存模型,需要2个参数。 第1个参数是保存的对象,可以为任意数据类型,因为有3个模型需要保存,所以下面代码第1个参数是字典。 第2个参数是保存的文件对象,数据类型为_io.BufferedWriter

代码语言:javascript
复制
import pickle

with open('allModel.pickle', 'wb') as file:
    save = {
        'labelEncoder' : labelEncoder,
        'tfidfVectorizer' : tfidf,
        'logistic_model' : logistic_model
    }
    pickle.dump(save, file)

7.3 模型加载

本文作者提供已经完成的模型文件,下载链接: https://pan.baidu.com/s/1lMbDgxBrGPsXACA_26c75g 提取码: vve6

代码语言:javascript
复制
import pickle

with open('allModel.pickle', 'rb') as file:
    allModel = pickle.load(file)
    labelEncoder = allModel['labelEncoder']
    tfidfVectorizer = allModel['tfidfVectorizer']
    logistic_model = allModel['logistic_model']

8.模型评估

8.1 交叉验证

代码语言:javascript
复制
from sklearn.linear_model import LogisticRegressionCV
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import ShuffleSplit

cv_split = ShuffleSplit(n_splits=5)
logisticCV_model = LogisticRegressionCV()
score_ndarray = cross_val_score(logisticCV_model, X, y, cv=cv_split)
print(score_ndarray)
print(score_ndarray.mean())

上面一段代码的运行结果如下:

[0.97833488 0.97756113 0.97384711 0.97709687 0.97709687] 0.9767873723305479

8.2 混淆矩阵

此节代码能够成功运行的前提是先运行7.1节或7.3节的代码。 绘制混淆矩阵的代码如下:

代码语言:javascript
复制
from sklearn.metrics import confusion_matrix
import pandas as pd

predict_y = logistic_model.predict(X)
pd.DataFrame(confusion_matrix(y, predict_y),
            columns=labelEncoder.classes_,
            index=labelEncoder.classes_)

上面一段代码的运行结果如下图所示:

image.png

8.3 报告表

绘制precision、recall、f1-score、support报告表,代码如下:

代码语言:javascript
复制
import numpy as np
from sklearn.metrics import precision_recall_fscore_support

def eval_model(y_true, y_pred, labels):
    # 计算每个分类的Precision, Recall, f1, support
    p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
    # 计算总体的平均Precision, Recall, f1, support
    tot_p = np.average(p, weights=s)
    tot_r = np.average(r, weights=s)
    tot_f1 = np.average(f1, weights=s)
    tot_s = np.sum(s)
    res1 = pd.DataFrame({
        u'Label': labels,
        u'Precision': p,
        u'Recall': r,
        u'F1': f1,
        u'Support': s
    })
    res2 = pd.DataFrame({
        u'Label': ['总体'],
        u'Precision': [tot_p],
        u'Recall': [tot_r],
        u'F1': [tot_f1],
        u'Support': [tot_s]
    })
    res2.index = [999]
    res = pd.concat([res1, res2])
    return res[['Label', 'Precision', 'Recall', 'F1', 'Support']]

eval_model(y, predict_y, labelEncoder.classes_)

上面一段代码的运行结果如下:

image.png

9.结论

本文是作者第3个NLP小型项目,数据共有64000多条。 经过交叉验证,模型平均得分为0.98左右。 最后在全部样本的f1-score指标为0.98,总体来说这个分类模型较优秀,能够投入实际应用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.09.27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0.打开jupyter
  • 1.数据下载
  • 2.数据观察
  • 3.数据准备
    • 3.1 预测目标值
      • 3.2 邮件文本文件路径
        • 3.2.1 使用trec06c文件夹中的full/index文件
        • 3.2.2 定义getFilePathList函数
        • 3.2.3 定义getFilePathList2函数
        • 3.2.4 本节小结
      • 3.3 邮件内容
        • 3.3.1 加载邮件内容
        • 3.3.2 正则表达式去除多余空格
    • 4.分词
      • 4.1 加载停顿词
        • 4.2 去除停顿词效率对比
          • 4.3 保存分词结果
            • 4.4 加载分词结果
            • 5.TfidfVectorizer模型
            • 6.训练数据准备
              • 6.1 特征矩阵
                • 6.2 预测目标值
                • 7.逻辑回归模型
                  • 7.1 模型训练
                    • 7.2 模型保存
                      • 7.3 模型加载
                      • 8.模型评估
                        • 8.1 交叉验证
                          • 8.2 混淆矩阵
                            • 8.3 报告表
                            • 9.结论
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档