前言:
上一节介绍了基于情感词典的文本情感分类方法,这一节主要介绍采用机器学习的方法,对已有分类的数据集进行训练并测试,我从网上下载了一些关于外卖评论的情感语料,分成两个txt文件,一个是正向评论的文本文档,一个是负面评论的文本文档,如下图所示,本文基于此进行分类学习。
目录:
1.读取数据
2.分词
3.提取特征向量
4.机器学习之分类
1.读取数据
import numpy as np
import pandas as pd
import jieba
from sklearn.model_selection import train_test_split #划分训练/测试集
from sklearn.feature_extraction.text import CountVectorizer #抽取特征
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import TfidfVectorizer
#读取并清洗数据
#因为几个文档的编码不大一样,所以兼容了三种编码模式,根据经验,这三种是经常会遇到的
def get_txt_data(txt_file):
mostwords=[]
try:
file=open(txt_file,'r',encoding='utf-8')
for line in file.readlines():
curline=line.strip().split("\t")
mostwords.append(curline)
except:
try:
file=open(txt_file,'r',encoding='gb2312')
for line in file.readlines():
curline=line.strip().split("\t")
mostwords.append(curline)
except:
try:
file=open(txt_file,'r',encoding='gbk')
for line in file.readlines():
curline=line.strip().split("\t")
mostwords.append(curline)
except:
''
return mostwords
neg_doc=get_txt_data(r'D:\nltk_data\waimai_nlp\情感分析语料\waimai_neg.txt')
pos_doc=get_txt_data(r'D:\nltk_data\waimai_nlp\情感分析语料\waimai_pos.txt')
2.分词
分词工具有很多,主流的主要包括以下几种:
- 哈工大的LTP HIT-SCIR/ltp
- jieba分词工具 yanyiwu/cppjieba
- 清华大学THULAC https://github.com/thunlp/THULAC
- 斯坦福分词器 https://nlp.stanford.edu/software/segmenter.shtml
- Hanlp分词 hankcs/HanLP
- 字嵌入+Bi-LSTM+CRF分词器 https://github.com/koth/kcws
- ZPar分词器 frcchang/zpar
本篇主要采用了比较主流的jieba分词包,具体内容可以详见:jieba分词完整文档
另外,我将每个评论中一些垃圾词汇去掉了。所谓的垃圾词汇,就是指意义模糊的词,或者一些语气助词,标点符号等等,通常他们对文本起不了分类特征的意义。这些垃圾词汇我们称之为停用词。把所有停用词集合起来构成一张停用词表格,这样,以后我们处理文本时,就可以从这个根据表格,过滤掉文本中的一些垃圾词汇了。我的停用词是从网上下载了几个,合并起来的。大家可以自行下载或者准备自己需要的停用词。
def context_cut(sentence):
words_list=[]
#获取停用词
stop=open(r'D:\nltk_data\NLP\情感分析\停用词库.txt','r+',encoding='utf-8')
stopwords=stop.read().split('\n')
cut_words=list(jieba.cut(sentence))
for word in cut_words:
if not(word in stopwords):
words_list.append(word)
words_str=','.join(words_list)
return words_str,words_list
#合并两个数据集,并且打上标签,分成测试集和训练集
words=[]
word_list=[]
for i in neg_doc:
cut_words_str,cut_words_list=context_cut(i[0])
word_list.append((cut_words_str,-1))
words.append(cut_words_list)
for j in pos_doc:
cut_words_str2,cut_words_list2=context_cut(j[0])
word_list.append((cut_words_str2,1))
words.append(cut_words_list2)
#word_list=[('菜品,质量,好,味道,好,就是,百度,的,问题,总是,用,运力,原因,来,解释,我,也,不,懂,这,是,什么,原因,晚,了,三个,小时,呵呵,厉害,吧,反正,订,了,就,退,不了,只能,干,等', -1),...,...]
#将word_list中的值和标签分别赋予给x,y
x,y=zip(*word_list)
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=0)
3.提取特征向量
3.1 ##将内容转为词袋向量(Bag of words)
在正式使用之前,我们首先举个例子来看一下词袋向量是什么样的:
data1= '今天上海的天气真好!我的心情非常高兴!如果去旅游的话我会非常兴奋!和你一起去旅游我会更加幸福!'
data2= '救命,你是个坏人,救命,你不要碰我,救命,你个大坏蛋!'
data3= '美国华裔科学家,祖籍江苏扬州市高邮县,生于上海,斯坦福大学物理系,电子工程系和应用物理系终身教授!'
words_str1,words_list1=context_cut(data1)
words_str2,words_list2=context_cut(data2)
words_str3,words_list3=context_cut(data3)
count = CountVectorizer()#这里可以加上max_features=10的参数,不加就是这三句话的全部词语。
doc = np.array([words_str1,words_str2,words_str3])
bag = count.fit_transform(doc)
print(count.vocabulary_) #查看词袋字典
[out]:{'今天': 3, '上海': 1, '天气': 8, '心情': 13, '非常高兴': 31, '如果': 9, '旅游': 19, '的话': 25, '我会': 14, '非常': 30,'兴奋': 4, '一起': 0, '更加': 20, '幸福': 11, '救命': 16, '坏人': 6, '不要': 2, '大坏蛋': 7, '美国': 29, '华裔': 5, '科学家': 27, '祖籍': 26, '江苏': 21, '扬州市': 15, '高邮县': 32,'生于': 23, '斯坦福大学': 18, '物理系': 22, '电子': 24, '工程系': 10, '应用': 12, '终身': 28, '教授': 17}
#三个句子的所有词的字典,后面的数字为每个词的唯一索引
print(bag.toarray()) #将语句转化为向量列表
#按照上述词袋的索引值从小到大进行排列,['一起','上海','不要',······],根据此顺序列出每句话的词语出现的频次,所以下列输出重得值为词的出现频率
[out]:[[1 1 0 1 1 0 0 0 1 0 1 0 1 2 0 0 0 0 2 1 0 0 0 0 0 0 0 0 1 1 0]
[0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 1 0 0 0 1 0 1 0 0 1 0 1 1 0 0 1 2 1 1 1 1 1 1 0 0 1]]
回归正题,下面将上面的分词文本转化成结构化向量:
#注意,countvectorizer只能训练str格式的,因为它里面会有lower之类的函数,不适用于列表等其它格式
count = CountVectorizer(max_features=500) #这里的max_features实根据后面的机器学习模型确定的,原先定为200的时候,机器学习效果较差,后改为500效果较好
bag = count.fit_transform(x_train)
3.2 TF-IDF(词频-逆向文件频率)
TF-IDF的主要思想是:如果某个单词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。具体原理可以参照TF-IDF算法介绍及实现
具体实现:
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
count = CountVectorizer(max_features=500) #该类会将文本中的词语转换为词频矩阵,矩阵元素a[i][j] 表示j词在i类文本下的词频
tfidf = TfidfTransformer(use_idf = True, norm = 'l2', smooth_idf = True) #该类会统计每个词语的tf-idf权值
tfidf_x_train=tfidf.fit_transform(count.fit_transform(x_train)) #将文本转为词频矩阵并计算tf-idf
np.set_printoptions(precision = 2)
x_train_weight = tfidf_x_train.toarray()
print(x_train_weight)
#还有另一种方法是TfidfVectorizer,
#关于TfidfVectorizer和TfidfTransformer的关联可以参照[TfidfVectorizer和TfidfTransformer](https://blog.csdn.net/liuchenbaidu/article/details/105063535?biz_id=102&utm_term=TfidfVectorizer&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-105063535&spm=1018.2118.3001.4187)
from sklearn.feature_extraction.text import TfidfVectorizer
vec=TfidfVectorizer(analyzer='word', ngram_range=(1,4), max_features=500)
tfidf_x_train=vec.fit_transform(x_tain) #与上面一种TfidfTransformer
tfidf_x_test=vec.fit_transform(x_test)
4.模型训练
4.1 回归模型
from sklearn.linear_model import LinearRegression
lin_reg=LinearRegression()
lin_reg.fit(vec.transform(x_train),y_train)
from sklearn.metrics import mean_squared_error
y_test_hat=lin_reg.predict(vec.transform(x_test))
y_train_hat=lin_reg.predict(vec.transform(x_train))
lin_mse1=mean_squared_error(y_test,y_test_hat)
lin_mse2=mean_squared_error(y_train,y_train_hat)
lin_rmse1=np.sqrt(lin_mse1)
lin_rmse2=np.sqrt(lin_mse2)
print ('测试集:',lin_rmse1)
print ('训练集:',lin_rmse2)
#使用tf-idf特征向量,使用线性回归分类,mean_squared_error均方误差为0.71440708
'''测试集: 0.7144070856551623
训练集: 0.6777972230714499'''
4.2 决策树模型
##----------2.决策树-----##
#模型效果不好的时候(拟合不足),考虑换个更强大的模型,决策树
from sklearn.tree import DecisionTreeRegressor
tree_reg=DecisionTreeRegressor()
tree_reg.fit(vec.transform(x_train),y_train)
y_test_hat=tree_reg.predict(vec.transform(x_test))
y_train_hat=tree_reg.predict(vec.transform(x_train))
tree_mse1=mean_squared_error(y_test,y_test_hat)
tree_mse2=mean_squared_error(y_train,y_train_hat)
tree_rmse1=np.sqrt(tree_mse1)
tree_rmse2=np.sqrt(tree_mse2)
print ('测试集',tree_rmse1)
print ('训练集',tree_rmse2)
#使用tf-idf特征向量,使用决策树,
#如果tree_rmse接近0,可能模型师完美的,更可能的是数据严重过度拟合了
#在有信心启动模型之前,都不要触碰测试集,所以,建议拿训练集中的一部分用户训练,另一部分用于模型的验证
'''测试集 0.7432269884814903
训练集 0.31452945551691786'''
4.3 贝叶斯分类器–MultinomialNB
'''
使用CountVectorizer进行特征提取,使用MultinomialNB分类训练,max_features=200时,结果为0.4728125
max_features=500时,结果为0.5665625
'''
classfier = MultinomialNB()
classfier.fit(count.fit_transform(x_train),y_train)
print(classfier.score(count.fit_transform(x_test),y_test))
#改用tf-idf进行特征提取,max_features=500时,结果为0.834375
classfier = MultinomialNB()
classfier.fit(vec.transform(x_train),y_train)
print('训练集:',classfier.score(vec.transform(x_test),y_test))
print('测试集:',classfier.score(vec.transform(x_train),y_train))
'''训练集: 0.8275
测试集: 0.83953125'''
4.4 SVM模型
##----------4.使用SVM模型进行训练----##
from sklearn import svm
#使用TF-IDF提取特征,使用SVM训练,结果为0.83125
from sklearn.metrics import accuracy_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
from sklearn.metrics import roc_auc_score
classfier = svm.SVC(kernel='linear')
classfier.fit(vec.transform(x_train),y_train)
#print(classfier.score(vec.transform(x_test),y_test))
print ('训练集:',classfier.score(vec.transform(x_train), y_train)) # 精度
print ('测试集:',classfier.score(vec.transform(x_test), y_test))
'''训练集: 0.85234375
测试集: 0.83125'''
y_train_hat=classfier.predict(vec.transform(x_train))
print ('训练集准确率:',accuracy_score(y_train_hat,y_train))
print ('训练集召回率:',recall_score(y_train_hat,y_train))
print ('F1:',f1_score(y_train_hat,y_train))
print ('ROC值:',roc_auc_score(y_train_hat,y_train))
'''训练集准确率: 0.85234375
训练集召回率: 0.8585552543453995
F1: 0.8506873123716229
ROC值: 0.852466476919981'''
# 分类报告:precision/recall/fi-score/均值/分类个数
from sklearn.metrics import classification_report
target_names = ['-1','1']
print(classification_report(y_train_hat,y_train, target_names=target_names))
4.5 随机森林(RandomForestRegressor)
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from IPython import display
forest_reg=RandomForestRegressor()
forest_reg.fit(vec.transform(x_train),y_train)
print('训练集:',forest_reg.score(vec.transform(x_test),y_test))
print('测试集:',forest_reg.score(vec.transform(x_train),y_train))
'''训练集: 0.5592796842264617
测试集: 0.8471523627994759'''
##引入交叉验证,对模型进行调优
x_train_tf=tfidf.fit_transform(count.fit_transform(x_train)).toarray()
forest_scores=cross_val_score(forest_reg,x_train_tf,y_train,scoring='neg_mean_squared_error',cv=10)
forest_rmse_scores=np.sqrt(-forest_scores)
display(forest_rmse_scores)
4.6 逻辑回归
#引入随机搜索,选择最优模型参数
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import GridSearchCV
tfidf = TfidfVectorizer(strip_accents=None,
lowercase=False,
preprocessor=None)
param_grid = [{'vect__ngram_range': [(1, 1)],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]},
{'vect__ngram_range': [(1, 1)],
'vect__use_idf':[False],
'vect__norm':[None],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]},
]
lr_tfidf = Pipeline([('vect', tfidf),
('clf', LogisticRegression(random_state=0))])
gs_lr_tfidf = GridSearchCV(lr_tfidf, param_grid,
scoring='accuracy',
cv=5,
verbose=1,
n_jobs=-1)
gs_lr_tfidf.fit(x_train, y_train)
print('Best parameter set: %s ' % gs_lr_tfidf.best_params_)
print('CV Accuracy: %.3f' % gs_lr_tfidf.best_score_)
clf = gs_lr_tfidf.best_estimator_
print('Test Accuracy: %.3f' % clf.score(x_test, y_test))
'''
Best parameter set: {'clf__C': 10.0, 'clf__penalty': 'l2', 'vect__ngram_range': (1, 1)}
CV Accuracy: 0.877
Test Accuracy: 0.888'''
总结:
从结果看下来,比较好的模型是逻辑回归,MultinomialNB和SVM三种,本文采用的是tf-idf进行的特征提取,后面计划尝试用word2vec进行看看。以上,为一个NLP的新手学习,有任何不正确的欢迎指正!