文字分類:Keras+RNN vs傳統機器學習

華為雲開發者社群發表於2021-11-30
摘要:本文通過Keras實現了一個RNN文字分類學習的案例,並詳細介紹了迴圈神經網路原理知識及與機器學習對比。

本文分享自華為雲社群《基於Keras+RNN的文字分類vs基於傳統機器學習的文字分類》,作者: eastmount 。

一.RNN文字分類

1.RNN

迴圈神經網路英文是Recurrent Neural Networks,簡稱RNN。RNN的本質概念是利用時序資訊,在傳統神經網路中,假設所有的輸入(以及輸出)都各自獨立。但是,對於很多工而言,這非常侷限。舉個例子,假如你想根據一句沒說完的話,預測下一個單詞,最好的辦法就是聯絡上下文的資訊。而RNN(迴圈神經網路)之所以是“迴圈”,是因為它們對序列的每個元素執行相同的任務,而每次的結果都獨立於之前的計算。

假設有一組資料data0、data1、data2、data3,使用同一個神經網路預測它們,得到對應的結果。如果資料之間是有關係的,比如做菜下料的前後步驟,英文單詞的順序,如何讓資料之間的關聯也被神經網路學習呢?這就要用到——RNN。

比如存在ABCD數字,需要預測下一個數字E,會根據前面ABCD順序進行預測,這就稱為記憶。預測之前,需要回顧以前的記憶有哪些,再加上這一步新的記憶點,最終輸出output,迴圈神經網路(RNN)就利用了這樣的原理。

文字分類:Keras+RNN vs傳統機器學習

首先,讓我們想想人類是怎麼分析事物之間的關聯或順序的。人類通常記住之前發生的事情,從而幫助我們後續的行為判斷,那麼是否能讓計算機也記住之前發生的事情呢?

在分析data0時,我們把分析結果存入記憶Memory中,然後當分析data1時,神經網路(NN)會產生新的記憶,但此時新的記憶和老的記憶沒有關聯,如上圖所示。在RNN中,我們會簡單的把老記憶呼叫過來分析新記憶,如果繼續分析更多的資料時,NN就會把之前的記憶全部累積起來。

文字分類:Keras+RNN vs傳統機器學習

下面是一個典型的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個單詞,那麼,橫向展開網路後將有五層神經網路,一層對應一個單詞。

文字分類:Keras+RNN vs傳統機器學習

總之,只要你的資料是有順序的,就可以使用RNN,比如人類說話的順序,電話號碼的順序,影像畫素排列的順序,ABC字母的順序等。RNN常用於自然語言處理、機器翻譯、語音識別、影像識別等領域。

2.文字分類

文字分類旨在對文字集按照一定的分類體系或標準進行自動分類標記,屬於一種基於分類體系的自動分類。文字分類最早可以追溯到上世紀50年代,那時主要通過專家定義規則來進行文字分類;80年代出現了利用知識工程建立的專家系統;90年代開始藉助於機器學習方法,通過人工特徵工程和淺層分類模型來進行文字分類。現在多采用詞向量以及深度神經網路來進行文字分類。

文字分類:Keras+RNN vs傳統機器學習

牛亞峰老師將傳統的文字分類流程歸納如下圖所示。在傳統的文字分類中,基本上大部分機器學習方法都在文字分類領域有所應用。主要包括:

  • Naive Bayes
  • KNN
  • SVM
  • 集合類方法
  • 最大熵
  • 神經網路

文字分類:Keras+RNN vs傳統機器學習

利用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文字分類

資料集採用基基偉老師的自定義文字,共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 CountVectorizer
from 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("\n")

#詞頻矩陣
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_report
from 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 PCA
import 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 6
15 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         6
weighted avg       0.83      0.67      0.67         6

繪製圖形如下圖所示:

文字分類:Keras+RNN vs傳統機器學習

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 lcut
from numpy import zeros
from gensim.models import Word2Vec
from sklearn.model_selection import train_test_split
from 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) #最大特徵 最低過濾頻次1
print(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.vectors
print("【詞向量矩陣】")
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 GaussianNB
from sklearn.metrics import classification_report
from 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 6
15 6
Word2Vec(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         6
weighted 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資料集百度雲連結:

作者將下載後的資料放在C:\Users\Administrator.keras\datasets資料夾下,如下圖所示。

文字分類:Keras+RNN vs傳統機器學習

該資料集是網際網路電影資料庫(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 Database
from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Flatten, Embedding

#-----------------------------------定義引數-----------------------------------
max_features = 20000       #按詞頻大小取樣本前20000個詞
input_dim = max_features   #詞庫大小 必須>=max_features
maxlen = 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_dim
model.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/2
25000/25000 [==============================] - 2s 98us/step - loss: 0.6111 - acc: 0.6956
Epoch 2/2
25000/25000 [==============================] - 2s 69us/step - loss: 0.3578 - acc: 0.8549
Model: "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,201
Trainable params: 803,201
Non-trainable params: 0

_________________________________________________________________

顯示矩陣如下圖所示:

文字分類:Keras+RNN vs傳統機器學習

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 Database
from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Flatten, Embedding
from keras.layers import SimpleRNN

#-----------------------------------定義引數-----------------------------------
max_features = 20000       #按詞頻大小取樣本前20000個詞
input_dim = max_features   #詞庫大小 必須>=max_features
maxlen = 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 Cell
model.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 plt
accuracy = 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可以用下圖進行形象的表示。

文字分類:Keras+RNN vs傳統機器學習

(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,449
Trainable params: 804,449
Non-trainable params: 0
_________________________________________________________________

Train on 22500 samples, validate on 2500 samples
Epoch 1/3
 - 11s - loss: 0.5741 - accuracy: 0.6735 - val_loss: 0.4462 - val_accuracy: 0.7876
Epoch 2/3
 - 14s - loss: 0.3572 - accuracy: 0.8430 - val_loss: 0.4928 - val_accuracy: 0.7616
Epoch 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

文字分類:Keras+RNN vs傳統機器學習

四.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) #最大特徵 最低過濾頻次1
print(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.vectors
print("【詞向量矩陣】")
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 6
15 6
Word2Vec(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()

#雙向RNN
model.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值非常不理想。怎麼解決呢?

文字分類:Keras+RNN vs傳統機器學習

神經網路模型和Epoch訓練結果如下圖所示:

  • test loss: 0.7160684466362
  • test accuracy: 0.33333334

文字分類:Keras+RNN vs傳統機器學習

這裡補充一個知識點——EarlyStopping。

EarlyStopping是Callbacks的一種,callbacks用於指定在每個epoch開始和結束的時候進行哪種特定操作。Callbacks中有一些設定好的介面,可以直接使用,如acc、val_acc、loss和val_loss等。EarlyStopping則是用於提前停止訓練的callbacks,可以達到當訓練集上的loss不在減小(即減小的程度小於某個閾值)的時候停止繼續訓練。上面程式中,當我們loss不在減小時就可以呼叫Callbacks停止訓練。
推薦文章:[深度學習] keras的EarlyStopping使用與技巧 - zwqjoy

最後給出該部分的完整程式碼:

# -*- coding: utf-8 -*-
"""
Created on Sat Mar 28 22:10:20 2020
@author: Eastmount CSDN
"""
from jieba import lcut
from numpy import zeros
import matplotlib.pyplot as plt
from gensim.models import Word2Vec
from sklearn.model_selection import train_test_split
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, GRU, Bidirectional
from 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) #最大特徵 最低過濾頻次1
print(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.vectors
print("【詞向量矩陣】")
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()

#雙向RNN
model.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 lcut
from numpy import zeros
import matplotlib.pyplot as plt
from gensim.models import Word2Vec
from sklearn.model_selection import train_test_split
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, LSTM, GRU, Embedding
from 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) #最大特徵 最低過濾頻次1
print(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.vectors
print("【詞向量矩陣】")
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=True
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("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,273
Trainable params: 134,273
Non-trainable params: 0
_________________________________________________________________
Train on 15 samples, validate on 6 samples
Epoch 1/9
15/15 [==============================] - 8s 552ms/sample - loss: 0.6971 - acc: 0.5333 - val_loss: 0.6911 - val_acc: 0.6667
Epoch 2/9
15/15 [==============================] - 5s 304ms/sample - loss: 0.6910 - acc: 0.7333 - val_loss: 0.7111 - val_acc: 0.3333
Epoch 3/9
15/15 [==============================] - 3s 208ms/sample - loss: 0.7014 - acc: 0.4667 - val_loss: 0.7392 - val_acc: 0.3333
Epoch 4/9
15/15 [==============================] - 4s 261ms/sample - loss: 0.6890 - acc: 0.5333 - val_loss: 0.7471 - val_acc: 0.3333
Epoch 5/9
15/15 [==============================] - 4s 248ms/sample - loss: 0.6912 - acc: 0.5333 - val_loss: 0.7221 - val_acc: 0.3333
Epoch 6/9
15/15 [==============================] - 3s 210ms/sample - loss: 0.6857 - acc: 0.5333 - val_loss: 0.7143 - val_acc: 0.3333
Epoch 7/9
15/15 [==============================] - 3s 187ms/sample - loss: 0.6906 - acc: 0.5333 - val_loss: 0.7346 - val_acc: 0.3333
Epoch 8/9
15/15 [==============================] - 3s 185ms/sample - loss: 0.7066 - acc: 0.5333 - val_loss: 0.7578 - val_acc: 0.3333
Epoch 9/9
15/15 [==============================] - 4s 235ms/sample - loss: 0.7197 - acc: 0.5333 - val_loss: 0.7120 - val_acc: 0.3333
6/6 [==============================] - 0s 43ms/sample - loss: 0.7120 - acc: 0.3333
test loss: 0.712007462978363
test accuracy: 0.33333334

對應的圖形如下所示。

文字分類:Keras+RNN vs傳統機器學習

3.LSTM+TFIDF文字分類

同時,補充LSTM+TFIDF文字分類程式碼。

# -*- coding: utf-8 -*-
"""
Created on Sat Mar 28 22:10:20 2020
@author: Eastmount CSDN
"""
from jieba import lcut
from numpy import zeros
import matplotlib.pyplot as plt
from gensim.models import Word2Vec
from sklearn.model_selection import train_test_split
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, LSTM, GRU, Embedding
from 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 CountVectorizer
from 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("\n")

#詞頻矩陣
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=True
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("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,273
Trainable params: 134,273
Non-trainable params: 0
_________________________________________________________________
Train on 15 samples, validate on 6 samples
Epoch 1/9
15/15 [==============================] - 2s 148ms/sample - loss: 0.6898 - acc: 0.5333 - val_loss: 0.7640 - val_acc: 0.3333
Epoch 2/9
15/15 [==============================] - 1s 48ms/sample - loss: 0.6779 - acc: 0.6000 - val_loss: 0.7773 - val_acc: 0.3333
Epoch 3/9
15/15 [==============================] - 1s 36ms/sample - loss: 0.6769 - acc: 0.6000 - val_loss: 0.7986 - val_acc: 0.3333
Epoch 4/9
15/15 [==============================] - 1s 47ms/sample - loss: 0.6722 - acc: 0.6000 - val_loss: 0.8097 - val_acc: 0.3333
Epoch 5/9
15/15 [==============================] - 1s 42ms/sample - loss: 0.7021 - acc: 0.6000 - val_loss: 0.7680 - val_acc: 0.3333
Epoch 6/9
15/15 [==============================] - 1s 36ms/sample - loss: 0.6890 - acc: 0.6000 - val_loss: 0.8147 - val_acc: 0.3333
Epoch 7/9
15/15 [==============================] - 1s 37ms/sample - loss: 0.6906 - acc: 0.6000 - val_loss: 0.8599 - val_acc: 0.3333
Epoch 8/9
15/15 [==============================] - 1s 43ms/sample - loss: 0.6819 - acc: 0.6000 - val_loss: 0.8303 - val_acc: 0.3333
Epoch 9/9
15/15 [==============================] - 1s 40ms/sample - loss: 0.6884 - acc: 0.6000 - val_loss: 0.7695 - val_acc: 0.3333
6/6 [==============================] - 0s 7ms/sample - loss: 0.7695 - acc: 0.3333
test loss: 0.7694947719573975
test accuracy: 0.33333334

對應圖形如下:

文字分類:Keras+RNN vs傳統機器學習

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等演算法,希望能與大家一起進步。

參考文獻:

再次感謝參考文獻前輩和老師們的貢獻,同時也參考了作者的Python人工智慧和資料分析系列文章,原始碼請在github下載。

[1] Keras的imdb和MNIST資料集無法下載問題解決 - 摸金青年v
[2] 官網例項詳解4.42(imdb.py)-keras學習筆記四 - wyx100
[3] Keras文字分類 - 強推基基偉老師文章
[4] TextCNN文字分類(keras實現)- 強推Asia-Lee老師的文章
[5] Keras之文字分類實現 - 強推知乎王奕磊老師的文章
[6 ]自然語言處理入門(二)–Keras實現BiLSTM+Attention新聞標題文字分類 - ilivecode
[7] 用深度學習(CNN RNN Attention)解決大規模文字分類問題 - 綜述和實踐 - 知乎清凇
[8] 基於 word2vec 和 CNN 的文字分類 :綜述 & 實踐 - 牛亞峰serena
[9] https://github.com/keras-team/keras
[10] [深度學習] keras的EarlyStopping使用與技巧 - zwqjoy
[11] 深度學習為什麼會出現validation accuracy大於train accuracy的現象?- ICOZ
[12] Keras實現CNN文字分類 - vivian_ll
[13] Keras文字分類實戰(上)- weixin_34351321
[14] 請問對於中文長文字分類,是CNN效果好,還是RNN效果好?- 知乎

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章