一.中文分词
当读者使用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工具对其进行中文分词。
- 好评:5000条
- 差评:1000条
完整代码:
# -*- 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计算
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 -*-
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.]]
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 -*-
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
六.算法对比实验
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() #支持向量机分类器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")
输出结果:
3.朴素贝叶斯
代码如下:
#朴素贝叶斯模型
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")
输出结果:
4.KNN
该算法准确率不高,并且执行时间较长,不建议大家用于文本分析。某些情况的算法对比倒是还行,核心代码如下:
#最近邻算法
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")
输出结果:
5.决策树
代码如下:
#决策树算法
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")
输出结果:
6.SGD
代码如下:
#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")
输出结果:
7.MLP
该算法时间比较慢,核心代码如下:
#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")
输出结果:
8.GradientBoosting
该算法时间比较慢,代码如下:
#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")
输出结果:
9.AdaBoost
代码如下:
#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")
输出结果:
七.总结
写到这里,这篇文章就结束了,下一篇我将带领大家看看深度学习(BiLSTM-CNN)的情感分类方法。希望对您有所帮助,同时文章中不足或错误的地方,欢迎读者提出。这些实验都是我在做论文研究或项目评价常见的一些问题,希望读者带着这些问题,结合自己的需求进行深入的思考,更希望大家能学以致用。最后如果文章对您有帮助,请点赞、评论、收藏,这将是我分享最大的动力。