預處理輸入特徵
預處理輸入特徵
為神經網路準備資料需要將所有特徵轉換為數值特徵,通常將其歸一化等。特別是如果資料包含分類特徵或文字特徵,則需要將它們轉換為數字。
在準備資料檔案時,可以使用任何喜歡的工具(例如NumPy、pandas或Scikit-Learn)提前完成此操作。或者,可以在使用Data API載入資料
時動態地與處理資料(例如使用資料集的map()方法),也在可以在模型中直接包含預處理層。
使用Lambda層實現標準化層的方法,對每個特徵,它減去均值併除以其標準差(加上一個微小的平滑項,以免被零除):
import numpy as np from tensorflow import keras means = np.mean(X_train, axis=0, keepdims=True) stds = np.std(X_train, axis=0, keepdims=True) eps = keras.backend.epsilon() model = keras.models.Sequetial([ keras.layers.Lambda(lambda inputs: (inputs - means) / (stds + eps)), ])
使用一個自包含的自定義層,而不是像means和stds之類的全域性變數:
class Standardization(keras.layers.Layer): def adapt(self, data_sample): self.means_ = np.mean(data_sample, axis=0, keepdims=True) self.stds_ = np.std(data_sample, axis=0, keepdims=True) def call(self, inputs): return (inputs - self.means_) / (self.stds_ + keras.backend.epsilon())
使用此標準層之前,需要通過呼叫adapt()方法來使適應資料集並將其傳遞給資料樣本。這樣它就可以為每個特徵使用適當的均值和標準差:
std_layer = Standardization()
std_layer.adapt(data_sample)
該樣本必須足夠大以代表資料集,但不必是完整的資料集:通常,隨機選擇幾百個例項就足夠了。接下來可以像常規一樣使用此預處理層:
model = keras.models.Sequential() model.add(std_layer) [...] # 建立剩下模型的層 model.compile([...]) model.fit([...])
也可以使用keras.layers.Normal標準化層,它的工作方式非常類似於自定義標準化層:首先,建立該層,然後通過將一個數據樣本傳遞個adapt()方法使其適應你的資料集,最後正常使用該層
使用獨熱向量編碼分類特徵
在加州住房資料集中的ocean_proximity特徵,它是一個具有5個可能值的分類特徵:'<1H OCEAN' 'INLAND' 'NEAR OCEAN' 'NEAR BAY'和'ISLAND'。再將其提供給
神經網路之前需要對該特徵進行編碼。由於類別很少,可以使用獨熱編碼。為此首先需要將每個類別對映到其索引(0到4),這可以使用查詢表來完成
import tensorflow as tf
vocab = ['<1H OCEAN', 'INLAND', 'NEAR OCEAN', 'NEAR BAY', 'ISLAND']
indices = tf.range(len(vocab), dtype=tf.int64)
table_init = tf.lookup.KeyValueTensorInitializer(vocab, indices)
num_oov_buckets = 2
table = tf.lookup.StaticVocabularyTable(table_init, num_oov_buckets)
- 首先定義詞彙表:這是所有可能類別的列表
- 建立帶有相應索引(0-4)的張量
- 接下啦,為查詢表建立一個初始化程式,將類別列表以及其對應的索引傳遞給它。在此示例中,已經有此資料所以使用KeyValueTensorInitializer。如果類別在文字檔案中列出(每行一個類別),要使用TextFileInitializer
- 在最後兩行中,建立了查詢表,為其初始化程式並制定了詞彙表外(out-of-vocabulary,oov)桶的數量。如果查詢詞彙表中不存在的類別,則查詢表將計算該類別的雜湊並將這個位置類別分配給oov桶之中的一個。它們的索引從已知類別開始,在此示例中,兩個oov桶的索引為5和6
為什麼要使用oov桶?如果類別數量很大(例如郵政編碼、城市、單詞、產品或使用者)並且資料集也很大,或者它們一直在變化,嗎,而得到類別的完整列表可能不是和那方便。一種解決方法是基於資料樣本(而不是整個訓練集)定義詞彙表,併為不在資料樣本中的其他類新增一些桶。希望在訓練期間找到的類別越多,就應該使用越多的oov桶。如果沒有足夠的oov桶,就會發生衝突:不同的類別最終會出現在同一個桶中,因此神經網路將無法區分它們
使用查詢表將一小批分類特徵編碼為獨熱向量
categories = tf.constant(['NEAR BAY', 'DESERT', 'INLAND', 'INLAND'])
cat_indices = table.lookup(categories)
cat_indices
<tf.Tensor: shape=(4,), dtype=int64, numpy=array([3, 5, 1, 1], dtype=int64)>
cat_one_hot = tf.one_hot(cat_indices, depth=len(vocab) + num_oov_buckets)
cat_one_hot
<tf.Tensor: shape=(4, 7), dtype=float32, numpy=
array([[0., 0., 0., 1., 0., 0., 0.],
[0., 0., 0., 0., 0., 1., 0.],
[0., 1., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0.]], dtype=float32)>
'NEAR BAY'被對映到索引3,位置類別'DESERT'被對映到兩個oov桶之一(在索引5),而'INLAND'被對映到索引1兩次。然後使用tf.one_hot()對這些索引進行獨熱編碼。必須告訴該函式索引的總數,該總數等於詞彙表大小加上oov桶的數量。
如果詞彙表很大,則使用嵌入對它們進行編碼會更加有效
使用嵌入編碼分類特徵
嵌入是表示類別的可訓練密集向量。預設情況下,嵌入是隨機初始化的,例如,'NEAT BAY'類別最初可以由[0.131,0.890]的隨機向量表示,而'NEAT OCEAN'類別可以由[0.631,0.791]表示。在此示例中,使用2D嵌入,但是維度是可以調整的超引數。由於這些嵌入是可訓練的,因此它們在訓練過程中會逐步改善。由於它們代表的類別相當相似,梯度下降肯定最終會把它們推到接近的位置,而把它們推離'INLAND'類別的嵌入。實際上,表徵越好,神經網路就越容易做出準確的預測,因此訓練使嵌入成為類別的有用表徵。這稱為表徵學習
詞嵌入:
嵌入通常不僅是當前任務的有用表示,而且很多時候這些相同的嵌入可以成功地重用於其他任務。最常見的示例是詞嵌入(即單個單詞的嵌入):在執行自然語言處理任務時,與訓練自己的詞嵌入相比,重用預先訓練好的詞嵌入通常效果更好。
使用向量來表示詞的想法可以追溯到20世紀60年代,許多複雜的技術已經被用來生成有用的向量,包括使用神經網路。但是事情真正在2013年取得了成功,當時Tomas Mikolov和其他Google研究人員發表了一篇論文,描述了一種使用神經網路學習詞嵌入地有效技術,大大優於以前的嘗試。這使他們能夠在非常大的文字語料庫上學習嵌入,法國、西班牙和義大利等於語義相關的詞最終聚類在一起。
但是這不僅於鄰近性有關:詞嵌入還沿著嵌入空間中有意義的軸進行組織。這是一個著名的示例:如果計算King-Man+Woman(新增和減去這些單詞的嵌入向量),則結果非常接近Queen單詞的嵌入。換句話說,詞嵌入編碼了性別的概念
同樣,可以計算Madrid-Spain+France,其結果接近Paris,這似乎表明首都的概念也在嵌入中進行了編碼
不幸的是,詞嵌入有時會捕捉最嚴重的偏見。例如,儘管它們正確地學習到男人是國王,女人是女王,但它們似乎也學習到了男人是醫生,而女人是護士:這是一種性別歧視!確保深度學習演算法的公平性是重要且活躍的研究課題
如果手動實現嵌入以瞭解它們的工作原理(使用一個簡單的Keras層)。首先需要建立一個包含每個類別嵌入的嵌入矩陣,並隨機初始化。每個類別和每個oov桶一行,每個嵌入維度都有一列:
embedding_dim = 2
embed_init = tf.random.uniform([len(vocab) + num_oov_buckets, embedding_dim])
embedding_matrix = tf.Variable(embed_init)
在此示例中,使用2D其阿奴,但是根據經驗,嵌入通常有10到300個維度,具體取決於任務和詞彙表
該嵌入矩陣是一個隨機的7x2矩陣,儲存在一個變數中(可以在訓練過程中通過梯度下降對其進行調整)
embedding_matrix
<tf.Variable 'Variable:0' shape=(7, 2) dtype=float32, numpy=
array([[0.24102378, 0.67658544],
[0.43882644, 0.7676766 ],
[0.04228592, 0.18631208],
[0.5599886 , 0.39891016],
[0.47777188, 0.18932772],
[0.8707634 , 0.6436963 ],
[0.6798345 , 0.21071827]], dtype=float32)>
對與之前相同的分類特徵進行編碼,但是這次使用這些嵌入:
categories = tf.constant(['NEAR BAY', 'DESERT', 'INLAND', 'INLAND'])
cat_indices = table.lookup(categories)
cat_indices
<tf.Tensor: shape=(4,), dtype=int64, numpy=array([3, 5, 1, 1], dtype=int64)>
tf.nn.embedding_lookup(embedding_matrix, cat_indices)
<tf.Tensor: shape=(4, 2), dtype=float32, numpy=
array([[0.5599886 , 0.39891016],
[0.8707634 , 0.6436963 ],
[0.43882644, 0.7676766 ],
[0.43882644, 0.7676766 ]], dtype=float32)>
tf.nn.embedding_lookup()函式以給定的索引查詢在嵌入矩陣中的行,這就是它所做的全部。例如查詢表中'INLAND'類別位於索引1,因此tf.nn.embeddnig_lookup()函式返回嵌入矩陣中第一行第一行的嵌入(兩次)[0.43882644,0.7676766]。
Keras提供了一個keras.layers.Embedding層來處理嵌入矩陣(預設情況下是可訓練的)。建立層時,它將隨機初始化嵌入矩陣,然後使用某些類別索引進行呼叫時,它將返回嵌入矩陣中這些索引處的行:
from tensorflow import keras
embedding = keras.layers.Embedding(input_dim=len(vocab) + num_oov_buckets, output_dim=embedding_dim)
embedding(cat_indices)
<tf.Tensor: shape=(4, 2), dtype=float32, numpy=
array([[-0.0440745 , 0.01170705],
[ 0.04262433, -0.00528568],
[ 0.03031978, 0.02593242],
[ 0.03031978, 0.02593242]], dtype=float32)>
將所有內容放在一起,可以建立一個Keras模型,該模型可以處理分類特徵(以及常規的數值特徵)並學習每個類別(以及每個oov桶)的嵌入:
regular_inputs = keras.layers.Input(shape=[8])
categories = keras.layers.Input(shape=[], dtype=tf.string)
cat_indices = keras.layers.Lambda(lambda cats: table.lookup(cats))(categories)
cat_embed = keras.layers.Embedding(input_dim=6, output_dim=2)(cat_indices)
encoded_inputs = keras.layers.concatenate([regular_inputs, cat_embed])
outputs = keras.layers.Dense(1)(encoded_inputs)
model = keras.models.Model(inputs=[regular_inputs, categories], outputs=[outputs])
該模型有兩個輸入:一個常規輸入(每個例項包含8個數字特徵),以及一個分類輸入(每個例項包含一個分類特徵)。它使用Lambda層查詢每個類別的索引,然後查詢這些索引的嵌入。接下來它將嵌入和常規輸入合併起來以提供已編碼的輸入,這些輸入準備好被饋送到神經網路。此時可以新增任何種類的神經網路
當keras.layers.TextVectorization層可用時,可以呼叫其adapt()方法以使其從一個數據樣本中提取詞彙表(它會建立查詢表)。然後可以將其新增到模型中,它會執行索引查詢(替換之前程式碼示例的Lambda層)
Keras預處理層
- kears.layers.Normalization層——執行特徵標準化(相當於前面定義的Standardization層)
- TextVectorization層——能夠將輸出中的每個單詞編碼為它在詞彙表中的索引
- keras.layers.Discretization層,它將連續的資料切成不同的離散塊,並將每個塊編碼成一個獨熱向量
Discretization層不可微分,應該僅在模型開始時使用。實際上,模型的預處理層在訓練過程中凍結,因此它們的引數不受梯度下降的影響,因此不需要微分。如果希望它可訓練,則不要再自定義預處理層中直接使用Embedding層:相反,應該將其單獨新增到模型中
TextVectorization層還可以選擇輸出單詞計數向量,而不是單詞索引。例如,如果詞彙表包含三個單詞:['and','basketball','more'],則文字'more and more'將行攝到向量[1,0,2]:單詞'and'出現一次,單詞'basketball'根本不出現,而單詞'more'出現兩次。這種文字表示形式稱為詞袋,因為它完全失去了單詞的順序。大多數情況下,像'and'之類的常用詞大多在文字中具有很大的值,即使它們通常是無趣的(例如,在'more and more basketball'文字中,'basketball'一詞顯然是最重要的,因為它不是一個很常見的詞)。因此應該用減少常用單詞重要性的方式對單詞進行歸一化。
一種常見的方法是將每個單詞計數除以出現單詞的訓練例項總數的對數。此技術稱為術語頻率 X 反文件頻率((Tern-Frequency X Inverse-Document-Frequency)(TF-IDF))。
例如,假設單詞'and'、'basketball'和'more'分別出現在訓練集中的200、10和100個文字例項中:在這種情況下,最終向量將為[1/log(200),0/log(10),2/log(100)]。