1+ import keras
2+ import numpy as np
3+
14from keras_hub .src .api_export import keras_hub_export
25from keras_hub .src .layers .preprocessing .preprocessing_layer import (
36 PreprocessingLayer ,
47)
8+ from keras_hub .src .utils .tensor_utils import (
9+ convert_preprocessing_outputs_python ,
10+ )
11+ from keras_hub .src .utils .tensor_utils import convert_to_list
512from keras_hub .src .utils .tensor_utils import convert_to_ragged_batch
13+ from keras_hub .src .utils .tensor_utils import in_tf_function
614from keras_hub .src .utils .tensor_utils import pad
715from keras_hub .src .utils .tensor_utils import preprocessing_function
816
@@ -74,7 +82,7 @@ class StartEndPacker(PreprocessingLayer):
7482 [ 1, 8, 9, 10, 11, 2]], dtype=int32)
7583
7684 Unbatched input (str).
77- >>> inputs = tf.constant( ["this", "is", "fun"])
85+ >>> inputs = ["this", "is", "fun"]
7886 >>> start_end_packer = keras_hub.layers.StartEndPacker(
7987 ... sequence_length=6, start_value="<s>", end_value="</s>",
8088 ... pad_value="<pad>"
@@ -84,7 +92,7 @@ class StartEndPacker(PreprocessingLayer):
8492 array(['<s>', 'this', 'is', 'fun', '</s>', '<pad>'], dtype='<U5')
8593
8694 Batched input (str).
87- >>> inputs = tf.ragged.constant( [["this", "is", "fun"], ["awesome"]])
95+ >>> inputs = [["this", "is", "fun"], ["awesome"]]
8896 >>> start_end_packer = keras_hub.layers.StartEndPacker(
8997 ... sequence_length=6, start_value="<s>", end_value="</s>",
9098 ... pad_value="<pad>"
@@ -95,7 +103,7 @@ class StartEndPacker(PreprocessingLayer):
95103 ['<s>', 'awesome', '</s>', '<pad>', '<pad>', '<pad>']], dtype='<U7')
96104
97105 Multiple start tokens.
98- >>> inputs = tf.ragged.constant( [["this", "is", "fun"], ["awesome"]])
106+ >>> inputs = [["this", "is", "fun"], ["awesome"]]
99107 >>> start_end_packer = keras_hub.layers.StartEndPacker(
100108 ... sequence_length=6, start_value=["</s>", "<s>"], end_value="</s>",
101109 ... pad_value="<pad>"
@@ -117,7 +125,10 @@ def __init__(
117125 padding_side = "right" ,
118126 ** kwargs ,
119127 ):
120- super ().__init__ (name = name , ** kwargs )
128+ _allow_python_workflow = kwargs .pop ("_allow_python_workflow" , True )
129+ super ().__init__ (
130+ name = name , _allow_python_workflow = _allow_python_workflow , ** kwargs
131+ )
121132
122133 self .sequence_length = sequence_length
123134
@@ -126,6 +137,8 @@ def __init__(
126137 self ._end_value = end_value
127138
128139 def check_special_value_type (value , value_name ):
140+ if value is None :
141+ return None
129142 if isinstance (value , (int , str )):
130143 return [value ]
131144 if value and not isinstance (value , (list , tuple )):
@@ -146,7 +159,7 @@ def check_special_value_type(value, value_name):
146159 self .padding_side = padding_side
147160
148161 @preprocessing_function
149- def call (
162+ def _call_tf (
150163 self ,
151164 inputs ,
152165 sequence_length = None ,
@@ -203,6 +216,174 @@ def call(
203216 return outputs , mask
204217 return outputs
205218
219+ def _call_python (
220+ self ,
221+ inputs ,
222+ sequence_length = None ,
223+ add_start_value = True ,
224+ add_end_value = True ,
225+ ):
226+ # TODO(hongyuc): Improve the performance of `_call_python`. It becomes
227+ # slower when encountering large inputs compared to `_call_tf`.
228+ def _canonicalize_inputs (inputs ):
229+ if isinstance (inputs , (tuple , list )):
230+ # Fast path for common cases:
231+ # If the inputs are just normal python types (or lists of
232+ # python types), it immediately returns.
233+ if not inputs :
234+ return [list (inputs )], False
235+ first = inputs [0 ]
236+ if isinstance (
237+ first , (int , str , float , bool , np .integer , np .floating )
238+ ):
239+ return [list (inputs )], False
240+ if isinstance (first , (tuple , list )) and (
241+ not first
242+ or isinstance (
243+ first [0 ],
244+ (int , str , float , bool , np .integer , np .floating ),
245+ )
246+ ):
247+ return [list (x ) for x in inputs ], True
248+
249+ # `keras.tree.map_structure` is expensive.
250+ inputs = keras .tree .map_structure (convert_to_list , inputs )
251+ if inputs and isinstance (inputs [0 ], (tuple , list )):
252+ return inputs , True
253+ else :
254+ return [inputs ], False
255+ elif tf is not None and isinstance (
256+ inputs , (tf .Tensor , tf .RaggedTensor )
257+ ):
258+ unbatched = inputs .shape .rank == 1
259+ if unbatched :
260+ inputs = tf .expand_dims (inputs , 0 )
261+ if isinstance (inputs , tf .Tensor ):
262+ inputs = convert_to_list (inputs )
263+ else :
264+ inputs = inputs .to_list ()
265+ return inputs , not unbatched
266+ elif keras .ops .is_tensor (inputs ):
267+ inputs = convert_to_list (inputs )
268+ if inputs and isinstance (inputs [0 ], (tuple , list )):
269+ return inputs , True
270+ else :
271+ return [inputs ], False
272+ else :
273+ raise ValueError (
274+ f"Input should be a list or a list of lists. "
275+ f"Received: { inputs } "
276+ )
277+
278+ def _get_type (inputs ):
279+ for sequence in inputs :
280+ if sequence is not None and len (sequence ) > 0 :
281+ return type (sequence [0 ])
282+ return int # Default to int if all sequences are empty.
283+
284+ def _canonicalize_value (values , input_type ):
285+ if input_type is str :
286+ return [str (v ) for v in values ]
287+ else :
288+ return [int (v ) for v in values ]
289+
290+ def _pad (x , pad_value , padding_side , sequence_length , input_type = None ):
291+ if padding_side not in ("left" , "right" ):
292+ raise ValueError (
293+ "padding_side must be 'left' or 'right'. "
294+ f"Received: { padding_side } "
295+ )
296+ if pad_value is None :
297+ pad_value = "" if input_type is str else 0
298+ if padding_side == "right" :
299+ x = [
300+ seq + [pad_value ] * (sequence_length - len (seq ))
301+ for seq in x
302+ ]
303+ else :
304+ x = [
305+ [pad_value ] * (sequence_length - len (seq )) + seq
306+ for seq in x
307+ ]
308+ return x
309+
310+ def _canonicalize_outputs (outputs , dtype = None ):
311+ flat_outputs = keras .tree .flatten (outputs )
312+ if not flat_outputs :
313+ return np .array (outputs , dtype = dtype or "int32" )
314+ first_element = flat_outputs [0 ]
315+ if not isinstance (first_element , str ):
316+ return np .array (outputs , dtype = dtype or "int32" )
317+ else :
318+ return outputs
319+
320+ inputs , batched = _canonicalize_inputs (inputs )
321+ input_type = _get_type (inputs )
322+ sequence_length = sequence_length or self .sequence_length
323+ x = inputs
324+
325+ # Truncate and normalize to list of lists.
326+ truncation_length = sequence_length
327+ if add_start_value and self .start_value is not None :
328+ truncation_length -= len (self .start_value )
329+ if add_end_value and self .end_value is not None :
330+ truncation_length -= len (self .end_value )
331+ x = [list (seq )[:truncation_length ] for seq in x ]
332+
333+ # Concatenate start and end tokens.
334+ if add_start_value and self .start_value is not None :
335+ start_value = _canonicalize_value (self .start_value , input_type )
336+ x = [start_value + seq for seq in x ]
337+ if add_end_value and self .end_value is not None :
338+ end_value = _canonicalize_value (self .end_value , input_type )
339+ x = [seq + end_value for seq in x ]
340+
341+ # Pad to desired length.
342+ outputs = _pad (
343+ x ,
344+ pad_value = self .pad_value ,
345+ padding_side = self .padding_side ,
346+ sequence_length = sequence_length ,
347+ input_type = input_type ,
348+ )
349+ outputs = _canonicalize_outputs (outputs )
350+ outputs = outputs [0 ] if not batched else outputs
351+
352+ if self .return_padding_mask :
353+ masks = keras .tree .map_structure (lambda _ : True , x )
354+ masks = _pad (
355+ masks ,
356+ pad_value = False ,
357+ padding_side = self .padding_side ,
358+ sequence_length = sequence_length ,
359+ )
360+ masks = masks [0 ] if not batched else masks
361+ masks = _canonicalize_outputs (masks , dtype = "bool" )
362+ return convert_preprocessing_outputs_python ((outputs , masks ))
363+ return convert_preprocessing_outputs_python (outputs )
364+
365+ def call (
366+ self ,
367+ inputs ,
368+ sequence_length = None ,
369+ add_start_value = True ,
370+ add_end_value = True ,
371+ ):
372+ if not self ._allow_python_workflow or in_tf_function ():
373+ return self ._call_tf (
374+ inputs ,
375+ sequence_length = sequence_length ,
376+ add_start_value = add_start_value ,
377+ add_end_value = add_end_value ,
378+ )
379+ else :
380+ return self ._call_python (
381+ inputs ,
382+ sequence_length = sequence_length ,
383+ add_start_value = add_start_value ,
384+ add_end_value = add_end_value ,
385+ )
386+
206387 def get_config (self ):
207388 config = super ().get_config ()
208389 config .update (
@@ -220,4 +401,6 @@ def get_config(self):
220401 def compute_output_shape (self , inputs_shape ):
221402 inputs_shape = list (inputs_shape )
222403 inputs_shape [- 1 ] = self .sequence_length
404+ if self .return_padding_mask :
405+ return tuple (inputs_shape ), tuple (inputs_shape )
223406 return tuple (inputs_shape )
0 commit comments