예제 #1
0
    def write_embeddings(
        self,
        mode: str,
        embeddings: Iterable[Tuple[str, Tensor, Optional[List[Any]],
                                   Optional[Tensor]]],
        step: int,
    ):
        """Write embeddings (like UMAP) to TensorBoard.

        Args:
            mode: The current mode of execution ('train', 'eval', 'test', 'infer').
            embeddings: A collection of quadruplets like [("key", <features>, [<label1>, ...], <label_images>)].
                Features are expected to be batched, and if labels and/or label images are provided they should have the
                same batch dimension as the features.
            step: The current training step.
        """
        for key, features, labels, label_imgs in embeddings:
            flat = to_number(reshape(features, [features.shape[0], -1]))
            if not isinstance(label_imgs, (torch.Tensor, type(None))):
                label_imgs = to_tensor(label_imgs, 'torch')
                if len(label_imgs.shape) == 4:
                    label_imgs = permute(label_imgs, [0, 3, 1, 2])
            self.summary_writers[mode].add_embedding(mat=flat,
                                                     metadata=labels,
                                                     label_img=label_imgs,
                                                     tag=key,
                                                     global_step=step)
예제 #2
0
    def transform(
            self,
            data: Dict[str, Any],
            mode: str,
            epoch: int = 1,
            ds_id: str = '',
            target_type: str = 'np') -> Union[Dict[str, Any], FilteredData]:
        """Apply all pipeline operations on a given data instance for the specified `mode` and `epoch`.

        Args:
            data: Input data in dictionary format.
            mode: The execution mode in which to run. This can be "train", "eval", "test" or "infer".
            epoch: The epoch index to run. Note that epoch indices are 1-indexed.
            ds_id: The current dataset id.
            target_type: What kind of tensor(s) to create. One of "tf", "torch", or "np".

        Returns:
            The transformed data.
        """
        data = deepcopy(data)
        instance_ops, batch_spec, batch_ops = self._get_op_split(mode=mode,
                                                                 epoch=epoch,
                                                                 ds_id=ds_id)
        state = {'mode': mode}
        op_data = forward_numpyop(instance_ops, data, state)
        if isinstance(op_data, FilteredData):
            return op_data
        data = batch_spec.collate_fn([data])
        op_data = forward_numpyop(batch_ops, data, state, batched='torch')
        if isinstance(op_data, FilteredData):
            return op_data
        return to_tensor(data, target_type=target_type)
예제 #3
0
    def _configure_loader(
        self, loader: Union[DataLoader, tf.data.Dataset]
    ) -> Union[DataLoader, tf.data.Dataset]:
        """A method to configure a given dataloader for use with this Estimator's Network.

        This method will ensure that the `loader` returns the correct data type (tf.Tensor or torch.Tensor) depending on
         the requirements of the Network. It also handles issues with multi-gpu data sharding.

        Args:
            loader: A data loader to be modified.

        Returns:
            The potentially modified dataloader to be used for training.
        """
        new_loader = loader
        if isinstance(new_loader, DataLoader) and isinstance(
                self.network, TFNetwork):
            add_batch = bool(new_loader.batch_size)
            if hasattr(loader, 'fe_postprocess_fn'
                       ) and loader.fe_postprocess_fn is not None:
                # The user is manually batching data and running ops on data batches. No reliable way to shortcut this
                # since ops might require specific batch composition.
                data_instance = next(iter(loader))
                add_batch = False
            else:
                # No batch-based ops so we can try and just use the OpDataset to more quickly get our data summary
                data_instance = loader.dataset[0]
                if isinstance(data_instance, list):
                    # This is a batched dataset
                    data_instance = data_instance[0]
                    add_batch = True
                if isinstance(data_instance, FilteredData):
                    # We got unlucky and drew filtered data as the zeroth element. Fall back to a slower but more robust
                    # analysis of the batch
                    data_instance = next(iter(loader))
                    add_batch = False
            data_instance = to_tensor(data_instance, target_type="tf")
            data_type = to_type(data_instance)
            data_shape = to_shape(data_instance,
                                  add_batch=add_batch,
                                  exact_shape=False)
            new_loader = tf.data.Dataset.from_generator(
                lambda: loader, data_type, output_shapes=data_shape)
            new_loader = new_loader.prefetch(1)
        if isinstance(new_loader, tf.data.Dataset):
            if self.system.train_steps_per_epoch and self.system.mode == "train":
                new_loader = new_loader.take(self.system.train_steps_per_epoch)
            if self.system.eval_steps_per_epoch and self.system.mode == "eval":
                new_loader = new_loader.take(self.system.eval_steps_per_epoch)
            if isinstance(tf.distribute.get_strategy(),
                          tf.distribute.MirroredStrategy) and isinstance(
                              self.network, TFNetwork) and not isinstance(
                                  new_loader, DistributedDataset):
                # The default autoshard policy is file, changing it to data to avoid warning
                options = tf.data.Options()
                options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.DATA
                new_loader = new_loader.with_options(options)
                new_loader = tf.distribute.get_strategy(
                ).experimental_distribute_dataset(new_loader)
        return new_loader
예제 #4
0
    def _configure_loader(self, loader: Union[DataLoader, tf.data.Dataset]) -> Union[DataLoader, tf.data.Dataset]:
        """A method to configure a given dataloader for use with this Estimator's Network.

        This method will ensure that the `loader` returns the correct data type (tf.Tensor or torch.Tensor) depending on
         the requirements of the Network. It also handles issues with multi-gpu data sharding.

        Args:
            loader: A data loader to be modified.

        Returns:
            The potentially modified dataloader to be used for training.
        """
        new_loader = loader
        if isinstance(new_loader, DataLoader) and isinstance(self.network, TFNetwork):
            add_batch = True
            if hasattr(loader.dataset, "dataset") and isinstance(loader.dataset.dataset, BatchDataset):
                add_batch = False
            batch = to_tensor(loader.dataset[0], target_type="tf")
            data_type = to_type(batch)
            data_shape = to_shape(batch, add_batch=add_batch, exact_shape=False)
            new_loader = tf.data.Dataset.from_generator(lambda: loader, data_type, output_shapes=data_shape)
            new_loader = new_loader.prefetch(1)
        if isinstance(new_loader, tf.data.Dataset):
            if self.system.max_train_steps_per_epoch and self.system.mode == "train":
                new_loader = new_loader.take(self.system.max_train_steps_per_epoch)
            if self.system.max_eval_steps_per_epoch and self.system.mode == "eval":
                new_loader = new_loader.take(self.system.max_eval_steps_per_epoch)
            if isinstance(tf.distribute.get_strategy(),
                          tf.distribute.MirroredStrategy) and not isinstance(new_loader, DistributedDataset):
                new_loader = tf.distribute.get_strategy().experimental_distribute_dataset(new_loader)
        return new_loader
예제 #5
0
 def build(self, framework: str) -> None:
     labels = hadamard(self.code_length).astype(np.float32)
     labels[np.arange(0, self.code_length, 2),
            0] = -1  # Make first column alternate
     labels = labels[:self.n_classes]
     self.labels = to_tensor(labels, target_type=framework)
     max_prob = 0.99999  # This will only be approximate since the first column is alternating
     power = 1.0
     self.eps = to_tensor(np.array((self.code_length + 1) * math.pow(
         (1.0 - max_prob) / (max_prob * (self.n_classes - 1)), 1 / power),
                                   dtype=np.float32),
                          target_type=framework)
     if framework == "torch":
         self.labels = self.labels.to(
             "cuda:0" if torch.cuda.is_available() else "cpu")
         self.eps = self.eps.to(
             "cuda:0" if torch.cuda.is_available() else "cpu")
예제 #6
0
def forward_numpyop(ops: List[NumpyOp],
                    data: MutableMapping[str, Any],
                    state: Dict[str, Any],
                    batched: Optional[str] = None) -> Optional[FilteredData]:
    """Call the forward function for list of NumpyOps, and modify the data dictionary in place.

    Args:
        ops: A list of NumpyOps to execute.
        data: The data dictionary.
        state: Information about the current execution context, ex. {"mode": "train"}. Must contain at least the mode.
        batched: Whether the `data` is batched or not. If it is batched, provide the string ('tf', 'torch', or 'np')
            indicating which type of tensors the batch contains.
    """
    if batched:
        # Cast data to Numpy before performing batch forward
        for key, val in data.items():
            data[key] = to_tensor(val, target_type='np')
    for op in ops:
        op_data = get_inputs_by_op(op, data, copy_on_write=op.in_place_edits)
        try:
            op_data = op.forward_batch(
                op_data, state) if batched else op.forward(op_data, state)
        except ValueError as err:
            if err.args[0] == 'assignment destination is read-only':
                # If the numpy error text changes we'll need to make adjustments in the future
                op.in_place_edits = True
                op_data = get_inputs_by_op(op,
                                           data,
                                           copy_on_write=op.in_place_edits)
                op_data = op.forward_batch(
                    op_data, state) if batched else op.forward(op_data, state)
            else:
                raise err
        if isinstance(op_data, FilteredData):
            return op_data
        if isinstance(op, Delete):
            for key in op.inputs:
                del data[key]
        if op.outputs:
            write_outputs_by_op(op, data, op_data)
    if batched:
        # Cast data back to original tensor type after performing batch forward
        for key, val in data.items():
            data[key] = to_tensor(val, target_type=batched, shared_memory=True)
    return None
예제 #7
0
    def _configure_tensor(self, loader: Union[DataLoader, tf.data.Dataset], batch: Dict[str, Any]) -> Dict[str, Any]:
        """A function to convert a batch of tf.Tensors to torch.Tensors if required.

        Returns:
            Either the original `batch`, or the `batch` converted to torch.Tensors if required.
        """
        if isinstance(loader, tf.data.Dataset) and isinstance(self.network, TorchNetwork):
            batch = to_tensor(batch, target_type="torch")
        return batch
예제 #8
0
def feed_forward(model: Union[tf.keras.Model, torch.nn.Module],
                 x: Union[Tensor, np.ndarray],
                 training: bool = True) -> Tensor:
    """Run a forward step on a given model.

    This method can be used with TensorFlow models:
    ```python
    m = fe.architecture.tensorflow.LeNet(classes=2)
    x = tf.ones((3,28,28,1))  # (batch, height, width, channels)
    b = fe.backend.feed_forward(m, x)  # [[~0.5, ~0.5], [~0.5, ~0.5], [~0.5, ~0.5]]
    ```

    This method can be used with PyTorch models:
    ```python
    m = fe.architecture.pytorch.LeNet(classes=2)
    x = torch.ones((3,1,28,28))  # (batch, channels, height, width)
    b = fe.backend.feed_forward(m, x)  # [[~0.5, ~0.5], [~0.5, ~0.5], [~0.5, ~0.5]]
    ```

    Args:
        model: A neural network to run the forward step through.
        x: An input tensor for the `model`. This value will be auto-cast to either a tf.Tensor or torch.Tensor as
            applicable for the `model`.
        training: Whether this forward step is part of training or not. This may impact the behavior of `model` layers
            such as dropout.

    Returns:
        The result of `model(x)`.

    Raises:
        ValueError: If `model` is an unacceptable data type.
    """
    if isinstance(model, tf.keras.Model):
        if not tf.is_tensor(x):
            x = to_tensor(x, "tf")
        x = model(x, training=training)
    elif isinstance(model, torch.nn.Module):
        model.train(mode=training)
        if not isinstance(x, torch.Tensor):
            x = to_tensor(x, "torch")
        x = model(x)
    else:
        raise ValueError("Unrecognized model instance {}".format(type(model)))
    return x
예제 #9
0
    def _configure_tensor(self, loader: Union[DataLoader, tf.data.Dataset], batch: Dict[str, Any]) -> Dict[str, Any]:
        """A function to convert a batch of tf.Tensors to torch.Tensors if required.

        Returns:
            Either the original `batch`, or the `batch` converted to torch.Tensors if required.
        """
        # TODO - if user has torch loader but custom collate that doesn't return torch tensor, need to cast here
        if isinstance(loader, tf.data.Dataset) and isinstance(self.network, TorchNetwork):
            batch = to_tensor(batch, target_type="torch")
        return batch
예제 #10
0
def gather_from_batch(tensor: Tensor, indices: Tensor) -> Tensor:
    """Gather specific indices from a batch of data.

    This method can be useful if you need to compute gradients based on a specific subset of a tensor's output values.
    The `indices` will automatically be cast to the correct type (tf, torch, np) based on the type of the `tensor`.

    This method can be used with Numpy data:
    ```python
    ind = np.array([1, 0, 1])
    n = np.array([[0, 1], [2, 3], [4, 5]])
    b = fe.backend.gather_from_batch(n, ind)  # [1, 2, 5]
    n = np.array([[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]])
    b = fe.backend.gather_from_batch(n, ind)  # [[2, 3], [4, 5], [10, 11]]
    ```

    This method can be used with TensorFlow tensors:
    ```python
    ind = tf.constant([1, 0, 1])
    t = tf.constant([[0, 1], [2, 3], [4, 5]])
    b = fe.backend.gather_from_batch(t, ind)  # [1, 2, 5]
    t = tf.constant([[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]])
    b = fe.backend.gather_from_batch(t, ind)  # [[2, 3], [4, 5], [10, 11]]
    ```

    This method can be used with PyTorch tensors:
    ```python
    ind = torch.tensor([1, 0, 1])
    p = torch.tensor([[0, 1], [2, 3], [4, 5]])
    b = fe.backend.gather_from_batch(p, ind)  # [1, 2, 5]
    p = torch.tensor([[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]])
    b = fe.backend.gather_from_batch(p, ind)  # [[2, 3], [4, 5], [10, 11]]
    ```

    Args:
        tensor: A tensor of shape (batch, d1, ..., dn).
        indices: A tensor of shape (batch, ) or (batch, 1) indicating which indices should be selected.

    Returns:
        A tensor of shape (batch, d2, ..., dn) containing the elements from `tensor` at the given `indices`.

    Raises:
        ValueError: If `tensor` is an unacceptable data type.
    """
    if tf.is_tensor(tensor):
        indices = to_tensor(indices, 'tf')
        indices = tf.cast(indices, tf.int64)
        if len(indices.shape) == 1:  # Indices not batched
            indices = expand_dims(indices, 1)
        return tf.gather_nd(tensor, indices=indices, batch_dims=1)
    elif isinstance(tensor, torch.Tensor):
        return tensor[torch.arange(tensor.shape[0]), squeeze(indices)]
    elif isinstance(tensor, np.ndarray):
        return tensor[np.arange(tensor.shape[0]), squeeze(indices)]
    else:
        raise ValueError("Unrecognized tensor type {}".format(type(tensor)))
예제 #11
0
def gather(tensor: Tensor, indices: Tensor) -> Tensor:
    """Gather specific indices from a tensor.

    The `indices` will automatically be cast to the correct type (tf, torch, np) based on the type of the `tensor`.

    This method can be used with Numpy data:
    ```python
    ind = np.array([1, 0, 1])
    n = np.array([[0, 1], [2, 3], [4, 5]])
    b = fe.backend.gather(n, ind)  # [[2, 3], [0, 1], [2, 3]]
    n = np.array([[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]])
    b = fe.backend.gather(n, ind)  # [[[4, 5], [6, 7]], [[0, 1], [2, 3]], [[4, 5], [6, 7]]]
    ```

    This method can be used with TensorFlow tensors:
    ```python
    ind = tf.constant([1, 0, 1])
    t = tf.constant([[0, 1], [2, 3], [4, 5]])
    b = fe.backend.gather(t, ind)  # [[2, 3], [0, 1], [2, 3]]
    t = tf.constant([[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]])
    b = fe.backend.gather(t, ind)  # [[[4, 5], [6, 7]], [[0, 1], [2, 3]], [[4, 5], [6, 7]]]
    ```

    This method can be used with PyTorch tensors:
    ```python
    ind = torch.tensor([1, 0, 1])
    p = torch.tensor([[0, 1], [2, 3], [4, 5]])
    b = fe.backend.gather(p, ind)  # [[2, 3], [0, 1], [2, 3]]
    p = torch.tensor([[[0, 1], [2, 3]], [[4, 5], [6, 7]], [[8, 9], [10, 11]]])
    b = fe.backend.gather(p, ind)  # [[[4, 5], [6, 7]], [[0, 1], [2, 3]], [[4, 5], [6, 7]]]
    ```

    Args:
        tensor: A tensor to gather values from.
        indices: A tensor indicating which indices should be selected. These represent locations along the 0 axis.

    Returns:
        A tensor containing the elements from `tensor` at the given `indices`.

    Raises:
        ValueError: If `tensor` is an unacceptable data type.
    """
    if tf.is_tensor(tensor):
        indices = to_tensor(indices, 'tf')
        indices = tf.cast(indices, tf.int64)
        return tf.gather(tensor, indices=squeeze(indices), axis=0)
    elif isinstance(tensor, torch.Tensor):
        return tensor[squeeze(indices).type(torch.int64)]
    elif isinstance(tensor, np.ndarray):
        return np.take(tensor, squeeze(indices).astype('int64'), axis=0)
    else:
        raise ValueError("Unrecognized tensor type {}".format(type(tensor)))
예제 #12
0
    def transform(self, data: Dict[str, Any], mode: str, epoch: int = 1) -> Dict[str, Any]:
        """Run a forward step through the Network on an element of data.

        Args:
            data: The element to data to use as input.
            mode: The mode in which to run the transform. One of 'train', 'eval', 'test', or 'infer'.
            epoch: The epoch in which to run the transform.

        Returns:
            prediction_data overlaid on the input `data`.
        """
        self.load_epoch(mode, epoch, warmup=False, eager=True)
        data = to_tensor(data, target_type=self.target_type)
        data, prediction = self.run_step(data)
        self.unload_epoch()
        return {**data, **prediction}
예제 #13
0
    def transform(self, data: Dict[str, Any], mode: str, epoch: int = 1) -> Dict[str, Any]:
        """Run a forward step through the Network on an element of data.

        Args:
            data: The element to data to use as input.
            mode: The mode in which to run the transform. One of 'train', 'eval', 'test', or 'infer'.
            epoch: The epoch in which to run the transform.

        Returns:
            (batch_data, prediction_data)
        """
        self.load_epoch(mode, epoch, warmup=False)
        data = to_tensor(data, "torch")
        data, prediction = self.run_step(data)
        self.unload_epoch()
        data.update(prediction)
        return data
예제 #14
0
    def transform(self, data: Dict[str, Any], mode: str, epoch: int = 1) -> Dict[str, Any]:
        """Run a forward step through the Network on an element of data.

        Args:
            data: The element to data to use as input.
            mode: The mode in which to run the transform. One of 'train', 'eval', 'test', or 'infer'.
            epoch: The epoch in which to run the transform.

        Returns:
            (batch_data, prediction_data)
        """
        self.load_epoch(mode, epoch, warmup=False)
        data = to_tensor(data, target_type="tf")
        data, prediction = self.run_step(data)
        self.unload_epoch()
        # handle tensorflow multi-gpu inferencing issue, it will replicate data on each device
        if isinstance(tf.distribute.get_strategy(), tf.distribute.MirroredStrategy):
            prediction = self._subsample_data(prediction, get_batch_size(data))
        data.update(prediction)
        return data
예제 #15
0
    def benchmark(self,
                  mode: str = "train",
                  epoch: int = 1,
                  ds_id: Optional[str] = None,
                  num_steps: int = 1000,
                  log_interval: int = 100,
                  detailed: bool = True) -> None:
        """Benchmark the pipeline processing speed.

        Args:
            mode: The execution mode to benchmark. This can be 'train', 'eval' or 'test'.
            epoch: The epoch index to benchmark. Note that epoch indices are 1-indexed.
            ds_id: The ds_id to benchmark. If None, all ds_ids will be benchmarked.
            num_steps: The number of steps over which to perform the benchmark.
            log_interval: The logging interval.
            detailed: Whether to display the detailed time used by each operator.
        """
        if ds_id is None:
            ds_ids = self.get_ds_ids(epoch=epoch, mode=mode)
        else:
            ds_ids = [ds_id]

        for ds_id in ds_ids:
            with self(mode=mode,
                      epoch=epoch,
                      ds_id=ds_id,
                      steps_per_epoch=num_steps) as loader:
                if isinstance(loader, tf.data.Dataset):
                    loader = loader.take(num_steps)
                start = time.perf_counter()
                for idx, _ in enumerate(loader, start=1):
                    if idx % log_interval == 0:
                        duration = time.perf_counter() - start
                        iters_per_sec = log_interval / duration
                        ds_str = f"Dataset: {ds_id}, " if ds_id else ""
                        print(
                            "FastEstimator-Benchmark ({}): {}Step: {}, Epoch: {}, Steps/sec: {}"
                            .format(mode.capitalize(), ds_str, idx, epoch,
                                    iters_per_sec))
                        start = time.perf_counter()
                # Pipeline Operations Benchmarking when using FEDataset
                if isinstance(loader, FEDataLoader) and isinstance(
                        loader.dataset, OpDataset) and detailed:
                    # (n_visited, duration)
                    duration_list = np.zeros(shape=(len(self.ctx_ops) + 1 +
                                                    len(self.ctx_batch_ops),
                                                    2))
                    data_len = len(loader.dataset)
                    ds_str = f", Dataset: {ds_id}" if ds_id else ""
                    print(
                        "\nBreakdown of time taken by Pipeline Operations (Mode: {}, Epoch: {}{})\n"
                        .format(mode.capitalize(), epoch, ds_str))
                    extra_memory_management_time = 0
                    for _ in range(log_interval):
                        filtered = False
                        batch = []
                        index = np.random.randint(data_len)
                        items = deepcopy(loader.dataset.dataset[index])
                        if isinstance(items, list):
                            while not batch:
                                filtered = False
                                # BatchDataset may randomly sample the same elements multiple times, avoid reprocessing
                                unique_samples = set()
                                for item in items:
                                    if id(item) not in unique_samples:
                                        for i, op in enumerate(self.ctx_ops):
                                            start = time.perf_counter()
                                            op_data = forward_numpyop(
                                                [op], item,
                                                {'mode': loader.dataset.mode})
                                            duration = time.perf_counter(
                                            ) - start
                                            duration_list[i][0] += 1
                                            duration_list[i][1] += duration
                                            if isinstance(
                                                    op_data, FilteredData):
                                                filtered = True
                                                break
                                        unique_samples.add(id(item))
                                if not filtered:
                                    batch = items
                        else:
                            while len(batch) < (self.ctx_batch_size or 1):
                                filtered = False
                                for i, op in enumerate(self.ctx_ops):
                                    start = time.perf_counter()
                                    op_data = forward_numpyop([op], items,
                                                              {'mode': mode})
                                    duration = time.perf_counter() - start
                                    duration_list[i][0] += 1
                                    duration_list[i][1] += duration
                                    if isinstance(op_data, FilteredData):
                                        filtered = True
                                        break
                                if not filtered:
                                    batch.append(items)
                                index = np.random.randint(data_len)
                                items = deepcopy(loader.dataset.dataset[index])
                        if not filtered:
                            # Perform the batching
                            start = time.perf_counter()
                            batch = self.ctx_batch_info.collate_fn(batch)
                            duration = time.perf_counter() - start
                            duration_list[len(self.ctx_ops)][0] += 1
                            duration_list[len(self.ctx_ops)][1] += duration
                            # Perform batch ops
                            start = time.perf_counter()
                            # Transform to numpy to not bias against the first op in the batch_op chain
                            batch = to_tensor(batch, target_type='np')
                            extra_memory_management_time += time.perf_counter(
                            ) - start

                            for i, op in enumerate(self.ctx_batch_ops,
                                                   start=len(self.ctx_ops) +
                                                   1):
                                start = time.perf_counter()
                                op_data = forward_numpyop([op],
                                                          data=batch,
                                                          state={'mode': mode},
                                                          batched='np')
                                duration = time.perf_counter() - start
                                duration_list[i][0] += 1
                                duration_list[i][1] += duration
                                if isinstance(op_data, FilteredData):
                                    break
                            # Count extra time needed to cast data back to torch
                            start = time.perf_counter()
                            to_tensor(batch,
                                      target_type='torch',
                                      shared_memory=True)
                            extra_memory_management_time += time.perf_counter(
                            ) - start

                    if self.ctx_batch_ops:
                        # Extra memory management penalty is only incurred when using batch ops
                        duration_list[len(
                            self.ctx_ops)][1] += extra_memory_management_time

                    total_time = np.sum(duration_list[:, 1])
                    normalized_times_ms = 1000 * duration_list[:, 1] / np.maximum(
                        duration_list[:, 0], 1)
                    op_names = ["Op"]

                    for op in self.ctx_ops + [self.ctx_batch_info
                                              ] + self.ctx_batch_ops:
                        if isinstance(op, Sometimes) and op.op:
                            op_names.append(op.__class__.__name__ + " (" +
                                            op.op.__class__.__name__ + ")")
                        elif isinstance(op, Repeat) and op.op:
                            op_names.append(op.__class__.__name__ + " (" +
                                            op.op.__class__.__name__ + ")")
                        elif isinstance(op, OneOf) and op.ops:
                            op_names.append(op.__class__.__name__ + " (" +
                                            ", ".join([
                                                sub_op.__class__.__name__
                                                for sub_op in op.ops
                                            ]) + ")")
                        elif isinstance(op, Fuse) and op.ops:
                            op_names.append(op.__class__.__name__ + " (" +
                                            ", ".join([
                                                sub_op.__class__.__name__
                                                for sub_op in op.ops
                                            ]) + ")")
                        elif isinstance(op, Batch):
                            op_names.append("<Collating Batch>")
                        else:
                            op_names.append(op.__class__.__name__)

                    max_op_len = max(len(op_name) for op_name in op_names)
                    max_in_len = max([
                        len(", ".join(op.inputs)) for op in self.ctx_ops +
                        [self.ctx_batch_info] + self.ctx_batch_ops
                    ] + [len("Inputs")])
                    max_out_len = max([
                        len(", ".join(op.outputs)) for op in self.ctx_ops +
                        [self.ctx_batch_info] + self.ctx_batch_ops
                    ] + [len("Outputs")])
                    ms_visit_len = max(
                        len("{:.3f}".format(max(normalized_times_ms))),
                        len("ms / Visit"))
                    visit_len = max(len(f"{int(np.max(duration_list[:, 0]))}"),
                                    len("Visits"))

                    print("{}: {}: {}: {}: {}: {}".format(
                        "Op".ljust(max_op_len + 1),
                        "Inputs".ljust(max_in_len + 1),
                        "Outputs".ljust(max_out_len + 1),
                        "ms / Visit".ljust(ms_visit_len + 1),
                        "Visits".ljust(visit_len + 1),
                        "Time (Total)".rjust(12)))
                    print("-" * (max_op_len + max_in_len + max_out_len +
                                 visit_len + 37))
                    for i, op in enumerate(self.ctx_ops +
                                           [self.ctx_batch_info] +
                                           self.ctx_batch_ops):
                        print("{}: {}: {}: {}: {}: {:11.2f}%".format(
                            op_names[i + 1].ljust(max_op_len + 1),
                            ", ".join(op.inputs).ljust(max_in_len + 1),
                            ", ".join(op.outputs).ljust(max_out_len + 1),
                            "{:.3f}".format(
                                normalized_times_ms[i]).ljust(ms_visit_len +
                                                              1),
                            str(int(duration_list[i][0])).ljust(visit_len + 1),
                            100 * duration_list[i][1] / total_time))
                    if self.ctx_batch_ops:
                        penalty = round(
                            100 * (duration_list[len(self.ctx_ops)][1] -
                                   extra_memory_management_time) /
                            duration_list[len(self.ctx_ops)][1], 1)
                        print(
                            f"\nNote that collation time would be cut by ~{penalty}% if there were no batched ops."
                        )
                print("\n")  # to make printing more obvious
예제 #16
0
def iwd(tensor: Tensor,
        power: float = 1.0,
        max_prob: float = 0.95,
        pairwise_distance: float = 1.0,
        eps: Optional[Tensor] = None) -> Tensor:
    """Compute the Inverse Weighted Distance from the given input.

    This can be used as an activation function for the final layer of a neural network instead of softmax. For example,
    instead of: model.add(layers.Dense(classes, activation='softmax')), you could use:
    model.add(layers.Dense(classes, activation=lambda x: iwd(tf.nn.sigmoid(x))))

    This method can be used with Numpy data:
    ```python
    n = np.array([[0.5]*5, [0]+[1]*4])
    b = fe.backend.iwd(n)  # [[0.2, 0.2, 0.2, 0.2, 0.2], [0.95, 0.0125, 0.0125, 0.0125, 0.0125]]
    ```

    This method can be used with TensorFlow tensors:
    ```python
    t = tf.constant([[0.5]*5, [0]+[1]*4])
    b = fe.backend.iwd(n)  # [[0.2, 0.2, 0.2, 0.2, 0.2], [0.95, 0.0125, 0.0125, 0.0125, 0.0125]]
    ```

    This method can be used with PyTorch tensors:
    ```python
    p = torch.tensor([[0.5]*5, [0]+[1]*4])
    b = fe.backend.iwd(n)  # [[0.2, 0.2, 0.2, 0.2, 0.2], [0.95, 0.0125, 0.0125, 0.0125, 0.0125]]
    ```

    Args:
        tensor: The input value. Should be of shape (Batch, C) where every element in C corresponds to a (non-negative)
            distance to a target class.
        power: The power to raise the inverse distances to. 1.0 results in a fairly intuitive probability output. Larger
            powers can widen regions of certainty, whereas values between 0 and 1 can widen regions of uncertainty.
        max_prob: The maximum probability to assign to a class estimate when it is distance zero away from the target.
            For numerical stability this must be less than 1.0. We have found that using smaller values like 0.95 can
            lead to natural adversarial robustness.
        pairwise_distance: The distance to any other class when the distance to a target class is zero. For example, if
            you have a perfect match for class 'a', what distance should be reported to class 'b'. If you have a metric
            where this isn't constant, just use an approximate expected distance. In that case `max_prob` will only give
            you approximate control over the true maximum probability.
        eps: The numeric stability constant to be used when d approaches zero. If None then it will be computed using
            `max_prob` and `pairwise_distance`. If not None, then `max_prob` and `pairwise_distance` will be ignored.

    Returns:
        A probability distribution of shape (Batch, C) where smaller distances from `tensor` correspond to larger
        probabilities.
    """
    if eps is None:
        eps = np.array(pairwise_distance * math.pow(
            (1.0 - max_prob) / (max_prob * (tensor.shape[-1] - 1)), 1 / power),
                       dtype=TENSOR_TO_NP_DTYPE[tensor.dtype])
        eps = to_tensor(
            eps,
            target_type='torch' if isinstance(tensor, torch.Tensor) else
            'tf' if tf.is_tensor(tensor) else 'np')
        if isinstance(eps, torch.Tensor):
            eps = eps.to("cuda:0" if torch.cuda.is_available() else "cpu")
    tensor = maximum(tensor, eps)
    tensor = tensor_pow(1.0 / tensor, power)
    tensor = tensor / reshape(reduce_sum(tensor, axis=-1), shape=[-1, 1])
    return tensor