def cross_entropy(predictions: np.ndarray, targets: np.ndarray, epsilon: float = 1e-15) -> float: """ Cross entropy calculation between :py:attr:`targets` (encoded as one-hot vectors) and :py:attr:`predictions`. Predictions are normalized to sum up to `1.0`. .. note:: The implementation of this function is based on the discussion on `StackOverflow <https://stackoverflow.com/a/47398312/10138546>`_. Due to ArrayBoxes that are required for automatic differentiation, we currently use this implementation instead of implementations provided by sklearn for example. :param predictions: Predictions in same order as targets. In case predictions for several samples are given, the weighted cross entropy is returned. :param targets: Ground truth labels for supplied samples. :param epsilon: Amount to clip predictions as log is not defined for `0` and `1`. """ assert ( predictions.shape == targets.shape ), f"Shape of predictions {predictions.shape} must match targets {targets.shape}" current_sum = np.sum(predictions, axis=predictions.ndim - 1) if predictions.ndim == 1: sample_count = 1 predictions = predictions / current_sum else: sample_count = predictions.shape[0] predictions = predictions / current_sum[:, np.newaxis] predictions = np.clip(predictions, epsilon, 1.0 - epsilon) return -np.sum(targets * np.log(predictions)) / sample_count
def mitigate_depolarizing_noise(K, num_wires, method, use_entries=None): r"""Estimate depolarizing noise rate(s) using on the diagonal entries of a kernel matrix and mitigate the noise, assuming a global depolarizing noise model. Args: K (array[float]): Noisy kernel matrix. num_wires (int): Number of wires/qubits of the quantum embedding kernel. method (``'single'`` | ``'average'`` | ``'split_channel'``): Strategy for mitigation * ``'single'``: An alias for ``'average'`` with ``len(use_entries)=1``. * ``'average'``: Estimate a global noise rate based on the average of the diagonal entries in ``use_entries``, which need to be measured on the quantum computer. * ``'split_channel'``: Estimate individual noise rates per embedding, requiring all diagonal entries to be measured on the quantum computer. use_entries (array[int]): Diagonal entries to use if method in ``['single', 'average']``. If ``None``, defaults to ``[0]`` (``'single'``) or ``range(len(K))`` (``'average'``). Returns: array[float]: Mitigated kernel matrix. Reference: This method is introduced in Section V in `arXiv:2105.02276 <https://arxiv.org/abs/2105.02276>`_. **Example:** For an example usage of ``mitigate_depolarizing_noise`` please refer to the `PennyLane demo on the kernel module <https://github.com/PennyLaneAI/qml/tree/master/demonstrations/tutorial_kernel_module.py>`_ or `the postprocessing demo for arXiv:2105.02276 <https://github.com/thubregtsen/qhack/blob/master/paper/post_processing_demo.py>`_. """ dim = 2**num_wires if method == "single": if use_entries is None: use_entries = (0, ) diagonal_element = K[use_entries[0], use_entries[0]] noise_rate = (1 - diagonal_element) * dim / (dim - 1) mitigated_matrix = (K - noise_rate / dim) / (1 - noise_rate) elif method == "average": if use_entries is None: diagonal_elements = np.diag(K) else: diagonal_elements = np.diag(K)[np.array(use_entries)] noise_rates = (1 - diagonal_elements) * dim / (dim - 1) mean_noise_rate = np.mean(noise_rates) mitigated_matrix = (K - mean_noise_rate / dim) / (1 - mean_noise_rate) elif method == "split_channel": eff_noise_rates = np.clip((1 - np.diag(K)) * dim / (dim - 1), 0.0, 1.0) noise_rates = 1 - np.sqrt(1 - eff_noise_rates) inverse_noise = (-np.outer(noise_rates, noise_rates) + noise_rates.reshape( (1, len(K))) + noise_rates.reshape((len(K), 1))) mitigated_matrix = (K - inverse_noise / dim) / (1 - inverse_noise) return mitigated_matrix
def imshow(inp, title=None): """Display image from tensor.""" inp = inp.numpy().transpose((1, 2, 0)) # Inverse of the initial normalization operation. mean = np.array([0.485, 0.456, 0.406]) std = np.array([0.229, 0.224, 0.225]) inp = std * inp + mean inp = np.clip(inp, 0, 1) plt.imshow(inp) if title is not None: plt.title(title)
def threshold_matrix(K): r"""Remove negative eigenvalues from the given kernel matrix. This method yields the closest positive semi-definite matrix in any unitarily invariant norm, e.g. the Frobenius norm. Args: K (array[float]): Kernel matrix, assumed to be symmetric. Returns: array[float]: Kernel matrix with cropped negative eigenvalues. **Example:** Consider a symmetric matrix with both positive and negative eigenvalues: .. code-block :: pycon >>> K = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 2]]) >>> np.linalg.eigvalsh(K) array([-1., 1., 2.]) We then can threshold/truncate the eigenvalues of the matrix via .. code-block :: pycon >>> K_thresh = qml.kernels.threshold_matrix(K) >>> np.linalg.eigvalsh(K_thresh) array([0., 1., 2.]) If the input matrix does not have negative eigenvalues, ``threshold_matrix`` does not have any effect. """ w, v = np.linalg.eigh(K) if w[0] < 0: # Transform spectrum: Threshold/clip at 0. w0 = np.clip(w, 0, None) return (v * w0) @ np.transpose(v) return K
def step(self, objective_fn, *args, **kwargs): """Update trainable arguments with one step of the optimizer. Args: objective_fn (function): the objective function for optimization *args: variable length argument list for objective function **kwargs: variable length of keyword arguments for the objective function Returns: list[array]: The new variable values :math:`x^{(t+1)}`. If single arg is provided, list[array] is replaced by array. """ self.trainable_args = set() for index, arg in enumerate(args): if getattr(arg, "requires_grad", True): self.trainable_args |= {index} if self.s is None: # Number of shots per parameter self.s = [ np.zeros_like(a, dtype=np.int64) + self.min_shots for i, a in enumerate(args) if i in self.trainable_args ] # keep track of the number of shots run s = np.concatenate([i.flatten() for i in self.s]) self.max_shots = max(s) self.shots_used = int(2 * np.sum(s)) self.total_shots_used += self.shots_used # compute the gradient, as well as the variance in the gradient, # using the number of shots determined by the array s. grads, grad_variances = self.compute_grad(objective_fn, args, kwargs) new_args = self.apply_grad(grads, args) if self.xi is None: self.chi = [np.zeros_like(g, dtype=np.float64) for g in grads] self.xi = [np.zeros_like(g, dtype=np.float64) for g in grads] # running average of the gradient self.chi = [self.mu * c + (1 - self.mu) * g for c, g in zip(self.chi, grads)] # running average of the gradient variance self.xi = [self.mu * x + (1 - self.mu) * v for x, v in zip(self.xi, grad_variances)] for idx, (c, x) in enumerate(zip(self.chi, self.xi)): xi = x / (1 - self.mu ** (self.k + 1)) chi = c / (1 - self.mu ** (self.k + 1)) # determine the new optimum shots distribution for the next # iteration of the optimizer s = np.ceil( (2 * self.lipschitz * self.stepsize * xi) / ((2 - self.lipschitz * self.stepsize) * (chi ** 2 + self.b * (self.mu ** self.k))) ) # apply an upper and lower bound on the new shot distributions, # to avoid the number of shots reducing below min(2, min_shots), # or growing too significantly. gamma = ( (self.stepsize - self.lipschitz * self.stepsize ** 2 / 2) * chi ** 2 - xi * self.lipschitz * self.stepsize ** 2 / (2 * s) ) / s argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) smax = max(s[argmax_gamma], 2) self.s[idx] = np.squeeze(np.int64(np.clip(s, min(2, self.min_shots), smax))) self.k += 1 # unwrap from list if one argument, cleaner return if len(new_args) == 1: return new_args[0] return new_args