教程 | TensorFlow 2.0 教程 ——機器學習基礎 —— 文本分類

更新至 TensorFlow 2.0 alpha 版本

譯自:官方 TensorFlow 教程


本教程使用評論文本對電影評論進行分類(正面或負面)。這是二分類的一個例子,一種重要且廣泛適用的機器學習問題。

我們將使用包含來自 IMDB 的 50,000 條電影評論文本的 IMDB 數據集。它們被分爲 25,000 條訓練評論和 25,000 條測試評論。訓練和測試集是平衡的,這意味着它們包含相同數量的正面和負面評論。

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

!pip install -q tensorflow==2.0.0-alpha0
from __future__ import absolute_import, division, print_function

import tensorflow as tf
from tensorflow import keras

import numpy as np

print(tf.__version__)
2.0.0-alpha0

下載 IMDB 數據集

IMDB 數據集已包含在 TensorFlow 中,並且經過預處理,使得評論(單詞序列)已經被轉換爲整數序列,其中每個整數表示字典中的一個特定單詞。

以下代碼將 IMDB 數據集下載到你的計算機中(如果你已經下載了它,則使用緩存副本):

imdb = keras.datasets.imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 0s 0us/step

參數 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)

將整數轉換回單詞

瞭解如何將整數轉換回文本可能很有用。這裏,我們將創建一個輔助函數來查詢包含整數到字符串映射的字典對象:

# 獲取單詞到整數索引的映射,格式爲字典
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])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 0s 0us/step

現在我們可以使用 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"

準備數據

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

  • 對數組進行獨熱編碼,將其轉換爲 0 和 1 的向量。例如,序列 [3, 5] 將變成 10,000 維向量,除了索引 3 和 5(它們是1)之外全部爲零。然後,將其作爲我們網絡中的第一層 —— 一個可以處理浮點矢量數據的 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)

我們現在看一下樣本的長度:

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

並檢查(填充後的)第一個評論:

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

建立模型

神經網絡是通過堆疊網絡層創建的 —— 這需要兩個主要的架構決策:

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

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

# 輸入大小是電影評論的詞彙數量 (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='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
_________________________________________________________________

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

  1. 第一層是 Embedding 層。該層採用整數編碼的詞彙表,並查找每個詞索引的嵌入向量。這些向量在模型訓練中進行學習,併爲輸出數組添加一個維度:(batch, sequence, embedding)
  2. 接下來,GlobalAveragePooling1D 層通過對序列維度求平均,使每個樣本返回固定長度的輸出向量。這允許模型以最簡單的方式處理可變長度的輸入。
  3. 這個固定長度的輸出向量通過一個全連接的帶有 16 個隱藏單元的 Dense 層傳輸。
  4. 最後一層與單個輸出節點密集連接。使用 sigmoid 激活函數,輸出是介於 0 和 1 之間的浮點數,表示概率或置信度。

隱藏單元

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

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

損失函數和優化器

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

這不是損失函數的唯一選擇,例如,你可以選擇 mean_squared_error。但是,通常用 binary_crossentropy 處理概率更好 —— 它度量概率分佈之間的“距離”,在本例中,測量真實分佈和預測之間的“距離”。

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

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

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

創建驗證集

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

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

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

訓練模型

使用包含 512 個樣本的小批量訓練模型 40 個週期。x_trainy_train 張量中所有樣本需要進行 40 次迭代。在訓練期間,監控模型在驗證集中的 10,000 個樣本的損失和準確率:

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)
Train on 15000 samples, validate on 10000 samples
Epoch 1/40
15000/15000 [==============================] - 1s 43us/step - loss: 0.6987 - acc: 0.5008 - val_loss: 0.6940 - val_acc: 0.4948
Epoch 2/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.6912 - acc: 0.5518 - val_loss: 0.6901 - val_acc: 0.5675
...
Epoch 39/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.1819 - acc: 0.9370 - val_loss: 0.2885 - val_acc: 0.8860
Epoch 40/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.1773 - acc: 0.9395 - val_loss: 0.2876 - val_acc: 0.8854

評估模型

讓我們看看模型的表現,evaluate 方法將返回兩個值。損失(代表我們的誤差的值,值越低越好)和準確率。

results = model.evaluate(test_data, test_labels)

print(results)
25000/25000 [==============================] - 1s 45us/sample - loss: 0.3145 - accuracy: 0.8742
[0.3145076360368729, 0.87424]

這種相當簡單的方法可以達到約 87% 的準確度率,採用更高級的方法,模型應該可以接近 95%。


創建準確率和損失的圖表

model.fit() 返回一個包含字典的 History 對象,其中包括訓練期間產生的所有數據:

history_dict = history.history
history_dict.keys()
dict_keys(['val_accuracy', 'accuracy', 'loss', 'val_loss'])

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

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)

# "bo" 表示 "blue dot(藍點)"
plt.plot(epochs, loss, 'bo', label='Training loss')
# "b" 表示 "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()   # 清除圖像

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

在這裏插入圖片描述

在上兩圖中,點表示訓練損失和準確率,實線表示驗證損失和準確率。

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

這和驗證損失和驗證準確率不同 —— 它們似乎在大約二十個週期之後達到峯值。這是過擬合的一個例子:模型在訓練數據上的表現比在以前從未見過的數據上表現得更好。在此之後,模型過度優化並學習特定於訓練數據的表徵,這些表徵沒有泛化至測試數據。

對於這種特殊情況,我們可以通過在大約二十個週期之後停止訓練來防止過擬合。稍後,你將看到如何使用回調自動執行此操作。

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