深度學習文本分類方法綜述
【導讀】本文是數據科學家Ahmed BESBES的一篇博文,主要內容是探索不同NLP模型在文本分類的性能,圍繞著文本分類任務,構建當前主流的七種不同模型:用詞ngrams進行邏輯回歸、用字元ngrams進行邏輯回歸、用詞和字元ngrams的邏輯回歸、沒有預先訓練embeddings的循環神經網路(雙向GRU)、基於GloVe預訓練embeddings的循環神經網路(雙向GRU)、多通道卷積神經網路、RNN(雙向GRU)+ CNN模型。最後並且附上作者自己的經驗總結,博文附完整詳細代碼,建議大家收藏學習!
作者 | Ahmed BESBES
編譯 | 專知
翻譯 | Desheng, Hujun
Overview and benchmark of traditional and deep learning models in text classification
本文是我之前用Twitter數據進行情感分析時寫的一篇文章的擴展。在那個時候,我探索了一個簡單的模型:一個在keras上訓練的兩層前饋神經網路【1】。 輸入推文被表示為由組成推文的單詞的embeddings進行加權平均得到的文檔向量。
我使用的是word2vec模型,我使用gensim從頭開始訓練語料庫。 該任務是一個二元分類,我能夠使用此設置達到79%的準確性。
本文的目標是探索在同一數據集上訓練的不同的NLP模型,然後在給定的測試集上測試它們各自的性能。
我們將討論不同的模型:從依賴詞袋錶示的簡單模型,到部署卷積/周期性網路的複雜模型:我們將看看我們的準確率能否超過79% !
我將從簡單的模型開始,逐步增加複雜性。我的目標是想要表明簡單的模型也能很好地工作。
所以我會試試這些:
用詞ngrams進行邏輯回歸
用字元ngrams進行邏輯回歸
用詞和字元ngrams的邏輯回歸
沒有預先訓練embeddings的循環神經網路(雙向GRU)
基於GloVe預訓練embeddings的循環神經網路(雙向GRU)
多通道卷積神經網路
RNN(雙向GRU)+ CNN模型
在這篇文章的最後,你將會獲得每種NLP技術的示例代碼。 它將幫助你進行你的NLP項目並獲得目前最好的效果(其中一些模型非常強大)。
我們還將提供一個綜合基準(benchmark),從中我們可以判斷哪種模型最適合預測推文所表達的情感。
在相關的git repo中,我將發布不同的模型、它們的預測結果以及測試集。你可以自己嘗試一下。
讓我們開始吧!
0 - 數據預處理
數據集可以從這個鏈接下載。
http://thinknook.com/twitter-sentiment-analysis-training-corpus-dataset-2012-09-22/
它包含1578614個已經標記類別的推文,每行標記為1或0, 1表示積極情緒,0表示消極情緒。
作者建議使用1/10來測試演算法,剩下的則用於訓練。
data = pd.read_csv("./data/tweets.csv",encoding="latin1",usecols=["Sentiment",
"SentimentText"])
data.columns = ["sentiment","text"]
data = data.sample(frac=1,random_state=42)
print(data.shape)
(1578614, 2)
forrowindata.head(10).iterrows():
print(row[1]["sentiment"],row[1]["text"])
1http://www.popsugar.com/2999655keep votingforrobert pattinson
inthe popsugar100aswell!!
1@GamrothTaylor I am starting to worry about you,only I have Navy
Seal type sleep hours.
sunburned...no sunbaked! ow. it hurts to sit.
1Celebrating my50th birthday by doing exactly the sameasI do
every other day - working on our websites. It"s just another day.
1LeahandAiden Gosselin are the cutest kids on the face of the Earth
1@MissHell23 Oh. I didn"t even notice.
WTFiswrongwithme?!!! I"m completely miserable. I need to snap
out of this
Was having the best timeinthe gym until I got to the carand
had messages waitingforme... back to the down stage!
1@JENTSYY oh what happened??
@catawu Ghod forbid he should feel responsibleforanything!
推文是有雜訊的,讓我們通過刪除url(網址)、hashtag(主題標籤)和user mentions(用戶提及)來清除它們。
deftokenize(tweet):
tweet = re.sub(r"httpS+","",tweet)
tweet = re.sub(r"#(w+)","",tweet)
tweet = re.sub(r"@(w+)","",tweet)
tweet = re.sub(r"[^ws]","",tweet)
tweet = tweet.strip().lower()
tokens = word_tokenize(tweet)
returntokens
一旦數據清理完畢,我們將其保存在磁碟上。
data["tokens"] = data.text.progress_map(tokenize)
data["cleaned_text"] = data["tokens"].map(lambdatokens:" ".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()
現在已經清理了數據集,讓我們準備一個train/test 分割來構建模型。
我們將在整個notebook中使用這種分割方式。
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, )
我將測試標籤保存在磁碟上供以後使用。
pd.DataFrame(y_test).to_csv("./predictions/y_true.csv",index=False,encoding="utf-8")
現在開始應用機器學習:
1 - 基於詞ngrams的詞袋模型
所以什麼是n-gram?
正如我們在圖中看到的那樣,n-gram是您可以在源文本中找到的長度為n的相鄰單詞的所有組合(在本例中為n)。
在我們的模型中,我們將使用unigrams(n = 1)和bigrams(n = 2)作為特徵。
因此數據集將被表示為一個矩陣,其中每行表示一條推文,每一列表示從文本中提取的特徵(單詞或雙字母)(在標記和清理之後)。 每個單元格將是tf-idf分數。 (我們也可以使用簡單的計數,但tf-idf更常用,通常效果更好)。 我們稱這個矩陣為文檔項矩陣。
正如你可以想像的那樣,150萬個推文語料庫中獨特的unigrams和bigrams的數量是巨大的。 在實踐中,出於計算原因,我們將此數字設置為固定值。 您可以使用交叉驗證來確定此值。
這是語料庫在矢量化之後的樣子:
I like pizza a lot
假設我們希望使用上述特徵將此句子提供給預測模型。鑒於我們使用的是unigrams和bigrams,該模型將提取以下特徵:
i, like, pizza, a, lot, i like, like pizza, pizza a, a lot
因此,這個句子將由一個大小為N的向量組成,其中包含許多0和tf-idf分數。你可以清楚地看到,我們要處理的是大而稀疏的向量。
處理大量稀疏數據時,線性模型通常表現相當好。 此外,他們比其他類型的模型(例如基於樹的模型)訓練的速度更快。
從過去的經驗中可知,邏輯回歸在稀疏tfidf矩陣的基礎上運行良好。
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)
在為訓練集和測試集生成tfidf矩陣之後,我們可以構建第一個模型並對其進行測試。tifidf矩陣是邏輯回歸的特徵。
lr_word = LogisticRegression(solver="sag",verbose=2)
lr_word.fit(tfidf_matrix_word_train,y_train)
一旦模型訓練完成,我們將其應用於測試數據以獲得預測結果。 然後我們將這些值以及模型保存在磁碟上。
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)
讓我們看看我們的準確度得分:
y_pred_word = pd.read_csv("./predictions/lr_word_ngram.csv")
print(accuracy_score(y_test,y_pred_word))
第一個模型的準確率為78.2%! 相當不錯。 我們轉到下一個模型。
2 -基於字元ngrams的詞袋模型
我們從來沒有說過ngram只能用在word上。我們也可以在字元層面上應用它們。
你看到了嗎?我們將把上面的代碼應用到字元 ngrams上,我們將會變成4-gram。
這基本上意味著像「I like this movie」這樣的句子將具有以下特徵:
I, l, i, k, e, ..., I li, lik, like, ..., this, ... , is m, s mo, movi, ...
字元ngram非常有效。它們甚至可以在建模語言任務時勝過單詞標記(tokens)。例如,垃圾郵件過濾器或母語識別就很依賴字元ngram。(這裡有兩個鏈接)
與以前學習單片語合的模型不同,該模型學習字母的組合,可以處理單詞的形態構成問題。
基於字元的表示的優點之一是更好地處理拼寫錯誤的單詞。
讓我們運行相同的pipeline:
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%!字元-ngrams比詞-ngrams更好。
3 - 基於詞和字元ngrams的詞袋模型
字元ngram特徵似乎可以比詞ngrams提供更好的準確性。 但是,這兩者的結合是怎麼樣的:詞 + 字元 ngrams?
我們連接我們生成的兩個tfidf矩陣並構建一個新的混合tfidf矩陣。
這個模型將幫助我們了解一個詞的特徵、它可能的鄰居以及它的形態結構。
這些屬性結合在一起:
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%的準確性。
在我們繼續討論之前,我們可以對詞袋模型說些什麼?
優點:考慮到它們的簡單性,它們的功能會非常強大,而且速度很快,而且易於理解。
缺點:儘管ngrams考慮了詞與詞之間的聯繫,但詞袋模型在建模一個序列中單詞之間的長期依賴關係時失敗了。
現在我們將深入深度學習模型。 深度學習勝過詞袋模型的原因在於它能夠捕捉句子中單詞之間的順序依賴性。 這得益於稱為遞歸神經網路的特殊神經網路架構的發明。
我不會介紹RNN的理論基礎,但這裡有一個我認為值得閱讀的鏈接(這裡是一個鏈接)。 來自Cristopher Olah的博客。 它詳述了LSTM:長短期記憶。 一種特殊的RNN。
在開始之前,我們必須建立一個深度學習的專用環境,在Tensorflow之上使用Keras。 我試圖在我的個人筆記本電腦上運行程序,但考慮到數據集的大小以及RNN體系結構的複雜性,這並不實際。
一個很好的選擇是AWS。 我通常在EC2 p2.xlarge實例上使用這種深度學習AMI。 (這裡有兩個鏈接)Amazon AMI是預先配置的VM映像,其中安裝了所有軟體包(Tensorflow,PyTocrh,Keras等)。 我強烈推薦這款我一直在使用的產品。
4 - 沒有預先訓練embedding的循環神經網路
RNN可能看起來很可怕。 雖然他們很難理解,但他們很有趣。 它們封裝了一個非常漂亮的設計,可以克服傳統神經網路在處理序列數據時出現的缺陷:文本,時間序列,視頻,DNA序列等。
RNN是一系列神經網路模塊,它們像鏈條一樣鏈接到彼此。 每個人都將消息傳遞給後繼者。
同樣,如果你想深入內部機制,我強烈推薦Colah的博客,下面的圖表就是其中的內容。
我們將處理文本數據,這是一種序列數據。詞的順序對語義非常重要。 希望RNN能夠很好地處理這一點,並且可以捕捉到長期的依賴關係。
要在文本數據上使用Keras,我們必須對其進行預處理。 為此,我們可以使用Keras的Tokenizer類。 該對象採用num_words作為參數,這是根據詞頻率進行token(標記)後保留的最大詞數。
MAX_NB_WORDS =80000
tokenizer = Tokenizer(num_words=MAX_NB_WORDS)
tokenizer.fit_on_texts(data["cleaned_text"])
對數據使用標記器後,可以將文本字元串轉換為數字序列。
這些數字代表字典中每個單詞的位置(將其視為映射)。
我們來看一個例子:
x_train[15]
"breakfast time happy time"
標記器如何將它轉換為一系列數字:
tokenizer.texts_to_sequences([x_train[15]])
[[530, 50, 119, 50]]
現在讓我們在訓練和測試上應用這個標記器:
train_sequences = tokenizer.texts_to_sequences(x_train)
test_sequences = tokenizer.texts_to_sequences(x_test)
現在這些推文被映射成數字列表。 但是,由於長度不同,我們仍然無法將它們堆疊在一起。 希望Keras允許用0填充序列到最大長度。 我們將此長度設置為35.(這是推文中標記的最大數量)。
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)
現在數據已準備好被送入RNN。
以下是我架構中將使用的一些細節:
embedding維度為300.這意味著我們將使用的80000個單詞都映射到300維密集向量(浮點數)。 映射將在整個訓練過程中進行調整。
在embedding層上應用空間dropout以減少過擬合:它主要查看35×300個矩陣的批次,並在每個矩陣中隨機刪除(設置為0)詞向量(即行)。 這有助於不將注意力集中在特定的詞語上,以嘗試一般化。
雙向門控循環單元(GRU):這是循環網路部分。 它是LSTM架構的一個更快的變體。 可以把它看作兩個循環網路的組合,它們從兩個方向掃描文本序列:從左到右,從右到左。 這允許網路在閱讀給定的單詞時,通過使用來自過去和未來信息的上下文來理解它。 GRU將每個網路塊的輸出h_t的維數設置為100.由於我們使用GRU的雙向版本,因此每個RNN塊的最終輸出將為200維。
雙向GRU的輸出具有維度(batch_size,timeteps,units)。 這意味著如果我們使用256的典型批量,這個尺寸將是(256,35,200)
在每個批處理之上,我們應用一個全局average pooling(平均池化),它包括對每個時間步驟對應的輸出向量進行平均。
我們應用與max pooling(最大池化)相同的操作。
我們連接前兩個操作的輸出。
我們來看看這個模型的不同層次:
plot_model(rnn_simple_model,
to_file="./images/article_5/rnn_simple_model.png",
show_shapes=True,
show_layer_names=True)
在訓練過程中,模型checkpoint被使用。它允許在每一個epoch結束時自動保存最好的模型(在磁碟上)。(w.r.t準確性度量)
filepath ="./models/rnn_no_embeddings/weights-improvement--.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_size=batch_size,
callbacks=[checkpoint],
epochs=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(lambdap:1
ifp >=0.5else)
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))
準確率達到82.6%! 相當不錯! 我們現在的表現比之前的詞袋模型表現得更好,因為我們正在考慮文本的序列性質。
我們可以做到更好嗎?
5 - GloVe預先訓練embedding的循環神經網路
在上一個模型中,embedding矩陣被隨機初始化。如果我們可以用預先訓練好的詞嵌入來取代它呢?
讓我們舉個例子:假設你的語料庫中有「pizza」這個詞。按照前面的體系結構,將它初始化為隨機浮點值的300維向量。這是非常好。您可以這樣做,這種embedding將在整個訓練過程中進行調整。然而,你可以做的,不是隨機選擇為pizza選擇一個向量,而是利用一個學習了非常大的語料庫的模型,進行詞embedding。 這是一種特殊的轉移學習。
使用來自外部embedding的知識可以提高RNN的精度,因為它集成了有關單詞的新信息(辭彙和語義),這些信息是在大量數據集上訓練和提煉出來的。
預先訓練的embedding我們將使用GloVe。
https://nlp.stanford.edu/projects/glove/
官方文檔:GloVe是一種無監督學習演算法,用於獲取單詞的矢量表示。 對來自語料庫的匯總的全球單詞共現統計進行訓練,結果展示了詞向量空間的有趣線性子結構。
我將使用的Glove embedding是在一個非常大的公共互聯網爬行訓練得到的,其中包括:
840億標記
220萬辭彙
壓縮文件有2.03 GB。注意,這個文件不能輕鬆載入到普通的筆記本電腦上
Glove embedding的維度是300
GloVe embedding來自原始文本數據,每行包含一個詞和300個浮點數。 所以首先要做的就是將這個結構轉換成一個Python字典。
defget_coefs(word,*arr):
try:
returnword,np.asarray(arr,dtype="float32")
except:
return None, None
embeddings_index =dict(get_coefs(*o.strip().split())forointqdm_notebook(
open("./embeddings/glove.840B.300d.txt")))
embed_size =300
forkintqdm_notebook(list(embeddings_index.keys())):
v = embeddings_index[k]
try:
ifv.shape != (embed_size,):
embeddings_index.pop(k)
except:
pass
embeddings_index.pop(None)
一旦創建了embedding索引,我們可以提取所有的向量,將它們疊加在一起,計算它們的均值和標準差。
values =list(embeddings_index.values())
all_embs = np.stack(values)
emb_mean,emb_std = all_embs.mean(),all_embs.std()
現在我們生成嵌入矩陣。我們將按照mean=emb_mean和std=emb_std的正態分布對其進行初始化。
然後我們看一下我們的語料庫的80000個單詞。 對於每個單詞,如果它包含在GloVe中,我們選擇它的embedding。否則,我們pass。
word_index = tokenizer.word_index
nb_words = MAX_NB_WORDS
embedding_matrix = np.random.normal(emb_mean,emb_std,(nb_words,embed_size))
oov =
forword,iintqdm_notebook(word_index.items()):
ifi >= MAX_NB_WORDS:continue
embedding_vector = embeddings_index.get(word)
ifembedding_vectoris not None:
embedding_matrix[i] = embedding_vector
else:
oov +=1
print(oov)
defget_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(inputs=inp,outputs=outp)
model.compile(loss="binary_crossentropy",
optimizer="adam",
metrics=["accuracy"])
returnmodel
rnn_model_with_embeddings = get_rnn_model_with_glove_embeddings()
filepath ="./models/rnn_with_embeddings/weights-improvement--.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_size=batch_size,
callbacks=[checkpoint],
epochs=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(lambdap:
1ifp >=0.5else)
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))
準確率達到83.7%!對於本教程的其餘部分,我將在embeddinig矩陣中使用GloVe embedding。
6 - 多通道卷積神經網路
在這一部分中,我正在試驗我在這裡(這裡是一個鏈接)讀到的一種卷積神經網路架構。CNNs通常用於計算機視覺。然而,我們最近開始將它們應用到NLP任務中,結果是很有希望的。
讓我們簡單地看看在文本數據上使用convnets時發生的情況。為了解釋這一點,我借用了wildm.com的這張著名的圖表。(一個非常好的博客!)
http://wildm.com/
讓我們考慮一下它使用的例子:我非常喜歡這部電影!(7個標記)
每個單詞的embedding維數為5。因此,這個句子由一個維度矩陣表示(7,5)。你可以把它想像成一個「圖像」(一個數字/浮點數的矩陣)。
6個過濾器,2個大小(2,5)(3,5)和(4,5)在這個矩陣上應用。這些濾波器的特殊性在於它們不是方陣,它們的寬度等於embedding矩陣的寬度。所以每個卷積的結果都是一個列向量。
卷積產生的每個列向量都使用max pooling操作進行子採樣。
max pooling操作的結果被連接到最後一個向量中,並傳遞給softmax函數進行分類。
背後的直覺是什麼?
每次卷積的結果將在檢測到特殊圖案時觸發。通過改變內核的大小並連接它們的輸出,您可以檢測多個大小的模式(2、3或5個相鄰的單詞)。
模式可以是表達式(詞ngrams?),比如「I hate」、「very good」,因此CNNs可以在句子中識別它們,而不管它們的位置。
defget_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[],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[] +1,1),
strides=(1,1),padding="valid")(conv_0)
maxpool_1 = MaxPool2D(pool_size=(MAX_LENGTH - filter_sizes[1] +1,1),
strides=(1,1),padding="valid")(conv_1)
maxpool_2 = MaxPool2D(pool_size=(MAX_LENGTH - filter_sizes[2] +1,1),
strides=(1,1),padding="valid")(conv_2)
concatenated_tensor = Concatenate(axis=1)(
[maxpool_0,maxpool_1,maxpool_2])
flatten = Flatten()(concatenated_tensor)
dropout = Dropout(drop)(flatten)
output = Dense(units=1,activation="sigmoid")(dropout)
model = Model(inputs=inputs,outputs=output)
adam = Adam(lr=1e-4,beta_1=0.9,beta_2=0.999,epsilon=1e-08,decay=0.0)
model.compile(optimizer=adam,loss="binary_crossentropy",metrics=["accuracy"])
returnmodel
cnn_model_multi_channel = get_cnn_model()
plot_model(cnn_model_multi_channel,
to_file="./images/article_5/cnn_model_multi_channel.png",
show_shapes=True,
show_layer_names=True)
filepath ="./models/cnn_multi_channel/weights-improvement--.hdf5"
checkpoint = ModelCheckpoint(filepath,monitor="val_acc",verbose=1,
save_best_only=True,mode="max")
batch_size =256
epochs =4
history = cnn_model_multi_channel.fit(x=padded_train_sequences,
y=y_train,
validation_data=(padded_test_sequences,y_test),
batch_size=batch_size,
callbacks=[checkpoint],
epochs=epochs,
verbose=1)
best_cnn_model = load_model("./models/cnn_multi_channel/weights-improvement-04-0.8264.hdf5")
y_pred_cnn_multi_channel = best_cnn_model.predict(padded_test_sequences,
verbose=1,batch_size=2048)
y_pred_cnn_multi_channel = pd.DataFrame(y_pred_cnn_multi_channel,columns=["prediction"])
y_pred_cnn_multi_channel["prediction"] = y_pred_cnn_multi_channel["prediction"].
map(lambdap:1ifp >=0.5else)
y_pred_cnn_multi_channel.to_csv("./predictions/y_pred_cnn_multi_channel.csv",index=False)
y_pred_cnn_multi_channel = pd.read_csv("./predictions/y_pred_cnn_multi_channel.csv")
print(accuracy_score(y_test,y_pred_cnn_multi_channel))
82.6%的準確率,沒有RNNs那麼精確,但仍然比BOW模型好。也許對超參數的研究(過濾器的數量和大小)會帶來優勢?
7 循環+卷積神經網路
RNN功能強大。 但是,有些人發現他們可以通過在循環層之上添加一個卷積層來使它們更加健壯。
RNN允許你(embed)嵌入關於序列和先前單詞的信息,並且CNN採用這種嵌入(embedding)並從中提取局部特徵。 這兩層共同工作是一個成功的組合。
更多請點擊這裡
http://konukoii.com/blog/2018/02/19/twitter-sentiment-analysis-using-combined-lstm-cnn-models/
defget_rnn_cnn_model():
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)
x = Conv1D(64,kernel_size=2,padding="valid",kernel_initializer="he_uniform")(x)
avg_pool = GlobalAveragePooling1D()(x)
max_pool = GlobalMaxPooling1D()(x)
conc = concatenate([avg_pool,max_pool])
outp = Dense(1,activation="sigmoid")(conc)
model = Model(inputs=inp,outputs=outp)
model.compile(loss="binary_crossentropy",
optimizer="adam",
metrics=["accuracy"])
returnmodel
rnn_cnn_model = get_rnn_cnn_model()
plot_model(rnn_cnn_model,to_file="./images/article_5/rnn_cnn_model.png",
show_shapes=True,show_layer_names=True)
filepath ="./models/rnn_cnn/weights-improvement--.hdf5"
checkpoint = ModelCheckpoint(filepath,monitor="val_acc",verbose=1,
save_best_only=True,mode="max")
batch_size =256
epochs =4
history = rnn_cnn_model.fit(x=padded_train_sequences,
y=y_train,
validation_data=(padded_test_sequences,y_test),
batch_size=batch_size,
callbacks=[checkpoint],
epochs=epochs,
verbose=1)
best_rnn_cnn_model = load_model("./models/rnn_cnn/weights-improvement-03-0.8379.hdf5")
y_pred_rnn_cnn = best_rnn_cnn_model.predict(padded_test_sequences,verbose=1,
batch_size=2048)
y_pred_rnn_cnn = pd.DataFrame(y_pred_rnn_cnn,columns=["prediction"])
y_pred_rnn_cnn["prediction"] = y_pred_rnn_cnn["prediction"].map(lambdap:1ifp >=0.5else)
y_pred_rnn_cnn.to_csv("./predictions/y_pred_rnn_cnn.csv",index=False)
y_pred_rnn_cnn = pd.read_csv("./predictions/y_pred_rnn_cnn.csv")
print(accuracy_score(y_test,y_pred_rnn_cnn))
83.8%的準確率。到目前為止最好的模型。
8 - 結論
我們運行了7個不同的模型。讓我們看看他們是如何比較的:
importseabornassns
fromsklearn.metricsimportroc_auc_score
sns.set_style("whitegrid")
sns.set_palette("pastel")
predictions_files = os.listdir("./predictions/")
predictions_dfs = []
forfinpredictions_files:
aux = pd.read_csv("./predictions/".format(f))
aux.columns = [f.strip(".csv")]
predictions_dfs.append(aux)
predictions = pd.concat(predictions_dfs,axis=1)
scores = {}
forcolumnintqdm_notebook(predictions.columns,leave=False):
ifcolumn !="y_true":
s = accuracy_score(predictions["y_true"].values,predictions[column].values)
scores[column] = s
scores = pd.DataFrame([scores],index=["accuracy"])
mapping_name =dict(zip(list(scores.columns),
["Char ngram + LR","(Word + Char ngram) + LR",
"Word ngram + LR","CNN (multi channel)",
"RNN + CNN","RNN no embd.","RNN + GloVe embds."]))
scores = scores.rename(columns=mapping_name)
scores = scores[["Word ngram + LR","Char ngram + LR","(Word + Char ngram) + LR",
"RNN no embd.","RNN + GloVe embds.","CNN (multi channel)",
"RNN + CNN"]]
scores = scores.T
ax = scores["accuracy"].plot(kind="bar",
figsize=(16,5),
ylim=(scores.accuracy.min() *0.97,scores.accuracy.max() *1.01),
color="red",
alpha=0.75,
rot=45,
fontsize=13)
ax.set_title("Comparative accuracy of the different models")
foriinax.patches:
ax.annotate(str(round(i.get_height(),3)),
(i.get_x() +0.1,i.get_height() *1.002),color="dimgrey",fontsize=14)
讓我們快速檢查模型預測之間的相關性。
fig = plt.figure(figsize=(10,5))
sns.heatmap(predictions.drop("y_true",axis=1).corr(method="kendall"),cmap="Blues",
annot=True);
結論
以下是我認為值得分享的發現:
使用字元 ngram的詞袋模型可以非常有效。不要低估他們! 它們計算相對簡單,而且易於解釋。
RNNs是強大的。然而,有時你可以用外部訓練過的embeddings,如Glove來抽取它們。您還可以使用其他流行的embedding,如word2vec和FastText。
CNNs可以應用於文本。他們的主要優勢是訓練非常快。此外,它們從文本中提取本地特徵的能力對nlp任務尤其有趣。
RNNs和CNNs可以疊加在一起,利用這兩種體系結構的優點。
以下是我寫這篇文章時參考的重要鏈接
http://wildml.com/2015/11/understanding-convolutional-neural-networks-for-nlp/
http://colah.github.io/posts/2015-08-Understanding-LSTMs/
1. https://ahmedbesbes.com/sentiment-analysis-on-twitter-using-word2vec-and-keras.html
https://ahmedbesbes.com/overview-and-benchmark-of-traditional-and-deep-learning-models-in-text-classification.html
-END-
專 · 知
人工智慧領域主題知識資料查看與加入專知人工智慧服務群:
TAG:專知 |