新聞中心
本文是我之前寫過的一篇基于推特?cái)?shù)據(jù)進(jìn)行情感分析的文章(https://ahmedbesbes.com/sentiment-analysis-on-twitter-using-word2vec-and-keras.html)的延伸內(nèi)容。那時(shí)我建立了一個(gè)簡單的模型:基于 keras 訓(xùn)練的兩層前饋神經(jīng)網(wǎng)絡(luò)。用組成推文的詞嵌入的加權(quán)平均值作為文檔向量來表示輸入推文。

南明ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
我用的嵌入是用 gensim 基于語料庫從頭訓(xùn)練出來的 word2vec 模型。該是一個(gè)二分類任務(wù),準(zhǔn)確率能達(dá)到 79%。
本文目標(biāo)在于探索其他在相同數(shù)據(jù)集上訓(xùn)練出來的 NLP 模型,然后在給定的測試集上對(duì)這些模型的性能進(jìn)行評(píng)估。
我們將通過不同的模型(從依賴于詞袋表征的簡單模型到部署了卷積/循環(huán)網(wǎng)絡(luò)的復(fù)雜模型)了解能否得到高于 79% 的準(zhǔn)確率!
首先,將從簡單的模型開始,逐步增加模型的復(fù)雜度。這項(xiàng)工作是為了說明簡單的模型也能很有效。
我會(huì)進(jìn)行這些嘗試:
- 用詞級(jí)的 ngram 做 logistic 回歸
- 用字符級(jí)的 ngram 做 logistic 回歸
- 用詞級(jí)的 ngram 和字符級(jí)的 ngram 做 Logistic 回歸
- 在沒有對(duì)詞嵌入進(jìn)行預(yù)訓(xùn)練的情況下訓(xùn)練循環(huán)神經(jīng)網(wǎng)絡(luò)(雙向 GRU)
- 用 GloVe 對(duì)詞嵌入進(jìn)行預(yù)訓(xùn)練,然后訓(xùn)練循環(huán)神經(jīng)網(wǎng)絡(luò)
- 多通道卷積神經(jīng)網(wǎng)絡(luò)
- RNN(雙向 GRU)+ CNN 模型
文末附有這些 NLP 技術(shù)的樣板代碼。這些代碼可以幫助你開啟自己的 NLP 項(xiàng)目并獲得最優(yōu)結(jié)果(這些模型中有一些非常強(qiáng)大)。
我們還可以提供一個(gè)綜合基準(zhǔn),我們可以利用該基準(zhǔn)分辨哪個(gè)模型最適合預(yù)測推文中的情緒。
在相關(guān)的 GitHub 庫中還有不同的模型、這些模型的預(yù)測結(jié)果以及測試集。你可以自己嘗試并得到可信的結(jié)果。
- import os
- import re
- import warnings
- warnings.simplefilter("ignore", UserWarning)
- from matplotlib import pyplot as plt
- %matplotlib inline
- import pandas as pd
- pd.options.mode.chained_assignment = None
- import numpy as np
- from string import punctuation
- from nltk.tokenize import word_tokenize
- from sklearn.model_selection import train_test_split
- from sklearn.feature_extraction.text import TfidfVectorizer
- from sklearn.linear_model import LogisticRegression
- from sklearn.metrics import accuracy_score, auc, roc_auc_score
- from sklearn.externals import joblib
- import scipy
- from scipy.sparse import hstack
一、數(shù)據(jù)預(yù)處理
你可以從該鏈接(http://thinknook.com/twitter-sentiment-analysis-training-corpus-dataset-2012-09-22/)下載數(shù)據(jù)集。
加載數(shù)據(jù)并提取所需變量(情感及情感文本)。
該數(shù)據(jù)集包含 1,578,614 個(gè)分好類的推文,每一行都用 1(積極情緒)和 0(消極情緒)進(jìn)行了標(biāo)記。
作者建議用 1/10 的數(shù)據(jù)進(jìn)行測試,其余數(shù)據(jù)用于訓(xùn)練。
- data = pd.read_csv('./data/tweets.csv', encoding='latin1', usecols=['Sentiment', 'SentimentText'])
- data.columns = ['sentiment', 'text']
- datadata = data.sample(frac=1, random_state=42)
- print(data.shape)
- (1578614, 2)
- for row in data.head(10).iterrows():
- print(row[1]['sentiment'], row[1]['text'])
- 1 http://www.popsugar.com/2999655 keep voting for robert pattinson in the popsugar100 as well!!
- 1 @GamrothTaylor I am starting to worry about you, only I have Navy Seal type sleep hours.
- 0 sunburned...no sunbaked! ow. it hurts to sit.
- 1 Celebrating my 50th birthday by doing exactly the same as I do every other day - working on our websites. It's just another day.
- 1 Leah and Aiden Gosselin are the cutest kids on the face of the Earth
- 1 @MissHell23 Oh. I didn't even notice.
- 0 WTF is wrong with me?!!! I'm completely miserable. I need to snap out of this
- 0 Was having the best time in the gym until I got to the car and had messages waiting for me... back to the down stage!
- 1 @JENTSYY oh what happened??
- 0 @catawu Ghod forbid he should feel responsible for anything!
推文數(shù)據(jù)中存在很多噪聲,我們刪除了推文中的網(wǎng)址、主題標(biāo)簽和用戶提及來清理數(shù)據(jù)。
- def tokenize(tweet):
- tweet = re.sub(r'http\S+', '', tweet)
- tweet = re.sub(r"#(\w+)", '', tweet)
- tweet = re.sub(r"@(\w+)", '', tweet)
- tweet = re.sub(r'[^\w\s]', '', tweet)
- tweettweet = tweet.strip().lower()
- tokens = word_tokenize(tweet)
- return tokens
將清理好的數(shù)據(jù)保存在硬盤上。
- data['tokens'] = data.text.progress_map(tokenize)
- data['cleaned_text'] = data['tokens'].map(lambda tokens: ' '.join(tokens))
- data[['sentiment', 'cleaned_text']].to_csv('./data/cleaned_text.csv')
- data = pd.read_csv('./data/cleaned_text.csv')
- print(data.shape)
- (1575026, 2)
- data.head()
既然數(shù)據(jù)集已經(jīng)清理干凈了,就可以準(zhǔn)備分割訓(xùn)練集和測試集來建立模型了。
本文數(shù)據(jù)都是用這種方式分割的。
- x_train, x_test, y_train, y_test = train_test_split(data['cleaned_text'],
- data['sentiment'],
- test_size=0.1,
- random_state=42,
- stratify=data['sentiment'])
- print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)
- (1417523,) (157503,) (1417523,) (157503,)
將測試集標(biāo)簽存儲(chǔ)在硬盤上以便后續(xù)使用。
- pd.DataFrame(y_test).to_csv('./predictions/y_true.csv', index=False, encoding='utf-8')
接下來就可以應(yīng)用機(jī)器學(xué)習(xí)方法了。
1. 基于詞級(jí) ngram 的詞袋模型
那么,什么是 n-gram 呢?
如圖所示,ngram 是將可在源文本中找到的長度為 n 的相鄰詞的所有組合。
我們的模型將以 unigrams(n=1)和 bigrams(n=2)為特征。
用矩陣表示數(shù)據(jù)集,矩陣的每一行表示一條推文,每一列表示從推文(已經(jīng)經(jīng)過分詞和清理)中提取的特征(一元模型或二元模型)。每個(gè)單元格是 tf-idf 分?jǐn)?shù)(也可以用更簡單的值,但 tf-idf 比較通用且效果較好)。我們將該矩陣稱為文檔-詞項(xiàng)矩陣。
略經(jīng)思考可知,擁有 150 萬推文的語料庫的一元模型和二元模型去重后的數(shù)量還是很大的。事實(shí)上,出于計(jì)算力的考慮,我們可將這個(gè)數(shù)設(shè)置為固定值。你可以通過交叉驗(yàn)證來確定這個(gè)值。
在向量化之后,語料庫如下圖所示:
- I like pizza a lot
假設(shè)使用上述特征讓模型對(duì)這句話進(jìn)行預(yù)測。
由于我們使用的是一元模型和二元模型后,因此模型提取出了下列特征:
- i, like, pizza, a, lot, i like, like pizza, pizza a, a lot
因此,句子變成了大小為 N(分詞總數(shù))的向量,這個(gè)向量中包含 0 和這些 ngram 的 tf-idf 分?jǐn)?shù)。所以接下來其實(shí)是要處理這個(gè)大而稀疏的向量。
一般而言,線性模型可以很好地處理大而稀疏的數(shù)據(jù)。此外,與其他模型相比,線性模型的訓(xùn)練速度也更快。
從過去的經(jīng)驗(yàn)可知,logistic 回歸可以在稀疏的 tf-idf 矩陣上良好地運(yùn)作。
- vectorizer_word = TfidfVectorizer(max_features=40000,
- min_df=5,
- max_df=0.5,
- analyzer='word',
- stop_words='english',
- ngram_range=(1, 2))
- vectorizer_word.fit(x_train, leave=False)
- tfidf_matrix_word_train = vectorizer_word.transform(x_train)
- tfidf_matrix_word_test = vectorizer_word.transform(x_test)
在為訓(xùn)練集和測試集生成了 tf-idf 矩陣后,就可以建立第一個(gè)模型并對(duì)其進(jìn)行測試。
tf-idf 矩陣是 logistic 回歸的特征。
- lr_word = LogisticRegression(solver='sag', verbose=2)
- lr_word.fit(tfidf_matrix_word_train, y_train)
一旦訓(xùn)練好模型后,就可以將其應(yīng)用于測試數(shù)據(jù)以獲得預(yù)測值。然后將這些值和模型一并存儲(chǔ)在硬盤上。
- joblib.dump(lr_word, './models/lr_word_ngram.pkl')
- y_pred_word = lr_word.predict(tfidf_matrix_word_test)
- pd.DataFrame(y_pred_word, columns=['y_pred']).to_csv('./predictions/lr_word_ngram.csv', index=False)
得到準(zhǔn)確率:
- y_pred_word = pd.read_csv('./predictions/lr_word_ngram.csv')
- print(accuracy_score(y_test, y_pred_word))
- 0.782042246814
第一個(gè)模型得到了 78.2% 的準(zhǔn)確率!真不賴。接下來了解一下第二個(gè)模型。
2. 基于字符級(jí) ngram 的詞袋模型
我們從未說過 ngram 僅為詞服務(wù),也可將其應(yīng)用于字符上。
如你所見,我們將對(duì)字符級(jí) ngram 使用與圖中一樣的代碼,現(xiàn)在直接來看 4-grams 建模。
基本上這意味著,像「I like this movie」這樣的句子會(huì)有下列特征:
- I, l, i, k, e, ..., I li, lik, like, ..., this, ... , is m, s mo, movi, ...
字符級(jí) ngram 很有效,在語言建模任務(wù)中,甚至可以比分詞表現(xiàn)得更好。像垃圾郵件過濾或自然語言識(shí)別這樣的任務(wù)就高度依賴字符級(jí) ngram。
與之前學(xué)習(xí)單詞組合的模型不同,該模型學(xué)習(xí)的是字母組合,這樣就可以處理單詞的形態(tài)構(gòu)成。
基于字符的表征的一個(gè)優(yōu)勢是可以更好地解決單詞拼寫錯(cuò)誤的問題。
我們來運(yùn)行同樣的流程:
- vectorizer_char = TfidfVectorizer(max_features=40000,
- min_df=5,
- max_df=0.5,
- analyzer='char',
- ngram_range=(1, 4))
- vectorizer_char.fit(tqdm_notebook(x_train, leave=False));
- tfidf_matrix_char_train = vectorizer_char.transform(x_train)
- tfidf_matrix_char_test = vectorizer_char.transform(x_test)
- lr_char = LogisticRegression(solver='sag', verbose=2)
- lr_char.fit(tfidf_matrix_char_train, y_train)
- y_pred_char = lr_char.predict(tfidf_matrix_char_test)
- joblib.dump(lr_char, './models/lr_char_ngram.pkl')
- pd.DataFrame(y_pred_char, columns=['y_pred']).to_csv('./predictions/lr_char_ngram.csv', index=False)
- y_pred_char = pd.read_csv('./predictions/lr_char_ngram.csv')
- print(accuracy_score(y_test, y_pred_char))
- 0.80420055491
80.4% 的準(zhǔn)確率!字符級(jí) ngram 模型的性能要比詞級(jí)的 ngram 更好。
3. 基于詞級(jí) ngram 和字符級(jí) ngram 的詞袋模型
與詞級(jí) ngram 的特征相比,字符級(jí) ngram 特征似乎提供了更好的準(zhǔn)確率。那么將字符級(jí) ngram 和詞級(jí) ngram 結(jié)合效果又怎么樣呢?
我們將兩個(gè) tf-idf 矩陣連接在一起,建立一個(gè)新的、混合 tf-idf 矩陣。該模型有助于學(xué)習(xí)單詞形態(tài)結(jié)構(gòu)以及與這個(gè)單詞大概率相鄰單詞的形態(tài)結(jié)構(gòu)。
將這些屬性結(jié)合在一起。
- tfidf_matrix_word_char_train = hstack((tfidf_matrix_word_train, tfidf_matrix_char_train))
- tfidf_matrix_word_char_test = hstack((tfidf_matrix_word_test, tfidf_matrix_char_test))
- lr_word_char = LogisticRegression(solver='sag', verbose=2)
- lr_word_char.fit(tfidf_matrix_word_char_train, y_train)
- y_pred_word_char = lr_word_char.predict(tfidf_matrix_word_char_test)
- joblib.dump(lr_word_char, './models/lr_word_char_ngram.pkl')
- pd.DataFrame(y_pred_word_char, columns=['y_pred']).to_csv('./predictions/lr_word_char_ngram.csv', index=False)
- y_pred_word_char = pd.read_csv('./predictions/lr_word_char_ngram.csv')
- print(accuracy_score(y_test, y_pred_word_char))
- 0.81423845895
得到了 81.4% 的準(zhǔn)確率。該模型只加了一個(gè)整體單元,但結(jié)果比之前的兩個(gè)都要好。
關(guān)于詞袋模型
- 優(yōu)點(diǎn):考慮到其簡單的特性,詞袋模型已經(jīng)很強(qiáng)大了,它們訓(xùn)練速度快,且易于理解。
- 缺點(diǎn):即使 ngram 帶有一些單詞間的語境,但詞袋模型無法建模序列中單詞間的長期依賴關(guān)系。
現(xiàn)在要用到深度學(xué)習(xí)模型了。深度學(xué)習(xí)模型的表現(xiàn)優(yōu)于詞袋模型是因?yàn)樯疃葘W(xué)習(xí)模型能夠捕捉到句子中單詞間的順序依賴關(guān)系。這可能要?dú)w功于循環(huán)神經(jīng)網(wǎng)絡(luò)這一特殊神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)的出現(xiàn)了。
本文并未涵蓋 RNN 的理論基礎(chǔ),但該鏈接(http://colah.github.io/posts/2015-08-Understanding-LSTMs/)中的內(nèi)容值得一讀。這篇文章來源于 Cristopher Olah 的博客,詳細(xì)敘述了一種特殊的 RNN 模型:長短期記憶網(wǎng)絡(luò)(LSTM)。
在開始之前,要先設(shè)置一個(gè)深度學(xué)習(xí)專用的環(huán)境,以便在 TensorFlow 上使用 Keras。誠實(shí)地講,我試著在個(gè)人筆記本上運(yùn)行這些代碼,但考慮到數(shù)據(jù)集的大小和 RNN 架構(gòu)的復(fù)雜程度,這是很不實(shí)際的。還有一個(gè)很好的選擇是 AWS。我一般在 EC2 p2.xlarge 實(shí)例上用深度學(xué)習(xí) AMI(https://aws.amazon.com/marketplace/pp/B077GCH38C?qid=1527197041958&sr=0-1&ref_=srh_res_product_title)。亞馬遜 AMI 是安裝了所有包(TensorFlow、PyTorch 和 Keras 等)的預(yù)先配置過的 VM 圖。強(qiáng)烈推薦大家使用!
- from keras.preprocessing.text import Tokenizer
- from keras.preprocessing.text import text_to_word_sequence
- from keras.preprocessing.sequence import pad_sequences
- from keras.models import Model
- from keras.models import Sequential
- from keras.layers import Input, Dense, Embedding, Conv1D, Conv2D, MaxPooling1D, MaxPool2D
- from keras.layers import Reshape, Flatten, Dropout, Concatenate
- from keras.layers import SpatialDropout1D, concatenate
- from keras.layers import GRU, Bidirectional, GlobalAveragePooling1D, GlobalMaxPooling1D
- from keras.callbacks import Callback
- from keras.optimizers import Adam
- from keras.callbacks import ModelCheckpoint, EarlyStopping
- from keras.models import load_model
- from keras.utils.vis_utils import plot_model
4. 沒有預(yù)訓(xùn)練詞嵌入的循環(huán)神經(jīng)網(wǎng)絡(luò)
RNN 可能看起來很可怕。盡管它們因?yàn)閺?fù)雜而難以理解,但非常有趣。RNN 模型封裝了一個(gè)非常漂亮的設(shè)計(jì),以克服傳統(tǒng)神經(jīng)網(wǎng)絡(luò)在處理序列數(shù)據(jù)(文本、時(shí)間序列、視頻、DNA 序列等)時(shí)的短板。
RNN 是一系列神經(jīng)網(wǎng)絡(luò)的模塊,它們彼此連接像鎖鏈一樣。每一個(gè)都將消息向后傳遞。強(qiáng)烈推薦大家從 Colah 的博客中深入了解它的內(nèi)部機(jī)制,下面的圖就來源于此。
我們要處理的序列類型是文本數(shù)據(jù)。對(duì)意義而言,單詞順序很重要。RNN 考慮到了這一點(diǎn),它可以捕捉長期依賴關(guān)系。
為了在文本數(shù)據(jù)上使用 Keras,我們首先要對(duì)數(shù)據(jù)進(jìn)行預(yù)處理??梢杂?Keras 的 Tokenizer 類。該對(duì)象用 num_words 作為參數(shù),num_words 是根據(jù)詞頻進(jìn)行分詞后保留下來的最大詞數(shù)。
- MAX_NB_WORDS = 80000
- tokenizer = Tokenizer(num_words=MAX_NB_WORDS)
- tokenizer.fit_on_texts(data['cleaned_text'])
當(dāng)分詞器適用于數(shù)據(jù)時(shí),我們就可以用分詞器將文本字符級(jí) ngram 轉(zhuǎn)換為數(shù)字序列。
這些數(shù)字表示每個(gè)單詞在字典中的位置(將其視為映射)。
如下例所示:
- x_train[15]
- 'breakfast time happy time'
這里說明了分詞器是如何將其轉(zhuǎn)換為數(shù)字序列的。
- tokenizer.texts_to_sequences([x_train[15]])
- [[530, 50, 119, 50]]
接下來在訓(xùn)練序列和測試序列中應(yīng)用該分詞器:
- train_sequences = tokenizer.texts_to_sequences(x_train)
- test_sequences = tokenizer.texts_to_sequences(x_test)
將推文映射到整數(shù)列表中。但是由于長度不同,還是沒法將它們?cè)诰仃囍卸询B在一起。還好 Keras 允許用 0 將序列填充至最大長度。我們將這個(gè)長度設(shè)置為 35(這是推文中的最大分詞數(shù))。
- MAX_LENGTH = 35
- padded_train_sequences = pad_sequences(train_sequences, maxlen=MAX_LENGTH)
- padded_test_sequences = pad_sequences(test_sequences, maxlen=MAX_LENGTH)
- padded_train_sequences
- array([[ 0, 0, 0, ..., 2383, 284, 9],
- [ 0, 0, 0, ..., 13, 30, 76],
- [ 0, 0, 0, ..., 19, 37, 45231],
- ...,
- [ 0, 0, 0, ..., 43, 502, 1653],
- [ 0, 0, 0, ..., 5, 1045, 890],
- [ 0, 0, 0, ..., 13748, 38750, 154]])
- padded_train_sequences.shape
- (1417523, 35)
現(xiàn)在就可以將數(shù)據(jù)傳入 RNN 了。
以下是我將使用的架構(gòu)的一些元素:
- 嵌入維度為 300。這意味著我們使用的 8 萬個(gè)單詞中的每一個(gè)都被映射至 300 維的密集(浮點(diǎn)數(shù))向量。該映射將在訓(xùn)練過程中進(jìn)行調(diào)整。
- 在嵌入層上應(yīng)用 spatial dropout 層以減少過擬合:按批次查看 35*300 的矩陣,隨機(jī)刪除每個(gè)矩陣中(設(shè)置為 0)的詞向量(行)。這有助于將注意力不集中在特定的詞語上,有利于模型的泛化。
- 雙向門控循環(huán)單元(GRU):這是循環(huán)網(wǎng)絡(luò)部分。這是 LSTM 架構(gòu)更快的變體。將其視為兩個(gè)循環(huán)網(wǎng)絡(luò)的組合,這樣就可以從兩個(gè)方向同時(shí)掃描文本序列:從左到右和從右到左。這使得網(wǎng)絡(luò)在閱讀給定單詞時(shí),可以結(jié)合之前和之后的內(nèi)容理解文本。GRU 中每個(gè)網(wǎng)絡(luò)塊的輸出 h_t 的維度即單元數(shù),將這個(gè)值設(shè)置為 100。由于用了雙向 GRU,因此每個(gè) RNN 塊的最終輸出都是 200 維的。
雙向 GRU 的輸出是有維度的(批尺寸、時(shí)間步和單元)。這意味著如果用的是經(jīng)典的 256 的批尺寸,維度將會(huì)是 (256, 35, 200)。
- 在每個(gè)批次上應(yīng)用的是全局平均池化,其中包含了每個(gè)時(shí)間步(即單詞)對(duì)應(yīng)的輸出向量的平均值。
- 我們應(yīng)用了相同的操作,只是用最大池化替代了平均池化。
- 將前兩個(gè)操作的輸出連接在了一起。
- def get_simple_rnn_model():
- embedding_dim = 300
- embedding_matrix = np.random.random((MAX_NB_WORDS, embedding_dim))
- inp = Input(shape=(MAX_LENGTH, ))
- x = Embedding(input_dim=MAX_NB_WORDS, output_dim=embedding_dim, input_length=MAX_LENGTH,
- weights=[embedding_matrix], trainable=True)(inp)
- x = SpatialDropout1D(0.3)(x)
- x = Bidirectional(GRU(100, return_sequences=True))(x)
- avg_pool = GlobalAveragePooling1D()(x)
- max_pool = GlobalMaxPooling1D()(x)
- conc = concatenate([avg_pool, max_pool])
- outp = Dense(1, activation="sigmoid")(conc)
- model = Model(inpinputs=inp, outpoutputs=outp)
- model.compile(loss='binary_crossentropy',
- optimizer='adam',
- metrics=['accuracy'])
- return model
- rnn_simple_model = get_simple_rnn_model()
該模型的不同層如下所示:
- plot_model(rnn_simple_model,
- to_file='./images/article_5/rnn_simple_model.png',
- show_shapes=True,
- show_layer_names=True)
在訓(xùn)練期間使用了模型檢查點(diǎn)。這樣可以在每個(gè) epoch 的最后將最佳模型(可以用準(zhǔn)確率度量)自動(dòng)存儲(chǔ)(在硬盤上)。
- filepath="./models/rnn_no_embeddings/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5"
- checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max')
- batch_size = 256
- epochs = 2
- history = rnn_simple_model.fit(x=padded_train_sequences,
- y=y_train,
- validation_data=(padded_test_sequences, y_test),
- batch_sizebatch_size=batch_size,
- callbacks=[checkpoint],
- epochsepochs=epochs,
- verbose=1)
- best_rnn_simple_model = load_model('./models/rnn_no_embeddings/weights-improvement-01-0.8262.hdf5')
- y_pred_rnn_simple = best_rnn_simple_model.predict(padded_test_sequences, verbose=1, batch_size=2048)
- y_pred_rnn_simple = pd.DataFrame(y_pred_rnn_simple, columns=['prediction'])
- y_pred_rnn_simple['prediction'] = y_pred_rnn_simple['prediction'].map(lambda p: 1 if p >= 0.5 else 0)
- y_pred_rnn_simple.to_csv('./predictions/y_pred_rnn_simple.csv', index=False)
- y_pred_rnn_simple = pd.read_csv('./predictions/y_pred_rnn_simple.csv')
- print(accuracy_score(y_test, y_pred_rnn_simple))
- 0.826219183127
準(zhǔn)確率達(dá)到了 82.6%!這真是很不錯(cuò)的結(jié)果了!現(xiàn)在的模型表現(xiàn)已經(jīng)比之前的詞袋模型更好了,因?yàn)槲覀儗⑽谋镜男蛄行再|(zhì)考慮在內(nèi)了。
還能做得更好嗎?
5. 用 GloVe 預(yù)訓(xùn)練詞嵌入的循環(huán)神經(jīng)網(wǎng)絡(luò)
在最后一個(gè)模型中,嵌入矩陣被隨機(jī)初始化了。那么如果用預(yù)訓(xùn)練過的詞嵌入對(duì)其進(jìn)行初始化又當(dāng)如何呢?舉個(gè)例子:假設(shè)在語料庫中有「pizza」這個(gè)詞。遵循之前的架構(gòu)對(duì)其進(jìn)行初始化后,可以得到一個(gè) 300 維的隨機(jī)浮點(diǎn)值向量。這當(dāng)然是很好的。這很好實(shí)現(xiàn),而且這個(gè)嵌入可以在訓(xùn)練過程中進(jìn)行調(diào)整。但你還可以使用在很大的語料庫上訓(xùn)練出來的另一個(gè)模型,為「pizza」生成詞嵌入來代替隨機(jī)選擇的向量。這是一種特殊的遷移學(xué)習(xí)。
使用來自外部嵌入的知識(shí)可以提高 RNN 的精度,因?yàn)樗狭诉@個(gè)單詞的相關(guān)新信息(詞匯和語義),而這些信息是基于大規(guī)模數(shù)據(jù)語料庫訓(xùn)練和提煉出來的。
我們使用的預(yù)訓(xùn)練嵌入是 GloVe。
官方描述是這樣的:GloVe 是一種獲取單詞向量表征的無監(jiān)督學(xué)習(xí)算法。該算法的訓(xùn)練基于語料庫全局詞-詞共現(xiàn)數(shù)據(jù),得到的表征展示出詞向量空間有趣的線性子結(jié)構(gòu)。
本文使用的 GloVe 嵌入的訓(xùn)練數(shù)據(jù)是數(shù)據(jù)量很大的網(wǎng)絡(luò)抓取,包括:
- 8400 億個(gè)分詞;
- 220 萬詞。
下載壓縮文件要 2.03GB。請(qǐng)注意,該文件無法輕松地加載在標(biāo)準(zhǔn)筆記本電腦上。
GloVe 嵌入有 300 維。
GloVe 嵌入來自原始文本數(shù)據(jù),在該數(shù)據(jù)中每一行都包含一個(gè)單詞和 300 個(gè)浮點(diǎn)數(shù)(對(duì)應(yīng)嵌入)。所以首先要將這種結(jié)構(gòu)轉(zhuǎn)換為 Python 字典。
- def get_coefs(word, *arr):
- try:
- return word, np.asarray(arr, dtype='float32')
- except:
- return None, None
- embeddings_index = dict(get_coefs(*o.strip().split()) for o in tqdm_notebook(open('./embeddings/glove.840B.300d.txt')))
- embed_size=300
- for k in tqdm_notebook(list(embeddings_index.keys())):
- v = embeddings_index[k]
- try:
- if v.shape != (embed_size, ):
- embeddings_index.pop(k)
- except:
- pass
- embeddings_index.pop(None)
一旦創(chuàng)建了嵌入索引,我們就可以提取所有的向量,將其堆疊在一起并計(jì)算它們的平均值和標(biāo)準(zhǔn)差。
- values = list(embeddings_index.values())
- all_embs = np.stack(values)
- emb_mean, emb_std = all_embs.mean(), all_embs.std()
現(xiàn)在生成了嵌入矩陣。按照 mean=emb_mean 和 std=emb_std 的正態(tài)分布對(duì)矩陣進(jìn)行初始化。遍歷語料庫中的 80000 個(gè)單詞。對(duì)每一個(gè)單詞而言,如果這個(gè)單詞存在于 GloVe 中,我們就可以得到這個(gè)單詞的嵌入,如果不存在那就略過。
- word_index = tokenizer.word_index
- nb_words = MAX_NB_WORDS
- embedding_matrix = np.random.normal(emb_mean, emb_std, (nb_words, embed_size))
- oov = 0
- for word, i in tqdm_notebook(word_index.items()):
- if i >= MAX_NB_WORDS: continue
- embedding_vector = embeddings_index.get(word)
- if embedding_vector is not None:
- embedding_matrix[i] = embedding_vector
- else:
- oov += 1
- print(oov)
- def get_rnn_model_with_glove_embeddings():
- embedding_dim = 300
- inp = Input(shape=(MAX_LENGTH, ))
- x = Embedding(MAX_NB_WORDS, embedding_dim, weights=[embedding_matrix], input_length=MAX_LENGTH, trainable=True)(inp)
- x = SpatialDropout1D(0.3)(x)
- x = Bidirectional(GRU(100, return_sequences=True))(x)
- avg_pool = GlobalAveragePooling1D()(x)
- max_pool = GlobalMaxPooling1D()(x)
- conc = concatenate([avg_pool, max_pool])
- outp = Dense(1, activation="sigmoid")(conc)
- model = Model(inpinputs=inp, outpoutputs=outp)
- model.compile(loss='binary_crossentropy',
- optimizer='adam',
- metrics=['accuracy'])
- return model
- rnn_model_with_embeddings = get_rnn_model_with_glove_embeddings()
- filepath="./models/rnn_with_embeddings/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5"
- checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max')
- batch_size = 256
- epochs = 4
- history = rnn_model_with_embeddings.fit(x=padded_train_sequences,
- y=y_train,
- validation_data=(padded_test_sequences, y_test),
- batch_sizebatch_size=batch_size,
- callbacks=[checkpoint],
- epochsepochs=epochs,
- verbose=1)
- best_rnn_model_with_glove_embeddings = load_model('./models/rnn_with_embeddings/weights-improvement-03-0.8372.hdf5')
- y_pred_rnn_with_glove_embeddings = best_rnn_model_with_glove_embeddings.predict(
- padded_test_sequences, verbose=1, batch_size=2048)
- y_pred_rnn_with_glove_embeddings = pd.DataFrame(y_pred_rnn_with_glove_embeddings, columns=['prediction'])
- y_pred_rnn_with_glove_embeddings['prediction'] = y_pred_rnn_with_glove_embeddings['prediction'].map(lambda p:
- 1 if p >= 0.5 else 0)
- y_pred_rnn_with_glove_embeddings.to_csv('./predictions/y_pred_rnn_with_glove_embeddings.csv', index=False)
- y_pred_rnn_with_glove_embeddings = pd.read_csv('./predictions/y_pred_rnn_with_glove_embeddings.csv')
- print(accuracy_score(y_test, y_pred_rnn_with_glove_embeddings))
- 0.837203100893
準(zhǔn)確率達(dá)到了 83.7%!來自外部詞嵌入的遷移學(xué)習(xí)起了作用!本教程剩余部分都會(huì)在嵌入矩陣中使用 GloVe 嵌入。
6. 多通道卷積神經(jīng)網(wǎng)絡(luò)
這一部分實(shí)驗(yàn)了我曾了解過的卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)(http://www.wildml.com/2015/11/understanding-convolutional-neural-networks-for-nlp/)。CNN 常用于計(jì)算機(jī)視覺任務(wù)。但最近我試著將其應(yīng)用于 NLP 任務(wù),而結(jié)果也希望滿滿。
簡要了解一下當(dāng)在文本數(shù)據(jù)上使用卷積網(wǎng)絡(luò)時(shí)會(huì)發(fā)生什么。為了解釋這一點(diǎn),我從 wildm.com(一個(gè)很好的博客)中找到了這張非常有名的圖(如下所示)。
了解一下使用的例子:I like this movie very much!(7 個(gè)分詞)
- 每個(gè)單詞的嵌入維度是 5。因此,可以用一個(gè)維度為 (7,5 的矩陣表示這句話。你可以將其視為一張「圖」(數(shù)字或浮點(diǎn)數(shù)的矩陣)。
- 6 個(gè)濾波器,大小為 (2, 5) (3, 5) 和 (4, 5) 的濾波器各兩個(gè)。這些濾波器應(yīng)用于該矩陣上,它們的特殊之處在于都不是方矩陣,但它們的寬度和嵌入矩陣的寬度相等。所以每個(gè)卷積的結(jié)果將是一個(gè)列向量。
- 卷積產(chǎn)生的每一列向量都使用了最大池化操作進(jìn)行下采樣。
- 將最大池化操作的結(jié)果連接至將要傳遞給 softmax 函數(shù)進(jìn)行分類的最終向量。
二、背后的原理是什么?
檢測到特殊模式會(huì)激活每一次卷積的結(jié)果。通過改變卷積核的大小和連接它們的輸出,你可以檢測多個(gè)尺寸(2 個(gè)、3 個(gè)或 5 個(gè)相鄰單詞)的模式。
模式可以是像是「我討厭」、「非常好」這樣的表達(dá)式(詞級(jí)的 ngram?),因此 CNN 可以在不考慮其位置的情況下從句子中分辨它們。
- def get_cnn_model():
- embedding_dim = 300
- filter_sizes = [2, 3, 5]
- num_filters = 256
- drop = 0.3
- inputs = Input(shape=(MAX_LENGTH,), dtype='int32')
- embedding = Embedding(input_dim=MAX_NB_WORDS,
- output_dim=embedding_dim,
- weights=[embedding_matrix],
- input_length=MAX_LENGTH,
- trainable=True)(inputs)
- reshape = Reshape((MAX_LENGTH, embedding_dim, 1))(embedding)
- conv_0 = Conv2D(num_filters,
- kernel_size=(filter_sizes[0], embedding_dim),
- padding='valid', kernel_initializer='normal',
- activation='relu')(reshape)
- conv_1 = Conv2D(num_filters,
- kernel_size=(filter_sizes[1], embedding_dim),
- padding='valid', kernel_initializer='normal',
- activation='relu')(reshape)
- conv_2 = Conv2D(num_filters,
- kernel_size=(filter_sizes[2], embedding_dim),
- padding='valid', kernel_initializer='normal',
- activation='relu')(reshape)
- maxpool_0 = MaxPool2D(pool_size=(MAX_LENGTH - filter_sizes[0] + 1, 1),
- &
分享標(biāo)題:CNN也能用于NLP任務(wù),一文簡述文本分類任務(wù)的7個(gè)模型
URL標(biāo)題:http://www.dlmjj.cn/article/cojjpch.html


咨詢
建站咨詢
