Skip to content

Commit 3f1554d

Browse files
SliceNdLayer: support layer as size argument (#670)
1 parent 5cc7999 commit 3f1554d

2 files changed

Lines changed: 107 additions & 29 deletions

File tree

returnn/tf/layers/basic.py

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -859,39 +859,50 @@ class SliceNdLayer(_ConcatInputLayer):
859859
def __init__(self, start, size, min_size=None, **kwargs):
860860
"""
861861
:param LayerBase start: (B,...)
862-
:param int|None size: if None, it uses the max possible size, and it becomes a dynamic axis
862+
:param int|LayerBase|None size: if None, it uses the max possible size,
863+
and it becomes a dynamic axis.
863864
:param int|None min_size: if size is None, but we want to have a min-size
864865
"""
865866
super(SliceNdLayer, self).__init__(**kwargs)
866-
from returnn.tf.util.basic import where_bc, expand_multiple_dims
867-
x = self.input_data.copy_as_batch_major()
868-
seq_lens = x.get_sequence_lengths() if x.is_time_axis_dynamic() else None # (B,) or None
867+
from returnn.tf.util.basic import where_bc
868+
from returnn.tf.util.data import Data
869+
x = self.input_data.copy()
870+
seq_lens_data = x.get_time_dim_tag().dyn_size_ext # (B,) or None
869871
self.start = start
870-
start_data = start.output.copy_as_batch_major() # e.g. (B,) or (B,T)
872+
self.size = size
873+
start_data = start.output.copy() # e.g. (B,) or (B,T)
874+
data_objs = [start_data]
875+
data_objs += [size.output] if isinstance(size, LayerBase) else []
876+
data_objs += [seq_lens_data] if isinstance(seq_lens_data, Data) else []
877+
common_data = Data.get_common_data(data_objs)
878+
start_data = start_data.copy_compatible_to(common_data, check_sparse=False)
871879
start_t = start_data.placeholder
872880
if size is None:
873881
if min_size is None:
874882
min_size = 0
875-
if seq_lens is None:
883+
if seq_lens_data is None:
876884
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
885+
size_t = x.batch_shape[x.time_dim_axis] - start_t
878886
else:
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
887+
seq_lens_t = seq_lens_data.copy_compatible_to(common_data, check_sparse=False).placeholder
888+
size_t = seq_lens_t - start_t
889+
size = tf.maximum(tf.reduce_max(size_t), min_size) # scalar
890+
elif isinstance(size, LayerBase):
891+
size_data = size.output.copy_compatible_to(common_data, check_sparse=False)
892+
size_t = size_data.placeholder
893+
min_size = 0
894+
size = tf.maximum(tf.reduce_max(size_t), min_size) # scalar
895+
else:
896+
size_t = None
884897
# for each start index in start_data, we want to gather a slice
885898
# therefore, the output's first axes are the same as the ones from start_data
886899
# and the next axis will therefore be the slice axis
887900
slice_tag = self.output.dim_tags[start_data.batch_ndim]
888901
assert slice_tag.description.startswith("sliced-time:")
889-
if not isinstance(size, int):
902+
if size_t is not None:
890903
# 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)
904+
assert not isinstance(size, int)
905+
dyn_size = tf.maximum(size_t, min_size) # (B,) or (B,T)
895906
dyn_size_ext = Data(
896907
name=("%s:dyn_size" % slice_tag.description),
897908
dtype=Data.size_dtype,
@@ -909,20 +920,25 @@ def __init__(self, start, size, min_size=None, **kwargs):
909920
axis=start_data.batch_ndim)
910921
# [start+0, start+1, ...]
911922
gather_positions = tf.expand_dims(start_t, -1) + tf.range(0, size) # e.g. (B, size) or (B, T, size)
912-
if seq_lens is not None:
913-
# broadcast from (B,) to the shape of the indices
914-
seq_lens = expand_multiple_dims( # e.g. (B,1) or (B,1,1)
915-
x=seq_lens,
916-
axes=[-1] * (len(gather_positions.shape) - len(seq_lens.shape)))
923+
if seq_lens_data is not None:
924+
seq_lens_t = seq_lens_data.copy_compatible_to(
925+
gather_positions_data,
926+
check_sparse=False).placeholder
917927
pad_mask = tf.logical_or( # shape like gather_positions
918-
tf.greater(gather_positions, seq_lens - 1),
928+
tf.greater(gather_positions, seq_lens_t - 1),
919929
tf.less(gather_positions, 0))
920-
gather_positions = tf.clip_by_value(gather_positions, 0, seq_lens - 1)
930+
gather_positions = tf.clip_by_value(gather_positions, 0, seq_lens_t - 1)
921931
else:
922932
pad_mask = tf.logical_or( # shape like gather_positions
923933
tf.greater(gather_positions, x.batch_shape[1] - 1),
924934
tf.less(gather_positions, 0))
925935
gather_positions = tf.clip_by_value(gather_positions, 0, x.batch_shape[1] - 1)
936+
if isinstance(self.size, LayerBase):
937+
pad_mask = tf.logical_or(tf.greater(gather_positions, tf.expand_dims(start_t + size_t - 1, -1)), pad_mask)
938+
pad_mask_data = gather_positions_data.copy_template(
939+
name="%s_gather_positions" % self.name,
940+
dtype="bool")
941+
pad_mask_data.placeholder = pad_mask
926942
gather_positions_data.placeholder = gather_positions
927943
position = InternalLayer(
928944
network=self.network,
@@ -944,28 +960,34 @@ def __init__(self, start, size, min_size=None, **kwargs):
944960
# the gradient flow would go into wrong frames
945961
# and might lead to unexpected behavior.
946962
# So to be on the safe side, we do the masking here.
947-
pad_mask = expand_multiple_dims(pad_mask, [-1] * (len(placeholder.shape) - len(pad_mask.shape)))
963+
pad_mask_data = pad_mask_data.copy_compatible_to(gather_layer.output, check_sparse=False, check_dtype=False)
964+
pad_mask = pad_mask_data.placeholder
948965
self.output.placeholder = where_bc(pad_mask, tf.zeros_like(placeholder), placeholder)
949966

950967
def get_dep_layers(self):
951968
"""
952969
:rtype: list[LayerBase]
953970
"""
954-
return super(SliceNdLayer, self).get_dep_layers() + [self.start]
971+
dep_layers = super(SliceNdLayer, self).get_dep_layers() + [self.start]
972+
if isinstance(self.size, LayerBase):
973+
dep_layers += [self.size]
974+
return dep_layers
955975

956976
@classmethod
957977
def get_out_data_from_opts(cls, name, sources=(), start=None, size=None, **kwargs):
958978
"""
959979
:param str name:
960980
:param list[LayerBase] sources:
961981
:param LayerBase|None start:
962-
:param int|None size:
982+
:param int|LayerBase|None size:
963983
:rtype: Data
964984
"""
965985
from ..util.data import DimensionTag
966-
start_data = start.output.copy_as_batch_major()
967-
input_data = sources[0].output.copy_as_batch_major()
986+
start_data = start.output.copy()
987+
input_data = sources[0].output.copy()
968988
gather_positions_data = start_data.copy_template(name="%s_gather_positions" % name)
989+
if isinstance(size, LayerBase):
990+
size = None
969991
# size might be None here in which case we set the dyn_size in __init__
970992
tag = DimensionTag(
971993
kind=DimensionTag.Types.Spatial,
@@ -991,6 +1013,8 @@ def transform_config_dict(cls, d, network, get_layer):
9911013
"""
9921014
super(SliceNdLayer, cls).transform_config_dict(d, network=network, get_layer=get_layer)
9931015
d["start"] = get_layer(d["start"])
1016+
if isinstance(d["size"], str):
1017+
d["size"] = get_layer(d["size"])
9941018

9951019

9961020
class GatherLayer(_ConcatInputLayer):

tests/test_TFNetworkLayer.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,6 +2820,60 @@ def test_SliceNdLayer_multidimensional_start():
28202820
numpy.testing.assert_equal(orig_seq[t2], segments[b, t, t2])
28212821

28222822

2823+
def test_SliceNdLayer_multidimensional_size():
2824+
with make_scope() as session:
2825+
n_out = 5
2826+
n_batch = 3
2827+
max_seq_len = 10
2828+
config = Config({
2829+
"debug_print_layer_output_template": True,
2830+
"extern_data": {
2831+
"data": {"dim": n_out},
2832+
"classes": {"dim": n_out, "sparse": True}
2833+
}})
2834+
net = TFNetwork(config=config, train_flag=True)
2835+
net.construct_from_dict({
2836+
"output": {
2837+
"class": "rec", "from": "data:data", "unit": {
2838+
"const1": {"class": "constant", "value": 1},
2839+
"start": {"class": "reinterpret_data", "from": "prev:choice", "set_sparse": False},
2840+
"size": {"class": "combine", "from": ["const1", "start"], "kind": "add"},
2841+
"slices": {"class": "slice_nd", "from": "base:data:data", "start": "start", "size": "size"},
2842+
"output": {"class": "reduce", "from": "slices", "mode": "max", "axes": "dyn:-1"},
2843+
"prob": {"class": "softmax", "from": "data:source", "target": "classes", "loss": "ce"},
2844+
'choice': {
2845+
'class': 'choice', 'target': "classes", 'beam_size': 3, 'from': "prob", "input_type": "prob",
2846+
"initial_output": 0,}}}})
2847+
session.run(tf_compat.v1.global_variables_initializer())
2848+
output_layer = net.layers["output"]
2849+
starts = output_layer.cell.output_layers_net.layers["start"].output.get_placeholder_as_batch_major()
2850+
sizes = output_layer.cell.output_layers_net.layers["size"].output.get_placeholder_as_batch_major()
2851+
segments = output_layer.cell.output_layers_net.layers["slices"].output.get_placeholder_as_batch_major()
2852+
feed = make_feed_dict(net.extern_data.data.values(), n_batch=n_batch, n_time=max_seq_len, same_time=True)
2853+
starts = session.run(starts, feed_dict=feed)
2854+
sizes = session.run(sizes, feed_dict=feed)
2855+
segments = session.run(segments, feed_dict=feed)
2856+
seq_lens = feed[net.extern_data.data["data"].size_placeholder[0]]
2857+
input_data = feed[net.extern_data.data["data"].placeholder]
2858+
max_size = numpy.amax(sizes)
2859+
max_size = max(max_size, 0)
2860+
assert segments.shape == (n_batch, max_seq_len, max_size, n_out)
2861+
for b in range(n_batch):
2862+
for t in range(max_seq_len):
2863+
s = starts[b, t]
2864+
size = sizes[b, t]
2865+
end = min(s + size, seq_lens[b])
2866+
orig_seq = input_data[b, s:end]
2867+
if len(orig_seq) < max_size:
2868+
orig_seq = numpy.pad(orig_seq, [(0, max_size - len(orig_seq)), (0, 0)], "constant")
2869+
elif len(orig_seq) > max_size:
2870+
orig_seq = orig_seq[:max_size]
2871+
assert orig_seq.shape == (max_size, n_out)
2872+
orig_seq = numpy.where((numpy.arange(s, s + max_size) >= seq_lens[b])[:, None], 0.0, orig_seq)
2873+
for t2 in range(max_size):
2874+
numpy.testing.assert_equal(orig_seq[t2], segments[b, t, t2])
2875+
2876+
28232877
def test_SliceNdLayer_set_tag_on_size_tensor():
28242878
with make_scope():
28252879
n_out = 5

0 commit comments

Comments
 (0)