def __call__(self, inputs, initial_state=None, constants=None, **kwargs): inputs, initial_state, constants = _standardize_args( inputs, initial_state, constants, self._num_constants) if initial_state is None and constants is None: return super(ConvRNN2D, self).__call__(inputs, **kwargs) # If any of `initial_state` or `constants` are specified and are Keras # tensors, then add them to the inputs and temporarily modify the # input_spec to include them. additional_inputs = [] additional_specs = [] if initial_state is not None: kwargs['initial_state'] = initial_state additional_inputs += initial_state self.state_spec = [] for state in initial_state: shape = K.int_shape(state) self.state_spec.append(InputSpec(shape=shape)) additional_specs += self.state_spec if constants is not None: kwargs['constants'] = constants additional_inputs += constants self.constants_spec = [ InputSpec(shape=K.int_shape(constant)) for constant in constants ] self._num_constants = len(constants) additional_specs += self.constants_spec # at this point additional_inputs cannot be empty for tensor in additional_inputs: if K.is_keras_tensor(tensor) != K.is_keras_tensor( additional_inputs[0]): raise ValueError('The initial state or constants of an RNN' ' layer cannot be specified with a mix of' ' Keras tensors and non-Keras tensors') if K.is_keras_tensor(additional_inputs[0]): # Compute the full input spec, including state and constants full_input = [inputs] + additional_inputs full_input_spec = self.input_spec + additional_specs # Perform the call with temporarily replaced input_spec original_input_spec = self.input_spec self.input_spec = full_input_spec output = super(ConvRNN2D, self).__call__(full_input, **kwargs) self.input_spec = original_input_spec return output else: return super(ConvRNN2D, self).__call__(inputs, **kwargs)
def __call__(self, inputs, initial_state=None, constants=None, **kwargs): inputs, initial_state, constants = _standardize_args( inputs, initial_state, constants, self._num_constants) if initial_state is None and constants is None: return super(ConvRNN2D, self).__call__(inputs, **kwargs) # If any of `initial_state` or `constants` are specified and are Keras # tensors, then add them to the inputs and temporarily modify the # input_spec to include them. additional_inputs = [] additional_specs = [] if initial_state is not None: kwargs['initial_state'] = initial_state additional_inputs += initial_state self.state_spec = [] for state in initial_state: shape = K.int_shape(state) self.state_spec.append(InputSpec(shape=shape)) additional_specs += self.state_spec if constants is not None: kwargs['constants'] = constants additional_inputs += constants self.constants_spec = [InputSpec(shape=K.int_shape(constant)) for constant in constants] self._num_constants = len(constants) additional_specs += self.constants_spec # at this point additional_inputs cannot be empty for tensor in additional_inputs: if K.is_keras_tensor(tensor) != K.is_keras_tensor(additional_inputs[0]): raise ValueError('The initial state or constants of an RNN' ' layer cannot be specified with a mix of' ' Keras tensors and non-Keras tensors') if K.is_keras_tensor(additional_inputs[0]): # Compute the full input spec, including state and constants full_input = [inputs] + additional_inputs full_input_spec = self.input_spec + additional_specs # Perform the call with temporarily replaced input_spec original_input_spec = self.input_spec self.input_spec = full_input_spec output = super(ConvRNN2D, self).__call__(full_input, **kwargs) self.input_spec = original_input_spec return output else: return super(ConvRNN2D, self).__call__(inputs, **kwargs)
def __call__(self, inputs, initial_state=None, constants=None, **kwargs): """`Bidirectional.__call__` implements the same API as the wrapped `RNN`.""" inputs, initial_state, constants = _standardize_args( inputs, initial_state, constants, self._num_constants) if isinstance(inputs, list): if len(inputs) > 1: initial_state = inputs[1:] inputs = inputs[0] if initial_state is None and constants is None: return super(Bidirectional, self).__call__(inputs, **kwargs) # Applies the same workaround as in `RNN.__call__` additional_inputs = [] additional_specs = [] if initial_state is not None: # Check if `initial_state` can be splitted into half num_states = len(initial_state) if num_states % 2 > 0: raise ValueError( 'When passing `initial_state` to a Bidirectional RNN, ' 'the state should be a list containing the states of ' 'the underlying RNNs. ' 'Found: ' + str(initial_state)) kwargs['initial_state'] = initial_state additional_inputs += initial_state state_specs = [ InputSpec(shape=K.int_shape(state)) for state in initial_state ] self.forward_layer.state_spec = state_specs[:num_states // 2] self.backward_layer.state_spec = state_specs[num_states // 2:] additional_specs += state_specs if constants is not None: kwargs['constants'] = constants additional_inputs += constants constants_spec = [ InputSpec(shape=K.int_shape(constant)) for constant in constants ] self.forward_layer.constants_spec = constants_spec self.backward_layer.constants_spec = constants_spec additional_specs += constants_spec self._num_constants = len(constants) self.forward_layer._num_constants = self._num_constants self.backward_layer._num_constants = self._num_constants is_keras_tensor = K.is_keras_tensor(additional_inputs[0]) for tensor in additional_inputs: if K.is_keras_tensor(tensor) != is_keras_tensor: raise ValueError('The initial state of a Bidirectional' ' layer cannot be specified with a mix of' ' Keras tensors and non-Keras tensors' ' (a "Keras tensor" is a tensor that was' ' returned by a Keras layer, or by `Input`)') if is_keras_tensor: # Compute the full input spec, including state full_input = [inputs] + additional_inputs full_input_spec = self.input_spec + additional_specs # Perform the call with temporarily replaced input_spec original_input_spec = self.input_spec self.input_spec = full_input_spec output = super(Bidirectional, self).__call__(full_input, **kwargs) self.input_spec = original_input_spec return output else: return super(Bidirectional, self).__call__(inputs, **kwargs)
def __call__(self, inputs, initial_state=None, constants=None, **kwargs): """`Bidirectional.__call__` implements the same API as the wrapped `RNN`.""" inputs, initial_state, constants = _standardize_args( inputs, initial_state, constants, self._num_constants) if isinstance(inputs, list): if len(inputs) > 1: initial_state = inputs[1:] inputs = inputs[0] if initial_state is None and constants is None: return super(Bidirectional, self).__call__(inputs, **kwargs) # Applies the same workaround as in `RNN.__call__` additional_inputs = [] additional_specs = [] if initial_state is not None: # Check if `initial_state` can be splitted into half num_states = len(initial_state) if num_states % 2 > 0: raise ValueError( 'When passing `initial_state` to a Bidirectional RNN, ' 'the state should be a list containing the states of ' 'the underlying RNNs. ' 'Found: ' + str(initial_state)) kwargs['initial_state'] = initial_state additional_inputs += initial_state state_specs = [InputSpec(shape=K.int_shape(state)) for state in initial_state] self.forward_layer.state_spec = state_specs[:num_states // 2] self.backward_layer.state_spec = state_specs[num_states // 2:] additional_specs += state_specs if constants is not None: kwargs['constants'] = constants additional_inputs += constants constants_spec = [InputSpec(shape=K.int_shape(constant)) for constant in constants] self.forward_layer.constants_spec = constants_spec self.backward_layer.constants_spec = constants_spec additional_specs += constants_spec self._num_constants = len(constants) self.forward_layer._num_constants = self._num_constants self.backward_layer._num_constants = self._num_constants is_keras_tensor = K.is_keras_tensor(additional_inputs[0]) for tensor in additional_inputs: if K.is_keras_tensor(tensor) != is_keras_tensor: raise ValueError('The initial state of a Bidirectional' ' layer cannot be specified with a mix of' ' Keras tensors and non-Keras tensors' ' (a "Keras tensor" is a tensor that was' ' returned by a Keras layer, or by `Input`)') if is_keras_tensor: # Compute the full input spec, including state full_input = [inputs] + additional_inputs # The original input_spec is None since there could be a nested tensor # input. Update the input_spec to match the inputs. full_input_spec = [None for _ in range(len(nest.flatten(inputs))) ] + additional_specs # Perform the call with temporarily replaced input_spec original_input_spec = self.input_spec self.input_spec = full_input_spec output = super(Bidirectional, self).__call__(full_input, **kwargs) self.input_spec = original_input_spec return output else: return super(Bidirectional, self).__call__(inputs, **kwargs)
def __call__(self, *args, inputs=None, initial_state=None, constants=None, mask=None, **kwargs): inputs, initial_state, constants = _standardize_args( inputs, initial_state, constants, self._num_constants) # We allow different shapes of input, even None. It doesn't really matter # because ultimately the input will be ignored except for the first step. # Nevertheless, we expand the input to have a timesteps dimension. This # is done simply for parent class calculations of output size, etc. # Allow None as an input. We will create an array of zeros of appropriate shape. if inputs is None: if initial_state is not None: # If LSTM then state might be a list. _state = initial_state[0] if isinstance( initial_state, list) else initial_state batch_size = _state.shape[:-1] inputs = K.zeros_like(_state[..., 0][..., tf.newaxis]) # inputs = 0 * _state[..., 0][..., tf.newaxis] # Assume dim=1 input else: # Neither inputs nor initial_state provided. This likely only happens # when building/testing the layer. inputs = tf.zeros( (self.timesteps, 1, 1)) if self.time_major else tf.zeros( (1, self.timesteps, 1)) # Allow 2D input, here reshape to 3D input if len(K.int_shape(inputs)) < 3: if self.time_major: inputs = inputs[tf.newaxis, ...] else: inputs = inputs[..., tf.newaxis, :] time_ax_ix, batch_ax_ix = (0, 1) if self.time_major else (-2, 0) input_shape = K.int_shape(inputs) input_timesteps = input_shape[time_ax_ix] if mask is not None and K.any(~mask): mask = nest.flatten(mask)[0] # We assume mask has a time dimension and require it is same size as input # (It doesn't make sense to use mask otherwise). mask_shape = K.int_shape(mask) # If the mask only has 1 item in the batch dim then tile it if mask_shape[batch_ax_ix] == 1 and input_shape[batch_ax_ix] > 1: if self.time_major: bcast_or = tf.zeros((1, input_shape[batch_ax_ix], 1), dtype=tf.bool) else: bcast_or = tf.zeros((input_shape[batch_ax_ix], 1, 1), dtype=tf.bool) mask = tf.math.logical_or(mask, bcast_or) if mask_shape[time_ax_ix] == input_timesteps: # Prepare slice parameters # For head (kept) h_sl_begin = [0 for _ in input_shape] h_sl_sz = [-1 for _ in input_shape] h_sl_sz[batch_ax_ix] = 1 # For tail (replaced) t_sl_begin = [0 for _ in input_shape] t_sl_sz = [-1 for _ in input_shape] t_sl_sz[batch_ax_ix] = 1 # Collect input replacements in list new_inputs = [] for batch_ix in range(input_shape[batch_ax_ix]): samp_mask = mask[ ..., batch_ix, :] if self.time_major else mask[batch_ix] if K.any(~samp_mask): h_sl_begin[batch_ax_ix] = batch_ix t_sl_begin[batch_ax_ix] = batch_ix first_bad = tf.where(~samp_mask)[0, 0] h_sl_sz[time_ax_ix] = first_bad # sz is 1-based t_sl_begin[time_ax_ix] = first_bad head = tf.slice(inputs, h_sl_begin, h_sl_sz) tail = tf.slice(inputs, t_sl_begin, t_sl_sz) if self.tile_input: tile_samp = head[-1] if self.time_major else head[ ..., -1, :] else: tile_samp = tf.zeros((1, input_shape[-1])) new_row = tf.concat( (head, tile_samp * K.ones_like(tail)), axis=time_ax_ix) new_inputs.append(new_row) inputs = tf.concat(new_inputs, axis=batch_ax_ix) # Fill/trim input time dimension to be self.timesteps if input_timesteps > self.timesteps: # Trim excess, if any inputs = inputs[:self.timesteps, ...] if self.time_major else inputs[ ..., :self.timesteps, :] elif input_timesteps < self.timesteps: # Take the last timestep as our starting point for the padding data pad_sample = inputs[-1] if self.time_major else inputs[..., -1, :] if not self.tile_input: # zero out padding data if we aren't tiling pad_sample = K.zeros_like(pad_sample) # pad_sample = 0 * pad_sample # Add the time axis back to our pad_sample pad_sample = pad_sample[tf.newaxis, ...] if self.time_major else pad_sample[ ..., tf.newaxis, :] # How many more timestamps do we need? pad_timestamps = self.timesteps - K.int_shape(inputs)[time_ax_ix] # Tile pad_data using broadcast-add. Does this same line work for time_major and not? pad_data = pad_sample + tf.zeros((pad_timestamps, 1)) inputs = tf.concat((inputs, pad_data), axis=time_ax_ix) if not self._built_with_input: self.build_with_input(inputs, *args, initial_state=initial_state, constants=constants, mask=mask, **kwargs) return super().__call__(inputs, initial_state=initial_state, constants=constants, **kwargs)