Keras情感分析(Sentiment Analysis)实战---自然语言处理技术(2)

情感分析(Sentiment Analysis)是自然语言处理里面比较高阶的任务之一。仔细思考一下,这个任务的究极目标其实是想让计算机理解人类的情感世界。我们自己都不一定能完全控制和了解自己的情感,更别说机器了。

不过在人工智能的认知智能阶段(人工智能三阶段——计算智能,感知智能,认知智能),商家还是可以用它来做一些商品或服务的评论分析,继而有效地去优化商品或服务,为消费者们提供更好用户体验。

情感分析任务简介

情感分析任务其实是个分类任务,给模型输入一句话,让它判断这句话的情感是积极的,消极的,还是中性的。例子如下:
输入:的确是专业,用心做,出品方面都给好评。
输出:2
输出可以是[0,1,2]其中一个,0表示情感消极,1表示情感中性,2表示情感积极。

情感分析这个任务还有一个升级版——细腻度的情感分析。升级版希望模型不仅能识别出情感的好坏,而且还希望模型能识别出是由于什么原因导致这种情感发生。举个例子,"这家餐厅的地理位置不错,可惜菜不怎么好吃",我们就需要识别出,在地理位置这个aspect上情感是积极的,而在菜的味道这个aspect上情感是消极的。听起来是不是很难,所以实战部分我只简单介绍一下麻瓜版的情感分析任务——简单的分类。

情感分析算法简介

分类任务的算法,想必大家都很熟悉:SVM,Logistic,Tree等。可是对于文本分类来说,最重要的是如何将一句话的映射到向量空间,同时保持其语义特征。所以文本的向量化表示是最最重要的一个环节。而文本的向量化就是涉及到Word Embedding技术和深度学习(Deep Learning)技术。

Word Embedding指的是把文本转换成计算机能处理的向量,而其中难点的是:将文本向量化时如何保持句子原有的语义。早期word embedding使用的是Bag of Words,TF-IDF等,这些算法有个共同的特点:就是没有考虑语序以及上下文关系。而近几年发展出来的Word2Vector ,Glove等考虑到了文本的上下文关系。今年NLP领域大放异彩的BERT就是在文本向量化上做出了重大的突破。

人工特征的挖掘是个极为费脑费时的过程,深度学习模型可以将特征工程自动化,通过神经网络自动做特征的表示学习。在NLP领域中,RNN(LSTM,GRU),CNNTransformer等各路深度学习模型各显神通,凭借他们强大的特征表示能力,在很多任务中都吊打人工特征(吹得 有些夸张了,没收住)。不过人工特征有时还是很重要的。

项目实战

本次的项目实战的总体架构可分为两个步骤:
(1)采用Word2Vector技术去训练词向量;
(2)采用BiLSTM去做特征的表示学习。
其项目架构如下图所示:


情感分析架构图

数据读取

数据格式如下:一句评论后面标记一个label,0表示消极情感,1表示中性情感,2表示积极情感。


情感分析数据

这里针对笔者自己的数据集定义了一个数据读入函数。

import numpy as np
from gensim.models.word2vec import Word2Vec
from gensim.corpora.dictionary import Dictionary
from gensim import models
import pandas as pd
import jieba
import logging
from keras import Sequential
from keras.preprocessing.sequence import pad_sequences
from keras.layers import Bidirectional,LSTM,Dense,Embedding,Dropout,Activation,Softmax
from sklearn.model_selection import train_test_split
from keras.utils import np_utils

def read_data(data_path):
    senlist = []
    labellist = []  
    with open(data_path, "r",encoding='gb2312',errors='ignore') as f:
         for data in  f.readlines():
                data = data.strip()
                sen = data.split("\t")[2] 
                label = data.split("\t")[3]
                if sen != "" and (label =="0" or label=="1" or label=="2" ) :
                    senlist.append(sen)
                    labellist.append(label) 
                else:
                    pass                    
    assert(len(senlist) == len(labellist))            
    return senlist ,labellist 

sentences,labels = read_data("data_train.csv")

数据读入之后,得到一个所有评论的sentences列表 ,和一个与之一一对应的labels列表。
sentences[1] :烤鸭还是不错的,别的菜没什么特殊的
labels[1] :1

训练词向量

将所有的评论文本数据用来训练词向量,这里使用的gensim中的Word2Vec,原理是的Skip-gram。这里对词向量的原理不多介绍,总之,这一步将一个词映射成一个100维的向量,并且考虑到了上下文的语义。这里直接将上一部得到的句子列表传给train_word2vec函数就可以了,同时需要定义一个词向量文件保存路径。模型保存后,以后使用就不需要再次训练,直接加载保存好的模型就可以啦。

def train_word2vec(sentences,save_path):
    sentences_seg = []
    sen_str = "\n".join(sentences)
    res = jieba.lcut(sen_str)
    seg_str = " ".join(res)
    sen_list = seg_str.split("\n")
    for i in sen_list:
        sentences_seg.append(i.split())
    print("开始训练词向量") 
#     logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
    model = Word2Vec(sentences_seg,
                size=100,  # 词向量维度
                min_count=5,  # 词频阈值
                window=5)  # 窗口大小    
    model.save(save_path)
    return model

model =  train_word2vec(sentences,'word2vec.model')    

数据预处理

这里定义了一些数据处理和变换方法。

def generate_id2wec(word2vec_model):
    gensim_dict = Dictionary()
    gensim_dict.doc2bow(model.wv.vocab.keys(), allow_update=True)
    w2id = {v: k + 1 for k, v in gensim_dict.items()}  # 词语的索引,从1开始编号
    w2vec = {word: model[word] for word in w2id.keys()}  # 词语的词向量
    n_vocabs = len(w2id) + 1
    embedding_weights = np.zeros((n_vocabs, 100))
    for w, index in w2id.items():  # 从索引为1的词语开始,用词向量填充矩阵
        embedding_weights[index, :] = w2vec[w]
    return w2id,embedding_weights

def text_to_array(w2index, senlist):  # 文本转为索引数字模式
    sentences_array = []
    for sen in senlist:
        new_sen = [ w2index.get(word,0) for word in sen]   # 单词转索引数字
        sentences_array.append(new_sen)
    return np.array(sentences_array)

def prepare_data(w2id,sentences,labels,max_len=200):
    X_train, X_val, y_train, y_val = train_test_split(sentences,labels, test_size=0.2)
    X_train = text_to_array(w2id, X_train)
    X_val = text_to_array(w2id, X_val)
    X_train = pad_sequences(X_train, maxlen=max_len)
    X_val = pad_sequences(X_val, maxlen=max_len)
    return np.array(X_train), np_utils.to_categorical(y_train) ,np.array(X_val), np_utils.to_categorical(y_val)
获取词向量矩阵和词典
w2id,embedding_weights = generate_id2wec(model)

这一步主要是为了拿到传给后续情感分析模型的词典(w2id)和词向量矩阵embedding_weights,
w2id格式如下:{
...
'一两天': 454,
'一两年': 455,
'一两次': 456,
'一个': 457,
'一个个': 458,
'一个劲': 459,
...
'不一会': 984,
'不上': 985,
'不下': 986,
'不严': 987,
'不为过': 988,
'不久': 989,
}

embedding_weights格式如下:
[[ 0. , 0. , 0. , ..., 0. ,
0. , 0. ],
[-1.1513499 , -0.00520114, 1.65645397, ..., 0.50586915,
-0.03466858, 0.84113288],
[ 0.01824509, -0.23613754, -0.47191045, ..., -0.16491373,
-0.25222906, -0.00384654],
...,
[ 0.10879639, 0.05459598, -0.02946772, ..., -0.17389177,
0.10144144, 0.21539673]]

这个矩阵保存了上面通过Word2Vector方法训练的词向量,每个词通过其在词典(w2id)中的index索引到对应得词向量,此矩阵将作为参数传给后续的情感分析模型。

数据变换
x_train,y_trian, x_val , y_val = prepare_data(w2id,sentences,labels,200)

将数据变换成模型能够处理的格式。
原始数据格式如下:
sen :不错,品种齐全,上菜很快,味道也不错
label :2

执行上面代码后句子数据变成如下格式:
输入:[0,0,0......,31,43,12,4,65,12,233,11,1391,131,4923,1233]
输出:[0,0,1]

构建模型

这里定义了一个Sentiment类,封装了模型的构建,训练和预测方法。

class Sentiment:
    def __init__(self,w2id,embedding_weights,Embedding_dim,maxlen,labels_category):
        self.Embedding_dim = Embedding_dim
        self.embedding_weights = embedding_weights
        self.vocab = w2id
        self.labels_category = labels_category
        self.maxlen = maxlen
        self.model = self.build_model()
      
        
    def build_model(self):
        model = Sequential()
        #input dim(140,100)
        model.add(Embedding(output_dim = self.Embedding_dim,
                           input_dim=len(self.vocab)+1,
                           weights=[self.embedding_weights],
                           input_length=self.maxlen))
        model.add(Bidirectional(LSTM(50),merge_mode='concat'))
        model.add(Dropout(0.5))
        model.add(Dense(self.labels_category))
        model.add(Activation('softmax'))
        model.compile(loss='categorical_crossentropy',
                     optimizer='adam', 
                     metrics=['accuracy'])
        model.summary()
        return model
    
    def train(self,X_train, y_train,X_test, y_test,n_epoch=5 ):
        self.model.fit(X_train, y_train, batch_size=32, epochs=n_epoch,
                      validation_data=(X_test, y_test))
        self.model.save('sentiment.h5')   
        
    def predict(self,model_path,new_sen):
        model = self.model
        model.load_weights(model_path)
        new_sen_list = jieba.lcut(new_sen)
        sen2id =[ self.vocab.get(word,0) for word in new_sen_list]
        sen_input = pad_sequences([sen2id], maxlen=self.maxlen)
        res = model.predict(sen_input)[0]
        return np.argmax(res)
senti = Sentiment(w2id,embedding_weights,100,200,3)

构建模型,同时传人词典和词向量矩阵。


模型架构

模型训练

senti.train(x_train,y_trian, x_val ,y_val,1)

运行上述代码让模型跑起来,笔者只是做个实验,所以只让模型训练了一个epoch。


训练模型

模型预测

label_dic = {0:"消极的",1:"中性的",2:"积极的"}
sen_new = "现如今的公司能够做成这样已经很不错了,微订点单网站的信息更新很及时,内容来源很真实"
pre = senti.predict("./sentiment.h5",sen_new)
print("'{}'的情感是:\n{}".format(sen_new,label_dic.get(pre)))

模型训练完之后,接下来就是见证奇迹的时刻了。


模型预测部分

笔者输入一句评论让模型去预测,结果如上图所示。只训练了一个epoch,就有这样的功力,不得不承认词向量+深度学习真是强。

结语

至此,我们通过深度学习技术让计算机学会人类世界中一些简单的情感判断。有没有觉得有那么一丝丝可怕,会不会真有一天,你在和一个计算机进行情感交流呢?
(想着想着,笔者先跑了)

参考:
https://github.com/Edward1Chou/SentimentAnalysis

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容