1. 程式人生 > >TensorFlow之tf.keras的文字分類

TensorFlow之tf.keras的文字分類

這段時間在學習TensorFlow,這些都是一些官網上的例子,在這裡和大家分享記錄一下。

此教程使用評論文字將電影評論分類為正面或負面。這是二元或兩類分類的一個例子,這是一種重要且廣泛適用的機器學習問題。

我們將使用包含來自Internet電影資料庫的50,000條電影評論文字的IMDB資料集。這些分為25,000條培訓評論和25,000條評審評論。訓練集和測試集包含相同數量的正面和負面評論。

教程使用tf.keras,一個高階API,用於在TensorFlow中構建和訓練模型。有關使用更高階的文字分類教程tf.keras,請參閱MLCC文字分類指南

1、下載IMDB資料集

IMDB資料集與TensorFlow一起打包。它已經被預處理,使得評論(單詞序列)已經被轉換為整數序列,其中每個整數表示字典中的特定單詞。

以下程式碼將IMDB資料集下載到您的計算機(如果您已經下載了它,則會自動使用快取副本):

imdb = keras.datasets.imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

該引數num_words=10000保留了訓練資料中最常出現的10,000個單詞。丟棄罕見的單詞以保持資料的大小可管理。

2、探索資料

我們花一點時間來了解資料的格式。資料集經過預處理:每個示例都是一個整數陣列,表示電影評論的單詞。每個標籤都是0或1的整數值,其中0表示負面評論,1表示正面評論。

print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))

輸出:

Training entries: 25000, labels: :25000

評論文字已轉換為整數(在機器學習處理文字中,一般都會根據語料建立一個字型檔,用詞語所在序號來替換掉語料中的文字),其中每個整數表示字典中的特定單詞。以下是第一篇評論的內容:

print(train_data[0])

輸出:

[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,112,167,2,336,385,39,4,172,4536,1111,17,546,38,13,447 ,4,192,50,16,6,147,2025,19,14,22,4,11920,4613,469,4,22,71,87,12,16,43,530,38,76,15 ,13,1247,4,22,17,515,17,12,16,626,18,2,5,62,386,12,8,316,8,106,5,4,2223,5244,16 ,480,66,3785,33,4,130,12,16,38,619,5,25,124,51,36,135,48,25,1415,33,6,22,12,215,28 ,77,52,5,14,407,16,82,2,8,10,10,117,5952,15,256,4,2,7,3766,5,723,36,71,43,530 ,476,26,400,317,46,7,4,2,1029,13,104,88,4,381,15,297,98,32,2071,56,26,141,6,194,7486 ,18,4,226,22,21,134,476,26,480,5,144,30,5535,18,51,36,28,224,92,25,104,4,226,65,16 ,38,1334,88,12,16,283,5,16,4472,113,103,32,15,16,5345,19,178,32]

電影評論的長度可能不同。以下程式碼顯示了第一次和第二次評論中的字數。由於對神經網路的輸入必須是相同的長度,我們稍後需要解決此問題。

len(train_data[0]), len(train_data[1])

輸出:

(218,189)

將整數轉換回單詞

瞭解如何將整數轉換回文字可能很有用。在這裡,我們將建立一個輔助函式來查詢包含整數到字串對映的字典物件:

# A dictionary mapping words to an integer index
word_index = imdb.get_word_index()

# The first indices are reserved
word_index = {k:(v+3) for k,v in word_index.items()} 
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2  # unknown
word_index["<UNUSED>"] = 3

reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])

現在我們可以使用該decode_review函式顯示第一次稽核的文字:

decode_review(train_data[0])

輸出:

" this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert  is an amazing actor and now the same being director  father came from the same scottish island as myself so i loved the fact 
there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for  and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was 
so sad and you know what they say if you cry at a film it must have been good and this definitely was also  to the two little boy's that played the  of norman and paul they were just brilliant 
children are often left out of the  list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was 
someone's life after all that was shared with us all"

3、準備資料

reviews—the arrays of integers 必須在輸入神經網路之前轉換為張量。這種轉換可以通過以下兩種方式完成:

  • 對陣列進行單熱編碼,將其轉換為0和1的向量。例如,序列[3,5]將成為10,000維向量,除了索引3和5(其為1)之外全部為零。然後,將其作為我們網路中的第一層 - 一個可以處理浮點向量資料的Dense層。然而,這種方法是儲存器密集型的,需要num_words * num_reviews大小矩陣。

  • 或者,我們可以填充陣列,使它們都具有相同的長度,然後建立一個整數張量的形狀max_length * num_reviews。我們可以使用能夠處理這種形狀的嵌入層作為我們網路中的第一層。

在本教程中,我們將使用第二種方法。

由於電影評論的長度必須相同,我們將使用pad_sequences函式來標準化長度:

train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                        value=word_index["<PAD>"],
                                                        padding='post',
                                                        maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                       value=word_index["<PAD>"],
                                                       padding='post',
                                                       maxlen=256)

這裡介紹一下pad_sequences函式 tf.keras.preprocessing.sequence.pad_sequences( sequences, maxlen=None, dtype=‘int32’, padding=‘pre’, truncating=‘pre’, value=0.0) 此函式將 num_samples序列列表(整數列表)轉換為2D Numpy形狀陣列(num_samples, num_timesteps)。 num_timesteps是maxlen提供的引數,或者是最長序列的長度。 短於的序列在末尾num_timesteps 填充value。 序列長於num_timesteps截短的序列,以便它們符合所需的長度。填充或截斷髮生的位置分別由引數padding和truncating。 預填充是預設值。 引數: 1、sequences:列表列表,其中每個元素都是一個序列。 2、maxlen:Int,所有序列的最大長度。 3、dtype:輸出序列的型別。 4、padding:String,‘pre’或’post’:在每個序列之前或之後填充。 5、truncating:String,‘pre’或’post’:從序列maxlen的開頭或結尾處大於序列的值中刪除值 。 6、value:浮點數,填充值。 返回: x:Numpy陣列形狀 (len(sequences), maxlen)

我們現在看一下示例的長度:

len(train_data[0]), len(train_data[1])

輸出:

(256,256)

並檢查(已填充)審查:

[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 112 167 2 336 385 39 4 
  172 4536 1111 17 546 38 13 447 4 192 50 16 6 147 
 2025 19 14 22 8 1920 4613 469 4 22 71 87 12 16 
   43 530 38 76 15 13 1247 4 22 17 515 17 12 16 
  626 18 2 5 62 386 12 8 316 8 106 5 4 2223 
 5244 16 480 66 3785 33 4 130 12 16 38 619 5 25 
  124 51 36 135 48 25 1415 33 6 22 12 215 28 77 
   52 5 14 407 16 82 2 8 4 107 117 5952 15 256
    4 2 7 3766 5 723 36 71 43 530 476 26 400 317 
   46 7 4 2 1029 13 104 88 4 381 15 297 98 32 
 2071 56 26 141 6 194 7486 18 4 226 22 21 134 476 
   26 480 5 144 30 5535 18 51 36 28 224 92 25 104 
    4 226 65 16 38 1334 88 12 16 283 5 16 4472 113 
  103 32 15 16 5345 19 178 32 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0]

4、建立模型

神經網路是通過堆疊層建立的 - 這需要兩個主要的架構決策:

  • 模型中要使用多少層?
  • 每層使用多少隱藏單位?

在此示例中,輸入資料由單詞索引陣列組成。要預測的標籤是0或1?讓我們為這個問題建立一個模型:

# input shape is the vocabulary count used for the movie reviews (10,000 words)
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))

model.summary()

輸出:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, None, 16)          160000    
_________________________________________________________________
global_average_pooling1d_1 ( (None, 16)                0         
_________________________________________________________________
dense_2 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________

這4個圖層按順序堆疊以構建分類器:

  1. 第一層是Embedding層。該層採用整數編碼的詞彙表,併為每個詞索引查詢嵌入向量。這些向量是作為模型訓練學習的。向量為輸出陣列新增維度。得到的尺寸是:(batch, sequence, embedding)。
  2. 接下來,GlobalAveragePooling1D通過對序列維度求平均,層為每個示例返回固定長度的輸出向量。這允許模型以最簡單的方式處理可變長度的輸入。想了解池化層可以看知乎的一篇文章
  3. 這個固定長度的輸出向量通過一個Dense帶有16個隱藏單元的完全連線層(Dense)傳輸。想了解全連線層可以看知乎的一篇文章
  4. 最後一層與單個輸出節點密集連線。使用sigmoid啟用函式,此值是介於0和1之間的浮點數,表示概率或置信度。想了解啟用函式可以看知乎的一篇文章

這裡說一下第一層的keras.layers.embeddings.Embedding

  • Embedding將正整數(下標)轉換為具有固定大小的向量,如[[4],[20]]->[[0.25,0.1],[0.6,-0.2]]
  • Embedding層只能作為模型的第一層

引數

  • input_dim:大或等於0的整數,字典長度,即輸入資料最大下標+1
  • output_dim:大於0的整數,代表全連線嵌入的維度
  • embeddings_initializer: 嵌入矩陣的初始化方法,為預定義初始化方法名的字串,或用於初始化權重的初始化器。參考initializers
  • embeddings_regularizer: 嵌入矩陣的正則項,為Regularizer物件
  • embeddings_constraint: 嵌入矩陣的約束項,為Constraints物件
  • mask_zero:布林值,確定是否將輸入中的‘0’看作是應該被忽略的‘填充’(padding)值,該引數在使用遞迴層處理變長輸入時有用。設定為True的話,模型中後續的層必須都支援masking,否則會丟擲異常。如果該值為True,則下標0在字典中不可用,input_dim應設定為|vocabulary| + 1。
  • input_length:當輸入序列的長度固定時,該值為其長度。如果要在該層後接Flatten層,然後接Dense層,則必須指定該引數,否則Dense層的輸出維度無法自動推斷。

輸入shape

  • 例如(samples,sequence_length)的2D張量

輸出shape

  • 例如(samples, sequence_length, output_dim)的3D張量

例子

model = Sequential()
model.add(Embedding(1000, 64, input_length=10))
# the model will take as input an integer matrix of size (batch, input_length).
# the largest integer (i.e. word index) in the input should be no larger than 999 (vocabulary size).
# now model.output_shape == (None, 10, 64), where None is the batch dimension.

input_array = np.random.randint(1000, size=(32, 10))

model.compile('rmsprop', 'mse')
output_array = model.predict(input_array)
assert output_array.shape == (32, 10, 64)

隱藏單元

上述模型在輸入和輸出之間有兩個中間或“隱藏”層。輸出的數量(單位,節點或神經元)是圖層的表示空間的維度。換句話說,在學習內部表示時允許網路的自由度。

如果模型具有更多隱藏單元(更高維度的表示空間)或更多層,則網路可以學習更復雜的表示。但是,它使網路的計算成本更高,並且可能導致學習不需要的patterns—patterns,這些patterns可以提高訓練資料的效能,但不會提高測試資料的效能。這稱為過度擬合,我們稍後會進行探討。

5、損失函式和優化器

模型需要一個損失函式和一個用於訓練的優化器。由於這是二元分類問題和模型輸出概率(具有S形啟用的單個單元層),我們將使用binary_crossentropy損失函式。

這不是損失函式的唯一選擇,例如,您可以選擇mean_squared_error。但是,通常,binary_crossentropy處理概率更好 - 它測量概率分佈之間的“距離”,或者在我們的情況下,測量地面實況分佈和預測之間的“距離”。

後來,當我們探索迴歸問題(比如預測房子的價格)時,我們將看到如何使用另一種稱為均方誤差的損失函式。

現在,配置模型以使用優化器和損失函式:

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='binary_crossentropy',
              metrics=['accuracy'])

6、建立驗證集

在訓練時,我們想要檢查模型在以前沒有見過的資料上的準確性。通過從原始訓練資料中分離10,000個示例來建立驗證集。(為什麼不立即使用測試集?我們的目標是僅使用訓練資料開發和調整我們的模型,然後僅使用測試資料來評估我們的準確性)。

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

其實我們也可以直接跳過這一步,直接在訓練方法中用validation_split=0.2,進行切分

7、訓練模型

以512個樣本的小批量訓練模型40個時期。這是對張量x_train和y_train張量中的所有樣本的40次迭代。在培訓期間,監控模型在驗證集中的10,000個樣本的損失和準確性:

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_split=0.2,
                    verbose=1)

輸出:

Train on 12000 samples, validate on 3000 samples
Epoch 1/40
12000/12000 [==============================] - 3s 279us/step - loss: 0.7570 - acc: 0.4969 - val_loss: 0.6953 - val_acc: 0.4947
Epoch 2/40
12000/12000 [==============================] - 1s 121us/step - loss: 0.6910 - acc: 0.5222 - val_loss: 0.6908 - val_acc: 0.5183
Epoch 3/40
12000/12000 [==============================] - 1s 123us/step - loss: 0.6892 - acc: 0.5382 - val_loss: 0.6878 - val_acc: 0.5800

8、評估模型

讓我們看看模型的表現。將返回兩個值。損失率(代表我們的錯誤的數字,更低的值更好)和準確性。

results = model.evaluate(test_data, test_labels)
print(results)

輸出:

25000/25000 [==============================] - 1s 31us/step
[0.32530811003684995, 0.8664]

這種相當天真的方法可以達到約87%的準確度。採用更先進的方法,模型應該接近95%。

9、建立一段時間內準確性和損失的圖表

model.fit()返回一個History包含字典的物件,其中包含訓練期間發生的所有事情:

history_dict = history.history
# history_dict.keys()
# dict_keys(['val_acc','val_loss','acc','loss'])

有四個條目:在培訓和驗證期間,每個條目對應一個受監控的指標。我們可以使用這些來繪製訓練和驗證損失以進行比較,以及培訓和驗證準確性:

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

第二個準確性圖表:

plt.clf()   # clear figure
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()

在這裡插入圖片描述 在這裡插入圖片描述 在這2張圖中,點表示訓練損失和準確度,實線表示驗證損失和準確度。

請注意,訓練損失隨著每個時期而減少,並且訓練準確度隨著每個時期而增加。這在使用梯度下降優化時是預期的 - 它應該在每次迭代時最小化期望的數量。

這不是驗證損失和準確性的情況 - 它們似乎在大約二十個時代之後達到峰值。這是過度擬合的一個例子:模型在訓練資料上的表現比在以前從未見過的資料上表現得更好。在此之後,模型過度優化並學習特定於訓練資料的表示,這些表示不會推廣到測試資料

對於這種特殊情況,我們可以通過在二十個左右的時期之後停止訓練來防止過度擬合。