Skip to content

Commit 2940f8f

Browse files
authored
Added Criteo TFT fill_in_missing helper (#39011)
* Added Criteo TFT fill_in_missing helper * Fix Criteo sparse tensor shape tracing * Lint fixed * updated the helper to use the old code verbatim
1 parent 0ec0847 commit 2940f8f

2 files changed

Lines changed: 110 additions & 13 deletions

File tree

sdks/python/apache_beam/testing/benchmarks/cloudml/criteo_tft/criteo.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ def get_transformed_categorical_column_name(column_name_or_id):
3838
return column_name + '_id'
3939

4040

41+
def fill_in_missing(feature, default_value):
42+
"""Fills missing values in a rank 2 SparseTensor.
43+
44+
Args:
45+
feature: A rank 2 SparseTensor with at most one value per row.
46+
default_value: The value to fill in for missing entries.
47+
48+
Returns:
49+
A rank 1 Tensor with missing entries filled in.
50+
"""
51+
feature = tft.sparse_tensor_to_dense_with_shape(
52+
feature, [None, 1], default_value=default_value)
53+
return tf.squeeze(feature, axis=1)
54+
55+
4156
_INTEGER_COLUMN_NAMES = [
4257
'int-feature-{}'.format(column_idx) for column_idx in range(1, 14)
4358
]
@@ -132,23 +147,12 @@ def preprocessing_fn(inputs):
132147
result = {'clicked': inputs['clicked']}
133148
for name in _INTEGER_COLUMN_NAMES:
134149
feature = inputs[name]
135-
# TODO(https://github.com/apache/beam/issues/24902):
136-
# Replace this boilerplate with a helper function.
137-
# This is a SparseTensor because it is optional. Here we fill in a
138-
# default value when it is missing.
139-
feature = tft.sparse_tensor_to_dense_with_shape(
140-
feature, [None, 1], default_value=-1)
141-
# Reshaping from a batch of vectors of size 1 to a batch of scalars and
142-
# adding a bucketized version.
143-
feature = tf.squeeze(feature, axis=1)
150+
feature = fill_in_missing(feature, -1)
144151
result[name] = feature
145152
result[name + '_bucketized'] = tft.bucketize(feature, _NUM_BUCKETS)
146153
for name in _CATEGORICAL_COLUMN_NAMES:
147154
feature = inputs[name]
148-
# Similar to for integer columns, but use '' as default.
149-
feature = tft.sparse_tensor_to_dense_with_shape(
150-
feature, [None, 1], default_value='')
151-
feature = tf.squeeze(feature, axis=1)
155+
feature = fill_in_missing(feature, '')
152156
result[get_transformed_categorical_column_name(
153157
name)] = tft.compute_and_apply_vocabulary(
154158
feature, frequency_threshold=frequency_threshold)
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import unittest
19+
20+
try:
21+
import tensorflow as tf
22+
23+
from apache_beam.testing.benchmarks.cloudml.criteo_tft import criteo
24+
except ImportError:
25+
raise unittest.SkipTest('Dependencies are not installed')
26+
27+
28+
class CriteoTest(tf.test.TestCase):
29+
def test_fill_in_missing_int_feature(self):
30+
feature = tf.SparseTensor(
31+
indices=[[0, 0], [2, 0]],
32+
values=tf.constant([10, 30], dtype=tf.int64),
33+
dense_shape=[3, 1])
34+
35+
result = criteo.fill_in_missing(feature, -1)
36+
37+
self.assertAllEqual(result, [10, -1, 30])
38+
self.assertEqual(result.shape.rank, 1)
39+
40+
def test_fill_in_missing_int_feature_traces_with_dynamic_shape(self):
41+
@tf.function(
42+
input_signature=[
43+
tf.SparseTensorSpec(shape=[None, None], dtype=tf.int64)
44+
])
45+
def fill_in_missing(feature):
46+
return criteo.fill_in_missing(feature, -1)
47+
48+
feature = tf.SparseTensor(
49+
indices=[[0, 0], [2, 0]],
50+
values=tf.constant([10, 30], dtype=tf.int64),
51+
dense_shape=[3, 1])
52+
53+
result = fill_in_missing(feature)
54+
55+
self.assertAllEqual(result, [10, -1, 30])
56+
self.assertEqual(result.shape.rank, 1)
57+
58+
def test_fill_in_missing_all_missing_int_feature(self):
59+
feature = tf.SparseTensor(
60+
indices=tf.zeros([0, 2], dtype=tf.int64),
61+
values=tf.constant([], dtype=tf.int64),
62+
dense_shape=[3, 0])
63+
64+
result = criteo.fill_in_missing(feature, -1)
65+
66+
self.assertAllEqual(result, [-1, -1, -1])
67+
self.assertEqual(result.shape.rank, 1)
68+
69+
def test_fill_in_missing_string_feature(self):
70+
feature = tf.SparseTensor(
71+
indices=[[0, 0], [2, 0]],
72+
values=tf.constant(['a', 'c'], dtype=tf.string),
73+
dense_shape=[3, 1])
74+
75+
result = criteo.fill_in_missing(feature, '')
76+
77+
self.assertAllEqual(result, [b'a', b'', b'c'])
78+
self.assertEqual(result.shape.rank, 1)
79+
80+
def test_fill_in_missing_all_missing_string_feature(self):
81+
feature = tf.SparseTensor(
82+
indices=tf.zeros([0, 2], dtype=tf.int64),
83+
values=tf.constant([], dtype=tf.string),
84+
dense_shape=[3, 0])
85+
86+
result = criteo.fill_in_missing(feature, '')
87+
88+
self.assertAllEqual(result, [b'', b'', b''])
89+
self.assertEqual(result.shape.rank, 1)
90+
91+
92+
if __name__ == '__main__':
93+
unittest.main()

0 commit comments

Comments
 (0)