def test_predict(self): """Test for predict""" from torch.utils.data import DataLoader data_loader = DataLoader(TorchDataset([1], [1]), batch_size=1, shuffle=False) model = TorchConnector(self._qnn, [1]) optimizer = Adam(model.parameters(), lr=0.1) loss_func = MSELoss(reduction="sum") # Default arguments (no provider or backend) torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, ) with self.assertRaises(ValueError): torch_runtime_client.predict(data_loader) # Test for the predict result torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, provider=self._infer_provider, backend=self._backend, ) result = torch_runtime_client.predict(data_loader) self.validate_infer_result(result)
def test_circuit_qnn_without_parameters(self): """Tests CircuitQNN without parameters.""" quantum_instance = self._sv_quantum_instance qc = QuantumCircuit(2) param_y = Parameter("y") qc.ry(param_y, range(2)) qnn = CircuitQNN( circuit=qc, input_params=[param_y], sparse=False, quantum_instance=quantum_instance, input_gradients=True, ) model = TorchConnector(qnn) self._validate_backward_pass(model) qnn = CircuitQNN( circuit=qc, weight_params=[param_y], sparse=False, quantum_instance=quantum_instance, input_gradients=True, ) model = TorchConnector(qnn) self._validate_backward_pass(model)
def __init__(self, input_size: int, hidden_size: int, n_qubits: int = 4, n_qlayers: int = 1, batch_first=True, backend='statevector_simulator'): super(QLSTM, self).__init__() self.input_size = input_size self.hidden_size = hidden_size self.concat_size = input_size + hidden_size self.n_qubits = n_qubits self.n_qlayers = n_qlayers self.batch_first = batch_first self.clayer_in = nn.Linear(self.concat_size, n_qubits) self.clayer_out = nn.Linear(self.n_qubits, self.hidden_size) self.qi = QuantumInstance(Aer.get_backend('statevector_simulator')) feature_map = ZZFeatureMap(self.n_qubits) ansatz = RealAmplitudes(self.n_qubits, reps=self.n_qlayers) self.qnn1 = TwoLayerQNN(self.n_qubits, feature_map, ansatz, exp_val=AerPauliExpectation(), quantum_instance=self.qi) self.qnn2 = TwoLayerQNN(self.n_qubits, feature_map, ansatz, exp_val=AerPauliExpectation(), quantum_instance=self.qi) self.qnn3 = TwoLayerQNN(self.n_qubits, feature_map, ansatz, exp_val=AerPauliExpectation(), quantum_instance=self.qi) self.qnn4 = TwoLayerQNN(self.n_qubits, feature_map, ansatz, exp_val=AerPauliExpectation(), quantum_instance=self.qi) self.qlayer = { 'forget': TorchConnector(self.qnn1), 'input': TorchConnector(self.qnn2), 'update': TorchConnector(self.qnn3), 'output': TorchConnector(self.qnn4) }
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_opflow_qnn_2_1(self, q_i): """ Test Torch Connector + Opflow QNN with input/output dimension 2/1.""" if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance # construct QNN qnn = TwoLayerQNN(2, quantum_instance=quantum_instance) try: model = TorchConnector(qnn) test_data = [ Tensor(1), Tensor([1, 2]), Tensor([[1, 2]]), Tensor([[1], [2]]), Tensor([[[1], [2]], [[3], [4]]]) ] # test model self.validate_output_shape(model, test_data) if q_i == 'sv': self.validate_backward_pass(model) except MissingOptionalLibraryError as ex: self.skipTest(str(ex))
def __init__(self, h, w, outputs): super(DQN, self).__init__() qi = QuantumInstance(Aer.get_backend('statevector_simulator')) num_inputs=4 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)) parity = lambda x: '{:b}'.format(x).count('1') % num_inputs output_shape =num_inputs # parity = 0, 1 qnn = CircuitQNN(qc, input_params=feature_map.parameters, weight_params=ansatz.parameters, interpret=parity, output_shape=output_shape, quantum_instance=qi) #set up PyTorch Module initial_weights = 0.1*(2*np.random.rand(qnn.num_weights) - 1) # Our final network choice. It is wide enough to capture a lot of detail but not too # large to have problems with vanishing gradients on such a small sample size self.conv1 = nn.Conv2d(1, 256, kernel_size=2, stride=1, padding=1) self.bn1 = nn.BatchNorm2d(256) self.fcl1 = nn.Linear(20736,10000) self.fcl2 = nn.Linear(10000, num_inputs) self.qnn = TorchConnector(qnn, initial_weights)
def test_circuit_qnn_sampling(self, interpret): """Test Torch Connector + Circuit QNN for sampling.""" 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) model = TorchConnector(qnn) test_data = [Tensor([2, 2]), Tensor([[1, 1], [2, 2]])] for i, x in enumerate(test_data): 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_opflow_qnn_2_1(self, q_i): """Test Torch Connector + Opflow QNN with input/output dimension 2/1.""" from torch import Tensor if q_i == "sv": quantum_instance = self._sv_quantum_instance else: quantum_instance = self._qasm_quantum_instance # construct QNN qnn = TwoLayerQNN(2, quantum_instance=quantum_instance, input_gradients=True) model = TorchConnector(qnn) test_data = [ Tensor([1]), Tensor([1, 2]), Tensor([[1, 2]]), Tensor([[1], [2]]), Tensor([[[1], [2]], [[3], [4]]]), ] # test model self._validate_output_shape(model, test_data) if q_i == "sv": self._validate_backward_pass(model)
def __init__(self): super().__init__() self.fc1 = Linear(4, 2) self.qnn = TorchConnector( qnn, np.array([0.1] * qnn.num_weights) ) # Apply torch connector self.fc2 = Linear(output_size, 1) # shape depends on the type of the QNN
def test_opflow_qnn_2_2(self, q_i): """Test Torch Connector + Opflow QNN with input/output dimension 2/2.""" from torch import Tensor if q_i == "sv": quantum_instance = self._sv_quantum_instance else: quantum_instance = self._qasm_quantum_instance # construct parametrized circuit params_1 = [Parameter("input1"), Parameter("weight1")] qc_1 = QuantumCircuit(1) qc_1.h(0) qc_1.ry(params_1[0], 0) qc_1.rx(params_1[1], 0) qc_sfn_1 = StateFn(qc_1) # construct cost operator h_1 = StateFn(PauliSumOp.from_list([("Z", 1.0), ("X", 1.0)])) # combine operator and circuit to objective function op_1 = ~h_1 @ qc_sfn_1 # construct parametrized circuit params_2 = [Parameter("input2"), Parameter("weight2")] qc_2 = QuantumCircuit(1) qc_2.h(0) qc_2.ry(params_2[0], 0) qc_2.rx(params_2[1], 0) qc_sfn_2 = StateFn(qc_2) # construct cost operator h_2 = StateFn(PauliSumOp.from_list([("Z", 1.0), ("X", 1.0)])) # combine operator and circuit to objective function op_2 = ~h_2 @ qc_sfn_2 op = ListOp([op_1, op_2]) qnn = OpflowQNN( op, [params_1[0], params_2[0]], [params_1[1], params_2[1]], quantum_instance=quantum_instance, input_gradients=True, ) model = TorchConnector(qnn) test_data = [ Tensor([1]), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[1, 2], [3, 4]]), ] # test model self._validate_output_shape(model, test_data) if q_i == "sv": self._validate_backward_pass(model)
def test_opflow_qnn_2_2(self, q_i): """ Test Torch Connector + Opflow QNN with input/output dimension 2/2.""" if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance # construct parametrized circuit params_1 = [Parameter('input1'), Parameter('weight1')] qc_1 = QuantumCircuit(1) qc_1.h(0) qc_1.ry(params_1[0], 0) qc_1.rx(params_1[1], 0) qc_sfn_1 = StateFn(qc_1) # construct cost operator h_1 = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)])) # combine operator and circuit to objective function op_1 = ~h_1 @ qc_sfn_1 # construct parametrized circuit params_2 = [Parameter('input2'), Parameter('weight2')] qc_2 = QuantumCircuit(1) qc_2.h(0) qc_2.ry(params_2[0], 0) qc_2.rx(params_2[1], 0) qc_sfn_2 = StateFn(qc_2) # construct cost operator h_2 = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)])) # combine operator and circuit to objective function op_2 = ~h_2 @ qc_sfn_2 op = ListOp([op_1, op_2]) qnn = OpflowQNN(op, [params_1[0], params_2[0]], [params_1[1], params_2[1]], quantum_instance=quantum_instance) try: model = TorchConnector(qnn) test_data = [ Tensor(1), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[1, 2], [3, 4]]) ] # test model self.validate_output_shape(model, test_data) if q_i == 'sv': self.validate_backward_pass(model) except MissingOptionalLibraryError as ex: self.skipTest(str(ex))
def test_fit(self): """Test for fit""" from torch.utils.data import DataLoader train_loader = DataLoader(TorchDataset([1], [1]), batch_size=1, shuffle=False) model = TorchConnector(self._qnn, [1]) optimizer = Adam(model.parameters(), lr=0.1) loss_func = MSELoss(reduction="sum") # Default arguments (no provider or backend) torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, ) with self.assertRaises(ValueError): torch_runtime_client.fit(train_loader) # Default arguments for fit torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, provider=self._trainer_provider, backend=self._backend, ) result = torch_runtime_client.fit(train_loader) self.validate_train_result(result) # Specify arguments torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, provider=self._trainer_provider, epochs=1, backend=self._backend, shots=1024, ) result = torch_runtime_client.fit(train_loader, start_epoch=0, seed=42) self.validate_train_result(result)
def test_score(self): """Test for score""" from torch.utils.data import DataLoader data_loader = DataLoader(TorchDataset([1], [1]), batch_size=1, shuffle=False) model = TorchConnector(self._qnn, [1]) optimizer = Adam(model.parameters(), lr=0.1) loss_func = MSELoss(reduction="sum") # Default arguments (no provider or backend) torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, ) with self.assertRaises(ValueError): torch_runtime_client.score(data_loader, score_func="regression") # Test for the score result torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, provider=self._infer_provider, backend=self._backend, ) # Test different score functions result = torch_runtime_client.score(data_loader, score_func="regression") self.validate_infer_result(result, score=True) result = torch_runtime_client.score(data_loader, score_func="classification") self.validate_infer_result(result, score=True) def score_classification(output: Tensor, target: Tensor) -> float: pred = output.argmax(dim=1, keepdim=True) correct = pred.eq(target.view_as(pred)).sum().item() return correct result = torch_runtime_client.score(data_loader, score_func=score_classification) self.validate_infer_result(result, score=True)
def test_fit_with_validation_set(self): """Test for fit with a validation set""" from torch.utils.data import DataLoader train_loader = DataLoader(TorchDataset([1], [1]), batch_size=1, shuffle=False) model = TorchConnector(self._qnn, [1]) optimizer = Adam(model.parameters(), lr=0.1) loss_func = MSELoss(reduction="sum") torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, provider=self._trainer_provider, backend=self._backend, ) validation_loader = DataLoader(TorchDataset([0], [0]), batch_size=1, shuffle=False) result = torch_runtime_client.fit(train_loader, val_loader=validation_loader) self.validate_train_result(result, val_loader=True)
def test_circuit_qnn_2_4(self, config): """Torch Connector + Circuit QNN with no interpret, dense output, and input/output shape 1/8 .""" from torch import Tensor interpret, output_shape, sparse, q_i = config if sparse and not _optionals.HAS_SPARSE: self.skipTest("sparse library is required to run this test") return if q_i == "sv": quantum_instance = self._sv_quantum_instance else: quantum_instance = self._qasm_quantum_instance qc = QuantumCircuit(2) # construct simple feature map param_x_1, param_x_2 = Parameter("x1"), Parameter("x2") qc.ry(param_x_1, range(2)) qc.ry(param_x_2, range(2)) # construct simple feature map param_y = Parameter("y") qc.ry(param_y, range(2)) qnn = CircuitQNN( qc, [param_x_1, param_x_2], [param_y], sparse=sparse, sampling=False, interpret=interpret, output_shape=output_shape, quantum_instance=quantum_instance, input_gradients=True, ) model = TorchConnector(qnn) test_data = [ Tensor([1]), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[1, 2], [3, 4]]), Tensor([[[1], [2]], [[3], [4]]]), ] # test model self._validate_output_shape(model, test_data) if q_i == "sv": self._validate_backward_pass(model)
def test_fit_with_hooks(self): """Test for fit with hooks""" from torch.utils.data import DataLoader train_loader = DataLoader(TorchDataset([1], [1]), batch_size=1, shuffle=False) model = TorchConnector(self._qnn, [1]) optimizer = Adam(model.parameters(), lr=0.1) loss_func = MSELoss(reduction="sum") torch_runtime_client = TorchRuntimeClient( model=model, optimizer=optimizer, loss_func=loss_func, provider=self._trainer_provider, backend=self._backend, ) hook = HookBase() # a single hook result = torch_runtime_client.fit(train_loader, hooks=hook) self.validate_train_result(result) # a hook list result = torch_runtime_client.fit(train_loader, hooks=[hook, hook]) self.validate_train_result(result)
def __init__(self, h, w, outputs): super(DQN, self).__init__() # We did a lot of experimentation with model sizes and types, so we decided to leave some # of our attempts here #1 # self.conv1 = nn.Conv2d(1, 16, kernel_size=5, stride=2) # self.bn1 = nn.BatchNorm2d(16) # self.conv2 = nn.Conv2d(16, 32, kernel_size=5, stride=2) # self.bn2 = nn.BatchNorm2d(32) # self.conv3 = nn.Conv2d(32, 32, kernel_size=5, stride=2) # self.bn3 = nn.BatchNorm2d(32) #2 # # Number of Linear input connections depends on output of conv2d layers # # and therefore the input image size, so compute it. # def conv2d_size_out(size, kernel_size = 5, stride = 2): # return (size - (kernel_size - 1) - 1) // stride + 1 # convw = conv2d_size_out(conv2d_size_out(conv2d_size_out(w))) # convh = conv2d_size_out(conv2d_size_out(conv2d_size_out(h))) # linear_input_size = convw * convh * 32 #3 # self.conv1 = nn.Conv2d(1, 128, kernel_size=2, stride=1, padding=1) # self.bn1 = nn.BatchNorm2d(128) # self.conv2 = nn.Conv2d(128, 256, kernel_size=5, stride=1, padding=1) # self.bn2 = nn.BatchNorm2d(256) # self.fcl1 = nn.Linear(12544,5000) # self.fcl2 = nn.Linear(5000, 64) qi = QuantumInstance(Aer.get_backend('statevector_simulator')) feature_map = ZZFeatureMap(4) ansatz = RealAmplitudes(4, reps=1) qnn = TwoLayerQNN(4, feature_map, ansatz, exp_val=AerPauliExpectation(), quantum_instance=qi) #4 # Our final network choice. It is wide enough to capture a lot of detail but not too # large to have problems with vanishing gradients on such a small sample size self.conv1 = nn.Conv2d(1, 256, kernel_size=2, stride=1, padding=1) self.bn1 = nn.BatchNorm2d(256) self.fcl1 = nn.Linear(20736, 10000) self.fcl2 = nn.Linear(10000, 4) self.qnn = TorchConnector(qnn)
def test_circuit_qnn_2_4(self, config): """Torch Connector + Circuit QNN with no interpret, dense output, and input/output shape 1/8 .""" interpret, output_shape, sparse, q_i = config if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance qc = QuantumCircuit(2) # construct simple feature map param_x_1, param_x_2 = Parameter('x1'), Parameter('x2') qc.ry(param_x_1, range(2)) qc.ry(param_x_2, range(2)) # construct simple feature map param_y = Parameter('y') qc.ry(param_y, range(2)) qnn = CircuitQNN(qc, [param_x_1, param_x_2], [param_y], sparse=sparse, sampling=False, interpret=interpret, output_shape=output_shape, quantum_instance=quantum_instance) try: model = TorchConnector(qnn) test_data = [ Tensor(1), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[1, 2], [3, 4]]), Tensor([[[1], [2]], [[3], [4]]]) ] # test model self.validate_output_shape(model, test_data) if q_i == 'sv': self.validate_backward_pass(model) except MissingOptionalLibraryError as ex: self.skipTest(str(ex))
def test_opflow_qnn_1_1(self, q_i): """ Test Torch Connector + Opflow QNN with input/output dimension 1/1.""" if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance # construct simple feature map param_x = Parameter('x') feature_map = QuantumCircuit(1, name='fm') feature_map.ry(param_x, 0) # construct simple feature map param_y = Parameter('y') ansatz = QuantumCircuit(1, name='vf') ansatz.ry(param_y, 0) # construct QNN with statevector simulator qnn = TwoLayerQNN(1, feature_map, ansatz, quantum_instance=quantum_instance) try: model = TorchConnector(qnn) test_data = [ Tensor(1), Tensor([1]), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[[1], [2]], [[3], [4]]]) ] # test model self.validate_output_shape(model, test_data) if q_i == 'sv': self.validate_backward_pass(model) except MissingOptionalLibraryError as ex: self.skipTest(str(ex))
def test_circuit_qnn_1_8(self, config): """Torch Connector + Circuit QNN with no interpret, dense output, and input/output shape 1/8 .""" interpret, output_shape, sparse, q_i = config if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance qc = QuantumCircuit(3) # construct simple feature map param_x = Parameter('x') qc.ry(param_x, range(3)) # construct simple feature map param_y = Parameter('y') qc.ry(param_y, range(3)) qnn = CircuitQNN(qc, [param_x], [param_y], sparse=sparse, sampling=False, interpret=interpret, output_shape=output_shape, quantum_instance=quantum_instance) model = TorchConnector(qnn) test_data = [ Tensor(1), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[[1], [2]], [[3], [4]]]) ] # test model self.validate_output_shape(model, test_data) if q_i == 'sv': self.validate_backward_pass(model)
def test_opflow_qnn_1_1(self, q_i): """Test Torch Connector + Opflow QNN with input/output dimension 1/1.""" from torch import Tensor if q_i == "sv": quantum_instance = self._sv_quantum_instance else: quantum_instance = self._qasm_quantum_instance # construct simple feature map param_x = Parameter("x") feature_map = QuantumCircuit(1, name="fm") feature_map.ry(param_x, 0) # construct simple feature map param_y = Parameter("y") ansatz = QuantumCircuit(1, name="vf") ansatz.ry(param_y, 0) # construct QNN with statevector simulator qnn = TwoLayerQNN(1, feature_map, ansatz, quantum_instance=quantum_instance, input_gradients=True) model = TorchConnector(qnn) test_data = [ Tensor([1]), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[[1], [2]], [[3], [4]]]), ] # test model self._validate_output_shape(model, test_data) if q_i == "sv": self._validate_backward_pass(model)
def test_batch_gradients(self): """Test backward pass for batch input.""" # 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) # test single gradient w = model.weights.detach().numpy() res_qnn = qnn.forward(x[0, :], w) # construct finite difference gradient for weights 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(Tensor(x[0, :])) self.assertAlmostEqual(np.linalg.norm(res_model.detach().numpy() - res_qnn[0]), 0.0, places=4) res_model.backward() grad_model = model.weights.grad self.assertAlmostEqual(np.linalg.norm(grad_model.detach().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(Tensor(x))) batch_res_model.backward() self.assertAlmostEqual(np.linalg.norm(model.weights.grad.numpy() - batch_grad.transpose()[0]), 0.0, places=4)
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, )