@@ -842,9 +842,13 @@ def get_out_data_from_opts(
842842
843843class SliceNdLayer (_ConcatInputLayer ):
844844 """
845- This takes out a slice-range from some axis,
845+ This takes out a slice-range from the time axis,
846846 e.g. ``x[start:start + size]``.
847- This layers allows a different start slice point for each batch,
847+ If the input is of shape (B,T,F) and start is of shape (B,),
848+ then the output will be of shape (B,size,F).
849+ If the input is of shape (B,T,F) and start is of shape (B,T),
850+ then the output will be of shape (B,T,size,F).
851+ This layer allows a different start slice point for each batch,
848852 in contrast to :class:`SliceLayer`, and the start is variable.
849853 See also :class:`GatherNdLayer`.
850854 :class:`PrefixInTimeLayer` can recover the original shape (by zero-padding).
@@ -854,44 +858,93 @@ class SliceNdLayer(_ConcatInputLayer):
854858
855859 def __init__ (self , start , size , min_size = None , ** kwargs ):
856860 """
857- :param LayerBase start:
861+ :param LayerBase start: (B,...)
858862 :param int|None size: if None, it uses the max possible size, and it becomes a dynamic axis
859- :param int|None min_size: if size is None, but we want to have a min-size, set this
863+ :param int|None min_size: if size is None, but we want to have a min-size
860864 """
861865 super (SliceNdLayer , self ).__init__ (** kwargs )
862- from returnn .tf .util .basic import slice_nd , where_bc , expand_multiple_dims , DimensionTag
866+ from returnn .tf .util .basic import where_bc , expand_multiple_dims
863867 x = self .input_data .copy_as_batch_major ()
864- assert x .time_dim_axis == 1 , "currently only time-axis==1 supported"
865- seq_lens = x .get_sequence_lengths () if x .is_time_axis_dynamic () else None
868+ seq_lens = x .get_sequence_lengths () if x .is_time_axis_dynamic () else None # (B,) or None
866869 self .start = start
867- assert start .output .have_batch_axis () and start . output . batch_shape == ( None , )
868- start = start . output . get_placeholder_as_batch_major ()
870+ start_data = start .output .copy_as_batch_major () # e.g. (B,) or (B,T )
871+ start_t = start_data . placeholder
869872 if size is None :
873+ if min_size is None :
874+ min_size = 0
870875 if seq_lens is None :
871- size = tf .maximum (tf .reduce_max (x .batch_shape [1 ] - start ), 0 )
876+ assert isinstance (x .batch_shape [x .time_dim_axis ], int )
877+ size = tf .maximum (tf .reduce_max (x .batch_shape [x .time_dim_axis ] - start_t ), min_size ) # scalar
872878 else :
873- size = tf .maximum (tf .reduce_max (seq_lens - start ), 0 )
874- if min_size is not None :
875- size = tf .maximum (size , min_size )
876- self .size = size
877- start = tf .expand_dims (start , axis = 1 ) # (B, T)
878- slices = slice_nd (x .placeholder , start = tf .cast (start , tf .int32 ), size = size ) # (B,size, ...)
879+ # make seq_lens compatible with start_t
880+ seq_lens = expand_multiple_dims ( # e.g. (B,) or (B,1)
881+ x = seq_lens ,
882+ axes = [- 1 ] * (len (start_t .shape ) - len (seq_lens .shape )))
883+ size = tf .maximum (tf .reduce_max (seq_lens - start_t ), min_size ) # scalar
884+ # for each start index in start_data, we want to gather a slice
885+ # therefore, the output's first axes are the same as the ones from start_data
886+ # and the next axis will therefore be the slice axis
887+ slice_tag = self .output .dim_tags [start_data .batch_ndim ]
888+ assert slice_tag .description .startswith ("sliced-time:" )
889+ if not isinstance (size , int ):
890+ # in this case, size is not known before runtime and becomes dynamic and we need to set dyn_size
891+ if seq_lens is None :
892+ dyn_size = tf .maximum (x .batch_shape [x .time_dim_axis ] - start_t , min_size ) # (B,) or (B,T)
893+ else :
894+ dyn_size = tf .maximum (seq_lens - start_t , min_size ) # (B,) or (B,T)
895+ dyn_size_ext = Data (
896+ name = ("%s:dyn_size" % slice_tag .description ),
897+ dtype = Data .size_dtype ,
898+ placeholder = dyn_size ,
899+ dim_tags = start_data .dim_tags ,
900+ batch = slice_tag .batch ,
901+ beam = slice_tag .batch .beam if slice_tag .batch else self .output .beam ,
902+ control_flow_ctx = slice_tag .control_flow_ctx )
903+ slice_tag .dyn_size_ext = dyn_size_ext
904+ gather_positions_data = start_data .copy_template (name = "%s_gather_positions" % self .name )
905+ gather_positions_data = gather_positions_data .copy_add_dim_by_tag (
906+ slice_tag ,
907+ unbroadcast = True ,
908+ axis = start_data .batch_ndim )
909+ # [start+0, start+1, ...]
910+ gather_positions = tf .expand_dims (start_t , - 1 ) + tf .range (0 , size ) # e.g. (B, size) or (B, T, size)
879911 if seq_lens is not None :
880- mask = tf .greater_equal (tf .range (size )[None , :] + start , seq_lens [:, None ]) # (B,T)
881- mask = expand_multiple_dims (mask , list (range (2 , x .batch_ndim )))
882- slices = where_bc (mask , tf .zeros_like (slices ), slices )
883- size_placeholder = x .size_placeholder .copy ()
884- if isinstance (size , tf .Tensor ):
885- size_placeholder [0 ] = tf .maximum (seq_lens - tf .reshape (start , tf .shape (seq_lens )), 0 )
886- tag = DimensionTag (
887- description = "sliced-time:%s" % self .get_absolute_name (),
888- kind = DimensionTag .Types .Spatial , batch = self .output .batch )
889- tag .set_tag_on_size_tensor (size_placeholder [0 ])
912+ # broadcast from (B,) to the shape of the indices
913+ seq_lens = expand_multiple_dims ( # e.g. (B,1) or (B,1,1)
914+ x = seq_lens ,
915+ axes = [- 1 ] * (len (gather_positions .shape ) - len (seq_lens .shape )))
916+ pad_mask = tf .logical_or ( # shape like gather_positions
917+ tf .greater (gather_positions , seq_lens - 1 ),
918+ tf .less (gather_positions , 0 ))
919+ gather_positions = tf .clip_by_value (gather_positions , 0 , seq_lens - 1 )
890920 else :
891- assert isinstance (size , int )
892- size_placeholder .pop (0 , None ) # static time axis
893- self .output .size_placeholder = size_placeholder
894- self .output .placeholder = slices
921+ pad_mask = tf .logical_or ( # shape like gather_positions
922+ tf .greater (gather_positions , x .batch_shape [1 ] - 1 ),
923+ tf .less (gather_positions , 0 ))
924+ gather_positions = tf .clip_by_value (gather_positions , 0 , x .batch_shape [1 ] - 1 )
925+ gather_positions_data .placeholder = gather_positions
926+ position = InternalLayer (
927+ network = self .network ,
928+ name = "%s_internal" % gather_positions_data .name ,
929+ output = gather_positions_data )
930+ gather_layer = GatherLayer (
931+ name = "%s_gather" % self .name ,
932+ network = self .network ,
933+ output = self .output ,
934+ sources = self .sources ,
935+ position = position ,
936+ axis = x .get_time_dim_tag ())
937+ placeholder = gather_layer .output .placeholder
938+ # In principle, the padded frames are being ignored
939+ # (unless get_padding_info_dict_ref et al are used).
940+ # However, you can still end up with gradients for them
941+ # in unexpected ways.
942+ # Due to our gather implementation,
943+ # the gradient flow would go into wrong frames
944+ # and might lead to unexpected behavior.
945+ # So to be on the safe side, we do the masking here.
946+ pad_mask = expand_multiple_dims (pad_mask , [- 1 ] * (len (placeholder .shape ) - len (pad_mask .shape )))
947+ self .output .placeholder = where_bc (pad_mask , tf .zeros_like (placeholder ), placeholder )
895948
896949 def get_dep_layers (self ):
897950 """
@@ -909,11 +962,24 @@ def get_out_data_from_opts(cls, name, sources=(), start=None, size=None, **kwarg
909962 :rtype: Data
910963 """
911964 from ..util .data import DimensionTag
912- input_data = get_concat_sources_data_template (sources ).copy_as_batch_spatial_major ()
913- if start :
914- input_data .beam = SearchBeam .get_combined_beam (input_data .beam , start .output .beam )
915- new_dim_tag = DimensionTag (kind = DimensionTag .Types .Spatial , description = "%s:slice_nd" % name , dimension = size )
916- return input_data .copy_template_replace_dim_tag (axis = 1 , new_dim_tag = new_dim_tag , name = "%s_output" % name )
965+ start_data = start .output .copy_as_batch_major ()
966+ input_data = sources [0 ].output .copy_as_batch_major ()
967+ gather_positions_data = start_data .copy_template (name = "%s_gather_positions" % name )
968+ # size might be None here in which case we set the dyn_size in __init__
969+ tag = DimensionTag (
970+ kind = DimensionTag .Types .Spatial ,
971+ description = "sliced-time:%s" % name ,
972+ dimension = size )
973+ gather_positions_data = gather_positions_data .copy_add_dim_by_tag (tag , unbroadcast = True , axis = start_data .batch_ndim )
974+ position = InternalLayer (
975+ network = sources [0 ].network ,
976+ name = "%s_internal" % gather_positions_data .name ,
977+ output = gather_positions_data )
978+ return GatherLayer .get_out_data_from_opts (
979+ name = "%s_gather" % name ,
980+ sources = sources ,
981+ position = position ,
982+ axis = input_data .get_time_dim_tag ())
917983
918984 @classmethod
919985 def transform_config_dict (cls , d , network , get_layer ):
0 commit comments