赞
踩
目录
本文使用朴素贝叶斯、逻辑回归算法进行文本分类,我写的另一篇是基于TextCNN实现垃圾邮件分类:基于tensorflow2+textCNN的中文垃圾邮件分类_唯余木叶下弦声的博客-CSDN博客_邮件数据集
本文进行文本分类任务的中文邮件数据来源于由国际文本检索会议提供一个公开的垃圾邮件语料库,点我下载。分为英文数据集(trec06p)和中文数据集(trec06c),其中所含的邮件均来源于真实邮件,并且还保留了邮件的原有格式(包括发送方、接收方、时间日期等等)和邮件中文内容。第二个链接即是中文文本的邮件数据集,点击链接即可下载。下载的压缩文件夹中,一个文件代表一封邮件,通过标签“spam”、“ham”进行区别是否垃圾邮件。“spam”表示是垃圾邮件,有4万多条。“ham”表示是正常邮件,有2万多条。“spam”全拼为"stupid pointless annoying messages",直译为“愚蠢的、毫无意义的、令人讨厌的信息”。
可以看到,垃圾邮件的内容一般是广告、推销类的有害信息,那么如何从这一大堆邮件中自动识别出垃圾邮件呢?
这实际上是一个文本分类任务,即将邮件文本分为垃圾邮件和正常邮件,简单地二分类。
在互联网时代下,网络上积累了各式各样海量的数据及信息,不仅包括文本,还有声音、图像等等。文本的种类也是各式各样,可以是新闻、报告、邮件、电子书、网页内容等等,关于如何有效管理这些信息并快速实现情感分析或是文本分类等技术,这些年来出现了很多算法模型,实现效果也越来越佳。文本分类技术一直是自然语言处理领域中研究的热点之一,其应用领域非常广泛,例如本文要做的对垃圾邮件的判定,可以自动将邮件进行二分类,识别出垃圾营销的邮件。
文本自动分类技术相关的研究始于上世纪50年代末,那时候的文本分类主要是基于知识工程,用人工定义相应规则的方法来对文本进行分类,举个简单的例子:从一本笔记上分类出语数英各科的笔记内容,基于生活经验可以试着这样判断:带有公式的内容是数学知识,而用带有字母书写的是英语的内容,其余的就是语文知识了。
显然这种方法十分耗费时间和精力,并且需要对文本涉及领域有一定的了解,可迁移性较差,准确率也很低。分类的质量严重依赖于人工所制定的规则的好与坏,而知识工程最大的缺陷之处在于:不能简单移植到其他领域,所以完全没有推广价值。例如,一个针对教育领域构建的分类系统,如果要将它移植用在银行、保险等领域,其中对教育领域制定的规则就完全不能适应新的领域,需要推翻并重新制定规则。
到了上世纪90年代,当统计学习、机器学习方法不断发展,文本分类问题有了新的解决思路,即人工特征工程与浅层分类相结合,所以此问题就拆分为特征工程和分类器两个部分。首先通过在已经分类好的数据集上进行训练,从而建立判别规则或是分类器,即可对计算机没有“见过”的文本内容也就是测试集自动分类,最终得到输入样本的类别。其分类准确度比得上专家手工分类,且其学习不需要人工干预,有很好的可迁移性和稳定性,在这之后被广泛应用。
与英文相比,中文的文本分类存在不同之处:
(1)、由于语言的差异,分词的思路不一样。中文文本分类需要从文本中切分出词汇,而英文通过空格和标点即可区分词汇。中文文本分类一般使用分词工具jieba,而英文分词常用NTLK,即Natural Language Toolkit,是自然语言处理工具包。
(2)、停用词不一样。停用词是文本中一些普遍使用的词语,对文档分析作用不大,在文档分析之前需要将这些词去掉。对中文文本来说,类似“他”、“是”、“之一”、“的”这样的词汇都会被去除,而英文需要消除“an”、“in”、“the”等。可以用分词工具提供的停用词,也可以自己构建一个停用词库并导入到程序中。
(3)、对于英文文本还需要多一步词根还原的操作。如writed和writing都应该还原成write。而中文词汇不需要进行此类操作。
不过中英文文本分类任务大致上的流程是一样的,一般是文本预处理、特征提取、构建分类模型并训练、评估模型等步骤。
文本预处理:大多数情况下,在文本内容中存在很多对分类任务无用甚至会妨碍的东西,例如特殊字符、停用词等。就要依次来对文本内容进行清洗,刨除不需要的部分。
特征提取:在分好词之后,不同的词汇对分类任务的贡献也不一样,例如,一看到“发票”、“五折”、“借贷”就能够判断大概率是垃圾营销内容,而看到“联系”、“发布”等词汇则无法做出判断。那么如何衡量不同词汇对分类任务的贡献程度呢?比如可以通过单词出现的次数,次数越多就表示越重要,但对于“你好”、“回信”这类常用词依然无法做出判断。所以合适的方法是计算词语的TF-IDF
值,词语的TF-IDF
值可以描述一个词语对文档的重要性,TF-IDF
值越大,则表示该词汇越重要。
建模:在提取特征之后,就可以按一定比例将数据划分为训练集、测试集,并使用训练集来对分类器进行训练。
评估:可以利用准确率、召回率等一些指标来衡量分类器的好坏与否。
朴素贝叶斯算法是基于贝叶斯公式的一种机器学习算法,著名的贝叶斯公式源于英国数学家Thomas Bayes,发表于18世纪,历史悠久,被用来解决条件概率问题。
求解事件发生的条件下,事件
发生的概率:
其中,称为先验概率,
称为后验概率。
当事件发生的条件下,可能发生多个事件时。求解其中事件
其中,
可以用贝叶斯公式来求解这样一个问题:
在学校里男生、女生的比例各是60%、40%,而所有男生都穿裤子,女生中穿裤子、裙子的比例各是50%。那么如果看到一个人穿裤子,这个人是女生的概率是多大呢?
用贝叶斯公式求解:
其中:
,表示人群中女生的比例是0.4。
,表示女生中穿裤子的概率是0.5。
,表示人群中男生的比例是0.6。
,表示男生中穿裤子的概率是1。
则在事件发生的条件下,判定为是
的概率是:
现在要对邮件进行分类,识别出垃圾邮件和正常邮件。使用朴素贝叶斯分类器来判断,就是在预先知道特征的条件下对是否垃圾邮件的概率做预测,即
feature就是特征,也是识别出垃圾邮件的依据。比如要从水果中识别出香蕉、苹果,可以从颜色、形状等特征分析。那么文本有什么特征呢?
在回答这个问题之前,可以先来想一想,按我们人的思维是怎么根据邮件的内容来识别出垃圾邮件的呢?垃圾邮件可不会自己在邮件中标明自己是垃圾邮件,如果垃圾邮件也会以通用的格式说“尊敬的先生你好……期待您的回复”,那么我们如何判断它是人际往来的正常聊天内容还是营销广告类的内容呢。
就是根据关键词来做出判断,即将关键词作为特征。
比如出现“发票”、“五折”、“借贷”等词时,可以判断这封邮件大概率是垃圾邮件。因为通过我们的生活经验可以知道,这些词语经常在垃圾邮件中出现,即先验概率比较高。
那么就可以计算后验概率
可以这样来求:这些words出现在spam中的次数/spam中所有词的总数。
表示一封邮件是垃圾邮件的概率,在我们的数据集中,垃圾邮件有42854封,而邮件总数是64620,那么
则表示在所有邮件中出现这些words的概率。
以上只是简单地举例描述,实际上的计算更为复杂,在后面的代码中会使用TF-IDF值来作为数据特征对分类器进行训练。
逻辑回归(Logistic Regression)逻辑回归算法原理简单,可解释性较强,在文本分类领域也有广泛的应用。它与线性回归(Linear Regression)都是一种广义线性模型(generalized linear model)。只不过线性回归的参数学习规则使用的是线性函数,预测的是一个值,不适合进行分类。而逻辑回归通过Sigmoid函数引入了非线性因素,所以可以用逻辑回归来完成分类任务,将任意的输入映射到了[0,1]区间,这样就完成了由值到概率的转换,从而进行二分类的操作,可以说Sigmoid函数是逻辑回归进行分类任务的核心。
sigmoid函数:
求导:
若将上式中sigmoid函数的输入t用表示,即:
其中,的个数和特征的数量是一致的,有多少个特征,就有多少个参数,实际上是根据特征来拟合一个平面。所有满足
而逻辑回归模型是通过sigmoid函数对上面的线性模型进行映射,分类任务实际上是概率问题,对于分类的结果,是得到一个概率值,而决策边界也就变成了P=0.5,即概率值大于0.5被划分为垃圾邮件,小于0.5被划分为正常邮件。
在这里标签y是一个离散型的随机变量,。它的概率分布取决于参数θ。即:
将这两个公式整合起来,用一个公式表示就是:
然后,我们当然希望分类的准确率越高越好,也就是随机的邮件被正确分类的概率最大化。那么就可以用最大似然估计来找到似然最大时所对应的参数,似然就是在给定观测数据的情况下描述参数值的可信度。
举个例子:
假设抛硬币正面朝上的概率值是θ,而每次掷硬币的结果是独立同分布的(服从同一分布,并且互相独立,互不影响)。如果这时连续抛了三次,都是正面朝上,那么似然函数可以写成:
是样本观察值,似然函数在θ=1时取得最大值。那么此时就可以认为抛硬币正面朝上的概率θ=1是最可信的。
如果再抛一次,这时正面朝下,那么似然函数可以写成:
是样本观察值,在此前提条件下,似然函数在θ=0.75时取得最大值(可通过求导计算极值)。那么此时就可以认为抛硬币正面朝上的概率θ=0.75是最可信的。
可以看到,似然函数是一个累乘的计算,当样本数量非常庞大时,就难以通过求导来计算极值了。那么此时可以在似然函数两边取对数,将累乘变成累加的形式,从而进行求解。并且这样的转换是不会影响似然函数的变化趋势,对数似然函数的极值点就是似然函数的极值点。
再回到我们的项目中,似然函数就是:
两边取对数,对数似然函数即:
然后对对数似然函数求导,就可以得到θ的梯度(此处省略了推导过程):
我们最后要求得的是否垃圾邮件的概率实际上是参数θ的函数,而x是给定的。那么想让概率越大,就要使得θ越大,就是一个梯度上升问题。
基于此就可以得到θ的学习规则:
最后将求解得到的最优θ带入sigmoid函数,即可以求出最终的概率值,大于0.5就认定它是垃圾邮件,小于0.5就认定是正常邮件。
- #导入程序运行必需的库
- import numpy as np
- import pandas as pd
- import matplotlib.pyplot as plt
- import re
- import os
- import jieba
- import itertools
- import time
- from sklearn.model_selection import train_test_split
- from sklearn.metrics import confusion_matrix,recall_score
- from sklearn.naive_bayes import MultinomialNB
- from sklearn.linear_model import LogisticRegressionCV
- from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
通过index文件来获取所有邮件存放的路径,并提取标签,将标签值用0、1进行标记。随后利用路径列表依次取出邮件的内本内容,同时去除一些特殊字符。最后我们得到两个列表,一个存放所有邮件正文文本,一个存放邮件是否是垃圾邮件的标记值。
- # 通过index文件获取所有文件路径及标签值
- def get_Label():
- mail_index = open("./trec06c/full/index", "r", encoding="gb2312", errors='ignore')
- index_list = [t for t in mail_index]
- index_split = [x.split() for x in index_list if len(x.split()) == 2] # 分割了标记和路径
- path_list = [y[1].replace('..', './trec06c') for y in index_split]
- label_list = [ 1 if y[0] == "spam" else 0 for y in index_split] #1:垃圾邮件;0:正常邮件
- return path_list, label_list
-
- # 根据路径打开文件 并提取每个邮件中的文本
- def get_Text(path):
- mail = open(path, "r", encoding="gb2312", errors='ignore')
- TextList = [text for text in mail]
- XindexList = [TextList.index(i) for i in TextList if re.match("[a-zA-Z0-9]", i)]
- text = ''.join(TextList[max(XindexList) + 1:]) #去除邮件头
- text = re.sub('\s+','',re.sub("\u3000","", re.sub("\n", "",text))) #去空格分隔符及一些特殊字符
- return text
-
- # 获取path、label列表
- path_list, label_list = get_Label()
- # 获取所有文本
- content_list = [get_Text(Path) for Path in path_list]

将自定义的停用词库导入。现将文件放在百度云上,有需要的可自行下载。
链接:https://pan.baidu.com/s/1K9v9w4mmlbhL3iVKfn-zOA
提取码:abcd
- #加载停用词
- with open('./stopwords.txt', encoding='utf8') as file:
- file_str = file.read()
- stopword_list = file_str.split('\n')
用分词工具jieba来对邮件文本进行切分,同时去除停用词。
- #jieba分词
- cutWords_list = []
- startTime = time.time()
-
- for mail in content_list:
- cutWords = [k for k in jieba.lcut(mail) if k not in set(stopword_list)]
- cutWords_list.append(cutWords)
-
- print('jieba分词用时%.2f秒' %(time.time()-startTime))
jieba分词用时1547.17秒
从运行结果可以看出,对6万多封邮件分词用了接近26分钟。
TF即Term Frequency,即词频(词语出现的频率),也就是一个词语在邮件中出现的次数,次数越多表示该词语越重要。
计算公式:一个词语的词频TF = 词语出现的次数 / 邮件中的总词汇数量。
通过函数CountVectorizer()来完成计算TF的步骤,以分好词之后的列表为输入,并得到词频输出。
在这里,只取前max_features=5000个词汇作为关键词集,并去除在60%邮件中都出现过的词语(因为没有区分度),去除只在5个以下数量的邮件中出现的词语(出现次数太少,也难以区分)。
- content_text = [' '.join(text) for text in cutWords_list]
-
- cv = CountVectorizer(max_features=5000,max_df=0.6,min_df=5)
- counts = cv.fit_transform(content_text)
比如(0,9) 2表示在序号为0的邮件文本中的序号为9的词语,出现了2次。
IDF即Inverse Document Frequency,即逆文本词频,是指一个词语在邮件中的区分度。可以这样认为:一个词语出现在的邮件数越少,这个词语对该邮件就越重要,就越能通过这个词语把该邮件和其他邮件区分开。
计算公式:一个词语的逆文本词频 IDF = log(数据集的邮件总数 / 包含该词语的邮件数 + 1)
TF-IDF值即是一个词语的TF值乘上IDF值。通过计算词语的TF-IDF值,就可以把TF-IDF值高的词汇,作为邮件的特征属性。TF-IDF是一种常用于信息检索与数据挖掘的加权技术,可以使得经常出现、没有区分度的词语的权重减少,从而减少这些词的重要性。所以TF-IDF被广泛地应用在文本分类领域,可以帮助分类系统取得更佳的效果。
在这里通过函数TfidfTransformer()来实现计算TF-IDF值,以上一步的词频输出作为输入,从而可以得到TF-IDF格式的输出。
- tfidf = TfidfTransformer()
- tfidf_matrix = tfidf.fit_transform(counts)
混淆矩阵(confusion matrix),又称为可能性表格或是错误矩阵。它是一种用来可视化呈现算法性能的特定矩阵,每一列代表预测值,每一行代表的是实际的类别即真实值。
- def plot_confusion_matrix(cm, classes,title='Confusion matrix',cmap=plt.cm.Blues):
- plt.imshow(cm, interpolation='nearest', cmap=cmap)
- plt.title(title)
- plt.colorbar()
- tick_marks = np.arange(len(classes))
- plt.xticks(tick_marks, classes, rotation=0)
- plt.yticks(tick_marks, classes)
-
- thresh = cm.max() / 2.
- for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
- plt.text(j, i, cm[i, j],
- horizontalalignment="center",
- color="white" if cm[i, j] > thresh else "black")
- plt.tight_layout()
- plt.ylabel('True label')
- plt.xlabel('Predicted label')

sklearn 库中的 naive_bayes 模块实现了 5 种朴素贝叶斯算法:BernoulliNB伯努利朴素贝叶斯、CategoricalNB分类朴素贝叶斯、GaussianNB高斯朴素贝叶斯、MultinomialNB多项式朴素贝叶斯、ComplementNB补充朴素贝叶斯。
其中,MultinomialNB多项式朴素贝叶斯适用于离散型数据,符合多项式分布的特征变量,如词语的TF-IDF值,所以在这里使用MultinomialNB算法来进行分类任务。默认参数alpha=1,即使用拉普拉斯平滑处理,采用加 1 的方式,来统计没有出现过的词汇的概率,从而避免了因训练集样本不充分而导致概率计算结果为0的问题。并且当训练集足够大时,修正过程引入的先验的影响也会变得可以被忽略,使得估值渐趋向于实际概率值。
- train_X, test_X, train_y, test_y = train_test_split(tfidf_matrix, label_list, test_size=0.2,random_state=0)
-
- mnb = MultinomialNB()
- startTime = time.time()
- mnb.fit(train_X, train_y) #训练过程
- print('贝叶斯分类器训练用时%.2f秒' %(time.time()-startTime))
-
- sc1 = mnb.score(test_X, test_y) #在测试集上计算得分
- print('准确率为:',sc1)
-
-
- y_pred1 = mnb.predict(test_X)
- print('召回率为:', recall_score(test_y, y_pred1 ))
- plot_confusion_matrix(confusion_matrix(test_y,y_pred1) ,[0,1])
- plt.show()
- 贝叶斯分类器训练用时0.12秒
- 准确率为:0.9586041473228103
- 召回率为:0.9738501050665421
可以看出,贝叶斯算法的分类准确率达到95.9%,还算比较不错,而且训练过程非常快速,几乎不耗时。
相比较LogisticRegression, LogisticRegressionCV不需要传入正则化系数作为参数,而是使用交叉验证来自行选择一个最合适的正则化系数C。
- lr= LogisticRegressionCV(max_iter=10000)
- startTime = time.time()
- lr.fit(train_X, train_y)
- print('逻辑回归分类器训练用时%.2f秒' %(time.time()-startTime))
-
- sc2 = lr.score(test_X, test_y)
- print('准确率为:',sc2)
-
- y_pred2 = lr.predict(test_X)
- print('召回率为:', recall_score(test_y, y_pred2 ))
- plot_confusion_matrix(confusion_matrix(test_y,y_pred2) ,[0,1])
- plt.show()
- 逻辑回归分类器训练用时81.10秒
- 准确率为:0.980578768183225
- 召回率为:0.9956805977118842
第一次在使用逻辑回归算法进行训练时,出现了一连串的警告内容:
ConvergenceWarning: lbfgs failed to converge (status=1): STOP: TOTAL NO. of ITERATIONS REACHED LIMIT. Increase the number of iterations (max_iter) or scale the data as shown in:
原因是逻辑回归模型的最大迭代次数默认为1000,此时系数没有收敛,而迭代总数达到限制。只需将max_iter参数设置为一个更大的数目即可。
从输出结果可以看出,逻辑回归算法的准确率要稍好于贝叶斯算法,但是训练用时一分钟多,要比贝叶斯算法长得多。
贝叶斯决策论在机器学习、模式识别等很多涉及数据分析的领域都有着广泛的应用,朴素贝叶斯分类器即是对贝叶斯定理进行近似求解,其核心就是贝叶斯公式,算法思想十分简单,有着强大的可解释性,本质就是针对词频特征的数学运算,所以运行速度相对于逻辑回归模型还是比较快的。而为了避免概率求解时面临的样本稀疏的问题,朴素贝叶斯分类器引入了属性条件独立性假设,属性指的就是特征,也就是假设特征与特征之间是没有联系的,相对独立。但事实上在同一邮件中,词与词之间一定是有着大大小小的联系的。除此之外,对于词频的计算只考虑一个词,忽略了词的顺序,所以对于“我吃饭”和“饭吃我”这两句话在分类模型中是没有差别的。不过也可以在使用CountVectorizer()计算词频时,通过设置参数ngram_range来切分出新的词组,即相邻的n个词作为词的组合,不过如果n的数目太大,会导致组合爆炸。考虑到忽略这些因素对分类结果并不会产生太大的负面影响,而且可以方便计算,就直接略去了。
逻辑回归模型在文本分类问题上也应用十分广泛,在线性回归的基础上,通过用sigmoid函数来计算概率从而进行二分类,和朴素贝叶斯模型一样,同样有着很好的可解释性。在LogisticRegressionCV中默认使用l2正则化。如果特征数目非常多,想让一些不重要的特征系数归零,从而让模型系数稀疏化的话,也可以使用L1正则化,由于没有连续导数这时只能使用liblinear优化算法,通过使用坐标轴下降法来迭代优化损失函数。经过测试,训练用时13秒,不过在准确率、召回率上并没有太大的变化。
总的来说,这两个算法模型比较明显的不同之处在于,逻辑回归属于判别式模型,而朴素贝叶斯属于生成式模型。也就是说,两者的目标虽然都是最大化后验概率,但逻辑回归是直接对后验概率进行建模,而朴素贝叶斯是对联合概率
进行建模。而且之前提过,朴素贝叶斯分类器是建立在属性条件独立假设下,对数据样本的要求更加严格,不适用于样本属性之间有强关联的情况。所以逻辑回归模型对特征工程的要求相对朴素贝叶斯模型更低,其应用范围要也就比后者更广。除此之外,逻辑回归模型可以通过梯度下降方法、牛顿法等优化方法来对参数θ进行优化,而朴素贝叶斯分类器的相关参数则是有固定的形式,不需要优化参数。
当数据量比较小时,朴素贝叶斯分类器相比逻辑回归分类器可以取得更好的效果。而随着数据量不断增大、特征维度不断增加,逻辑回归分类器的效果要更优秀一些,这也是本项目中逻辑回归分类器的准确率要略好于朴素贝叶斯分类器的原因。
参考:
1、唐宇迪老师python数据分析与机器学习实战课程
2、周志华老师《机器学习》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。