步驟 3:準備資料

您必須先將模型轉換為模型可解讀的格式,才能將資料提供給模型。

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

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

接著,機器學習演算法會將數字視為輸入內容。這表示我們必須將文字轉換為數值向量。這個程序有兩個步驟:

  1. 權杖化:將文字分為多個字詞或較小的子文字,有助於提高文字與標籤之間的關係。這會決定資料集的「詞彙」,也就是資料中具備的一組專屬權杖。

  2. 向量化:定義合適的數值測量以標示這些文字。

我們來看看如何針對 N 元向量和序列向量執行這兩個步驟,以及如何使用特徵選取和正規化技術來最佳化向量表示法。

N 公克向量 [選項 A]

在後續的段落中,我們會說明如何為 n-gram 模型進行權杖化和向量化。我們也會探討如何利用特徵選取和正規化技巧,最佳化 n-gram 表示法。

在 n 公克向量中,文字表示為一組不重複的 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 公克後,我們必須將這些 N 元轉換為數值向量中的機器學習模型。以下範例顯示指派給兩條文字的一元語法和文法產生的索引。

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 公克後,我們通常會使用下列其中一個選項進行向量化。

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] (See Scikit-learn TfidfTransformer)

還有許多向量表示法,但上述三個是最常用的表示法。

我們觀察到 tf-idf 編碼的精確度遠高於其他兩個 (平均值:平均高出 0.25% 到 15%),建議使用這個方法將 n-gram 向量化。但請注意,這類記憶體佔用較多記憶體 (因為使用了浮點表示法),而且需要更多時間進行運算,特別是大型資料集 (某些情況下可能需要花費兩倍)。

特徵選項

將資料集中的所有文字轉換為字詞 Unii+Biggram 權杖時,我們最後可能會產生數萬個權杖。並非所有權杖/功能都有助於完成標籤預測。因此,我們可以捨棄特定憑證,例如在資料集中極少出現的憑證。也可以評估特徵重要性 (每個權杖對標籤預測的貢獻量),並且只包含最具資訊性的權杖。

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

更重要的是,我們發現許多資料集的準確率約為 20,000 個特徵的高峰 (請見圖 6)。在這項門檻中新增更多功能,會導致網路資源極少,有時甚至會導致過度配適和效能降低。

主要 K 與 準確率

圖 6:成效最佳的 K 功能與準確率。每個資料集都約提供約 2 萬個功能的準確率。

正規化

正規化後,所有特徵/範例值都會轉換為較小的值。這可簡化學習演算法的梯度下降融合能力。從我們觀察到的結果,在資料預先處理期間進行正規化似乎不是為文字分類問題造成的附加價值,建議您略過這個步驟。

下列程式碼合併了上述所有步驟:

  • 將文字樣本權杖化為字詞萬國碼 (Unicode) 或
  • 使用 tf-idf 編碼進行向量搜尋
  • 請捨棄顯示次數少於 2 次的權杖,並使用 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-gram 向量表示法時,我們會捨棄大量與字詞順序和文法相關的資訊 (理想情況下,在 n> 1 時,我們可以保留部分部分排序資訊)。這就是所謂的「單詞」做法。這個表示法會搭配不會排序的順序模型使用,例如邏輯迴歸、多層感知器、梯度增強機器、支援向量機器。

序列向量 [Option B]

在後續的段落中,我們會說明如何為序列模型進行權杖化與向量化。我們也會探討如何使用特徵選取和正規化技巧,對序列表示進行最佳化調整。

以某些文字樣本來說,字詞順序對文字的意義至關重要。例如,我說「我討厭通勤,我的新自行車完全改造後,僅可依順序朗讀。CNN/RNN 等模型可從樣本中的字詞順序推斷。針對這些模型,我們會以序列的順序顯示文字,並保留順序。

代碼化

文字可透過一系列半形字元或一連串的字詞表示。我們發現,使用字詞層級表示的表現會比字元權杖更好。這也是業界通用的一般常規。只有在文字含有大量錯字時,才會用到字元符記,因為這種情況通常並不常見。

向量化

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

Texts: 'The mouse ran up the clock' and 'The mouse ran down'
Index assigned for every token: {'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}.
NOTE: 'the' occurs most frequently, so the index value of 1 is assigned to it.
Some libraries reserve index 0 for unknown tokens, as is the case here.
Sequence of token indexes: '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」這個值來代表這些類別。內部會在內部使用單一熱向量表示這些值 (以避免推論標籤之間的錯誤關係)。此表示法取決於損失的以及神經網路中使用的最後層啟用函式。詳情請見下一節。