目录
一、实验目的
1、掌握向量空间模型;
2、掌握文本分类的方法。
二、实验任务
文本分类指的是将一篇文档归于预先给定的一个类别集合中的某一类或某几类。针对20Newsgroup(http://qwone.com/~jason/20Newsgroups/)数据集做文本分类,从以下方法中任选一种:
(1)sklearn实现文本分类;
(2)自己编程实现向量空间模型。
三、实验原理
1 数据集介绍
20newsgroups数据集是用于文本分类、文本挖据和信息检索研究的国际标准数据集之一。数据集收集了大约20,000左右的新闻组文档,均匀分为20个不同主题的新闻组集合。一些新闻组的主题特别相似(e.g. comp.sys.ibm.pc.hardware/comp.sys.mac.hardware),还有一些却完全不相关 (e.g misc.forsale /soc.religion.christian)。
图 1:数据集介绍
2 向量空间模型
向量空间模型VSM(Vector Space Model)是Salton等人于1975年提出的最常用的检索模型,它的主要思想是文章的语义通过所使用的词语来表达;具体方法为每一篇文档用一个向量来表达,查询用一个向量来表达,通过向量的方式来计算相似度。
图2 :VSM模型示意图
VSM主要涉及两方面的工作,构建向量以及度量向量之间的相似度,下面介绍采用TF-IDF的方法进行VSM模型的构建。TF/IDF(term frequency/inverse document frequency) 的概念被公认为信息检索中最重要的发明。
(1)构建向量:如何构建一个向量来表示文档中的词项,构建另一个向量来表示查询中的词项。对于文档集中每一个不同的词项(或概念),我们在向量中只记录一个分量。当词项出现时,就在对应向量的分量处记1;如果词项未出现,就在对应的分量处记0,这种方法称为二值表示法。二值表示方法并没有考虑一个词项在文档中出现的次数。通过扩展这种表示形式,将词项在文档中出现的频率作为向量中各个分量的值。除了简单地给出查询词列表外,用户通常还会给出权重,该权重表示一个词项比另外一个词项更重要。其思想为不频繁出现的词的权重应该比频繁出现的词的权重更高。具体操作方法如下:
- 人工赋值—在初始查询中用户人工指定词项权重来实现的。
- 自动赋值—通过基于词项在整个文档集中出现的频率。
我们用来t表示文档集中不同词项的个数。tfij表示词项tj在文档Di中出现的次数,也就是词频。dfj包含词项tj的文档的篇数。逆文档频率表示为idfj, 其中d表示所有文档的篇数。
idfj=lgddfj
对于每一篇文档向量,都有n个分量。向量中的每个分量为在整个文档集中计算出来的每个词项的权重。在每篇文档中,词项权重基于词项在整个文档集中出现的频率情况以及词项在某一个特定文档中出现的频率自动赋值。
对于文档中词项的权重因素,主要综合考虑词频和逆文档频率。文档i对应的向量中第j个词条的值:
dij=tfij×idfj
(2)如何来度量任意文档向量和查询向量的相似度。查询Q和文档Di的相似度可以简单地定义为两个向量的内积或者利用余弦相似性也可以。
SC(Q,Di)=j=1twqj×dij
四、实验过程
这里先对PPT上的示例,按照以下步骤进行编程,完整代码详见附录1。对于新闻文本的TF-DIF构建,与示例类似,故不在这里赘述其步骤,只展示实验结果(具体代码见附录2)。
Q:“gold silver truck”
D1:“Shipment of gold damaged in a fire”
D2:“Delivery of silver arrived in a silver truck”
D3:“Shipment of gold arrived in a truck”
(1)声明文档,分词,去重合并;
(2)统计词频:词项tj在文档Di中出现的次数;
(3)计算逆文档频率IDF;
(4)计算tf-idf,tf-idf为pandas中的dataframe,这里最终导出为excel;
(5)利用内积和余弦相似性分别计算与文档Q的相似性,进而判断出最相似的文章。
2 sklearn实现文本分类
(1)选用sklearn常用的10种分类方法
(2)采用TF-IDF方法将文章数据向量化
(3)选取20个类中7种比较典型的类别进行实验。
(4)用分类器进行训练和测试。
五、实验结果
1 TF-IDF
1.1 PPT示例
程序具体运行结果见如下图所示,相似度结果如下表所示, 按照向量内积和余弦相似度进行相似度排序,可以发现D2>D1>D3,即对于文档q,D2文档与它的相似性最高。
图 5.1:运行结果
表 1:文档q与其他文档的相似性比较
| D1 | D2 | D3 |
内积 | 0.031 | 0.486 | 0.062 |
余弦相似度 | 0.080 | 0.825 | 0.327 |
表 2:示例TF-IDF
Delivery | Shipment | a |
| arrived | damaged | fire | gold | in | of | silver | truck | |
0 | 0 | 0.176 | 0 |
| 0 | 0.477 | 0.477 | 0.176 | 0 | 0 | 0 | 0 |
1 | 0.477 | 0 | 0 |
| 0.176 | 0 | 0 | 0 | 0 | 0 | 0.954 | 0.176 |
2 | 0 | 0.176 | 0 |
| 0.176 | 0 | 0 | 0.176 | 0 | 0 | 0 | 0.176 |
3 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0.176 | 0 | 0 | 0.477 | 0.176 |
1.2 新闻文本分类
这里选取了alt.atheism这一类新闻进行程序的演示,另外需要特别注明的是,由于编码方式问题[3],需要对读入的文件做一定的处理,同时利用re模块进行正规式的正确选择。
split=re.split('[!@#$%^&*()}\]\|\\t\\n\[\{\\_\\\¯+-;:`~\'"<>=./?, ]',text[i].decode('gb18030','ignore')).lower()) |
处理之后得到的TF-DIF向量大小为480*11072,与网上的参考资料[2]相比,维度降低了近2/3,因为他没有考虑正则化以及其他非单词符号的处理,例如“!@#$%^&*()}\]\|\\t\\n\[\{\\_\\\¯+-;:`~\'"<>=./?,”等。TF-IDF部分截图如下所示。
图 3:TF-IDF部分截图
由上图可以看出,运行结果中存在大量的0,因此我们可以采用PCA进行降维。这里直接调用sklearn自带的PCA函数即可,代码设置如下。
from sklearn.decomposition import PCA pca = PCA(n_components=0.9)# 保证降维后的数据保持90%的信息 pca.fit(tfidf) pca.transform(tfidf) |
通过调节不同的n_components,得到的TF-IDF矩阵大小如下表所示:
表 3:TF-IDF矩阵大小
n_components | 0.9 | 0.95 | 0.97 | 0.99 | 0.999 |
矩阵大小 | (480,189) | (480,264) | (480,311) | (480,386) | (480, 455) |
由于数据量巨大,这里不再继续进行后续的编程,其大致思路和示例相同,下面主要展示sklearn解决文本分类的问题。
2 Sklearn解决文本分类
图 5:Sklearn程序运行截图
采用不同的分类器及对应的运行时间和准确率如下表所示,我们可以看出采用适合稀疏矩阵的SGD和多层感知器MLP的分类器预测结果较好。
表 4:不同的分类器及对应的运行时间和准确率
分类器 | 运行时间(S) | 准确率 |
MultinomialNB() | 0.063016s | 87.14% |
DecisionTreeClassifier() | 1.968856s | 72.62% |
KNeighborsClassifier() | 1.046378s | 80.80% |
LogisticRegression() | 0.770427s | 91.51% |
SGDClassifier() | 0.065823s | 94.07% |
RandomForestClassifier() | 0.569523s | 72.69% |
AdaBoostClassifier() | 4.385354s | 76.54% |
GradientBoostingClassifier() | 136.232895s | 87.55% |
MLPClassifier() | 387.603116s | 94.38% |
SVC() | 40.563239s | -------* |
总计 | 578.35s |
|
*注:SVC运行结果为0.03,由于时间原因,没有继续深入研究这个结果不合理的原因。
参考资料
- 利用余弦相似度公式计算两字符串的相似性python:https://blog.csdn.net/weixin_44208569/article/details/90315904
- 文档向量化:https://blog.csdn.net/qq_41856733/article/details/106418377
- 编码与解码问题:https://blog.csdn.net/shijing_0214/article/details/51971734
- Sklearn:https://blog.csdn.net/qq_36614557/article/details/85623864
附录
1 TF-IDF示例代码
# -*- coding: utf-8 -*- """ Created on Sat May 30 17:22:32 2020
@author: """
import numpy as np import pandas as pd import math
#1.声明文档 分词 去重合并 D1 = 'Shipment of gold damaged in a fire' D2 = 'Delivery of silver arrived in a silver truck' D3 = 'Shipment of gold arrived in a truck' q = 'gold silver truck' #查询文档Q split1 = D1.split(' ') split2 = D2.split(' ') split3 = D3.split(' ') split_q = q.split(' ') #分词 wordSet = set(split1).union(split2,split3) #通过set去重来构建词库
#2.统计词项tj在文档Di中出现的次数,也就是词频。 def computeTF(wordSet,split): tf = dict.fromkeys(wordSet, 0) for word in split: tf[word] += 1 return tf tf1 = computeTF(wordSet,split1) tf2 = computeTF(wordSet,split2) tf3 = computeTF(wordSet,split3) print('tf1:\n',tf1)
#3.计算逆文档频率IDF def computeIDF(tfList): idfDict = dict.fromkeys(tfList[0],0) #词为key,初始值为0 N = len(tfList) #总文档数量 for tf in tfList: # 遍历字典中每一篇文章 for word, count in tf.items(): #遍历当前文章的每一个词 if count > 0 : #当前遍历的词语在当前遍历到的文章中出现 idfDict[word] += 1 #包含词项tj的文档的篇数df+1 for word, Ni in idfDict.items(): #利用公式将df替换为逆文档频率idf idfDict[word] = math.log10(N/Ni) #N,Ni均不会为0 return idfDict #返回逆文档频率IDF字典 idfs = computeIDF([tf1, tf2, tf3]) print('idfs:\n',idfs)
#4.计算tf-idf(term frequency–inverse document frequency) def computeTFIDF(tf, idfs): #tf词频,idf逆文档频率 tfidf = {} for word, tfval in tf.items(): tfidf[word] = tfval * idfs[word] return tfidf
tfidf1 = computeTFIDF(tf1, idfs) tfidf2 = computeTFIDF(tf2, idfs) tfidf3 = computeTFIDF(tf3, idfs) tfidf = pd.DataFrame([tfidf1, tfidf2, tfidf3]) print(tfidf) tf_q = computeTF(wordSet,split_q) #计算Q的词频 tfidf_q = computeTFIDF(tf_q, idfs) #计算Q的tf_idf(构建向量) ans = pd.DataFrame([tfidf1, tfidf2, tfidf3, tfidf_q]) ans.to_excel("ans.xlsx")
print(ans) #5.计算Q和文档Di的相似度(可以简单地定义为两个向量的内积) print('Q和文档D1的相似度SC(Q, D1) :', (ans.loc[0,:]*ans.loc[3,:]).sum()) print('Q和文档D2的相似度SC(Q, D2) :', (ans.loc[1,:]*ans.loc[3,:]).sum()) print('Q和文档D3的相似度SC(Q, D3) :', (ans.loc[2,:]*ans.loc[3,:]).sum())
dist1=float(np.dot(ans.loc[0,:],ans.loc[3,:])/(np.linalg.norm(ans.loc[0,:])*np.linalg.norm(ans.loc[3,:]))) dist2=float(np.dot(ans.loc[1,:],ans.loc[3,:])/(np.linalg.norm(ans.loc[1,:])*np.linalg.norm(ans.loc[3,:]))) dist3=float(np.dot(ans.loc[2,:],ans.loc[3,:])/(np.linalg.norm(ans.loc[2,:])*np.linalg.norm(ans.loc[3,:]))) print(dist1,dist2,dist3) |
2 文本TF-IDF构建
# -*- coding: utf-8 -*- """ Created on Sat May 30 17:32:54 2020
@author: """
# -*- coding: utf-8 -*- import os import math import pandas as pd import re import jieba from sklearn.feature_extraction.text import TfidfVectorizer
def TF(wordSet,split): tf = dict.fromkeys(wordSet, 0) for word in split: tf[word] += 1 return tf
def IDF(tfList): idfDict = dict.fromkeys(tfList[0],0) #词为key,初始值为0 N = len(tfList) #总文档数量 for tf in tfList: # 遍历字典中每一篇文章 for word, count in tf.items(): #遍历当前文章的每一个词 if count > 0 : #当前遍历的词语在当前遍历到的文章中出现 idfDict[word] += 1 #包含词项tj的文档的篇数df+1 for word, Ni in idfDict.items(): #利用公式将df替换为逆文档频率idf idfDict[word] = math.log10(N/Ni) #N,Ni均不会为0 return idfDict #返回逆文档频率IDF字典
def TFIDF(tf, idfs): #tf词频,idf逆文档频率 tfidf = {} for word, tfval in tf.items(): tfidf[word] = tfval * idfs[word] return tfidf
if __name__ == "__main__": #1 获取文件 text=[] name_all = os.listdir(r'20news-bydate/20news-bydate-train/alt.atheism/') for i in range(len(name_all)): name = "20news-bydate/20news-bydate-train/alt.atheism/" + name_all[i] f = open(name,"rb") str1=f.read() text.append(str1) f.close() #2 将每篇文档进行分词 wordSet = {} split_list = [] for i in range(len(text)): split =re.split('[!@#$%^&*()}\]\|\\t\\n\[\{\\_\\\¯+-;:`~\'"<>=./?, ]',text[i].decode('gb18030','ignore').lower()) split_list.append(split) wordSet = set(wordSet ).union(split)#通过set去重来构建词库 #3 统计每篇文章各项词语的词频 tf = [] for i in range(len(split_list)): tf.append(TF(wordSet,split_list[i])) #4 计算文档集的逆文档频率 idfs = IDF(tf) #5 tf*idf = tfidf算法 tfidf = [] for i in range(len(tf)): tfidf.append(TFIDF(tf[i], idfs)) A=pd.DataFrame(tfidf) A.to_csv("tfidf1.csv") print(A) #可转换为DataFrame类型用于后序操作 pca = PCA(n_components=0.9)# 保证降维后的数据保持90%的信息 pca.fit(A) pca.transform(A) #pca1 = PCA(n_components=0.95)# 保证降维后的数据保持90%的信息 #b1=pca1.fit(A) #c1=pca1.transform(A) #c1.shape
|
3 Sklearn 实现文本分类
import time t0=time.time() print('程序开始的时间:',time.strftime('%H:%M:%S',time.localtime(time.time()))) import numpy as np from sklearn.datasets import fetch_20newsgroups #获取数据集 from sklearn.naive_bayes import MultinomialNB #朴素贝叶斯 from sklearn.tree import DecisionTreeClassifier #决策树 from sklearn.neighbors import KNeighborsClassifier #K近邻 from sklearn.linear_model.logistic import LogisticRegression #逻辑回归 from sklearn.linear_model.stochastic_gradient import SGDClassifier #随机梯度下降(适合稀疏矩阵) from sklearn.ensemble import RandomForestClassifier #随机森林 from sklearn.ensemble import AdaBoostClassifier #平时就是叫AdaBoost from sklearn.ensemble import GradientBoostingClassifier #梯度提升决策树(慢) from sklearn.neural_network.multilayer_perceptron import MLPClassifier #多层感知器(慢) from sklearn.svm import SVC #支持向量机(慢) from sklearn.feature_extraction.text import TfidfVectorizer #TF-IDF文本特征提取 #选取20个类中7种比较典型的类别进行实验 select = ['alt.atheism','comp.graphics','misc.forsale','rec.autos', 'sci.crypt','soc.religion.christian','talk.politics.guns'] train=fetch_20newsgroups(subset='train',categories=select) test=fetch_20newsgroups(subset='test',categories=select) #将文章数据向量化(TF-IDF算法) vectorizer = TfidfVectorizer() train_v=vectorizer.fit_transform(train.data) test_v=vectorizer.transform(test.data) Classifier = [MultinomialNB(),DecisionTreeClassifier(),KNeighborsClassifier(), LogisticRegression(),SGDClassifier(),RandomForestClassifier()] Classifier_str = ['MultinomialNB()','DecisionTreeClassifier()','KNeighborsClassifier()', 'LogisticRegression()','SGDClassifier()','RandomForestClassifier()', 'AdaBoostClassifier()','GradientBoostingClassifier()','MLPClassifier()','SVC()'] for i in Classifier_str: t2=time.time() model = eval(i) model.fit(train_v,train.target) print(i+"准确率为:",model.score(test_v,test.target)) print(i+'用时:%.6fs'%(time.time()-t2)) #y_predict=model.predict(test_v) #print(np.mean(y_predict==test.target)) t1=time.time() print('程序结束的时间:',time.strftime('%H:%M:%S',time.localtime(time.time()))) print("用时:%.2fs"%(t1-t0))
|