记一次nlp学习
数据来源:
这里我忘记了,但是是已经打好标签的酒店评论的数据集,1表示正向评论,0表示负向评论。
1、数据导入与预处理
1.1数据导入
import pandas as pd
df = pd.read_csv('../hoteData.csv',index_col=0)
数据展示如下

1.2数据预处理
1.2.1 去掉空缺值
df.dropna(how="any",inplace=True)
1.2.2 划分正负样本
posdf = df[df["label"] == 1] # 正样本
negdf = df[df["label"] == 0] # 负样本
划分正负样本后,查看正负样本得到,正样本有5322条,负样本有2443条负样本。
1.2.3 分词和停词处理
在分词这里将主要使用python的jieba库,并且加上正则表达式来处理一些数字
import jieba
import re
- 添加自定义的用户词典
这里一般来说是要添加有关领域的专业词典,但是我没有找到,就不添加了 - 删除数字
这里是发现数字及数据后面紧跟的第一个字符,往往是可以看成一个整体的,并且这些数字往往是形容日期等等一些无关的信息。所以自己想方法将其剔除(这是自己的想法,并没有找到依据,并且代码有一定的不合理之处)。
def drop_num_near(string):
#将汉字中的数字替换为阿拉伯数字
han = ["一","二","三","四","五","六","七","八","九","十"]
yin = [1,2,3,4,5,6,7,8,9,10]
for k in range(len(han)):
string = string.replace(han[k],str(yin[k]))
#将数字及数字后一个字符一起删除
pattern = re.compile('[0-9]+.+?')
term = re.findall(pattern,string)
if len(term) != 0:
for i in term:
string = string.replace(i,"")
return string
else :
return string
# 函数的一个实例如下
drop_num_near("这次是308的行政大床,总体感觉非常不错,就是价格稍许高了点,旁边有个五星的豪华客房才398。估计小天鹅也只有这个房型以上的,看得过去,以前住过的房间实在是很差。以后大家如果要住这里,还是选这个行政大床吧!")
# '这次是行政大床,总体感觉非常不错,就是价格稍许高了点,旁边有个的豪华客房才估计小天鹅也只有这个房型以上的,看得过去,以前住过的房间实在是很差。以后大家如果要住这里,还是选这个行政大床吧!'
- 分词和停词处理
首先是读入停词表,这里的停词使用的是哈工大和川大的停词表合并去重后的结果
# 读入哈工大停词表
with open("../stop/hit.txt", 'r', encoding='utf-8') as fp:
stop3 = []
for item in fp.readlines():
stop3.append(item.strip())
fp.close()
# 读入川大停词表
with open("../stop/scu.txt", 'r', encoding='utf-8') as fp:
stop4 = []
for item in fp.readlines():
stop4.append(item.strip())
fp.close()
# 停词表合并与去重
sumstop = stop3 + stop4
stopdf = pd.DataFrame(sumstop)# 这里的去重方法用的是pandas的drop_dupllicates()方法。不要问我为什么用这个方法,我也不知道,自己怎么会想到这个方法
stopwordlist = list(stopdf.drop_duplicates()[0])
stopwordlist就是最终的停词表
然后是定义分词函数,这里之所以定义一个分词函数,是为了使用pandas的apply方法。
# 评论文本中还有一些英文,这里要将英文也给删除
# 定义判断是不是英文字符的函数
def judge(item):
pattern = re.compile('[a-zA-Z]+')
result = re.findall(pattern,item)
if len(result)== 0:
return True
else:
return False
def cut_words(string):
# 传入一个字符串,首先去掉数字
string = drop_num_near(string)
#调用jieba的lcut方法进行分词,分词的结果以list形式表示
cut_list = jieba.lcut(string)
# 定义一个新的列表,用来存储在不在停词表和非英文的词
cut_result_list = []
# 遍历 分词结果,存储到cut_result_list中
for item in cut_list:
if (item not in stopwordlist) and (judge(item)):
cut_result_list.append(item)
#返回分词结果
return cut_result_list
# 分词函数的一个实例如下
cut_result = cut_words("这次是行政大床,总体感觉非常不错,就是价格稍许高了点,旁边有个的豪华客房才估计小天鹅也只有这个房型以上的,看得过去,以前住过的房间实在是很差。以后大家如果要住这里,还是选这个行政大床吧!")
for item in cut_result:
print(item,end = " ")
# 结果如下
# 这次 行政 大床 总体 感觉 不错 价格 稍许 高 点 旁边 有个 豪华 客房 才 估计 小天鹅 房型 以上 看 过去 以前 住 房间 实在 差 以后 住 选 行政 大床
对posdf和negdf调用分词函数,得到分词结果,存储到list_cuted_review列中
posdf["list_cuted_review"] = posdf["review"].apply(cut_words)
negdf["list_cuted_review"] = negdf["review"].apply(cut_words)
1.2.4 分词后结果查看
这里主要是绘制词云图和查看频率前10的单词,首先绘制正样本的词云图
# 导入wordcloud包
from wordcloud import WordCloud
# 正样本的词云图绘制
#这里是调整为wordcloud绘制词云图需要的格式也即是用,分割的字符串
long_string = ""
for item in posdf["list_cuted_review"]:
string = ','.join(item)
long_string = long_string+string + ','
# 创建一个词云对象
wordcloud = WordCloud(font_path='C:\windows\Fonts# \STZHONGS.TTF',# 字体
background_color="white",
max_words=100, contour_width=3,
contour_color='steelblue',
width=1500,height=1000)
# 生成词云
wordcloud.generate(long_string)
# 可视化词云
wordcloud.to_image()
正样本词云图如下所示

然后绘制负样本的词云图
#这里是调整为wordcloud绘制词云图需要的格式也即是用,分割的字符串
long_string = ""
for item in negdf["list_cuted_review"]:
string = ','.join(item)
long_string = long_string+string + ','
# 创建一个词云对象
wordcloud = WordCloud(font_path='C:\windows\Fonts# \STZHONGS.TTF',# 字体
background_color="white",
max_words=100, contour_width=3,
contour_color='steelblue',
width=1500,height=1000)
# 生成词云
wordcloud.generate(long_string)
# 可视化词云
wordcloud.to_image()
负样本词云图如下所示

通过查看正负样本的词云图,发现如酒店、房间、服务员等词都是高频词汇,并且同时出现在正样本和负样本中。于是决定进一步查看正负样本的词频
#自己定义一个获取单词和词频的函数
def get_ci_ping(string):
word_dict={}
long_list = string.split(",")
for item in long_list:
if item in word_dict.keys():
word_dict[item] += 1
else:
word_dict[item] = 1
return word_dict
# 获取正向评论的分词表的词频结果
long_string = ""
for item in posdf["list_cuted_review"]:
string = ','.join(item)
long_string = long_string+string + ','
# 获取正向评论的词频词典
pos_word_dict = get_ci_ping(long_string)
# 获取正向评论的频数排名前10的词
pos_word_df = pd.DataFrame(pos_word_dict.values(),index = pos_word_dict.keys())
pos_list = list(pos_word_df.sort_values(0,ascending=False).iloc[0:10,].index)
pos_list
# ['酒店', '房间', '不错', '好', '还', '服务', '都', '住', '比较', '不']
# 同理可得负样本的频数前10的词
long_string = ""
for item in negdf["list_cuted_review"]:
string = ','.join(item)
long_string = long_string+string + ','
# 获取正向评论的词频词典
neg_word_dict = get_ci_ping(long_string)
# 获取正向评论的频数排名前10的词
neg_word_df = pd.DataFrame(neg_word_dict.values(),index = neg_word_dict.keys())
neg_list = list(neg_word_df.sort_values(0,ascending=False).iloc[0:10,].index)
neg_list
#['酒店', '房间', '不', '都', '说', '住', '还', '入住', '服务', '前台']
可以看到,频数前10的词有很大的重叠,于是决定去掉频数前十的重合的词
#获取重叠的词的列表
lap_word_list = []
for item in pos_list:
if item in neg_list:
lap_word_list.append(item)
#定义删除重叠的词的函数
def drop_lap_word(wlist):
for item in wlist:
if item in lap_word_list:
wlist.remove(item)
return wlist
#删除正负样本中的高频重叠词
negdf["list_cuted_review"].apply(drop_lap_word)
posdf["list_cuted_review"].apply(drop_lap_word)
至此数据预处理部分基本完成,但是考虑到后面要使用sklearn中的一些工具包,这里将list形式的分词结果要转换为str形式的
def str_cuted_review(wlist):
return ' '.join(wlist)
posdf["str_cuted_review"] = posdf["list_cuted_review"].apply(str_cuted_review)
negdf["str_cuted_review"] = negdf["list_cuted_review"].apply(str_cuted_review)
至此处理完毕,正负样本如下所示


2 模型构建预处理
2.1 构建词向量与降维
这里使用正负样本整体进行词向量的训练,所以要将正负样本进行重新拼接
df = pd.concat([posdf[["label","str_cuted_review"]],negdf[["label","str_cuted_review"]]])
#重新设置index
df.index = list(range(df.shape[0]))
#查看正负样本的index分布
df[df["label"] == 0]
导入有关的包,对分词后的结果进行词向量的训练
from sklearn.feature_extraction.text import CountVectorizer
#构建模型
countvectorizer = CountVectorizer(token_pattern=r"(?u)\b\w+\b")
#训练模型
count = countvectorizer.fit_transform(df["str_cuted_review"])
#查看词向量的纬度
count.toarray().shape
#(7765, 26832)
可以看出,词向量的维度特别高,不能直接拿来进行训练和测试,但是本来是准备用pca方法来进行降维的,但是pca不能对稀疏矩阵进行降维,这里转用svd方法
from sklearn.decomposition import TruncatedSVD
#设置模型的输出结果为200维
svd = TruncatedSVD(200)
#对词向量的结果进行转换
data = svd.fit_transform(count)
2.2 划分训练集和测试集
这里划分训练集和测试集的方法是抽取随机数
def get_rand_index(rate,num1,num2):
N = int((num2-num1) * rate)
resultList=random.sample(range(num1,num2),N)
return resultList
def get_train_test_index(rate):
train_index = get_rand_index(rate,0,5322)+get_rand_index(rate,5322,7765)
test_index = []
for item in range(0,7764):
if item not in train_index:
test_index.append(item)
return train_index,test_index
#获取训练集和测试集的index组成的list
train_index ,test_index = get_train_test_index(0.7)
根据train_index和test_index划分x_train,x_test,y_train,y_test
x_train = data[train_index]
y_train = list(df.iloc[train_index,0])
x_test = data[test_index]
y_test = list(df.iloc[test_index,0])
3模型构建
3.1 svm模型
from sklearn import svm
#构建模型
model=svm.SVC(C=0.6,kernel="linear",gamma=10,decision_function_shape='ovo',probability = False)
#训练
model.fit(x_train,y_train)
#训练集得分
model.score(x_train,y_train)
#0.8809567617295309
#测试集得分
model.score(x_test,y_test)
#0.8750536711034779
可以看到,在训练集上准确率达到了87.5。为了进一步探究recall和,1score,可以用以下代码
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
from sklearn.metrics import recall_score
#首先使用训练好的模型,预测测试集的结果
svmresult = model.predict(x_test)
#使用预测的结果来计算F1,recall,准确率
accuracy_score(y_test,svmresult)
#准确率为0.8750536711034779
f1_score(y_test,svmresult)
#f1得分为0.9107635694572216
recall_score(y_test,svmresult)
#recall值为0.9298685034439574
可以看到svm模型表现的很不错,接下来使用朴素贝叶斯、随机森林、逻辑回归、KNN算法分别看看表现
3.2 朴素贝叶斯
from sklearn.naive_bayes import BernoulliNB
model = BernoulliNB()
model.fit(x_train,y_train)
#查看在训练集上的准确率
model.score(x_train,y_train)
#0.8099356025758969
#预测测试集上的输出结果
nbresult = model.predict(x_test)
#使用预测的结果来计算F1,recall,准确率
accuracy_score(y_test,nbresult)
#准确率为0.8016316015457278
f1_score(y_test,nbresult)
#f1得分为0.8598300970873787
recall_score(y_test,nbresult)
#recall值为0.8872886662492173
可以看到朴素贝叶斯表现不如svm
3.3 随机森林
from sklearn.ensemble import RandomForestClassifier as RFC
model = RFC(n_estimators= 220)
model.fit(x_train,y_train)
#查看在训练集上的准确率
print(model.score(x_train,y_train))
#0.9996320147194112
#预测测试集上的输出结果
nbresult = model.predict(x_test)
#使用预测的结果来计算F1,recall,准确率
print(accuracy_score(y_test,nbresult))
#准确率为0.8355517389437527
print(f1_score(y_test,nbresult))
#f1得分为0.886045819696519
print(recall_score(y_test,nbresult))
#recall值为0.9323731997495304
随机森林要好于朴素贝叶斯,部分值高于svm
3.4 最近邻算法
from sklearn import neighbors
model = neighbors.KNeighborsClassifier(n_neighbors=15)
model.fit(x_train,y_train)
#查看在训练集上的准确率
print(model.score(x_train,y_train))
#0.8132474701011959
#预测测试集上的输出结果
nbresult = model.predict(x_test)
#使用预测的结果来计算F1,recall,准确率
print(accuracy_score(y_test,nbresult))
#准确率为0.793902962644912
print(f1_score(y_test,nbresult))
#f1得分为0.861910241657077
print(recall_score(y_test,nbresult))
#recall值为0.9380087664370695
KNN算法的recall是表现的最好的,但是其他指标表现并不理想
3.5 逻辑回归
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(x_train,y_train)
#查看在训练集上的准确率
print(model.score(x_train,y_train))
#0.8776448942042319
#预测测试集上的输出结果
nbresult = model.predict(x_test)
#使用预测的结果来计算F1,recall,准确率
print(accuracy_score(y_test,nbresult))
#准确率为0.8656075568913697
print(f1_score(y_test,nbresult))
#f1得分为0.9038992938286767
print(recall_score(y_test,nbresult))
#recall值为0.9217282404508453
逻辑回归表现也还行。
感觉机器学习的算法都还行,接下来将学习bilstm方法