Feature column
本文檔詳細介紹了特徵列(feature columns)。您可以將特徵列視爲原始數據和 Estimator 之間的媒介。特徵列非常豐富,使您可以將各種原始數據轉換爲 Estimators 可用的格式,從而可以輕鬆進行實驗。
在內置 Estimators 部分的教程中,我們訓練了一個 tf.estimator.DNNClassifier
去完成 Iris 花的分類任務。在該例子中,我們只使用了numerical feature columns(tf.feature_column.numeric_column
)類型。儘管numeric column可以有效地表示花瓣、花蕊的長度和寬度,但在實際的數據集中包含了各種特徵,其中很多不是數值。
1. 深度神經網絡的輸入
深度神經網絡只能處理數值類型的數據,但我們收集的特徵並不全是數值類型的。以一個可包含下列三個非數值的 product_class 特徵爲例:
- kitchenware
- electronics
- sports
機器學習模型一般將分類值表示爲簡單的矢量,其中 1 表示存在某個值,0 表示不存在某個值。例如,如果將product_class
設置爲sports
時,機器學習模型通常將product_class
表示爲[0, 0, 1]
,即:
- 0:kitchenware is absent。
- 0:electronics is absent。
- 1:sports is present。
因此,雖然原始數據可以是數值或分類值,但機器學習模型會將所有特徵表示爲數值。
2. Feature Columns
如下圖所示,你可以通過 Estimator 的 feature_columns
參數來指定模型的輸入。特徵列在輸入數據(由input_fn
返回)與模型之間架起了橋樑。
要創建特徵列,請調用 tf.feature_column
模塊的函數。本文檔介紹了該模塊中的 9 個函數。如下圖所示,除了 bucketized_column
外的函數要麼返回一個 Categorical Column 對象,要麼返回一個 Dense Column 對象。
下面我們詳細介紹下這些函數。
2.1 Numeric column(數值列)
Iris 分類器對所有輸入特徵調用 tf.feature_column.numeric_column
函數:
- SepalLength
- SepalWidth
- PetalLength
- PetalWidth
tf.feature_column
有許多可選參數。如果不指定可選參數,將默認指定該特徵列的數值類型爲 tf.float32
。
# Defaults to a tf.float32 scalar.
numeric_feature_column = tf.feature_column.numeric_column(key="SepalLength")
可以使用dtype
參數來指定數值類型。
# Represent a tf.float64 scalar.
numeric_feature_column = tf.feature_column.numeric_column(key="SepalLength",
dtype=tf.float64)
默認情況下,numeric column 只表示單個值(標量)。可以使用 shape
參數來指定形狀。
# Represent a 10-element vector in which each cell contains a tf.float32.
vector_feature_column = tf.feature_column.numeric_column(key="Bowling",
shape=10)
# Represent a 10x5 matrix in which each cell contains a tf.float32.
matrix_feature_column = tf.feature_column.numeric_column(key="MyMatrix",
shape=[10,5])
2.2 Bucketized column(分桶列)
通常,我們不直接將一個數值直接傳給模型,而是根據數值範圍將其值分爲不同的 categories。上述功能可以通過 tf.feature_column.bucketized_column
實現。以表示房屋建造年份的原始數據爲例。我們並非以標量數值列表示年份,而是將年份分成下列四個分桶:
模型將按以下方式表示這些 bucket:
日期範圍 | 表示爲… |
---|---|
< 1960 年 | [1, 0, 0, 0] |
>= 1960 年但 < 1980 年 | [0, 1, 0, 0] |
>= 1980 年但 < 2000 年 | [0, 0, 1, 0] |
>= 2000 年 | [0, 0, 0, 1] |
爲什麼要將數字(一個完全有效的模型輸入)拆分爲分類值?首先,該分類將單個輸入數字分成了一個四元素矢量。因此模型現在可以學習四個單獨的權重而不是一個。四個權重能夠創建一個更強大的模型。更重要的是,藉助 bucket,模型能夠清楚地區分不同年份類別,因爲僅設置了一個元素 (1),其他三個元素則被清除 (0)。例如,當我們僅將單個數字(年份)用作輸入時,線性模型只能學習線性關係,而使用 bucket 後,模型可以學習更復雜的關係。
以下代碼演示瞭如何創建 bucketized feature:
# 首先,將原始輸入轉換爲一個numeric column
numeric_feature_column = tf.feature_column.numeric_column("Year")
# 然後,按照邊界[1960,1980,2000]將numeric column進行bucket
bucketized_feature_column = tf.feature_column.bucketized_column(
source_column = numeric_feature_column,
boundaries = [1960, 1980, 2000])
請注意,指定一個三元素邊界矢量可創建一個四元素 bucket 矢量。
2.3 Categorical identity column(類別標識列)
可以將 categorical identity column 看成 bucketized column 的一個特例。在一般的 bucketized column 中,每一個 bucket 表示值的一個範圍(例如,從1960到1979)。在一個 categorical identity column 中,每個 bucket 表示單個、獨一無二的整數。例如,假設您想要表示整數範圍 [0, 4)。也就是說,您想要表示整數 0、1、2 或 3。在這種情況下,分類標識映射如下所示:
與分桶列一樣,模型可以在類別標識列中學習每個類別各自的權重。例如,我們使用唯一的整數值來表示每個類別,而不是使用某個字符串來表示 product_class。即:
- 0=“kitchenware”
- 1=“electronics”
- 2=“sport”
調用 tf.feature_column.categorical_column_with_identity
以實現類別標識列。例如:
# Create categorical output for an integer feature named "my_feature_b",
# The values of my_feature_b must be >= 0 and < num_buckets
identity_feature_column = tf.feature_column.categorical_column_with_identity(
key='my_feature_b',
num_buckets=4) # Values [0, 4)
# In order for the preceding call to work, the input_fn() must return
# a dictionary containing 'my_feature_b' as a key. Furthermore, the values
# assigned to 'my_feature_b' must belong to the set [0, 4).
def input_fn():
...
return ({ 'my_feature_a':[7, 9, 5, 2], 'my_feature_b':[3, 1, 2, 2] },
[Label_values])
2.4 Categorical vocabulary column(類別詞彙表)
我們不能直接向模型中輸入字符串。我們必須首先將字符串映射爲數值或類別值。Categorical vocabulary column 可以將字符串表示爲one_hot格式的向量。
如上所示,categorical vocabulary columns 是 categorical identity columns 的一種特例。TensorFlow提供了兩種不同的函數去創建categorical vocabulary columns:
tf.feature_column.categorical_column_with_vocabulary_list
tf.feature_column.categorical_column_with_vocabulary_file
categorical_column_with_vocabulary_list
根據明確的詞彙表將每個字符串映射到一個整數。
# Given input "feature_name_from_input_fn" which is a string,
# create a categorical feature by mapping the input to one of
# the elements in the vocabulary list.
vocabulary_feature_column =
tf.feature_column.categorical_column_with_vocabulary_list(
key=feature_name_from_input_fn,
vocabulary_list=["kitchenware", "electronics", "sports"])
上面的函數非常簡單,但它有一個明顯的缺點。那就是,當詞彙表很長時,需要輸入的內容太多了。在這種情況下,可以調用 tf.feature_column.categorical_column_with_vocabulary_file
,以便將詞彙表放在單獨的文件中。
# Given input "feature_name_from_input_fn" which is a string,
# create a categorical feature to our model by mapping the input to one of
# the elements in the vocabulary file
vocabulary_feature_column =
tf.feature_column.categorical_column_with_vocabulary_file(
key=feature_name_from_input_fn,
vocabulary_file="product_class.txt",
vocabulary_size=3)
product_class.txt
應該讓每個詞彙各佔一行。在我們的示例中:
kitchenware
electronics
sports
2.5 Hashed Column(哈希列)
到目前爲止,我們處理的示例都包含很少的類別。但當類別的數量特別大時,我們不可能爲每個詞彙或整數設置單獨的類別,因爲這將會消耗非常大的內存。對於此類情況,我們可以反問自己:“我願意爲我的輸入設置多少類別?”實際上,tf.feature_column.categorical_column_with_hash_bucket
函數使您能夠指定類別的數量。對於這種 feature column,模型會計算輸入值的 hash 值,然後使用模運算符將其置於其中一個 hash_bucket_size 類別中,如以下僞代碼所示:
# 僞代碼
feature_id = hash(raw_feature) % hash_bucket_size
創建 categorical_column_with_hash_bucket
的代碼可能如下所示:
hashed_feature_column =
tf.feature_column.categorical_column_with_hash_bucket(
key = "some_feature",
hash_bucket_size = 100) # The number of categories
此時,您可能會認爲:“這太瘋狂了!”,這種想法很正常。畢竟,我們是將不同的輸入值強制劃分成更少數量的類別。這意味着,兩個可能不相關的輸入會被映射到同一個類別,這樣一來,神經網絡也會面臨同樣的結果。下面的圖說明了這個問題,廚具和運動用品都被分配到類別(哈希分桶)12::
與機器學習中的很多反直覺現象一樣,事實證明哈希技術經常非常有用。這是因爲哈希類別爲模型提供了一些分隔方式。模型可以使用其他特徵進一步將廚具與運動用品分隔開來。
2.6 Crossed column(組合列)
通過將多個特徵組合爲一個特徵(稱爲特徵組合,),模型可學習每個特徵組合的單獨權重。
更具體地說,假設我們希望模型計算佐治亞州亞特蘭大的房地產價格。這個城市的房地產價格在不同位置差異很大。在確定對房地產位置的依賴性方面,將緯度和經度表示爲單獨的特徵用處不大;但是,將緯度和經度組合爲一個特徵則可精確定位位置。假設我們將亞特蘭大表示爲一個 100x100 的矩形網格區塊,按緯度和經度的特徵組合標識全部 10000 個區塊。藉助這種特徵組合,模型可以針對與各個區塊相關的房價條件進行訓練,這比單獨的經緯度信號強得多。
下圖展示了我們的想法(以紅色文本顯示城市各角落的緯度和經度值):
爲了解決此問題,我們同時使用了 tf.feature_column.crossed_column
函數及先前介紹的 bucketized_column
。
def make_dataset(latitude, longitude, labels):
assert latitude.shape == longitude.shape == labels.shape
features = {'latitude': latitude.flatten(),
'longitude': longitude.flatten()}
labels=labels.flatten()
return tf.data.Dataset.from_tensor_slices((features, labels))
# Bucketize the latitude and longitude using the `edges`
latitude_bucket_fc = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column('latitude'),
list(atlanta.latitude.edges))
longitude_bucket_fc = tf.feature_column.bucketized_column(
tf.feature_column.numeric_column('longitude'),
list(atlanta.longitude.edges))
# Cross the bucketized columns, using 5000 hash bins.
crossed_lat_lon_fc = tf.feature_column.crossed_column(
[latitude_bucket_fc, longitude_bucket_fc], 5000)
fc = [
latitude_bucket_fc,
longitude_bucket_fc,
crossed_lat_lon_fc]
# Build and train the Estimator.
est = tf.estimator.LinearRegressor(fc, ...)
您可以根據下列內容創建一個特徵組合:
- Feature names(
input_fn
函數返回的dict
中的名字)。 - 除
categorical_column_with_hash_bucket
之外的categorical column
(因爲 crossed_column 會對輸入進行哈希處理)。
當特徵列 latitude_bucket_fc
和 longitude_bucket_fc
組合時,TensorFlow 會爲每個樣本創建 (latitude_fc, longitude_fc) 對。這會生成完整的網格,如下所示:
(0,0), (0,1)... (0,99)
(1,0), (1,1)... (1,99)
... ... ...
(99,0), (99,1)...(99, 99)
爲了避免創建一個完整的巨大輸入表,crossed_column
通過hash_bucket_size
參數來控制組合後的特徵的維度。特徵列通過對輸入元組進行 hash 及 模運算 來爲輸入指定一個索引。
如前面所說,進行“hash”和“模運算”可以限制categories的數量,但是可能導致category衝突:多個 (latitude, longitude) 組合特徵可能會位於相同的 hash bucket 中。不過,在實踐中特徵組合仍能夠有效地提升模型的效果。
有些反直覺的是,在創建特徵組合時,通常仍應在模型中包含原始(未組合)特徵(如前面的代碼段中所示)。獨立的緯度和經度特徵有助於模型區分組合特徵中發生哈希衝突的樣本。
2.7 Indicator and embedding columns(指示列和嵌入列)
指標列和嵌入列從不直接處理特徵,而是將分類列視爲輸入。
使用指標列時,我們指示 TensorFlow 完成我們在分類 product_class 樣本中看到的確切操作。也就是說,指標列將每個類別視爲獨熱矢量中的一個元素,其中匹配類別的值爲 1,其餘類別爲 0:
以下是通過調用 tf.feature_column.indicator_column
創建指標列的方法:
categorical_column = ... # Create any type of categorical column.
# Represent the categorical column as an indicator column.
indicator_column = tf.feature_column.indicator_column(categorical_column)
現在,假設我們有一百萬個可能的類別,或者可能有十億個,而不是隻有三個。出於多種原因,隨着類別數量的增加,使用指標列來訓練神經網絡變得不可行。
我們可以使用嵌入列來克服這一限制。嵌入列並非將數據表示爲很多維度的獨熱矢量,而是將數據表示爲低維度普通矢量,其中每個單元格可以包含任意數字,而不僅僅是 0 或 1。通過使每個單元格能夠包含更豐富的數字,嵌入列包含的單元格數量遠遠少於指標列。
我們來看一個將指標列和嵌入列進行比較的示例。假設我們的輸入樣本包含多個不同的字詞(取自僅有 81 個字詞的有限詞彙表)。我們進一步假設數據集在 4 個不同的樣本中提供了下列輸入字詞:
- “dog”
- “spoon”
- “scissors”
- “guitar”
在這種情況下,下圖說明了嵌入列或指標列的處理流程。
處理樣本時,其中一個 categorical_column_with...
函數會將樣本字符串映射到分類數值。例如,一個函數將“spoon”映射到 [32]。(32 是我們想象出來的,實際值取決於映射函數。)然後,您可以通過下列兩種方式之一表示這些分類數值:
作爲指標列。函數將每個分類數值轉換爲一個 81 元素的矢量(因爲我們的詞彙表由 81 個字詞組成),將 1 置於分類值 (0, 32, 79, 80) 的索引處,將 0 置於所有其他位置。
作爲嵌入列。函數將分類數值
(0, 32, 79, 80)
用作對照表的索引。該對照表中的每個槽位都包含一個 3 元素矢量。
爲什麼示例中的嵌入矢量大小爲 3?下面的“公式”提供了關於嵌入維度數量的一般經驗法則:
embedding_dimensions = number_of_categories**0.25
也就是說,嵌入矢量維數應該是類別數量的 4 次方根。由於本示例中的詞彙量爲 81,建議維數爲 3:
3 = 81**0.25
請注意,這只是一個一般規則;您可以根據需要設置嵌入維度的數量。
調用 tf.feature_column.embedding_column
來創建一個 embedding_column
,如以下代碼段所示:
categorical_column = ... # Create any categorical column
# Represent the categorical column as an embedding column.
# This means creating a one-hot vector with one element for each category.
embedding_column = tf.feature_column.embedding_column(
categorical_column=categorical_column,
dimension=dimension_of_embedding_vector)
嵌入是機器學習中的一個重要主題。這些信息僅僅是幫助您將其用作特徵列的入門信息。
3. 將特徵列傳遞給 Estimator
如下所示,並非所有 Estimator 都支持所有類型的 feature_columns 參數:
LinearClassifier
和LinearRegressor
:接受所有類型的特徵列。DNNClassifier
和DNNRegressor
:只接受密集列。其他類型的列必須封裝在indicator_column
或embedding_column
中。DNNLinearCombinedClassifier
和DNNLinearCombinedRegressor
:linear_feature_columns
參數接受任何類型的特徵列。dnn_feature_columns
參數只接受密集列。
4. 其他資料
關於特徵列的更多實例,請查看:
- 低階 API 簡介:展示了 TensorFlow 的低階 API 與
feature_columns
的配合使用。 - 寬度模型和寬度與深度模型教程:針對各種輸入數據類型使用
feature_columns
解決二元分類問題。
- 深度學習、NLP 和表示法(Chris Olah 的博客)
- TensorFlow Embedding Projector