摘要:这篇文章将详细讲解自然语言处理过程,基于机器学习和TFIDF的情感分类算法,并进行了各种分类算法(SVM、RF、LR、Boosting)对比
在数据分析和数据挖掘中,通常需要经历前期准备、数据爬取、数据预处理、数据分析、数据可视化、评估分析等步骤,而数据分析之前的工作几乎要花费数据工程师近一半的工作时间,其中的数据预处理也将直接影响后续模型分析的好坏。图是数据预处理的基本步骤,包括中文分词、词性标注、数据清洗、特征提取(向量空间模型存储)、权重计算(TF-IDF)等。
一.中文分词
当读者使用Python爬取了中文数据集之后,首先需要对数据集进行中文分词处理。由于英文中的词与词之间是采用空格关联的,按照空格可以直接划分词组,所以不需要进行分词处理,而中文汉字之间是紧密相连的,并且存在语义,词与词之间没有明显的分隔点,所以需要借助中文分词技术将语料中的句子按空格分割,变成一段段词序列。下面开始详细介绍中文分词技术及Jiaba中文分词工具。
中文分词(Chinese Word Segmentation)指将汉字序列切分成一个个单独的词或词串序列,它能够在没有词边界的中文字符串中建立分隔标志,通常采用空格分隔。下面举个简单示例,对句子“我是程序员”进行分词操作。
输入:我是程序员输出1:我\是\程\序\员输出2:我是\是程\程序\序员输出3:我\是\程序员
简单举个例子,代码中主要导入Jieba扩展包,然后调用其函数进行中文分词。
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))
上述代码输出如下,包括全模式、精确模式和搜索引擎模式输出的结果。
二.数据清洗
在分析语料的过程中,通常会存在一些脏数据或噪声词组干扰我们的实验结果,这就需要对分词后的语料进行数据清洗(Data Cleaning)。比如前面使用Jieba工具进行中文分词,它可能存在一些脏数据或停用词,如“我们”、“的”、“吗”等。这些词降低了数据质量,为了得到更好的分析结果,需要对数据集进行数据清洗或停用词过滤等操作。
残缺数据
重复数据
错误数据
停用词
这里主要讲解停用词过滤,将这些出现频率高却不影响文本主题的停用词删除。在Jieb分词过程中引入stop_words.txt停用词词典,如果存在则过滤即可。
下面是从大众点评、美团之类的网站抓取“黄果树瀑布”的评论信息,我们通过Jieba工具对其进行中文分词。
好评:5000条
差评:1000条
完整代码:
#-*-coding:utf-8-*-importcsvimportpandasaspdimportnumpyasnpimportjiebaimportjieba.analyse#添加自定义词典和停用词典jieba.load_userdict("user_dict.txt")stop_list=pd.read_csv('stop_words.txt',engine='python',encoding='utf-8',delimiter="\n",names=['t'])['t'].tolist()#中文分词函数deftxt_cut(juzi):return[wforwinjieba.lcut(juzi)ifwnotinstop_list]#写入分词结果fw=open('fenci_data.csv',"a+",newline='',encoding='gb18030')writer=csv.writer(fw)writer.writerow(['content','label'])#使用csv.DictReader读取文件中的信息labels=[]contents=[]file="data.csv"withopen(file,"r",encoding="UTF-8")asf:reader=csv.DictReader(f)forrowinreader:#数据元素获取ifrow['label']=='好评':res=0else:res=1labels.append(res)content=row['content']seglist=txt_cut(content)output=''.join(list(seglist))#空格拼接contents.append(output)#文件写入tlist=[]tlist.append(output)tlist.append(res)writer.writerow(tlist)print(labels[:5])print(contents[:5])fw.close()
运行结果如下图所示,一方面它将特殊标点符号、停用词过滤,另一方面导入了user_dict.txt词典,将“黄果树瀑布”、“风景区”等专有名词分词,否则它可能会划分为“黄果树”和“瀑布”、“风景”和“区”。
数据清洗前
还记得小时候,常常守在电视机前,等候《西游记》的播出。“你挑着担,我牵着马。翻山涉水两肩双滑……"熟悉的歌曲,又在耳边响起时。 这歌词中的水,就有贵州的水,准确的说,是贵州的黄果树瀑布;那一帘瀑布,流进了我们的童年,让我们流连忘返。 黄果树瀑布并不是只有一个瀑布,而是一个大景区,包括陡坡塘瀑布、天星桥景区、黄果树大瀑布,其中黄果树大瀑布是最有名的。
数据清洗后
记得 小时候 守 电视机 前 等候 西游记 播出 挑 担 牵 马 翻山 涉水 两肩 双滑 熟悉 歌曲 耳边 响起 时 歌词 中 水 贵州 水 准确 说 贵州 黄果树瀑布 那一帘 瀑布 流进 童年 流连忘返 黄果树瀑布 瀑布 景区 包括 陡坡 塘 瀑布 天星桥 景区 黄果树 瀑布 黄果树 瀑布 有名
三.特征提取及TF-IDF计算
1.基本概念
权重计算是指通过特征权重来衡量特征项在文档表示中的重要程度,给特征词赋予一定的权重来衡量统计文本特征词。TF-IDF(Term Frequency-Invers Document Frequency)是近年来用于数据分析和信息处理经典的权重计算技术。该技术根据特征词在文本中出现的次数和在整个语料中出现的文档频率来计算该特征词在整个语料中的重要程度,其优点是能过滤掉一些常见却无关紧要的词语,尽可能多的保留影响程度高的特征词。
TF-IDF的计算公式如下,式中TF-IDF表示词频TF和倒文本词频IDF的乘积,TF-IDF中权重与特征项在文档中出现的频率成正比,与在整个语料中出现该特征项的文档数成反比。TF-IDF值越大则该特征词对这个文本的重要程度越高。
其中,TF词频的计算公式如下,ni,j 为特征词 ti 在训练文本 Dj 中出现的次数,分母是文本 Dj 中所有特征词的个数,计算的结果即为某个特征词的词频。
倒文档频率(Inverse Document Frequency,简称IDF)是Spark Jones在1972年提出的,用于计算词与文献相关权重的经典方法。计算公式如下,参数|D|表示语料的文本总数,|Dt| 表示文本所包含特征词 tj 的数量。
在倒文档频率方法中,权重是随着特征词的文档数量的变化呈反向变化。如某些常用词“我们”、“但是”、“的”等,在所有文档中出现频率很高,但它的IDF值却非常低。甚至如果它每篇文档都出现,则log1的计算结果为0,从而降低了这些常用词的作用;相反,如果某篇介绍“人工智能”的词,仅仅在该篇文档中出现很多次,它的作用就非常高。
TF-IDF技术的核心思想是如果某个特征词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来做权重计算。TF-IDF算法简单快速,结果也符合实际情况,是文本挖掘、情感分析、主题分布等领域的常用手段。
2.代码实现
Scikit-Learn中主要使用Scikit-Learn中的两个类CountVectorizer和TfidfTransformer,用来计算词频和TF-IDF值。
CountVectorizer
该类是将文本词转换为词频矩阵的形式。比如“I am a teacher”文本共包含四个单词,它们对应单词的词频均为1,“I”、“am”、“a”、“teacher”分别出现一次。CountVectorizer将生成一个矩阵 a[M][N],共M个文本语料,N个单词,比如a[i][j]表示单词j在i类文本下的词频。再调用fit_transform()函数计算各个词语出现的次数,get_feature_names()函数获取词库中的所有文本关键词。
TfidTransformer
当使用CountVectorizer类计算得到词频矩阵后,接下来通过TfidfTransformer类实现统计vectorizer变量中每个词语的TF-IDF值。TF-IDF值采用矩阵数组的形式存储,每一行数据代表一个文本语料,每一行的每一列都代表其中一个特征对应的权重,得到TF-IDF后就可以运用各种数据分析算法进行分析,比如聚类分析、LDA主题分布、舆情分析等等。
完整代码:
#-*-coding:utf-8-*-importcsvimportpandasaspdimportnumpyasnpimportjiebaimportjieba.analysefromscipy.sparseimportcoo_matrixfromsklearnimportfeature_extractionfromsklearn.feature_extraction.textimportTfidfVectorizerfromsklearn.feature_extraction.textimportCountVectorizerfromsklearn.feature_extraction.textimportTfidfTransformer#----------------------------------第一步读取文件--------------------------------withopen('fenci_data.csv','r',encoding='UTF-8')asf:reader=csv.DictReader(f)labels=[]contents=[]forrowinreader:labels.append(row['label'])#0-好评1-差评contents.append(row['content'])print(labels[:5])print(contents[:5])#----------------------------------第二步数据预处理--------------------------------#将文本中的词语转换为词频矩阵矩阵元素a[i][j]表示j词在i类文本下的词频vectorizer=CountVectorizer()#该类会统计每个词语的tf-idf权值transformer=TfidfTransformer()#第一个fit_transform是计算tf-idf第二个fit_transform是将文本转为词频矩阵tfidf=transformer.fit_transform(vectorizer.fit_transform(contents))fornintfidf[:5]:print(n)print(type(tfidf))#获取词袋模型中的所有词语word=vectorizer.get_feature_names()forninword[:10]:print(n)print("单词数量:",len(word))#将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重#X=tfidf.toarray()X=coo_matrix(tfidf,dtype=np.float32).toarray()#稀疏矩阵注意floatprint(X.shape)print(X[:10])
输出结果如下所示:
<class'scipy.sparse.csr.csr_matrix'>aaaaaachievementsamazinganananananancientanshunapertureapp单词数量:20254(6074,20254)[[0.0.0....0.0.0.][0.0.0....0.0.0.][0.0.0....0.0.0.]...[0.0.0....0.0.0.][0.0.0....0.0.0.][0.0.0....0.0.0.]]
3.MemoryError内存溢出错误
当我们数据量很大时,矩阵往往存储不了这么大的数据,会出现如下错误:
ValueError: array is too big; arr.size * arr.dtype.itemsize is larger than the maximum possible size.
MemoryError: Unable to allocate array with shape (26771, 69602) and data type float64
我提供的解决方法如下:
停用词过滤降低不需要的特征词
scipy包的提供了稀疏矩阵的创建,使用coo_matrix(tfidf, dtype=np.float32)转换tfidf
CountVectorizer(min_df=5)增加min_df参数,过滤掉出现频率少的特征词,该参数可以不断调试
max_df用于删除过于频繁出现的术语,称为语料库特定的停用词,默认的max_df是1.0即忽略出现在100%文档的术语;min_df用于删除不经常出现的术语min_df=5表示忽略少于5个文档中出现的术语。
使用GPU或扩大内存解决
四.基于逻辑回归的情感分类
获取文本TF-IDF值之后,本小节简单讲解使用TF-IDF值进行情感分类的过程,主要包括如下步骤:
对中文分词和数据清洗后的语料进行词频矩阵生成操作。主要调用CountVectorizer类计算词频矩阵,生成的矩阵为X。
调用TfidfTransformer类计算词频矩阵X的TF-IDF值,得到Weight权重矩阵。
调用Sklearn机器学习包执行分类操作,调用fit()函数训练,并将预测的类标赋值给pre数组。
调用Sklearn库PCA()函数进行降维操作,将这些特征降低为二维,对应X和Y轴,接着进行可视化呈现。
算法优化及算法评估。
逻辑回归完整代码:
#-*-coding:utf-8-*-importcsvimportpandasaspdimportnumpyasnpimportjiebaimportjieba.analysefromscipy.sparseimportcoo_matrixfromsklearnimportfeature_extractionfromsklearn.feature_extraction.textimportTfidfVectorizerfromsklearn.feature_extraction.textimportCountVectorizerfromsklearn.feature_extraction.textimportTfidfTransformerfromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportclassification_reportfromsklearn.linear_modelimportLogisticRegressionfromsklearn.ensembleimportRandomForestClassifierfromsklearn.treeimportDecisionTreeClassifierfromsklearnimportsvmfromsklearnimportneighborsfromsklearn.naive_bayesimportMultinomialNB#----------------------------------第一步读取文件--------------------------------withopen('fenci_data.csv','r',encoding='UTF-8')asf:reader=csv.DictReader(f)labels=[]contents=[]forrowinreader:labels.append(row['label'])#0-好评1-差评contents.append(row['content'])print(labels[:5])print(contents[:5])#----------------------------------第二步数据预处理--------------------------------#将文本中的词语转换为词频矩阵矩阵元素a[i][j]表示j词在i类文本下的词频vectorizer=CountVectorizer(min_df=5)#该类会统计每个词语的tf-idf权值transformer=TfidfTransformer()#第一个fit_transform是计算tf-idf第二个fit_transform是将文本转为词频矩阵tfidf=transformer.fit_transform(vectorizer.fit_transform(contents))fornintfidf[:5]:print(n)print(type(tfidf))#获取词袋模型中的所有词语word=vectorizer.get_feature_names()forninword[:10]:print(n)print("单词数量:",len(word))#将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重#X=tfidf.toarray()X=coo_matrix(tfidf,dtype=np.float32).toarray()#稀疏矩阵注意floatprint(X.shape)print(X[:10])#----------------------------------第三步数据划分--------------------------------#使用train_test_split分割Xy列表X_train,X_test,y_train,y_test=train_test_split(X,labels,test_size=0.3,random_state=1)#--------------------------------第四步机器学习分类--------------------------------#逻辑回归分类方法模型LR=LogisticRegression(solver='liblinear')LR.fit(X_train,y_train)print('模型的准确度:{}'.format(LR.score(X_test,y_test)))pre=LR.predict(X_test)print("逻辑回归分类")print(len(pre),len(y_test))print(classification_report(y_test,pre))print("\n")
运行结果如下图所示:
五.算法性能评估
算法评价很多实时需要我们自己编写程序去实现,比如绘制ROC曲线、统计各种特征信息、显示4位数结果。这里作者尝试自定义准确率(Precision)、召回率(Recall)和F特征值(F-measure),其计算公式如下:
由于本文主要针对2分类问题,其实验评估主要分为0和1两类,完整代码如下:
#-*-coding:utf-8-*-importcsvimportpandasaspdimportnumpyasnpimportjiebaimportjieba.analysefromscipy.sparseimportcoo_matrixfromsklearnimportfeature_extractionfromsklearn.feature_extraction.textimportTfidfVectorizerfromsklearn.feature_extraction.textimportCountVectorizerfromsklearn.feature_extraction.textimportTfidfTransformerfromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportclassification_reportfromsklearn.linear_modelimportLogisticRegressionfromsklearn.ensembleimportRandomForestClassifierfromsklearn.treeimportDecisionTreeClassifierfromsklearnimportsvmfromsklearnimportneighborsfromsklearn.naive_bayesimportMultinomialNB#----------------------------------第一步读取文件--------------------------------withopen('fenci_data.csv','r',encoding='UTF-8')asf:reader=csv.DictReader(f)labels=[]contents=[]forrowinreader:labels.append(row['label'])#0-好评1-差评contents.append(row['content'])print(labels[:5])print(contents[:5])#----------------------------------第二步数据预处理--------------------------------#将文本中的词语转换为词频矩阵矩阵元素a[i][j]表示j词在i类文本下的词频vectorizer=CountVectorizer(min_df=5)#该类会统计每个词语的tf-idf权值transformer=TfidfTransformer()#第一个fit_transform是计算tf-idf第二个fit_transform是将文本转为词频矩阵tfidf=transformer.fit_transform(vectorizer.fit_transform(contents))fornintfidf[:5]:print(n)print(type(tfidf))#获取词袋模型中的所有词语word=vectorizer.get_feature_names()forninword[:10]:print(n)print("单词数量:",len(word))#将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重#X=tfidf.toarray()X=coo_matrix(tfidf,dtype=np.float32).toarray()#稀疏矩阵注意floatprint(X.shape)print(X[:10])#----------------------------------第三步数据划分--------------------------------#使用train_test_split分割Xy列表X_train,X_test,y_train,y_test=train_test_split(X,labels,test_size=0.3,random_state=1)#--------------------------------第四步机器学习分类--------------------------------#逻辑回归分类方法模型LR=LogisticRegression(solver='liblinear')LR.fit(X_train,y_train)print('模型的准确度:{}'.format(LR.score(X_test,y_test)))pre=LR.predict(X_test)print("逻辑回归分类")print(len(pre),len(y_test))print(classification_report(y_test,pre))#----------------------------------第五步评价结果--------------------------------defclassification_pj(name,y_test,pre):print("算法评价:",name)#正确率Precision=正确识别的个体总数/识别出的个体总数#召回率Recall=正确识别的个体总数/测试集中存在的个体总数#F值F-measure=正确率*召回率*2/(正确率+召回率)YC_B,YC_G=0,0#预测badgoodZQ_B,ZQ_G=0,0#正确CZ_B,CZ_G=0,0#存在#0-good1-bad同时计算防止类标变化i=0whilei<len(pre):z=int(y_test[i])#真实y=int(pre[i])#预测ifz==0:CZ_G+=1else:CZ_B+=1ify==0:YC_G+=1else:YC_B+=1ifz==yandz==0andy==0:ZQ_G+=1elifz==yandz==1andy==1:ZQ_B+=1i=i+1print(ZQ_B,ZQ_G,YC_B,YC_G,CZ_B,CZ_G)print("")#结果输出P_G=ZQ_G*1.0/YC_GP_B=ZQ_B*1.0/YC_Bprint("PrecisionGood0:",P_G)print("PrecisionBad1:",P_B)R_G=ZQ_G*1.0/CZ_GR_B=ZQ_B*1.0/CZ_Bprint("RecallGood0:",R_G)print("RecallBad1:",R_B)F_G=2*P_G*R_G/(P_G+R_G)F_B=2*P_B*R_B/(P_B+R_B)print("F-measureGood0:",F_G)print("F-measureBad1:",F_B)#函数调用classification_pj("LogisticRegression",y_test,pre)
输出结果如下:
逻辑回归分类18231823precisionrecallf1-scoresupport00.940.990.97152010.930.700.80303accuracy0.941823macroavg0.940.850.881823weightedavg0.940.940.941823算法评价:LogisticRegression213150422915943031520PrecisionGood0:0.9435382685069009PrecisionBad1:0.9301310043668122RecallGood0:0.9894736842105263RecallBad1:0.7029702970297029F-measureGood0:0.9659601798330122F-measureBad1:0.800751879699248
六.算法对比实验
1.RandomForest
代码如下:
#随机森林分类方法模型n_estimators:森林中树的数量clf=RandomForestClassifier(n_estimators=20)clf.fit(X_train,y_train)print('模型的准确度:{}'.format(clf.score(X_test,y_test)))print("\n")pre=clf.predict(X_test)print('预测结果:',pre[:10])print(len(pre),len(y_test))print(classification_report(y_test,pre))classification_pj("RandomForest",y_test,pre)print("\n")
输出结果:
2.SVM
代码如下:
#SVM分类方法模型SVM=svm.LinearSVC()#支持向量机分类器LinearSVCSVM.fit(X_train,y_train)print('模型的准确度:{}'.format(SVM.score(X_test,y_test)))pre=SVM.predict(X_test)print("支持向量机分类")print(len(pre),len(y_test))print(classification_report(y_test,pre))classification_pj("LinearSVC",y_test,pre)print("\n")
输出结果:
3.朴素贝叶斯
代码如下:
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))0
输出结果:
4.KNN
该算法准确率不高,并且执行时间较长,不建议大家用于文本分析。某些情况的算法对比倒是还行,核心代码如下:
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))1
输出结果:
5.决策树
代码如下:
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))2
输出结果:
6.SGD
代码如下:
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))3
输出结果:
7.MLP
该算法时间比较慢,核心代码如下:
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))4
输出结果:
8.GradientBoosting
该算法时间比较慢,代码如下:
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))5
输出结果:
9.AdaBoost
代码如下:
#encoding=utf-8importjiebatext="北京理工大学生前来应聘"data=jieba.cut(text,cut_all=True)#全模式print("[全模式]:","".join(data))data=jieba.cut(text,cut_all=False)#精确模式print("[精确模式]:","".join(data))data=jieba.cut(text)#默认是精确模式print("[默认模式]:","".join(data))data=jieba.cut_for_search(text)#搜索引擎模式print("[搜索引擎模式]:","".join(data))6
输出结果:
本文分享自华为云社区,作者: eastmount。