def test_min_rmse_rate(self): warnings.filterwarnings("ignore") # Check that the minimum RMSE solution is returned n_freqs = 10 n_wavs = 100 n_iterations = 50 n_sources = 5 lam_p = [0.0] * 5 # Generate ground truth A and S A_org = np.random.normal(size=(n_freqs, n_sources)) helpers.A_norm(A_org) S_org = np.random.normal(size=(n_sources, n_wavs)) X = np.dot(A_org, S_org) # Initialize A and S for GMCA A_p = np.ones(A_org.shape) helpers.A_norm(A_p) A = np.ones(A_org.shape) helpers.A_norm(A) S = np.ones(S_org.shape) # Run GMCA gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, ret_min_rmse=False, min_rmse_rate=n_iterations) # Check that GMCA returns the minimum RMSE solution np.testing.assert_almost_equal(S, np.dot(np.linalg.pinv(A), X)) # Reset A and S A = np.ones(A_org.shape) helpers.A_norm(A) S = np.ones(S_org.shape) # Re-run GMCA without ret_min_rmse gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, ret_min_rmse=False, min_rmse_rate=n_iterations - 1) # Check that GMCA does not return the min_rmse solution self.assertGreater(np.mean(np.abs(S - np.dot(np.linalg.pinv(A), X))), 1e-4)
def test_wrapper(self): # Test that the wrapper returns the same results as the numba # implementation freq_dim = 10 pix_dim = 100 n_iterations = 10 n_sources = 5 lam_p = [0.0] * 5 X = np.random.normal(loc=1000, size=(freq_dim, pix_dim)) A_numba = np.random.normal(size=(freq_dim, n_sources)) helpers.A_norm(A_numba) S_numba = np.ones((n_sources, pix_dim)) A_p = np.random.normal(size=(freq_dim, n_sources)) helpers.A_norm(A_p) lam_s_vals = [0, 10] lam_p_vals = [0, 1000] min_rmse_rates = [0, 2] ret_min_rmse_vals = [True, False] enforce_nn_A = True for lam_s in lam_s_vals: for lam_p_val in lam_p_vals: lam_p = [lam_p_val] * n_sources for min_rmse_rate in min_rmse_rates: for ret_min_rmse in ret_min_rmse_vals: A_init = np.copy(A_numba) S_init = np.copy(S_numba) A, S = gmca_core.gmca(X, n_sources, n_iterations, A_init, S_init, A_p, lam_p, enforce_nn_A, lam_s, ret_min_rmse, min_rmse_rate, seed=2) gmca_core.gmca_numba(X, n_sources, n_iterations, A_numba, S_numba, A_p, lam_p, enforce_nn_A, lam_s, ret_min_rmse, min_rmse_rate, seed=2) self.assertAlmostEqual(np.max(np.abs(A_numba - A)), 0) self.assertAlmostEqual(np.max(np.abs(S_numba - S)), 0)
def test_ret_min_rmse(self): warnings.filterwarnings("ignore") # Check that the minimum RMSE solution is returned n_freqs = 10 n_wavs = 100 n_iterations = 50 n_sources = 5 lam_p = [0.0] * 5 # Generate ground truth A and S A_org = np.random.normal(size=(n_freqs, n_sources)) S_org = np.random.normal(size=(n_sources, n_wavs)) X = np.dot(A_org, S_org) # Initialize A and S for GMCA A_p = np.ones(A_org.shape) A = np.ones(A_org.shape) S = np.ones(S_org.shape) # Run GMCA gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, ret_min_rmse=True) # Check that GMCA returns the minimum RMSE solution self.assertAlmostEqual(np.sum(S), np.sum(np.dot(np.linalg.pinv(A), X))) # Reset A and S A = np.ones(A_org.shape) S = np.ones(S_org.shape) # Re-run GMCA without ret_min_rmse gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, ret_min_rmse=False) # Check that GMCA does not return the min_rmse solution self.assertNotEqual(np.sum(S), np.sum(np.dot(np.linalg.pinv(A), X)))
def test_init_consistent(self): # Test that passing or not passing in the initialization returns the # same thing. n_freqs = 10 n_wavs = 100 n_sources = 5 n_iterations = 10 lam_p = np.array([0.0] * 5) X = np.random.normal(size=(n_freqs, n_wavs)) # Run GMCA A_p = np.ones((n_freqs, n_sources)) A = np.ones((n_freqs, n_sources)) S = np.ones((n_sources, n_wavs)) gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, ret_min_rmse=True, seed=2) # Premade initializations R_i = np.zeros(X.shape) AS = np.zeros(X.shape) A_R = np.zeros((1, X.shape[1])) A_i = np.zeros((A.shape[0], 1)) A2 = np.ones((n_freqs, n_sources)) S2 = np.ones((n_sources, n_wavs)) gmca_core.gmca_numba(X, n_sources, n_iterations, A2, S2, A_p, lam_p, ret_min_rmse=True, seed=2, R_i_init=R_i, AS_init=AS, A_R_init=A_R, A_i_init=A_i) np.testing.assert_almost_equal(S, S2) np.testing.assert_almost_equal(A, A2)
def test_random_seed(self): warnings.filterwarnings("ignore") # Test that setting the random seed leads to consistent results. freq_dim = 10 pix_dim = 100 n_iterations = 1 n_sources = 5 lam_p = [0.0] * 5 # Generate ground truth A and S A_org = np.abs(np.random.normal(size=(freq_dim, n_sources))) S_org = np.random.normal(loc=1000, size=(n_sources, pix_dim)) X = np.dot(A_org, S_org) # Initialize A and S for GMCA A_p = np.ones(A_org.shape) A1 = np.ones(A_org.shape) S1 = np.ones(S_org.shape) A2 = np.ones(A_org.shape) S2 = np.ones(S_org.shape) A3 = np.ones(A_org.shape) S3 = np.ones(S_org.shape) # Run GMCA with different seeds. gmca_core.gmca_numba(X, n_sources, n_iterations, A1, S1, A_p, lam_p, ret_min_rmse=False, min_rmse_rate=n_iterations, enforce_nn_A=False, seed=1) gmca_core.gmca_numba(X, n_sources, n_iterations, A2, S2, A_p, lam_p, ret_min_rmse=False, min_rmse_rate=n_iterations, enforce_nn_A=False, seed=1) gmca_core.gmca_numba(X, n_sources, n_iterations, A3, S3, A_p, lam_p, ret_min_rmse=False, min_rmse_rate=n_iterations, enforce_nn_A=False, seed=2) # Make sure the two runs with the same random seed give the same # answer. Given that we only ran for 1 iteration, make sure that # different random seeds do not give the same answer. self.assertAlmostEqual(np.max(np.abs(A1 - A2)), 0) self.assertAlmostEqual(np.max(np.abs(S1 - S2)), 0) self.assertGreater(np.mean(np.abs(A1 - A3)), 1e-7) self.assertGreater(np.mean(np.abs(S1 - S3)), 1e-7)
def test_gmca_end_to_end(self): warnings.filterwarnings("ignore") # Test that gmca works end to end, returning reasonable results. rseed = 5 n_freqs = 10 n_wavs = int(1e3) n_iterations = 50 n_sources = 5 lam_s = 1e-5 lam_p = [0.0] * n_sources s_mag = 1e3 # Generate ground truth A and S A_org = np.random.rand(n_freqs * n_sources).reshape( (n_freqs, n_sources)) helpers.A_norm(A_org) S_org = np.random.rand(n_sources * n_wavs).reshape((n_sources, n_wavs)) S_org *= s_mag X = np.dot(A_org, S_org) # Initialize A and S for GMCA A_p = np.ones(A_org.shape) helpers.A_norm(A_p) A = np.ones(A_org.shape) helpers.A_norm(A) S = np.ones(S_org.shape) # Run GMCA gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, lam_s=lam_s, ret_min_rmse=False, min_rmse_rate=2 * n_iterations, enforce_nn_A=True, seed=rseed) # Save sparsity of S for later test sparsity_1 = np.sum(np.abs(S)) err1 = np.sum(np.abs(np.dot(A, S) - X)) # Continue GMCA gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, lam_s=lam_s, ret_min_rmse=False, min_rmse_rate=2 * n_iterations, enforce_nn_A=True, seed=rseed) err2 = np.sum(np.abs(np.dot(A, S) - X)) self.assertGreater(err1, err2) gmca_core.gmca_numba(X, n_sources, 200, A, S, A_p, lam_p, lam_s=lam_s, ret_min_rmse=False, min_rmse_rate=2 * n_iterations, enforce_nn_A=True, seed=rseed) np.testing.assert_almost_equal(np.dot(A, S), X, decimal=4) # Test that lam_s enforces sparsity end_to_end lam_s = 10 A = np.ones(A_org.shape) helpers.A_norm(A) S = np.ones(S_org.shape) gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, lam_s=lam_s, ret_min_rmse=False, min_rmse_rate=2 * n_iterations, enforce_nn_A=True, seed=rseed) # Save closeness to prior for later test A_p_val = np.sum(np.abs(A - A_p)) self.assertLess(np.sum(np.abs(S)), sparsity_1) # Test that lam_p enforcces prior end_to_end A = np.ones(A_org.shape) helpers.A_norm(A) S = np.ones(S_org.shape) lam_p = [1e8] * n_sources gmca_core.gmca_numba(X, n_sources, n_iterations, A, S, A_p, lam_p, lam_s=lam_s, ret_min_rmse=False, min_rmse_rate=2 * n_iterations, enforce_nn_A=True, seed=rseed) self.assertLess(np.sum(np.abs(A - A_p)), A_p_val) # Finally, test data with nans does not impose constraining power # where there are nans. X_copy = np.copy(X) for i in range(n_freqs): X_copy[i, np.random.randint(0, X.shape[1], 10)] = np.nan A = np.ones(A_org.shape) helpers.A_norm(A) S = np.ones(S_org.shape) lam_p = [0.0] * n_sources lam_s = 1e-5 gmca_core.gmca_numba(X_copy, n_sources, 200, A, S, A_p, lam_p, lam_s=lam_s, ret_min_rmse=False, min_rmse_rate=2 * n_iterations, enforce_nn_A=True, seed=rseed) self.assertEqual(np.sum(np.isnan(np.dot(A, S))), 0) self.assertLess(np.mean(np.abs(X - np.dot(A, S))) / s_mag, 0.1)
def hgmca_epoch_numba(X_level, A_hier_list, lam_hier, A_global, lam_global, S_level, n_epochs, m_level, n_iterations, lam_s, seed, enforce_nn_A, min_rmse_rate, epoch_start=0): """ Runs the epoch loop for a given level of hgmca using numba optimization. For an in depth description of the algorithm see arxiv 1910.08077 Parameters: X_level (np.array): A numba.typed.List of numpy arrays corresponding to the data for each level of analysis. A_hier_list ([np.array]): A numba.typed.List of numpy arrays containing the mixing matrix hierarchy for each level of analysis. lam_hier (np.array): A n_sources long array of the prior for each of the columns of the mixing matrices in A_hier. This allows for a source dependent prior. A_global (np.array): A global mixing matrix prior that will be applied to all matrices in the hierarchy lam_global (np.array): A n_sources long array of the prior for each of the columns of the global prior. S_level ([np.array,...]):A numba.typed.List of numpy arrays containing the source matrices for each level of analysis. n_epochs (int): The number of times the algorithm should pass through all the levels. m_level (int): The maximum level of analysis. n_iterations (int): The number of iterations of coordinate descent to conduct per epoch. lam_s (float): The lambda parameter for the sparsity l1 norm. seed (int): An integer to seed the random number generator. enforce_nn_A (bool): A boolean that determines if the mixing matrix will be forced to only have non-negative values. min_rmse_rate (int): How often the source matrix will be set to the minimum rmse solution. 0 will never return min_rmse within the gmca optimization. epoch_start (int): What epoch the code is starting at. Important for min_rmse_rate. Notes: A_hier and S_level will be updated in place. """ # We allocate the arrays that gmca will use here to avoid them being # reallocated R_i_level = numba.typed.List() AS_level = numba.typed.List() A_R_level = numba.typed.List() A_i_level = numba.typed.List() for level in range(m_level + 1): R_i_level.append(np.zeros(X_level[level][0].shape)) AS_level.append(np.zeros(X_level[level][0].shape)) A_R_level.append(np.zeros((1, X_level[level].shape[2]))) A_i_level.append(np.zeros((X_level[level].shape[1], 1))) # Set the random seed. np.random.seed(seed) # Now we iterate through our graphical model using our approximate closed # form solution for the desired number of epochs. for epoch in range(epoch_start, epoch_start + n_epochs): # We want to iterate through the levels in random order. This should # theoretically speed up convergence. level_perm = np.random.permutation(m_level + 1) for level in level_perm: npatches = wavelets_hgmca.level_to_npatches(level) for patch in range(npatches): # Calculate the mixing matrix prior and the number of matrices # used to construct that prior. A_prior = get_A_prior(A_hier_list, level, patch, lam_hier) A_prior += lam_global * A_global # First we deal with the relatively simple case where there are # no sources at this level if X_level[level].size == 0: A_hier_list[level][patch] = A_prior helpers.A_norm(A_hier_list[level][patch]) # If there are sources at this level we must run GMCA else: # Extract the data for the patch. X_p = X_level[level][patch] S_p = S_level[level][patch] # For HGMCA we want to store the source signal that # includes the lasso shooting subtraction of lam_s for # stability of the loss function. Only in the last step do # we want gmca to return the min_rmse solution. if min_rmse_rate == 0: ret_min_rmse = False else: ret_min_rmse = (((epoch + 1) % min_rmse_rate) == 0) # Call gmca for the patch. Note the lam_p has already been # accounted for. n_sources = len(S_p) gmca_core.gmca_numba(X_p, n_sources, n_iterations, A_hier_list[level][patch], S_p, A_p=A_prior, lam_p=np.ones(n_sources), enforce_nn_A=enforce_nn_A, lam_s=lam_s, ret_min_rmse=ret_min_rmse, R_i_init=R_i_level[level], AS_init=AS_level[level], A_R_init=A_R_level[level], A_i_init=A_i_level[level])