def _validate_output_shape(self, model: TorchConnector, test_data: List) -> None: """Creates a Linear PyTorch module with the same in/out dimensions as the given model, applies the list of test input data to both, and asserts that they have the same output shape. Args: model: model to be tested test_data: list of test input tensors Raises: QiskitMachineLearningError: Invalid input. """ from torch.nn import Linear # create benchmark model in_dim = model.neural_network.num_inputs if len(model.neural_network.output_shape) != 1: raise QiskitMachineLearningError( "Function only works for one dimensional output") out_dim = model.neural_network.output_shape[0] # we target our tests to either cpu or gpu linear = Linear(in_dim, out_dim, device=self._device) model.to(self._device) # iterate over test data and validate behavior of model for x in test_data: x = x.to(self._device) # test linear model and track whether it failed or store the output shape c_worked = True try: c_shape = linear(x).shape except Exception: # pylint: disable=broad-except c_worked = False # test quantum model and track whether it failed or store the output shape q_worked = True try: q_shape = model(x).shape except Exception: # pylint: disable=broad-except q_worked = False # compare results and assert that the behavior is equal with self.subTest("c_worked == q_worked", tensor=x): self.assertEqual(c_worked, q_worked) if c_worked and q_worked: with self.subTest("c_shape == q_shape", tensor=x): self.assertEqual(c_shape, q_shape)
def test_circuit_qnn_sampling(self, interpret): """Test Torch Connector + Circuit QNN for sampling.""" from torch import Tensor qc = QuantumCircuit(2) # construct simple feature map param_x1, param_x2 = Parameter("x1"), Parameter("x2") qc.ry(param_x1, range(2)) qc.ry(param_x2, range(2)) # construct simple feature map param_y = Parameter("y") qc.ry(param_y, range(2)) qnn = CircuitQNN( qc, [param_x1, param_x2], [param_y], sparse=False, sampling=True, interpret=interpret, output_shape=None, quantum_instance=self._qasm_quantum_instance, input_gradients=True, ) model = TorchConnector(qnn) model.to(self._device) test_data = [Tensor([2, 2]), Tensor([[1, 1], [2, 2]])] for i, x in enumerate(test_data): x = x.to(self._device) if i == 0: self.assertEqual(model(x).shape, qnn.output_shape) else: shape = model(x).shape self.assertEqual(shape, (len(x), *qnn.output_shape))
def test_circuit_qnn_batch_gradients(self, config): """Test batch gradient computation of CircuitQNN gives the same result as the sum of individual gradients.""" import torch from torch.nn import MSELoss from torch.optim import SGD output_shape, interpret = config num_inputs = 2 feature_map = ZZFeatureMap(num_inputs) ansatz = RealAmplitudes(num_inputs, entanglement="linear", reps=1) qc = QuantumCircuit(num_inputs) qc.append(feature_map, range(num_inputs)) qc.append(ansatz, range(num_inputs)) qnn = CircuitQNN( qc, input_params=feature_map.parameters, weight_params=ansatz.parameters, interpret=interpret, output_shape=output_shape, quantum_instance=self._sv_quantum_instance, ) # set up PyTorch module initial_weights = np.array([0.1] * qnn.num_weights) model = TorchConnector(qnn, initial_weights) model.to(self._device) # random data set x = torch.rand(5, 2) y = torch.rand(5, output_shape) # define optimizer and loss optimizer = SGD(model.parameters(), lr=0.1) f_loss = MSELoss(reduction="sum") sum_of_individual_losses = 0.0 for x_i, y_i in zip(x, y): x_i = x_i.to(self._device) y_i = y_i.to(self._device) output = model(x_i) sum_of_individual_losses += f_loss(output, y_i) optimizer.zero_grad() sum_of_individual_losses.backward() sum_of_individual_gradients = model.weight.grad.detach().cpu() x = x.to(self._device) y = y.to(self._device) output = model(x) batch_loss = f_loss(output, y) optimizer.zero_grad() batch_loss.backward() batch_gradients = model.weight.grad.detach().cpu() self.assertAlmostEqual(np.linalg.norm(sum_of_individual_gradients - batch_gradients), 0.0, places=4) self.assertAlmostEqual( sum_of_individual_losses.detach().cpu().numpy(), batch_loss.detach().cpu().numpy(), places=4, )
def test_opflow_qnn_batch_gradients(self): """Test backward pass for batch input.""" import torch # construct random data set num_inputs = 2 num_samples = 10 x = np.random.rand(num_samples, num_inputs) # set up QNN qnn = TwoLayerQNN( num_qubits=num_inputs, quantum_instance=self._sv_quantum_instance, ) # set up PyTorch module initial_weights = np.random.rand(qnn.num_weights) model = TorchConnector(qnn, initial_weights=initial_weights) model.to(self._device) # test single gradient w = model.weight.detach().cpu().numpy() res_qnn = qnn.forward(x[0, :], w) # construct finite difference gradient for weight eps = 1e-4 grad = np.zeros(w.shape) for k in range(len(w)): delta = np.zeros(w.shape) delta[k] += eps f_1 = qnn.forward(x[0, :], w + delta) f_2 = qnn.forward(x[0, :], w - delta) grad[k] = (f_1 - f_2) / (2 * eps) grad_qnn = qnn.backward(x[0, :], w)[1][0, 0, :] self.assertAlmostEqual(np.linalg.norm(grad - grad_qnn), 0.0, places=4) model.zero_grad() res_model = model(torch.tensor(x[0, :], device=self._device)) self.assertAlmostEqual( np.linalg.norm(res_model.detach().cpu().numpy() - res_qnn[0]), 0.0, places=4) res_model.backward() grad_model = model.weight.grad self.assertAlmostEqual( np.linalg.norm(grad_model.detach().cpu().numpy() - grad_qnn), 0.0, places=4) # test batch input batch_grad = np.zeros((*w.shape, num_samples, 1)) for k in range(len(w)): delta = np.zeros(w.shape) delta[k] += eps f_1 = qnn.forward(x, w + delta) f_2 = qnn.forward(x, w - delta) batch_grad[k] = (f_1 - f_2) / (2 * eps) batch_grad = np.sum(batch_grad, axis=1) batch_grad_qnn = np.sum(qnn.backward(x, w)[1], axis=0) self.assertAlmostEqual(np.linalg.norm(batch_grad - batch_grad_qnn.transpose()), 0.0, places=4) model.zero_grad() batch_res_model = sum(model(torch.tensor(x, device=self._device))) batch_res_model.backward() self.assertAlmostEqual( np.linalg.norm(model.weight.grad.detach().cpu().numpy() - batch_grad.transpose()[0]), 0.0, places=4, )