文本分类实践:基于sklearn与gensim模块

理论分析

通过之前过数据挖掘课程的学习,尝试做过几个基于机器学习的多文档自动摘要,基于文本内容相似度的引文推荐系统,因此对于文本处理的基本流程有了一个比较清晰的认识。

无论是挖掘文本内容的语义信息,还是文本之间的相似度,我们的第一步都是进行数据预处理。因此为了用数学语言来描述文本数据,引入向量空间模型(VSM)。我们可以将一篇文档视作向量空间中的一个向量,而将该文档中的每一个词项作为向量空间中的一个轴,将该向量在每一个轴上的投影等同于该向量在该方向上的权重。

已经知道向量空间中的每个轴代表一个词项,我们考虑计算每个词项的权重,也即是该词的重要程度。再引入TF-IDF表示,具体公式在此不做赘述。TF反映的是某词项在该文档中的重要程度,而IDF反映的是某词项在文档库中的普遍重要性,因此TF-IDF达到高权重的词项必定是在某一特定文件中高频出现,并且在整个语料库中有着低文件频率,该指标可以很好地表征某词项的重要程度,这就为我们接下来的特征提取提供了理论基础。考虑到多篇文档组成的文档集合,我们可以用TFIDF算法得到句子的重要程度,而它倾向与选出在较少文章中出现过的,但在同一篇文档中出现次数较多的词项。因为符合这种特征的词项对于文档来说比较具有区分度。

我们也可以看出向量空间表示方法的特点:

  • 语义鸿沟:每个词项与其他词项之间对应的特征方向都相互正交,因此语义上十分相似的词项,在向量空间中也没有任何关系;对于同一个单词他在空间中只对应一个特征方向,因此向量空间模型也无法处理在自然语言中常出现一词多义,同义词等的现象。
  • 高维性:在文档集合的规模较大的时候,为了给每一个此项都指定一个维度,因此会造成向量空间表示的维度过高,也就是常提到的维数灾难(Curse of Dimensionality)
  • 稀疏性:对于文本较短而文档数模较多时,稀疏性问题尤其明显。因为对于高维的向量空间,每个文档可能只出现了其中极小一部分词,因此该向量极其稀疏。

我们可以通过特征选择和提取,在保证数据中信息损失在容忍范围内的情况下,舍弃一些不重要的信息。我之前的做法是对个个词项按照TF-IDF排序,设定阈值,舍弃掉权重低于阈值词项,进而达到降维的效果。

对于语义鸿沟,我们引入隐性语义分析(LSI),将文档向量通过奇异值分解(SVD)的方法从向量空间映射到隐形语义空间中。与向量空间相对的,隐形语义空间的维度不再是词项,而是文档集合中的主题,或者概念。因为文档的主题数远小于文档中的词项总数,所以隐性语义分析不仅能够在一定程度上解决同义词的问题,还能够进行降维。其缺点是由于没有先验知识,在指定主题数目的时候我们只能拍脑袋选取。好在此次的数据集20newsgroup已经告诉我们新闻共有20个主题,因此我们可以果断地选择20个隐形语义空间维度,20个聚类中心。

因为向量空间中的向量极其稀疏,如果密集表示的话会造成储存空间的浪费,以及运算速度减缓。我们考虑用稀疏的方式表示该向量,实际上python中的稀疏表示的原理是利用三元组,其中两个整数表示该元素的位置,另外一个浮点数表示该元素的标量值。

编程实现

理论分析如上,接下来进行实验。实验分为两部分,第一部分是基于sklearn模块进行的,第二部分是基于gensim模块进行完整的文本分类流程。

第一部分

这次的数据集用到了sklearn自带的数据集中的fetch_20newsgroups,该数据集是包含了20个文章分类下的共18846篇新闻文本。

基本的处理流程为:数据采集,特征提取,模型训练,模型评估。为了获取文本特征,我们利用sklearn模块中feature_extraction.text的文本特征提取函数 CountVectorizer和TfidfTransformer,分别获得文本的词频数据,以及TF-IDF。笔者考虑使用朴素贝叶斯算法来对文档进行分类,训练出模型之后再对训练集合进行验证。具体代码实现如下:


import numpy as np
from sklearn import datasets 
from sklearn import metrics
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB

#该数据集已经对新闻文本进行了测试集和训练集的划分,并将测试集的顺序进行打乱
newsTrain = datasets.fetch_20newsgroups(subset='train') 
newsTest = datasets.fetch_20newsgroups(subset='test', shuffle=True, random_state=7)

#进行特征提取:词频,TFIDF
count_vect = CountVectorizer()
tfidf_transformer = TfidfTransformer()
X_trainCounts = count_vect.fit_transform(newsTrain.data) 
X_testCounts = count_vect.transform(newsTest.data)
X_trainTfidf = tfidf_transformer.fit_transform(X_trainCounts) 
X_testTfidf = tfidf_transformer.transform(X_testCounts)

#利用训练集训练出一个服从高斯分布的贝叶斯分类器模型
clf = MultinomialNB().fit(X_trainTfidf, newsTrain.target)

#利用sklearn模块中的metrics评估分类器效果
predicted = clf.predict(X_testTfidf)
print(metrics.classification_report(newsTest.target, predicted,target_names=newsTest.target_names))
print("accurary\t"+str(np.mean(predicted == newsTest.target)))

输出结果如下:


                          precision    recall  f1-score   support

             alt.atheism       0.80      0.52      0.63       319
           comp.graphics       0.81      0.65      0.72       389
 comp.os.ms-windows.misc       0.82      0.65      0.73       394
comp.sys.ibm.pc.hardware       0.67      0.78      0.72       392
   comp.sys.mac.hardware       0.86      0.77      0.81       385
          comp.windows.x       0.89      0.75      0.82       395
            misc.forsale       0.93      0.69      0.80       390
               rec.autos       0.85      0.92      0.88       396
         rec.motorcycles       0.94      0.93      0.93       398
      rec.sport.baseball       0.92      0.90      0.91       397
        rec.sport.hockey       0.89      0.97      0.93       399
               sci.crypt       0.59      0.97      0.74       396
         sci.electronics       0.84      0.60      0.70       393
                 sci.med       0.92      0.74      0.82       396
               sci.space       0.84      0.89      0.87       394
  soc.religion.christian       0.44      0.98      0.61       398
      talk.politics.guns       0.64      0.94      0.76       364
   talk.politics.mideast       0.93      0.91      0.92       376
      talk.politics.misc       0.96      0.42      0.58       310
      talk.religion.misc       0.97      0.14      0.24       251

             avg / total       0.82      0.77      0.77      7532

可见,再假设每篇新闻文档之间独立同分布于高斯分布时,朴素贝叶斯的分类效果尚可,准确率达到了0.7738980350504514。分析其原因,是机器学习与深度学习不同,利用朴素贝叶斯分类器还是抓住了文本的关键词来进行区分,而没有从文章的语义方面分析,因此导致分类效果差强人意。

第二部分

接下来尝试按照标准的文本处理方法,使用gensim进行标准的文本处理,以及构造一个有趣的相关课程推荐的小系统。该数据集是Coursera的课程数据,数据集中的文本包括课程的名字以及课程简介。每行一条课程信息,名字和简介之间用制表符分割开来。

完整的处理流程是对文档进行分句,分词,去除停用词,并且词干化。得到每篇文档的词干化的单词集合之后,用其构建词典,词袋模型,TFIDF模型,LSI模型等等。然而之前用sklearn自带的文本特征提取函数 CountVectorizer和TfidfTransformer,并没有将词进行词形还原或词根化,也没有去除对于文档语义作用不大的停用词,因此会对分类效果造成一定的影响。

主要程序如下所示:

import nltk
from gensim import corpora, models, similarities
from nltk.stem.lancaster import LancasterStemmer


#读取Coursera的课程数据,注意修改编码格式
courses = []
with open('coursera_corpus', 'r', encoding="utf8") as data:
    for line in data.readlines():
        courses.append(line.strip())

#构造停用词表(停用词+英文标点+字母),特征提取,得到文本的词频特征,以及TFIDF参数
stopwords = nltk.corpus.stopwords.words('english')
punctuations = [i for i in string.punctuation]
alphabet = [i for i in 'abcdefghigklmnopqrstuvwxyz']
setStopwords = set(punctuations + stopwords + alphabet)

#课程名与简介之间用制表符分隔开,作为target
courses_name = [course.split('\t')[0] for course in courses]

#分词,去停用词
lstemmer = LancasterStemmer()
alltexts = [[word for word in nltk.word_tokenize(doc.lower()) if word not in stopwords] for doc in courses]
texts = [[lstemmer.stem(word) for word in doc] for doc in alltexts]

#构建资料库的词典,语料库,BOW模型,TF-IDF模型,LSI模型,并构造相似度索引矩阵
dictionary = corpora.Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]
tfidf = models.TfidfModel(corpus)
corpus_tfidf = tfidf[corpus]
lsi = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=10)
index = similarities.MatrixSimilarity(lsi[corpus])

#courses_name[210]为'Machine Learning',凭借课程简介找出与machine learning最为相关的课程
ml_course = texts[210]
ml_bow = dictionary.doc2bow(ml_course)
ml_lsi = lsi[ml_bow]

#求相似度,并排序,输出最为相关的前十个课程名字
sims = index[ml_lsi]
sort_sims = sorted(enumerate(sims), key=lambda item: -item[1])

top10sims = sort_sims[:10]
for i in range(len(top10sims)):
print(i, courses_name[top10sims[i][0]])

结果如下:

0 Machine Learning
1 Machine Learning
2 Computer Security
3 Human-Computer Interaction
4 Probabilistic Graphical Models
5 Cryptography I
6 Algorithms: Design and Analysis, Part 2
7 Computer Architecture
8 Introduction to Databases
9 Metadata: Organizing and Discovering Information

可以看出返回的相关课程于MachineLearning的主题还是紧密相关的。

上面是通过LSI模型获取相似度索引矩阵的,现在做出以下更改,通过TFIDF模型构建相似度索引矩阵,在获取相关度最高的十个课程的课程名。

numFeature =len(dictionary.token2id.keys())
index = similarities.SparseMatrixSimilarity(corpus_tfidf,num_features=numFeature)

结果如下:

0 Machine Learning
1 Machine Learning
2 Neural Networks for Machine Learning
3 Natural Language Processing
4 Probabilistic Graphical Models
5 Artificial Intelligence Planning
6 Coding the Matrix: Linear Algebra throughComputer Science Applications
7 Human-Computer Interaction
8 Algorithms: Design and Analysis, Part 1
9 Linear and Integer Programming

推荐阅读

使用gensim和sklearn搭建一个文本分类器(一):流程概述(https://blog.csdn.net/u014595019/article/details/52433754

该博主将gensim生成的lsi模型,转换为稀疏矩阵的表示,传入sklearn中进行模型训练。但是对于20newsgroup数据集,文档数过多程序非常耗时,我就没有进行运行。不过其原理十分简单,因为gensim生成的tfidf是用二元组表示每个句子的词频特性,而sklearn待传入的是一个矩阵,我们只需要将二元组的表示转化成稀疏矩阵的表示即可。我们需要用到scipy模块,从sparse中载入csr_matrix,以生成稀疏矩阵。如下所示:

tfidfTrain = tfidf[bowCorpus]
row = []; col = []; data = [];
for i in range(len(newsTrain.data)):
    for j in range(len(tfidfTrain[i])):
        row.append(i)
        col.append(tfidfTrain[i][j][0])
        data.append(tfidfTrain[i][j][1])
row = np.array(row)
col = np.array(col)
data = np.array(data)
X_train_tfidf = csr_matrix((data, (row, col)))


猜你喜欢

转载自blog.csdn.net/lau_sen/article/details/80539748
今日推荐