基于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 -------------------
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
推薦專題
- 1 Intel宣布40年來最重大轉(zhuǎn)型:年底前裁員15000人、拋掉2/3房產(chǎn)
- 2 因美封殺TikTok,字節(jié)股價骨折!估值僅Meta1/5
- 3 宏山激光重磅發(fā)布行業(yè)解決方案,助力智能制造產(chǎn)業(yè)新飛躍
- 4 國產(chǎn)AI芯片公司破產(chǎn)!白菜價拍賣
- 5 具身智能火了,但規(guī)模落地還需時間
- 6 國產(chǎn)英偉達(dá)們,抓緊沖刺A股
- 7 三次錯失風(fēng)口!OpenAI前員工殺回AI編程賽道,老東家捧金相助
- 8 英特爾賦能智慧醫(yī)療,共創(chuàng)數(shù)字化未來
- 9 英偉達(dá)的麻煩在后頭?
- 10 將“網(wǎng)紅”變成“商品”,AI“爆改”實力拉滿
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市