TensorFlow feature_column 介紹與體驗

TensorFlow feature_column 介紹與體驗

Tensorflow 提供了名爲 tf.feature_column 的強大工具用於特徵處理, 後續可以很方便的用於基於 Estimator 的模型, 這一點在官方提供的兩個例子中體現的淋漓盡致:

爲了能儘快體驗 tf.feature_column 提供的各項功能, 本文提供了一小片 demo 代碼, 可以直觀感受 tf.feature_column 的作用, 之後還會簡單分析下 tf.feature_column 底層實現源碼.

迫不及待了吧 🤣 🤣 🤣, 那麼下面先來看看 tf.feature_column 的體驗代碼.

體驗代碼

測試環境爲:

  • Python 3.5.6
  • TensorFlow 1.12.0
#_*_ coding:utf-8 _*_
import warnings
warnings.filterwarnings('ignore')
import tensorflow as tf

## 手動創建簡單的數據集, 方便看效果
dataset = tf.data.Dataset.from_tensor_slices(({'age': [1, 2, 3],
                                               'relationship': ['Wife', 'Husband', 'Unmarried']},
                                              [0, 1, 0]))
dataset = dataset.batch(3)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
features, label = next_element

## 創建類別特徵
age = tf.feature_column.categorical_column_with_identity('age', num_buckets=5, default_value=0)
relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    'relationship', [
        'Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried',
        'Other-relative'])
## 由於後面使用 tf.feature_column.input_layer 來查看結果, 而它只接受 dense 特徵,
## 因此要先使用 indicator_column 或者 embedding_column 將類別特徵轉換爲 dense 特徵 
age_one_hot = tf.feature_column.indicator_column(age)
relationship_one_hot = tf.feature_column.indicator_column(relationship)

age_one_hot_dense = tf.feature_column.input_layer(features, age_one_hot)
relationship_one_hot_dense = tf.feature_column.input_layer(features, relationship_one_hot)
concat_one_hot_dense = tf.feature_column.input_layer(features, [age_one_hot, relationship_one_hot])

with tf.Session() as sess:
	# 使用 tables.initializer() 初始化 Graph 中的所有 LookUpTable
    sess.run(tf.tables_initializer())
    a, b, c = sess.run([age_one_hot_dense, relationship_one_hot_dense, concat_one_hot_dense])
    print('age_one_hot:\n{}'.format(a))
    print('relationship_one_hot:\n{}'.format(b))
    print('concat_one_hot:\n{}'.format(c))

運行程序, 得到輸出結果如下:

不錯, 符合預期~. 下面再對上面的代碼進行簡要的分析.

代碼分析

創建表格

先來看數據集. 注意到 tf.feature_column 是用來處理結構化的數據的. 將上面 demo 中的數據集繪成表格如下:

age relationship label
1 'Wife' 0
2 'Husband' 1
3 'Unmarried' 0

由於設置的 batch_size 爲 3, 在 features, label = next_element 這一步時, 會將 3 條數據全部讀入.

創建類別特徵

在如下代碼中,

age = tf.feature_column.categorical_column_with_identity('age', num_buckets=5, default_value=0)
relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    'relationship', [
        'Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried',
        'Other-relative'])

分別用 tf.feature_column.categorical_column_with_identitytf.feature_column.categorical_column_with_vocabulary_list 來創建了類別特徵, 這兩個函數的作用推薦看 TF 的官方文檔, 簡單來說, 它們將原始特徵轉化爲 SparseTensor, 以便機器學習算法能夠利用. 其中 age 的 tensor 形式如下:

SparseTensorValue(indices=array([
	   [0],
       [1],
       [2]]), values=array([1, 2, 3]), dense_shape=array([3]))

可以看到 values 中的值和 age 原始特徵的值是一樣的.

relationship 的形式如下:

SparseTensorValue(indices=array([
       [0],
       [1],
       [2]]), values=array([2, 0, 4]), dense_shape=array([3]))

values 此時爲 [2, 0, 4], 注意到我們在生成 relationship 時, 傳入的 list 爲:

['Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried', 'Other-relative']

而原始特徵爲 ['Wife', 'Husband', 'Unmarried'], 在 list 中的索引依次是 [2, 0, 4].

至於如何得到 agerelationship 對應的 SparseTensor, 我會在之後分析源碼時介紹.

下一步, 爲了看到 agerelationship 的 OneHot 形式, 需要使用 tf.feature_column.indicator_column 進行轉換.

age_one_hot = tf.feature_column.indicator_column(age)
relationship_one_hot = tf.feature_column.indicator_column(relationship)

age_one_hot_dense = tf.feature_column.input_layer(features, age_one_hot)
relationship_one_hot_dense = tf.feature_column.input_layer(features, relationship_one_hot)

注意到還使用 tf.feature_column.input_layer, 它的作用是將輸入的 dense 特徵按照 axis=1 進行 concatenation.

比如:

concat_one_hot_dense = tf.feature_column.input_layer(features, [age_one_hot, relationship_one_hot])

可以將上面這段代碼想象(沒錯, “想象” 🤣🤣🤣)成, 從 features 中提取 agerelationship 的 OneHot 結果進行 concatenation.

feature_column 源碼簡析

簡單分析一下~

categorical_column_with_identity

categorical_column_with_identity 的實現位於 _IdentityCategoricalColumn, 在 _transform_feature 函數中返回一個 SparseTensor:

return sparse_tensor_lib.SparseTensor(
        indices=input_tensor.indices,
        values=values,
        dense_shape=input_tensor.dense_shape)

爲了能看到這個返回的結果 (比如前面返回 agerelationship 結果), 可以試試如下的代碼:

import warnings
warnings.filterwarnings('ignore')
import tensorflow as tf

dataset = tf.data.Dataset.from_tensor_slices(({'age': [1, 2, 3],
                                               'relationship': ['Wife', 'Husband', 'Unmarried']},
                                              [0, 1, 0]))
dataset = dataset.batch(3)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
features, label = next_element

age = tf.feature_column.categorical_column_with_identity('age', num_buckets=5, default_value=0)
relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    'relationship', [
        'Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried',
        'Other-relative'])
age_one_hot = tf.feature_column.indicator_column(age)
relationship_one_hot = tf.feature_column.indicator_column(relationship)

relationship_one_hot = tf.feature_column.input_layer(features, relationship_one_hot)
a = age._transform_feature(features)
b = relationship._transform_feature(features)
with tf.Session() as sess:
    sess.run(tf.tables_initializer())
    a1, b1 = sess.run([a, b])
    print(a1)
    print(b1)

輸出結果如下:

SparseTensorValue(indices=array([[0],
       [1],
       [2]]), values=array([1, 2, 3]), dense_shape=array([3]))
SparseTensorValue(indices=array([[0],
       [1],
       [2]]), values=array([2, 0, 4]), dense_shape=array([3]))

categorical_column_with_vocabulary_list

categorical_column_with_vocabulary_list 的功能實現位於 _VocabularyListCategoricalColumn, 在 transform_feature 函數中看到最後返回的結果如下:

return lookup_ops.index_table_from_tensor(
        vocabulary_list=tuple(self.vocabulary_list),
        default_value=self.default_value,
        num_oov_buckets=self.num_oov_buckets,
        dtype=key_dtype,
        name='{}_lookup'.format(self.key)).lookup(input_tensor)

雖然不知道 lookup_ops.index_table_from_tensor 的細節, 但看其名字和輸入參數就應該對其的作用瞭然於心了 (別信, 別信). 🤣 🤣 🤣

indicator_column

indicator_column 的功能實現位於 _IndicatorColumn, 在 _transform_feature 函數中可以看到最後返回一個 OneHot 的結果.

# One hot must be float for tf.concat reasons since all other inputs to
# input_layer are float32.
one_hot_id_tensor = array_ops.one_hot(
        dense_id_tensor,
        depth=self._variable_shape[-1],
        on_value=1.0,
        off_value=0.0)

input_layer

最後來看看 input_layer, 我們已經知道它是將特徵進行 concatenation 了, 其實現位於: _internal_input_layer, 其輸入參數包括爲:

def _internal_input_layer(features,
                          feature_columns,

之後對於 feature_columns 中的 column, 均要求爲 _DenseColumn:

for column in feature_columns:
    if not isinstance(column, _DenseColumn):
      raise ValueError(
          'Items of feature_columns must be a _DenseColumn. '
          'You can wrap a categorical column with an '
          'embedding_column or indicator_column. Given: {}'.format(column))

否則報錯. 在 get_logits() 函數中, 最後返回:

return array_ops.concat(output_tensors, 1)

結果就是各個輸入特徵的 concatenation.

源碼暫分析到這~

關於 tables_initializer

如果沒有加上 tables_initializer, 可能會報如下錯誤:

FailedPreconditionError (see above for traceback): Table not initialized.
         [[node hash_table_Lookup (defined at 5.py:23)  = LookupTableFindV2[Tin=DT_STRING, Tout=DT_INT64, _device="/job:localhost/replica:0/task:0/device:CPU:0"](relationship_lookup/hash_table, to_sparse_input_1/values, relationship_lookup/hash_table/Const)]]

老老實實加上唄.

推薦資料

本文並沒有過多介紹 feature_column 下的各個 API 的使用, 但看完以上內容, 結合官方文檔, 應該能如魚得水, 瀟灑自如.

另外再推薦文章 Introduction to Tensorflow Estimators, 構建分佈式Tensorflow模型系列:特徵工程 以及 【Tensorflow2】FeatureColumn簡明教程, 圖文並茂, 向大佬學習.

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