tensorflow交叉特征实现
tensorflow本身的实现是带batch_size的,这里主要看单条样本怎么做的。
假设有两个特征年龄和性别,年龄数数值特征,性别是类别特征。
对年龄先分桶,然后和性别做交叉,大概实现如下。
假设年龄是0~100岁,分10个桶,其中几个年龄数值和分桶的对应关系:
age bucketized_age
27岁 17
28岁 18
29岁 19
30岁 20
对性别的取值(字符串):
男 "1"
女 "2"
特征交叉python代码
age = tf.feature_column.numeric_column('age')
bucketized_age = tf.feature_column.bucketized_column(age, boundaries=[0,10,...,27,28,29,30,35,...100])
gender = tf.feature_column.categorical_column_with_vocabulary_list('gender', vocabulary_list=('1', '2')
age_cross_gender = tf.feature_column.crossed_column([gender, bucketized_age], 10)
此处的bucketized_column主要针对数值特征,数值特征必须经过分桶或identity才能和其他特征交叉。假设输入是29岁,分桶后的值为19。这是一个整数index,会直接作为交叉特征列的输入被使用。
对于性别,使用categorical_column_with_vocabulary_list后,性能男被映射到index=0上,性别女被映射到index=1上,所以如果是男,那此时对交叉特征列的输入就是整数0。
在上面这个例子里,交叉特征的输入都是整数,因为实际上都是index。
tensorflow内部怎么实现呢?官方给的解释是说,会按倒序的方式做hash拼接,如下:
# 假设输入的交叉特值分别是[a]和[b]
Fingerprint64Cat(Fingerprint64('b'), Fingerprint64('a'))
但实际上,这是针对字符串输入才是这样,整数不会做hash,直接拼接。源码如下:
dense tensor
// tensorflow/core/kernels/sparse_cross_op.cc
// 以下的 n 即是前面提到的输入 index
template <>
int64 DenseTensorColumn::Feature(int64 batch, int64 n) const {
if (DT_STRING == tensor_.dtype())
return Fingerprint64(tensor_.matrix()(batch, n));
return tensor_.matrix()(batch, n);
}
sparse tensor
// 和dense tensor实现是一致的,对于整数值直接返回
template <>
string SparseTensorColumn::Feature(int64 batch, int64 n) const {
const int64 start = feature_start_indices_[batch];
if (DT_STRING == values_.dtype())
return values_.vec().data()[start + n];
return std::to_string(values_.vec().data()[start + n]);
}
取到值之后如何交叉呢:
// 以下实现来自代码 tensorflow/core/kernels/sparse_cross_op.cc:HashCrosser
// 会有一个默认的hash_key排在最前面,然后依次拼接余下的值
int64 Generate(const int64 batch_index,
const std::vector& permutation) const {
// Do the fingerprint concatenation on uint64.
uint64 hashed_output = hash_key_;
for (size_t i = 0; i < permutation.size(); ++i) {
uint64 hash_i = columns_[i]->Feature(batch_index, permutation[i]);
hashed_output = FingerprintCat64(hashed_output, hash_i);
}
// The return value is int64 based on the number of buckets.
if (num_buckets_ > 0) {
return hashed_output % num_buckets_;
} else {
// To prevent negative output we take modulo to max int64.
return hashed_output % std::numeric_limits::max();
}
}
默认的hash*key定义在源码 *sparse_ops.py
_DEFAULT_HASH_KEY = 0xDECAFCAFFE
怎么测试正确性呢?可以在tensorflow/python/kernel_tests/sparse_cross_op_test.py中测试:
print('##############')
a1 = constant_op.constant([[19]])
a2 = constant_op.constant([[0]])
cross = sparse_ops.sparse_cross_hashed([a2, a1], num_buckets=12)
cross_dense = sparse_ops.sparse_tensor_to_dense(cross)
with session.Session():
values = cross_dense.eval()
print('#### ', values)
print('##############')
编译命令:
bazel build tensorflow/python/kernel_tests:sparse_cross_op_test
以上就会输出a2和 a1的交叉特征值。
怎么验证实现方式?可以在tensorflow/core/platform/fingerprint_test.cc中测试:
std::cout << "crossed_column (0, 17) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 0), 17) % 12) << std::endl;
std::cout << "crossed_column (1, 17) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 1), 17) % 12) << std::endl;
std::cout << "crossed_column (0, 18) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 0), 18) % 12) << std::endl;
std::cout << "crossed_column (1, 18) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 1), 18) % 12) << std::endl;
std::cout << "crossed_column (0, 19) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 0), 19) % 12) << std::endl;
std::cout << "crossed_column (1, 19) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 1), 19) % 12) << std::endl;
std::cout << "crossed_column (0, 20) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 0), 20) % 12) << std::endl;
std::cout << "crossed_column (1, 20) " << (FingerprintCat64(FingerprintCat64(0xDECAFCAFFE, 1), 20) % 12) << std::endl;
编译命令:
bazel build tensorflow/core:platform_fingerprint_test
以上对于a1, a2的排列顺序非常重要,目前看来交叉特征取名是按字符串排序的,但对于输入值是否有排序还不确定,目前只有2个特征还不必研究排序,等需要的时候再研究吧。
Comments