文本预处理
英文文本的分类语料选择公共的“20种新闻组”语料集,该资料集包括20个新闻分类总共两万篇新闻左右。为了对每篇新闻的类别进行分类,需要去掉常用停用词之后将每篇文章转换成TF-IDF词特征向量,在此基础上再对每篇文章的类别进行分类。
将数据集下载并且解压后,因为数据集较小,可以将文本内容全部读取到内存中:
这里传给函数的第一个参数是包含各个类别新闻文本的子文件夹的上层目录;categories参数用于指定选择加载的新闻文本类别,这里赋值None表示加载指定目录下的所有子目录的文本;后面的load_content和encoding表示以’latin1’的编码格式解析新闻文本的内容并加载到内存中;因为后面的许多算法优化时用的是随机梯度下降,而SGD的假设是每个样本之间是独立同分布的,因此将文本加载到内存后进行shuffle操作,随机打乱文本的顺序。load_files()函数返回一个Bunch对象,其中的’data’保存的是所有新闻文本的纯文本内容;’target’表示整数型类别标签向量;’target_names’表示’target’中每个整数对应的字符串类别标签。
然后需要计算每篇文章的词频向量构成词频矩阵,利用词频矩阵计算由TF-IDF组成的文档-词矩阵,不过在这两步之前需要对一些不具有实际意义的虚词、介词、感叹词等停用词进行过滤处理。
参数stop_words = ‘english’表示采用sklearn内建的英文停用词表进行过滤,也可以将我们自定义的停用词表通过list的方式传递给该参数;得到的x_train_counts表示文档-词的词频矩阵,再经过TfidfTransformer函数处理后得到的x_train_tfidf才是我们所想要的文档-词的TF-IDF矩阵,每一行代表一个新闻文本,每一列代表一个词在各个新闻文本中的TF-IDF值,相当于一个特征,所有的这些特征构成一个词特征空间,我们的分类模型在这个空间中寻找分类面进行文本分类任务。
KNN
KNN模型不需要学习过程,直接在训练数据集上寻找到K个与待预测样本最为接近的其他样本,根据这K个邻居的输出值来预测新样本的输出值。这个模型最重要的是选择邻居的度量方法和邻居个数K的选择。在文本分类中用的较多的度量方式是文本TF-IDF向量之间的余弦相似度,K的选择我们可以用GridSearchCV进行交叉验证选择。这里进行了多轮的K值选择,最开始K的选择跨度比较大(10, 50, 100, 200),在得到这一轮中最好的K是100后,再以100为中心,在跨度比较小的范围重新开始一轮K的选择(80, 100, 120),这个过程进行了四次,后面寻找K时尽可能的靠近前一轮的最好值。训练集比较大时,为了较快的得到每一轮的最佳K值,可以取一部分训练集来做交叉验证(我选择了10折),如果想得到结果比较好的K值得话,可以用全部的训练集来做CV,付出的时间代价会高一点。
因为每次预测一个新样本的输出值都需要从训练集中寻找到最近的K个邻居,因此如果用暴力搜索(brute-force)的方式来寻找的话,时间复杂度会比较高。为了加快K个邻居的搜索过程,有KDTree和BallTree两种数据结构用于组织训练集样本,其中KDTree适用于属性较少的数据集,属性较多时KDTree的性能会下降,此时用BallTree会更合适。在scikit learn的KNeighborsClassifier函数中可以指定这三种寻找最佳K近邻的方法,但是对于稀疏数据来说,只支持brute force的方式,因为文本的TF-IDF向量是以稀疏矩阵csr-matrix的格式存储的,因此只能够选择brute force方法。根据余弦相似度找出K个最近邻之后,一般采取多数投票来确定新样本的类别,也可以根据新样本与K个最近邻的相近程度(这里就是余弦相似度)来确定邻居的投票权重,设置参数weights=’distance’即采取后一种方法。
从结果来看,KNN的分类效果一般,总体准确率只有73%,许多类的F1值在0.6和0.7附近,最好的F1值没有破90%,而最后一个类的F1仅仅为47%。如果在KNN之前TF-IDF矩阵进行PCA处理,选择1000个主成分,采用GridSearchCV交叉验证选择350个邻居进行预测,得到的测试结果为:
总体来看,准确率有少许提高,总体的精确率、召回率及F1值也有提高,一部分类的F1值又提高,也有许多类的F1值由下降,特别是最后一个类的F1值从0.47减少到0.35.
朴素贝叶斯
我们采用朴素贝叶斯模型对文本进行分类,并对新的字符串进行预测:
MultinomialNB代表样本特征对类别服从多项式分布前提下的朴素贝叶斯模型,常用于文本分类应用。利用最大似然估计特定类下样本的条件分布为:
$$
\hat \theta_{yi} = {{N_{yi}+\alpha} \over {N_y + \alpha n}} \\
$$
其中$N_{yi} = \sum_{x \in T} x_i$表示在训练集T中特征i出现在类别y的样本中的次数,$N_y = \sum_{i=1}^{\| T \|} N_{yi}$表示在类别y中所有特征的次数和。平滑项$\alpha$的加入是为了避免零概率的出现,$\alpha = 1$称为拉普拉斯平滑,$\alpha \lt 1$称为Lidstone平滑。
待预测的文本docs_new需要用count_vect和tfidf_transformer对象进行转换以保证转换的一致性,另外就是预测的结果predicted是类别对应的整数,实际的字符串类别需要在Bunch的target_names列表中索引得到。
上面的词频统计、TF-IDF计算、模型拟合这三个过程可以通过scikit-learn提供的Pipeline进行串接,如下所示:
训练得到的text_clf_pipeline跟之前的clf模型是一样的,那为什么需要用Pipeline来做呢?官网的解释是”assemble several steps that can be cross-validated together while setting different parameters”,可知Pipeline主要是方便利用交叉验证进行参数选择的。我们将测试集加载进来,并用text_clf_pipeline进行预测:
从结果可以看到朴素贝叶斯模型总的分类准确率为81.7%,从recall列来看,talk.religion.misc,talk.politics.misc的召回率比较低,特别是前者,尽管两者的精确度均较高。精确率方面comp.sys.ibm.pc.hardware,soc.religiion.christian,talk.politics.guns比较低。最后一列support表示每一类下实际包含文本的总数。如果需要对分类模型或者文本处理的过程中的参数利用交叉验证进行选择的话需要将参数传递给GridSearchCV函数:
|
|
需要选择的参数通过字典的形式传递,参数字典的key部分以’__’分隔,前半部分代表text_clf_pipeline构建时各个处理阶段对应的名称,后半部分代表传递给对应阶段的函数的待选择参数,单参数以元组形式传递,多参数以元组组成的列表传递。如上所示,我们需要选择$2 \times 3 \times 2 = 12$种可能的模型,GridSearchCV中的n_jobs=-1表示利用所有的处理核进行计算。因为只选择了前500个文本作为模型参数的选择语料,因此得到的结果与之前的81.7%不同。
朴素贝叶斯模型一个较为严苛的前提假设是特征之间在同一个类别下是条件独立的,在这一个前提假设下,根据样本的各个特征在类别下的条件分布是高斯分布、多项式分布以及多元伯努立分布从而得到不同的朴素贝叶斯模型,高斯分布的朴素贝叶斯不常用于文本分类,这是因为文本特征的稀疏性导致不符合高斯分布的前提,因此在高斯分布的朴素贝叶斯没有针对稀疏数据进行实现。常用于文本分类的是多项式朴素贝叶斯模型,多元伯努立朴素贝叶斯适用于每个特征均为二值的情况。考虑到PCA可以去除特征之间的相关性,而多项式朴素贝叶斯要求每个特征的值必须大于0,因此实际上经过PCA转换的数据一般无法应用其分类。又考虑到文本特征的取值非二值,因此这里用高斯分布的朴素贝叶斯进行PCA处理后的文本分类。
引入TruncatedSVD是因为它是专门针对稀疏数据进行PCA分解的方法,而且不会事先自动对数据进行center处理,在文本分类的环境下,该方法推荐主成分取100,具体的SVD分解算法选择了arpack,因为这里是稀疏数据,该方法效率更高。从结果来看,分类效果比较差,总的准确率比较低,大部分类的F1值也在0.6左右,我们将主成分增加到500再看结果如何。
主成分的增加应该会为高斯朴素贝叶斯分类提供更多的信息,但是准确率和平均F1值的降低说明高斯朴素贝叶斯在文本分类中的效果较差。
支持向量机
另一个在文本分类中常用的分类模型是支持向量机,因为SVM的参数较多,采用GridSearchCV在训练集上采用交叉验证选择参数:
SGDClassifier是利用随机梯度下降进行优化的线性分类器集合,参数loss的不同取值对应不同的模型,这里取’hinge’表示线性SVM。我们用这个在训练集上效果最佳的SVM模型在测试集上测试它的效果:
可以看到,SVM模型对于本文本的分类效果比之前基于多项式朴素贝叶斯的分类效果要好一点,但是最后两个类的文本召回率仍然比较低,可以在SVM模型中加大这两个类的权重。
随机森林
下面是用随机森林进行文本分类的结果,同样,类别权重的初始化采取balanced的方式,在不限制RF中每颗树的高度、叶子结点最少结点个数等参数的情况下,建立1500颗树的分类结果只是略微的好于建立500颗树的情况(如下),在训练时间远远超过多项式朴素贝叶斯和SVM的前提下,预测的精确率、召回率和F1值在绝大多数类上比SVM的结果差,跟NB的结果接近。因此实际上RF不具有明显优势。
随机森林有两个主要的特点:一是建立每颗树的样本是从原始样本集中bootstrap抽样得到的样本集;二是树的每个结点的分裂变量的选择是全部特征变量的一个随机子集,从这个随机的子集中选择最佳的分裂变量和分裂点。
ExtremelyRandomizedTrees
另一种森林:Extremely Randomized Trees同样是建立许多子树,然后综合全部子树的结果来预测输出值,但是与RF相比一个最大的不同在于分裂变量和分裂值的选择:对于随机子集中的所有特征变量,随机采样变量中的一个值作为候选分裂值,从这些候选分裂值中选择最好的那个分裂值和对应的分裂变量。这种随机性的增加使得每颗子树的bias会变大,但是会使得总体模型的variance降低。我们训练500颗树来建立Extremely Randomized Trees模型:
可以看到Extremely Randomized Trees的效果在子树数量相同的情况下(其他参数默认)比RF的效果会稍微好一点,18个类的F1值有平均3%的提高。
多层感知机
我们可以将神经网络应用于文本分类问题,采用最简单的多层感知机模型,因为每个文本的向量特别的稀疏,也就是说输入层大部分为0,因此第一个隐藏层的神经元个数可以设置的小一点,我们设置三个隐藏层,分别为100、40和20,采用Adam优化方法,得到的测试结果如下:
可以看到,准确率方面比多项式朴素贝叶斯好一点,接近SVM的分类效果,但是最后两个类的召回率上多层感知机的效果要好很多。上面的感知机模型具有比较多可选的参数,默认情况下,激活函数为ReLU,优化方法为Adam(这种方法在较大的数据集上的效果较好,如果是小型的数据集,用L-BFGS来优化更新权重速度和效果上会好一点),权重参数采取L2范数为正则化方法,对应系数alpha默认取0.0001。但是提供的三种学习步长方案只适用于随机梯度下降的优化方法。