[Python人工智能] 二十.基于Keras+RNN的文本分類vs基于傳統機器學習的文本分類
從本專欄開始,作者正式研究Python深度學習、神經網絡及人工智能相關知識。前一篇文章分享了循環神經網絡RNN的原理知識,并采用Keras實現手寫數字識別的RNN分類案例及可視化呈現。這篇文章作者將帶領大家用Keras實現RNN和LSTM的文本分類算法,并與傳統的機器學習分類算法進行對比實驗。基礎性文章,希望對您有所幫助!同時,如果文章中存在錯誤或不足之處,也歡迎與我探討,作者也是初學者,非常希望您的交流能促進共同成長。感謝參考文獻中基基偉老師、知乎老師們的文章,共勉~

本專欄主要結合作者之前的博客、AI經驗和相關視頻及論文介紹,后面隨著深入會講解更多的Python人工智能案例及應用。基礎性文章,希望對您有所幫助,如果文章中存在錯誤或不足之處,還請海涵~作者作為人工智能的菜鳥,希望大家能與我在這一筆一劃的博客中成長起來。該專欄也會用心撰寫,望對得起讀者,共勉!
文章目錄:
- 一.RNN文本分類
- 1.RNN
- 2.文本分類
- 二.基于傳統機器學習貝葉斯算法的文本分類
- 1.MultinomialNB+TFIDF文本分類
- 2.GaussianNB+Word2Vec文本分類
- 三.Keras實現RNN文本分類
- 1.IMDB數據集和序列預處理
- 2.詞嵌入模型訓練
- 3.RNN文本分類
- 四.RNN實現中文數據集的文本分類
- 1.RNN+Word2Vector文本分類
- 2.LSTM+Word2Vec文本分類
- 3.LSTM+TFIDF文本分類
- 4.機器學習和深度學習對比分析
- 五.總結
代碼下載地址(歡迎大家關注點贊):
- https://github.com/eastmountyxz/
- AI-for-TensorFlow
- https://github.com/eastmountyxz/
- AI-for-Keras
學Python近十年,認識了很多大佬和朋友,感恩。作者的本意是幫助更多初學者入門,因此在github開源了所有代碼,也在公眾號同步更新。深知自己很菜,得拼命努力前行,編程也沒有什么捷徑,干就對了。希望未來能更透徹學習和撰寫文章,也能在讀博幾年里學會真正的獨立科研。同時非常感謝參考文獻中的大佬們的文章和分享。
- https://blog.csdn.net/eastmount
補充一張深度學習的思維導圖。

一.RNN文本分類
1.RNN
循環神經網絡英文是Recurrent Neural Networks,簡稱RNN。RNN的本質概念是利用時序信息,在傳統神經網絡中,假設所有的輸入(以及輸出)都各自獨立。但是,對于很多任務而言,這非常局限。舉個例子,假如你想根據一句沒說完的話,預測下一個單詞,最好的辦法就是聯系上下文的信息。而RNN(循環神經網絡)之所以是“循環”,是因為它們對序列的每個元素執行相同的任務,而每次的結果都獨立于之前的計算。
假設有一組數據data0、data1、data2、data3,使用同一個神經網絡預測它們,得到對應的結果。如果數據之間是有關系的,比如做菜下料的前后步驟,英文單詞的順序,如何讓數據之間的關聯也被神經網絡學習呢?這就要用到——RNN。
比如存在ABCD數字,需要預測下一個數字E,會根據前面ABCD順序進行預測,這就稱為記憶。預測之前,需要回顧以前的記憶有哪些,再加上這一步新的記憶點,最終輸出output,循環神經網絡(RNN)就利用了這樣的原理。

首先,讓我們想想人類是怎么分析事物之間的關聯或順序的。人類通常記住之前發生的事情,從而幫助我們后續的行為判斷,那么是否能讓計算機也記住之前發生的事情呢?
在分析data0時,我們把分析結果存入記憶Memory中,然后當分析data1時,神經網絡(NN)會產生新的記憶,但此時新的記憶和老的記憶沒有關聯,如上圖所示。在RNN中,我們會簡單的把老記憶調用過來分析新記憶,如果繼續分析更多的數據時,NN就會把之前的記憶全部累積起來。

下面是一個典型的RNN結果模型,按照時間點t-1、t、t+1,每個時刻有不同的x,每次計算會考慮上一步的state和這一步的x(t),再輸出y值。在該數學形式中,每次RNN運行完之后都會產生s(t),當RNN要分析x(t+1)時,此刻的y(t+1)是由s(t)和s(t+1)共同創造的,s(t)可看作上一步的記憶。多個神經網絡NN的累積就轉換成了循環神經網絡,其簡化圖如下圖的左邊所示。例如,如果序列中的句子有5個單詞,那么,橫向展開網絡后將有五層神經網絡,一層對應一個單詞。

總之,只要你的數據是有順序的,就可以使用RNN,比如人類說話的順序,電話號碼的順序,圖像像素排列的順序,ABC字母的順序等。RNN常用于自然語言處理、機器翻譯、語音識別、圖像識別等領域。
2.文本分類
文本分類旨在對文本集按照一定的分類體系或標準進行自動分類標記,屬于一種基于分類體系的自動分類。文本分類最早可以追溯到上世紀50年代,那時主要通過專家定義規則來進行文本分類;80年代出現了利用知識工程建立的專家系統;90年代開始借助于機器學習方法,通過人工特征工程和淺層分類模型來進行文本分類。現在多采用詞向量以及深度神經網絡來進行文本分類。

牛亞峰老師將傳統的文本分類流程歸納如下圖所示。在傳統的文本分類中,基本上大部分機器學習方法都在文本分類領域有所應用。主要包括:
- Naive Bayes
- KNN
- SVM
- 集合類方法
- 最大熵
- 神經網絡

利用Keras框架進行文本分類的基本流程如下:
- 步驟 1:文本的預處理,分詞->去除停用詞->統計選擇top n的詞做為特征詞
- 步驟 2:為每個特征詞生成ID
- 步驟 3:將文本轉化成ID序列,并將左側補齊
- 步驟 4:訓練集shuffle
- 步驟 5:Embedding Layer 將詞轉化為詞向量
- 步驟 6:添加模型,構建神經網絡結構
- 步驟 7:訓練模型
- 步驟 8:得到準確率、召回率、F1值
注意,如果使用TFIDF而非詞向量進行文檔表示,則直接分詞去停后生成TFIDF矩陣后輸入模型。本文將采用詞向量、TFIDF兩種方式進行實驗。
深度學習文本分類方法包括:
- 卷積神經網絡(TextCNN)
- 循環神經網絡(TextRNN)
- TextRNN+Attention
- TextRCNN(TextRNN+CNN)
推薦牛亞峰老師的文章:基于 word2vec 和 CNN 的文本分類 :綜述 & 實踐
二.基于傳統機器學習的文本分類
1.MultinomialNB+TFIDF文本分類
推薦作者之前的文章:[python數據挖掘課程] 二十一.樸素貝葉斯分類器詳解及中文文本輿情分析。數據集采用基基偉老師的自定義文本,共21行數據,包括2類(小米手機、小米粥)。其基本流程是:
- 獲取數據集data和target
- 調用Jieba庫實現中文分詞
- 計算TF-IDF值,將詞頻矩陣轉換為TF-IDF向量矩陣
- 調用機器學習算法進行訓練和預測
- 實驗評估及可視化分析
完整代碼如下:
# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcut
#--------------------------------載入數據及預處理-------------------------------data = [ [0, '小米粥是以小米作為主要食材熬制而成的粥,口味清淡,清香味,具有簡單易制,健胃消食的特點'], [0, '煮粥時一定要先燒開水然后放入洗凈后的小米'], [0, '蛋白質及氨基酸、脂肪、維生素、礦物質'], [0, '小米是傳統健康食品,可單獨燜飯和熬粥'], [0, '蘋果,是水果中的一種'], [0, '粥的營養價值很高,富含礦物質和維生素,含鈣量豐富,有助于代謝掉體內多余鹽分'], [0, '雞蛋有很高的營養價值,是優質蛋白質、B族維生素的良好來源,還能提供脂肪、維生素和礦物質'], [0, '這家超市的蘋果都非常新鮮'], [0, '在北方小米是主要食物之一,很多地區有晚餐吃小米粥的習俗'], [0, '小米營養價值高,營養全面均衡 ,主要含有碳水化合物'], [0, '蛋白質及氨基酸、脂肪、維生素、鹽分'], [1, '小米、三星、華為,作為安卓三大手機旗艦'], [1, '別再管小米華為了!魅族手機再曝光:這次真的完美了'], [1, '蘋果手機或將重陷2016年困境,但這次它無法再大幅提價了'], [1, '三星想要繼續壓制華為,僅憑A70還不夠'], [1, '三星手機屏占比將再創新高,超華為及蘋果旗艦'], [1, '華為P30、三星A70爆賣,斬獲蘇寧最佳手機營銷獎'], [1, '雷軍,用一張圖告訴你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上線,適配深度系統'], [1, '三星剛剛更新了自家的可穿戴設備APP'], [1, '華為、小米跨界并不可怕,可怕的打不破內心的“天花板”'],]
#中文分析X, Y = [' '.join(lcut(i[1])) for i in data], [i[0] for i in data]print(X)print(Y)#['煮粥 時 一定 要 先燒 開水 然后 放入 洗凈 后 的 小米', ...]
#--------------------------------------計算詞頻------------------------------------from sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformer
#將文本中的詞語轉換為詞頻矩陣vectorizer = CountVectorizer()
#計算個詞語出現的次數X_data = vectorizer.fit_transform(X)print(X_data)
#獲取詞袋中所有文本關鍵詞word = vectorizer.get_feature_names()print('【查看單詞】')for w in word: print(w, end = " ")else: print("")
#詞頻矩陣print(X_data.toarray())
#將詞頻矩陣X統計成TF-IDF值transformer = TfidfTransformer()tfidf = transformer.fit_transform(X_data)
#查看數據結構 tfidf[i][j]表示i類文本中的tf-idf權重weight = tfidf.toarray()print(weight)
#--------------------------------------數據分析------------------------------------from sklearn.naive_bayes import MultinomialNB from sklearn.metrics import classification_reportfrom sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(weight, Y)print(len(X_train), len(X_test))print(len(y_train), len(y_test))print(X_train)
#調用MultinomialNB分類器 clf = MultinomialNB().fit(X_train, y_train)pre = clf.predict(X_test)print("預測結果:", pre)print("真實結果:", y_test)print(classification_report(y_test, pre))
#--------------------------------------可視化分析------------------------------------#降維繪制圖形from sklearn.decomposition import PCAimport matplotlib.pyplot as plt
pca = PCA(n_components=2)newData = pca.fit_transform(weight)print(newData) L1 = [n[0] for n in newData]L2 = [n[1] for n in newData]plt.scatter(L1, L2, c=Y, s=200)plt.show()
輸出結果如下所示:
- 6個預測數據的accuracy ===> 0.67
['小米粥 是 以 小米 作為 主要 食材 熬 制而成 的 粥 , 口味 清淡 , 清香味 , 具有 簡單 易制 , 健胃 消食 的 特點', '煮粥 時 一定 要 先燒 開水 然后 放入 洗凈 后 的 小米', '蛋白質 及 氨基酸 、 脂肪 、 維生素 、 礦物質', ... '三星 剛剛 更新 了 自家 的 可 穿戴 設備 APP', '華為 、 小米 跨界 并 不 可怕 , 可怕 的 打 不破 內心 的 “ 天花板 ”'][0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 【查看單詞】2016 app linux p30 一定 一張 一種 三星 健康 ... 雷軍 食品 食材 食物 魅族 雞蛋
[[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 1 ... 0 0 0] [0 0 0 ... 0 0 0]] 15 615 6[[0. 0. 0. ... 0. 0. 0. ] [0. 0. 0. ... 0. 0. 0.32161043] [0. 0. 0. ... 0. 0. 0. ] ... [0. 0.31077094 0. ... 0. 0. 0. ] [0. 0. 0. ... 0. 0. 0. ] [0.35882035 0. 0. ... 0. 0. 0. ]]預測結果: [0 1 0 1 1 1]真實結果: [0, 0, 0, 0, 1, 1]
precision recall f1-score support
0 1.00 0.50 0.67 4 1 0.50 1.00 0.67 2
accuracy 0.67 6 macro avg 0.75 0.75 0.67 6weighted avg 0.83 0.67 0.67 6
繪制圖形如下圖所示:

2.GaussianNB+Word2Vec文本分類
該方法與前面不同之處是采用Word2Vec進行詞向量計算,將每行數據集分詞,并計算每個特征詞的詞向量,接著轉換為詞向量矩陣,比如15行數據,每行數據40個特征詞,每個特征詞用20維度的詞向量表示,即(15, 40, 20)。同時,由于詞向量存在負數,所以需要使用GaussianNB算法替代MultinomialNB算法。
Word2Vec詳見作者前文:[Python人工智能] 九.gensim詞向量Word2Vec安裝及《慶余年》中文短文本相似度計算
- sentences:傳入的數據集序列(list of lists of tokens),默認值為None
- size:詞向量維數,默認值為100
- window:同句中當前詞和預測詞的最大距離,默認值為5
- min_count:最低詞頻過濾,默認值為5
- workers:線程數,默認值為3
- sg:模型參數,其值為0表示CBOW,值為1表示skip-gram,默認值為0
- hs:模型參數,其值為0表示負例采樣,值為1表示層次softmax,默認值為0
- negative:負例樣本數,默認值為5
- ns_exponent:用于形成負例樣本的指數,默認值為0.75
- cbow_mean:上下文詞向量參數,其值為0表示上下文詞向量求和值,值為1表示上下文詞向量平均值,默認值為1
- alpha:初始學習率,默認值為0.025
- min_alpha:最小學習率,默認值為0.0001
完整代碼如下:
# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequences
max_features = 20 #詞向量維度maxlen = 40 #序列最大長度
#--------------------------------載入數據及預處理-------------------------------data = [ [0, '小米粥是以小米作為主要食材熬制而成的粥,口味清淡,清香味,具有簡單易制,健胃消食的特點'], [0, '煮粥時一定要先燒開水然后放入洗凈后的小米'], [0, '蛋白質及氨基酸、脂肪、維生素、礦物質'], [0, '小米是傳統健康食品,可單獨燜飯和熬粥'], [0, '蘋果,是水果中的一種'], [0, '粥的營養價值很高,富含礦物質和維生素,含鈣量豐富,有助于代謝掉體內多余鹽分'], [0, '雞蛋有很高的營養價值,是優質蛋白質、B族維生素的良好來源,還能提供脂肪、維生素和礦物質'], [0, '這家超市的蘋果都非常新鮮'], [0, '在北方小米是主要食物之一,很多地區有晚餐吃小米粥的習俗'], [0, '小米營養價值高,營養全面均衡 ,主要含有碳水化合物'], [0, '蛋白質及氨基酸、脂肪、維生素、鹽分'], [1, '小米、三星、華為,作為安卓三大手機旗艦'], [1, '別再管小米華為了!魅族手機再曝光:這次真的完美了'], [1, '蘋果手機或將重陷2016年困境,但這次它無法再大幅提價了'], [1, '三星想要繼續壓制華為,僅憑A70還不夠'], [1, '三星手機屏占比將再創新高,超華為及蘋果旗艦'], [1, '華為P30、三星A70爆賣,斬獲蘇寧最佳手機營銷獎'], [1, '雷軍,用一張圖告訴你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上線,適配深度系統'], [1, '三星剛剛更新了自家的可穿戴設備APP'], [1, '華為、小米跨界并不可怕,可怕的打不破內心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#劃分訓練集和預測集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '剛剛', '更新', '了', '自家', '的', '可', '穿戴', '設備', 'APP']"""
#--------------------------------Word2Vec詞向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低過濾頻次1print(word2vec)
#映射特征詞w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【顯示詞語】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '維生素', '蛋白質', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '華為': 4, ....}"""
#詞向量計算vectors = word2vec.wv.vectorsprint("【詞向量矩陣】")print(vectors.shape)print(vectors)
#自定義函數-獲取詞向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定義函數-序列預處理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化處理 轉換為詞向量X_train, X_test = pad(X_train), pad(X_test)print(X_train.shape)print(X_test.shape)"""(15, 40, 20) 15個樣本 40個特征 每個特征用20詞向量表示"""
#拉直形狀 (15, 40, 20)=>(15, 40*20) (6, 40, 20)=>(6, 40*20)X_train = X_train.reshape(len(y_train), maxlen*max_features)X_test = X_test.reshape(len(y_test), maxlen*max_features)print(X_train.shape)print(X_test.shape)
#--------------------------------建模與訓練-------------------------------from sklearn.naive_bayes import GaussianNBfrom sklearn.metrics import classification_reportfrom sklearn.model_selection import train_test_split
#調用GaussianNB分類器 clf = GaussianNB().fit(X_train, y_train)pre = clf.predict(X_test)print("預測結果:", pre)print("真實結果:", y_test)print(classification_report(y_test, pre))
輸出結果如下所示:
- 6個預測數據的accuracy ===> 0.83
15 615 6Word2Vec(vocab=126, size=20, alpha=0.025)
【顯示詞語】[',', '、', '小米', '的', '華為', '手機', '蘋果', '維生素', 'APP', '官方', 'Linux', ... '安卓三大', '旗艦']{',': 0, '、': 1, '小米': 2, '的': 3, '華為': 4, '手機': 5, '蘋果': 6, ..., '安卓三大': 124, '旗艦': 125}
【詞向量矩陣】(126, 20)[[ 0.02041552 -0.00929706 -0.00743623 ... -0.00246041 -0.00825108 0.02341811] [-0.00256093 -0.01301112 -0.00697959 ... -0.00449076 -0.00551124 -0.00240511] [ 0.01535473 0.01690796 -0.00262145 ... -0.01624218 0.00871249 -0.01159615] ... [ 0.00631155 0.00369085 -0.00382834 ... 0.02468265 0.00945442 -0.0155745 ] [-0.01198495 0.01711261 0.01097644 ... 0.01003117 0.01074963 0.01960118] [ 0.00450704 -0.01114052 0.0186879 ... 0.00804681 0.01060277 0.01836049]] (15, 40, 20)(6, 40, 20)(15, 800)(6, 800)
預測結果: [1 1 1 0 1 0]真實結果: [0, 1, 1, 0, 1, 0] precision recall f1-score support
0 1.00 0.67 0.80 3 1 0.75 1.00 0.86 3
accuracy 0.83 6 macro avg 0.88 0.83 0.83 6weighted avg 0.88 0.83 0.83 6
三.Keras實現RNN文本分類
1.IMDB數據集和序列預處理
(1) IMDB數據集
Keras框架為我們提供了一些常用的內置數據集。比如,圖像識別領域的手寫識別MNIST數據集、文本分類領域的電影影評imdb數據集等等。這些數據庫可以用一條代碼就可以調用:
- (trainX, trainY), (testX, testY) = imdb.load_data(path=“imdb.npz”, num_words=max_features)
這些數據集是通過https://s3.amazonaws.com進行下載的,但有時該網站不能使用,需要下載數據至本地,再進行調用分析。Keras數據集百度云鏈接:
- https://pan.baidu.com/s/1aZRp0uMkNj2QEWYstaNsKQ,提取碼: 3a2u
作者將下載后的數據放在C:\Users\Administrator.keras\datasets文件夾下,如下圖所示。

該數據集是互聯網電影資料庫(Internet Movie Database,簡稱IMDb),它是一個關于電影演員、電影、電視節目、電視明星和電影制作的在線數據庫。
imdb.npz文件中數據和格式如下:
[list([1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, ...]) list([1, 194, 1153, 194, 8255, 78, 228, 5, 6, 1463, 4369, 5012, 134, 26, 4, 715, 8, 118, 1634, 14, 394, 20, 13, 119, 954, 189, 102, 5, 207, 110, 3103, 21, 14, 69, ...]) list([1, 14, 47, 8, 30, 31, 7, 4, 249, 108, 7, 4, 5974, 54, 61, 369, 13, 71, 149, 14, 22, 112, 4, 2401, 311, 12, 16, 3711, 33, 75, 43, 1829, 296, 4, 86, 320, 35, ...]) ... list([1, 11, 6, 230, 245, 6401, 9, 6, 1225, 446, 2, 45, 2174, 84, 8322, 4007, 21, 4, 912, 84, 14532, 325, 725, 134, 15271, 1715, 84, 5, 36, 28, 57, 1099, 21, 8, 140, ...]) list([1, 1446, 7079, 69, 72, 3305, 13, 610, 930, 8, 12, 582, 23, 5, 16, 484, 685, 54, 349, 11, 4120, 2959, 45, 58, 1466, 13, 197, 12, 16, 43, 23, 2, 5, 62, 30, 145, ...]) list([1, 17, 6, 194, 337, 7, 4, 204, 22, 45, 254, 8, 106, 14, 123, 4, 12815, 270, 14437, 5, 16923, 12255, 732, 2098, 101, 405, 39, 14, 1034, 4, 1310, 9, 115, 50, 305, ...])] train sequences
每個list是一個句子,句子中每個數字表示單詞的編號。那么,怎么獲取編號對應的單詞?此時需要使用imdb_word_index.json文件,其文件格式如下:
{"fawn": 34701, "tsukino": 52006,..., "paget": 18509, "expands": 20597}
共有88584個單詞,采用key-value格式存放,key代表單詞,value代表(單詞)編號。詞頻(單詞在語料中出現次數)越高編號越小,例如, “the:1”出現次數最高,編號為1。
(2) 序列預處理
在進行深度學習向量轉換過程中,通常需要使用pad_sequences()序列填充。其基本用法如下:
keras.preprocessing.sequence.pad_sequences( sequences, maxlen=None, dtype='int32', padding='pre', truncating='pre', value=0. )
參數含義如下:
- sequences:浮點數或整數構成的兩層嵌套列表
- maxlen:None或整數,為序列的最大長度。大于此長度的序列將被截短,小于此長度的序列將在后部填0
- dtype:返回的numpy array的數據類型
- padding:pre或post,確定當需要補0時,在序列的起始還是結尾補0
- truncating:pre或post,確定當需要截斷序列時,從起始還是結尾截斷
- value:浮點數,此值將在填充時代替默認的填充值0
- 返回值是個2維張量,長度為maxlen
基本用法如下所示:
from keras.preprocessing.sequence import pad_sequences print(pad_sequences([[1, 2, 3], [1]], maxlen=2))"""[[2 3] [0 1]]"""print(pad_sequences([[1, 2, 3], [1]], maxlen=3, value=9))"""[[1 2 3] [9 9 1]]""" print(pad_sequences([[2,3,4]], maxlen=10))"""[[0 0 0 0 0 0 0 2 3 4]]"""print(pad_sequences([[1,2,3,4,5],[6,7]], maxlen=10))"""[[0 0 0 0 0 1 2 3 4 5] [0 0 0 0 0 0 0 0 6 7]]""" print(pad_sequences([[1, 2, 3], [1]], maxlen=2, padding='post'))"""結束位置補: [[2 3] [1 0]]"""print(pad_sequences([[1, 2, 3], [1]], maxlen=4, truncating='post'))"""起始位置補: [[0 1 2 3] [0 0 0 1]]"""
在自然語言中一般和分詞器一起使用。
>>> tokenizer.texts_to_sequences(["下 雨 我 加班"])[[4, 5, 6, 7]] >>> keras.preprocessing.sequence.pad_sequences(tokenizer.texts_to_sequences(["下 雨 我 加班"]), maxlen=20)array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7]],dtype=int32)
2.詞嵌入模型訓練
此時我們將通過詞嵌入模型進行訓練,具體流程包括:
- 導入IMDB數據集
- 數據集轉換為序列
- 創建Embedding詞嵌入模型
- 神經網絡訓練
完整代碼如下:
# -*- coding: utf-8 -*-"""Created on Sat Mar 28 17:08:28 2020@author: Eastmount CSDN"""from keras.datasets import imdb #Movie Databasefrom keras.preprocessing import sequencefrom keras.models import Sequentialfrom keras.layers import Dense, Flatten, Embedding
#-----------------------------------定義參數-----------------------------------max_features = 20000 #按詞頻大小取樣本前20000個詞input_dim = max_features #詞庫大小 必須>=max_featuresmaxlen = 80 #句子最大長度batch_size = 128 #batch數量output_dim = 40 #詞向量維度epochs = 2 #訓練批次
#--------------------------------載入數據及預處理-------------------------------#數據獲取(trainX, trainY), (testX, testY) = imdb.load_data(path="imdb.npz", num_words=max_features) print(trainX.shape, trainY.shape) #(25000,) (25000,)print(testX.shape, testY.shape) #(25000,) (25000,)
#序列截斷或補齊為等長trainX = sequence.pad_sequences(trainX, maxlen=maxlen)testX = sequence.pad_sequences(testX, maxlen=maxlen)print('trainX shape:', trainX.shape)print('testX shape:', testX.shape)
#------------------------------------創建模型------------------------------------model = Sequential()
#詞嵌入:詞庫大小、詞向量維度、固定序列長度model.add(Embedding(input_dim, output_dim, input_length=maxlen))
#平坦化: maxlen*output_dimmodel.add(Flatten())
#輸出層: 2分類model.add(Dense(units=1, activation='sigmoid'))
#RMSprop優化器 二元交叉熵損失model.compile('rmsprop', 'binary_crossentropy', ['acc'])
#訓練model.fit(trainX, trainY, batch_size, epochs)
#模型可視化model.summary()
輸出結果如下所示:
(25000,) (25000,)(25000,) (25000,)trainX shape: (25000, 80)testX shape: (25000, 80) Epoch 1/225000/25000 [==============================] - 2s 98us/step - loss: 0.6111 - acc: 0.6956Epoch 2/225000/25000 [==============================] - 2s 69us/step - loss: 0.3578 - acc: 0.8549Model: "sequential_2"_________________________________________________________________Layer (type) Output Shape Param # =================================================================embedding_2 (Embedding) (None, 80, 40) 800000 _________________________________________________________________flatten_2 (Flatten) (None, 3200) 0 _________________________________________________________________dense_2 (Dense) (None, 1) 3201 =================================================================Total params: 803,201Trainable params: 803,201Non-trainable params: 0_________________________________________________________________
顯示矩陣如下圖所示:

3.RNN文本分類
RNN對IMDB電影數據集進行文本分類的完整代碼如下所示:
# -*- coding: utf-8 -*-"""Created on Sat Mar 28 17:08:28 2020@author: Eastmount CSDN"""from keras.datasets import imdb #Movie Databasefrom keras.preprocessing import sequencefrom keras.models import Sequentialfrom keras.layers import Dense, Flatten, Embeddingfrom keras.layers import SimpleRNN
#-----------------------------------定義參數-----------------------------------max_features = 20000 #按詞頻大小取樣本前20000個詞input_dim = max_features #詞庫大小 必須>=max_featuresmaxlen = 40 #句子最大長度batch_size = 128 #batch數量output_dim = 40 #詞向量維度epochs = 3 #訓練批次units = 32 #RNN神經元數量
#--------------------------------載入數據及預處理-------------------------------#數據獲取(trainX, trainY), (testX, testY) = imdb.load_data(path="imdb.npz", num_words=max_features) print(trainX.shape, trainY.shape) #(25000,) (25000,)print(testX.shape, testY.shape) #(25000,) (25000,)
#序列截斷或補齊為等長trainX = sequence.pad_sequences(trainX, maxlen=maxlen)testX = sequence.pad_sequences(testX, maxlen=maxlen)print('trainX shape:', trainX.shape)print('testX shape:', testX.shape)
#-----------------------------------創建RNN模型-----------------------------------model = Sequential()
#詞嵌入 詞庫大小、詞向量維度、固定序列長度model.add(Embedding(input_dim, output_dim, input_length=maxlen))
#RNN Cellmodel.add(SimpleRNN(units, return_sequences=True)) #返回序列全部結果model.add(SimpleRNN(units, return_sequences=False)) #返回序列最尾結果
#輸出層 2分類model.add(Dense(units=1, activation='sigmoid'))
#模型可視化model.summary()
#-----------------------------------建模與訓練-----------------------------------#激活神經網絡 model.compile(optimizer = 'rmsprop', #RMSprop優化器 loss = 'binary_crossentropy', #二元交叉熵損失 metrics = ['accuracy'] #計算誤差或準確率 )
#訓練history = model.fit(trainX, trainY, batch_size=batch_size, epochs=epochs, verbose=2, validation_split=.1 #取10%樣本作驗證 )
#-----------------------------------預測與可視化-----------------------------------import matplotlib.pyplot as pltaccuracy = history.history['accuracy']val_accuracy = history.history['val_accuracy']plt.plot(range(epochs), accuracy)plt.plot(range(epochs), val_accuracy)plt.show()
輸出結果如下所示,三個Epoch訓練。
- 訓練數據的accuracy ===> 0.9075
- 評估數據的val_accuracy ===> 0.7844
Epoch可以用下圖進行形象的表示。

(25000,) (25000,)(25000,) (25000,)trainX shape: (25000, 40)testX shape: (25000, 40)Model: "sequential_2"_________________________________________________________________Layer (type) Output Shape Param # =================================================================embedding_2 (Embedding) (None, 40, 40) 800000 _________________________________________________________________simple_rnn_3 (SimpleRNN) (None, 40, 32) 2336 _________________________________________________________________simple_rnn_4 (SimpleRNN) (None, 32) 2080 _________________________________________________________________dense_2 (Dense) (None, 1) 33 =================================================================Total params: 804,449Trainable params: 804,449Non-trainable params: 0_________________________________________________________________ Train on 22500 samples, validate on 2500 samplesEpoch 1/3 - 11s - loss: 0.5741 - accuracy: 0.6735 - val_loss: 0.4462 - val_accuracy: 0.7876Epoch 2/3 - 14s - loss: 0.3572 - accuracy: 0.8430 - val_loss: 0.4928 - val_accuracy: 0.7616Epoch 3/3 - 12s - loss: 0.2329 - accuracy: 0.9075 - val_loss: 0.5050 - val_accuracy: 0.7844
繪制的accuracy和val_accuracy曲線如下圖所示:
- loss: 0.2329 - accuracy: 0.9075 - val_loss: 0.5050 - val_accuracy: 0.7844

四.RNN實現文本分類
1.RNN+Word2Vector文本分類
第一步,導入文本數據集并轉換為詞向量。
data = [ [0, '小米粥是以小米作為主要食材熬制而成的粥,口味清淡,清香味,具有簡單易制,健胃消食的特點'], [0, '煮粥時一定要先燒開水然后放入洗凈后的小米'], [0, '蛋白質及氨基酸、脂肪、維生素、礦物質'], [0, '小米是傳統健康食品,可單獨燜飯和熬粥'], [0, '蘋果,是水果中的一種'], [0, '粥的營養價值很高,富含礦物質和維生素,含鈣量豐富,有助于代謝掉體內多余鹽分'], [0, '雞蛋有很高的營養價值,是優質蛋白質、B族維生素的良好來源,還能提供脂肪、維生素和礦物質'], [0, '這家超市的蘋果都非常新鮮'], [0, '在北方小米是主要食物之一,很多地區有晚餐吃小米粥的習俗'], [0, '小米營養價值高,營養全面均衡 ,主要含有碳水化合物'], [0, '蛋白質及氨基酸、脂肪、維生素、鹽分'], [1, '小米、三星、華為,作為安卓三大手機旗艦'], [1, '別再管小米華為了!魅族手機再曝光:這次真的完美了'], [1, '蘋果手機或將重陷2016年困境,但這次它無法再大幅提價了'], [1, '三星想要繼續壓制華為,僅憑A70還不夠'], [1, '三星手機屏占比將再創新高,超華為及蘋果旗艦'], [1, '華為P30、三星A70爆賣,斬獲蘇寧最佳手機營銷獎'], [1, '雷軍,用一張圖告訴你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上線,適配深度系統'], [1, '三星剛剛更新了自家的可穿戴設備APP'], [1, '華為、小米跨界并不可怕,可怕的打不破內心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#劃分訓練集和預測集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '剛剛', '更新', '了', '自家', '的', '可', '穿戴', '設備', 'APP']"""
#--------------------------------Word2Vec詞向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低過濾頻次1print(word2vec)
#映射特征詞w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【顯示詞語】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '維生素', '蛋白質', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '華為': 4, ....}"""
#詞向量計算vectors = word2vec.wv.vectorsprint("【詞向量矩陣】")print(vectors.shape)print(vectors)
#自定義函數-獲取詞向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定義函數-序列預處理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化處理 轉換為詞向量X_train, X_test = pad(X_train), pad(X_test)
此時輸出結果如下所示:
15 615 6Word2Vec(vocab=120, size=20, alpha=0.025)【顯示詞語】[',', '的', '、', '小米', '三星', '是', '維生素', '蛋白質', '及', '脂肪', '華為', '蘋果', '可', 'APP', '氨基酸', '在', '手機', '旗艦', '礦物質', '主要', '有', '小米粥', '作為', '剛剛', '更新', '設備', ...]{',': 0, '的': 1, '、': 2, '小米': 3, '三星': 4, '是': 5, '維生素': 6, '蛋白質': 7, '及': 8, '脂肪': 9, '和': 10, '華為': 11, '蘋果': 12, '可': 13, 'APP': 14, '氨基酸': 15, ...}【詞向量矩陣】(120, 20)[[ 0.00219526 0.00936278 0.00390177 ... -0.00422463 0.01543128 0.02481441] [ 0.02346811 -0.01520025 -0.00563479 ... -0.01656673 -0.02222313 0.00438196] [-0.02253242 -0.01633896 -0.02209039 ... 0.01301584 -0.01016752 0.01147605] ... [ 0.01793107 0.01912305 -0.01780855 ... -0.00109831 0.02460653 -0.00023512] [-0.00599797 0.02155897 -0.01874896 ... 0.00149929 0.00200266 0.00988515] [ 0.0050361 -0.00848463 -0.0235001 ... 0.01531716 -0.02348576 0.01051775]]
第二步,建立RNN神經網絡結構,使用Bi-GRU模型,并進行訓練與預測。
#--------------------------------建模與訓練-------------------------------model = Sequential()
#雙向RNNmodel.add(Bidirectional(GRU(units), input_shape=(maxlen, max_features)))
#輸出層 2分類model.add(Dense(units=1, activation='sigmoid'))
#模型可視化model.summary()
#激活神經網絡 model.compile(optimizer = 'rmsprop', #RMSprop優化器 loss = 'binary_crossentropy', #二元交叉熵損失 metrics = ['acc'] #計算誤差或準確率 )
#訓練history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------預測與可視化------------------------------#預測score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可視化acc = history.history['acc']val_acc = history.history['val_acc']
# 設置類標plt.xlabel("Iterations")plt.ylabel("Accuracy")
#繪圖plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("RNN-Word2vec")plt.show()
輸出結果如下圖所示,發現accuracy和val_accuracy值非常不理想。怎么解決呢?

神經網絡模型和Epoch訓練結果如下圖所示:
- test loss: 0.7160684466362
- test accuracy: 0.33333334

這里補充一個知識點——EarlyStopping。
EarlyStopping是Callbacks的一種,callbacks用于指定在每個epoch開始和結束的時候進行哪種特定操作。Callbacks中有一些設置好的接口,可以直接使用,如acc、val_acc、loss和val_loss等。EarlyStopping則是用于提前停止訓練的callbacks,可以達到當訓練集上的loss不在減小(即減小的程度小于某個閾值)的時候停止繼續訓練。上面程序中,當我們loss不在減小時就可以調用Callbacks停止訓練。
最后給出該部分的完整代碼:
# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosimport matplotlib.pyplot as pltfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequencesfrom tensorflow.python.keras.models import Sequentialfrom tensorflow.python.keras.layers import Dense, GRU, Bidirectionalfrom tensorflow.python.keras.callbacks import EarlyStopping
#-----------------------------------定義參數----------------------------------max_features = 20 #詞向量維度units = 30 #RNN神經元數量maxlen = 40 #序列最大長度epochs = 9 #訓練最大輪數batch_size = 12 #每批數據量大小verbose = 1 #訓練過程展示patience = 1 #沒有進步的訓練輪數
callbacks = [EarlyStopping('val_acc', patience=patience)]
#--------------------------------載入數據及預處理-------------------------------data = [ [0, '小米粥是以小米作為主要食材熬制而成的粥,口味清淡,清香味,具有簡單易制,健胃消食的特點'], [0, '煮粥時一定要先燒開水然后放入洗凈后的小米'], [0, '蛋白質及氨基酸、脂肪、維生素、礦物質'], [0, '小米是傳統健康食品,可單獨燜飯和熬粥'], [0, '蘋果,是水果中的一種'], [0, '粥的營養價值很高,富含礦物質和維生素,含鈣量豐富,有助于代謝掉體內多余鹽分'], [0, '雞蛋有很高的營養價值,是優質蛋白質、B族維生素的良好來源,還能提供脂肪、維生素和礦物質'], [0, '這家超市的蘋果都非常新鮮'], [0, '在北方小米是主要食物之一,很多地區有晚餐吃小米粥的習俗'], [0, '小米營養價值高,營養全面均衡 ,主要含有碳水化合物'], [0, '蛋白質及氨基酸、脂肪、維生素、鹽分'], [1, '小米、三星、華為,作為安卓三大手機旗艦'], [1, '別再管小米華為了!魅族手機再曝光:這次真的完美了'], [1, '蘋果手機或將重陷2016年困境,但這次它無法再大幅提價了'], [1, '三星想要繼續壓制華為,僅憑A70還不夠'], [1, '三星手機屏占比將再創新高,超華為及蘋果旗艦'], [1, '華為P30、三星A70爆賣,斬獲蘇寧最佳手機營銷獎'], [1, '雷軍,用一張圖告訴你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上線,適配深度系統'], [1, '三星剛剛更新了自家的可穿戴設備APP'], [1, '華為、小米跨界并不可怕,可怕的打不破內心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#劃分訓練集和預測集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '剛剛', '更新', '了', '自家', '的', '可', '穿戴', '設備', 'APP']"""
#--------------------------------Word2Vec詞向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低過濾頻次1print(word2vec)
#映射特征詞w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【顯示詞語】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '維生素', '蛋白質', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '華為': 4, ....}"""
#詞向量計算vectors = word2vec.wv.vectorsprint("【詞向量矩陣】")print(vectors.shape)print(vectors)
#自定義函數-獲取詞向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定義函數-序列預處理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化處理 轉換為詞向量X_train, X_test = pad(X_train), pad(X_test)
#--------------------------------建模與訓練-------------------------------model = Sequential()
#雙向RNNmodel.add(Bidirectional(GRU(units), input_shape=(maxlen, max_features)))
#輸出層 2分類model.add(Dense(units=1, activation='sigmoid'))
#模型可視化model.summary()
#激活神經網絡 model.compile(optimizer = 'rmsprop', #RMSprop優化器 loss = 'binary_crossentropy', #二元交叉熵損失 metrics = ['acc'] #計算誤差或準確率 )
#訓練history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------預測與可視化------------------------------#預測score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可視化acc = history.history['acc']val_acc = history.history['val_acc']
# 設置類標plt.xlabel("Iterations")plt.ylabel("Accuracy")
#繪圖plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("RNN-Word2vec")plt.show()
2.LSTM+Word2Vec文本分類
接著我們使用LSTM和Word2Vec進行文本分類。整個神經網絡的結構很簡單,第一層是嵌入層,將文本中的單詞轉化為向量;之后經過一層LSTM層,使用LSTM中最后一個時刻的隱藏狀態;再接一個全連接層,即可完成整個網絡的構造。
注意矩陣形狀的變換。
- X_train = X_train.reshape(len(y_train), maxlen*max_features)
- X_test = X_test.reshape(len(y_test), maxlen*max_features)
完整代碼如下所示:
# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosimport matplotlib.pyplot as pltfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequencesfrom tensorflow.python.keras.models import Sequentialfrom tensorflow.python.keras.layers import Dense, LSTM, GRU, Embeddingfrom tensorflow.python.keras.callbacks import EarlyStopping
#-----------------------------------定義參數----------------------------------max_features = 20 #詞向量維度units = 30 #RNN神經元數量maxlen = 40 #序列最大長度epochs = 9 #訓練最大輪數batch_size = 12 #每批數據量大小verbose = 1 #訓練過程展示patience = 1 #沒有進步的訓練輪數
callbacks = [EarlyStopping('val_acc', patience=patience)]
#--------------------------------載入數據及預處理-------------------------------data = [ [0, '小米粥是以小米作為主要食材熬制而成的粥,口味清淡,清香味,具有簡單易制,健胃消食的特點'], [0, '煮粥時一定要先燒開水然后放入洗凈后的小米'], [0, '蛋白質及氨基酸、脂肪、維生素、礦物質'], [0, '小米是傳統健康食品,可單獨燜飯和熬粥'], [0, '蘋果,是水果中的一種'], [0, '粥的營養價值很高,富含礦物質和維生素,含鈣量豐富,有助于代謝掉體內多余鹽分'], [0, '雞蛋有很高的營養價值,是優質蛋白質、B族維生素的良好來源,還能提供脂肪、維生素和礦物質'], [0, '這家超市的蘋果都非常新鮮'], [0, '在北方小米是主要食物之一,很多地區有晚餐吃小米粥的習俗'], [0, '小米營養價值高,營養全面均衡 ,主要含有碳水化合物'], [0, '蛋白質及氨基酸、脂肪、維生素、鹽分'], [1, '小米、三星、華為,作為安卓三大手機旗艦'], [1, '別再管小米華為了!魅族手機再曝光:這次真的完美了'], [1, '蘋果手機或將重陷2016年困境,但這次它無法再大幅提價了'], [1, '三星想要繼續壓制華為,僅憑A70還不夠'], [1, '三星手機屏占比將再創新高,超華為及蘋果旗艦'], [1, '華為P30、三星A70爆賣,斬獲蘇寧最佳手機營銷獎'], [1, '雷軍,用一張圖告訴你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上線,適配深度系統'], [1, '三星剛剛更新了自家的可穿戴設備APP'], [1, '華為、小米跨界并不可怕,可怕的打不破內心的“天花板”'],]
#中文分析X, Y = [lcut(i[1]) for i in data], [i[0] for i in data]
#劃分訓練集和預測集X_train, X_test, y_train, y_test = train_test_split(X, Y)#print(X_train)print(len(X_train), len(X_test))print(len(y_train), len(y_test))"""['三星', '剛剛', '更新', '了', '自家', '的', '可', '穿戴', '設備', 'APP']"""
#--------------------------------Word2Vec詞向量-------------------------------word2vec = Word2Vec(X_train, size=max_features, min_count=1) #最大特征 最低過濾頻次1print(word2vec)
#映射特征詞w2i = {w:i for i, w in enumerate(word2vec.wv.index2word)}print("【顯示詞語】")print(word2vec.wv.index2word)print(w2i)"""['小米', '三星', '是', '維生素', '蛋白質', '及', 'APP', '氨基酸',...""""""{',': 0, '的': 1, '小米': 2, '、': 3, '華為': 4, ....}"""
#詞向量計算vectors = word2vec.wv.vectorsprint("【詞向量矩陣】")print(vectors.shape)print(vectors)
#自定義函數-獲取詞向量def w2v(w): i = w2i.get(w) return vectors[i] if i else zeros(max_features)
#自定義函數-序列預處理def pad(ls_of_words): a = [[w2v(i) for i in x] for x in ls_of_words] a = pad_sequences(a, maxlen, dtype='float') return a
#序列化處理 轉換為詞向量X_train, X_test = pad(X_train), pad(X_test)print(X_train.shape)print(X_test.shape)"""(15, 40, 20) 15個樣本 40個特征 每個特征用20詞向量表示"""
#拉直形狀 (15, 40, 20)=>(15, 40*20) (6, 40, 20)=>(6, 40*20)X_train = X_train.reshape(len(y_train), maxlen*max_features)X_test = X_test.reshape(len(y_test), maxlen*max_features)
#--------------------------------建模與訓練-------------------------------model = Sequential()
#構建Embedding層 128代表Embedding層的向量維度model.add(Embedding(max_features, 128))
#構建LSTM層model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
#構建全連接層#注意上面構建LSTM層時只會得到最后一個節點的輸出,如果需要輸出每個時間點的結果需將return_sequences=Truemodel.add(Dense(units=1, activation='sigmoid'))
#模型可視化model.summary()
#激活神經網絡 model.compile(optimizer = 'rmsprop', #RMSprop優化器 loss = 'binary_crossentropy', #二元交叉熵損失 metrics = ['acc'] #計算誤差或準確率 )
#訓練history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------預測與可視化------------------------------#預測score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可視化acc = history.history['acc']val_acc = history.history['val_acc']
# 設置類標plt.xlabel("Iterations")plt.ylabel("Accuracy")
#繪圖plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("LSTM-Word2vec")plt.show()
輸出結果如下所示,仍然不理想。
- test loss: 0.712007462978363
- test accuracy: 0.33333334
Model: "sequential_22"_________________________________________________________________Layer (type) Output Shape Param # =================================================================embedding_8 (Embedding) (None, None, 128) 2560 _________________________________________________________________lstm_8 (LSTM) (None, 128) 131584 _________________________________________________________________dense_21 (Dense) (None, 1) 129 =================================================================Total params: 134,273Trainable params: 134,273Non-trainable params: 0_________________________________________________________________Train on 15 samples, validate on 6 samplesEpoch 1/915/15 [==============================] - 8s 552ms/sample - loss: 0.6971 - acc: 0.5333 - val_loss: 0.6911 - val_acc: 0.6667Epoch 2/915/15 [==============================] - 5s 304ms/sample - loss: 0.6910 - acc: 0.7333 - val_loss: 0.7111 - val_acc: 0.3333Epoch 3/915/15 [==============================] - 3s 208ms/sample - loss: 0.7014 - acc: 0.4667 - val_loss: 0.7392 - val_acc: 0.3333Epoch 4/915/15 [==============================] - 4s 261ms/sample - loss: 0.6890 - acc: 0.5333 - val_loss: 0.7471 - val_acc: 0.3333Epoch 5/915/15 [==============================] - 4s 248ms/sample - loss: 0.6912 - acc: 0.5333 - val_loss: 0.7221 - val_acc: 0.3333Epoch 6/915/15 [==============================] - 3s 210ms/sample - loss: 0.6857 - acc: 0.5333 - val_loss: 0.7143 - val_acc: 0.3333Epoch 7/915/15 [==============================] - 3s 187ms/sample - loss: 0.6906 - acc: 0.5333 - val_loss: 0.7346 - val_acc: 0.3333Epoch 8/915/15 [==============================] - 3s 185ms/sample - loss: 0.7066 - acc: 0.5333 - val_loss: 0.7578 - val_acc: 0.3333Epoch 9/915/15 [==============================] - 4s 235ms/sample - loss: 0.7197 - acc: 0.5333 - val_loss: 0.7120 - val_acc: 0.33336/6 [==============================] - 0s 43ms/sample - loss: 0.7120 - acc: 0.3333test loss: 0.712007462978363test accuracy: 0.33333334
對應的圖形如下所示。

3.LSTM+TFIDF文本分類
同時,補充LSTM+TFIDF文本分類代碼。
# -*- coding: utf-8 -*-"""Created on Sat Mar 28 22:10:20 2020@author: Eastmount CSDN"""from jieba import lcutfrom numpy import zerosimport matplotlib.pyplot as pltfrom gensim.models import Word2Vecfrom sklearn.model_selection import train_test_splitfrom tensorflow.python.keras.preprocessing.sequence import pad_sequencesfrom tensorflow.python.keras.models import Sequentialfrom tensorflow.python.keras.layers import Dense, LSTM, GRU, Embeddingfrom tensorflow.python.keras.callbacks import EarlyStopping
#-----------------------------------定義參數----------------------------------max_features = 20 #詞向量維度units = 30 #RNN神經元數量maxlen = 40 #序列最大長度epochs = 9 #訓練最大輪數batch_size = 12 #每批數據量大小verbose = 1 #訓練過程展示patience = 1 #沒有進步的訓練輪數
callbacks = [EarlyStopping('val_acc', patience=patience)]
#--------------------------------載入數據及預處理-------------------------------data = [ [0, '小米粥是以小米作為主要食材熬制而成的粥,口味清淡,清香味,具有簡單易制,健胃消食的特點'], [0, '煮粥時一定要先燒開水然后放入洗凈后的小米'], [0, '蛋白質及氨基酸、脂肪、維生素、礦物質'], [0, '小米是傳統健康食品,可單獨燜飯和熬粥'], [0, '蘋果,是水果中的一種'], [0, '粥的營養價值很高,富含礦物質和維生素,含鈣量豐富,有助于代謝掉體內多余鹽分'], [0, '雞蛋有很高的營養價值,是優質蛋白質、B族維生素的良好來源,還能提供脂肪、維生素和礦物質'], [0, '這家超市的蘋果都非常新鮮'], [0, '在北方小米是主要食物之一,很多地區有晚餐吃小米粥的習俗'], [0, '小米營養價值高,營養全面均衡 ,主要含有碳水化合物'], [0, '蛋白質及氨基酸、脂肪、維生素、鹽分'], [1, '小米、三星、華為,作為安卓三大手機旗艦'], [1, '別再管小米華為了!魅族手機再曝光:這次真的完美了'], [1, '蘋果手機或將重陷2016年困境,但這次它無法再大幅提價了'], [1, '三星想要繼續壓制華為,僅憑A70還不夠'], [1, '三星手機屏占比將再創新高,超華為及蘋果旗艦'], [1, '華為P30、三星A70爆賣,斬獲蘇寧最佳手機營銷獎'], [1, '雷軍,用一張圖告訴你:小米和三星的差距在哪里'], [1, '小米米聊APP官方Linux版上線,適配深度系統'], [1, '三星剛剛更新了自家的可穿戴設備APP'], [1, '華為、小米跨界并不可怕,可怕的打不破內心的“天花板”'],]
#中文分詞X, Y = [' '.join(lcut(i[1])) for i in data], [i[0] for i in data]print(X)print(Y)#['煮粥 時 一定 要 先燒 開水 然后 放入 洗凈 后 的 小米', ...]
#--------------------------------------計算詞頻------------------------------------from sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformer
#將文本中的詞語轉換為詞頻矩陣vectorizer = CountVectorizer()
#計算個詞語出現的次數X_data = vectorizer.fit_transform(X)print(X_data)
#獲取詞袋中所有文本關鍵詞word = vectorizer.get_feature_names()print('【查看單詞】')for w in word: print(w, end = " ")else: print("")
#詞頻矩陣print(X_data.toarray())
#將詞頻矩陣X統計成TF-IDF值transformer = TfidfTransformer()tfidf = transformer.fit_transform(X_data)
#查看數據結構 tfidf[i][j]表示i類文本中的tf-idf權重weight = tfidf.toarray()print(weight)
#數據集劃分X_train, X_test, y_train, y_test = train_test_split(weight, Y)print(X_train.shape, X_test.shape)print(len(y_train), len(y_test))#(15, 117) (6, 117) 15 6
#--------------------------------建模與訓練-------------------------------model = Sequential()
#構建Embedding層 128代表Embedding層的向量維度model.add(Embedding(max_features, 128))
#構建LSTM層model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
#構建全連接層#注意上面構建LSTM層時只會得到最后一個節點的輸出,如果需要輸出每個時間點的結果需將return_sequences=Truemodel.add(Dense(units=1, activation='sigmoid'))
#模型可視化model.summary()
#激活神經網絡 model.compile(optimizer = 'rmsprop', #RMSprop優化器 loss = 'binary_crossentropy', #二元交叉熵損失 metrics = ['acc'] #計算誤差或準確率 )
#訓練history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=verbose, validation_data=(X_test, y_test))
#----------------------------------預測與可視化------------------------------#預測score = model.evaluate(X_test, y_test, batch_size=batch_size)print('test loss:', score[0])print('test accuracy:', score[1])
#可視化acc = history.history['acc']val_acc = history.history['val_acc']
# 設置類標plt.xlabel("Iterations")plt.ylabel("Accuracy")
#繪圖plt.plot(range(epochs), acc, "bo-", linewidth=2, markersize=12, label="accuracy")plt.plot(range(epochs), val_acc, "gs-", linewidth=2, markersize=12, label="val_accuracy")plt.legend(loc="upper left")plt.title("LSTM-TFIDF")plt.show()
輸出結果如下所示:
- test loss: 0.7694947719573975
- test accuracy: 0.33333334
Model: "sequential_1"_________________________________________________________________Layer (type) Output Shape Param # =================================================================embedding_1 (Embedding) (None, None, 128) 2560 _________________________________________________________________lstm_1 (LSTM) (None, 128) 131584 _________________________________________________________________dense_1 (Dense) (None, 1) 129 =================================================================Total params: 134,273Trainable params: 134,273Non-trainable params: 0_________________________________________________________________Train on 15 samples, validate on 6 samplesEpoch 1/915/15 [==============================] - 2s 148ms/sample - loss: 0.6898 - acc: 0.5333 - val_loss: 0.7640 - val_acc: 0.3333Epoch 2/915/15 [==============================] - 1s 48ms/sample - loss: 0.6779 - acc: 0.6000 - val_loss: 0.7773 - val_acc: 0.3333Epoch 3/915/15 [==============================] - 1s 36ms/sample - loss: 0.6769 - acc: 0.6000 - val_loss: 0.7986 - val_acc: 0.3333Epoch 4/915/15 [==============================] - 1s 47ms/sample - loss: 0.6722 - acc: 0.6000 - val_loss: 0.8097 - val_acc: 0.3333Epoch 5/915/15 [==============================] - 1s 42ms/sample - loss: 0.7021 - acc: 0.6000 - val_loss: 0.7680 - val_acc: 0.3333Epoch 6/915/15 [==============================] - 1s 36ms/sample - loss: 0.6890 - acc: 0.6000 - val_loss: 0.8147 - val_acc: 0.3333Epoch 7/915/15 [==============================] - 1s 37ms/sample - loss: 0.6906 - acc: 0.6000 - val_loss: 0.8599 - val_acc: 0.3333Epoch 8/915/15 [==============================] - 1s 43ms/sample - loss: 0.6819 - acc: 0.6000 - val_loss: 0.8303 - val_acc: 0.3333Epoch 9/915/15 [==============================] - 1s 40ms/sample - loss: 0.6884 - acc: 0.6000 - val_loss: 0.7695 - val_acc: 0.33336/6 [==============================] - 0s 7ms/sample - loss: 0.7695 - acc: 0.3333test loss: 0.7694947719573975test accuracy: 0.33333334
對應圖形如下:

4.機器學習和深度學習對比分析
最終結果我們進行簡單對比,發現機器學習比深度學習好,這是為什么呢?我們又能做哪些提升呢?
- MultinomialNB+TFIDF:test accuracy = 0.67
- GaussianNB+Word2Vec:test accuracy = 0.83
- RNN+Word2Vector:test accuracy = 0.33333334
- LSTM+Word2Vec:test accuracy = 0.33333334
- LSTM+TFIDF:test accuracy = 0.33333334
作者結合大佬們的文章及自己的經驗對其進行簡單分析,原因如下:
- 一是 數據集預處理的原因,上述代碼沒有進行停用詞過濾,大量標點符號和停用詞影響了文本分類效果。同時詞向量的維度設置也需要進行調試。
- 二是 數據集大小的原因。數據量少的情況下,推薦使用CNN,RNN的過擬合會讓你欲哭無淚。如果數據量多,也許RNN效果會更好。如果為了創新,RLSTM和RCNN等都是近幾年不錯的選擇。但如果僅僅是為了應用,用普通的機器學習方法已經足夠優秀了(特別是新聞數據集),如果考慮上時間成本,貝葉斯無疑才是真正的最好選擇。
- 三是 CNN和RNN適用性不同。CNN擅長空間特征的學習和捕獲,RNN擅長時序特征的捕獲。從結構來講,RNN更勝一籌。主流的NLP問題,比如翻譯、生成文本,seq2seq(倆獨立RNN)的引入突破了很多之前的benchmark。Attention的引入是為了解決長句問題,其本質就是外掛了額外的一個softmax去學詞和詞的映射關系,有點像外掛存儲,其根源來自一篇名為“neural turing machine”的paper。
- 四是 不同的數據集適應不同的方法,各種方法各有所長。有的情感分析GRU好于CNN,而新聞分類、文本分類競賽CNN可能會有優勢。CNN具有速度優勢,基本比較大的數據上CNN能加大參數,擬合更多種類的local phrase frequency,獲得更好的效果。如果你是想做系統,兩個算法又各有所長,就是ensemble登場的時候了。
- 五是 在文本情感分類領域,GRU是要好于CNN,并且隨著句子長度的增長,GRU的這一優勢會進一步放大。當句子的情感分類是由整個句子決定的時候,GRU會更容易分類正確, 當句子的情感分類是由幾個局部的key-phrases決定的時候,CNN會更容易分類正確。
總之,我們在真實的實驗中,盡量選擇適合我們數據集的算法,這也是實驗中的一部分,我們需要對比各種算法、各種參數、各種學習模型,從而找到一個更好的算法。后續作者會進一步學習TextCNN、Attention、BiLSTM、GAN等算法,希望能與大家一起進步。
參考及推薦文章:請問對于中文長文本分類,是CNN效果好,還是RNN效果好?- 知乎
五.總結
寫道這里,這篇文章就結束了。希望對您有所幫助,同時文章中不足或錯誤的地方,歡迎讀者提出。這些實驗都是我在做論文研究或項目評價常見的一些問題,希望讀者帶著這些問題,結合自己的需求進行深入的思考,更希望大家能學以致用。最后如果文章對您有幫助,請點贊、評論、收藏,這將是我分享最大的動力。
總之,本文通過Keras實現了一個RNN文本分類學習的案例,并詳細介紹了循環神經網絡原理知識及與機器學習對比。最后,作為人工智能的菜鳥,我希望自己能不斷進步并深入,后續將它應用于圖像識別、網絡安全、對抗樣本等領域,指導大家撰寫簡單的學術論文,一起加油!
考研、考博的童鞋加油,在我心中,你們永遠是最棒的。這一年我們一起相互鼓勵,陪伴著前行,今年是真的辛苦,祝大家都能考上心儀的學校。最后,合理利用好時間,試卷都寫滿,專業難題都往知識點靠,加油!