From 5c8bd71d4c53a1a77969228ee9bd0396e2ed4ad0 Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Fri, 4 May 2018 12:45:26 -0400 Subject: [PATCH 01/12] Update srm.py Added code based on what is in rsrm to allow for transformation of new participant data without changing the shared response s --- brainiak/funcalign/srm.py | 115 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/brainiak/funcalign/srm.py b/brainiak/funcalign/srm.py index 668085a81..dbcb83529 100644 --- a/brainiak/funcalign/srm.py +++ b/brainiak/funcalign/srm.py @@ -357,6 +357,60 @@ def _likelihood(self, chol_sigma_s_rhos, log_det_psi, chol_sigma_s, return loglikehood + @staticmethod + def _update_transform_subject(Xi, S): + """Updates the mappings `W_i` for one subject. + + Parameters + ---------- + + Xi : array, shape=[voxels, timepoints] + The fMRI data :math:`X_i` for aligning the subject. + + S : array, shape=[features, timepoints] + The shared response. + + Returns + ------- + + Wi : array, shape=[voxels, features] + The orthogonal transform (mapping) :math:`W_i` for the subject. + """ + A = Xi.dot(S.T) + # Solve the Procrustes problem + U, _, V = np.linalg.svd(A, full_matrices=False) + return U.dot(V) + + def transform_subject(self, X): + """Transform a new subject using the existing model + + Parameters + ---------- + + X : 2D array, shape=[voxels, timepoints] + The fMRI data of the new subject. + + Returns + ------- + + w : 2D array, shape=[voxels, features] + Orthogonal mapping `W_{new}` for new subject + + """ + # Check if the model exist + if hasattr(self, 'w_') is False: + raise NotFittedError("The model fit has not been run yet.") + + # Check the number of TRs in the subject + if X.shape[1] != self.s_.shape[1]: + raise ValueError("The number of timepoints(TRs) does not match the" + "one in the model.") + + for i in range(self.n_iter): + w = self._update_transform_subject(X, self.s_) + + return w + def _srm(self, data): """Expectation-Maximization algorithm for fitting the probabilistic SRM. @@ -393,7 +447,7 @@ def _srm(self, data): subjects = len(data) self.random_state_ = np.random.RandomState(self.rand_seed) random_states = [ - np.random.RandomState(self.random_state_.randint(2**32)) + np.random.RandomState(self.random_state_.randint(2 ** 32)) for i in range(len(data))] # Initialization step: initialize the outputs with initial values, @@ -453,7 +507,7 @@ def _srm(self, data): # Update the shared response shared_response = sigma_s.dot( np.identity(self.features) - rho0 * inv_sigma_s_rhos).dot( - wt_invpsi_x) + wt_invpsi_x) # M-step @@ -649,7 +703,7 @@ def _objective_function(self, data, w, s): objective = 0.0 for m in range(subjects): objective += \ - np.linalg.norm(data[m] - w[m].dot(s), 'fro')**2 + np.linalg.norm(data[m] - w[m].dot(s), 'fro') ** 2 return objective * 0.5 / data[0].shape[1] @@ -678,6 +732,59 @@ def _compute_shared_response(self, data, w): return s + @staticmethod + def _update_transform_subject(Xi, S): + """Updates the mappings `W_i` for one subject. + + Parameters + ---------- + + Xi : array, shape=[voxels, timepoints] + The fMRI data :math:`X_i` for aligning the subject. + + S : array, shape=[features, timepoints] + The shared response. + + Returns + ------- + + Wi : array, shape=[voxels, features] + The orthogonal transform (mapping) :math:`W_i` for the subject. + """ + A = Xi.dot(S.T) + # Solve the Procrustes problem + U, _, V = np.linalg.svd(A, full_matrices=False) + return U.dot(V) + + def transform_subject(self, X): + """Transform a new subject using the existing model + + Parameters + ---------- + + X : 2D array, shape=[voxels, timepoints] + The fMRI data of the new subject. + + Returns + ------- + + w : 2D array, shape=[voxels, features] + Orthogonal mapping `W_{new}` for new subject + """ + # Check if the model exist + if hasattr(self, 'w_') is False: + raise NotFittedError("The model fit has not been run yet.") + + # Check the number of TRs in the subject + if X.shape[1] != self.s_.shape[1]: + raise ValueError("The number of timepoints(TRs) does not match the" + "one in the model.") + + for i in range(self.n_iter): + w = self._update_transform_subject(X, self.s_) + + return w + def _srm(self, data): """Expectation-Maximization algorithm for fitting the probabilistic SRM. @@ -702,7 +809,7 @@ def _srm(self, data): self.random_state_ = np.random.RandomState(self.rand_seed) random_states = [ - np.random.RandomState(self.random_state_.randint(2**32)) + np.random.RandomState(self.random_state_.randint(2 ** 32)) for i in range(len(data))] # Initialization step: initialize the outputs with initial values, From fa27bcc669dd01373832209cada8212a213f571a Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Fri, 4 May 2018 13:26:02 -0400 Subject: [PATCH 02/12] Update test_srm.py Added tests for new SRM code (transform_subject addition) --- tests/funcalign/test_srm.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index c07aa42bf..2e79a9b0b 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -98,7 +98,14 @@ def test_can_instantiate(): "Invalid computation of SRM! (wrong # features after transform)") assert new_s[subject].shape[1] == samples, ( "Invalid computation of SRM! (wrong # samples after transform)") - + + # Check that it does run to compute a new subject + new_w = s.transform_subject(X[0]) + assert new_w.shape[1] == features, ( + "Invalid computation of SRM! (wrong # features for new subject)") + assert new_w.shape[0] == voxels, ( + "Invalid computation of SRM! (wrong # voxels for new subject)") + # Check that it does NOT run with non-matching number of subjects with pytest.raises(ValueError): s.transform(X[1]) @@ -199,7 +206,14 @@ def test_det_srm(): "transform)") assert new_s[subject].shape[1] == samples, ( "Invalid computation of DetSRM! (wrong # samples after transform)") - + + # Check that it does run to compute a new subject + new_w = model.transform_subject(X[0]) + assert new_w.shape[1] == features, ( + "Invalid computation of SRM! (wrong # features for new subject)") + assert new_w.shape[0] == voxels, ( + "Invalid computation of SRM! (wrong # voxels for new subject)") + # Check that it does NOT run with non-matching number of subjects with pytest.raises(ValueError): model.transform(X[1]) From 536a5eec211802f690214e99050c260627473d60 Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Fri, 4 May 2018 16:15:06 -0400 Subject: [PATCH 03/12] Update srm.py Removed fitting and added to docstring for transform_subject --- brainiak/funcalign/srm.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/brainiak/funcalign/srm.py b/brainiak/funcalign/srm.py index dbcb83529..318037b3c 100644 --- a/brainiak/funcalign/srm.py +++ b/brainiak/funcalign/srm.py @@ -382,7 +382,8 @@ def _update_transform_subject(Xi, S): return U.dot(V) def transform_subject(self, X): - """Transform a new subject using the existing model + """Transform a new subject using the existing model. + The subject is assumed to have recieved equivalent stimulation Parameters ---------- @@ -406,8 +407,7 @@ def transform_subject(self, X): raise ValueError("The number of timepoints(TRs) does not match the" "one in the model.") - for i in range(self.n_iter): - w = self._update_transform_subject(X, self.s_) + w = self._update_transform_subject(X, self.s_) return w @@ -757,7 +757,8 @@ def _update_transform_subject(Xi, S): return U.dot(V) def transform_subject(self, X): - """Transform a new subject using the existing model + """Transform a new subject using the existing model. + The subject is assumed to have recieved equivalent stimulation Parameters ---------- @@ -780,8 +781,7 @@ def transform_subject(self, X): raise ValueError("The number of timepoints(TRs) does not match the" "one in the model.") - for i in range(self.n_iter): - w = self._update_transform_subject(X, self.s_) + w = self._update_transform_subject(X, self.s_) return w From cd1ecdcbce72351efcd0c0e2ebf3c7a2387efabd Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Fri, 4 May 2018 16:18:09 -0400 Subject: [PATCH 04/12] Update test_srm.py --- tests/funcalign/test_srm.py | 53 ++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index 2e79a9b0b..2c87f1e8d 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -99,13 +99,6 @@ def test_can_instantiate(): assert new_s[subject].shape[1] == samples, ( "Invalid computation of SRM! (wrong # samples after transform)") - # Check that it does run to compute a new subject - new_w = s.transform_subject(X[0]) - assert new_w.shape[1] == features, ( - "Invalid computation of SRM! (wrong # features for new subject)") - assert new_w.shape[0] == voxels, ( - "Invalid computation of SRM! (wrong # voxels for new subject)") - # Check that it does NOT run with non-matching number of subjects with pytest.raises(ValueError): s.transform(X[1]) @@ -124,7 +117,53 @@ def test_can_instantiate(): s.fit(X) print("Test: different number of samples per subject") +def test_new_subject(): + import brainiak.funcalign.srm + s = brainiak.funcalign.srm.SRM() + assert s, "Invalid SRM instance!" + + import numpy as np + np.random.seed(0) + + voxels = 100 + samples = 500 + subjects = 2 + features = 3 + + s = brainiak.funcalign.srm.SRM(n_iter=5, features=features) + assert s, "Invalid SRM instance!" + + # Create a Shared response S with K = 3 + theta = np.linspace(-4 * np.pi, 4 * np.pi, samples) + z = np.linspace(-2, 2, samples) + r = z**2 + 1 + x = r * np.sin(theta) + y = r * np.cos(theta) + + S = np.vstack((x, y, z)) + + X = [] + W = [] + Q, R = np.linalg.qr(np.random.random((voxels, features))) + W.append(Q) + X.append(Q.dot(S) + 0.1*np.random.random((voxels, samples))) + + for subject in range(1, subjects): + Q, R = np.linalg.qr(np.random.random((voxels, features))) + W.append(Q) + X.append(Q.dot(S) + 0.1*np.random.random((voxels, samples))) + # Check that runs with 2 subject + s.fit(X) + + # Check that it does run to compute a new subject + new_w = s.transform_subject(X[0]) + assert new_w.shape[1] == features, ( + "Invalid computation of SRM! (wrong # features for new subject)") + assert new_w.shape[0] == voxels, ( + "Invalid computation of SRM! (wrong # voxels for new subject)") + + def test_det_srm(): import brainiak.funcalign.srm model = brainiak.funcalign.srm.DetSRM() From 8ed1f9ebdd28419c3be0673be634ac2d37c05bf8 Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Wed, 9 May 2018 10:08:14 -0400 Subject: [PATCH 05/12] Update test_srm.py Added test that transforming before fit fails --- tests/funcalign/test_srm.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index 2c87f1e8d..8666b9512 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -152,6 +152,11 @@ def test_new_subject(): Q, R = np.linalg.qr(np.random.random((voxels, features))) W.append(Q) X.append(Q.dot(S) + 0.1*np.random.random((voxels, samples))) + + # Check that transform does NOT run before fitting the model + with pytest.raises(NotFittedError): + s.transform(X) + print("Test: transforming before fitting the model") # Check that runs with 2 subject s.fit(X) From 77e7b1fe9c1808e3972c7600cfb5ccbe2d19ba85 Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Fri, 11 May 2018 15:03:51 -0400 Subject: [PATCH 06/12] Update srm.py PEP8 --- brainiak/funcalign/srm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brainiak/funcalign/srm.py b/brainiak/funcalign/srm.py index 318037b3c..1eef5f79c 100644 --- a/brainiak/funcalign/srm.py +++ b/brainiak/funcalign/srm.py @@ -382,7 +382,7 @@ def _update_transform_subject(Xi, S): return U.dot(V) def transform_subject(self, X): - """Transform a new subject using the existing model. + """Transform a new subject using the existing model. The subject is assumed to have recieved equivalent stimulation Parameters From 177b71c791b0c8464f6e7a94170963d16e645d5e Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Sun, 13 May 2018 13:48:43 -0400 Subject: [PATCH 07/12] Update test_srm.py PEP8 --- tests/funcalign/test_srm.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index 8666b9512..c381d18f2 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -98,7 +98,7 @@ def test_can_instantiate(): "Invalid computation of SRM! (wrong # features after transform)") assert new_s[subject].shape[1] == samples, ( "Invalid computation of SRM! (wrong # samples after transform)") - + # Check that it does NOT run with non-matching number of subjects with pytest.raises(ValueError): s.transform(X[1]) @@ -117,6 +117,7 @@ def test_can_instantiate(): s.fit(X) print("Test: different number of samples per subject") + def test_new_subject(): import brainiak.funcalign.srm s = brainiak.funcalign.srm.SRM() @@ -152,7 +153,7 @@ def test_new_subject(): Q, R = np.linalg.qr(np.random.random((voxels, features))) W.append(Q) X.append(Q.dot(S) + 0.1*np.random.random((voxels, samples))) - + # Check that transform does NOT run before fitting the model with pytest.raises(NotFittedError): s.transform(X) @@ -160,15 +161,15 @@ def test_new_subject(): # Check that runs with 2 subject s.fit(X) - + # Check that it does run to compute a new subject new_w = s.transform_subject(X[0]) assert new_w.shape[1] == features, ( "Invalid computation of SRM! (wrong # features for new subject)") assert new_w.shape[0] == voxels, ( "Invalid computation of SRM! (wrong # voxels for new subject)") - - + + def test_det_srm(): import brainiak.funcalign.srm model = brainiak.funcalign.srm.DetSRM() @@ -250,14 +251,14 @@ def test_det_srm(): "transform)") assert new_s[subject].shape[1] == samples, ( "Invalid computation of DetSRM! (wrong # samples after transform)") - + # Check that it does run to compute a new subject new_w = model.transform_subject(X[0]) assert new_w.shape[1] == features, ( "Invalid computation of SRM! (wrong # features for new subject)") assert new_w.shape[0] == voxels, ( "Invalid computation of SRM! (wrong # voxels for new subject)") - + # Check that it does NOT run with non-matching number of subjects with pytest.raises(ValueError): model.transform(X[1]) From ca821fdafe3c376c0ecd85c116d45c66584fa51f Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Sun, 13 May 2018 14:16:10 -0400 Subject: [PATCH 08/12] Update test_srm.py PEP8 --- tests/funcalign/test_srm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index c381d18f2..f4e379b9b 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -117,7 +117,7 @@ def test_can_instantiate(): s.fit(X) print("Test: different number of samples per subject") - + def test_new_subject(): import brainiak.funcalign.srm s = brainiak.funcalign.srm.SRM() From 6e9df087127eab314c88e6c10866f672a09ce3d8 Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Mon, 14 May 2018 17:56:22 -0400 Subject: [PATCH 09/12] Update test_srm.py Increasing coverage --- tests/funcalign/test_srm.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index f4e379b9b..a1387c297 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -162,6 +162,10 @@ def test_new_subject(): # Check that runs with 2 subject s.fit(X) + # Check that you get an error when the data is the wrong shape + with pytest.raises(ValueError): + s.transform_subject(X[0].T) + # Check that it does run to compute a new subject new_w = s.transform_subject(X[0]) assert new_w.shape[1] == features, ( @@ -169,6 +173,20 @@ def test_new_subject(): assert new_w.shape[0] == voxels, ( "Invalid computation of SRM! (wrong # voxels for new subject)") + # Check that these analyses work with the deterministic SRM too + ds = brainiak.funcalign.srm.DetSRM(n_iter=5, features=features) + ds.fit(X) + # Check that you get an error when the data is the wrong shape + with pytest.raises(ValueError): + ds.transform_subject(X[0].T) + + # Check that it does run to compute a new subject + new_w = ds.transform_subject(X[0]) + assert new_w.shape[1] == features, ( + "Invalid computation of SRM! (wrong # features for new subject)") + assert new_w.shape[0] == voxels, ( + "Invalid computation of SRM! (wrong # voxels for new subject)") + def test_det_srm(): import brainiak.funcalign.srm From 265941d91efdcb47e390eecfd0d282b717d1ce7d Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Mon, 14 May 2018 18:27:21 -0400 Subject: [PATCH 10/12] Update test_srm.py Increasing coverage --- tests/funcalign/test_srm.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index a1387c297..3181e7ec6 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -128,7 +128,7 @@ def test_new_subject(): voxels = 100 samples = 500 - subjects = 2 + subjects = 3 features = 3 s = brainiak.funcalign.srm.SRM(n_iter=5, features=features) @@ -159,7 +159,7 @@ def test_new_subject(): s.transform(X) print("Test: transforming before fitting the model") - # Check that runs with 2 subject + # Check that runs with 3 subject s.fit(X) # Check that you get an error when the data is the wrong shape @@ -176,6 +176,7 @@ def test_new_subject(): # Check that these analyses work with the deterministic SRM too ds = brainiak.funcalign.srm.DetSRM(n_iter=5, features=features) ds.fit(X) + # Check that you get an error when the data is the wrong shape with pytest.raises(ValueError): ds.transform_subject(X[0].T) From f2ee4e91dfb8accbf57e472ada8dbde805c857d4 Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Mon, 14 May 2018 22:07:47 -0400 Subject: [PATCH 11/12] Update test_srm.py Increase coverage --- tests/funcalign/test_srm.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index 3181e7ec6..2afac5714 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -175,6 +175,13 @@ def test_new_subject(): # Check that these analyses work with the deterministic SRM too ds = brainiak.funcalign.srm.DetSRM(n_iter=5, features=features) + + # Check that transform does NOT run before fitting the model + with pytest.raises(NotFittedError): + ds.transform(X) + print("Test: transforming before fitting the model") + + # Check that runs with 3 subject ds.fit(X) # Check that you get an error when the data is the wrong shape From ab6b961192e567450a5b5deeb4781fbe77c52fde Mon Sep 17 00:00:00 2001 From: CameronTEllis Date: Tue, 15 May 2018 13:13:10 -0400 Subject: [PATCH 12/12] Update test_srm.py Increasing coverage --- tests/funcalign/test_srm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/funcalign/test_srm.py b/tests/funcalign/test_srm.py index 2afac5714..08a022dc4 100644 --- a/tests/funcalign/test_srm.py +++ b/tests/funcalign/test_srm.py @@ -156,7 +156,7 @@ def test_new_subject(): # Check that transform does NOT run before fitting the model with pytest.raises(NotFittedError): - s.transform(X) + s.transform_subject(X) print("Test: transforming before fitting the model") # Check that runs with 3 subject @@ -178,7 +178,7 @@ def test_new_subject(): # Check that transform does NOT run before fitting the model with pytest.raises(NotFittedError): - ds.transform(X) + ds.transform_subject(X) print("Test: transforming before fitting the model") # Check that runs with 3 subject