From 0b188101e0a3cd9a5f9815b707c4c74ac4aa59d4 Mon Sep 17 00:00:00 2001 From: Aivar Sootla Date: Fri, 15 Mar 2024 16:20:47 +0000 Subject: [PATCH 1/4] Update objective computation: multiply quadratic biases with (1-alpha) --- dwave/plugins/sklearn/transformers.py | 30 ++++++++++++------- .../algorithm-update-d73cd4f7c854a8b3.yaml | 3 ++ 2 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/algorithm-update-d73cd4f7c854a8b3.yaml diff --git a/dwave/plugins/sklearn/transformers.py b/dwave/plugins/sklearn/transformers.py index aa186be..f0fe455 100644 --- a/dwave/plugins/sklearn/transformers.py +++ b/dwave/plugins/sklearn/transformers.py @@ -185,7 +185,8 @@ def correlation_cqm( label=f"{num_features}-hot", ) - with tempfile.TemporaryFile() as fX, tempfile.TemporaryFile() as fout: + with tempfile.TemporaryFile() as fX, tempfile.TemporaryFile() as fout,\ + tempfile.TemporaryFile() as targetcor_out: # we make a copy of X because we'll be modifying it in-place within # some of the functions X_copy = np.memmap(fX, X.dtype, mode="w+", shape=(X.shape[0], X.shape[1] + 1)) @@ -199,23 +200,30 @@ def correlation_cqm( mode="w+", shape=(X_copy.shape[1], X_copy.shape[1]), ) - + # make the matrix that will hold the output correlations + target_correlations = np.memmap( + targetcor_out, + dtype=np.result_type(X, y), + mode="w+", + shape=(X_copy.shape[1], 1), + ) # main calculation. It modifies X_copy in-place corrcoef(X_copy, out=correlations, rowvar=False, copy=False) - # we don't care about the direction of correlation in terms of # the penalty/quality np.absolute(correlations, out=correlations) - + # copying the correlations with the target column + target_correlations = correlations[:, -1].copy() + # multiplying all terms with (1 - alpha) + np.multiply(correlations, (1 - alpha), out=correlations) # our objective - # we multiply by 2 because the matrix is symmetric - np.fill_diagonal(correlations, correlations[:, -1] * (-2 * alpha * num_features)) - - # Note: the full symmetric matrix (with both upper- and lower-diagonal - # entries for each correlation coefficient) is retained for consistency with - # the original formulation from Milne et al. + # we multiply by num_features to have consistent performance + # with the increase of the number of features + np.fill_diagonal(correlations, target_correlations * (- alpha * num_features)) + # Note: we only add terms on and above the diagonal it = np.nditer(correlations[:-1, :-1], flags=['multi_index'], op_flags=[['readonly']]) - cqm.set_objective((*it.multi_index, x) for x in it if x) + cqm.set_objective((*it.multi_index, x) for x in it + if it.multi_index[0] <= it.multi_index[1] and x) return cqm diff --git a/releasenotes/notes/algorithm-update-d73cd4f7c854a8b3.yaml b/releasenotes/notes/algorithm-update-d73cd4f7c854a8b3.yaml new file mode 100644 index 0000000..dc85f5f --- /dev/null +++ b/releasenotes/notes/algorithm-update-d73cd4f7c854a8b3.yaml @@ -0,0 +1,3 @@ +--- +fixes: + - Multiply off-diagonal terms of the correlation matrix with 1-alpha. \ No newline at end of file From 6981c61d8a59fb655d27fc67063bd7beb8194823 Mon Sep 17 00:00:00 2001 From: Aivar Sootla Date: Wed, 20 Mar 2024 10:01:22 +0000 Subject: [PATCH 2/4] address comments: replace a tempfile with direct computations, add a test --- dwave/plugins/sklearn/transformers.py | 20 +++++--------------- tests/test_transformer.py | 4 ++++ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/dwave/plugins/sklearn/transformers.py b/dwave/plugins/sklearn/transformers.py index f0fe455..ada1ad3 100644 --- a/dwave/plugins/sklearn/transformers.py +++ b/dwave/plugins/sklearn/transformers.py @@ -185,8 +185,7 @@ def correlation_cqm( label=f"{num_features}-hot", ) - with tempfile.TemporaryFile() as fX, tempfile.TemporaryFile() as fout,\ - tempfile.TemporaryFile() as targetcor_out: + with tempfile.TemporaryFile() as fX, tempfile.TemporaryFile() as fout: # we make a copy of X because we'll be modifying it in-place within # some of the functions X_copy = np.memmap(fX, X.dtype, mode="w+", shape=(X.shape[0], X.shape[1] + 1)) @@ -199,27 +198,18 @@ def correlation_cqm( dtype=np.result_type(X, y), mode="w+", shape=(X_copy.shape[1], X_copy.shape[1]), - ) - # make the matrix that will hold the output correlations - target_correlations = np.memmap( - targetcor_out, - dtype=np.result_type(X, y), - mode="w+", - shape=(X_copy.shape[1], 1), - ) + ) # main calculation. It modifies X_copy in-place corrcoef(X_copy, out=correlations, rowvar=False, copy=False) # we don't care about the direction of correlation in terms of # the penalty/quality np.absolute(correlations, out=correlations) - # copying the correlations with the target column - target_correlations = correlations[:, -1].copy() - # multiplying all terms with (1 - alpha) - np.multiply(correlations, (1 - alpha), out=correlations) + # multiplying all but last columns and rows with (1 - alpha) + np.multiply(correlations[:-1, :-1], (1 - alpha), out=correlations[:-1, :-1]) # our objective # we multiply by num_features to have consistent performance # with the increase of the number of features - np.fill_diagonal(correlations, target_correlations * (- alpha * num_features)) + np.fill_diagonal(correlations, correlations[:, -1] * (- alpha * num_features)) # Note: we only add terms on and above the diagonal it = np.nditer(correlations[:-1, :-1], flags=['multi_index'], op_flags=[['readonly']]) cqm.set_objective((*it.multi_index, x) for x in it diff --git a/tests/test_transformer.py b/tests/test_transformer.py index 14214cc..6e0ef3e 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -126,6 +126,10 @@ def test_alpha_0(self): cqm = SelectFromQuadraticModel.correlation_cqm(self.X, self.y, num_features=3, alpha=0) self.assertTrue(not any(cqm.objective.linear.values())) + def test_alpha_1_no_quadratic(self): + cqm = SelectFromQuadraticModel.correlation_cqm(self.X, self.y, num_features=3, alpha=1) + self.assertTrue(not any(cqm.objective.quadratic.values())) + def test_alpha_1(self): rng = np.random.default_rng(42) From 39957bba08001aa426945713112365bd6e2b6d52 Mon Sep 17 00:00:00 2001 From: Aivar Sootla Date: Tue, 2 Apr 2024 15:55:47 +0100 Subject: [PATCH 3/4] bug fix: need to use all matrix entries in this case --- dwave/plugins/sklearn/transformers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dwave/plugins/sklearn/transformers.py b/dwave/plugins/sklearn/transformers.py index ada1ad3..10c6ffa 100644 --- a/dwave/plugins/sklearn/transformers.py +++ b/dwave/plugins/sklearn/transformers.py @@ -212,8 +212,7 @@ def correlation_cqm( np.fill_diagonal(correlations, correlations[:, -1] * (- alpha * num_features)) # Note: we only add terms on and above the diagonal it = np.nditer(correlations[:-1, :-1], flags=['multi_index'], op_flags=[['readonly']]) - cqm.set_objective((*it.multi_index, x) for x in it - if it.multi_index[0] <= it.multi_index[1] and x) + cqm.set_objective((*it.multi_index, x) for x in it if x) return cqm From f7862e8302e2ca241182620e7dad9d92dca45ec1 Mon Sep 17 00:00:00 2001 From: Aivar Sootla Date: Tue, 2 Apr 2024 15:57:26 +0100 Subject: [PATCH 4/4] fixing comments --- dwave/plugins/sklearn/transformers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dwave/plugins/sklearn/transformers.py b/dwave/plugins/sklearn/transformers.py index 10c6ffa..74aa070 100644 --- a/dwave/plugins/sklearn/transformers.py +++ b/dwave/plugins/sklearn/transformers.py @@ -210,7 +210,6 @@ def correlation_cqm( # we multiply by num_features to have consistent performance # with the increase of the number of features np.fill_diagonal(correlations, correlations[:, -1] * (- alpha * num_features)) - # Note: we only add terms on and above the diagonal it = np.nditer(correlations[:-1, :-1], flags=['multi_index'], op_flags=[['readonly']]) cqm.set_objective((*it.multi_index, x) for x in it if x)