步驟 3:準備資料

您必須先將資料轉換為模型能瞭解的格式,才能將資料饋送至模型。

首先,我們所收集的資料樣本可能按照特定順序排列。我們不希望任何與樣本順序相關的資訊,影響文字和標籤之間的關係。例如,如果資料集是按照類別排序,然後被分成訓練/驗證集,這些資料集並不代表資料的整體分佈。

如要確保模型不受資料順序影響,有一個簡易的最佳做法,就是一律先重組資料再執行其他作業。如果您的資料已分為訓練和驗證集,請務必按照轉換訓練資料的方式轉換驗證資料。如果您沒有獨立的訓練和驗證集,可以在重組後分割範例。我們通常會使用 80% 的樣本進行訓練,20% 用於驗證。

第二,機器學習演算法將數字做為輸入內容。這表示我們需要將文字轉換成數值向量。此程序有兩個步驟:

  1. 符記化:將文字分成文字,或較小的子文字,如此可以促進文字和標籤之間的關係。這會決定資料集的「詞彙」(資料中不重複的符記組合)。

  2. 向量:定義良好的數值度量,將這些文字描述成特徵。

以下說明如何針對 n 元語法向量和序列向量執行這兩個步驟,以及如何使用特徵選取和正規化技術將向量表示法最佳化。

N 公克向量 [Option A]

在後續段落中,我們將學習如何為 n 元語法模型進行代碼化和向量化。並介紹如何使用特徵選取和正規化技術,最佳化 n-gram 表示法。

在 n 元語法向量中,文字是以不重複 n 元語法的集合表示,也就是相鄰符記 (通常是字詞) 的群組。假設文字為 The mouse ran up the clock。這裡:

  • 一字不差 (n = 1) 為 ['the', 'mouse', 'ran', 'up', 'clock']
  • 「大字」字樣 (n = 2) 是['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock']
  • 而其他人員也會有資料管理的需求。

代碼化

我們發現,將文字一元語法和大型元代碼化,可帶來良好的準確率,並減少運算時間。

向量化

將文字樣本分割為 n-grams 後,我們必須將這些 n 元語法轉換成 Google 機器學習模型可以處理的數值向量。以下範例顯示指派給兩段文字產生的一元和大型索引。

Texts: 'The mouse ran up the clock' and 'The mouse ran down'
Index assigned for every token: {'the': 7, 'mouse': 2, 'ran': 4, 'up': 10,
  'clock': 0, 'the mouse': 9, 'mouse ran': 3, 'ran up': 6, 'up the': 11, 'the
clock': 8, 'down': 1, 'ran down': 5}

將索引指派給 n-gram 後,我們通常會使用下列其中一個選項進行向量化。

One-hot 編碼:每個範例文字都會以向量表示,指出文字中是否存在符記。

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]

計數編碼:每個範例文字都會以向量表示,指出文字內容中的符記數量。請注意,與一元圖「the」對應的元素現在以 2 表示,因為「the」一詞在文字中出現兩次。

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1]

Tf-idf 編碼:上述兩種方法的問題在於,所有文件中出現相似頻率的常見字詞 (也就是對資料集中的文字樣本並非特有的字詞) 不會受懲處。舉例來說,「a」這類字詞在所有文字中都很常見。因此,「the」的符記數量 高於其他較有意義的字詞,並不算有用。

'The mouse ran up the clock' = [0.33, 0, 0.23, 0.23, 0.23, 0, 0.33, 0.47, 0.33, 0.23, 0.33, 0.33]

(請參閱 Scikit-learn TfidfTransformer)。

還有許多其他向量表示法,但前三個表示法較常用。

我們發現 tf-idf 編碼的準確度遠優於其他兩者 (平均高出 0.25-15%),因此建議採用這種方法來向量 n-gram 。不過請注意,這會佔用更多記憶體 (因為其使用浮點表示法),且需要較多時間運算,尤其是大型資料集 (在某些情況下可能會耗時兩倍)。

選取特徵

當我們將資料集中的所有文字轉換為單字 + 方格符記時,可能會產生數萬個符記。並非所有這些符記/特徵都會用於標籤預測作業。比如說,我們可以捨棄特定符記 像是資料集內極少發生過的符記我們也可以衡量特徵重要性 (每個符記對標籤預測結果的影響程度),並僅納入最豐富的符記。

許多統計函式會使用特徵和對應的標籤,並輸出特徵重要性分數。兩種常用的函式為 f_classifchi2。我們的實驗顯示,這兩個函式的成效都相同。

更重要的是,我們看到許多資料集的準確率達到約 20,000 個特徵 (如圖 6)。超過這個門檻的特徵會幾乎不會造成過度配適,甚至導致效能降低。

「前 K 個」與「準確率」的比較

圖 6:主要 K 功能與準確率。如為多個資料集,準確度約為 2 萬個特徵。

正規化

正規化會將所有特徵/範例值轉換為小型和類似的值。這可簡化學習演算法中的梯度下降法收斂。從我們觀察到的經驗中,在資料預先處理期間的正規化似乎並未增加文字分類問題的價值;建議您略過這個步驟。

以下程式碼集結了以上所有步驟:

  • 將文字樣本代碼化為單字 + Bigrams,
  • 使用 tf-idf 編碼進行向量化
  • 使用 f_classif 計算特徵重要性,從符記向量中只選取前 20,000 個地圖項目。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif

# Vectorization parameters
# Range (inclusive) of n-gram sizes for tokenizing text.
NGRAM_RANGE = (1, 2)

# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Whether text should be split into word or character n-grams.
# One of 'word', 'char'.
TOKEN_MODE = 'word'

# Minimum document/corpus frequency below which a token will be discarded.
MIN_DOCUMENT_FREQUENCY = 2

def ngram_vectorize(train_texts, train_labels, val_texts):
    """Vectorizes texts as n-gram vectors.

    1 text = 1 tf-idf vector the length of vocabulary of unigrams + bigrams.

    # Arguments
        train_texts: list, training text strings.
        train_labels: np.ndarray, training labels.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val: vectorized training and validation texts
    """
    # Create keyword arguments to pass to the 'tf-idf' vectorizer.
    kwargs = {
            'ngram_range': NGRAM_RANGE,  # Use 1-grams + 2-grams.
            'dtype': 'int32',
            'strip_accents': 'unicode',
            'decode_error': 'replace',
            'analyzer': TOKEN_MODE,  # Split text into word tokens.
            'min_df': MIN_DOCUMENT_FREQUENCY,
    }
    vectorizer = TfidfVectorizer(**kwargs)

    # Learn vocabulary from training texts and vectorize training texts.
    x_train = vectorizer.fit_transform(train_texts)

    # Vectorize validation texts.
    x_val = vectorizer.transform(val_texts)

    # Select top 'k' of the vectorized features.
    selector = SelectKBest(f_classif, k=min(TOP_K, x_train.shape[1]))
    selector.fit(x_train, train_labels)
    x_train = selector.transform(x_train).astype('float32')
    x_val = selector.transform(x_val).astype('float32')
    return x_train, x_val

使用 n 元語法向量表示法時,我們會捨棄許多字詞順序和文法的相關資訊 (最好能在 n 大於 1 時保留部分部分排序資訊)。也就是所謂的簡明扼要。此表示法會與沒有考量排序的模型搭配使用,例如邏輯迴歸、多層感知、梯度提升機器、支援向量機器。

序列向量 [選項 B]

在後續段落中,我們會說明如何進行序列模型的代碼化和向量化。我們也會探討如何運用特徵選擇和正規化技術,將序列表示法最佳化。

在某些文字樣本中,字詞順序對文字的含意至關重要。例如:「我以前討厭通勤時,我的新腳踏車改動了」CNN/RNN 等模型可以從樣本的字詞順序推論出含意。針對這類模型,我們會依保留順序,將文字表示為符記序列。

代碼化

文字能以一串字元或字詞序列表示。我們發現,使用字詞層級表示法的成效優於字元符記。這也是業界遵循的一般常規只有在文字有大量拼寫錯誤時,才適合使用字元符記,而這通常並非如此。

向量化

將文字樣本轉換為字詞序列後,我們必須將這些序列轉換為數值向量。以下範例顯示指派給兩段文字產生的一元公元索引,以及第一個文字轉換到的符記索引序列。

Texts: 'The mouse ran up the clock' and 'The mouse ran down'

每個符記指派的索引:

{'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}

注意:「the」這個字詞最常出現,因此系統會將索引值 1 指派給這個字詞。有些程式庫會為未知符記保留索引 0,如此處所示。

權杖索引的順序:

'The mouse ran up the clock' = [1, 2, 3, 4, 1, 5]

有兩種選項可用於向量符記序列:

One-hot 編碼:序列是以 n 維空間中的字詞向量表示,其中 n = 詞彙的大小。當我們將字元進行代碼化,但詞彙量很小時,這種表示法就非常實用。當我們將詞彙代碼化為字詞時,詞彙通常會有數萬個符記,導致一次性向量非常稀疏且效率低。示例:

'The mouse ran up the clock' = [
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 1, 0, 0, 0, 0],
  [0, 0, 0, 1, 0, 0, 0],
  [0, 0, 0, 0, 1, 0, 0],
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 1, 0]
]

嵌入字詞:字詞含意與字詞相關聯。因此,我們可以在密集向量空間 (約數百個實數) 中表示字詞符記,字詞之間的位置和距離代表字詞在語意上的相似程度 (請參閱圖 7)。此表示法稱為「字詞嵌入」

文字嵌入

圖 7:字詞嵌入

序列模型的第一層通常會有這樣的嵌入層。這個層會學習如何在訓練過程中,將字詞索引序列轉換為字詞嵌入向量,使每個字詞索引都會對應到代表該字詞在語意空間中位置的密集向量 (請見圖 8)。

嵌入層

圖 8:嵌入層

選取特徵

並非所有資料都會用於標籤預測。我們可以捨棄詞彙中罕見或不相關的字詞,藉此最佳化學習程序。事實上,我們發現最常使用 20,000 個特徵通常已足夠。這也適用於 n 元語法模型 (請見圖 6)。

讓我們將上述所有步驟以序列向量化。以下程式碼會執行這些工作:

  • 將文字權杖化
  • 使用前 20,000 個符記建立詞彙
  • 將符記轉換為序列向量
  • 將序列固定至固定序列長度
from tensorflow.python.keras.preprocessing import sequence
from tensorflow.python.keras.preprocessing import text

# Vectorization parameters
# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Limit on the length of text sequences. Sequences longer than this
# will be truncated.
MAX_SEQUENCE_LENGTH = 500

def sequence_vectorize(train_texts, val_texts):
    """Vectorizes texts as sequence vectors.

    1 text = 1 sequence vector with fixed length.

    # Arguments
        train_texts: list, training text strings.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val, word_index: vectorized training and validation
            texts and word index dictionary.
    """
    # Create vocabulary with training texts.
    tokenizer = text.Tokenizer(num_words=TOP_K)
    tokenizer.fit_on_texts(train_texts)

    # Vectorize training and validation texts.
    x_train = tokenizer.texts_to_sequences(train_texts)
    x_val = tokenizer.texts_to_sequences(val_texts)

    # Get max sequence length.
    max_length = len(max(x_train, key=len))
    if max_length > MAX_SEQUENCE_LENGTH:
        max_length = MAX_SEQUENCE_LENGTH

    # Fix sequence length to max value. Sequences shorter than the length are
    # padded in the beginning and sequences longer are truncated
    # at the beginning.
    x_train = sequence.pad_sequences(x_train, maxlen=max_length)
    x_val = sequence.pad_sequences(x_val, maxlen=max_length)
    return x_train, x_val, tokenizer.word_index

標籤向量

我們已經瞭解如何將範例文字資料轉換成數值向量。您必須對標籤套用類似的程序。我們可以直接將標籤轉換為 [0, num_classes - 1] 範圍內的值。舉例來說,如果有 3 個類別,可以直接使用值 0、1 和 2 來表示。網路會在內部使用 one-hot 向量來表示這些值 (避免推斷標籤之間的關係)。此表示法取決於損失函式和我們在類神經網路中使用的最後一個層啟動函式。我們將在下一節深入探討