一、数据获取
1、爬虫评论一次之后就被屏蔽(好像是网站被一个IP频繁访问会让你输验证码),解决办法:先试了用代理IP,大众点评好像不能用代理IP访问,然后加入了time.sleep(random.uniform(1,10)),让它访问不要太频繁。
2、爬完数据写入csv文件乱码问题:out = codecs.open(’./data/Stu_csv.csv’, ‘a’, encoding=“gbk”)
3、爬下来的评论详情,显示乱七八糟:蛮久之座,提周预周末位,都。次国庆,估计队出游了,然易到了。发现,大众点评上的评论是字和图片混着来的,而且也不能复制。数据不好,后期做出来效果肯定也不好,于是去github上找了个数据集。
4、csv文件用excel打开乱码,原因:csv用UTF-8编码,而excel默认编码是ANSI,解决办法:将csv文件用TXT打开,另存为ANSI格式,再用excel打开就好了。
二、数据预处理
1、数据查看
import pandas as pd
def loaddata():
#显示所有列
pd.set_option('display.max_columns', None)
pd.set_option('display.width',200)
data=pd.read_csv('data/data.csv')
print(data.head())
结果:
查看数据集的规模:data.shape:(32483, 14)
也可以查看数据集的信息:data.info()
可以看到,没有缺失值(其实贝叶斯对缺失值没有很敏感)。
因为是对评论进行情感分析,因此我们用到的数据也就是cus_comment(特征)和stars(类别)两列数据,这里的stars是评论的类别,1、2是差评,3是中评,4、5是好评,我们把1-2记为0,4-5记为1,3为中评,对我们的情感分析作用不大,丢弃掉这部分数据,但是可以用来作为语料。我们的情感评分可以转化为标签值为1的概率值,这样我们就把情感分析问题转为文本分类问题了。
def getLabel(score):
if score > 3:
return 1
elif score < 3:
return 0
else:
return None
def createLabel(data):
data['target'] = data['stars'].map(lambda x:getLabel(x))
dataSelect=data[['cus_comment','target']]#选择评论列和类别列。
return dataSelect.dropna()#删除掉中评的数据
现在我们还剩21691条数据。
将数据集切分为训练集和测试集:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(data_model['cus_comment'], data_model['target'], random_state=3, test_size=0.25)#random_state是随机数的种子,不同的种子会造成不同的随机采样结果,相同的种子采样结果相同。
data['target'].value_counts()
输出:
1.0 14920
0.0 1348
从输出结果可以看出,训练集的样本分布不平衡。正样本是14920条,而负样本只有1348条。
在样本不均衡的情况下,我们应该选择合适的评估标准,比如ROC或者F1,而不是准确度(accuracy)。处理样本不均衡问题的方法,首先可以选择调整阈值,使得模型对于较少的类别更为敏感,或者另外一种方法就是通过采样(sampling)来调整数据的不平衡。其中欠采样抛弃了大部分正例数据,从而弱化了其影响,可能会造成偏差很大的模型,同时,数据总是宝贵的,抛弃数据是很奢侈的。另外一种是过采样。
这里我们用过采样的方式来解决样本不平衡的问题。但如果是单纯复制反例,因此会过分强调已有的反例。如果其中部分点标记错误或者是噪音,那么错误也容易被成倍的放大。容易造成对反例的过拟合,因此,我们采用SMOTE算法来人工合成数据实现过采样。
后续我们会加入过采样。
三、特征工程
接下来对训练集进行文本特征处理,首先用jieba分词进行中文分词:
def fenci(data):
data=data.apply(lambda x:' '.join(jieba.cut(x)))
return data
载入停用词:
def load_stopwords():
stop_file=open("data/stopwords.txt", encoding='utf-8')
stopwords_list=stop_file.readlines()
stopwords=[x.strip() for x in stopwords_list]
return stopwords
文本转向量,有词库表示法、TF-IDF、word2vec等, 这里我们使用sklearn库的TF-IDF工具进行文本特征提取:
def feature_extraction(data,stopwords):
#TF-IDF构建文本向量 sklearn库中可以指定stopwords
tf = TfidfVectorizer(stop_words=stopwords, max_features=3000)
tf.fit(data)
return tf #返回一个适配器
四、模型训练
特征和标签准备好之后,接下来就是建模了。这里我们使用文本分类的经典算法朴素贝叶斯算法,而且朴素贝叶斯算法的计算量较少。特征值是评论文本经过TF-IDF处理的向量,标签值评论的分类共两类,好评是1,差评是0。情感评分为分类器预测分类1的概率值,概率值越大,情感评分越高。
#模型训练--贝叶斯分类器
def train_model(x_train,y_train,x_test,y_test):
classifier=MultinomialNB()
classifier.fit(x_train,y_train)
print(classifier.score(x_test,y_test))
return classifier #返回模型
对测试集同样要进行分词、去停用词、转TF-IDF,然后测试模型的准确率、ROC值:
if __name__ == '__main__':
data=loaddata()
data=preprocess(data)
x_train,x_test,y_train,y_test=train_test_split(data['cus_comment'],data['target'],random_state=3,test_size=0.25)
stopwords=load_stopwords()
x_train_fenci=fenci(x_train)
tf=feature_extraction(x_train_fenci, stopwords)
model=train_model(tf.transform(x_train_fenci), y_train,tf.transform(fenci(x_test)),y_test)
输出:
accuracy 0.9302968836437396
roc-score 0.5657246489975608
可以看出来,虽然准确率可以,但是ROC却很低。
从大众点评网找两条评论来测试一下
def predict(model,comment,tf):
return model.predict_proba(tf.transform(fenci((comment))))# 返回预测属于某标签的概率
comment1="一如既往的好。已经快成了陆家嘴上班的我的食堂了。满减活动非常给力,上次叫了八样东西,折扣下来居然就六十左右,吃得好爽好爽。南瓜吃过几次,就一次不够酥烂,其他几次都很好。烤麸非常入味,适合上海人。鱼香肉丝有点辣,下饭刚好。那个蔬菜每次都点。总体很好吃。"
comment2="糯米外皮不绵滑,豆沙馅粗躁,没有香甜味。12元一碗不值。"
print(predict(model,pd.Series([comment1]) , tf))
print(predict(model,pd.Series([comment2]), tf))
输出:
[[0.01838771 0.98161229]]
[[0.17337369 0.82662631]]
predict_proba返回的是一个 n 行 k 列的数组, 第 i 行 第 j 列上的数值是模型预测 第 i 个预测样本为某个标签的概率,并且每一行的概率和为1。
可看到,5星好评模型预测出来了,1星差评却预测错误。这种情况就是样本不平衡,我们查看一下混淆矩阵
y_predict=model.predict(tf.transform(fenci((x_test))))
print(confusion_matrix(y_test,y_predict))
输出:
[[ 57 374]
[ 4 4988]]
第一行是负样本,第二行是正样本,负类样本中有57个被预测为正类(15%),这是由于数据的不平衡导致的,模型的默认阈值为输出值的中位数。
SMOTE算法实现过采样:
原理:对少量类别的每一个样本o计算其K近邻,根据采样倍率N从其K近邻中随机选取N个样本x,根据公式:
计算新样本。
代码:
import random
from sklearn.neighbors import NearestNeighbors
import numpy as np
class Smote:
def __init__(self,samples,N=10,k=5):
self.n_samples,self.n_attrs=samples.shape
self.N=N
self.k=k
self.samples=samples
self.newindex=0
# self.synthetic=np.zeros((self.n_samples*N,self.n_attrs))
def over_sampling(self):
N=int(self.N/100)
self.synthetic = np.zeros((self.n_samples * N, self.n_attrs))
neighbors=NearestNeighbors(n_neighbors=self.k).fit(self.samples)
print("neighbors",neighbors)
for i in range(len(self.samples)):
nnarray=neighbors.kneighbors(self.samples[i].reshape(1,-1),return_distance=False)[0]
self._populate(N,i,nnarray)
return self.synthetic
# for each minority class samples,choose N of the k nearest neighbors and generate N synthetic samples.
def _populate(self,N,i,nnarray):
for j in range(N):
nn=random.randint(0,self.k-1)
dif=self.samples[nnarray[nn]]-self.samples[i]
gap=random.random()
self.synthetic[self.newindex]=self.samples[i]+gap*dif
self.newindex+=1
训练集中的样本分布:
1.0 14920
0.0 1348
负样本只有1348条,所以利用SMOTE算法根据这1348条数据人工合成为原来的10倍,加入到训练集中进行模型的训练。
#下面的代码是为了解决引用同目录下py文件中的类时报错的问题。
current_dir = os.path.abspath(os.path.dirname(__file__))
sys.path.append(current_dir)
sys.path.append("..")
import smote
#将负样本中的词向量传入SMOTE算法进行过采样
x_train_tf=tf.transform(x_train_fenci).toarray()
samples0=[]
for i, label in enumerate(y_train):
if label==0:
samples0.append(x_train_tf[i])
s=smote.Smote(np.array(samples0),N=100)
over_samplings_x=s.over_sampling()
#下面是矩阵的合并,组成新的训练集
total_samplings_x=np.row_stack((x_train_tf,over_samplings_x))
total_samplings_y=np.concatenate((y_train,np.zeros(len(over_samplings_x))),axis=0)
再次训练模型:
采样倍率N=100(多一倍)
accuracy 0.9382260741287111
roc-score 0.6410470209411625
[[0.03223564 0.96776436]]
[[0.30446218 0.69553782]]
采样倍率N=500(多5倍)
accuracy 0.9280840862990964
roc-score 0.7828688314295913
[[0.0898476 0.9101524]]
[[0.59244416 0.40755584]]
采样倍率N=600(多6倍)
accuracy 0.9203392955928453
roc-score 0.8041004818847046
[[0.08742767 0.91257233]]
[[0.60816768 0.39183232]]
因为采样倍率再大的话np好像有MemoryError的错误,所以先到6倍,但是结果已经优化了好多,ROC值提升了很多,差评也能预测出来。
这个项目基本已经结束,通过SMOTE算法也实现了不错的效果。后续优化方向:
使用更复杂的机器学习模型如神经网络、支持向量机等
模型的调参
行业词库的构建
增加数据量
优化情感分析的算法
增加标签提取等
过程总结:
1、简单的爬虫操作,爬取点评网站上的评论数据。
2、怎么对数据进行分析、预处理。用pandas的info或describe方法或其他方法对数据做统计分析,发现数据的缺失值,样本的平衡性,并使用过采样(SMOTE算法)解决这个问题。
3、特征处理。这里就是基本的文本特征处理:分词–>去停用词–>转向量
4、标签类别处理。将几个评分类别转化为二分类问题。
5、模型训练。使用sklearn的贝叶斯进行训练并使用准确率和ROC来评估模型分类效果。
6、使用混淆矩阵查看预测情况,对模型做出评价。
知识点总结:
- TF-IDF的参数,调用,原理
- 朴素贝叶斯的原理
- 朴素贝叶斯的调用。
- smote算法的基本思想
- 模型评价指标:PR、ROC、混淆矩阵