訂閱
糾錯
加入自媒體

基于word embedding對IMDB影評數(shù)據(jù)二分類

最近做了很多和文本相關(guān)的項目,但不管是要計算文本相似度,還是對文本進(jìn)行分類或者聚類,都免不了要將文本轉(zhuǎn)換成詞向量(wordvec),然后再進(jìn)行一系列的數(shù)學(xué)操作。所以這可以說是整個流程中最重要的一項,有點(diǎn)類似于數(shù)據(jù)分析中的特征工程。

/ 01 / 詞向量

轉(zhuǎn)換成詞向量本人用的最多的是將文本切割為word,這一過程被稱之為token,而將文本轉(zhuǎn)化為token這一過程稱之為tokenization。而token有兩種常見的編碼形式,一種是one-h(huán)ot,另一種是詞嵌入(word-embedding)。這兩種方式都是將二維的文本轉(zhuǎn)換成三維的向量集合。

/ 02 / one-h(huán)ot編碼

關(guān)于One-h(huán)ot在這也提一嘴。比如這樣兩句話:The cat sat on the mat/The dog ate my homework。它將所有出現(xiàn)過的詞語存入一個列表a(

['The', 'cat', 'sat', 'on', 'the', 'mat.', 'The', 'dog', 'ate', 'my', 'homework.']

),每一個單詞對應(yīng)一個索引,比如The對映1,cat對映2,依次類推,最后將每個單詞替換為索引處為1,其余為0的一組長度為11(len(a)=11)的向量。

samples = ['The cat sat on the mat.', 'The dog ate my homework.']# 10# 定義一個集合,得到{'The': 1, 'cat': 2, 'sat': 3, 'on': 4, 'the': 5, 'mat.': 6, 'dog': 7, 'ate': 8, 'my': 9, 'homework.': 10},也就是篩選出這個句子中對應(yīng)的了哪些詞,然后并賦予索引值,其實就是個詞庫token_index = {}for sample in samples:    for word in sample.split():        if word not in token_index:            token_index[word] = len(token_index) + 1
!∠拗屏俗x取的句子的長度,一句話最長10個詞max_length = 10results = np.zeros(shape=(len(samples),                          max_length,                          max(token_index.values()) + 1))
# print(results) 2, 10, 11for i, sample in enumerate(samples):    for j, word in list(enumerate(sample.split()))[:max_length]:        index = token_index.get(word)        results[i, j, index] = 1.print(results)[[[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
[[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]]

但是這樣編碼方式得到的向量集是二進(jìn)制,且非常稀疏且維度巨大的,同時也沒有保留前后文的聯(lián)系,脫離了整個語境。

/ 03 / word-embedding編碼

第二種編碼方式word-embedding完全解決了上述這些缺點(diǎn),它會給每個單詞分配一個固定長度的向量(有50維的,有100維的,也有300維的),并且根據(jù)兩個向量的余弦相似度可以得到兩個詞的相關(guān)性。

有兩種業(yè)界常用的WordEmbedding生成方式,Continuous Bag Of Words (CBOW)方法和n-gram方法,我們采用n-gram方法。它是將一個句子按照固定長度分割為許多batch,從前往后每次選長度為skip_window的窗口(可以設(shè)置為3,也可以設(shè)置為5)。對于窗口中的5個單詞,以中間(也就是第三個單詞)為中心,生成兩隊source-target單詞對,source為中心單詞,target為窗口中除中心詞以外的其余單詞。栗子如下。

The cat sat on the matskip_window=5source ---> target第一個窗口The cat sat on thesat ---> catsat ---> the第二個窗口cat sat on the maton ---> caton ---> mat

然后得到的這些source-target作為分別作為輸入和輸出,具體的訓(xùn)練方法可以參照這篇博客。但是不一般不建議大家自己訓(xùn)練嵌入層,因為計算量實在是太大,

下面用到的嵌入層是用已經(jīng)訓(xùn)練好的GLoVE數(shù)據(jù)集。

/ 04 / 導(dǎo)入數(shù)據(jù)

先來看下要做分類的數(shù)據(jù)集。

兩個文件夾neg和pos分別存放差評和好評。一個txt對應(yīng)一條評論。分別讀入dataset中,其中neg定為0,pos定為1,對應(yīng)存入labels中。

def read_data(self):      dataset = []      labels = []      def eachFile(filepath,lab):          pathDir = os.listdir(filepath)          for allDir in pathDir:              with open(filepath + '/' + allDir) as f:                  try:                      dataset.a(chǎn)ppend(f.read())                      labels.a(chǎn)ppend(lab)                  except UnicodeDecodeError:                      pass      filepath = ['C:/Users/伊雅/PycharmProjects/untitled/venv/share/nlp/aclImdb/aclImdb/test/neg','C:/Users/伊雅/PycharmProjects/untitled/venv/share/nlp/aclImdb/aclImdb/test/pos']      eachFile(filepath[0],0)      eachFile(filepath[1], 1)      return dataset,labels

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

第二步就是得到按照頻率大小排序的詞語編號,并將每一個句子中所有單詞替換為對應(yīng)的編號。舉個栗子:

somestr = ['ha ha gua angry','howa ha gua excited']可以得到下面的字典,key為單詞,value為單詞的頻數(shù){'ha':3,"gua":2,"angry":1,"howa":1,"excited":1}根據(jù)每個單詞的頻數(shù)將單詞從大到小排列得到一個list['ha','gua','angry','howa','excited']第一個單詞ha賦予編號1,gua賦予編號2,angry賦予編號3,...依次類推,這樣可以得到所有單詞的編號,word_indexword_index={'ha':1,'gua':2,'angry':3,'howa':4,'excited':5}在現(xiàn)實計算中,由于出現(xiàn)的單詞基數(shù)過大,所以會設(shè)定一個max_words=10000,表示取list的前10000個單詞。最終可以將['ha ha gua angry','howa ha gua excited']轉(zhuǎn)換為詞向量['ha ha gua angry'] ---> [1,1,2,3]['howa ha gua excited'] ---> [4,1,2,5]

文本預(yù)處理的基本流程就如上所示。接下來就是使用keras的Tokenizer把導(dǎo)入的數(shù)據(jù)進(jìn)行批量處理,轉(zhuǎn)換為對應(yīng)的詞向量,并隨機(jī)打亂順序得到訓(xùn)練樣本和測試樣本。

def tokennize_data(self):      # 導(dǎo)入數(shù)據(jù)集      dataset,labels=self.read_data()      # 構(gòu)造一個分詞器,num_words默認(rèn)是None處理所有字詞,但是如果設(shè)置成一個整數(shù),那么最后返回的是最常見的、出現(xiàn)頻率最高的num_words個字詞。      tokenizer = Tokenizer(num_words=self.max_words)      # 類方法之一,texts為將要訓(xùn)練的文本列表      tokenizer.fit_on_texts(texts=dataset)      # 將texts所有句子所有單詞變?yōu)閣ord_index對應(yīng)的數(shù)字      sequences = tokenizer.texts_to_sequences(texts=dataset)      # 將所有的單詞從大到小排列l(wèi)ist,構(gòu)建一個key為單詞,value為單詞對應(yīng)在list的位置      word_index = tokenizer.word_index      # print(word_index)      print('Found %s unique tokens.' % len(word_index))      # 序列填充,如果向量長度超過maxlen則保留前maxlen,如果沒有maxlen這么長,就用0填充      data = pad_sequences(sequences, maxlen=self.max_len)      # asarray和array不同點(diǎn)是當(dāng)labels發(fā)生變化時,asarray(labels)跟著發(fā)生變化,但array(labels)不變      labels = np.a(chǎn)sarray(labels)      print('Shape of data tensor:', data.shape)      print('Shape of label tensor:', labels.shape)      indices = np.a(chǎn)range(data.shape[0])      # 將indices的順序打亂      np.random.shuffle(indices)      data = data[indices]      labels = labels[indices]      # 生成訓(xùn)練樣本和測試樣本      # 訓(xùn)練樣本數(shù)據(jù)集x_train,訓(xùn)練樣本類標(biāo)簽y_train      x_train = data[:self.training_samples]      y_train = labels[:self.training_samples]      x_val = data[self.training_samples: self.training_samples + self.validation_samples]      y_val = labels[self.training_samples: self.training_samples + self.validation_samples]      return x_train, y_train, x_val, y_val, word_index

/ 06 / 構(gòu)造embedding矩陣

下一步就是把word_index的前10000個單詞轉(zhuǎn)化成對應(yīng)的特征向量,構(gòu)成embedding_matrix嵌入層矩陣。下圖為glove數(shù)據(jù)集,第一個字符為單詞,第二到五十一為這個單詞對應(yīng)的1*50維的向量。

因為glove數(shù)據(jù)集中的單詞是成千上萬的,我這里選的是50維的(一個單詞對應(yīng)一個1*50的向量),但出現(xiàn)在word_index前10000中的單詞是有限的,因此要構(gòu)造一個10000*50維的嵌入層矩陣。

def parse_word_embedding(self,word_index):      glove_dir = 'C:/Users/伊雅/AppData/Local/Temp/baiduyunguanjia/onlinedit/cache/cddea73f1336cbed6744e3e9a91ad7f3/glove.6B.50d.txt'      embeddings_index = {}      # 將glove中的單詞存為key,對應(yīng)的向量存為value      f = open(glove_dir, encoding='UTF-8')      for line in f:          values = line.split()          word = values[0]          coefs = np.a(chǎn)sarray(values[1:], dtype='float32')          embeddings_index[word] = coefs      f.close()      print('Found %s word vectors.' % len(embeddings_index))      # embedding_matrix為訓(xùn)練好的嵌入層矩陣      embedding_matrix = np.zeros((self.max_words, self.embedding_dim))      for word, i in word_index.items():      # 取前10000個          if i < self.max_words:              embedding_vector = embeddings_index.get(word)              # embedding_matrix[i]為對應(yīng)word的向量              if embedding_vector is not None:                  embedding_matrix[i] = embedding_vector      return embedding_matrix

/ 07 / 基于keras構(gòu)建二分類模型

一切準(zhǔn)備好以后就可以開始建模了。這里使用的是keras庫。這里要說的是嵌入層,它的shape是(N,100,50)N是樣本數(shù),100是max_len,因為在進(jìn)行向量計算時,緯度必須保持一致,因為每個句子都變?yōu)殚L度為100的向量。如果這個句子只有3個單詞,則前面97個為0,98,99,100分別為三個單詞對應(yīng)的編號。在文本預(yù)處理部分已經(jīng)將所有句子填充為長度100的向量。50是指每一個單詞用1*50的向量替換,一個句子從1*100維變成了1*100*50的三維向量。

def model(self):      # 創(chuàng)建模型,序貫?zāi)P?     model = Sequential()      model.a(chǎn)dd(Embedding(self.max_words, self.embedding_dim, input_length=self.max_len))      #output= shape=(?,100,50).input=shape=(?,100)      print(model.layers[0].get_weights())      model.a(chǎn)dd(Flatten())      # input=shape=(?,100,50),output=(?,5000)      #  (32, 10, 64) #32句話,每句話10個單詞,每個單詞有64維的詞向量      print(model.layers[0].get_weights())      # Dense表示連接層,32是units,是輸出緯度,activation表示激活函數(shù),relu表示平均函數(shù),sigmoid是正交矩陣      model.a(chǎn)dd(Dense(32, activation='relu'))      model.a(chǎn)dd(Dense(1, activation='sigmoid'))      model.summary()      # 將GLOVE加載到模型中      x_train, y_train, x_val, y_val, word_index = self.tokennize_data()      # 嵌入層:      # input_dim:字典長度,即輸入數(shù)據(jù)最大下標(biāo)+1      # output_dim:全連接嵌入的維度      # input_length:當(dāng)輸入序列的長度固定時,該值為其長度。如果要在該層后接Flatten層,然后接Dense層,則必須指定該參數(shù),否則Dense層的輸出維度無法自動推斷。      embedding_matrix = self.parse_word_embedding(word_index)      print(embedding_matrix)      model.layers[0].set_weights([embedding_matrix])      # 權(quán)重矩陣固定,不在需要自我訓(xùn)練      model.layers[0].trainable = False#模型的編譯,優(yōu)化器,可以是現(xiàn)成的優(yōu)化器如rmsprop,binary_crossentropy是交叉熵?fù)p失函數(shù),一般用于二分類,metrics=['acc']評估模型在訓(xùn)練和測試時的網(wǎng)絡(luò)性能的指標(biāo),      model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])      # epochs表示循環(huán)次數(shù),batch_size表示每一次循環(huán)使用多少數(shù)據(jù)量      history = model.fit(x_train, y_train,epochs=10,batch_size=512,validation_data=(x_val, y_val))      result=model.evaluate(x_val, y_val, verbose=1)      print('test score is',result[0])      print('test accuracy is ', result[1])      history_dict = history.history      return history_dict

/ 08 / 模型精度評價

得到的結(jié)果如下。Loss為損失,acc為準(zhǔn)確率,val_loss是驗證集損失,val_acc為驗證集準(zhǔn)確率。一共進(jìn)行了10輪,由于數(shù)據(jù)量過大,為了提高效率,每一次只使用512個樣本量進(jìn)行計算。

Train on 3000 samples, validate on 12000 samplesEpoch 1/10
512/3000 [====>.........................] - ETA: 8s - loss: 0.5417 - acc: 0.76562048/3000 [===================>..........] - ETA: 0s - loss: 1.1453 - acc: 0.71243000/3000 [==============================] - 2s 692us/step - loss: 0.9635 - acc: 0.7367 - val_loss: 0.4777 - val_acc: 0.8094Epoch 2/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.4469 - acc: 0.81252560/3000 [========================>.....] - ETA: 0s - loss: 0.4571 - acc: 0.80593000/3000 [==============================] - 0s 111us/step - loss: 0.4675 - acc: 0.8043 - val_loss: 0.5002 - val_acc: 0.8094Epoch 3/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.5139 - acc: 0.77342048/3000 [===================>..........] - ETA: 0s - loss: 0.4773 - acc: 0.80473000/3000 [==============================] - 0s 121us/step - loss: 0.4619 - acc: 0.8060 - val_loss: 0.6160 - val_acc: 0.8094Epoch 4/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.4900 - acc: 0.82622560/3000 [========================>.....] - ETA: 0s - loss: 0.4332 - acc: 0.80863000/3000 [==============================] - 0s 115us/step - loss: 0.4307 - acc: 0.8070 - val_loss: 0.4597 - val_acc: 0.8107Epoch 5/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.3809 - acc: 0.83402048/3000 [===================>..........] - ETA: 0s - loss: 0.4643 - acc: 0.79053000/3000 [==============================] - 0s 121us/step - loss: 0.4444 - acc: 0.7943 - val_loss: 0.4704 - val_acc: 0.8094Epoch 6/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.4009 - acc: 0.79882048/3000 [===================>..........] - ETA: 0s - loss: 0.3815 - acc: 0.80133000/3000 [==============================] - 0s 123us/step - loss: 0.3935 - acc: 0.8063 - val_loss: 0.4878 - val_acc: 0.8112Epoch 7/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.4118 - acc: 0.80272560/3000 [========================>.....] - ETA: 0s - loss: 0.3754 - acc: 0.82663000/3000 [==============================] - 0s 124us/step - loss: 0.3882 - acc: 0.8247 - val_loss: 0.4903 - val_acc: 0.8099Epoch 8/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.4186 - acc: 0.82622048/3000 [===================>..........] - ETA: 0s - loss: 0.3527 - acc: 0.84083000/3000 [==============================] - 0s 120us/step - loss: 0.3526 - acc: 0.8393 - val_loss: 0.4804 - val_acc: 0.7933Epoch 9/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.3581 - acc: 0.90232048/3000 [===================>..........] - ETA: 0s - loss: 0.3764 - acc: 0.85603000/3000 [==============================] - 0s 115us/step - loss: 0.3583 - acc: 0.8573 - val_loss: 0.4496 - val_acc: 0.8108Epoch 10/10
512/3000 [====>.........................] - ETA: 0s - loss: 0.3099 - acc: 0.85352560/3000 [========================>.....] - ETA: 0s - loss: 0.3728 - acc: 0.85823000/3000 [==============================] - 0s 114us/step - loss: 0.3630 - acc: 0.8563 - val_loss: 0.4508 - val_acc: 0.8093
test score is 0.45084257221221924test accuracy is  0.8093333333333333

得到的損失-準(zhǔn)確曲線如下圖所示。

/ 09 / 終章

本文所有代碼已全部上次GitHub,感興趣的朋友可以給個小星星?fork噢。

數(shù)據(jù)集自行從百度云下載。

------------------- End -------------------

聲明: 本文由入駐維科號的作者撰寫,觀點(diǎn)僅代表作者本人,不代表OFweek立場。如有侵權(quán)或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯
x
*文字標(biāo)題:
*糾錯內(nèi)容:
聯(lián)系郵箱:
*驗 證 碼:

粵公網(wǎng)安備 44030502002758號