赞
踩
导入必要的库函数
import pandas
import numpy as np
import matplotlib.pyplot as plt
from time import time
import seaborn as sns
sns.set()
from sklearn import model_selection as MS
pd_all = pd.read_csv('weibo_senti_100k.csv')
print(f'评论数目(总体):{pd_all.shape[0]}')
print(f'评论数目(正向):{pd_all[pd_all.label==1].shape[0]}')
print(f'评论数目(负向):{pd_all[pd_all.label==0].shape[0]}')
import re #删除用户名 def delete_user(text): patt = re.compile(r'@.*?[:: ]') #正则表达式匹配 line = re.sub(patt,"",text) return line #删除非必要字符的方法 def preprocessor(text): text = re.sub('<[^>]*>', '', text) emoticons = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)', text) text = (re.sub('[\W]+', ' ', text.lower()) + ' '.join(emoticons).replace('-', '')) return text #删除非中文字符 def remain_character(String): blog_new = u"" for i in range(0,len(String)): if(String[i] >=u'\u4e00' and String[i]<=u'\u9fa5'): #在中文范围内的保存 blog_new = blog_new + String[i] return blog_new pd_all['review'] = pd_all['review'].apply(delete_user) pd_all['review'] = pd_all['review'].apply(preprocessor) pd_all['review'] = pd_all['review'].apply(remain_character) import jieba #将所有的空格删去,方便分词 pd_all['review'] = pd_all['review'].str.replace(" ","")
st = time()
word = jieba.cut(pd_all.loc[50000,'review'])
for i in word:
print(i,end = ' ')
print()
print("耗时为:",time()-st)
马总说 你 太累 了 也 该 歇歇 嘞 哈哈
耗时为: 0.6213374137878418
#因为已经有了文件,为了节省时间,这个直接注释掉
#对停用词进行处理,并保存为npy文件
data = open('stop_words.txt','r').read()
words = jieba.cut(data)
data_words = []
for word in words:
if word == '\n':
continue
data_words.append(word)
print(data_words)
data_np = np.array(data_words)
data_np.shape
np.save("stop_data",data_np)
stop_data = np.load('stop_data.npy')
stop_words = list(stop_data)
#这个比较费时
st = time()
#将所有的评论进行分词,放进words列表中
words = [] #最后存放结果的列表
for i, row in pd_all.iterrows():
content = [] #保存当前那一行的内容
tmp = jieba.cut(row['review']) #对当前行进行分词
for word in tmp:
if word not in stop_words: #如果不在停用词当中,就保存下来
content.append(word)
words.append(content) #将当前行加入到总的当中
print("去除停用词耗时为:",time()-st,"秒")
这部分耗时了77秒。
接下来保存上面运行的结果,避免每次运行都要进行数据预处理
#将每一行合并,因为分词之后上述的words是一个二维列表,利用空格将它们合并起来
st = time()
word_all = []
for i in range(len(words)):
hang = " ".join(words[i]) #用空格将它们串起来
word_all.append(hang)
print("耗时为:",time()-st,'秒')
word_all_npy = np.save("word_all",word_all)
数据预处理到此就结束了
之后运行模型,只需要调用上述保存的数据即可
word_all_npy = np.load("word_all.npy")
word_all = list(word_all_npy)
通过建立词频矩阵构造特征变量
词袋模型(Bag-of-words model)是个在自然语言处理和信息检索(IR)下被简化的表达模型。此模型下,一段文本(比如一个句子或是一个文档)可以用一个装着这些词的袋子来表示,这种表示方式不考虑文法以及词的顺序。
使用机器学习算法解决文本数据分析问题,必需将文本数据转化为数字形式。
可通过CountVectorizer的fit_transform方法构建词袋模型的词汇表,词汇表存储在Python字典,该字典将每个独立的单词映射为整数索引。
特征向量的每个索引位置对应于词汇存储在CountVectorizer字典项中的整数值。特征向量也被称为原词频率:tf(t,d) ,即 t 项在 d 文档中出现的次数。
在分析文本数据时,经常会发现好的和坏的词在多个文档中出现。经常出现的单词基本上不包含有用或者判断性的信息。 词频-逆文本频率(tf-idf) 一算法,用于减少特征向量中频繁出现的词。tf-idf 可以定义为词频与逆反文档频率的乘积:
tf-idf ( t , d ) = tf (t,d) × idf ( t , d ) \text{tf-idf}(t,d)=\text{tf (t,d)}\times \text{idf}(t,d) tf-idf(t,d)=tf (t,d)×idf(t,d)
idf ( t , d ) = log n d 1 + df ( d , t ) , \text{idf}(t,d) = \text{log}\frac{n_d}{1+\text{df}(d, t)}, idf(t,d)=log1+df(d,t)nd,
这里 n d n_d nd 是文档总数 ; df(d, t) 是包含单词 t 的文档数目.
或者通俗理解为:
词频 ( T F ) = 某个词在文章中的出现次数 该文出现次数最多的词的出现次数 \text{词频}(TF) = \frac{某个词在文章中的出现次数}{该文出现次数最多的词的出现次数} 词频(TF)=该文出现次数最多的词的出现次数某个词在文章中的出现次数
逆文档频率 ( I D F ) = log ( 语料库的文档总数 包含该词的文档数 + 1 ) \text{逆文档频率}(IDF) = \text{log}(\frac{语料库的文档总数}{包含该词的文档数 + 1}) 逆文档频率(IDF)=log(包含该词的文档数+1语料库的文档总数)
为分母添加常数1为可选,目的在于为所有训练样本中出现的单词赋予非零值,用对数来确保低文档频率的权重不会过大。
可以看到,TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语言中的出现次数成反比。
此外scikit-learn实现了另外一个转换器TfidfTransformer类,它以来自于CountVectorizer类,以原始词频为输入,然后转换为tf-idfs格式:
scikit-learn实现的逆文档频率计算公式如下:
idf ( t , d ) = l o g 1 + n d 1 + df ( d , t ) \text{idf} (t,d) = log\frac{1 + n_d}{1 + \text{df}(d, t)} idf(t,d)=log1+df(d,t)1+nd
在scikit-learn中计算tf-idf公式为:
tf-idf ( t , d ) = tf ( t , d ) × ( idf ( t , d ) + 1 ) \text{tf-idf}(t,d) = \text{tf}(t,d) \times (\text{idf}(t,d)+1) tf-idf(t,d)=tf(t,d)×(idf(t,d)+1)
在调用 TfidfTransformer 类直接归一化并计算tf-idf之前,归一化原始单词的词频更具代表性。定义默认参数 norm=‘l2’,用scikit-learn的
TfidfTransformer 进行L2归一化,返回长度为1的向量,
v norm = v ∣ ∣ v ∣ ∣ 2 = v v 1 2 + v 2 2 + ⋯ + v n 2 = v ( ∑ i = 1 n v i 2 ) 1 2 v_{\text{norm}} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v_{1}^{2} + v_{2}^{2} + \dots + v_{n}^{2}}} = \frac{v}{\big (\sum_{i=1}^{n} v_{i}^{2}\big)^\frac{1}{2}} vnorm=∣∣v∣∣2v=v12+v22+⋯+vn2 v=(∑i=1nvi2)21v
#将之前所有的新闻标题进行文本向量化
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
因为原数据量过于庞大,使得电脑无法运转
经过反复试验之后,发现max_features参数为120的时候效果最好
st = time()
model = CountVectorizer(max_features=120)
X = model.fit_transform(word_all)
X = X.toarray()
print("耗时为:",time()-st)
耗时为: 1.9630837440490723
st = time()
#Tfidf = TfidfVectorizer(min_df=20,max_df=25)
Tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b", max_features=120, ngram_range=(1,2), stop_words=stop_words)
X2 = Tfidf.fit_transform(word_all)
X2 = X2.toarray()
print("耗时为:",time()-st)
耗时为: 7.643373489379883
原数据的特征如果不经筛选,会有20万之多,现在仅仅取其中出现频率最高的前120个
print(X2.shape) #此时的数据规模为(119988,120)
words_bag = Tfidf.get_feature_names() # 第二种查看词袋的方法
data_all = pd.DataFrame(X2, columns=words_bag)
data_all.head()
观察上述运行结果可知,已经将用户名,数字,符号,英文都删除了。
st = time()
from sklearn.model_selection import train_test_split
Xtrain,Xtest,ytrain,ytest = train_test_split(data_all,pd_all['label'],test_size=0.3)
print("耗时为:",time()-st)
检查数据是否按照类别分类
print("训练数据中类别为1的比例为:",sum(ytrain)/ytrain.shape[0])
print("测试数据中类别为1的比例为:",sum(ytest)/ytest.shape[0])
训练数据中类别为1的比例为: 0.49955352359181343
测试数据中类别为1的比例为: 0.5010139733866711
至此,数据预处理部分全部结束,可以导入模型开始训练
st = time()
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression #导入Logistic模型
LR = LogisticRegression(penalty='l2',C=1000) #偏置设置为True,即有偏置
LR.fit(Xtrain,ytrain) #模型训练
pred_LR = LR.predict(Xtest) #模型预测
print("logistic分类所花费的时间为:",time()-st,"秒")
print("accuracy_score = ",accuracy_score(ytest,pred_LR)) #分类准确率
print(metrics.classification_report(ytest,pred_LR)) #分类报告
运行结果如下图
#ROC曲线的面积
y_score_LR= MS.cross_val_predict(LR,Xtest,ytest,cv=3)
metrics.roc_auc_score(ytest,y_score_LR)
#绘制出ROC曲线
metrics.plot_roc_curve(LR,Xtest,ytest)
ROC曲线的面积为0.907
#网格搜索最佳参数 penaltys = ['l1','l2'] #罚函数的类型 Cs = [0.001, 0.01, 0.1, 1, 10, 100, 1000] #惩罚系数 tuned_parameters = dict(penalty = penaltys, C = Cs) #做成字典 lr_penalty= LogisticRegression() #选择模型 #网格搜索模型初始化 grid_log= GridSearchCV(lr_penalty, #模型 tuned_parameters, #网格搜索的参数 cv=10, #10折交叉验证 scoring='accuracy') #用准确率作为得分 grid_log.fit(Xtrain,ytrain) #网格搜索的结果 print("网格搜索的结果为:") print(grid_log.cv_results_) #网格搜索的最好得分 print("GridSearchCV的最好得分为:",grid_log.best_score_) print("GridSearchCV的最好参数为:",grid_log.best_params_)
以分类准确率为得分的网格搜索,在logistic回归的准确率上,最高得分为0.91,相比默认参数提高了1个百分点
st = time()
from sklearn.naive_bayes import GaussianNB
clf = GaussianNB(var_smoothing = 1e-06)
clf.fit(Xtrain,ytrain) #模型训练
pred_clf = clf.predict(Xtest)
print(time()-st)
print("accuracy_score = ",accuracy_score(ytest,pred_clf)) #分类准确率
print(metrics.classification_report(ytest,pred_clf)) #分类报告
绘制ROC曲线及计算ROC曲线面积
#绘制出ROC曲线
metrics.plot_roc_curve(clf,Xtest,ytest)
#ROC曲线的面积
y_score_clf = MS.cross_val_predict(clf,Xtest,ytest,cv=3)
metrics.roc_auc_score(ytest,y_score_clf)
ROC曲线的面积为0.865
# 调参 GNB
param_grid ={}
param_grid['var_smoothing'] = [1e-5,1e-6,1e-7,1e-8,1e-9]
model = GaussianNB()
grid_GNB = GridSearchCV(estimator= model, param_grid = param_grid, scoring='accuracy', cv=10)
grid_GNB.fit(Xtrain,ytrain)
#网格搜索的结果
print("网格搜索的结果为:")
print(grid_GNB.cv_results_)
#网格搜索的最好得分
print("GridSearchCV的最好得分为:",grid_GNB.best_score_)
print("GridSearchCV的最好参数为:",grid_GNB.best_params_)
st = time()
from sklearn.ensemble import RandomForestClassifier #导入随机森林模型
RFC = RandomForestClassifier(n_estimators=100,max_depth=9,min_samples_leaf=10,min_samples_split=140,max_features=9) #模型初始化:树的个数为100
RFC.fit(Xtrain,ytrain) #模型训练
ypred_RFC = RFC.predict(Xtest) #模型预测
print(time()-st)
print("accuracy_score = ",accuracy_score(ytest,ypred_RFC))
print(metrics.classification_report(ytest,ypred_RFC))
#绘制出ROC曲线
metrics.plot_roc_curve(RFC,Xtest,ytest)
#ROC曲线的面积
y_score_RFC = MS.cross_val_predict(RFC,Xtest,ytest,cv=3)
metrics.roc_auc_score(ytest,y_score_RFC)
ROC曲线面积为0.905
#首先对n_estimators进行网格搜索:
param_test1 = {'n_estimators':[50,100,150,200]}
gsearch1 = GridSearchCV(estimator = RandomForestClassifier(min_samples_split=100,
min_samples_leaf=20,
max_depth=8, #最大深度
max_features='sqrt',random_state=10,n_jobs=-1),
param_grid = param_test1,
scoring='accuracy',
cv=5)
gsearch1.fit(Xtrain,ytrain)
#最佳参数,最佳得分
print(gsearch1.best_params_, gsearch1.best_score_)
{‘n_estimators’: 150} 0.9068233201590468
#接着对决策树最大深度max_depth进行网格搜索。
param_test2 = {'max_depth':[3,5,7,9]}
gsearch2 = GridSearchCV(estimator = RandomForestClassifier(n_estimators=200, min_samples_split=100,
min_samples_leaf=20,max_features='sqrt' ,oob_score=True, random_state=10,n_jobs=-1),
param_grid = param_test2,
scoring='accuracy',
cv=5)
gsearch2.fit(Xtrain,ytrain)
print( gsearch2.best_params_, gsearch2.best_score_)
{‘max_depth’: 9} 0.9072638374535147
#下面再对内部节点再划分所需最小样本数min_samples_split和叶子节点最少样本数min_samples_leaf一起调参。
param_test3 = {'min_samples_split':[80,100,120,140],'min_samples_leaf':[10,20,30,40,50]}
gsearch3 = GridSearchCV(estimator = RandomForestClassifier(n_estimators= 200, max_depth=11,
max_features='sqrt' ,oob_score=True, random_state=10,n_jobs=-1),
param_grid = param_test3,
scoring='accuracy',
cv=5)
gsearch3.fit(Xtrain,ytrain)
print( gsearch3.best_params_, gsearch3.best_score_)
{‘min_samples_leaf’: 10, ‘min_samples_split’: 120} 0.9088354403646666
#最后再对最大特征数max_features做调参:
param_test4 = {'max_features':[3,5,7,9]}
gsearch4 = GridSearchCV(estimator = RandomForestClassifier(n_estimators= 200, max_depth=11, min_samples_split=80,
min_samples_leaf=10 ,oob_score=True, random_state=10,n_jobs=-1),
param_grid = param_test4,
scoring='accuracy',
cv=5)
gsearch4.fit(Xtrain,ytrain)
print( gsearch4.best_params_, gsearch4.best_score_)
{‘max_features’: 9} 0.9083472990617139
综上可得出结论:
经过数据预处理之后
随机森林 语义分类最优,其次是Logistic回归分类准确率,朴素贝叶斯分类准确率最低
以上仅仅是提取出现频率最高的前120个中文词汇进行语义分类的结果,主要还是由于个人电脑的内存原因,无法提取更多特征
理论上,如果能够提取更多的特征,分类准确率会有明显的提高
但是本文的研究方向主要是区分正向评论和负向评论,从词义的角度来看,一句话中仅仅有部分的词汇能够表明该评论是正向的还是负向的,如果数据量足够大,如本文中,12万的评论数据,那么一些能够体现评论种类的词语出现频率会非常高,因此这解释了为什么只取前120个词汇的分类准确率就可以达到90%的原因了
vectors = [10,20,30,40,50,60,70,80,90,100,110,120] from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from sklearn.linear_model import LogisticRegression #导入Logistic模型 def calcu(feature): model = CountVectorizer(max_features=feature) X = model.fit_transform(word_all) X = X.toarray() words_bag = model.get_feature_names() data_all = pd.DataFrame(X, columns=words_bag) data_all.head() Xtrain,Xtest,ytrain,ytest = train_test_split(data_all,pd_all['label'],test_size=0.3) LR = LogisticRegression(penalty='l2',C=1000) #偏置设置为True,即有偏置 LR.fit(Xtrain,ytrain) #模型训练 pred_LR = LR.predict(Xtest) #模型预测 print("特征数为:",feature,"时logistic分类所花费的时间为:",time()-st,"秒") return accuracy_score(ytest,pred_LR) #分类准确率 from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfVectorizer accuracy = [] st = time() for feature in vectors: accuracy.append(calcu(feature)) plt.plot(vectors,accuracy) plt.xlabel("the number of high frequency words") plt.ylabel("classification accuracy")
因此本文所选取的最高频率词汇数量具有一定的可取性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。