def _build(self, y_pred, y_true): """One-time setup of metric objects.""" if self._output_names is None: # Subclass output names like 'output_1' are used for `Metric` names. self._output_names = create_pseudo_output_names(y_pred) # If a single metric or flat list of metrics, apply to all outputs. self._metrics = self._maybe_broadcast(self._metrics, y_pred) self._weighted_metrics = self._maybe_broadcast(self._weighted_metrics, y_pred) # Accept a dict of metrics keyed by output_name when outputs are a flat # list. self._metrics = map_to_output_names(y_pred, self._output_names, self._metrics) self._weighted_metrics = map_to_output_names(y_pred, self._output_names, self._weighted_metrics) # Standardize on tuple since `tf.data` turns lists into `Tensor`s. # pylint: disable=protected-access y_pred = nest._list_to_tuple(y_pred) y_true = nest._list_to_tuple(y_true) self._metrics = nest._list_to_tuple(self._metrics) self._weighted_metrics = nest._list_to_tuple(self._weighted_metrics) # pylint: enable=protected-access # Convert to `Metric` objects, potentially disambiguating based on output # properties. self._metrics = nest.map_structure_up_to(y_pred, self._get_metric_objects, self._metrics, y_true, y_pred) self._weighted_metrics = nest.map_structure_up_to( y_pred, self._get_metric_objects, self._weighted_metrics, y_true, y_pred) self._metrics = nest.flatten_up_to(y_pred, self._metrics, check_types=False) self._weighted_metrics = nest.flatten_up_to(y_pred, self._weighted_metrics, check_types=False) # Assumes metrics, weighted_metrics have been flattened up to outputs. self._set_metric_names() # Cache the flat order needed when returning metrics, for backwards compat. self._metrics_in_order = [] for output_metrics, output_weighted_metrics in zip( self._metrics, self._weighted_metrics): for m in nest.flatten(output_metrics): if m is not None: self._metrics_in_order.append(m) for wm in nest.flatten(output_weighted_metrics): if wm is not None: self._metrics_in_order.append(wm) self._built = True
def _process_tensorlike(inputs): """Process tensor-like inputs. This function: (1) Converts `Numpy` arrays to `Tensor`s. (2) Converts `Scipy` sparse matrices to `SparseTensor`s. (2) Converts `list`s to `tuple`s (for `tf.data` support). Args: inputs: Structure of `Tensor`s, `NumPy` arrays, or tensor-like. Returns: Structure of `Tensor`s or tensor-like. """ def _convert_numpy_and_scipy(x): if isinstance(x, np.ndarray): dtype = None if issubclass(x.dtype.type, np.floating): dtype = backend.floatx() return ops.convert_to_tensor(x, dtype=dtype) elif scipy_sparse and scipy_sparse.issparse(x): return _scipy_sparse_to_sparse_tensor(x) return x inputs = nest.map_structure(_convert_numpy_and_scipy, inputs) return nest._list_to_tuple(inputs) # pylint: disable=protected-access
def _build(self, y_pred, y_true): """One-time setup of metric objects.""" super(MetricsContainer, self)._build(y_pred) self._metrics = self._maybe_broadcast_to_outputs(y_pred, self._metrics) self._metrics = self._conform_to_outputs(y_pred, self._metrics) self._weighted_metrics = self._maybe_broadcast_to_outputs( y_pred, self._weighted_metrics) self._weighted_metrics = self._conform_to_outputs( y_pred, self._weighted_metrics) # Standardize on tuple since `tf.data` turns lists into `Tensor`s. # pylint: disable=protected-access y_pred = nest._list_to_tuple(y_pred) y_true = nest._list_to_tuple(y_true) self._metrics = nest._list_to_tuple(self._metrics) self._weighted_metrics = nest._list_to_tuple(self._weighted_metrics) # pylint: enable=protected-access # Convert to `Metric` objects, potentially disambiguating based on output # properties. self._metrics = nest.map_structure_up_to(y_pred, self._get_metric_objects, self._metrics, y_true, y_pred) self._weighted_metrics = nest.map_structure_up_to( y_pred, self._get_metric_objects, self._weighted_metrics, y_true, y_pred) self._metrics = nest.flatten_up_to(y_pred, self._metrics, check_types=False) self._weighted_metrics = nest.flatten_up_to(y_pred, self._weighted_metrics, check_types=False) # Assumes metrics, weighted_metrics have been flattened up to outputs. self._set_metric_names() self._create_ordered_metrics() self._built = True
def map_fn(x, y=None, sample_weights=None): """Tensor manipulation portion of standardization for Dataset.map.""" standardized = model._standardize_tensors( x, y, sample_weights, run_eagerly=False, dict_inputs=isinstance(x, dict), is_dataset=False, class_weight=class_weights, batch_size=None) x, y, sample_weights = nest._list_to_tuple(standardized) if y is None: return (x,) if sample_weights is None: return x, y return x, y, sample_weights
def _standardize_batch(self, data): """Standardizes a batch output by a generator.""" # Removes `None`s. x, y, sample_weight = unpack_x_y_sample_weight(data) data = pack_x_y_sample_weight(x, y, sample_weight) data = nest._list_to_tuple(data) # pylint: disable=protected-access def _convert_dtype(t): if (isinstance(t, np.ndarray) and issubclass(t.dtype.type, np.floating)): return np.array(t, dtype=backend.floatx()) return t data = nest.map_structure(_convert_dtype, data) return data
def wrapped_generator(): """Remove Nones and lists before invoking Dataset.from_generator.""" for batch in generator_fn(): if wrap_in_tuple: batch = (batch,) if must_extract_lists: batch = nest._list_to_tuple(batch) # pylint: disable=protected-access if must_prune_nones: batch = batch[:elements_to_keep] if partial_sample_weight: sample_weights, _, _ = training_utils.handle_partial_sample_weights( batch[1], batch[2], sample_weight_modes, check_all_flat=False) batch = batch[:2] + (sample_weights,) yield batch
def map_fn(x, y=None, sample_weights=None): """Tensor manipulation portion of standardization for Dataset.map.""" if (y is None and sample_weights is None): # namedtuples are forbidden because it is ambiguous if they should be # unpacked. If y or sample_weights is present then `x` was not the # top level structure, and the correct behavior is unambiguous. data_adapter.assert_not_namedtuple(x) standardized = model._standardize_tensors( x, y, sample_weights, run_eagerly=False, dict_inputs=isinstance(x, dict), is_dataset=False, class_weight=class_weights, batch_size=None) x, y, sample_weights = nest._list_to_tuple(standardized) if y is None: return (x,) if sample_weights is None: return x, y return x, y, sample_weights
def _make_bridging_callable(generator_fn, wrap_in_tuple, peek, elements_to_keep, partial_sample_weight, sample_weight_modes): """Optional compatibility layer between user's data and Dataset.""" must_prune_nones = (elements_to_keep != len(peek)) try: nest.assert_same_structure(peek, nest._list_to_tuple(peek)) # pylint: disable=protected-access must_extract_lists = False except TypeError: must_extract_lists = True # No additional transformations are needed. if not (wrap_in_tuple or must_extract_lists or must_prune_nones or partial_sample_weight): return generator_fn def wrapped_generator(): """Remove Nones and lists before invoking Dataset.from_generator.""" for batch in generator_fn(): if wrap_in_tuple: batch = (batch, ) if must_extract_lists: batch = nest._list_to_tuple(batch) # pylint: disable=protected-access if must_prune_nones: batch = batch[:elements_to_keep] if partial_sample_weight: sample_weights, _, _ = training_utils.handle_partial_sample_weights( batch[1], batch[2], sample_weight_modes, check_all_flat=False) batch = batch[:2] + (sample_weights, ) yield batch return wrapped_generator
def _canonicalize_peek(self, peek, sample_weight_modes): """Map the peeked batch into a regular form. This function serves two purposes. First, it determines if per-batch transformations are needed. Second, it extracts the structure to be used by Dataset.from_generator. Args: peek: The first batch of the user's data sample_weight_modes: Optional structure indicating how to handle sample weights. If it is a string, it will be mapped to match the target structure. Returns: An updated peek and various inspection results. """ wrap_in_tuple = False if not isinstance(peek, tuple): peek, wrap_in_tuple = (peek, ), True if len(peek) not in (1, 2, 3): raise ValueError( "Output of generator should be a tuple of 1 or 2 or 3 elements: " "(input,) or (input, target) or (input, target, sample_weights). " "Received {}".format(peek)) x_peek, y_peek, sample_weights_peek = list(peek) + [None ] * (3 - len(peek)) any_sample_weight, partial_sample_weight = False, False sample_weight_modes = broadcast_sample_weight_modes( sample_weights_peek if sample_weights_peek is not None else y_peek, sample_weight_modes) if len(peek) == 3: (sample_weights_peek, any_sample_weight, partial_sample_weight ) = training_utils.handle_partial_sample_weights( y_peek, sample_weights_peek, sample_weight_modes, check_all_flat=True) peek = (x_peek, y_peek, sample_weights_peek) # Users often return None for fields which are not used. For instance: # (x, y, None) to indicate no sample weights. if len(peek) >= 2 and y_peek is None: if any_sample_weight: raise ValueError( "Found sample weights but no targets\n{}".format(peek)) elements_to_keep = 1 elif len(peek) == 3 and not any_sample_weight: elements_to_keep = 2 else: elements_to_keep = len(peek) def dynamic_shape_like(t): return tuple(None for _ in t.shape) def convert_for_inspection(t): if getattr(t, "shape", None) and getattr(t, "dtype", None): return t return np.array(t, dtype=backend.floatx()) canonicalized_peek = nest._list_to_tuple( # pylint: disable=protected-access nest.map_structure(convert_for_inspection, peek[:elements_to_keep])) nested_dtypes = nest.map_structure(lambda t: t.dtype, canonicalized_peek) nested_shape = nest.map_structure(dynamic_shape_like, canonicalized_peek) try: self._first_batch_size = int( nest.flatten(canonicalized_peek)[0].shape[0]) except IndexError: raise IndexError( "Could not infer batch size from: {}".format(peek)) return (peek, wrap_in_tuple, elements_to_keep, partial_sample_weight, sample_weight_modes, nested_shape, nested_dtypes)