Keras情感分析(Sentiment Analysis)實(shí)戰(zhàn)---自然語言處理技術(shù)(2)

情感分析(Sentiment Analysis)是自然語言處理里面比較高階的任務(wù)之一。仔細(xì)思考一下,這個(gè)任務(wù)的究極目標(biāo)其實(shí)是想讓計(jì)算機(jī)理解人類的情感世界。我們自己都不一定能完全控制和了解自己的情感,更別說機(jī)器了。

不過在人工智能的認(rèn)知智能階段(人工智能三階段——計(jì)算智能,感知智能,認(rèn)知智能),商家還是可以用它來做一些商品或服務(wù)的評論分析,繼而有效地去優(yōu)化商品或服務(wù),為消費(fèi)者們提供更好用戶體驗(yàn)。

情感分析任務(wù)簡介

情感分析任務(wù)其實(shí)是個(gè)分類任務(wù),給模型輸入一句話,讓它判斷這句話的情感是積極的,消極的,還是中性的。例子如下:
輸入:的確是專業(yè),用心做,出品方面都給好評。
輸出:2
輸出可以是[0,1,2]其中一個(gè),0表示情感消極,1表示情感中性,2表示情感積極。

情感分析這個(gè)任務(wù)還有一個(gè)升級版——細(xì)膩度的情感分析。升級版希望模型不僅能識別出情感的好壞,而且還希望模型能識別出是由于什么原因?qū)е逻@種情感發(fā)生。舉個(gè)例子,"這家餐廳的地理位置不錯(cuò),可惜菜不怎么好吃",我們就需要識別出,在地理位置這個(gè)aspect上情感是積極的,而在菜的味道這個(gè)aspect上情感是消極的。聽起來是不是很難,所以實(shí)戰(zhàn)部分我只簡單介紹一下麻瓜版的情感分析任務(wù)——簡單的分類。

情感分析算法簡介

分類任務(wù)的算法,想必大家都很熟悉:SVM,Logistic,Tree等??墒菍τ谖谋痉诸悂碚f,最重要的是如何將一句話的映射到向量空間,同時(shí)保持其語義特征。所以文本的向量化表示是最最重要的一個(gè)環(huán)節(jié)。而文本的向量化就是涉及到Word Embedding技術(shù)和深度學(xué)習(xí)(Deep Learning)技術(shù)。

Word Embedding指的是把文本轉(zhuǎn)換成計(jì)算機(jī)能處理的向量,而其中難點(diǎn)的是:將文本向量化時(shí)如何保持句子原有的語義。早期word embedding使用的是Bag of Words,TF-IDF等,這些算法有個(gè)共同的特點(diǎn):就是沒有考慮語序以及上下文關(guān)系。而近幾年發(fā)展出來的Word2Vector ,Glove等考慮到了文本的上下文關(guān)系。今年NLP領(lǐng)域大放異彩的BERT就是在文本向量化上做出了重大的突破。

人工特征的挖掘是個(gè)極為費(fèi)腦費(fèi)時(shí)的過程,深度學(xué)習(xí)模型可以將特征工程自動(dòng)化,通過神經(jīng)網(wǎng)絡(luò)自動(dòng)做特征的表示學(xué)習(xí)。在NLP領(lǐng)域中,RNN(LSTM,GRU),CNN,Transformer等各路深度學(xué)習(xí)模型各顯神通,憑借他們強(qiáng)大的特征表示能力,在很多任務(wù)中都吊打人工特征(吹得 有些夸張了,沒收?。2贿^人工特征有時(shí)還是很重要的。

項(xiàng)目實(shí)戰(zhàn)

本次的項(xiàng)目實(shí)戰(zhàn)的總體架構(gòu)可分為兩個(gè)步驟:
(1)采用Word2Vector技術(shù)去訓(xùn)練詞向量;
(2)采用BiLSTM去做特征的表示學(xué)習(xí)。
其項(xiàng)目架構(gòu)如下圖所示:


情感分析架構(gòu)圖

數(shù)據(jù)讀取

數(shù)據(jù)格式如下:一句評論后面標(biāo)記一個(gè)label,0表示消極情感,1表示中性情感,2表示積極情感。


情感分析數(shù)據(jù)

這里針對筆者自己的數(shù)據(jù)集定義了一個(gè)數(shù)據(jù)讀入函數(shù)。

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")

數(shù)據(jù)讀入之后,得到一個(gè)所有評論的sentences列表 ,和一個(gè)與之一一對應(yīng)的labels列表。
sentences[1] :烤鴨還是不錯(cuò)的,別的菜沒什么特殊的
labels[1] :1

訓(xùn)練詞向量

將所有的評論文本數(shù)據(jù)用來訓(xùn)練詞向量,這里使用的gensim中的Word2Vec,原理是的Skip-gram。這里對詞向量的原理不多介紹,總之,這一步將一個(gè)詞映射成一個(gè)100維的向量,并且考慮到了上下文的語義。這里直接將上一部得到的句子列表傳給train_word2vec函數(shù)就可以了,同時(shí)需要定義一個(gè)詞向量文件保存路徑。模型保存后,以后使用就不需要再次訓(xùn)練,直接加載保存好的模型就可以啦。

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("開始訓(xùn)練詞向量") 
#     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')    

數(shù)據(jù)預(yù)處理

這里定義了一些數(shù)據(jù)處理和變換方法。

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):  # 文本轉(zhuǎn)為索引數(shù)字模式
    sentences_array = []
    for sen in senlist:
        new_sen = [ w2index.get(word,0) for word in sen]   # 單詞轉(zhuǎn)索引數(shù)字
        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)

這一步主要是為了拿到傳給后續(xù)情感分析模型的詞典(w2id)和詞向量矩陣embedding_weights,
w2id格式如下:{
...
'一兩天': 454,
'一兩年': 455,
'一兩次': 456,
'一個(gè)': 457,
'一個(gè)個(gè)': 458,
'一個(gè)勁': 459,
...
'不一會(huì)': 984,
'不上': 985,
'不下': 986,
'不嚴(yán)': 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]]

這個(gè)矩陣保存了上面通過Word2Vector方法訓(xùn)練的詞向量,每個(gè)詞通過其在詞典(w2id)中的index索引到對應(yīng)得詞向量,此矩陣將作為參數(shù)傳給后續(xù)的情感分析模型。

數(shù)據(jù)變換
x_train,y_trian, x_val , y_val = prepare_data(w2id,sentences,labels,200)

將數(shù)據(jù)變換成模型能夠處理的格式。
原始數(shù)據(jù)格式如下:
sen :不錯(cuò),品種齊全,上菜很快,味道也不錯(cuò)
label :2

執(zhí)行上面代碼后句子數(shù)據(jù)變成如下格式:
輸入:[0,0,0......,31,43,12,4,65,12,233,11,1391,131,4923,1233]
輸出:[0,0,1]

構(gòu)建模型

這里定義了一個(gè)Sentiment類,封裝了模型的構(gòu)建,訓(xùn)練和預(yù)測方法。

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)

構(gòu)建模型,同時(shí)傳人詞典和詞向量矩陣。


模型架構(gòu)

模型訓(xùn)練

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

運(yùn)行上述代碼讓模型跑起來,筆者只是做個(gè)實(shí)驗(yàn),所以只讓模型訓(xùn)練了一個(gè)epoch。


訓(xùn)練模型

模型預(yù)測

label_dic = {0:"消極的",1:"中性的",2:"積極的"}
sen_new = "現(xiàn)如今的公司能夠做成這樣已經(jīng)很不錯(cuò)了,微訂點(diǎn)單網(wǎng)站的信息更新很及時(shí),內(nèi)容來源很真實(shí)"
pre = senti.predict("./sentiment.h5",sen_new)
print("'{}'的情感是:\n{}".format(sen_new,label_dic.get(pre)))

模型訓(xùn)練完之后,接下來就是見證奇跡的時(shí)刻了。


模型預(yù)測部分

筆者輸入一句評論讓模型去預(yù)測,結(jié)果如上圖所示。只訓(xùn)練了一個(gè)epoch,就有這樣的功力,不得不承認(rèn)詞向量+深度學(xué)習(xí)真是強(qiáng)。

結(jié)語

至此,我們通過深度學(xué)習(xí)技術(shù)讓計(jì)算機(jī)學(xué)會(huì)人類世界中一些簡單的情感判斷。有沒有覺得有那么一絲絲可怕,會(huì)不會(huì)真有一天,你在和一個(gè)計(jì)算機(jī)進(jìn)行情感交流呢?
(想著想著,筆者先跑了)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容