| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | """An abstract objective function definition and common objective functions suitable
|
| | for classifiers/regressors."""
|
| |
|
| | from abc import abstractmethod
|
| | from typing import Optional, Union
|
| |
|
| | import numpy as np
|
| |
|
| | import qiskit_machine_learning.optionals as _optionals
|
| | from qiskit_machine_learning.neural_networks import NeuralNetwork
|
| | from qiskit_machine_learning.utils.loss_functions import Loss
|
| |
|
| | if _optionals.HAS_SPARSE:
|
| |
|
| | from sparse import SparseArray
|
| | else:
|
| |
|
| | class SparseArray:
|
| | """Empty SparseArray class
|
| | Replacement if sparse.SparseArray is not present.
|
| | """
|
| |
|
| | pass
|
| |
|
| |
|
| | class ObjectiveFunction:
|
| | """An abstract objective function. Provides methods for computing objective value and
|
| | gradients for forward and backward passes."""
|
| |
|
| |
|
| | def __init__(
|
| | self, X: np.ndarray, y: np.ndarray, neural_network: NeuralNetwork, loss: Loss
|
| | ) -> None:
|
| | """
|
| | Args:
|
| | X: The input data.
|
| | y: The target values.
|
| | neural_network: An instance of an quantum neural network to be used by this
|
| | objective function.
|
| | loss: A target loss function to be used in training.
|
| | """
|
| | super().__init__()
|
| | self._X = X
|
| | self._num_samples = X.shape[0]
|
| | self._y = y
|
| | self._neural_network = neural_network
|
| | self._loss = loss
|
| | self._last_forward_weights: Optional[np.ndarray] = None
|
| | self._last_forward: Optional[Union[np.ndarray, SparseArray]] = None
|
| |
|
| | @abstractmethod
|
| | def objective(self, weights: np.ndarray) -> float:
|
| | """Computes the value of this objective function given weights.
|
| |
|
| | Args:
|
| | weights: an array of weights to be used in the objective function.
|
| |
|
| | Returns:
|
| | Value of the function.
|
| | """
|
| | raise NotImplementedError
|
| |
|
| | @abstractmethod
|
| | def gradient(self, weights: np.ndarray) -> np.ndarray:
|
| | """Computes gradients of this objective function given weights.
|
| |
|
| | Args:
|
| | weights: an array of weights to be used in the objective function.
|
| |
|
| | Returns:
|
| | Gradients of the function.
|
| | """
|
| | raise NotImplementedError
|
| |
|
| | def _neural_network_forward(self, weights: np.ndarray) -> Union[np.ndarray, SparseArray]:
|
| | """
|
| | Computes and caches the results of the forward pass. Cached values may be re-used in
|
| | gradient computation.
|
| |
|
| | Args:
|
| | weights: an array of weights to be used in the forward pass.
|
| |
|
| | Returns:
|
| | The result of the neural network.
|
| | """
|
| |
|
| | if self._last_forward_weights is None or (
|
| | not np.all(np.isclose(weights, self._last_forward_weights))
|
| | ):
|
| |
|
| | self._last_forward = self._neural_network.forward(self._X, weights)
|
| |
|
| |
|
| | self._last_forward_weights = np.copy(weights)
|
| | return self._last_forward
|
| |
|
| |
|
| | class BinaryObjectiveFunction(ObjectiveFunction):
|
| | """An objective function for binary representation of the output. For instance, classes of
|
| | ``-1`` and ``+1``."""
|
| |
|
| | def objective(self, weights: np.ndarray) -> float:
|
| |
|
| | predict = self._neural_network_forward(weights)
|
| | target = np.array(self._y).reshape(predict.shape)
|
| |
|
| | return float(np.sum(self._loss(predict, target)) / self._num_samples)
|
| |
|
| | def gradient(self, weights: np.ndarray) -> np.ndarray:
|
| |
|
| | num_outputs = self._neural_network.output_shape[0]
|
| | if num_outputs != 1:
|
| | raise ValueError(f"Number of outputs is expected to be 1, got {num_outputs}")
|
| |
|
| |
|
| | output = self._neural_network_forward(weights)
|
| |
|
| | _, weight_grad = self._neural_network.backward(self._X, weights)
|
| |
|
| |
|
| |
|
| | loss_gradient = self._loss.gradient(output, self._y.reshape(-1, 1))
|
| |
|
| |
|
| |
|
| | grad = loss_gradient[:, 0] @ weight_grad[:, 0, :]
|
| |
|
| | grad = grad.reshape(1, -1) / self._num_samples
|
| |
|
| | return grad
|
| |
|
| |
|
| | class MultiClassObjectiveFunction(ObjectiveFunction):
|
| | """
|
| | An objective function for multiclass representation of the output. For instance, classes of
|
| | ``0``, ``1``, ``2``, etc.
|
| | """
|
| |
|
| | def objective(self, weights: np.ndarray) -> float:
|
| |
|
| | probs = self._neural_network_forward(weights)
|
| |
|
| | num_outputs = self._neural_network.output_shape[0]
|
| | val = 0.0
|
| | num_samples = self._X.shape[0]
|
| | for i in range(num_outputs):
|
| |
|
| |
|
| |
|
| |
|
| | val += probs[:, i] @ self._loss(np.full(num_samples, i), self._y)
|
| | val = val / self._num_samples
|
| |
|
| | return val
|
| |
|
| | def gradient(self, weights: np.ndarray) -> np.ndarray:
|
| |
|
| | _, weight_prob_grad = self._neural_network.backward(self._X, weights)
|
| |
|
| | grad = np.zeros((1, self._neural_network.num_weights))
|
| | num_samples = self._X.shape[0]
|
| | num_outputs = self._neural_network.output_shape[0]
|
| | for i in range(num_outputs):
|
| |
|
| |
|
| | grad += weight_prob_grad[:, i, :].T @ self._loss(np.full(num_samples, i), self._y)
|
| |
|
| | grad = grad / self._num_samples
|
| | return grad
|
| |
|
| |
|
| | class OneHotObjectiveFunction(ObjectiveFunction):
|
| | """
|
| | An objective function for one hot encoding representation of the output. For instance, classes
|
| | like ``[1, 0, 0]``, ``[0, 1, 0]``, ``[0, 0, 1]``.
|
| | """
|
| |
|
| | def objective(self, weights: np.ndarray) -> float:
|
| |
|
| | probs = self._neural_network_forward(weights)
|
| |
|
| | value = float(np.sum(self._loss(probs, self._y)) / self._num_samples)
|
| | return value
|
| |
|
| | def gradient(self, weights: np.ndarray) -> np.ndarray:
|
| |
|
| | y_predict = self._neural_network_forward(weights)
|
| |
|
| | _, weight_prob_grad = self._neural_network.backward(self._X, weights)
|
| |
|
| | grad = np.zeros(self._neural_network.num_weights)
|
| | num_outputs = self._neural_network.output_shape[0]
|
| |
|
| | loss_gradient = self._loss.gradient(y_predict, self._y)
|
| | for i in range(num_outputs):
|
| |
|
| |
|
| | grad += loss_gradient[:, i] @ weight_prob_grad[:, i, :]
|
| |
|
| | grad = grad / self._num_samples
|
| | return grad
|
| |
|