tf.feature_column
是官方提供的一套用于处理结构化数据的工具。它是原始数据和Estimator
模型之间的桥梁。丰富的tf.feature_column
方法,让你可以将各种原始数据转换为Estimators
可以使用的格式,从而更加容易的进行模型实验。
这里我将对tf.feature_column
目前支持的16种方法的使用进行说明和演示。通过下面的图示可以看到,tf.feature_column
支持的所有特征列都继承自FeatureColumn
类。通过FeatureColumn
又引申出三个子类CategoricalColumn
、DenseColumn
、SequenceDenseColumn
分别对应离散特征、连续特征、连续序列特征。如果我们想自定义类似tf.feature_column
的方法,就需要通过继承这三个特征列来覆写对应的方法。不过这里只介绍官方的方法,自定义部分后续会再分享。
除了上面tf.feature_column
方法依赖关系的介绍,为了方便后面的演示,还需要了解两个很重要的类FeatureTransformationCache
和StateManager
。
FeatureTransformationCache
是输入数据的持有工具,它能够将输入进行缓存,从而方便后续对特征的多次使用。FeatureTransformationCache
的本质就是dict
。
StateManager
是为具有状态数据的特征列提供状态管理,主要涉及状态的创建、增加、获取等。官方给出的一种实现是将StateManager
与Layer
关联,将状态数据存到Layer
中进行管理。
预备知识基本就这些,下面我们来对每个tf.feature_column
方法依次进行说明。
CategoricalColumn系列
CategoricalColumn
派生出的10个特征处理方法功能上是类似的,区别主要在于离散特征映射为数值类型的方法不同:
tf.feature_column.categorical_column_with_vocabulary_list
:通过定义离散特征的取值列表,将离散特征映射为其对应的列表下标(从0开始)。
tf.feature_column.sequence_categorical_column_with_vocabulary_list
:同上,区别是输入数据是离散序列特征。
tf.feature_column.categorical_column_with_vocabulary_file
:通过定义离散特征的取值文件,将离散特征映射为其对应的文件行数(从0开始)。
tf.feature_column.sequence_categorical_column_with_vocabulary_file
:同上,区别是输入数据是离散序列特征。
tf.feature_column.categorical_column_with_identity
:是将数值特征视为离散特征,并直接映射到自身。
tf.feature_column.sequence_categorical_column_with_identity
:同上,区别是输入数据是离散序列特征。
tf.feature_column.categorical_column_with_hash_bucket
:通过定义hash的空间大小,将离散特征映射为其对应的哈希值。
tf.feature_column.sequence_categorical_column_with_hash_bucket
:同上,区别是输入数据是离散序列特征。
tf.feature_column.crossed_column
:通过定义hash的空间大小,对指定特征列表中的所有列进行交叉,将交叉后的离散值映射为其对应的哈希值。
tf.feature_column.weighted_categorical_column
:这个是带权特征列,只是将CategoricalColumn
和DenseColumn
进行组合,本身并不定义映射方法。
CategoricalColumn自定义方法
get_sparse_tensors(transformation_cache, state_manager)
该方法会返回一个IdWeightPair(id_tensor, weight_tensor)。id_tensor表示离散特征进过数值映射(例如hash)之后的SparseTensor形式,其对应的values是离散特征映射后的数值(注意这里并不是one-hot/multi-hot的SparseTensor形式)。weight_tensor表示离散特征对应的权重值。
num_buckets()
该方法会返回一个数值,表示离散特征映射为数值类型后的取值空间大小,也就是通过one-hot编码后的向量维度大小。
categorical_column_with_vocabulary_list
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def categorical_list_column(): column = tf.feature_column.categorical_column_with_vocabulary_list( key="feature", vocabulary_list=["value1", "value2", "value3"], dtype=tf.string, default_value=-1, num_oov_buckets=3) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [["value1", "value2"], ["value3", "value3"]], [["value3", "value5"], ["value4", "value4"]] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
def sequence_categorical_list_column(): column = tf.feature_column.sequence_categorical_column_with_vocabulary_list( key="feature", vocabulary_list=["value1", "value2", "value3"], dtype=tf.string, default_value=-1, num_oov_buckets=2) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ ["value1", "value2", "value3", "value3"], ["value3", "value5", "value4", "value4"] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
|
categorical_column_with_vocabulary_file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def categorical_file_column(): column = tf.feature_column.categorical_column_with_vocabulary_file( key="feature", vocabulary_file="path/valuelist", dtype=tf.string, default_value=-1, num_oov_buckets=3) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.SparseTensor( indices=[ [0, 0, 2], [0, 0, 3], [0, 2, 1], [1, 0, 1], [1, 1, 3] ], values=["value1", "value2", "value3", "value4", "value1"], dense_shape=[2, 3, 5] ) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
def sequence_categorical_file_column(): column = tf.feature_column.sequence_categorical_column_with_vocabulary_file( key="feature", vocabulary_file="path/valuelist", dtype=tf.string, default_value=-1, num_oov_buckets=3) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [["value1", "value2"], ["value3", "value3"]], [["value3", "value5"], ["value4", "value4"]] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
|
categorical_column_with_identity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def categorical_identity_column(): column = tf.feature_column.categorical_column_with_identity( key='feature', num_buckets=10, default_value=3) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [1, 2, 3, 4, 5, 6], [5, 6, 7, 8, 9, 10], [8, 9, 10, 11, 12, 13] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
def sequence_categorical_identity_column(): column = tf.feature_column.sequence_categorical_column_with_identity( key='feature', num_buckets=10, default_value=3) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[1, 2, 3], [4, 5, 6]], [[5, 6, 7], [8, 9, 10]], [[8, 9, 10], [11, 12, 13]] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
|
categorical_column_with_hash_bucket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def categorical_hash_column(): column = tf.feature_column.categorical_column_with_hash_bucket( key="feature", hash_bucket_size=5000, dtype=tf.string) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[["value1"], ["value2"]], [["value3"], ["value3"]]], [[["value3"], ["value5"]], [["value4"], ["value4"]]] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
def sequence_categorical_hash_column(): column = tf.feature_column.sequence_categorical_column_with_hash_bucket( key="feature", hash_bucket_size=5000, dtype=tf.string) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[["value1"], ["value2"]], [["value3"], ["value3"]]], [[["value3"], ["value5"]], [["value4"], ["value4"]]] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
|
crossed_column
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def cross_column(): column = tf.feature_column.crossed_column( keys=["feature1", "feature2"], hash_bucket_size=1000, hash_key=None) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature1": tf.constant(value=[ ["value11", "value12", "value13"], ["value11", "value11", "value14"] ]), "feature2": tf.SparseTensor( indices=[ [0, 1], [0, 3], [0, 5], [0, 6], [1, 0], [1, 2], [1, 4] ], values=[4, 1, 7, 9, 3, 4., 4], dense_shape=[2, 7]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
|
weighted_categorical_column
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def weighted_column(): sub_categorical_column = tf.feature_column.categorical_column_with_identity( key="feature", num_buckets=5, default_value=0) column = tf.feature_column.weighted_categorical_column( categorical_column=sub_categorical_column, weight_feature_key="feature_weight", dtype=tf.float32) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[[1, 2]], [[3, 4]], [[5, 5]]], [[[9, 8]], [[7, 6]], [[5, 4]]] ]), "feature_weight": tf.constant(value=[ [[1.1, 2.2, 3.3, 4.4, 5.5, 6.6]], [[9.9, 8.8, 7.7, 6.6, 5.5, 4.4]] ]) }) return column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None)
|
DenseColumn系列
DenseColumn
派生出的方法只有numeric_column
,就是简单的获得输入的连续特征数据。
DenseColumn自定义方法
get_dense_tensor(transformation_cache, state_manager)
这个方法返回一个Tensor,表示的就是最终输出的连续特征数据。
variable_shape()
这个方法返回一个TensorShape,表示get_dense_tensor
返回值的shape大小(不包含batch维度)。
numeric_column
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def numeric_column(): column = tf.feature_column.numeric_column( key="feature", shape=(5,), default_value=0, dtype=tf.float32, normalizer_fn=lambda x: x / 6) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]] ]) }) return column.get_dense_tensor(transformation_cache=feature_cache, state_manager=None)
|
SequenceDenseColumn系列
这个是DenseColumn的序列形式。序列数据是允许有缺失值的。所以,SequenceDenseColumn的输入数据必须是SparseTensor。而DenseColumn的输入数据必须是Tensor。
SequenceDenseColumn自定义方法
get_sequence_dense_tensor(transformation_cache, state_manager)
这个方法返回一个TensorSequenceLengthPair(dense_tensor, sequence_length)。dense_tensor是序列数据的Tensor格式的输出,对于缺失的值会进行default_value填充。sequence_length是记录每个batch序列长度的Tensor,这里的序列长度是不包括最后连续填充的长度。
sequence_numeric_column
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def sequence_numeric_column(): column = tf.feature_column.sequence_numeric_column( key="feature", shape=(3,), default_value=60, dtype=tf.float32, normalizer_fn=lambda x: x / 6) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.SparseTensor( indices=[ [0, 0, 1], [0, 1, 0], [0, 5, 0], [0, 5, 1], [1, 2, 1], [1, 3, 0], [1, 3, 1] ], values=[4, 1, 7, 9, 3, 4., 4], dense_shape=[2, 6, 2]) }) return column.get_sequence_dense_tensor(transformation_cache=feature_cache, state_manager=None)
|
CategoricalColumn X DenseColumn 系列
该系列只有一个方法bucketized_column
。这个方法需要传入一个1-D
的NumericColumn
(注意这里肯定是不支持SequenceNumericColumn
的)。
其功能就是利用boundaries
,将数值特征进行离散化。
例如,boundaries=[3, 5, 7, 10]
,就会得到下表的映射规则:
规则 |
映射值 |
x < 3 |
1 0 0 0 0 |
3 <= x < 5 |
0 1 0 0 0 |
5 <= x < 7 |
0 0 1 0 0 |
7 <= x < 10 |
0 0 0 1 0 |
10 <= x |
0 0 0 0 1 |
bucketized_column的两个输出方法
get_dense_tensor(transformation_cache, state_manager)
这个方法是从DenseColumn
继承来的。返回的值是每个数值通过映射之后的one-hot的表示形式,就是上面映射规则中的映射值。
get_sparse_tensors(transformation_cache, state_manager)
这个方法是从CategoricalColumn
继承来的。返回的值是get_dense_tensor
的输出在每个batch
做flatten
之后one-hot
编码的SparseTensor
格式。
bucketized_column
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def bucketized_column(): numeric_column = tf.feature_column.numeric_column( key="feature", shape=6, default_value=0, dtype=tf.float32) column = tf.feature_column.bucketized_column( source_column=numeric_column, boundaries=[3, 5, 7, 10]) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[1, 2], [3, 4], [5, 6]], [[7, 7], [9, 10], [11, 12]] ]) }) dense_tensor = column.get_dense_tensor(transformation_cache=feature_cache, state_manager=None) sparse_tensors = column.get_sparse_tensors(transformation_cache=feature_cache, state_manager=None) return dense_tensor, sparse_tensors
|
DenseColumn X SequenceDenseColumn 系列
该系列下的方法都是利用CategoricalColumn
作为输入数据的入口。由于继承了DenseColumn
和SequenceDenseColumn
,所以对非序列和序列特征都是支持的。
但是对于继承而来的两个方法get_dense_tensor
和get_sequence_dense_tensor
在使用时,需要看传入的CategoricalColumn
是哪一种。
如果使用categorical_*
方法创建的CategoricalColumn
,需要调用get_dense_tensor
。
如果使用sequence_categorical_*
方法创建的CategoricalColumn
,需要调用get_sequence_dense_tensor
。
注意这两个方法不会同时请作用。
indicator_column的两个输出方法
get_dense_tensor(transformation_cache, state_manager)
这个方法是从DenseColumn
继承来的。返回值计算的具体逻辑如下:
- 通过
CategoricalColumn
获得输入数据的映射值,得到一个shape=[a,b,c,d]
的SparseTensor
- 根据
CategoricalColumn
的num_buckets=N
值,将上面的SparseTensor
转成shape=[a,b,c,d,N]
的Tensor
- 将上面的
Tensor
以shape[-2]
为聚合维度进行reduce_sum
操作,得到一个shape=[a,b,c,N]
的Tensor
- 返回上面的
Tensor
get_sequence_dense_tensor(transformation_cache, state_manager)
这个方法是从SequenceDenseColumn
继承来的。这个方法返回一个TensorSequenceLengthPair(dense_tensor, sequence_length)
。
dense_tensor
的逻辑和get_dense_tensor
一样。不过需要注意的是SequenceColumn
对应的数据永远是3-D
的,分别为[batch, sequence, element]
。
sequence_length
记录每个batch
序列长度的Tensor
,这里的序列长度是不包括最后连续填充的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def dense_indicator_column(): categorical_column = tf.feature_column.categorical_column_with_identity( key="feature", num_buckets=5, default_value=0) column = tf.feature_column.indicator_column(categorical_column=categorical_column) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[[1, 2]], [[3, 4]], [[5, 5]]], [[[9, 8]], [[7, 6]], [[5, 4]]] ]) }) return column.get_dense_tensor(transformation_cache=feature_cache, state_manager=None)
def dense_sequence_indicator_column(): sequence_categorical_column = tf.feature_column.sequence_categorical_column_with_identity( key="feature", num_buckets=5, default_value=0) column = tf.feature_column.indicator_column(categorical_column=sequence_categorical_column) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[[1, 2]], [[3, 4]], [[5, 5]]], [[[9, 8]], [[7, 6]], [[5, 4]]] ]) }) return column.get_sequence_dense_tensor(transformation_cache=feature_cache, state_manager=None)
|
embedding_column的两个输出方法
EmbeddingColumn
(包括后面的SharedEmbeddingColumn
)与前面的Column
都不太一样。它是一种具有状态的Column
,其状态数据通过StateManager
进行管理。
这里所说的状态,其实就是模型需要进行训练的参数。FeatureColumn
基类中有个create_state
方法,就是用来进行参数初始化的。所以,对于具有状态的Column
,需要调用create_state
来初始化参数,才能进行后续的操作。
get_dense_tensor(transformation_cache, state_manager)
这个方法是从DenseColumn
继承来的。实现逻辑可以参考上面的indicator_column
。两者的区别在于,embedding_column
相较于indicator_column
会在进行one-hot
编码之后,不会直接返回这个编码,而是会将one-hot
编码和StateManager
管理的嵌入矩阵进行矩阵相乘,从而得到一个稠密的维度为dimension
的向量。最终返回的值是这个稠密的向量。
get_sequence_dense_tensor(transformation_cache, state_manager)
这个方法是从SequenceDenseColumn
继承来的。这个和indicator_column
是一样的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def embedding_column(): categorical_column = tf.feature_column.categorical_column_with_identity( key="feature", num_buckets=5, default_value=0) weighted_categorical_column = tf.feature_column.weighted_categorical_column( categorical_column=categorical_column, weight_feature_key="feature_weights", dtype=tf.float32) column = tf.feature_column.embedding_column( categorical_column=weighted_categorical_column, dimension=10, combiner="sqrtn", initializer=tf.initializers.ones, ckpt_to_load_from=None, tensor_name_in_ckpt=None, max_norm=None, trainable=True) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[[1, 2]], [[3, 4]], [[5, 5]]], [[[9, 8]], [[7, 6]], [[5, 4]]] ]), "feature_weights": tf.constant(value=[ [[1.1, 2.2, 3.3, 4.4, 5.5, 6.6]], [[9.9, 8.8, 7.7, 6.6, 5.5, 4.4]] ]) }) state_manager = feature_column_v2._StateManagerImplV2(layer=tf.keras.layers.Layer(), trainable=True) column.create_state(state_manager=state_manager) return column.get_dense_tensor(transformation_cache=feature_cache, state_manager=state_manager)
def sequence_embedding_column(): sequence_categorical_column = tf.feature_column.sequence_categorical_column_with_identity( key="feature", num_buckets=5, default_value=0) column = tf.feature_column.embedding_column( categorical_column=sequence_categorical_column, dimension=10, combiner="mean", initializer=tf.initializers.glorot_normal, ckpt_to_load_from=None, tensor_name_in_ckpt=None, max_norm=None, trainable=True) feature_cache = feature_column_lib.FeatureTransformationCache(features={ "feature": tf.constant(value=[ [[[1, 2]], [[3, 4]], [[5, 5]]], [[[9, 8]], [[7, 6]], [[5, 4]]] ]) }) state_manager = feature_column_v2._StateManagerImplV2(layer=tf.keras.layers.Layer(), trainable=True) column.create_state(state_manager=state_manager) return column.get_sequence_dense_tensor(transformation_cache=feature_cache, state_manager=state_manager)
|
shared_embedding_column
实现逻辑与EmbeddingColumn
相同。使用的时候,通过指定一个CategoricalColumn
列表,得到一个EmbeddingColumn
的列表。这个列表中的EmbeddingColumn
共享同一份嵌入向量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import tensorflow as tf from tensorflow.python.feature_column import feature_column_lib
def shared_embedding_column(): column1 = ... column2 = ... columns = tf.feature_column.shared_embeddings( categorical_columns=[column1, column2], shared_embedding_collection_name=None, dimension=10, combiner="mean", initializer=tf.initializers.glorot_normal, ckpt_to_load_from=None, tensor_name_in_ckpt=None, max_norm=None, trainable=True) shared_column1, shared_column2 = columns return shared_column1, shared_column2
|