【tf.keras】basic 02: Movie reviews Text classification

本文是 tf.keras教程的第二篇,使用Keras對電影評論(IMDB數據集)進行二分類(積極和消極)。數據集包含來自電影數據庫的50,000個電影評論的文本。這些內容中消極和積極評論是平衡的,即各有25000條。



代碼環境:

python version: 3.7.6 
tensorflow version: 2.1.0

導入必要的包:

import numpy as np
import tensorflow as tf
from tensorflow import keras

1. 數據處理

1.1 查看數據

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

參數 num_words=10000 保留了訓練數據中最常出現的 10,000 個單詞。爲了保持數據規模的可管理性,低頻詞被丟棄。

該數據集是經過預處理的:每個樣本都是一個表示影評中詞彙的整數數組。每個標籤都是一個值爲 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, 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]

電影評論可能具有不同的長度。以下代碼顯示了第一條和第二條評論的中單詞數量。由於神經網絡的輸入必須是統一的長度,因此需要解決這個問題。

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

輸出:

(218, 189)

1.2 將數據編碼轉換爲單詞

瞭解如何將整數轉換回文本可能是有幫助的。創建一個輔助函數來查詢一個包含了整數到字符串映射的字典對象:

# 一個映射單詞到整數索引的詞典
word_index = imdb.get_word_index()

# 保留第一個索引
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])

輸出:

"<START> 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 <UNK> is an amazing actor and now the same being director <UNK> 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 <UNK> 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 <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> 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"

1.3 準備數據

影評(整數數組)必須在輸入神經網絡之前轉換爲張量。這種轉換可以通過以下兩種方式來完成:

  • 將數組轉換爲表示單詞出現與否的由 0 和 1 組成的向量,類似於 one-hot 編碼。例如,序列[3, 5]將轉換爲一個 10,000 維的向量,該向量除了索引爲 3 和 5 的位置是 1 以外,其他都爲 0。然後,將其作爲網絡的首層——一個可以處理浮點型向量數據的Dense層(全連接層)。但是該方法需要大量的內存,矩陣大小爲 num_words * num_reviews

  • 通過填充數組的方式來保證輸入數據具有相同的長度,然後創建一個大小爲 max_length * num_reviews 的整型張量。可以使用能夠處理此形狀數據的嵌入層(Embedding)作爲網絡中的第一層。本教程使用該方法。

由於電影評論長度必須相同,我們將使用 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官方文檔]

查看文本長度:

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    4 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]

2. 建模

2.1 順序堆疊模型

神經網絡由堆疊的層來構建,這需要從兩個主要方面來進行體系結構決策:

  • 模型裏有多少層?
  • 每個層裏有多少隱層單元(hidden units)?
    在此樣本中,輸入數據包含一個單詞索引的數組。要預測的標籤爲 0 或 1爲該問題建模:
# 輸入形狀是用於電影評論的詞彙數目(10,000 詞)
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='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()

模型摘要信息:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 16)          160000    
_________________________________________________________________
global_average_pooling1d (Gl (None, 16)                0         
_________________________________________________________________
dense (Dense)                (None, 16)                272       
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________

層按順序堆疊以構建分類器:

  • 第一層是嵌入(Embedding)層。該層採用整數編碼的詞彙表,並查找每個詞索引的嵌入向量(embedding vector)。這些向量是通過模型訓練學習到的。向量向輸出數組增加了一個維度。得到的維度爲:(batch, sequence, embedding)。
  • 接下來,GlobalAveragePooling1D 將通過對序列維度求平均值來爲每個樣本返回一個定長輸出向量。這允許模型以儘可能最簡單的方式處理變長輸入。
  • 該定長輸出向量通過一個有 16 個隱層單元的全連接(Dense)層傳輸。
  • 最後一層與單個輸出結點密集連接。使用 Sigmoid 激活函數,其函數值爲介於 0 與 1 之間的浮點數,表示概率或置信度。

2.2 隱層單元數量設置

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

如果模型具有更多的隱層單元(更高維度的表示空間)和/或更多層,則可以學習到更復雜的表示。但是,這會使網絡的計算成本更高,並且可能導致學習到不需要的模式——一些能夠在訓練數據上而不是測試數據上改善性能的模式,即過擬合(overfitting)。


2.3 損失函數與優化器選擇

模型需要損失函數和優化器來進行訓練。由於這是一個二分類問題且模型輸出概率值(一個使用 sigmoid 激活函數的單一單元層),所以使用 binary_crossentropy 損失函數(二分類交交叉熵損失函數)。

這不是損失函數的唯一選擇,例如,也可以選擇 mean_squared_error (均方誤差損失,mse)。但是,一般來說 binary_crossentropy 更適合處理概率——它能夠度量概率分佈之間的“距離”,本例中指的是度量 ground-truth 分佈與預測值之間的“距離”。

配置模型來使用優化器和損失函數:

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

2.4 設置驗證集

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

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

2.5 訓練模型

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)

輸出:

Epoch 40/40
15000/15000 [==============================] - 1s 65us/sample - loss: 0.0891 - accuracy: 0.9779 - val_loss: 0.3147 - val_accuracy: 0.8814

2.6 評估模型

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

輸出:

25000/25000 - 2s - loss: 0.3356 - accuracy: 0.8718
[0.33557795378684996, 0.8718]

這種十分樸素的方法得到了約 87% 的準確率(accuracy)。若採用更好的方法,模型的準確率應當接近 95%。


3. 查看準確率和損失曲線

model.fit() 返回一個 History 對象,該對象包含一個字典,其中包含訓練階段所發生的一切事件:

history_dict = history.history
history_dict.keys()

輸出:

dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])

有四個條目:在訓練和驗證期間,每個條目對應一個監控指標。可以使用這些條目來繪製訓練與驗證過程的損失值(loss)和準確率(accuracy),以便進行比較。

損失曲線:

import matplotlib.pyplot as plt

acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

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

plt.figure(figsize=(6,4), dpi=150)
# “bo”代表 "藍點"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b代表“藍色實線”
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()   # 清除數字

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()

在這裏插入圖片描述
在該圖中,藍色圓點代表訓練損失值(loss)與準確率(accuracy),藍色實線代表驗證損失值(loss)與準確率(accuracy)。

注意訓練損失值隨每一個 epoch 下降而訓練準確率(accuracy)隨每一個 epoch 上升。這在使用梯度下降優化所可預期的——理應在每次迭代中最小化期望值。

驗證過程的損失值(loss)與準確率(accuracy)的情況卻並非如此——它們似乎在 20 個 epoch 後達到峯值。這是過擬合的一個實例:模型在訓練數據上的表現比在新數據上的表現要更好。在此之後,模型過度優化並學習特定於訓練數據的表示,而不能夠泛化到測試數據。直觀上來看就是,訓練損失不斷下降,驗證損失先下降後有上升趨勢;訓練準確率不斷上升,驗證準確率先上升後有下降趨勢。可以認爲這就是出現了過擬合,從而導致泛化能力很差。

避免過擬合的策略下篇介紹。


參考:
https://www.tensorflow.org/tutorials/keras/text_classification#

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章