前言
在互联网兴起、科技发达的现代社会,人们已经习惯于通过互联网获取、处 理、分享信息,对于文本内容的处理就迫切的需要进一步发展。文本分类作为多 年来机器学习中的一个重要问题,是大数据时代下信息数据处理的关键技术之一 [1]。文本分类是指对于一个特定的文档,判断其是否属于某个类别[2],此技术的 最主要作用就是通过预先设定的分类模型,在对文本内容进行自动识别的基础上 实现对文本类别的判定[3]。一个酒店想要长久的经营下去,必然要了解自身在客 户眼中的优缺点,然后扬长避短,才能够长久地运行下去。而客户对于酒店的评 价都是文本内容,人为一点点去看太过于繁琐,因此对于酒店评价文本分析就十 分必要,可以帮助酒店快速分清请假的好坏,然后针对不好的评价去改善。
数据清洗
先观察从未处理过的数据:
这个数据是由标签和评价两部分组成,只是数据是混合在一起没有分开的。 标签 1 代表的是好评,标签 0 代表的是差评,一共有 7767 条数据,其中好评 5322 条,差评 2445 条。可以看到数据中除了中文文本还包括标点符号、数字、字母 和空格,在对文本进行分析处理之前,要对这些标点符号和无意义的数字字母进 行处理。
这里采用一个常用的中文停用词表,其中包括了常见的标点符号以及常见的 中文停用词。先将文本中的字母转化为大写,再去除文本中的数字及标点符号, 使用结巴分词对文本进行分词处理。这里使用结巴分词中适用于文本分析的精确 模式,分词后再去掉停用词和多余空格,将处理后的词语使用空格连接为字符串 生成新的一列储存回列表中,输出部分清洗后的数据如下:
特征工程(TF-IDF 编码)
用以上得到的字典和语料构建一个元组嵌套列表,每一个元组为一个词袋, 词袋中每个词有两个属性,用来表示每个评价内容中词语对应的 id 和词频,处 理后得到的列表示例如下:
再使用词频-逆文档频率方法对语料库建模,这个建模的过程实际是在统计 每个词在多少个提问内容中出现,建立模型后利用该模型计算语料中每个词的 TF-IDF 值。
词云图
对于清洗处理好的文本数据,对其做词云分析如下:
可以看到最明显的几个词为房间、酒店、入住、服务、感觉。表明酒店的房 间布置情况和酒店整体感觉,以及酒店的服务水平都是影响顾客体验感的重要因 素。其次明显的是前台、早餐、晚上,服务员、价格和元,表明服务人员的整体 素质对于一个酒店的外在形象来说至关重要,还有夜晚的安静程度和房间的隔音 情况也是影响客人体验感的重要因素,还有餐饮服务和价格也是顾客衡量性价比 的关键因素。
贝叶斯分类
将清洗后的文本数据及其标签分成百分之八十的训练集和百分之二十的测 试集,再将分词后的数据训练集评价文本进行特征工程处理。对处理后的数据用 多项式贝叶斯分类器进行分类预测,预测准确率为 74.00%,用网格搜索方法对 其进行五折交叉验证调参,调参后的预测准确率为 84.18%。
多次调参后的贝叶斯分类混淆矩阵如图,可以看到整体的预测结果是非常不 错的,但是测试集中的差评一共 493 个,就有 189 个被错误的预测为了好评,而 测试集中的好评却很少被预测错误。考虑是原始数据中差评好评的数量悬殊导致, 贝叶斯分类用于预测酒店评价的好坏还是十分有效的。
K近邻算法和逻辑回归对比
类似的,还使用K近邻算法和逻辑回归进行了分类预测,得到混淆矩阵,最终预测结果对比如下:
整体来看,k 近邻算法的分类效果最差,可能是 k 近邻算法在计算每一个数 据的分类的时候都会重新开启一次全局运算,在高维空间进行计算的时候,计算 速度就会比较慢,而且在高维空间中计算距离,就容易使距离变的非常远。调参 后的朴素贝叶斯和逻辑回归的效果差不多,都比较好。观察三个分类方法的混淆 矩阵,对于差评的分类普遍比较差,考虑是原始数据中对于酒店的评价差评就少 很多,以至于可供学习的好评的样本数量足够而差评的样本数量不足以让机器拥 有良好的预测能力。
结论
对酒店评价数据进行分类预测,贝叶斯分类器和逻辑回归都表现很好。但受 到原始数据的分布局限,给分类器带来了一定程度的学习困难,以至于 k 近邻算 法的预测效果即使调参之后也很一般,而贝叶斯分类器和逻辑回归在原始数据集 存在分布不平衡的情况下,依然做出了良好的学习预测,并且取得了比较好的预 测结果,可以期望,消除原始数据分布不平衡的影响之后,这两种方法会得到更 好的结果。
代码
from gensim import corpora
from gensim import models
from gensim.corpora import Dictionary
import WordCloud
import numpy as np
from PIL import Image
from wordcloud import WordCloud
import numpy as np
import pandas as pd
import jieba.posseg as jp, jieba
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] #这两列设置显示中文
plt.rcParams['axes.unicode_minus'] = False
pd.set_option('display.max_columns', None) #显示所有列
pd.set_option('display.max_rows', None) #显示所有行
pd.set_option('display.width', 1000)
pd.set_option('display.max_colwidth',1000)
data = pd.read_csv('D:/somedata/酒店评论数据集.cvs',encoding = 'utf--8')
#%% 清洗数据
import re
def remove_punctuation(line): #去除所有标点符号和数字
line = str(line)
if line.strip()=='':
return ''
rule = re.compile(u"[^a-zA-Z0-9\u4E00-\u9FA5]") #去除所有标点符号
line = rule.sub('',line)
line = ''.join([i for i in line if not i.isdigit()]) #去除数字
return line
data['review'] = data['review'].apply(remove_punctuation)
#import sys
#np.set_printoptions(threshold=sys.maxsize)
data1 = open("D:/somedata/酒店评论数据集(无标点数字).csv",'w+') # a+是如果文件不存在就创建,如果文件存在就在文件内容的后面追加
print(data,file=data1) # 写入文档
data1.close()
def get_jiebaword(): #定义分词函数
try:
with open('D:/somedata/酒店评论数据集(无标点数字).csv', "r", encoding='gbk') as fr:
lines = fr.readlines()
except FileNotFoundError:
print("no file like this")
jiebaword = []
for line in lines:
line = line.strip('\n')
# 清除多余的空格
line = "".join(line.split())
# 默认精确模式
seg_list = jieba.cut(line, cut_all=False)
word = "/".join(seg_list)
jiebaword.append(word)
return jiebaword
jiebaword = get_jiebaword() #jieba分词
def get_stopword(): #获取停用词表
stopword = []
try:
with open('D:/somedata/stopword1.txt', "r", encoding='utf-8') as fr:
lines = fr.readlines()
except FileNotFoundError:
print("no file like this")
for line in lines:
line = line.strip('\n')
stopword.append(line)
return stopword
stopword = get_stopword()
def clean_stopword(jiebaword,stopword):
fw = open('D:/somedata/cleanWords.txt', 'w+',encoding='utf-8') #w+文件不存在就创建,存在就覆盖
for words in jiebaword:
words = words.split('/')
for word in words:
if word not in stopword:
fw.write(word + '\t')
fw.write('\n')
fw.close() #去除停用词
clean_stopword(jiebaword,stopword) #保存清洗后的数据
f=open('D:/somedata/cleanWords.txt',encoding='utf-8')
cleanwords=[]
for line in f:
cleanwords.append(line.strip())
#print(cleanwords)
data_clean = {'cleanwords':cleanwords, 'label':data['label']}
data_clean = pd.DataFrame(data_clean)
#%%
a=[]
for i in range(0,7766,1):
a.append(cleanwords[i].split()) #zhuanhuanweiliebiao
#将清洗后的数据转化为 Corpora_Dictionary
dictionary = corpora.Dictionary(a)
#压缩词向量,去掉出现的文章小于2的词,和在70%的文章都出现的词,整体长度限制在1000
dictionary.filter_extremes(no_below=1, no_above=0.7, keep_n=1000)
corpus = [ dictionary.doc2bow(text) for text in a]
#%%词云图
import jieba
import matplotlib.pyplot as plt
from wordcloud import WordCloud
text = open("D:/somedata/cleanWords.txt", encoding="utf-8").read() # 标明文本路径,打开
# 生成对象
wc = WordCloud(font_path = "C:\Windows\Fonts\Microsoft YaHei UI\msyh.ttc",width=500, height=400, max_words=100, # 显示单词数
max_font_size=123, mode="RGBA", background_color=None).generate(text)
# 显示词云图
plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.show()
#保存文件
wc.to_file("D:/somedata/ciyun1.png")
#%% 特征工程及贝叶斯分类器
from sklearn.model_selection import train_test_split
xtrain,xtest,ytrain,ytest = train_test_split(data_clean['cleanwords'],data_clean['label'],test_size=0.2,random_state=123)
from sklearn.feature_extraction.text import TfidfVectorizer
# 利用tf-idf从文本中提取特征,写到数组里面.
tfidf = TfidfVectorizer()
X_train = tfidf.fit_transform(xtrain) # 训练数据的特征
#print(X_train)
#y_train = train_labels # 训练数据的label
X_test = tfidf.transform(xtest) # 测试数据的特征
#y_test = test_labels# 测试数据的label
#print (np.shape(X_train), np.shape(X_test), np.shape(y_train), np.shape(y_test))
#%%朴素贝叶斯
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score
clf = MultinomialNB()
# 利用朴素贝叶斯做训练
clf.fit(X_train, ytrain)
ypred = clf.predict(X_test)
print("accuracy on test data: ", accuracy_score(ytest, ypred))
#accuracy on test data: 0.7400257400257401
#调参
from sklearn.model_selection import GridSearchCV
param_grid ={}
param_grid['alpha'] = [0.001,0.1,0.5,1]
grid = GridSearchCV(estimator= clf, param_grid = param_grid, scoring='accuracy', cv=5)
grid_result = grid.fit(X = X_train, y = ytrain)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
#最优:0.8407955403723006 使用{'alpha': 0.1}
param_grid ={}
param_grid['alpha'] = [0.06,0.08,0.1,0.12,0.14]
grid = GridSearchCV(estimator= clf, param_grid = param_grid, scoring='accuracy', cv=5)
grid_result = grid.fit(X = X_train, y = ytrain)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
#最优:0.8417609466474415 使用{'alpha': 0.12}
param_grid ={}
param_grid['alpha'] = [0.11,0.12,0.13]
grid = GridSearchCV(estimator= clf, param_grid = param_grid, scoring='accuracy', cv=5)
grid_result = grid.fit(X = X_train, y = ytrain)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
#最优:0.8427273893222335 使用{'alpha': 0.11}
clf = MultinomialNB(alpha = 0.11)
# 利用朴素贝叶斯做训练
clf.fit(X_train, ytrain)
ypred = clf.predict(X_test)
print("accuracy on test data: ", accuracy_score(ytest, ypred))
#accuracy on test data: 0.8436293436293436
#混淆矩阵
from sklearn.utils.multiclass import unique_labels
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
def plot_confusion_matrix(y_true, y_pred, classes,
normalize=False,
title=None,
cmap=plt.cm.Blues):
"""
This function prints and plots the confusion matrix.
Normalization can be applied by setting `normalize=True`.
"""
if not title:
if normalize:
title = 'confusion matrix'
else:
title = 'confusion matrix'
# Compute confusion matrix
cm = confusion_matrix(y_true, y_pred)
# Only use the labels that appear in the data
classes = classes[unique_labels(y_true, y_pred)]
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
#print("Normalized confusion matrix")
else:
pass
#print('Confusion matrix, without normalization')
#print(cm)
fig, ax = plt.subplots()
im = ax.imshow(cm, interpolation='nearest', cmap=cmap)
ax.figure.colorbar(im, ax=ax)
# We want to show all ticks...
ax.set(xticks=np.arange(cm.shape[1]),
yticks=np.arange(cm.shape[0]),
# ... and label them with the respective list entries
xticklabels=classes, yticklabels=classes,
title=title,
ylabel='True label',
xlabel='Predicted label')
ax.set_ylim(len(classes)-0.5, -0.5)
# Rotate the tick labels and set their alignment.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
rotation_mode="anchor")
# Loop over data dimensions and create text annotations.
fmt = '.2f' if normalize else 'd'
thresh = cm.max() / 2.
for i in range(cm.shape[0]):
for j in range(cm.shape[1]):
ax.text(j, i, format(cm[i, j], fmt),
ha="center", va="center",
color="white" if cm[i, j] > thresh else "black")
fig.tight_layout()
return ax
# y_test为真实label,y_pred为预测label,classes为类别名称,是个ndarray数组,内容为string类型的标签
class_names = np.array(["0","1"]) #按你的实际需要修改名称
plot_confusion_matrix(ytest, ypred, classes=class_names, normalize=False,title=None)
#%%knn
from sklearn.neighbors import KNeighborsClassifier
#进行K近邻算法的预测
knn = KNeighborsClassifier()
#将训练集放入模型中开始训练
knn.fit(X_train,ytrain)
ypred = knn.predict(X_test)
print("accuracy on test data: ", accuracy_score(ytest, ypred))
#accuracy on test data: 0.7631917631917632
'''
param_grid = {} #,metric = 'brute'
param_grid['p'] = [4,5,6,7,8]
'''
param_grid = [
{ # 需遍历10次
'weights': ['uniform'], # 参数取值范围
'n_neighbors': [i for i in range(1, 11)] # 使用其他方式如np.arange()也可以
# 这里没有p参数
},
{ # 需遍历50次
'weights': ['distance'],
'n_neighbors': [i for i in range(1, 11)],
'p': [i for i in range(1, 6)]
}
]
grid = GridSearchCV(estimator=knn,param_grid = param_grid, scoring='accuracy', cv=5)
grid_result = grid.fit(X=X_train,y=ytrain)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
#最优:0.7830021388697802 使用{'n_neighbors': 10, 'weights': 'uniform'}
knn = KNeighborsClassifier(n_neighbors=10,weights='uniform')
#将训练集放入模型中开始训练
knn.fit(X_train,ytrain)
ypred = knn.predict(X_test)
print("accuracy on test data: ", accuracy_score(ytest, ypred))
#accuracy on test data: 0.7882882882882883
plot_confusion_matrix(ytest, ypred, classes=class_names, normalize=False,title=None)
#%%LogisticRegression
from sklearn.linear_model import LogisticRegression
log = LogisticRegression()
log.fit(X_train, ytrain)
ypred = log.predict(X_test)
print("accuracy on test data: ", accuracy_score(ytest, ypred))
#accuracy on test data: 0.8642213642213642
param_grid = {}
param_grid['C'] = [4,5,6,7,8]
grid = GridSearchCV(estimator=log,param_grid = param_grid, scoring='accuracy', cv=5)
grid_result = grid.fit(X=X_train,y=ytrain)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
#最优:0.8544753680190386 使用{'C': 7}
plot_confusion_matrix(ytest, ypred, classes=class_names, normalize=False,title=None)