Skip to content
This repository has been archived by the owner on Dec 7, 2021. It is now read-only.

Fix QSVM problem wit non-positive semi-definite kernel matrices #1190

Merged
merged 27 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1887377
Merge pull request #116 from Qiskit/master
stefan-woerner May 10, 2020
8043075
Merge pull request #117 from Qiskit/master
stefan-woerner May 11, 2020
4b32d32
Merge pull request #118 from Qiskit/master
stefan-woerner May 12, 2020
c320511
Merge pull request #119 from Qiskit/master
stefan-woerner May 13, 2020
06221e1
Merge pull request #120 from Qiskit/master
stefan-woerner May 19, 2020
d9754ea
Merge pull request #121 from Qiskit/master
stefan-woerner Jun 14, 2020
86e2dc3
Merge pull request #123 from Qiskit/master
stefan-woerner Jun 22, 2020
0e532bf
Merge pull request #124 from Qiskit/master
stefan-woerner Jul 8, 2020
081acf5
Merge pull request #125 from Qiskit/master
stefan-woerner Jul 15, 2020
3b7f27a
Merge pull request #126 from Qiskit/master
stefan-woerner Aug 3, 2020
ad6d91c
Merge pull request #127 from Qiskit/master
stefan-woerner Aug 4, 2020
8b62f82
Merge pull request #128 from Qiskit/master
stefan-woerner Aug 4, 2020
160d74f
Merge pull request #129 from Qiskit/master
stefan-woerner Aug 13, 2020
51789a6
Update _qsvm_binary.py
stefan-woerner Aug 13, 2020
b7b0f94
Merge branch 'master' into qsvm_fix
manoelmarques Aug 13, 2020
bb511ce
Merge branch 'master' into qsvm_fix
manoelmarques Aug 21, 2020
f0bc002
move psd approx to static method for kernel matrix creation
stefan-woerner Aug 27, 2020
435e62f
Update _qsvm_binary.py
stefan-woerner Aug 27, 2020
bfcd34b
Update _qsvm_binary.py
stefan-woerner Aug 27, 2020
0f8613c
update qsvm.py comments
stefan-woerner Aug 27, 2020
75f47db
Fix lint and spelling
woodsp-ibm Aug 31, 2020
a34e5b1
only apply PSD approximation for symmetric matrices
stefan-woerner Sep 1, 2020
04d91fa
Update qiskit/aqua/algorithms/classifiers/qsvm/qsvm.py
woodsp-ibm Sep 1, 2020
89eabc8
remove `print(mat)` from qsvm.py `get_kernel_matrix`
stefan-woerner Sep 1, 2020
490ab06
Merge branch 'qsvm_fix' of github.com:stefan-woerner/qiskit-aqua into…
stefan-woerner Sep 1, 2020
c4c68cb
Add unit test to check enforce_psd
woodsp-ibm Sep 1, 2020
b052049
Merge branch 'master' into qsvm_fix
woodsp-ibm Sep 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion qiskit/aqua/algorithms/classifiers/qsvm/qsvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def construct_circuit(self, x1, x2, measurement=False):
return QSVM._construct_circuit((x1, x2), self.feature_map, measurement)

@staticmethod
def get_kernel_matrix(quantum_instance, feature_map, x1_vec, x2_vec=None):
def get_kernel_matrix(quantum_instance, feature_map, x1_vec, x2_vec=None, enforce_psd=True):
"""
Construct kernel matrix, if x2_vec is None, self-innerproduct is conducted.

Expand All @@ -234,6 +234,9 @@ def get_kernel_matrix(quantum_instance, feature_map, x1_vec, x2_vec=None):
D is the feature dimension
x2_vec (numpy.ndarray): data points, 2-D array, N2xD, where N2 is the number of data,
D is the feature dimension
enforce_psd (bool): enforces that the kernel matrix is positive semi-definite by setting
negative eigenvalues to zero. This is only applied in the symmetric
case, i.e., if `x2_vec == None`.
Returns:
numpy.ndarray: 2-D matrix, N1xN2
"""
Expand Down Expand Up @@ -357,6 +360,14 @@ def get_kernel_matrix(quantum_instance, feature_map, x1_vec, x2_vec=None):
if is_symmetric:
mat[j, i] = mat[i, j]

if enforce_psd and is_symmetric and not is_statevector_sim:
# Find the closest positive semi-definite approximation to kernel matrix, in case it is
# symmetric. The (symmetric) matrix should always be positive semi-definite by
# construction, but this can be violated in case of noise, such as sampling noise, thus,
# the adjustment is only done if NOT using the statevector simulation.
D, U = np.linalg.eig(mat)
mat = U @ np.diag(np.maximum(0, D)) @ U.transpose()

return mat

def construct_kernel_matrix(self, x1_vec, x2_vec=None, quantum_instance=None):
Expand Down
58 changes: 58 additions & 0 deletions test/aqua/test_qsvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@

import os
from test.aqua import QiskitAquaTestCase

import numpy as np
from ddt import ddt, data

from qiskit import BasicAer, QuantumCircuit
from qiskit.circuit.library import ZZFeatureMap
from qiskit.aqua import QuantumInstance, aqua_globals, MissingOptionalLibraryError
from qiskit.aqua.components.multiclass_extensions import (ErrorCorrectingCode,
AllPairs,
OneAgainstRest)
from qiskit.aqua.algorithms import QSVM
from qiskit.aqua.utils import split_dataset_to_data_and_labels, optimize_svm
from qiskit.ml.datasets import ad_hoc_data


@ddt
Expand Down Expand Up @@ -218,3 +222,57 @@ def test_multiclass(self, multiclass_extension):
self.assertEqual(result['predicted_classes'], predicted_classes[multiclass_extension])
except MissingOptionalLibraryError as ex:
self.skipTest(str(ex))

def test_matrix_psd(self):
""" Test kernel matrix positive semi-definite enforcement. """
try:
from cvxpy.error import DQCPError
except ImportError:
self.skipTest('cvxpy does not appeat to be installed')

seed = 10598
feature_dim = 2
_, training_input, _, _ = ad_hoc_data(
training_size=10,
test_size=5,
n=feature_dim,
gap=0.3
)
training_input, _ = split_dataset_to_data_and_labels(training_input)
training_data = training_input[0]
training_labels = training_input[1]
labels = training_labels * 2 - 1 # map label from 0 --> -1 and 1 --> 1
labels = labels.astype(np.float)

feature_map = ZZFeatureMap(feature_dimension=feature_dim, reps=2, entanglement='linear')

with self.assertRaises(DQCPError):
# Sampling noise means that the kernel matrix will not quite be positive
# semi-definite which will cause the optimize svm to fail
backend = BasicAer.get_backend('qasm_simulator')
quantum_instance = QuantumInstance(backend, shots=1024, seed_simulator=seed,
seed_transpiler=seed)
kernel_matrix = QSVM.get_kernel_matrix(quantum_instance, feature_map=feature_map,
x1_vec=training_data, enforce_psd=False)
_ = optimize_svm(kernel_matrix, labels)

# This time we enforce that the matrix be positive semi-definite which runs logic to
# make it so.
backend = BasicAer.get_backend('qasm_simulator')
quantum_instance = QuantumInstance(backend, shots=1024, seed_simulator=seed,
seed_transpiler=seed)
kernel_matrix = QSVM.get_kernel_matrix(quantum_instance, feature_map=feature_map,
x1_vec=training_data, enforce_psd=True)
alpha, b, support = optimize_svm(kernel_matrix, labels)

expected_alpha = [0.855861781, 2.59807482, 0, 0.962959215,
1.08141696, 0.217172547, 0, 0,
0.786462904, 0, 0.969727949, 1.98066946,
0, 0, 1.62049430, 0,
0.394212728, 0, 0.507740935, 1.02910286]
expected_b = [-0.17543365]
expected_support = [True, True, False, True, True, True, False, False, True, False,
True, True, False, False, True, False, True, False, True, True]
np.testing.assert_array_almost_equal(alpha, expected_alpha)
np.testing.assert_array_almost_equal(b, expected_b)
np.testing.assert_array_equal(support, expected_support)