wordcloud里面设置mask加载不出来词频_一条龙搞定情感分析:文本预处理、加载词向量、搭建RNN
IMDB Sentiment Classification from scratch
Author: Beyond
Time: 2019.04.26
情感分析是上手NLP的最簡單的任務之一,它就是一個簡單的文本分類問題,判斷一段文本的情感極性。最簡單的就是二分類,判斷是積極的還是消極的;更難一點的就是三分類,除了積極消極還有無情感傾向的;更加復雜的就比如情感打分,例如電影打1~5分,這就是五分類。但本質上都一樣,無非類別太多更難以學習罷了。IMDB是一個專業的電影評論網站,類似國內的豆瓣,IMDB的電影評論數據是大家經常使用來練手的情感分析數據集,也是各種比賽,如Kaggle,和各種學者做研究常用的數據集。
本文嘗試用這個數據做一個情感二分類,作為一個NLP的練手。具體涉及到:
數據集地址:http://ai.stanford.edu/~amaas/data/sentiment/
本文采用Keras作為框架在進行模型搭建。
本文目錄:
一、文本預處理&訓練測試集的準備
1.數據集
①關于數據集
其實,keras自帶了IMDB的已經進行很好的預處理的數據集,可以一行代碼下載,不需要進行任何的處理就可以訓練,而且效果比較好。但是,這樣就太沒意思了。在真實場景中,我們拿到的都是臟臟的數據,我們必須自己學會讀取、清洗、篩選、分成訓練集測試集。而且,從我自己的實踐經驗來看,數據預處理的本事才是真本事,模型都好搭,現在的各種框架已經讓搭建模型越來越容易,但是數據預處理只能自己動手。所有往往實際任務中,數據預處理花費的時間、精力是最多的,而且直接影響后面的效果。
另外,我們要知道,對文本進行分析,首先要將文本數值化。因為計算機不認字的,只認數字。所以最后處理好的文本應該是數值化的形式。而keras自帶的數據集全都數值化了,而它并不提供對應的查詢字典讓我們知道每個數字對應什么文字,這讓我們只能訓練模型,看效果,無法拓展到其他語料上,也無法深入分析。綜上,我上面推薦的數據集,是原始數據集,都是真實文本,當然,為了方便處理,也已經被斯坦福的大佬分好類了。但是怎么數值化,需要我們自己動手。
下載后解壓,會看到有兩個文件夾,test和train:
我們點進train中,會發現正樣本和負樣本已經分好類了:
neg和pos分別是負樣本和正樣本,unsup是未標注的樣本,可用后續需要采用。其他的都自己去看看吧。
打開pos文件,看看里面啥樣:
都是一個個文本。
注意到,這些文本一般都不短...
數據集中,共有5w條文本,test集和train集各半,每個集合中,pos和neg也是各半。
當然,他們劃分的train和test,你不一定真的要這樣用。例如本文中,我為了方便,就吧train集合當做我所有的數據,在這2.5w條數據中再按照7:3劃分train set和test set.
②導入數據集的代碼
import os datapath = r'datasetsaclImdb_v1train' pos_files = os.listdir(datapath+'/pos') neg_files = os.listdir(datapath+'/neg') print(len(pos_files)) print(len(neg_files))輸出:
12500 12500所以我們總共有12500個正樣本和12500個負樣本。
import numpy as np pos_all = [] neg_all = [] for pf,nf in zip(pos_files,neg_files):with open(datapath+'/pos'+'/'+pf,encoding='utf-8') as f:s = f.read()pos_all.append(s)with open(datapath+'/neg'+'/'+nf,encoding='utf-8') as f:s = f.read()neg_all.append(s) print(len(pos_all)) print(len(neg_all)) X_orig = np.array(pos_all+neg_all) Y_orig = np.array([1 for _ in range(12500)] + [0 for _ in range(12500)]) print("X_orig:",X_orig.shape) print("Y_orig:",Y_orig.shape)上面代碼的主要作用是把一個個樣本放進正負樣本對應的列表中,同時配上對應的label。代碼很好理解。
輸出:
12500 12500 X_orig: (25000,) Y_orig: (25000,)2.文本數值化
①文本數值化的思路
前面提到過,NLP問題比CV問題更難的一部分原因,就是文本都是離散化的數據,不像圖像數據都是連續的數值數據,所以我們要想辦法把一系列文本轉化成一系列數字。
這里的方法很多,我們這里采用的方法是,給詞匯表中每一個詞一個index,用index代替那個詞。如一個語料庫共有1w個詞,那么就設置1w個index,每個詞直接替換程index就行。
但是,很多問題中,詞匯量巨大,但是可能大部分詞都是低頻詞,對訓練模型的貢獻很小,反而會嚴重拖累模型的訓練。所以,一般我們可以分析一下文本詞匯的詞頻分布特征,選取詞頻占大頭的一批詞就行了。
例如,在本文的任務中,數據集共涉及到的詞匯量有8~9w,這樣訓練起來會很慢。經過分析,發現大概2w個詞就已經覆蓋了絕大部分篇幅,所以我就選取詞典大小為2w。然后,對文本數值化的時候,那些低頻詞就直接過濾掉了,只留下高頻詞。這樣,模型訓練起來效率就會大大提高。
詞向量
如果你接觸過詞向量,那么一定會想到可以使用詞向量吧文本轉化成數值類型。不錯,我們在本文中也會這么做。但是,如果直接吧文本轉化成詞向量,輸入進模型的話,我們可能無法繼續調優(fine-tune),詞向量相當于是對文本的特征的一種表示,本身性質已經很好了。但是對于特定任務場景,我們一般都希望可以在訓練好的詞向量的基礎上,繼續用對應領域的數據對詞向量進一步進行優化。所以,今天我們會探索,如果在加入詞向量后,可以接著fine-tune。
②文本數值化,詞向量導入的代碼
keras自帶的文本預處理的工具十分好用,具體可參加我單獨寫的一個短文:https://beyondguo.github.io/2019-03-18-Keras-Text-Preprocessing/
我們設置詞典大小為20000,文本序列最大長度為200.
from keras.preprocessing.text import text_to_word_sequence,one_hot,Tokenizer from keras.preprocessing.sequence import pad_sequences import time vocab_size = 20000 maxlen = 200 print("Start fitting the corpus......") t = Tokenizer(vocab_size) # 要使得文本向量化時省略掉低頻詞,就要設置這個參數 tik = time.time() t.fit_on_texts(X_orig) # 在所有的評論數據集上訓練,得到統計信息 tok = time.time() word_index = t.word_index # 不受vocab_size的影響 print('all_vocab_size',len(word_index)) print("Fitting time: ",(tok-tik),'s') print("Start vectorizing the sentences.......") v_X = t.texts_to_sequences(X_orig) # 受vocab_size的影響 print("Start padding......") pad_X = pad_sequences(v_X,maxlen=maxlen,padding='post') print("Finished!")上面的代碼可以第一次讀會比較難理解,這里稍微解釋一下:
Tokenizer是一個類,可以接收一個vocab_size的參數,也就是詞典大小。設置了詞典大小后,在后面生成文本的向量的時候,會把那些低頻詞(詞頻在20000開外的)都篩掉。
定義了Tokenizer的一個實例t,然后調用方法t.fit_on_texts(X_orig)的作用,就是把我們所有的預料丟進去,讓t去統計,它會幫你統計詞頻,給每個詞分配index,形成字典等等。
想獲取index和詞的對照字典的話,就使用t.word_index方法。注意,獲取字典的時候,不會篩掉那些低頻詞,是所有詞的一個字典。
然后,想把一個句子、段落,轉化成對應的index表示的向量怎么辦呢?Tokenizer也提供了便捷的方法,不用你自己去慢慢查表,直接使用t.texts_to_sequences(X_orig)方法,就可以獲取每句話的index組成的向量表示。注意,這里,就已經吧低頻詞給過濾掉了,比如一句話有100個詞,其中有30個低頻詞,那么經過這個函數,得到的就是長度為70的一個向量。
得到每個句子的向量后,會發現大家長度各有不同,長的長短的短,這樣在后面的RNNs訓練時,就不方便批處理。所以,我們還需要對句子進行一個padding(填白,補全),把所有句子弄程統一長度,短的補上0,長的切掉。用的方法就是pad_sequences。
上面代碼的輸出是:
Start fitting the corpus...... all_vocab_size 88582 Fitting time: 9.10555362701416 s Start vectorizing the sentences....... Start padding...... Finished!可以看到,我們2.5w個文本,幾百萬詞,丟進去統計,效率還是挺高的,不到10秒就統計好了。
剛剛說了,獲取字典的時候,不會篩掉那些低頻詞,是所有詞的一個字典。但后面我們需要只保留那些高頻詞的一個字典,所以需要進行這樣一個操作,形成一個高頻詞字典:
import copy x = list(t.word_counts.items()) s = sorted(x,key=lambda p:p[1],reverse=True) small_word_index = copy.deepcopy(word_index) # 防止原來的字典也被改變了 print("Removing less freq words from word-index dict...") for item in s[20000:]:small_word_index.pop(item[0]) print("Finished!") print(len(small_word_index)) print(len(word_index))輸出:
Removing less freq words from word-index dict... Finished! 20000 88582詞向量的導入:
import gensim model_file = '../big_things/w2v/GoogleNews-vectors-negative300.bin' print("Loading word2vec model......") wv_model = gensim.models.KeyedVectors.load_word2vec_format(model_file,binary=True)這里采用Google發布的使用GoogleNews進行訓練的一個300維word2vec詞向量。這個讀者可以自行去網上下載。如果無法下載,可以到公眾號留言申請。
現在,我們需要把這個詞向量,跟我們本任務中的詞匯的index對應起來,也就是構建一個embedding matrix這樣就可以通過index找到對應的詞向量了。方法也很簡單:
先隨機初始化一個embedding matrix,這里需要注意的是,我們的詞匯量vocab_size雖然是20000,但是訓練的時候還是會碰到不少詞不在詞匯表里,也在詞向量也查不到,那這些詞怎么處理呢?我們就需要單獨給這些未知詞(UNK)一個index,在keras的文本預處理中,會默認保留index=0給這些未知詞。
embedding_matrix = np.random.uniform(size=(vocab_size+1,300)) # +1是要留一個給index=0 print("Transfering to the embedding matrix......") # sorted_small_index = sorted(list(small_word_index.items()),key=lambda x:x[1]) for word,index in small_word_index.items():try:word_vector = wv_model[word]embedding_matrix[index] = word_vectorexcept:print("Word: [",word,"] not in wvmodel! Use random embedding instead.") print("Finished!") print("Embedding matrix shape:n",embedding_matrix.shape)通過上面的操作,所有的index都對應上了詞向量,那些不在word2vec中的詞和index=0的詞,詞向量就是隨機初始化的值。
3.劃分訓練集和測試集
劃分訓練集和測試集,當然使用經典的sklearn的train_test_split了。
廢話少說,直接上代碼:
from sklearn.model_selection import train_test_split np.random.seed = 1 random_indexs = np.random.permutation(len(pad_X)) X = pad_X[random_indexs] Y = Y_orig[random_indexs] print(Y[:50]) X_train, X_test, y_train, y_test = train_test_split(X,Y,test_size=0.2) print("X_train:",X_train.shape) print("y_train:",y_train.shape) print("X_test:",X_test.shape) print("y_test:",y_test.shape) print(list(y_train).count(1)) print(list(y_train).count(0))輸出:
[0 0 1 0 0 1 0 0 0 1 0 1 0 0 0 1 1 1 0 1 1 0 1 0 1 1 0 0 0 0 0 0 0 0 1 0 11 0 1 0 1 1 0 1 0 0 1 1 0] X_train: (20000, 200) y_train: (20000,) X_test: (5000, 200) y_test: (5000,) 9982 10018訓練樣本2w,測試樣本5k.
唯一值得注意的一點就是,由于前面我們加載數據集的時候,正樣本和負樣本都聚在一塊,所以我們在這里要把他們隨機打亂一下,用的就是numpy的random.permutation方法。這些都是慣用伎倆了。
恭喜!您已閱讀本文80%的內容!二、搭建模型跑起來
做完了數據的預處理,后面的東西,就都是小菜一碟了。那么多框架是干嘛的?就是為了讓你用盡可能少的代碼把那些無聊的事情給做了!Keras尤其如此。
1.模型的結構設計
處理NLP問題,最常用的模型的就是RNN系列,LSTM和GRU隨便用。然后,一般還會在前面加一個embedding層。
之前我一直以為embedding層就是把預訓練好的詞向量加進去,實際上不是。即使沒有訓練好的詞向量,我們也可以使用embedding層。因為我們可以用我們的訓練數據,來訓練出詞的embedding,只不過這個embedding不同于word2vec的那種表達詞的含義的embedding,更多的是針對特定場景下的一個embedding。(不知道這樣說有沒有說清楚...)
所以,我們直接配置一個embedding層,不提供詞向量都可以訓練。如果提供了詞向量,這樣可以加速我們的訓練,相當于我們已經有一個訓練好的參數,提供給了模型,模型無非就需要接著改一改即可,而不是從一個隨機的狀態來慢慢訓練。
2. 模型的搭建
Talk is cheap, the code below is also cheap:
import keras from keras.models import Sequential,Model from keras.layers import Input,Dense,GRU,LSTM,Activation,Dropout,Embedding from keras.layers import Multiply,Concatenate,Dotinputs = Input(shape=(maxlen,)) use_pretrained_wv = True if use_pretrained_wv:wv = Embedding(VOCAB_SIZE+1,wv_dim,input_length=MAXLEN,weights=[embedding_matrix]) (inputs) else:wv = Embedding(VOCAB_SIZE+1,wv_dim,input_length=MAXLEN)(inputs)h = LSTM(128)(wv) y = Dense(1,activation='sigmoid')(h) m = Model(input=inputs,output=y) m.summary()m.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy']) m.fit(X_train,y_train,batch_size=32,epochs=3,validation_split=0.15)從上面的代碼可以知道,想要把預訓練的word2vec詞向量加入到模型中,就是把詞向量作為embedding層的參數(weights),具體我們需要先構建一個embedding matrix,這個我們在前面已經構建好了,然后傳進embedding層即可。
運行!輸出:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_7 (InputLayer) (None, 200) 0 _________________________________________________________________ embedding_7 (Embedding) (None, 200, 128) 2560128 _________________________________________________________________ lstm_7 (LSTM) (None, 128) 131584 _________________________________________________________________ dense_7 (Dense) (None, 1) 129 ================================================================= Total params: 2,691,841 Trainable params: 2,691,841 Non-trainable params: 0 _________________________________________________________________ Train on 17000 samples, validate on 3000 samples Epoch 1/3 17000/17000 [==============================] - 178s 10ms/step - loss: 0.6711 - acc: 0.5692 - val_loss: 0.6701 - val_acc: 0.5697 Epoch 2/3 17000/17000 [==============================] - 168s 10ms/step - loss: 0.5964 - acc: 0.6479 - val_loss: 0.5072 - val_acc: 0.7940 Epoch 3/3 17000/17000 [==============================] - 169s 10ms/step - loss: 0.5104 - acc: 0.7171 - val_loss: 0.4976 - val_acc: 0.7943可以發現,參數的大部分,都是embedding層的參數。所以,讀者可以嘗試一下將詞向量參數固定,可以發現訓練速度會快得多。但是效果可能會略差一些。
建議讀者對比一下:
①不使用word2vec作為embedding的參數
②使用word2vec作為embedding的參數并固定參數
③使用word2vec作為embedding的參數并繼續fine-tune
相信會有一些有意思的發現。
但是你可能沒時間(~~多半是懶!~~),所以這里我也告訴大家我的實驗結果:
①效果最差,時間最長
②效果最好,時間較長
③效果中等,時間最快
本文帶著讀者詳細的了解了使用keras進行文本預處理,如何將詞向量加入到訓練模型中提升性能,動手的讀者更可以體會到不同詞向量使用方法的差別。
這里,我們差不多直觀上感受到了NLP是啥感覺,后面的文章,會主要探討一下Attention機制在這個基礎上的應用,然后我們還會嘗試使用CNN來做一下同樣的任務,看看效果如何。相信我們會有新的發現!
如果喜歡我的教程,歡迎關注我的專欄:
【 DeepLearning.ai學習筆記】
和我一起一步步學習深度學習。
歡迎來微信公眾號 SimpleAI 踩踩喲ヾ(????)ノ"~專欄文章目錄:
Hello NLP(1)——詞向量Why&How
【DL筆記1】Logistic回歸:最基礎的神經網絡
【DL筆記2】神經網絡編程原則&Logistic Regression的算法解析
【DL筆記3】一步步用python實現Logistic回歸
【DL筆記4】神經網絡詳解,正向傳播和反向傳播
【DL筆記5】TensorFlow搭建神經網絡:手寫數字識別
【DL筆記6】從此明白了卷積神經網絡(CNN)
【DL筆記7】他山之玉——窺探CNN經典模型
【DL筆記8】如果你愿意一層一層剝開CNN的心
【DL筆記9】搭建CNN哪家強?TF,Keras誰在行?
【DL碎片1】神經網絡參數初始化的學問
【DL碎片2】神經網絡中的優化算法
【DL碎片3】神經網絡中的激活函數及其對比
【DL碎片4】深度學習中的的超參數調節
【DL碎片5】深度學習中的正則化Regularization
總結
以上是生活随笔為你收集整理的wordcloud里面设置mask加载不出来词频_一条龙搞定情感分析:文本预处理、加载词向量、搭建RNN的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何解决 IDEA 占用大量 CPU 导
- 下一篇: creo配置文件config选项详解_5