-
Notifications
You must be signed in to change notification settings - Fork 3
/
tf_cross.py
212 lines (171 loc) · 6.78 KB
/
tf_cross.py
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# Author: [email protected] | Date: Oct 29, 2019
#
# TF 2.0 required. Simply run the code under python 3.
#
# Real world implementation of a cross-feature using Autograph ops.
import time
import tensorflow as tf
# Scroll down till you see the top level function:
# The functions below define the basic UFR style feature inputs.
# The code is the implementation of the following cross feature from C++ code:
# http://opengrok.pinadmin.com/xref/cosmos/scorpion/ads_count/preprocessor_utils.cpp?r=1cbe156e#populateRelatedPinsAnnotationMatchScores
# Outputs
# Enum: ViewType::RELATED_PINS
def sqf_impression_view_type():
return tf.constant([42])
# map<pterm.PTermType, list<search_common.AnnotationData>>
# The access is only to the ID field inside AnnotationData.
# The 500x is annotation ids. The 100x are PTermTypes.
def sqf_query_annotations():
return tf.sparse.SparseTensor(
indices=[[1001, 0], [1002, 0], [1003, 0]],
values=[5001., 5002., 5003.],
dense_shape=[10000, 2]) # 1D ID array
# map<pterm.PTermType, list<search_common.AnnotationData>>
# The access is only to the ID field inside AnnotationData.
# The 500x is annotation ids. The 100x are PTermTypes.
def ppd_pin_annotations():
return tf.sparse.SparseTensor(
indices=[[1001, 0], [1002, 0], [1003, 0]],
values=[5001., 5003., 5005.],
dense_shape=[10000, 2]) # 1D ID array.
# list<PTermType> - constant lookup list for PTerms
def pterm_types():
return tf.constant([1001, 1002, 1004])
# int32
def task_type():
return tf.constant(1)
# bool
def need_cmp():
return tf.constant(False)
# Slice map<PTermType, AnnotationIds> on a constant PTermList used for indexing
# https://github.com/tensorflow/tensorflow/issues/1950
def gather_op(tensor, index_list):
return tensor
# return tf.gather(
# tensor,
# index_list,
# validate_indices=True,
# axis=0,
# name="slice_pterms")
# Implements the cosine max norm match.
# Returns a 2 element list:
# (numMatchesAboveThreshold, cosineMaxNormMatch)
# Tensor1 has shape [10000, 2]
# PyTorch API is better but under-documented:
# https://pytorch.org/docs/stable/sparse.html
def cosine_op(tensor1, tensor2):
eps_tensor_1 = tf.sparse.SparseTensor(
tensor1.indices,
[1e-14] * len(tensor1.values),
tensor1.dense_shape,
dtype=tf.float32)
eps_tensor_2 = tf.sparse.SparseTensor(
tensor2.indices,
[1e-14] * len(tensor2.values),
tensor2.dense_shape,
dtype=tf.float32)
minimum_1 = tf.sparse.minimum(eps_tensor_1, tensor1, name='minimum_1')
minimum_2 = tf.sparse.minimum(eps_tensor_2, tensor2, name='minimum_2')
max_score_1 = tf.sparse.reduce_max(tensor1, name='maxScore1')
if max_score_1 < 1e-14:
return tf.constant([0., 0.])
if (not tf.reduce_all(tf.math.equal(minimum_1.values, eps_tensor_1.values)) or
not tf.reduce_all(tf.math.equal(minimum_2.values, eps_tensor_2.values))):
# Early return
return tf.constant([0., 0.])
# Not the most efficient, but ok.
# tf.sparse_dense_matmul and tf.embedding_lookup_sparse
# https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/sparse/sparse_dense_matmul
# Best option is to do a sparse gather + mult but that's taking too much time.
# https://github.com/tensorflow/tensorflow/issues/1950
# We can implement this manually, will take time.
## Edit: manual implementation
i = 0
j = 0
score = 0.
i_max = tf.constant(len(tensor1.indices))
j_max = tf.constant(len(tensor2.indices))
while i < i_max:
while j < j_max:
if tf.reduce_any(tf.math.greater(tensor1.indices[i], tensor2.indices[j])):
j += 1
elif tf.reduce_all(tf.math.equal(tensor2.indices[j], tensor1.indices[i])):
score += tensor1.values[i] * tensor2.values[j]
j += 1
else:
break
i += 1
## Edit: old code
matmul = tf.matmul(tf.sparse.to_dense(tensor1, 0.0), tf.transpose(tf.sparse.to_dense(tensor2, 0.0)))
score = tf.reduce_sum(matmul)
max_score_2 = tf.reduce_max(matmul)
num_match = tf.math.count_nonzero(matmul)
divide_1 = tf.math.divide(score, max_score_1, name='div1')
divide_2 = tf.math.divide(divide_1, max_score_2, name='div2')
return tf.stack([tf.cast(num_match, tf.dtypes.float32, name='float_cast'), divide_2], axis=0)
###################################################
# Top level function
# Needs to return a shape [10000, 2]
@tf.function
def cross_feature_model(
sqf_impression_view_type, # Shape: [1] - int
sqf_query_annotations, # Shape: [10000, 2] - float
ppd_pin_annotations, # Shape: [10000, 2] - float
needCmp # Shape: [1] - bool
): # Returns cross feature
if not needCmp or not tf.equal(sqf_impression_view_type, tf.constant(42)):
return tf.zeros(
[10000, 2],
dtype=tf.dtypes.float32,
name='early_return_zeros')
# Slice to only the restricted PTermTypes
pterm_list = pterm_types()
query_annotations = gather_op(sqf_query_annotations, pterm_list)
pin_annotations = gather_op(ppd_pin_annotations, pterm_list)
cosine_max_norm_match = cosine_op(
query_annotations,
pin_annotations)
return cosine_max_norm_match
# Map<PTermType, AnnotationMatchScore>
# PTermType == annotation id
# AnnotationMatchScore == (numMatchesAboveThreshold: i16, cosineMaxNormMatch: double)
def related_pins_annotation_match_score():
indices = [[1001, 0], [1002, 0], [1003, 0],
[1001, 1], [1002, 1], [1003, 1]] # Map entries
values = [32, 64, 128, # numMatchesAboveThreshold
0.1, 0.2, 0.3] # cosineMaxNorm
dense_shape = [10000, 2] # Max number of annotations.
return tf.sparse.SparseTensor(
indices=indices,
values=values,
dense_shape=dense_shape)
sqf = {
'impression.view_type': sqf_impression_view_type()
}
ppd = {
}
intf = {
'relatedPinsAnnotationMatchScore': related_pins_annotation_match_score()
}
print('Hello world!')
start = time.perf_counter_ns()
mod_result = cross_feature_model(
sqf_impression_view_type(),
sqf_query_annotations(),
ppd_pin_annotations(),
tf.constant(True))
end = time.perf_counter_ns()
print("Time (usec): ", (end - start) / 1e3)
print('Cosine max norm match: ',
mod_result)
class Test(tf.Module):
@tf.function
def model(self, sqf_impression_view_type, # Shape: [1] - int
sqf_query_annotations, # Shape: [10000, 2] - float
ppd_pin_annotations, # Shape: [10000, 2] - float
needCmp # Shape: [1] - bool
): # Returns cross feature
cross_feature_model(sqf_impression_view_type, sqf_query_annotations, ppd_pin_annotations, needCmp)
mod = Test()
tf.saved_model.save(mod, '/tmp/model.pb')