摘要:这篇文章将详细讲解自然语言处理过程,基于机器学习和TFIDF的情感分类算法,并进行了各种分类算法(SVM、RF、LR、Boosting)对比
本文分享自华为云社区《[Python人工智能] 二十三.基于机器学习和TFIDF的情感分类(含详细的NLP数据清洗)》,作者: eastmount。
在数据分析和数据挖掘中,通常需要经历前期准备、数据爬取、数据预处理、数据分析、数据可视化、评估分析等步骤,而数据分析之前的工作几乎要花费数据工程师近一半的工作时间,其中的数据预处理也将直接影响后续模型分析的好坏。图是数据预处理的基本步骤,包括中文分词、词性标注、数据清洗、特征提取(向量空间模型存储)、权重计算(TF-IDF)等。
当读者使用Python爬取了中文数据集之后,首先需要对数据集进行中文分词处理。由于英文中的词与词之间是采用空格关联的,按照空格可以直接划分词组,所以不需要进行分词处理,而中文汉字之间是紧密相连的,并且存在语义,词与词之间没有明显的分隔点,所以需要借助中文分词技术将语料中的句子按空格分割,变成一段段词序列。下面开始详细介绍中文分词技术及Jiaba中文分词工具。
中文分词(Chinese Word Segmentation)指将汉字序列切分成一个个单独的词或词串序列,它能够在没有词边界的中文字符串中建立分隔标志,通常采用空格分隔。下面举个简单示例,对句子“我是程序员”进行分词操作。
输入:我是程序员 输出1:我\是\程\序\员 输出2:我是\是程\程序\序员 输出3:我\是\程序员
简单举个例子,代码中主要导入Jieba扩展包,然后调用其函数进行中文分词。
#encoding=utf-8 import jieba text = "北京理工大学生前来应聘" 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工具对其进行中文分词。
完整代码:
# -*- coding:utf-8 -*- import csv import pandas as pd import numpy as np import jieba import jieba.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() #中文分词函数 def txt_cut(juzi): return [w for w in jieba.lcut(juzi) if w not in stop_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" with open(file, "r", encoding="UTF-8") as f: reader = csv.DictReader(f) for row in reader: # 数据元素获取 if row['label'] == '好评': res = 0 else: res = 1 labels.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(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算法简单快速,结果也符合实际情况,是文本挖掘、情感分析、主题分布等领域的常用手段。
Scikit-Learn中主要使用Scikit-Learn中的两个类CountVectorizer和TfidfTransformer,用来计算词频和TF-IDF值。
该类是将文本词转换为词频矩阵的形式。比如“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()函数获取词库中的所有文本关键词。
当使用CountVectorizer类计算得到词频矩阵后,接下来通过TfidfTransformer类实现统计vectorizer变量中每个词语的TF-IDF值。TF-IDF值采用矩阵数组的形式存储,每一行数据代表一个文本语料,每一行的每一列都代表其中一个特征对应的权重,得到TF-IDF后就可以运用各种数据分析算法进行分析,比如聚类分析、LDA主题分布、舆情分析等等。
完整代码:
# -*- coding:utf-8 -*- import csv import pandas as pd import numpy as np import jieba import jieba.analyse from scipy.sparse import coo_matrix from sklearn import feature_extraction from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfTransformer #----------------------------------第一步 读取文件-------------------------------- with open('fenci_data.csv', 'r', encoding='UTF-8') as f: reader = csv.DictReader(f) labels = [] contents = [] for row in reader: 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)) for n in tfidf[:5]: print(n) print(type(tfidf)) # 获取词袋模型中的所有词语 word = vectorizer.get_feature_names() for n in word[: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() #稀疏矩阵 注意float print(X.shape) print(X[:10])
输出结果如下所示:
<class 'scipy.sparse.csr.csr_matrix'> aaaaa achievements amazing ananananan ancient anshun aperture app 单词数量: 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.]]
当我们数据量很大时,矩阵往往存储不了这么大的数据,会出现如下错误:
我提供的解决方法如下:
获取文本TF-IDF值之后,本小节简单讲解使用TF-IDF值进行情感分类的过程,主要包括如下步骤:
逻辑回归完整代码:
# -*- coding:utf-8 -*- import csv import pandas as pd import numpy as np import jieba import jieba.analyse from scipy.sparse import coo_matrix from sklearn import feature_extraction from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfTransformer from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.tree import DecisionTreeClassifier from sklearn import svm from sklearn import neighbors from sklearn.naive_bayes import MultinomialNB #----------------------------------第一步 读取文件-------------------------------- with open('fenci_data.csv', 'r', encoding='UTF-8') as f: reader = csv.DictReader(f) labels = [] contents = [] for row in reader: 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)) for n in tfidf[:5]: print(n) print(type(tfidf)) # 获取词袋模型中的所有词语 word = vectorizer.get_feature_names() for n in word[: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() #稀疏矩阵 注意float print(X.shape) print(X[:10]) #----------------------------------第三步 数据划分-------------------------------- #使用 train_test_split 分割 X y 列表 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 -*- import csv import pandas as pd import numpy as np import jieba import jieba.analyse from scipy.sparse import coo_matrix from sklearn import feature_extraction from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfTransformer from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.tree import DecisionTreeClassifier from sklearn import svm from sklearn import neighbors from sklearn.naive_bayes import MultinomialNB #----------------------------------第一步 读取文件-------------------------------- with open('fenci_data.csv', 'r', encoding='UTF-8') as f: reader = csv.DictReader(f) labels = [] contents = [] for row in reader: 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)) for n in tfidf[:5]: print(n) print(type(tfidf)) # 获取词袋模型中的所有词语 word = vectorizer.get_feature_names() for n in word[: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() #稀疏矩阵 注意float print(X.shape) print(X[:10]) #----------------------------------第三步 数据划分-------------------------------- #使用 train_test_split 分割 X y 列表 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)) #----------------------------------第五步 评价结果-------------------------------- def classification_pj(name, y_test, pre): print("算法评价:", name) # 正确率 Precision = 正确识别的个体总数 / 识别出的个体总数 # 召回率 Recall = 正确识别的个体总数 / 测试集中存在的个体总数 # F值 F-measure = 正确率 * 召回率 * 2 / (正确率 + 召回率) YC_B, YC_G = 0,0 #预测 bad good ZQ_B, ZQ_G = 0,0 #正确 CZ_B, CZ_G = 0,0 #存在 #0-good 1-bad 同时计算防止类标变化 i = 0 while i<len(pre): z = int(y_test[i]) #真实 y = int(pre[i]) #预测 if z==0: CZ_G += 1 else: CZ_B += 1 if y==0: YC_G += 1 else: YC_B += 1 if z==y and z==0 and y==0: ZQ_G += 1 elif z==y and z==1 and y==1: ZQ_B += 1 i = i + 1 print(ZQ_B, ZQ_G, YC_B, YC_G, CZ_B, CZ_G) print("") # 结果输出 P_G = ZQ_G * 1.0 / YC_G P_B = ZQ_B * 1.0 / YC_B print("Precision Good 0:", P_G) print("Precision Bad 1:", P_B) R_G = ZQ_G * 1.0 / CZ_G R_B = ZQ_B * 1.0 / CZ_B print("Recall Good 0:", R_G) print("Recall Bad 1:", 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-measure Good 0:", F_G) print("F-measure Bad 1:", F_B) #函数调用 classification_pj("LogisticRegression", y_test, pre)
输出结果如下:
逻辑回归分类 1823 1823 precision recall f1-score support 0 0.94 0.99 0.97 1520 1 0.93 0.70 0.80 303 accuracy 0.94 1823 macro avg 0.94 0.85 0.88 1823 weighted avg 0.94 0.94 0.94 1823 算法评价: LogisticRegression 213 1504 229 1594 303 1520 Precision Good 0: 0.9435382685069009 Precision Bad 1: 0.9301310043668122 Recall Good 0: 0.9894736842105263 Recall Bad 1: 0.7029702970297029 F-measure Good 0: 0.9659601798330122 F-measure Bad 1: 0.800751879699248
代码如下:
# 随机森林分类方法模型 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")
输出结果:
代码如下:
# SVM分类方法模型 SVM = svm.LinearSVC() #支持向量机分类器LinearSVC SVM.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")
输出结果:
代码如下:
#朴素贝叶斯模型 nb = MultinomialNB() nb.fit(X_train, y_train) print('模型的准确度:{}'.format(nb.score(X_test, y_test))) pre = nb.predict(X_test) print("朴素贝叶斯分类") print(len(pre), len(y_test)) print(classification_report(y_test, pre)) classification_pj("MultinomialNB", y_test, pre) print("\n")
输出结果:
该算法准确率不高,并且执行时间较长,不建议大家用于文本分析。某些情况的算法对比倒是还行,核心代码如下:
#最近邻算法 knn = neighbors.KNeighborsClassifier(n_neighbors=7) knn.fit(X_train, y_train) print('模型的准确度:{}'.format(knn.score(X_test, y_test))) pre = knn.predict(X_test) print("最近邻分类") print(classification_report(y_test, pre)) classification_pj("KNeighbors", y_test, pre) print("\n")
输出结果:
代码如下:
#决策树算法 dtc = DecisionTreeClassifier() dtc.fit(X_train, y_train) print('模型的准确度:{}'.format(dtc.score(X_test, y_test))) pre = dtc.predict(X_test) print("决策树分类") print(len(pre), len(y_test)) print(classification_report(y_test, pre)) classification_pj("DecisionTreeClassifier", y_test, pre) print("\n")
输出结果:
代码如下:
#SGD分类模型 from sklearn.linear_model.stochastic_gradient import SGDClassifier sgd = SGDClassifier() sgd.fit(X_train, y_train) print('模型的准确度:{}'.format(sgd.score(X_test, y_test))) pre = sgd.predict(X_test) print("SGD分类") print(len(pre), len(y_test)) print(classification_report(y_test, pre)) classification_pj("SGDClassifier", y_test, pre) print("\n")
输出结果:
该算法时间比较慢,核心代码如下:
#MLP分类模型 from sklearn.neural_network.multilayer_perceptron import MLPClassifier mlp = MLPClassifier() mlp.fit(X_train, y_train) print('模型的准确度:{}'.format(mlp.score(X_test, y_test))) pre = mlp.predict(X_test) print("MLP分类") print(len(pre), len(y_test)) print(classification_report(y_test, pre)) classification_pj("MLPClassifier", y_test, pre) print("\n")
输出结果:
该算法时间比较慢,代码如下:
#GradientBoosting分类模型 from sklearn.ensemble import GradientBoostingClassifier gb = GradientBoostingClassifier() gb.fit(X_train, y_train) print('模型的准确度:{}'.format(gb.score(X_test, y_test))) pre = gb.predict(X_test) print("GradientBoosting分类") print(len(pre), len(y_test)) print(classification_report(y_test, pre)) classification_pj("GradientBoostingClassifier", y_test, pre) print("\n")
输出结果:
代码如下:
#AdaBoost分类模型 from sklearn.ensemble import AdaBoostClassifier AdaBoost = AdaBoostClassifier() AdaBoost.fit(X_train, y_train) print('模型的准确度:{}'.format(AdaBoost.score(X_test, y_test))) pre = AdaBoost.predict(X_test) print("AdaBoost分类") print(len(pre), len(y_test)) print(classification_report(y_test, pre)) classification_pj("AdaBoostClassifier", y_test, pre) print("\n")
输出结果:
点击关注,第一时间了解华为云新鲜技术~