您的位置:首页 > 编程语言 > Python开发

成功当一回调包侠---基于LSTM的短评论分析

2017-09-04 14:29 921 查看
    出于项目需要,要做一个酒店评论的情感分析,并对酒店的某些主题进行评价,这里主要是说前半部分,也就是情感分析的部分。

    一开始是没日没夜的爬虫,这也是我之前几篇博文里面写到的有关爬虫的一些知识的原因。本着快捷方便的理念,本想直接调用现有的情感分析接口进行分析,如SNOWNLP。但做了一些测试,感觉它做出来的效果不如人意。当然这不是说它模型做的不好,但估计是因为它模型的训练预料并不是专门的酒店评论,以及大家对正负样本的理解也有偏差,所以做出来想过不如想象中的好,所以和经理合计不如自己做模型吧。要做就做高端的,所以就直接选了LSTM网络来做这个情感分析。在这里感谢这个链接的文章和github上的代码,没错,我们这次只是做了回调包侠,而这个项目的目的是做出情感分析这么个功能,而不是建模竞赛中百分之零点几的正确率的执着,所以这篇文章写的是代码和包的使用和理解,而不是调参。(https://buptldy.github.io/2016/07/20/2016-07-20-sentiment%20analysis/)

    LSTM网络适合做语义分析,是因为它能够关联不同时刻的输出结果作为当前时刻的输入之一(我是这么理解的),借用一下李宏毅老师的PPT内容表达一下这点, 但具体的理论细节我也还在学习。



这里的实现思路很经典和直接,先把所有评论分词,去掉停用词;然后建立word2vec模型,将每条评论表达成词向量,然后输入到LSTM网络中进行训练,下面具体来看看代码。(并非全部代码,只是主要功能)

def word2vec_train(combined_dic):
CPU_COUNT = multiprocessing.cpu_count()#指定为使用CPU处理
model =Word2Vec(sg=1, size=vocab_dim, window=6, min_count=10, negative=3, sample=0.001, hs=1, workers=CPU_COUNT)#定义词向量模型
model.build_vocab(combined_dic)#建立词典,必须步骤,不然会报错
model.train(combined_dic,total_examples=model.corpus_count,epochs=model.iter)#训练词向量模型
model.save('Word2vec_model.word2vec')#保存词向量模型
index_dict, word_vectors,combined = create_dictionaries(model=model,combined=combined_dic)#使用另一个函数,将评论表达为index->Wordvector模型
return   index_dict,word_vectors,combined

  上面的代码要注意的首先是输入,word2vec模型的输入模型是已经分好词,用\t分割开的评论,一条评论一行;如果是用列表表示,则是嵌套列表,列表中每一个列表元素代表一条评论。类似这样[[''我,'喜欢','酒店'],['酒店','很','干净']],这里表示的就是两条评论。 然后是Word2Vec定义的参数,sg表示是用skip gram 还是 CBOW,1表示用SG,从原理上来讲效果要比CBOW要好一点,size就是词向量的长度。min_count是最小词频。更多参数可参考这里http://blog.csdn.net/baidu_26550817/article/details/48653889。

词向量可直接通过model.save 和 mode.load进行保存和导入。然后跳到另一个函数。

 

def create_dictionaries(model=None, combined=None):
if (combined is not None) and (model is not None):
gensim_dict = Dictionary()
gensim_dict.doc2bow(model.wv.vocab.keys(),allow_update=True)
w2indx = {v: k+1 for
4000
k, v in gensim_dict.items()}#所有频数超过10的词语的索引
w2vec = {word: model[word] for word in w2indx.keys()}#所有频数超过10的词语的词向量
def parse_dataset(combined):
data=[]
for sentence in combined:
new_txt = []
for word in sentence:
try:
new_txt.append(w2indx[word])
except:
new_txt.append(0)
data.append(new_txt)
return data
combined=parse_dataset(combined)
combined= sequence.pad_sequences(combined, maxlen=maxlen)#保证每个评论的长度都和maxlen相等
return w2indx,w2vec,combined
else:
print('No data provided...')

  这里一开始怎么都想不明白,看了很多资料和想了很久才看懂了,估计是因为我笨吧。。gensim的doc2bow 这个方法,要实现的将一个列表中的文字转化为 index->word这样的形式,而items返回的就是{0:u‘酒店’}这样的字典,这里w2indx是要将这个字典调转成word->index的模式,并且要让index的下标从1开始,后面会用到。(这里用到的几个方法可参考这篇文章, 写的很清楚 http://blog.csdn.net/Yan456jie/article/details/52120921?locationNum=5&fps=1)。w2vec要实现的是word->vector这样的形式,即如{‘酒店’:[0.5,0.4,0.6.........0.1]}。parse_dataset要实现的将每条评论表现成词的索引形式,即如[''我,'喜欢','酒店']的评论,就表现成[1,0,5],对应的是词的索引,但需要注意的是,索引为0的词表示词频小于min_count,其对应的词向量是一个零行向量。sequence.pad_sequences是按照maxlen把列表补全,超过截断,不过长就补零。因为每条评论的长度不一,而网络要求输入的维度肯定是一样的,所以需要这个操作。
def get_data(index_dict,word_vectors,combined,y):
n_symbols = len(index_dict) + 1  # 所有单词的索引数,频数小于10的词语索引为0,所以加1
embedding_weights = np.zeros((n_symbols, vocab_dim))#索引为0的词语,词向量全为0
for word, index in index_dict.items():#从索引为1的词语开始,对每个词语对应其词向量
embedding_weights[index, :] = word_vectors[word]
x_train, x_test, y_train, y_test = train_test_split(combined, y, test_size=0.2)
print(x_train.shape,y_train.shape)
return n_symbols,embedding_weights,x_train,y_train,x_test,y_test

  get_data函数主要要实现的是返回一个矩阵(embedding_weights),这个矩阵的每一行代表一个词的向量,因此共有n_symbols个词,每个词向量的长度是vocab_dim,和上面word2vec训练的词向量长度一样。train_test_split是sklearn中的方法,按照test_size的比例,选择训练集和测试集。准备好这些,下面就开始定义网络结构和训练模型了。

def train_lstm(n_symbols,embedding_weights,x_train,y_train,x_test,y_test):
print('Defining a Simple Keras Model...')
model = Sequential()  # or Graph or whatever
print("build model sucessed")
model.add(Embedding(output_dim=vocab_dim,  #这是网络的一个融合层,output_dim指的是输出到下一层的数据
input_dim=n_symbols,
mask_zero=True,
weights=[embedding_weights],
input_length=input_length))  # Adding Input Length
print("add embedding sucessed")
model.add(LSTM(units=50, activation="sigmoid", recurrent_activation="hard_sigmoid")) #可以试试ReLU做激活函数
print("add lstm sucessed")
model.add(Dropout(0.5)) #dropout是防止过拟合,每次只有部分的神经元起作用
print("add Dropout sucessed")
model.add(Dense(1))
print("add Dense sucessed")
model.add(Activation('sigmoid'))
print('Compiling the Model...')
model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])#可以选用不同的loss函数
print ("Train...")
model.fit(x_train, y_train, batch_size=batch_size, nb_epoch=n_epoch,verbose=1, validation_data=(x_test, y_test))
print ("Evaluate...")
score = model.evaluate(x_test, y_test,batch_size=batch_size)
yaml_string = model.to_yaml()
with open('lstm_data/lstm.yml', 'w') as outfile:
outfile.write(yaml.dump(yaml_string, default_flow_style=True) )
model.save_weights('lstm_data/lstm.h5')
print ('Test score:', score)

 经过上面的步骤,整个网络就训练好了,这里倒是没什么好说的,主要我也还在理解中!!!可以通过改一下参数,多加几个隐藏层去改善效果吧我想,不过这个项目的目的到这里就达到了目的了。

def lstm_loadmodel():
print('loading model......')
with open('lstm_data/lstm.yml', 'r') as f:
yaml_string = yaml.load(f)
model = model_from_yaml(yaml_string)

print('loading weights......')
model.load_weights('lstm_data/lstm.h5')
model.compile(loss='binary_crossentropy',
optimizer='adam',metrics=['accuracy'])
return model

def lstm_predict(model, string):
print(i)
data = input_transform(string)
data.reshape(1, -1)
score = model.predict(data)[0][0]
return score

  上面两个函数的功能就如名称一样好理解,分别是加载模型和对具体的语句进行预测,但待预测的评论也需要分词,去掉停用词,用词向量表示评论等过程,所以需要一下函数。
def input_transform(string):
words=jieba.lcut(string)
words = list(set(words) - set(stopwords))
words=np.array(words).reshape(1,-1)
model=Word2Vec.load('lstm_data/Word2vec_model.word2vec')
_,_,combined=create_dictionaries(model,words)
return combined

  铛铛!!至此已完成了全部工作,下面就是将一条条评论扔进去跑分了!!

<
酒店环境还可以,房间比较小!就是位置稍微偏一点!0.807286739
房间里环境还好,灯光不是很亮,有点压抑感。前面是马路,后面是个比较老的小区,交通倒是很方便,没有一次性拖鞋,窗户没有防盗网,并且窗户只能开一条缝,不能全开。价格五一期间要220元,不便宜!平时价格倒是有优势,相比之下还是****好一点!0.991319358
很好,在市中心,交通方便,很安静。0.98629874
服务态度超差,9号11点左右入住酒店,前台办理登记入住的戴眼镜男员工态度不行,入住房间发现电源插头不够用打电话去前台,戴眼镜男显得跟不耐烦,更气人的是故意找借口不借,然后等到工作人员交接班换人打入是一个女服务员接听,态度服务好多了,直接就安排清洁大姐送来房间.0.02476237
安排的去另一幢楼,床单无数毛发,厕所纸只有15张,厕所里面门把手已经断了一半,关上门如果不小心弄断了,那你就呵呵了。热水水小,没毛巾,没镜子,电视只有5个台,有一个是重复的,周边一堆58的公寓。我就搞不懂他们凭什么卖88甚至1080.020576861
赞!环境不错0.988427818
酒店位于金砂公园附近,吃住满方便的,就是房间没暖气,其他的都不错,服务也很不错0.986264646
  最后效果就如上面所示,不过其实我们也还在修改这个模型,力图达到更好的效果。 

  谢谢看完!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐