├── .gitignore ├── .gitmodules ├── Code ├── Algorithms │ ├── CH │ │ ├── xt_ch_lm_m.m │ │ ├── xt_ch_lm_m_gn.m │ │ ├── xt_ch_lm_m_rw2.m │ │ ├── xt_ch_lm_s.m │ │ ├── xt_ch_lm_s_gn.m │ │ └── xt_ch_lm_s_rw2.m │ ├── CO │ │ ├── xt_co_communication.m │ │ ├── xt_co_lm_m.m │ │ ├── xt_co_lm_m_get_diff.m │ │ ├── xt_co_lm_m_gn.m │ │ ├── xt_co_lm_m_gn_get_diff.m │ │ ├── xt_co_lm_s.m │ │ ├── xt_co_lm_s_get_diff.m │ │ ├── xt_co_mtr.m │ │ └── xt_co_obj_missing.m │ ├── DB │ │ └── xt_db_balm.m │ ├── JE │ │ ├── je_add_hessian_layer.cpp │ │ ├── je_alternation.m │ │ ├── je_compute_error_matrix.m │ │ ├── je_compute_unreg_wiberg_hessian.m │ │ ├── je_form_hessian.cpp │ │ ├── je_generate_sample.m │ │ ├── je_preprocess_values.m │ │ ├── je_read_binary_matrix.m │ │ ├── je_solve_inner_problem.m │ │ ├── je_solve_reg_linear_lsq.m │ │ ├── je_solve_unreg_linear_lsq.m │ │ ├── je_solve_unreg_linear_lsq_qr.m │ │ ├── je_varpro.m │ │ ├── je_write_binary_matrix.m │ │ └── lrmf_ceres_wrapper.m │ ├── NB │ │ ├── xt_nb_README.txt │ │ ├── xt_nb_buildAchol.c │ │ ├── xt_nb_buildproblem.m │ │ ├── xt_nb_cholsolvecell.c │ │ ├── xt_nb_initialguess.m │ │ ├── xt_nb_installrtrmc.m │ │ ├── xt_nb_license.txt │ │ ├── xt_nb_lsqfit.m │ │ ├── xt_nb_main.m │ │ ├── xt_nb_multfullsparse.m │ │ ├── xt_nb_multsparsefull.m │ │ ├── xt_nb_randmask.m │ │ ├── xt_nb_rtrmc.m │ │ ├── xt_nb_rtrmc_wrapper.m │ │ ├── xt_nb_setsparseentries.c │ │ ├── xt_nb_spbuildmatrix.c │ │ ├── xt_nb_spmaskmult.c │ │ └── xt_nb_sqfrobnormfactors.m │ ├── PG │ │ ├── xt_pg_README.txt │ │ ├── xt_pg_check_buildkron.m │ │ ├── xt_pg_csf.m │ │ └── xt_pg_kronmex.c │ ├── RC │ │ ├── xt_rc_alm.m │ │ └── xt_rc_rcalm.m │ └── TO │ │ └── xt_to_dw.m ├── Experiment │ ├── je_analyze_results.m │ ├── je_compute_russo.m │ ├── je_external_wrapper.m │ ├── je_generate_sample.m │ └── je_run_experiment.m ├── run_demo.m └── setup.m ├── Documents ├── hong_awf_iccv15.pdf ├── hong_awf_iccv15_further.pdf ├── hong_awf_iccv15_poster.pdf └── hong_awf_iccv15_supmat.pdf └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows XP 2 | Thumbs.db 3 | 4 | # Mac 5 | .DS_Store 6 | 7 | # Mex files 8 | *.mexa64 9 | *.mexglx 10 | *.mexmaci64 11 | *.mexw32 12 | *.mexw64 13 | 14 | # Results folder 15 | Results 16 | Code/Temp 17 | 18 | # Ceres executable 19 | Code/Algorithms/JE/lrmf_ceres_exec 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Code/Toolboxes/manopt"] 2 | path = Code/Toolboxes/manopt 3 | url = https://github.com/jhh37/manopt-unofficial 4 | [submodule "Code/Toolboxes/awful"] 5 | path = Code/Toolboxes/awful 6 | url = https://github.com/awf/awful 7 | [submodule "Code/Algorithms/JE/lrmf_ceres"] 8 | path = Code/Algorithms/JE/lrmf_ceres 9 | url = https://github.com/jhh37/lrmf-ceres 10 | [submodule "Datasets"] 11 | path = Datasets 12 | url = https://github.com/jhh37/lrmf-datasets 13 | -------------------------------------------------------------------------------- /Code/Algorithms/CH/xt_ch_lm_m.m: -------------------------------------------------------------------------------- 1 | function [e, iter, Ns] = xt_ch_lm_m(M, mask, r, N, max_iter, tol, disp_level) 2 | 3 | % LM_M in the paper (Damped RW0 + ambiguity constraint A) 4 | 5 | if nargin < 5, max_iter = 300; 6 | end 7 | if nargin < 6, tol = 1e-10; 8 | end 9 | if nargin < 7, disp_level = 1; 10 | end 11 | 12 | vec = @(X) X(:); 13 | [m,n]=size(M); 14 | n_r = n - r; 15 | nr_r2 = n_r * r; 16 | 17 | % Form cells of repeatedly-used variables. 18 | store.C = cell(m, 1); 19 | store.index = cell(m, 1); 20 | store.n0 = cell(m, 1); 21 | store.rng = cumsum([0; sum(mask, 2)]); 22 | for i = 1 : m 23 | store.index{i} = find(mask(i,:)' == 1); 24 | store.n0{i} = length(store.index{i}); 25 | store.C{i} = sparse(1 : (store.n0{i} * r), vec(reshape(1 : (store.n0{i} * r), store.n0{i}, r)'), 1); 26 | % store.C{i} = communication(store.n0{i}, r); 27 | end 28 | 29 | % converged=0; 30 | % times=0; 31 | [N, ~] = qr(N, 0); 32 | [e, NiQ, NiR] = obj_missing(M, N, store); 33 | lambda=1e-4; 34 | % lambda=1e-6; 35 | % k=10; 36 | k_inc = 10; 37 | k_dec = 100; 38 | max_trials = 150; 39 | 40 | for iter = 1 : max_iter 41 | [b, H, N_perp] = get_b_H(M, N, NiQ, NiR, r, store); 42 | 43 | for trial = 1 : max_trials 44 | delta = (H + lambda * speye(nr_r2)) \ b; 45 | %delta=inv(H+lambda*diag(diag(H)))*b; 46 | 47 | % variation=b'*delta; 48 | % K=mtr(delta,n-r,r); 49 | [N_new, ~] = qr(N + N_perp * reshape(delta, n_r, r), 0); 50 | [e_new, NiQ, NiR] = obj_missing(M, N_new, store); 51 | 52 | if e_new < e 53 | lambda = lambda / k_dec; 54 | break; 55 | else 56 | lambda = lambda * k_inc; 57 | end 58 | end 59 | 60 | if disp_level 61 | fprintf('[%04d] %.2e %.4e\n', iter, lambda, e_new); 62 | end 63 | 64 | if (trial == max_trials) || (abs(e - e_new) < tol * e), break 65 | end 66 | 67 | N = N_new; 68 | e = e_new; 69 | 70 | end 71 | 72 | Ns = N_new; 73 | e = e_new; 74 | 75 | end 76 | 77 | function [b, H, N_perp] = get_b_H(M, N, NiQ, NiR, r, store) 78 | 79 | [m,n]=size(M); 80 | b=zeros(n*r, 1); 81 | H=zeros(n*r); 82 | N_perp=null(N'); 83 | 84 | for i = 1 : m 85 | % if(mod(i,100)==0) 86 | % j=0; 87 | % end 88 | % mm=mask(i,:)'; 89 | % index=find(mm==1); 90 | % n0=length(index); 91 | C = store.C{i}; 92 | index = store.index{i}; 93 | n0 = store.n0{i}; 94 | 95 | % I=eye(n); 96 | % L=N_perp(index,:); 97 | % L=I(index,:); 98 | % D=kron(eye(r),L); 99 | rng = bsxfun(@plus, index - 1, 1:n:n*r); 100 | rng = rng(:); 101 | 102 | mi=M(i,index)'; 103 | % Ni=N(index,:); 104 | % E=inv(Ni'*Ni); 105 | % A=Ni*E; 106 | % P=A*Ni'; 107 | % C=communication(n0,r); 108 | 109 | E = eye(r) / NiR{i}; 110 | AT = NiR{i} \ NiQ{i}'; 111 | 112 | % temp1=A'*mi; 113 | % temp2 = (eye(n0)-P)*mi; 114 | temp0 = NiQ{i}' * mi; 115 | temp1 = NiR{i} \ temp0; 116 | temp2 = mi - NiQ{i} * temp0; 117 | b(rng) = b(rng) + kron(temp1, temp2); 118 | 119 | % temp = kron(temp1 * temp1', speye(n0) - NiQ * NiQ'); % RW2 120 | % temp = temp + C' * kron(temp2 * temp2', E * E') * C; % RW1 121 | % temp = temp + C' * kron(temp2,eye(r)) * (2 * kron(temp1',AT) - kron(temp2',E) * C); % RW0 122 | temp = C' * kron(temp2 * temp1', AT); 123 | H(rng, rng) = H(rng, rng) ... 124 | + kron(temp1 * temp1', speye(n0) - NiQ{i} * NiQ{i}') ... 125 | - C' * kron(temp2 * temp2', E * E') * C ... 126 | + 2 * temp; 127 | end 128 | 129 | PT = kron(speye(r), N_perp'); 130 | b = PT * b; 131 | H = PT * H * PT'; 132 | H = 0.5 * (H + H'); 133 | 134 | end 135 | 136 | function [f, NiQ, NiR] = obj_missing(M, N, store) 137 | 138 | m = size(M, 1); 139 | NiQ = cell(m, 1); 140 | NiR = cell(m, 1); 141 | 142 | f=0; 143 | for i=1:m 144 | % mm=mask(i,:)'; 145 | % index=find(mm==1); 146 | index = store.index{i}; 147 | 148 | mi=M(i,index)'; 149 | % Ni=N(index,:); 150 | [NiQ{i}, NiR{i}] = qr(N(index, :), 0); 151 | 152 | % P=Ni*inv(Ni'*Ni)*Ni'; 153 | % temp=mi-P*mi; 154 | % f=f+mi'*temp; 155 | % b=Ni\mi; 156 | % temp=mi-Ni*b; 157 | temp = mi - NiQ{i} * (NiQ{i}' * mi); 158 | f=f+temp'*temp; 159 | end 160 | 161 | end 162 | -------------------------------------------------------------------------------- /Code/Algorithms/CH/xt_ch_lm_m_gn.m: -------------------------------------------------------------------------------- 1 | function [e, iter, Ns] = xt_ch_lm_m_gn(M, mask, r, N, max_iter, tol, disp_level) 2 | 3 | % LM_M_GN (Damped RW1 + ambiguity constraint A) 4 | 5 | if nargin < 5, max_iter = 300; 6 | end 7 | if nargin < 6, tol = 1e-10; 8 | end 9 | if nargin < 7, disp_level = 1; 10 | end 11 | 12 | vec = @(X) X(:); 13 | [m,n]=size(M); 14 | n_r = n - r; 15 | nr_r2 = n_r * r; 16 | 17 | % Form cells of repeatedly-used variables. 18 | store.C = cell(m, 1); 19 | store.index = cell(m, 1); 20 | store.n0 = cell(m, 1); 21 | store.rng = cumsum([0; sum(mask, 2)]); 22 | for i = 1 : m 23 | store.index{i} = find(mask(i,:)' == 1); 24 | store.n0{i} = length(store.index{i}); 25 | store.C{i} = sparse(1 : (store.n0{i} * r), vec(reshape(1 : (store.n0{i} * r), store.n0{i}, r)'), 1); 26 | % store.C{i} = communication(store.n0{i}, r); 27 | end 28 | 29 | % converged=0; 30 | % times=0; 31 | [N, ~] = qr(N, 0); 32 | [e, NiQ, NiR] = obj_missing(M, N, store); 33 | lambda=1e-4; 34 | % lambda=1e-6; 35 | % k=10; 36 | k_inc = 10; 37 | k_dec = 100; 38 | max_trials = 150; 39 | 40 | for iter = 1 : max_iter 41 | [b, H, N_perp] = get_b_H(M, N, NiQ, NiR, r, store); 42 | 43 | for trial = 1 : max_trials 44 | delta = (H + lambda * speye(nr_r2)) \ b; 45 | %delta=inv(H+lambda*diag(diag(H)))*b; 46 | 47 | % variation=b'*delta; 48 | % K=mtr(delta,n-r,r); 49 | [N_new, ~] = qr(N + N_perp * reshape(delta, n_r, r), 0); 50 | [e_new, NiQ, NiR] = obj_missing(M, N_new, store); 51 | 52 | if e_new < e 53 | lambda = lambda / k_dec; 54 | break; 55 | else 56 | lambda = lambda * k_inc; 57 | end 58 | end 59 | 60 | if disp_level 61 | fprintf('[%04d] %.2e %.4e\n', iter, lambda, e_new); 62 | end 63 | 64 | if (trial == max_trials) || (abs(e - e_new) < tol * e), break 65 | end 66 | 67 | N = N_new; 68 | e = e_new; 69 | 70 | end 71 | 72 | Ns = N_new; 73 | e = e_new; 74 | 75 | end 76 | 77 | function [b, H, N_perp] = get_b_H(M, N, NiQ, NiR, r, store) 78 | 79 | [m,n]=size(M); 80 | b=zeros(n*r, 1); 81 | H=zeros(n*r); 82 | N_perp=null(N'); 83 | 84 | for i = 1 : m 85 | % if(mod(i,100)==0) 86 | % j=0; 87 | % end 88 | % mm=mask(i,:)'; 89 | % index=find(mm==1); 90 | % n0=length(index); 91 | C = store.C{i}; 92 | index = store.index{i}; 93 | n0 = store.n0{i}; 94 | 95 | % I=eye(n); 96 | % L=N_perp(index,:); 97 | % L=I(index,:); 98 | % D=kron(eye(r),L); 99 | rng = bsxfun(@plus, index - 1, 1:n:n*r); 100 | rng = rng(:); 101 | 102 | mi=M(i,index)'; 103 | % Ni=N(index,:); 104 | % E=inv(Ni'*Ni); 105 | % A=Ni*E; 106 | % P=A*Ni'; 107 | % C=communication(n0,r); 108 | 109 | E = eye(r) / NiR{i}; 110 | % AT = NiR{i} \ NiQ{i}'; 111 | 112 | % temp1=A'*mi; 113 | % temp2 = (eye(n0)-P)*mi; 114 | temp0 = NiQ{i}' * mi; 115 | temp1 = NiR{i} \ temp0; 116 | temp2 = mi - NiQ{i} * temp0; 117 | b(rng) = b(rng) + kron(temp1, temp2); 118 | 119 | % temp = kron(temp1 * temp1', speye(n0) - NiQ * NiQ'); % RW2 120 | % temp = temp + C' * kron(temp2 * temp2', E * E') * C; % RW1 121 | % temp = temp + C' * kron(temp2,eye(r)) * (2 * kron(temp1',AT) - kron(temp2',E) * C); % RW0 122 | % temp = C' * kron(temp2 * temp1', AT); 123 | H(rng, rng) = H(rng, rng) ... 124 | + kron(temp1 * temp1', speye(n0) - NiQ{i} * NiQ{i}') ... 125 | + C' * kron(temp2 * temp2', E * E') * C; 126 | end 127 | 128 | PT = kron(speye(r), N_perp'); 129 | b = PT * b; 130 | H = PT * H * PT'; 131 | H = 0.5 * (H + H'); 132 | 133 | end 134 | 135 | function [f, NiQ, NiR] = obj_missing(M, N, store) 136 | 137 | m = size(M, 1); 138 | NiQ = cell(m, 1); 139 | NiR = cell(m, 1); 140 | 141 | f=0; 142 | for i=1:m 143 | % mm=mask(i,:)'; 144 | % index=find(mm==1); 145 | index = store.index{i}; 146 | 147 | mi=M(i,index)'; 148 | % Ni=N(index,:); 149 | [NiQ{i}, NiR{i}] = qr(N(index, :), 0); 150 | 151 | % P=Ni*inv(Ni'*Ni)*Ni'; 152 | % temp=mi-P*mi; 153 | % f=f+mi'*temp; 154 | % b=Ni\mi; 155 | % temp=mi-Ni*b; 156 | temp = mi - NiQ{i} * (NiQ{i}' * mi); 157 | f=f+temp'*temp; 158 | end 159 | 160 | end 161 | -------------------------------------------------------------------------------- /Code/Algorithms/CH/xt_ch_lm_m_rw2.m: -------------------------------------------------------------------------------- 1 | function [e, iter, Ns] = xt_ch_lm_m_rw2(M, mask, r, N, max_iter, tol, disp_level) 2 | 3 | % LM_M_RW2 (Damped RW2 + ambiguity constraint A) 4 | 5 | if nargin < 5, max_iter = 300; 6 | end 7 | if nargin < 6, tol = 1e-10; 8 | end 9 | if nargin < 7, disp_level = 1; 10 | end 11 | 12 | vec = @(X) X(:); 13 | [m,n]=size(M); 14 | n_r = n - r; 15 | nr_r2 = n_r * r; 16 | 17 | % Form cells of repeatedly-used variables. 18 | store.C = cell(m, 1); 19 | store.index = cell(m, 1); 20 | store.n0 = cell(m, 1); 21 | store.rng = cumsum([0; sum(mask, 2)]); 22 | for i = 1 : m 23 | store.index{i} = find(mask(i,:)' == 1); 24 | store.n0{i} = length(store.index{i}); 25 | store.C{i} = sparse(1 : (store.n0{i} * r), vec(reshape(1 : (store.n0{i} * r), store.n0{i}, r)'), 1); 26 | % store.C{i} = communication(store.n0{i}, r); 27 | end 28 | 29 | % converged=0; 30 | % times=0; 31 | [N, ~] = qr(N, 0); 32 | [e, NiQ, NiR] = obj_missing(M, N, store); 33 | lambda=1e-4; 34 | % lambda=1e-6; 35 | % k=10; 36 | k_inc = 10; 37 | k_dec = 100; 38 | max_trials = 150; 39 | 40 | for iter = 1 : max_iter 41 | [b, H, N_perp] = get_b_H(M, N, NiQ, NiR, r, store); 42 | 43 | for trial = 1 : max_trials 44 | delta = (H + lambda * speye(nr_r2)) \ b; 45 | %delta=inv(H+lambda*diag(diag(H)))*b; 46 | 47 | % variation=b'*delta; 48 | % K=mtr(delta,n-r,r); 49 | [N_new, ~] = qr(N + N_perp * reshape(delta, n_r, r), 0); 50 | [e_new, NiQ, NiR] = obj_missing(M, N_new, store); 51 | 52 | if e_new < e 53 | lambda = lambda / k_dec; 54 | break; 55 | else 56 | lambda = lambda * k_inc; 57 | end 58 | end 59 | 60 | if disp_level 61 | fprintf('[%04d] %.2e %.4e\n', iter, lambda, e_new); 62 | end 63 | 64 | if (trial == max_trials) || (abs(e - e_new) < tol * e), break 65 | end 66 | 67 | N = N_new; 68 | e = e_new; 69 | 70 | end 71 | 72 | Ns = N_new; 73 | e = e_new; 74 | 75 | end 76 | 77 | function [b, H, N_perp] = get_b_H(M, N, NiQ, NiR, r, store) 78 | 79 | [m,n]=size(M); 80 | b=zeros(n*r, 1); 81 | H=zeros(n*r); 82 | N_perp=null(N'); 83 | 84 | for i = 1 : m 85 | % if(mod(i,100)==0) 86 | % j=0; 87 | % end 88 | % mm=mask(i,:)'; 89 | % index=find(mm==1); 90 | % n0=length(index); 91 | C = store.C{i}; 92 | index = store.index{i}; 93 | n0 = store.n0{i}; 94 | 95 | % I=eye(n); 96 | % L=N_perp(index,:); 97 | % L=I(index,:); 98 | % D=kron(eye(r),L); 99 | rng = bsxfun(@plus, index - 1, 1:n:n*r); 100 | rng = rng(:); 101 | 102 | mi=M(i,index)'; 103 | % Ni=N(index,:); 104 | % E=inv(Ni'*Ni); 105 | % A=Ni*E; 106 | % P=A*Ni'; 107 | % C=communication(n0,r); 108 | 109 | E = eye(r) / NiR{i}; 110 | % AT = NiR{i} \ NiQ{i}'; 111 | 112 | % temp1=A'*mi; 113 | % temp2 = (eye(n0)-P)*mi; 114 | temp0 = NiQ{i}' * mi; 115 | temp1 = NiR{i} \ temp0; 116 | temp2 = mi - NiQ{i} * temp0; 117 | b(rng) = b(rng) + kron(temp1, temp2); 118 | 119 | % temp = kron(temp1 * temp1', speye(n0) - NiQ * NiQ'); % RW2 120 | % temp = temp + C' * kron(temp2 * temp2', E * E') * C; % RW1 121 | % temp = temp + C' * kron(temp2,eye(r)) * (2 * kron(temp1',AT) - kron(temp2',E) * C); % RW0 122 | % temp = C' * kron(temp2 * temp1', AT); 123 | H(rng, rng) = H(rng, rng) ... 124 | + kron(temp1 * temp1', speye(n0) - NiQ{i} * NiQ{i}'); 125 | end 126 | 127 | PT = kron(speye(r), N_perp'); 128 | b = PT * b; 129 | H = PT * H * PT'; 130 | H = 0.5 * (H + H'); 131 | 132 | end 133 | 134 | function [f, NiQ, NiR] = obj_missing(M, N, store) 135 | 136 | m = size(M, 1); 137 | NiQ = cell(m, 1); 138 | NiR = cell(m, 1); 139 | 140 | f=0; 141 | for i=1:m 142 | % mm=mask(i,:)'; 143 | % index=find(mm==1); 144 | index = store.index{i}; 145 | 146 | mi=M(i,index)'; 147 | % Ni=N(index,:); 148 | [NiQ{i}, NiR{i}] = qr(N(index, :), 0); 149 | 150 | % P=Ni*inv(Ni'*Ni)*Ni'; 151 | % temp=mi-P*mi; 152 | % f=f+mi'*temp; 153 | % b=Ni\mi; 154 | % temp=mi-Ni*b; 155 | temp = mi - NiQ{i} * (NiQ{i}' * mi); 156 | f=f+temp'*temp; 157 | end 158 | 159 | end 160 | -------------------------------------------------------------------------------- /Code/Algorithms/CH/xt_ch_lm_s.m: -------------------------------------------------------------------------------- 1 | function [e, iter, Ns] = xt_ch_lm_s(M, mask, r, N, max_iter, tol, disp_level) 2 | 3 | % LM_S in the paper (Damped RW0) 4 | 5 | if nargin < 5, max_iter = 300; 6 | end 7 | if nargin < 6, tol = 1e-10; 8 | end 9 | if nargin < 7, disp_level = 1; 10 | end 11 | 12 | warning('off', 'MATLAB:nearlySingularMatrix'); % No ambiguity constraint present 13 | 14 | vec = @(X) X(:); 15 | [m,n]=size(M); 16 | nr = n * r; 17 | 18 | % Form cells of repeatedly-used variables. 19 | store.C = cell(m, 1); 20 | store.index = cell(m, 1); 21 | store.n0 = cell(m, 1); 22 | store.rng = cumsum([0; sum(mask, 2)]); 23 | for i = 1 : m 24 | store.index{i} = find(mask(i,:)' == 1); 25 | store.n0{i} = length(store.index{i}); 26 | store.C{i} = sparse(1 : (store.n0{i} * r), vec(reshape(1 : (store.n0{i} * r), store.n0{i}, r)'), 1); 27 | % store.C{i} = communication(store.n0{i}, r); 28 | end 29 | 30 | % converged=0; 31 | % times=0; 32 | [N, ~] = qr(N, 0); 33 | [e, NiQ, NiR] = obj_missing(M, N, store); 34 | lambda=1e-4; 35 | % lambda=1e-6; 36 | % k=10; 37 | k_inc = 10; 38 | k_dec = 100; 39 | max_trials = 150; 40 | 41 | for iter = 1 : max_iter 42 | [b, H] = get_b_H0(M, NiQ, NiR, r, store); 43 | 44 | for trial = 1 : max_trials 45 | delta = (H + lambda * speye(nr)) \ b; 46 | %delta=inv(H+lambda*diag(diag(H)))*b; 47 | 48 | % variation=b'*delta; 49 | % K=mtr(delta,n-r,r); 50 | [N_new, ~] = qr(N + reshape(delta, n, r), 0); 51 | [e_new, NiQ, NiR] = obj_missing(M, N_new, store); 52 | 53 | if e_new < e 54 | lambda = lambda / k_dec; 55 | break; 56 | else 57 | lambda = lambda * k_inc; 58 | end 59 | end 60 | 61 | if disp_level 62 | fprintf('[%04d] %.2e %.4e\n', iter, lambda, e_new); 63 | end 64 | 65 | if (trial == max_trials) || (abs(e - e_new) < tol * e), break 66 | end 67 | 68 | N = N_new; 69 | e = e_new; 70 | 71 | end 72 | 73 | Ns = N_new; 74 | e = e_new; 75 | 76 | end 77 | 78 | function [b, H] = get_b_H0(M, NiQ, NiR, r, store) 79 | 80 | [m,n]=size(M); 81 | b=zeros(n*r, 1); 82 | H=zeros(n*r); 83 | % N_perp=null(N'); 84 | 85 | for i = 1 : m 86 | % if(mod(i,100)==0) 87 | % j=0; 88 | % end 89 | % mm=mask(i,:)'; 90 | % index=find(mm==1); 91 | % n0=length(index); 92 | C = store.C{i}; 93 | index = store.index{i}; 94 | n0 = store.n0{i}; 95 | 96 | % I=eye(n); 97 | % L=N_perp(index,:); 98 | % L=I(index,:); 99 | % D=kron(eye(r),L); 100 | rng = bsxfun(@plus, index - 1, 1:n:n*r); 101 | rng = rng(:); 102 | 103 | mi=M(i,index)'; 104 | % Ni=N(index,:); 105 | % E=inv(Ni'*Ni); 106 | % A=Ni*E; 107 | % P=A*Ni'; 108 | % C=communication(n0,r); 109 | 110 | E = eye(r) / NiR{i}; 111 | AT = NiR{i} \ NiQ{i}'; 112 | 113 | % temp1=A'*mi; 114 | % temp2 = (eye(n0)-P)*mi; 115 | temp0 = NiQ{i}' * mi; 116 | temp1 = NiR{i} \ temp0; 117 | temp2 = mi - NiQ{i} * temp0; 118 | b(rng) = b(rng) + kron(temp1, temp2); 119 | 120 | % temp = kron(temp1 * temp1', speye(n0) - NiQ * NiQ'); % RW2 121 | % temp = temp + C' * kron(temp2 * temp2', E * E') * C; % RW1 122 | % temp = temp + C' * kron(temp2,eye(r)) * (2 * kron(temp1',AT) - kron(temp2',E) * C); % RW0 123 | temp = C' * kron(temp2 * temp1', AT); 124 | H(rng, rng) = H(rng, rng) ... 125 | + kron(temp1 * temp1', speye(n0) - NiQ{i} * NiQ{i}') ... 126 | - C' * kron(temp2 * temp2', E * E') * C ... 127 | + (temp + temp'); 128 | end 129 | 130 | end 131 | 132 | function [f, NiQ, NiR] = obj_missing(M, N, store) 133 | 134 | m = size(M, 1); 135 | NiQ = cell(m, 1); 136 | NiR = cell(m, 1); 137 | 138 | f=0; 139 | for i=1:m 140 | % mm=mask(i,:)'; 141 | % index=find(mm==1); 142 | index = store.index{i}; 143 | 144 | mi=M(i,index)'; 145 | % Ni=N(index,:); 146 | [NiQ{i}, NiR{i}] = qr(N(index, :), 0); 147 | 148 | % P=Ni*inv(Ni'*Ni)*Ni'; 149 | % temp=mi-P*mi; 150 | % f=f+mi'*temp; 151 | % b=Ni\mi; 152 | % temp=mi-Ni*b; 153 | temp = mi - NiQ{i} * (NiQ{i}' * mi); 154 | f=f+temp'*temp; 155 | end 156 | 157 | end 158 | -------------------------------------------------------------------------------- /Code/Algorithms/CH/xt_ch_lm_s_gn.m: -------------------------------------------------------------------------------- 1 | function [e, iter, Ns] = xt_ch_lm_s_gn(M, mask, r, N, max_iter, tol, disp_level) 2 | 3 | % LM_S_GN (Damped RW1) 4 | 5 | if nargin < 5, max_iter = 300; 6 | end 7 | if nargin < 6, tol = 1e-10; 8 | end 9 | if nargin < 7, disp_level = 1; 10 | end 11 | 12 | warning('off', 'MATLAB:nearlySingularMatrix'); % No ambiguity constraint present 13 | 14 | vec = @(X) X(:); 15 | [m,n]=size(M); 16 | nr = n * r; 17 | 18 | % Form cells of repeatedly-used variables. 19 | store.C = cell(m, 1); 20 | store.index = cell(m, 1); 21 | store.n0 = cell(m, 1); 22 | store.rng = cumsum([0; sum(mask, 2)]); 23 | for i = 1 : m 24 | store.index{i} = find(mask(i,:)' == 1); 25 | store.n0{i} = length(store.index{i}); 26 | store.C{i} = sparse(1 : (store.n0{i} * r), vec(reshape(1 : (store.n0{i} * r), store.n0{i}, r)'), 1); 27 | % store.C{i} = communication(store.n0{i}, r); 28 | end 29 | 30 | % converged=0; 31 | % times=0; 32 | [N, ~] = qr(N, 0); 33 | [e, NiQ, NiR] = obj_missing(M, N, store); 34 | lambda=1e-4; 35 | % lambda=1e-6; 36 | % k=10; 37 | k_inc = 10; 38 | k_dec = 100; 39 | max_trials = 150; 40 | 41 | for iter = 1 : max_iter 42 | [b, H] = get_b_H0(M, NiQ, NiR, r, store); 43 | 44 | for trial = 1 : max_trials 45 | delta = (H + lambda * speye(nr)) \ b; 46 | %delta=inv(H+lambda*diag(diag(H)))*b; 47 | 48 | % variation=b'*delta; 49 | % K=mtr(delta,n-r,r); 50 | [N_new, ~] = qr(N + reshape(delta, n, r), 0); 51 | [e_new, NiQ, NiR] = obj_missing(M, N_new, store); 52 | 53 | if e_new < e 54 | lambda = lambda / k_dec; 55 | break; 56 | else 57 | lambda = lambda * k_inc; 58 | end 59 | end 60 | 61 | if disp_level 62 | fprintf('[%04d] %.2e %.4e\n', iter, lambda, e_new); 63 | end 64 | 65 | if (trial == max_trials) || (abs(e - e_new) < tol * e), break 66 | end 67 | 68 | N = N_new; 69 | e = e_new; 70 | 71 | end 72 | 73 | Ns = N_new; 74 | e = e_new; 75 | 76 | end 77 | 78 | function [b, H] = get_b_H0(M, NiQ, NiR, r, store) 79 | 80 | [m,n]=size(M); 81 | b=zeros(n*r, 1); 82 | H=zeros(n*r); 83 | % N_perp=null(N'); 84 | 85 | for i = 1 : m 86 | % if(mod(i,100)==0) 87 | % j=0; 88 | % end 89 | % mm=mask(i,:)'; 90 | % index=find(mm==1); 91 | % n0=length(index); 92 | C = store.C{i}; 93 | index = store.index{i}; 94 | n0 = store.n0{i}; 95 | 96 | % I=eye(n); 97 | % L=N_perp(index,:); 98 | % L=I(index,:); 99 | % D=kron(eye(r),L); 100 | rng = bsxfun(@plus, index - 1, 1:n:n*r); 101 | rng = rng(:); 102 | 103 | mi=M(i,index)'; 104 | % Ni=N(index,:); 105 | % E=inv(Ni'*Ni); 106 | % A=Ni*E; 107 | % P=A*Ni'; 108 | % C=communication(n0,r); 109 | 110 | E = eye(r) / NiR{i}; 111 | % AT = NiR \ NiQ'; 112 | 113 | % temp1=A'*mi; 114 | % temp2 = (eye(n0)-P)*mi; 115 | temp0 = NiQ{i}' * mi; 116 | temp1 = NiR{i} \ temp0; 117 | temp2 = mi - NiQ{i} * temp0; 118 | b(rng) = b(rng) + kron(temp1, temp2); 119 | 120 | % temp = kron(temp1 * temp1', speye(n0) - NiQ * NiQ'); % RW2 121 | % temp = temp + C' * kron(temp2 * temp2', E * E') * C; % RW1 122 | % temp = temp + C' * kron(temp2,eye(r)) * (2 * kron(temp1',AT) - kron(temp2',E) * C); % RW0 123 | H(rng, rng) = H(rng, rng) ... 124 | + kron(temp1 * temp1', speye(n0) - NiQ{i} * NiQ{i}') ... 125 | + C' * kron(temp2 * temp2', E * E') * C; 126 | end 127 | 128 | end 129 | 130 | function [f, NiQ, NiR] = obj_missing(M, N, store) 131 | 132 | m = size(M, 1); 133 | NiQ = cell(m, 1); 134 | NiR = cell(m, 1); 135 | 136 | f=0; 137 | for i=1:m 138 | % mm=mask(i,:)'; 139 | % index=find(mm==1); 140 | index = store.index{i}; 141 | 142 | mi=M(i,index)'; 143 | % Ni=N(index,:); 144 | [NiQ{i}, NiR{i}] = qr(N(index, :), 0); 145 | 146 | % P=Ni*inv(Ni'*Ni)*Ni'; 147 | % temp=mi-P*mi; 148 | % f=f+mi'*temp; 149 | % b=Ni\mi; 150 | % temp=mi-Ni*b; 151 | temp = mi - NiQ{i} * (NiQ{i}' * mi); 152 | f=f+temp'*temp; 153 | end 154 | 155 | end 156 | -------------------------------------------------------------------------------- /Code/Algorithms/CH/xt_ch_lm_s_rw2.m: -------------------------------------------------------------------------------- 1 | function [e, iter, Ns] = xt_ch_lm_s_rw2(M, mask, r, N, max_iter, tol, disp_level) 2 | 3 | % LM_S_RW2 (Damped RW2) 4 | 5 | if nargin < 5, max_iter = 300; 6 | end 7 | if nargin < 6, tol = 1e-10; 8 | end 9 | if nargin < 7, disp_level = 1; 10 | end 11 | 12 | warning('off', 'MATLAB:nearlySingularMatrix'); % No ambiguity constraint present 13 | 14 | vec = @(X) X(:); 15 | [m,n]=size(M); 16 | nr = n * r; 17 | 18 | % Form cells of repeatedly-used variables. 19 | store.C = cell(m, 1); 20 | store.index = cell(m, 1); 21 | store.n0 = cell(m, 1); 22 | store.rng = cumsum([0; sum(mask, 2)]); 23 | for i = 1 : m 24 | store.index{i} = find(mask(i,:)' == 1); 25 | store.n0{i} = length(store.index{i}); 26 | store.C{i} = sparse(1 : (store.n0{i} * r), vec(reshape(1 : (store.n0{i} * r), store.n0{i}, r)'), 1); 27 | % store.C{i} = communication(store.n0{i}, r); 28 | end 29 | 30 | % converged=0; 31 | % times=0; 32 | [N, ~] = qr(N, 0); 33 | [e, NiQ, NiR] = obj_missing(M, N, store); 34 | lambda=1e-4; 35 | % lambda=1e-6; 36 | % k=10; 37 | k_inc = 10; 38 | k_dec = 100; 39 | max_trials = 150; 40 | 41 | for iter = 1 : max_iter 42 | [b, H] = get_b_H0(M, NiQ, NiR, r, store); 43 | 44 | for trial = 1 : max_trials 45 | delta = (H + lambda * speye(nr)) \ b; 46 | %delta=inv(H+lambda*diag(diag(H)))*b; 47 | 48 | % variation=b'*delta; 49 | % K=mtr(delta,n-r,r); 50 | [N_new, ~] = qr(N + reshape(delta, n, r), 0); 51 | [e_new, NiQ, NiR] = obj_missing(M, N_new, store); 52 | 53 | if e_new < e 54 | lambda = lambda / k_dec; 55 | break; 56 | else 57 | lambda = lambda * k_inc; 58 | end 59 | end 60 | 61 | if disp_level 62 | fprintf('[%04d] %.2e %.4e\n', iter, lambda, e_new); 63 | end 64 | 65 | if (trial == max_trials) || (abs(e - e_new) < tol * e), break 66 | end 67 | 68 | N = N_new; 69 | e = e_new; 70 | 71 | end 72 | 73 | Ns = N_new; 74 | e = e_new; 75 | 76 | end 77 | 78 | function [b, H] = get_b_H0(M, NiQ, NiR, r, store) 79 | 80 | [m,n]=size(M); 81 | b=zeros(n*r, 1); 82 | H=zeros(n*r); 83 | % N_perp=null(N'); 84 | 85 | for i = 1 : m 86 | % if(mod(i,100)==0) 87 | % j=0; 88 | % end 89 | % mm=mask(i,:)'; 90 | % index=find(mm==1); 91 | % n0=length(index); 92 | C = store.C{i}; 93 | index = store.index{i}; 94 | n0 = store.n0{i}; 95 | 96 | % I=eye(n); 97 | % L=N_perp(index,:); 98 | % L=I(index,:); 99 | % D=kron(eye(r),L); 100 | rng = bsxfun(@plus, index - 1, 1:n:n*r); 101 | rng = rng(:); 102 | 103 | mi=M(i,index)'; 104 | % Ni=N(index,:); 105 | % E=inv(Ni'*Ni); 106 | % A=Ni*E; 107 | % P=A*Ni'; 108 | % C=communication(n0,r); 109 | 110 | % E = eye(r) / NiR{i}; 111 | % AT = NiR \ NiQ'; 112 | 113 | % temp1=A'*mi; 114 | % temp2 = (eye(n0)-P)*mi; 115 | temp0 = NiQ{i}' * mi; 116 | temp1 = NiR{i} \ temp0; 117 | temp2 = mi - NiQ{i} * temp0; 118 | b(rng) = b(rng) + kron(temp1, temp2); 119 | 120 | % temp = kron(temp1 * temp1', speye(n0) - NiQ * NiQ'); % RW2 121 | % temp = temp + C' * kron(temp2 * temp2', E * E') * C; % RW1 122 | % temp = temp + C' * kron(temp2,eye(r)) * (2 * kron(temp1',AT) - kron(temp2',E) * C); % RW0 123 | H(rng, rng) = H(rng, rng) ... 124 | + kron(temp1 * temp1', speye(n0) - NiQ{i} * NiQ{i}'); 125 | end 126 | 127 | end 128 | 129 | function [f, NiQ, NiR] = obj_missing(M, N, store) 130 | 131 | m = size(M, 1); 132 | NiQ = cell(m, 1); 133 | NiR = cell(m, 1); 134 | 135 | f=0; 136 | for i=1:m 137 | % mm=mask(i,:)'; 138 | % index=find(mm==1); 139 | index = store.index{i}; 140 | 141 | mi=M(i,index)'; 142 | % Ni=N(index,:); 143 | [NiQ{i}, NiR{i}] = qr(N(index, :), 0); 144 | 145 | % P=Ni*inv(Ni'*Ni)*Ni'; 146 | % temp=mi-P*mi; 147 | % f=f+mi'*temp; 148 | % b=Ni\mi; 149 | % temp=mi-Ni*b; 150 | temp = mi - NiQ{i} * (NiQ{i}' * mi); 151 | f=f+temp'*temp; 152 | end 153 | 154 | end 155 | -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_communication.m: -------------------------------------------------------------------------------- 1 | function C = communication(p, q) 2 | % vec(A')=C*vec(A) with A in R^{p,q} 3 | temp=0:p:p*(q-1); 4 | temp=temp'; 5 | index=[]; 6 | for i=1:p 7 | index=[index;temp+i]; 8 | end 9 | C=eye(p*q); 10 | C=C(index,:); 11 | 12 | end -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_lm_m.m: -------------------------------------------------------------------------------- 1 | function [e, no, N] = xt_co_lm_m(M, mask, r, N, max_iter, tol) 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % LM_M in the paper 4 | % M \in R^{m,n} with m>=n. mask with 1 for observed 0 for missing. 5 | % Entries in M can't be 'NaN', should be any real value, 0, or random 6 | % value. r is the rank. N is the initialization. N \in R^{n,r}, i.e., the 7 | % initial subspace of row vectors 8 | % Output the objectives at each iteration. no is the iteration number. 9 | % Ns are the estimated subspaces at each iteration. 10 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 11 | 12 | if nargin < 5, max_iter = 300; 13 | end 14 | if nargin < 6, tol = 1e-10; 15 | end 16 | 17 | %M=randn(20,9);r=3;mask=rand(20,9)>0.3;N=rand(9,3);%N=orth(N); 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | % When used, pls note this part. 20 | % N=randn(9,3);M0=randn(20,3)*N';M=M0+randn(20,9)*0.2;r=3;mask=rand(20,9)>0.3;N=N+0.01*randn(9,3);N=orth(N); 21 | % [m,n]=size(M); 22 | % if(size(N)==[m,n]) 23 | % [u,s,v]=svds(N,r); 24 | % else 25 | % if(size(N)==[n,r]) 26 | % N=orth(N); 27 | % else 28 | % return; 29 | % end 30 | % end 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | 33 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 | %for simulation 35 | % N=randn(9,3);M0=randn(20,3)*N';M=M0+randn(20,9)*0.2;r=3;mask=rand(20,9)>0.3;N=N+0.01*randn(9,3);N=orth(N); 36 | % [m,n]=size(M); 37 | % if(size(N)==[m,n]) 38 | % [u,s,v]=svds(N,r); 39 | % else 40 | % if(size(N)==[n,r]) 41 | % N=orth(N); 42 | % else 43 | % return; 44 | % end 45 | % end 46 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%% 47 | 48 | [m,n]=size(M); 49 | 50 | converged=0; 51 | times=0; 52 | e(1)=xt_co_obj_missing(M,mask,r,N); 53 | e_old=e(1); 54 | lambda=0.001; 55 | % lambda=1e-6; 56 | k=10; 57 | N_old=N; 58 | while converged==0 59 | [b,H,N_com]=xt_co_lm_m_get_diff(M,mask,N_old,r); 60 | 61 | while(1) 62 | delta=inv(H+lambda*eye(size(H,1)))*b; 63 | %delta=inv(H+lambda*diag(diag(H)))*b; 64 | 65 | % variation=b'*delta; 66 | K=xt_co_mtr(delta,n-r,r); 67 | N_new=N_old+N_com*K; 68 | e_new=xt_co_obj_missing(M,mask,r,N_new); 69 | 70 | if(e_new=0&e_old-e_new<1e-10) 82 | if(1-min(d)<1e-10) 83 | converged=1; 84 | end 85 | 86 | % if(mod(times,10)==0) 87 | % tmp=times/10+1; 88 | % Ns(:,:,tmp)=N_new; 89 | % end 90 | 91 | % lambda 92 | % e_old-e_new 93 | e(times+2)=e_new; 94 | N_old=N_new; 95 | e_old=e_new; 96 | times=times+1; 97 | if(times==max_iter) 98 | converged=1; 99 | end 100 | end 101 | %e(1)=times; 102 | no=times; 103 | i=0; 104 | % Ns(:,:,tmp+1)=N_new; 105 | N=N_new; 106 | % times 107 | % e_new 108 | % if(sqrt(min(e)/sum(sum(mask)))<0.0226) 109 | % rms=sqrt(min(e)/sum(sum(mask))); 110 | % save('N','N','rms'); 111 | % end 112 | 113 | end -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_lm_m_get_diff.m: -------------------------------------------------------------------------------- 1 | function [b, H, N_perp] = xt_co_lm_m_get_diff(M, mask, N, r) 2 | %N=randn(9,3);M0=randn(20,3)*N';M=M0+randn(20,9)*0.1;r=3;mask=rand(20,9)>0.3;N=N+0.01*randn(9,3);N=orth(N); 3 | [m,n]=size(M); 4 | b=zeros((n-r)*r,1); 5 | H=zeros((n-r)*r); 6 | % b=zeros(n*r,1); 7 | % H=zeros(n*r); 8 | N_perp=null(N'); 9 | for i=1:m 10 | if(mod(i,100)==0) 11 | j=0; 12 | end 13 | mm=mask(i,:)'; 14 | index=find(mm==1); 15 | n0=length(index); 16 | I=eye(n); 17 | L=N_perp(index,:); 18 | % L=I(index,:); 19 | D=kron(eye(r),L); 20 | 21 | mi=M(i,index)'; 22 | Ni=N(index,:); 23 | E=inv(Ni'*Ni); 24 | A=Ni*E; 25 | P=A*Ni'; 26 | C=xt_co_communication(n0,r); 27 | 28 | temp1=A'*mi; 29 | temp2=(eye(n0)-P)*mi; 30 | b=b-D'*(kron(temp1,temp2)+C'*kron(temp2,temp1)); 31 | 32 | % temp=C'*kron(temp2,eye(r))*(kron(temp2',E)*C-2*kron(temp1',A')); 33 | % temp=temp-kron(temp1,eye(n0))*kron(temp1',eye(n0)-P); 34 | % temp=temp+temp'; 35 | 36 | temp=kron(eye(r),temp2)*(kron(temp2',E)*C-2*kron(temp1',A')); 37 | temp=temp-C'*kron(eye(n0),temp1)*kron(temp1',eye(n0)-P); 38 | temp=temp+temp'; 39 | H=H-D'*temp*D; 40 | end 41 | H=H; 42 | b=-b; 43 | 44 | end -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_lm_m_gn.m: -------------------------------------------------------------------------------- 1 | function [e, no, N] = xt_co_lm_m_gn(M, mask, r, N, max_iter, tol) 2 | 3 | if nargin < 5, max_iter = 300; 4 | end 5 | if nargin < 6, tol = 1e-10; 6 | end 7 | 8 | %M=randn(20,9);r=3;mask=rand(20,9)>0.3;N=rand(9,3);%N=orth(N); 9 | %N=randn(9,3);M0=randn(20,3)*N';M=M0+randn(20,9)*0.2;r=3;mask=rand(20,9)>0.3;N=N+0.01*randn(9,3);N=orth(N); 10 | %[m,n]=size(M); 11 | % if(size(N)==[m,n]) 12 | % [u,s,v]=svds(N,r); 13 | % else 14 | % if(size(N)==[n,r]) 15 | % N=orth(N); 16 | % else 17 | % return; 18 | % end 19 | % end 20 | 21 | [m,n]=size(M); 22 | 23 | converged=0; 24 | times=0; 25 | e(1)=xt_co_obj_missing(M,mask,r,N); 26 | e_old=e(1); 27 | lambda=0.001; 28 | % lambda=1e-6; 29 | k=10; 30 | N_old=N; 31 | while converged==0 32 | % [b,H,N_com]=get_b_H(M,mask,N_old,r); 33 | [b,H,N_com]=xt_co_lm_m_gn_get_diff(M,mask,N_old,r); 34 | while(1) 35 | delta=inv(H+lambda*eye(size(H,1)))*b; 36 | %delta=inv(H+lambda*diag(diag(H)))*b; 37 | 38 | % variation=b'*delta; 39 | K=xt_co_mtr(delta,n-r,r); 40 | N_new=N_old+N_com*K; 41 | e_new=xt_co_obj_missing(M,mask,r,N_new); 42 | 43 | if(e_new=0&e_old-e_new<1e-10) 55 | if(1-min(d)<1e-10) 56 | converged=1; 57 | end 58 | 59 | % if(mod(times,10)==0) 60 | % tmp=times/10+1; 61 | % Ns(:,:,tmp)=N_new; 62 | % end 63 | 64 | % lambda 65 | % e_old-e_new 66 | e(times+2)=e_new; 67 | N_old=N_new; 68 | e_old=e_new; 69 | times=times+1; 70 | if(times==max_iter) 71 | converged=1; 72 | end 73 | end 74 | %e(1)=times; 75 | no=times; 76 | i=0; 77 | % Ns(:,:,tmp+1)=N_new; 78 | N=N_new; 79 | % times 80 | % e_new 81 | % if(sqrt(min(e)/sum(sum(mask)))<0.0226) 82 | % rms=sqrt(min(e)/sum(sum(mask))); 83 | % save('N','N','rms'); 84 | % end 85 | % function u = CLM (X1, Y1, X2, Y2, F0, u0) 86 | % 87 | % iter = 0; 88 | % iter_max = 100; 89 | % c = 0.0001; 90 | % eps = 1.0e-15; 91 | % 92 | % Xi = calcXi (X1, Y1, X2, Y2, F0); 93 | % 94 | % F = reshape (u0, 3, 3); 95 | % F = F'; 96 | % [U, S, V] = svd (F); 97 | % theta = asin (S(2,2) / sqrt (S(1,1) * S(1,1) + S(2,2) * S(2,2))); 98 | % S(1, 1) = cos (theta); 99 | % S(2, 2) = sin (theta); 100 | % S(3, 3) = 0.0; 101 | % F = U * S * V'; 102 | % F_ = eye (3, 3); 103 | % J = calcJ (F, Xi, X1, Y1, X2, Y2, F0); 104 | % 105 | % while (iter < iter_max) 106 | % 107 | % iter = iter + 1; 108 | % 109 | % Fu = calcFu (F); 110 | % Fv = calcFv (F); 111 | % ut = calcUt (U, S, V); 112 | % u = reshape (F', 9, 1); 113 | % 114 | % X = calcX (u, Xi, X1, Y1, X2, Y2, F0); 115 | % 116 | % [H, g] = setHandG (X, Fu, Fv, u, ut); 117 | % DH = diag (diag(H), 0); 118 | % 119 | % while (1) 120 | % param = (H + c * DH) \ (-g); 121 | % omega = param(1:3, 1); 122 | % U_ = createRotationMatrix (omega) * U; 123 | % omega = param(4:6, 1); 124 | % V_ = createRotationMatrix (omega) * V; 125 | % theta_ = theta + param(7); 126 | % S_ = zeros(3, 3); 127 | % S_(1, 1) = cos (theta_); 128 | % S_(2, 2) = sin (theta_); 129 | % F_ = U_ * S_ * V_'; 130 | % J_ = calcJ (F_, Xi, X1, Y1, X2, Y2, F0); 131 | % 132 | % if (J_ < J | abs (J_ - J) < eps) 133 | % J = J_; 134 | % c = c * 0.1; 135 | % break; 136 | % else 137 | % c = c * 10.0; 138 | % end 139 | % end 140 | % 141 | % if (calcDistance (F, F_) < 0.5e-12) 142 | % u = reshape (F_', 9, 1); 143 | % break; 144 | % else 145 | % F = F_; 146 | % U = U_; 147 | % S = S_; 148 | % V = V_; 149 | % theta = theta_; 150 | % end 151 | % end 152 | % endfunction 153 | 154 | end -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_lm_m_gn_get_diff.m: -------------------------------------------------------------------------------- 1 | function [b, H, N_perp]=xt_co_lm_m_gn_get_diff(M, mask, N, r) 2 | %N=randn(9,3);M0=randn(20,3)*N';M=M0+randn(20,9)*0.1;r=3;mask=rand(20,9)>0.3;N=N+0.01*randn(9,3);N=orth(N); 3 | [m,n]=size(M); 4 | b=zeros((n-r)*r,1); 5 | H=zeros((n-r)*r); 6 | % b=zeros(n*r,1); 7 | % H=zeros(n*r); 8 | N_perp=null(N'); 9 | for i=1:m 10 | if(mod(i,100)==0) 11 | j=0; 12 | end 13 | mm=mask(i,:)'; 14 | index=find(mm==1); 15 | n0=length(index); 16 | I=eye(n); 17 | L=N_perp(index,:); 18 | % L=I(index,:); 19 | D=kron(eye(r),L); 20 | 21 | mi=M(i,index)'; 22 | Ni=N(index,:); 23 | E=inv(Ni'*Ni); 24 | A=Ni*E; 25 | P=A*Ni'; 26 | C=xt_co_communication(n0,r); 27 | 28 | temp1=A'*mi; 29 | temp2=(eye(n0)-P)*mi; 30 | 31 | fi=temp2; 32 | Bi=(kron(temp1',eye(n0)-P)+kron(temp2',A)*C)*D; 33 | 34 | H=H+Bi'*Bi; 35 | b=b-Bi'*fi; 36 | 37 | 38 | % b=b-D'*(kron(temp1,temp2)+C'*kron(temp2,temp1)); 39 | % 40 | % % temp=C'*kron(temp2,eye(r))*(kron(temp2',E)*C-2*kron(temp1',A')); 41 | % % temp=temp-kron(temp1,eye(n0))*kron(temp1',eye(n0)-P); 42 | % % temp=temp+temp'; 43 | % 44 | % temp=kron(eye(r),temp2)*(kron(temp2',E)*C-2*kron(temp1',A')); 45 | % temp=temp-C'*kron(eye(n0),temp1)*kron(temp1',eye(n0)-P); 46 | % temp=temp+temp'; 47 | % H=H-D'*temp*D; 48 | end 49 | H=H; 50 | b=-b; 51 | 52 | end -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_lm_s.m: -------------------------------------------------------------------------------- 1 | function [e, no, N]=xt_co_lm_s(M, mask, r, N, max_iter, tol) 2 | 3 | %M=randn(20,9);r=3;mask=rand(20,9)>0.3;N=rand(9,3);%N=orth(N); 4 | 5 | % LM_S in the paper 6 | 7 | if nargin < 5, max_iter = 300; 8 | end 9 | if nargin < 6, tol = 1e-10; 10 | end 11 | 12 | % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | % %for simulation 14 | % N=randn(9,3);M0=randn(20,3)*N';M=M0+randn(20,9)*0.2;r=3;mask=rand(20,9)>0.3;N=N+0.01*randn(9,3);N=orth(N); 15 | % [m,n]=size(M); 16 | % if(size(N)==[m,n]) 17 | % [u,s,v]=svds(N,r); 18 | % else 19 | % if(size(N)==[n,r]) 20 | % N=orth(N); 21 | % else 22 | % return; 23 | % end 24 | % end 25 | % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | 27 | [m,n]=size(M); 28 | 29 | converged=0; 30 | times=0; 31 | e(1)=xt_co_obj_missing(M,mask,r,N); 32 | e_old=e(1); 33 | lambda=0.001; 34 | % lambda=1e-6; 35 | k=10; 36 | N_old=N; 37 | while converged==0 38 | [b,H]=xt_co_lm_s_get_diff(M,mask,N_old,r); 39 | 40 | while(1) 41 | delta=inv(H+lambda*eye(size(H,1)))*b; 42 | %delta=inv(H+lambda*diag(diag(H)))*b; 43 | %delta=inv(H'*H)*H'*b; 44 | 45 | % variation=b'*delta; 46 | K=xt_co_mtr(delta,n,r); 47 | N_new=N_old+K; 48 | e_new=xt_co_obj_missing(M,mask,r,N_new); 49 | 50 | if(e_new0.3;N=N+0.01*randn(9,3);N=orth(N); 3 | [m,n]=size(M); 4 | % b=zeros((n-r)*r,1); 5 | % H=zeros((n-r)*r); 6 | b=zeros(n*r,1); 7 | H=zeros(n*r); 8 | % N_perp=null(N'); 9 | for i=1:m 10 | if(mod(i,100)==0) 11 | j=0; 12 | end 13 | mm=mask(i,:)'; 14 | index=find(mm==1); 15 | n0=length(index); 16 | I=eye(n); 17 | % L=N_perp(index,:); 18 | L=I(index,:); 19 | D=kron(eye(r),L); 20 | 21 | mi=M(i,index)'; 22 | Ni=N(index,:); 23 | E=inv(Ni'*Ni); 24 | A=Ni*E; 25 | P=A*Ni'; 26 | C=xt_co_communication(n0,r); 27 | 28 | temp1=A'*mi; 29 | temp2=(eye(n0)-P)*mi; 30 | b=b-D'*(kron(temp1,temp2)+C'*kron(temp2,temp1)); 31 | 32 | temp=C'*kron(temp2,eye(r))*(kron(temp2',E)*C-2*kron(temp1',A')); 33 | temp=temp-kron(temp1,eye(n0))*kron(temp1',eye(n0)-P); 34 | temp=temp+temp'; 35 | H=H-D'*temp*D; 36 | end 37 | H=H; 38 | b=-b; 39 | 40 | end -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_mtr.m: -------------------------------------------------------------------------------- 1 | function M = xt_co_mtr(v, m, n) 2 | M=[]; 3 | [k,l]=size(v); 4 | if(k==1) 5 | v=v'; 6 | end 7 | if(length(v)==m*n) 8 | for i=1:n 9 | M=[M,v(1+m*(i-1):m*i)]; 10 | end 11 | end -------------------------------------------------------------------------------- /Code/Algorithms/CO/xt_co_obj_missing.m: -------------------------------------------------------------------------------- 1 | function f = xt_co_obj_missing(M, mask, r, N) 2 | 3 | %N=randn(9,3);M0=randn(20,3)*N';M=M0+randn(20,9)*0.1;r=3;mask=rand(20,9)>0.3;N=N+0.01*randn(9,3);N=orth(N); 4 | [m,n]=size(M); 5 | 6 | f=0; 7 | for i=1:m 8 | mm=mask(i,:)'; 9 | index=find(mm==1); 10 | 11 | mi=M(i,index)'; 12 | Ni=N(index,:); 13 | 14 | % P=Ni*inv(Ni'*Ni)*Ni'; 15 | % temp=mi-P*mi; 16 | % f=f+mi'*temp; 17 | b=Ni\mi; 18 | temp=mi-Ni*b; 19 | f=f+temp'*temp; 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /Code/Algorithms/DB/xt_db_balm.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter] = xt_db_balm(M, W, r, U, V, varargin) 2 | %UNTITLED4 Summary of this function goes here 3 | % Detailed explanation goes here 4 | 5 | %% PRE-PROCESSING 6 | % Fetch all the options. 7 | opts = au_opts( ... 8 | 'nu=0', ... % regularization parameter 9 | 'max_iter=300', ... % maximum number of iterations 10 | 'max_trials=50', ... % maximum number of trials 11 | 'tol=1e-10', ... % function tolerance 12 | 'display=1', ... % whether to display progress 13 | 'alg=0', ... % which algorithm to use 14 | 'r_init=0', ... % initial rank (for rank continuation-based approaches) 15 | varargin{:} ); 16 | 17 | [m, n] = size(M); 18 | 19 | cost_best = inf; 20 | error_best = inf; 21 | M(W==0) = 0; 22 | gamma = 5; 23 | tau = 0.50; 24 | rho = 50; 25 | Lambda = zeros(m, r); 26 | 27 | %% Initialization 28 | Mz = U * V'; 29 | Mz(W~=0) = M(W~=0); 30 | 31 | %% Optimization 32 | % Outer loop 33 | for iter = 1 : opts.max_iter 34 | 35 | % inner Gauss-Seidel iteration 36 | for trial = 1 : opts.max_trials 37 | % Step 1 : Update U_hat. 38 | U_hat = U - Lambda / rho; 39 | [U_hat, ~] = qr(U_hat, 0); 40 | 41 | % Step 2 : Update Q using Manopt. 42 | % G = [ sqrt(rho / 2) * Mz' ; (U_hat + Lambda / rho)' ]; 43 | % 44 | % % Generate the problem data. 45 | % GTG = G' * G; 46 | % 47 | % % Create the problem structure. 48 | % % manifold = spherefactory(m); 49 | % manifold = stiefelfactory(m, r); 50 | % problem.M = manifold; 51 | % 52 | % % Define the problem cost function and its Euclidean gradient. 53 | % problem.cost = @(Q) - trace(Q' * (GTG * Q)); 54 | % problem.egrad = @(Q) -2 * GTG * Q; 55 | % 56 | % % Numerically check gradient consistency (optional). 57 | % % checkgradient(problem); 58 | % 59 | % % Solve. 60 | % tropts.verbosity = 0; 61 | % [Q, Qcost, info, options] = trustregions(problem, [], tropts); 62 | 63 | % Step 2 : Update Q using SVD. 64 | G = [ sqrt(rho / 2) * Mz' ; (U_hat + Lambda / rho)' ]; 65 | [~, ~, Q] = svd(G, 0); 66 | Q = Q(:, 1 : r); 67 | 68 | % Step 2 : Update U & V. 69 | S = G * Q; 70 | A = S(n+1 : end, :)'; 71 | U = Q * A; 72 | V = sqrt(2 / rho) * S(1 : n, :) / A'; 73 | 74 | % Step 3 : Update Z. 75 | Mz = U * V'; 76 | Mz(W~=0) = M(W~=0); 77 | end 78 | 79 | error = norm(U - U_hat, 'fro') ^ 2; 80 | 81 | % PRIMAL-DUAL UPDATE 82 | if error <= tau * error_best 83 | % If constraint is pretty good, update the dual parameters 84 | Lambda = Lambda - rho * (U - U_hat); 85 | % rho = rho; 86 | error_best = error; 87 | else 88 | % Otherwise, increase the penalty proportion. 89 | rho = min(gamma * rho, 1e+20); 90 | end 91 | 92 | % STOPPING CRITERIA 93 | cost = sqrt(norm(W .* (M - U*V'), 'fro')^2 / sum(W(:)~=0)); 94 | % disp(k); disp(cost); 95 | if opts.display, fprintf('[%03d] %0.6d\n', iter, cost); 96 | end 97 | if abs(cost - cost_best) < 1e-9, break 98 | else cost_best = cost; 99 | end 100 | 101 | end 102 | 103 | end -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_add_hessian_layer.cpp: -------------------------------------------------------------------------------- 1 | #include "mex.h" 2 | #include 3 | 4 | /* JE_ADD_HESSIAN_LAYER 5 | * Adds a layer of triangular components of the Hessian matrix. 6 | * 7 | */ 8 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { 9 | 10 | /* 11 | * mat_out: trihess 12 | * mat_in1 = AtQ2{j} or R2_j * R2_j' 13 | * mat_in2 = B(j, :)' * B(j, :) or AtR2inv{j} 14 | */ 15 | double *mat_out, *mat_in1, *mat_in2; 16 | double A_ij; 17 | mwSize mr, p_j, r, mrr, hidx[3], kidx; 18 | uint32_t *rng; 19 | 20 | if (nlhs > 0) { 21 | mexErrMsgTxt("This function does not create any output."); 22 | } 23 | 24 | if (nrhs != 4) { 25 | mexErrMsgTxt("This function requires 4 inputs."); 26 | } 27 | 28 | // Pointers to the input variables. 29 | mat_out = mxGetPr(prhs[0]); 30 | mat_in1 = mxGetPr(prhs[1]); 31 | mat_in2 = mxGetPr(prhs[2]); 32 | rng = (unsigned int*) mxGetData(prhs[3]); 33 | 34 | // Get rank and nnz. 35 | mr = mxGetM(prhs[0]); 36 | r = mxGetM(prhs[2]); 37 | mrr = mr * r; 38 | p_j = mxGetM(prhs[3]); 39 | 40 | // Compute the triangular elements. 41 | for (mwSize j = 0; j < p_j; j++) { 42 | hidx[0] = mrr * rng[j]; 43 | for (mwSize i = 0; i <= j; i++) { 44 | A_ij = mat_in1[i * p_j + j]; 45 | hidx[1] = hidx[0] + r * rng[i]; 46 | for (mwSize l = 0; l < r; l++) { 47 | hidx[2] = hidx[1] + l * mr; 48 | kidx = l * r; 49 | for (mwSize k =0; k <= l; k++) { 50 | mat_out[hidx[2] + k] += A_ij * mat_in2[kidx + k]; 51 | } 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_alternation.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter, store] = je_alternation(M, W, r, U, store, varargin) 2 | % JE_ALTERNATION 3 | % An implementation of the alternation algorithm optimized for medium-sized matrices. 4 | % Mainly exploits the structure of the matrices and repetitions in arithmetics. 5 | 6 | %% PRE-PROCESSING 7 | 8 | % Fetch all the options. 9 | opts = au_opts( ... 10 | 'retraction=1', ... % q factor-based manifold retraction 11 | 'nu=0', ... % regularization parameter 12 | 'max_iter=300', ... % maximum iteration 13 | 'tol=1e-10', ... % function tolerance 14 | 'use_qr=1', ... % whether to use QR decomposition 15 | 'use_mp_inv=0', ... % whether we are going to build explicit Moore-Penrose inverse matrices 16 | 'use_indicator_weight=1', ... % whether we are going to do 0.5 ALS only 17 | 'use_layerwise_computation=0', ... % whether to compute Hessian by summing n layers or by using sparse matrix computation. 18 | 'search_unique_weights=1', ... % whether to search for unique weight columns and rows. 19 | 'init_v_only=0', ... % whether we are going to do 0.5 ALS only 20 | 'display=1', ... % whether to display progress 21 | varargin{:} ); 22 | 23 | % If store variable has not been passed, pre-process the required variables. 24 | if nargin < 5 || ~isstruct(store) 25 | [opts, store] = je_preprocess_values(M, W, r, opts); 26 | end 27 | 28 | % Fetch U if it has not been generated. 29 | if size(U, 1) == 1 && size(U, 2) == 1 30 | U = je_generate_sample(U, store.dim.m, store.dim.n, r); 31 | end 32 | 33 | % If manifold retraction is enabled, take the q-factor of U. 34 | if opts.retraction, [U, ~] = qr(U, 0); 35 | end 36 | % toc 37 | 38 | % Get the initial estimate of V. 39 | V = je_solve_inner_problem(U, opts, store.wc); 40 | % Compute the cost: this will be replaced with a more efficient code later on. 41 | [~, cost] = je_compute_error_matrix(M, W, U, V, store.dim.nnz); 42 | iter = 0; 43 | 44 | % If display is on, show the cost. 45 | if opts.display, fprintf('[%05d]\t%.6f\n', iter, cost); 46 | end 47 | 48 | if opts.init_v_only, return 49 | end 50 | 51 | %% ALTERNATION 52 | for iter = 1:opts.max_iter 53 | 54 | U = je_solve_inner_problem(V, opts, store.wr); 55 | % If manifold retraction is enabled, take the q-factor of U. 56 | if opts.retraction, [U, ~] = qr(U, 0); 57 | end 58 | V = je_solve_inner_problem(U, opts, store.wc); 59 | 60 | % Compute the cost: this will be replaced with a more efficient code later on. 61 | [~, cost_eval] = je_compute_error_matrix(M, W, U, V, store.dim.nnz); 62 | % cost_eval = sqrt(norm(je_compute_errfunc(U, V, store.wc, opts)) ^ 2 / store.dim.nnz); 63 | 64 | % If display is on, show the cost. 65 | if opts.display, fprintf('[%05d]\t%.6f\n', iter, cost_eval); 66 | end 67 | 68 | % Stop if the convergence criteria is satisfied. 69 | if abs(cost - cost_eval) < cost * opts.tol 70 | break 71 | else cost = cost_eval; 72 | end 73 | end 74 | 75 | end 76 | 77 | -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_compute_error_matrix.m: -------------------------------------------------------------------------------- 1 | function [R, cost] = je_compute_error_matrix(M, W, A, B, nnz) 2 | %JE_COMPUTE_ERRFUNC 3 | % Compute the error function such that: 4 | % ||f||^2 = || W .* (A * B' - M) ||^2_F 5 | 6 | if nargin < 5, nnz = sum(W(:) ~= 0); 7 | end 8 | 9 | %% ERROR MATRIX 10 | R = W .* (A * B' - M); 11 | 12 | if nargout > 1, cost = sqrt(norm(R, 'fro')^2 / nnz); 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_compute_unreg_wiberg_hessian.m: -------------------------------------------------------------------------------- 1 | function hess = je_compute_unreg_wiberg_hessian(A, AtQ, AtR, B, R, r, opts, store) 2 | % JE_COMPUTE_UNREG_WIBERG_HESSIAN 3 | % Compute Hessian of unregularized Wiberg-varian algorithm. 4 | 5 | %% HESSIAN FORMING 6 | % Sum over the layers. 7 | % Hess = zeros(store.dim.mr); 8 | 9 | %% ALGORITHM III GAUSS-NEWTON MATRIX (Ruhe and Wedin '80) 10 | % Blockwise computation - same as ALS. 11 | % tic 12 | VtT_Vt = cell(store.wr.unique.count, 1); 13 | if opts.use_indicator_weight 14 | for j = 1 : store.wr.unique.count 15 | VtT_Vt{j} = B(store.wr.unique.nz{j}, :); 16 | VtT_Vt{j} = sparse(VtT_Vt{j}' * VtT_Vt{j}); 17 | end 18 | else 19 | for j = 1 : store.wr.unique.count 20 | VtT_Vt{j} = bsxfun(@times, store.wr.unique.wnz{j}, B(store.wr.unique.nz{j}, :)); 21 | VtT_Vt{j} = sparse(VtT_Vt{j}' * VtT_Vt{j}); 22 | end 23 | end 24 | 25 | hess = blkdiag(VtT_Vt{store.wr.wid}); 26 | % toc 27 | 28 | %% ALGORITHM II GAUSS-NEWTON MATRIX (Ruhe and Wedin '80) 29 | % Slow for large dataset. 30 | % tic 31 | if opts.alg_type < 3 32 | % Add n Hessian layers of size mr x mr. 33 | hess = full(hess); 34 | % tic 35 | if opts.use_indicator_weight 36 | for j = 1 : store.wc.unique.count 37 | B_j = B(store.wc.unique.ac{j}, :); 38 | je_add_hessian_layer(hess, AtQ{j} * AtQ{j}', - (B_j' * B_j), uint32(store.wc.unique.nz{j} - 1)); 39 | end 40 | else 41 | for j = 1 : store.wc.unique.count 42 | B_j = bsxfun(@times, store.wc.unique.wnz{j}, B_j); 43 | je_add_hessian_layer(hess, AtQ{j} * AtQ{j}', - (B_j' * B_j), uint32(store.wc.unique.nz{j} - 1)); 44 | end 45 | end 46 | % toc 47 | 48 | %% ALGORITHM I GAUSS-NEWTON MATRIX 49 | % tic 50 | if opts.alg_type < 2 51 | % Add n Hessian layers of size mr x mr. 52 | if opts.use_indicator_weight 53 | for j = 1 : store.wc.unique.count 54 | R_j = R(store.wc.unique.nz{j}, store.wc.unique.ac{j}); 55 | AtRinv_j = eye(r) / AtR{j}; 56 | je_add_hessian_layer(hess, R_j * R_j', AtRinv_j * AtRinv_j', uint32(store.wc.unique.nz{j} - 1)); 57 | end 58 | else 59 | for j = 1 : store.wc.unique.count 60 | R_j = bsxfun(@times, store.wc.unique.wnz{j}, R_j); 61 | AtRinv_j = eye(r) / AtR{j}; 62 | je_add_hessian_layer(hess, R_j * R_j', AtRinv_j * AtRinv_j', uint32(store.wc.unique.nz{j} - 1)); 63 | end 64 | end 65 | end 66 | end 67 | % toc 68 | 69 | % Form the full matrix from its triangular components. If using the 70 | % projection constraint, add it to Hessian. 71 | if strcmpi(opts.constraint, 'projection') 72 | je_form_hessian(hess, r, A * A'); 73 | else je_form_hessian(hess, r); 74 | end 75 | % hess = hess + K * kron(speye(r), A * A') * K'; 76 | % else 77 | % % If not using layer-wise computation, use sparse matrix % 78 | % multiplication. This is the fastest option if you have enough memory. 79 | % AtQ2 = cell(store.wc.unique.count, 1); for j = 1 : 80 | % store.wc.unique.count 81 | % AtQ2{j} = sparse(AtQ{j} * AtQ{j}'); 82 | % end % Compute the GN matrix. AtQ22 = blkdiag(AtQ2{store.wc.wid}); if 83 | % opts.use_indicator_weight 84 | % BtT = au_sparse(int32(store.vt.irng), int32(store.vt.jrng), 85 | % B(store.vt.v, :)'); hess = hess - BtT * AtQ22 * BtT'; 86 | % end 87 | end -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_form_hessian.cpp: -------------------------------------------------------------------------------- 1 | #include "mex.h" 2 | 3 | /* JE_FORM_HESSIAN 4 | * Adds a layer of triangular components of the Hessian matrix. 5 | * 6 | */ 7 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { 8 | 9 | /* 10 | * Input 1: trihess 11 | * Input 2: r 12 | */ 13 | double *hess, *rankfill; 14 | mwSize m, r, mr, mrr, hidx[6]; 15 | mwSize i, j, k, l; 16 | mwSize *rng; 17 | 18 | if (nlhs > 0) { 19 | mexErrMsgTxt("This function does not create any output."); 20 | } 21 | 22 | if (nrhs < 2) { 23 | mexErrMsgTxt("This function requires at least 2 inputs."); 24 | } else if (nrhs > 3) { 25 | mexErrMsgTxt("This function requires at most 3 inputs."); 26 | } 27 | 28 | // Pointers to the input variables. 29 | hess = mxGetPr(prhs[0]); 30 | 31 | // Get rank and nnz. 32 | mr = mxGetM(prhs[0]); 33 | r = mxGetScalar(prhs[1]); 34 | m = mr / r; 35 | mrr = mr * r; 36 | 37 | // If Okatani's constraint is added, add it to current Hessian. 38 | if (nrhs == 3) { 39 | rankfill = mxGetPr(prhs[2]); 40 | for (j = 0; j < m; j++) { 41 | hidx[0] = j * mrr; 42 | hidx[1] = j * m; 43 | for (i = 0; i <= j; i++) { 44 | hidx[2] = hidx[0] + i * r; 45 | hidx[3] = hidx[1] + i; 46 | for (k = 0; k < r; k++) { 47 | hess[hidx[2] + k * mr + k] += rankfill[hidx[3]]; 48 | } 49 | } 50 | } 51 | } 52 | 53 | // Fill the lower triangular components of the upper triangluar Hessian blocks. 54 | for (i = 0; i < m; i++) { 55 | hidx[0] = i * mrr; 56 | for (j = 0; j <= i; j++) { 57 | hidx[1] = hidx[0] + j * r; 58 | if (hess[hidx[1] + mr] == 0) { 59 | continue; 60 | } else { 61 | for (k = 1; k < r; k++) { 62 | hidx[2] = hidx[1] + k; 63 | hidx[3] = hidx[1] + k * mr; 64 | for (l = 0; l < k; l++) { 65 | hess[hidx[2] + l * mr] = hess[hidx[3] + l]; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | // Fill the lower tringular Hessian blocks. 73 | for (i = 1; i < m; i++) { 74 | hidx[0] = i * r; 75 | hidx[1] = i * mrr; 76 | for (j = 0; j < i; j++) { 77 | hidx[2] = hidx[0] + j * mrr; 78 | hidx[3] = hidx[1] + j * r; 79 | if (hess[hidx[3] + mr] == 0) { 80 | if (nrhs == 2) { 81 | continue; 82 | } else { 83 | for (k = 0; k < r; k++) { 84 | hess[hidx[2] + k * mr + k] = hess[hidx[3] + k * mr + k]; 85 | } 86 | } 87 | } else { 88 | for (k = 0; k < r; k++) { 89 | hidx[4] = hidx[2] + k; 90 | hidx[5] = hidx[3] + k * mr; 91 | for (l = 0; l < r; l++) { 92 | // hess[i * r + j * mrr + l * mr + k] = hess[i * mrr + j * r + k * mr + l]; 93 | hess[hidx[4] + l * mr] = hess[hidx[5] + l]; 94 | } 95 | } 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_generate_sample.m: -------------------------------------------------------------------------------- 1 | function [U, V] = je_generate_sample(sample_id, m, n, r, base) 2 | % JE_GENERATE_SAMPLE 3 | % Generate a set of samples with input dimension. 4 | % 5 | 6 | if nargin < 5, base = 1348032014; 7 | end 8 | 9 | width = 10; 10 | 11 | rng(base + sample_id * width, 'twister'); 12 | 13 | U = randn(m, r); 14 | 15 | if nargout > 1, V = randn(n, r); 16 | end 17 | 18 | end 19 | 20 | -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_preprocess_values.m: -------------------------------------------------------------------------------- 1 | function [opts, store] = je_preprocess_values(M, W, r, opts) 2 | % JE_PREPROCESS_VALUES 3 | % Preprocess variables that are required for LRMC-solving algorithms. 4 | % The processed variables are as follows: 5 | % - m, n, r 6 | % - wid 7 | % - nnz 8 | % - wnz 9 | 10 | %% CUSTOM FUNCTIONS 11 | vec = @(X) X(:); 12 | 13 | %% DATASET DIMENSION 14 | % Obtain the size of the dataset. 15 | [store.dim.m, store.dim.n] = size(W); 16 | store.dim.mr = store.dim.m * r; 17 | store.dim.mn = store.dim.m * store.dim.n; 18 | 19 | %% WEIGHT MATRIX TYPE AND RANGES 20 | % Vectorize the weight and the measurement values. 21 | [w_vec, ~, ws] = find(W(:)); 22 | store.dim.nnz = size(w_vec, 1); 23 | 24 | % Store the ranges column-wise (and row-wise if necessary). 25 | store.wc.width = store.dim.n; 26 | store.wc.rng = cumsum([1 sum(W ~= 0, 1)]'); 27 | if ~opts.init_v_only 28 | store.wr.width = store.dim.m; 29 | store.wr.rng = cumsum([1; sum(W ~= 0, 2)]); 30 | end 31 | % Set the indicator weight flag to 1. 32 | if sum(ws ~= 1) == 0, opts.use_indicator_weight = 1; 33 | end 34 | 35 | %% WEIGHTED MEASUREMENT 36 | % Compute m-tilde (Wt * vec(M)). 37 | store.wc.mt = M(w_vec); 38 | if issparse(store.wc.mt), store.wc.mt = full(store.wc.mt); 39 | end 40 | if opts.use_indicator_weight, store.wc.mt = store.wc.mt .* ws; 41 | end 42 | 43 | %% ROW-WISE WEIGHT INDICES 44 | if ~opts.init_v_only 45 | % Obtain the column-to-row indices and save m-tilde in row-major format as well. 46 | mnt_vec = 1 : store.dim.mn; 47 | mnt_vec = ceil(mnt_vec / store.dim.m) + store.dim.n * (mod((mnt_vec - 1), store.dim.m)); 48 | [~, store.crid] = sort(mnt_vec(w_vec)); 49 | store.wr.mt = store.wc.mt(store.crid); 50 | 51 | if opts.search_unique_weights 52 | % Obtain the corresponding id of each weight row to avoid repetitions in forming Ut_Q. 53 | % May need a better method for Netflix. (8~9 sec for AIT8050) 54 | [W2, ~, store.wr.wid] = unique(W, 'rows'); 55 | 56 | % Form a vector of weights in ascending order of columns. 57 | store.wr.unique.count = size(W2, 1); % Store the number of unique weight columns. 58 | [w_vec, ~, ws] = find(W2'); 59 | store.wr.unique.rng = cumsum([1; sum(W2 ~= 0, 2)]); 60 | else 61 | store.wr.wid = 1 : store.dim.m; 62 | 63 | % Form a vector of weights in ascending order of columns. 64 | store.wr.unique.count = store.dim.m; % Store the number of unique weight columns. 65 | [w_vec, ~, ws] = find(W'); 66 | store.wr.unique.rng = store.wr.rng; 67 | end 68 | 69 | % Make a list of nz for each weight column. 70 | store.wr.unique.nz = cell(store.wr.unique.count, 1); 71 | store.wr.unique.nnz = zeros(store.wr.unique.count, 1); 72 | for j = 1 : store.wr.unique.count 73 | store.wr.unique.nz{j} = w_vec(store.wr.unique.rng(j) : store.wr.unique.rng(j+1) - 1); 74 | store.wr.unique.nnz(j) = size(store.wr.unique.nz{j}, 1); 75 | end 76 | 77 | % Also store the individual weight values if a skyline-type weight matrix is used. 78 | if ~opts.use_indicator_weight 79 | store.wr.unique.wnz = cell(store.wr.unique.count, 1); 80 | for j = 1 : store.wr.unique.count 81 | store.wr.unique.wnz{j} = ws(store.wr.unique.rng(j) : store.wr.unique.rng(j+1) - 1); 82 | end 83 | else 84 | end 85 | end 86 | 87 | %% COLUMN-WISE WEIGHT INDICES 88 | % Obtain the corresponding id of each weight column to avoid repetitions in forming Ut_Q. 89 | % May need a better method for Netflix. (8~9 sec for AIT8050) 90 | if opts.search_unique_weights 91 | [W2, ~, store.wc.wid] = unique(W', 'rows'); 92 | 93 | % Form a vector of weights in ascending order of columns. 94 | store.wc.unique.count = size(W2, 1); % Store the number of unique weight columns. 95 | [w_vec, ~, ws] = find(W2'); 96 | store.wc.unique.rng = cumsum([1; sum(W2 ~= 0, 2)]); 97 | 98 | else 99 | store.wc.wid = 1 : store.dim.n; 100 | 101 | % Form a vector of weights in ascending order of columns. 102 | store.wc.unique.count = store.dim.n; % Store the number of unique weight columns. 103 | [w_vec, ~, ws] = find(W); 104 | store.wc.unique.rng = store.wc.rng; 105 | end 106 | 107 | % Store the assigned columns for each unique weight column. 108 | % This is required for layer-wise computation. 109 | if opts.use_layerwise_computation 110 | store.wc.unique.ac = cell(store.wc.unique.count, 1); 111 | for j = 1 : store.wc.unique.count 112 | store.wc.unique.ac{j} = find(store.wc.wid == j); 113 | end 114 | end 115 | 116 | % Make a list of nz for each weight column. 117 | store.wc.unique.nz = cell(store.wc.unique.count, 1); 118 | for j = 1 : store.wc.unique.count 119 | store.wc.unique.nz{j} = w_vec(store.wc.unique.rng(j) : store.wc.unique.rng(j+1) - 1); 120 | end 121 | if ~opts.init_v_only, 122 | store.wc.unique.nnz = zeros(store.wr.unique.count, 1); 123 | for j = 1 : store.wc.unique.count 124 | store.wc.unique.nnz(j) = size(store.wc.unique.nz{j}, 1); 125 | end 126 | end 127 | 128 | % Also store the individual weight values if a skyline-type weight matrix is used. 129 | if ~opts.use_indicator_weight 130 | store.wc.unique.wnz = cell(store.wc.unique.count, 1); 131 | for j = 1 : store.wc.unique.count 132 | store.wc.unique.wnz{j} = ws(store.wc.unique.rng(j) : store.wc.unique.rng(j+1) - 1); 133 | end 134 | else 135 | end 136 | 137 | %% Vt indices 138 | % Permutated Vt version 2 139 | if ~opts.use_layerwise_computation 140 | % If not using the layer-wise computation option store the entire ranges for 141 | % generating sparse matrices. 142 | store.vt.irng = bsxfun(@plus, (vertcat(store.wc.unique.nz{store.wc.wid}) - 1) * r, 1:r)'; 143 | store.vt.jrng = bsxfun(@times, 1 : store.dim.nnz, ones(1,r)'); 144 | for i = 1 : store.wc.width 145 | store.vt.v(store.wc.rng(i) : (store.wc.rng(i + 1) - 1)) = i; 146 | end 147 | else 148 | % Otherwise, store in blocks. 149 | store.vt.irng = cell(store.wc.unique.count, 1); 150 | store.vt.jrng = cell(store.wc.unique.count, 1); 151 | for j = 1 : store.wc.unique.count 152 | store.vt.irng{j} = [vec(bsxfun(@plus, (store.wc.unique.nz{j} - 1) * r, 1:r)'); store.dim.mr]; 153 | store.vt.jrng{j} = [vec(bsxfun(@times, 1 : store.wc.unique.nnz(j), ones(1,r)')); store.wc.unique.nnz(j)]; 154 | end 155 | end 156 | end 157 | 158 | -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_read_binary_matrix.m: -------------------------------------------------------------------------------- 1 | function X = je_read_binary_matrix(filename, nRows, nCols, precision) 2 | % JE_READ_BINARY_MATRIX 3 | % Read CSC binary matrix as matrix with specified precision. (default 4 | % precision: double) 5 | 6 | if nargin < 4, precision = 'double'; 7 | end 8 | 9 | fid = fopen(filename, 'r'); 10 | X = fread(fid, [nRows nCols], precision); 11 | fclose(fid); 12 | 13 | end 14 | 15 | -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_solve_inner_problem.m: -------------------------------------------------------------------------------- 1 | function [X, AtQ, AtR] = je_solve_inner_problem(U, opts, substore) 2 | % JE_SOLVE_LINEAR_LSQ 3 | % Solve the inner linear least squares problem. 4 | 5 | % If the regularization parameter is 0, use the unregularized LLSQ solver. 6 | if opts.nu == 0 7 | if opts.use_qr 8 | [X, AtQ, AtR] = je_solve_unreg_linear_lsq_qr(U, opts, substore); 9 | else 10 | [X, AtQ] = je_solve_unreg_linear_lsq(U, opts, substore); 11 | end 12 | else 13 | [X, AtR] = je_solve_reg_linear_lsq(U, opts, substore); 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_solve_reg_linear_lsq.m: -------------------------------------------------------------------------------- 1 | function [X, At, AtR] = je_solve_reg_linear_lsq(A, opts, substore) 2 | % JE_SOLVE_REG_LINEAR_LSQ 3 | % Solve an unregularized linear least squares problem of the form: || Wt * (I kron A) * x - substore.mt ||^2_2 4 | 5 | %% CHOLKESY DECOMPOSITION 6 | r = size(A, 2); 7 | At = cell(substore.unique.count, 1); 8 | AtR = cell(substore.unique.count, 1); 9 | if opts.use_indicator_weight 10 | for j = 1 : substore.unique.count 11 | At{j} = A(substore.unique.nz{j}, :); 12 | AtR{j} = At{j}' * At{j} + opts.nu * speye(r); 13 | end 14 | else 15 | for j = 1 : substore.unique.count 16 | At{j} = bsxfun(@times, substore.unique.wnz{j}, A(substore.unique.nz{j}, :)); 17 | AtR{j} = At{j}' * At{j} + opts.nu * speye(r); 18 | end 19 | end 20 | 21 | if opts.use_mp_inv 22 | AtP = cell(substore.unique.count, 1); 23 | for j = 1 : substore.unique.count 24 | AtP{j} = AtR{j} \ At{j}'; 25 | end 26 | end 27 | 28 | %% NORMAL EQUATION 29 | X = zeros(size(A, 2), substore.width); 30 | 31 | if ~opts.use_mp_inv 32 | for i = 1 : substore.width 33 | j = substore.wid(i); 34 | X(:, i) = AtR{j} \ (At{j}' * substore.mt(substore.rng(i) : substore.rng(i + 1) - 1)); 35 | end 36 | else 37 | for i = 1 : substore.width 38 | j = substore.wid(i); 39 | X(:, i) = AtP{j} * substore.mt(substore.rng(i) : substore.rng(i + 1) - 1); 40 | end 41 | end 42 | 43 | X = X'; 44 | 45 | end -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_solve_unreg_linear_lsq.m: -------------------------------------------------------------------------------- 1 | function [X, At] = je_solve_unreg_linear_lsq(A, opts, substore) 2 | % JE_SOLVE_REG_LINEAR_LSQ 3 | % Solve an unregularized linear least squares problem of the form: || Wt * (I kron A) * x - substore.mt ||^2_2 4 | 5 | %% MOORE-PENROSE PSEUDO-INVERSE 6 | At = cell(substore.unique.count, 1); 7 | if opts.use_indicator_weight 8 | for j = 1 : substore.unique.count 9 | At{j} = A(substore.unique.nz{j}, :); 10 | end 11 | else 12 | for j = 1 : substore.unique.count 13 | At{j} = bsxfun(@times, substore.unique.wnz{j}, A(substore.unique.nz{j}, :)); 14 | end 15 | end 16 | 17 | if opts.use_mp_inv 18 | AtP = cell(substore.unique.count, 1); 19 | for j = 1 : substore.unique.count 20 | AtP{j} = (At{j} * At{j}) \ At{j}'; 21 | end 22 | end 23 | 24 | %% NORMAL EQUATION 25 | X = zeros(size(A, 2), substore.width); 26 | 27 | if ~opts.use_mp_inv 28 | for i = 1 : substore.width 29 | j = substore.wid(i); 30 | X(:, i) = At{j} \ substore.mt(substore.rng(i) : substore.rng(i + 1) - 1); 31 | end 32 | else 33 | for i = 1 : substore.width 34 | j = substore.wid(i); 35 | X(:, i) = AtP{j} * substore.mt(substore.rng(i) : substore.rng(i + 1) - 1); 36 | end 37 | end 38 | 39 | X = X'; 40 | 41 | end -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_solve_unreg_linear_lsq_qr.m: -------------------------------------------------------------------------------- 1 | function [X, AtQ, AtR] = je_solve_unreg_linear_lsq_qr(A, opts, substore) 2 | % JE_SOLVE_UNREG_LINEAR_LSQ 3 | % Solve an unregularized linear least squares problem of the form: || Wt * (I kron A) * x - substore.mt ||^2_2 4 | 5 | %% QR DECOMPOSITION 6 | AtQ = cell(substore.unique.count, 1); 7 | AtR = cell(substore.unique.count, 1); 8 | if opts.use_indicator_weight 9 | for j = 1 : substore.unique.count 10 | [AtQ{j}, AtR{j}] = qr(A(substore.unique.nz{j}, :), 0); 11 | end 12 | else 13 | for j = 1 : substore.unique.count 14 | [AtQ{j}, AtR{j}] = qr(bsxfun(@times, substore.unique.wnz{j}, A(substore.unique.nz{j}, :)), 0); 15 | end 16 | end 17 | 18 | if opts.use_mp_inv 19 | AtP = cell(substore.unique.count, 1); 20 | for j = 1 : substore.unique.count 21 | AtP{j} = AtR{j} \ AtQ{j}'; 22 | end 23 | end 24 | 25 | %% NORMAL EQUATION 26 | X = zeros(size(A, 2), substore.width); 27 | 28 | if ~opts.use_mp_inv 29 | for i = 1 : substore.width 30 | j = substore.wid(i); 31 | X(:, i) = AtR{j} \ (AtQ{j}' * substore.mt(substore.rng(i) : substore.rng(i + 1) - 1)); 32 | end 33 | else 34 | for i = 1 : substore.width 35 | j = substore.wid(i); 36 | X(:, i) = AtP{j} * substore.mt(substore.rng(i) : substore.rng(i + 1) - 1); 37 | end 38 | end 39 | 40 | X = X'; 41 | 42 | end -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_varpro.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter, store] = je_wiberg(M, W, r, U, store, varargin) 2 | % JE_WIBERG 3 | % An implementation of the unregularized variable projection algorithm on low-rank 4 | % matrix completion problem 5 | 6 | % tic 7 | %% PRE-PROCESSING 8 | % Custom function 9 | vec = @(X) X(:); 10 | 11 | % Fetch all the options. 12 | opts = au_opts( ... 13 | 'alg_type=2', ... % which VP algorithm to choose (1: Exact Wiberg, 2: Approximate Wiberg, 3: Approximate-approximate Wiberg) 14 | 'retraction=1', ... 15 | 'constraint=projection', ... 16 | 'nu=0', ... % regularization parameter 17 | 'lambda=1e-4', ... % damping parameter 18 | 'lambda_inc_factor=10', ... % damping increase rate 19 | 'lambda_dec_factor=10', ... % damping decrease rate 20 | 'lambda_min=1e-14', ... % minimum value for lambda 21 | 'max_iter=300', ... % maximum number of iterations 22 | 'max_trials=50', ... % maximum number of trials for each iteration 23 | 'tol=1e-10', ... % function tolerance 24 | 'use_qr=1', ... % whether to use QR decomposition 25 | 'use_mp_inv=0', ... % whether we are going to build explicit Moore-Penrose inverse matrices 26 | 'use_indicator_weight=1', ... % whether we are going to do 0.5 ALS only 27 | 'search_unique_weights=1', ... % whether to search for unique weight columns and rows. 28 | 'use_layerwise_computation=1', ... % whether to compute Hessian by summing n layers or by using sparse matrix computation. 29 | 'init_v_only=0', ... % whether we are going to do 0.5 ALS only 30 | 'display=1', ... % whether to display progress 31 | varargin{:} ); 32 | 33 | % If store variable has not been passed, pre-process the required variables. 34 | if nargin < 5 || ~isstruct(store) 35 | [opts, store] = je_preprocess_values(M, W, r, opts); 36 | end 37 | 38 | % Fetch U if it has not been generated. 39 | if size(U, 1) == 1 && size(U, 2) == 1 40 | U = je_generate_sample(U, store.dim.m, store.dim.n, r); 41 | end 42 | 43 | % If manifold retraction is enabled, take the q-factor of U. 44 | if opts.retraction, [U, ~] = qr(U, 0); 45 | end 46 | % toc 47 | 48 | % Get the initial estimate of V. 49 | % tic 50 | [V, UtQ, UtR] = je_solve_inner_problem(U, opts, store.wc); 51 | % toc 52 | % Compute the cost: this will be replaced with a more efficient code later on. 53 | [R, cost] = je_compute_error_matrix(M, W, U, V, store.dim.nnz); 54 | 55 | %% SECOND-ORDER SUBPROBLEM SOLVER 56 | % Initialize parameters. 57 | iter = 0; 58 | eval = 0; 59 | lambda = opts.lambda; 60 | 61 | % If display is on, show the initial cost. 62 | if opts.display, fprintf('[%05d][%05d]\t%.2e\t%.6f\n', iter, eval, lambda, cost); 63 | end 64 | 65 | % Start iteration. 66 | for iter = 1 : opts.max_iter 67 | 68 | % Compute the gradient and JTJ. 69 | if ~opts.use_indicator_weight, R = W .* R; 70 | end 71 | JTe = - vec((R * V)'); 72 | JTJ = je_compute_unreg_wiberg_hessian(U, UtQ, UtR, V, R, r, opts, store); 73 | 74 | % Evaluate until the cost decreases or reaches the max number of fail. 75 | for trial = 1 : opts.max_trials 76 | eval = eval + 1; 77 | 78 | % Compute U*. 79 | dU = reshape((JTJ + lambda * speye(store.dim.mr)) \ JTe, r, store.dim.m)'; 80 | U_eval = U + dU; 81 | 82 | % If manifold retraction is enabled, take the q-factor of U + dU. 83 | if opts.retraction, [U_eval, ~] = qr(U_eval, 0); 84 | end 85 | 86 | % U_eval = reshape((JTJ + lambda * speye(store.dim.mr)) \ (JTe + lambda * vec(U')), r, store.dim.m)'; 87 | 88 | % Compute the corresponding V*. 89 | [V_eval, UtQ, UtR] = je_solve_inner_problem(U_eval, opts, store.wc); 90 | 91 | % Compute the cost: this will be replaced with a more efficient code later on. 92 | [R, cost_eval] = je_compute_error_matrix(M, W, U_eval, V_eval, store.dim.nnz); 93 | 94 | if cost_eval < cost, break 95 | else 96 | lambda = lambda * opts.lambda_inc_factor; 97 | end 98 | end 99 | 100 | % If display is on, print the number of iterations and evaluations and the cost function value. 101 | cost_diff = abs(cost - cost_eval); 102 | if opts.display, fprintf('[%05d][%05d] %.2e %.6f %.6e\n', iter, eval, lambda, cost_eval, cost_diff); 103 | end 104 | 105 | % Update variables. 106 | U = U_eval; 107 | V = V_eval; 108 | lambda = max(lambda / opts.lambda_dec_factor, opts.lambda_min); 109 | less_than_tol = cost_diff < cost * opts.tol; 110 | cost = cost_eval; 111 | 112 | % Stop if the change in cost function is smaller than the function 113 | % tolerance value. 114 | if trial == opts.max_trials || less_than_tol, break; 115 | end 116 | end 117 | 118 | end 119 | 120 | -------------------------------------------------------------------------------- /Code/Algorithms/JE/je_write_binary_matrix.m: -------------------------------------------------------------------------------- 1 | function je_write_binary_matrix(filename, matrix, precision) 2 | % JE_WRITE_BINARY_MATRIX 3 | % Writes a binary matrix in CSC binary format. 4 | 5 | if nargin < 4, precision = 'double'; 6 | end 7 | 8 | fid = fopen(filename, 'w'); 9 | fwrite(fid, matrix, precision); 10 | fclose(fid); 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /Code/Algorithms/JE/lrmf_ceres_wrapper.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter, store] = lrmf_ceres_wrapper(M, W, r, U, store, varargin) 2 | % LRMF_CERES_WRAPPER 3 | % The wrapper function for CERES-solver LMs 4 | 5 | % tic 6 | %% PRE-PROCESSING 7 | % Fetch all the options. 8 | opts = au_opts( ... 9 | 'nu=0.0', ... % regularization parameter 10 | 'retraction=0', ... % manifold retraction 11 | 'max_iter=300', ... % maximum number of evaluations (iterations for Ceres) 12 | 'func_tol=1e-9', ... % function tolerance 13 | 'use_qr=1', ... % whether to use QR decomposition 14 | 'use_mp_inv=0', ... % whether we are going to build explicit Moore-Penrose inverse matrices 15 | 'use_indicator_weight=1', ... % whether we are going to do 0.5 ALS only 16 | 'use_layerwise_computation=0', ... % whether to compute Hessian by summing n layers or by using sparse matrix computation 17 | 'search_unique_weights=1', ... % whether to search for unique weight columns and rows 18 | 'init_v_only=1', ... % whether we are going to do 0.5 ALS only 19 | 'max_als=0', ... % set the maximum number of additional set of ALS iterations 20 | 'use_inner_iterations=0', ... % whether to use Ceres inner iterations 21 | 'use_auto_differentiation=0', ... % whether to use Ceres automatic differentiation 22 | 'use_jacobi_scaling=0', ... 23 | 'use_levenberg_damping=0', ... 24 | 'use_traditional_damping_update=0', ... 25 | 'use_rw2_for_inner_iterations=0', ... 26 | 'use_linear_inner_iterations=0', ... 27 | 'use_inner_iterations_for_v_only=0', ... 28 | 'use_block_qr_for_rw2=0', ... 29 | 'initialize_with_inner_iteration=0', ... 30 | 'use_pca=0', ... % whether to use PCA formulation 31 | 'reg_unreg=0', ... % whether to solve the regularized problem then the unregularized problem 32 | 'sample_id=0', ... % whether to use the already-generated sample file 33 | 'init_rank=0', ... % initial rank to start with (0 means start from the specified rank) 34 | 'write_binary_mw=1', ... % whether to write binary files for the measurement matrix and the weight matrix 35 | 'start_from_binary_uv=1', ... % whether to start directly from binary files of U and V 36 | 'display=1', ... % whether to display progress 37 | 'debug=0', ... 38 | 'cmd_args=', ... 39 | varargin{:} ); 40 | 41 | % If the maximum number of ALS iterations is greater than 0, set 42 | % init_v_only flag to 0. 43 | if opts.max_als > 0, opts.init_v_only = 0; 44 | end 45 | 46 | % If store variable has not been passed, pre-process the required variables. 47 | if nargin < 5 || ~isstruct(store) || opts.init_v_only 48 | [opts, store] = je_preprocess_values(M, W, r, opts); 49 | end 50 | 51 | % Fetch U if it has not been generated. 52 | if size(U, 1) == 1 && size(U, 2) == 1 53 | U = je_generate_sample(U, store.dim.m, store.dim.n, r + opts.use_pca); 54 | end 55 | 56 | % If manifold retraction is enabled, take the q-factor of U. 57 | if opts.retraction, [U, ~] = qr(U, 0); 58 | end 59 | % toc 60 | 61 | % Get the initial estimate of V or ALS output of U and V. 62 | % tic 63 | if opts.init_v_only, 64 | [V, ~, ~] = je_solve_inner_problem(U, opts, store.wc); 65 | % Compute the cost: this will be replaced with a more efficient code later on. 66 | [~, cost] = je_compute_error_matrix(M, W, U, V, store.dim.nnz); 67 | iter = 0; 68 | else 69 | % tic 70 | [U, V, cost, iter] = je_alternation(M, W, r, U, nan, varargin{:}, ... 71 | sprintf('max_iter=%d', opts.max_als), ... 72 | sprintf('retraction=%d', opts.retraction)); 73 | % If display is on, show the cost. 74 | if opts.display, fprintf('--- [ %d ALS iterations completed ] ---\n', iter); 75 | end 76 | end 77 | % toc 78 | 79 | % If display is on, show the cost. 80 | if opts.display, fprintf('[%05d]\t%.6f\n', 0, cost); 81 | end 82 | 83 | % If the sample ID is not given, write the sample matrices in CSC-binary format. 84 | if opts.sample_id == 0, opts.sample_id = sprintf('s%08d', floor(rand * 1e8)); 85 | end 86 | 87 | filename = ['Temp/', opts.sample_id, '_r', num2str(r)]; 88 | 89 | % Write the sample matrices in CSC-binary format. 90 | if opts.write_binary_mw 91 | je_write_binary_matrix([filename, '_M.bin'], M); 92 | je_write_binary_matrix([filename, '_W.bin'], W); 93 | end 94 | je_write_binary_matrix([filename, '_U0.bin'], U); 95 | je_write_binary_matrix([filename, '_V0.bin'], V); 96 | 97 | % If rank-1 initialization is set, then run first with rank-1 constraint. 98 | if opts.init_rank ~= 0 99 | U_init = lrmf_ceres_wrapper(M, W, opts.init_rank, U(:, 1:(opts.init_rank + opts.use_pca)), opts.func_tol, opts.max_iter, varargin{:}, 'init_rank=0'); 100 | U(:, 1 : opts.init_rank) = U_init(:, 1 : opts.init_rank); 101 | 102 | % If using PCA, also copy the translational vector. 103 | if opts.use_pca, U(:, end) = U_init(:, end); 104 | end 105 | end 106 | 107 | if ~strcmpi(opts.cmd_args, ''), 108 | opts.cmd_args = [opts.cmd_args, ';']; 109 | end 110 | statement = [opts.cmd_args, 'Algorithms/JE/lrmf_ceres_exec ', ... 111 | '--dataset=', opts.sample_id, ' ', ... 112 | '--path=Temp ', ... 113 | '-m=', num2str(store.dim.m), ' ', ... 114 | '-n=', num2str(store.dim.n), ' ', ... 115 | '-r=', num2str(r), ' ', ... 116 | '--func_tol=', num2str(opts.func_tol), ' ', ... 117 | '--max_eval=', num2str(opts.max_iter), ' ', ... 118 | '--nu=', num2str(opts.nu), ' ', ... 119 | '--use_pca=', num2str(opts.use_pca), ' ', ... 120 | '--use_inner_iterations=', num2str(opts.use_inner_iterations'), ' ', ... 121 | '--use_auto_differentiation=', num2str(opts.use_auto_differentiation), ' ', ... 122 | '--display=', num2str(opts.display), ' ', ... 123 | '--debug=' num2str(opts.debug), ' ', ... 124 | '--use_jacobi_scaling=', num2str(opts.use_jacobi_scaling'), ' ', ... 125 | '--use_levenberg_damping=', num2str(opts.use_levenberg_damping'), ' ', ... 126 | '--use_traditional_damping_update=', num2str(opts.use_traditional_damping_update'), ' ', ... 127 | '--use_rw2_for_inner_iterations=', num2str(opts.use_rw2_for_inner_iterations'), ' ', ... 128 | '--use_linear_inner_iterations=', num2str(opts.use_linear_inner_iterations'), ' ', ... 129 | '--use_inner_iterations_for_v_only=', num2str(opts.use_inner_iterations_for_v_only'), ' ', ... 130 | '--use_block_qr_for_rw2=', num2str(opts.use_block_qr_for_rw2'), ' ', ... 131 | '--initialize_with_inner_iteration=', num2str(opts.initialize_with_inner_iteration'), ' ', ... 132 | ]; 133 | 134 | status = system(statement); 135 | 136 | % Throw an error if not run correctly. 137 | if status ~= 0, error('[ lrmf_ceres_exec ] failed running'); 138 | end 139 | 140 | % Read U and V. 141 | U = je_read_binary_matrix([filename, '_U.bin'], store.dim.m, r + opts.use_pca); 142 | 143 | % Increment the iteration counter. 144 | iter = iter + je_read_binary_matrix([filename, '_iters.bin'], 2, 1); 145 | 146 | % If using RU-mode, set current U and V to be the initial matrices for the 147 | % un-regularized problem (warm start). 148 | if opts.reg_unreg && (opts.nu > 0.0) 149 | [U, V, cost, iter_unreg] = lrmf_ceres_wrapper(M, W, r, U, store, varargin{:}, ... 150 | 'reg_unreg=0', 'max_als=0', 'nu=0.0', ['sample_id=', opts.sample_id], 'write_binary_mw=0'); 151 | iter = iter + iter_unreg; 152 | else 153 | % Otherwise, fetch relevant statistics. 154 | V = je_read_binary_matrix([filename, '_V.bin'], store.dim.n, r); 155 | 156 | % If using PCA formulation, concatenate 1-vector to V. 157 | if opts.use_pca, V = [V ones(store.dim.n, 1)]; 158 | end 159 | 160 | % Compute the final cost. 161 | [~, cost] = je_compute_error_matrix(M, W, U, V, store.dim.nnz); 162 | if opts.display, fprintf('[%05d]\t%.6f\n', iter, cost); 163 | end 164 | 165 | delete([filename, '_U.bin']); 166 | delete([filename, '_V.bin']); 167 | delete([filename, '_U0.bin']); 168 | delete([filename, '_V0.bin']); 169 | 170 | if opts.write_binary_mw 171 | delete([filename, '_M.bin']); 172 | delete([filename, '_W.bin']); 173 | end 174 | 175 | end 176 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_README.txt: -------------------------------------------------------------------------------- 1 | ######################################################## 2 | # # 3 | # Thank you for downloading RTRMC / RCGMC. # 4 | # # 5 | ######################################################## 6 | 7 | # 8 | # To get started: read and run main.m 9 | # 10 | 11 | # 12 | # Feedback and questions welcome: nicolasboumal@gmail.com 13 | # 14 | 15 | # 16 | # This is open source software, distributed under BSD license 17 | # (See LICENSE.TXT) 18 | # 19 | 20 | 21 | This zip file was released on July 22, 2015. This is version 3.2. 22 | The only difference compared to 3.1 is moving from Manopt 1.07 to 2.0. 23 | 24 | 25 | --------------------------------------------------------------------------- 26 | 27 | 28 | This is Matlab code to solve the problem of low-rank matrix completion. The 29 | optimization is executed using optimization on manifolds, via the Manopt toolbox: 30 | 31 | www.manopt.org. 32 | 33 | For ease of use, version 2.0 of Manopt is bundled in this zip file, but 34 | it is recommended to download the latest version of Manopt on the website. 35 | 36 | The best way to get started is to read and run main.m. 37 | It will generate a random instance of a low-rank matrix completion problem 38 | and solve it. Parameters let the user choose between RCTRMC 1, 2, 2p and 39 | RCGMC and RCGMCp. 40 | 41 | An important concept is that of the problem structure. All functions which 42 | manipulate a completion problem will take as input a problem structure. 43 | This structure contains all the necessary information to describe an 44 | instance of the problem: the observation mask, the values of the entries, 45 | the desired rank... It also contains a few preprocessing fields. This is 46 | why, to obtain a problem structure, you must call 47 | 48 | buildproblem.m 49 | 50 | 51 | The following paper describes the base algorithm, 52 | available in rtrmc.m: 53 | 54 | % N. Boumal and P.-A. Absil, 55 | % RTRMC: A Riemannian trust-region method for low-rank matrix completion, 56 | % in the proceedings of the Neural Information Processing Systems conference (NIPS), 2011. 57 | 58 | 59 | The present code is for a revised and extended version of the algorithm, 60 | as described in the journal paper: 61 | 62 | % N. Boumal and P.-A. Absil, 63 | % Low-rank matrix completion via preconditioned optimization on the Grassmann manifold, 64 | % Linear Algebra and its Applications, 2015. 65 | % http://www.sciencedirect.com/science/article/pii/S0024379515001342 66 | % Also available on optimization-online.org: 67 | % http://www.optimization-online.org/DB_HTML/2012/09/3619.html 68 | 69 | 70 | Both papers are present in the zip file this README came with. See below 71 | for BiBTex entries. Please cite either (or both) of these papers if you use 72 | the accompanying Matlab codes in your research. 73 | 74 | 75 | 76 | 77 | 78 | 79 | @incollection{boumal2011rtrmc, 80 | title={{RTRMC}: A {R}iemannian trust-region method for low-rank matrix completion}, 81 | author={Boumal, N. and Absil, P.-A.}, 82 | booktitle={Advances in Neural Information Processing Systems 24 ({NIPS})}, 83 | editor={J. Shawe-Taylor and R.S. Zemel and P. Bartlett and F.C.N. Pereira and K.Q. Weinberger}, 84 | pages={406--414}, 85 | year = {2011} 86 | } 87 | 88 | 89 | 90 | 91 | @Article{boumal2015rtrmc, 92 | Title = {Low-rank matrix completion via preconditioned optimization on the {G}rassmann manifold}, 93 | Author = {Boumal, N. and Absil, P.-A.}, 94 | Journal = {Linear Algebra and its Applications}, 95 | Year = {2015}, 96 | Pages = {200--239}, 97 | Volume = {475}, 98 | 99 | Doi = {10.1016/j.laa.2015.02.027}, 100 | Url = {http://www.sciencedirect.com/science/article/pii/S0024379515001342} 101 | } 102 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_buildAchol.c: -------------------------------------------------------------------------------- 1 | /*================================================================= 2 | % function Achol = buildAchol(Chat, U, sqlambda, I, J, n) 3 | % 4 | % Specific function to reduce the computation time in a bottleneck 5 | % portion of the Matlab code for our low-rank matrix completion 6 | % software RTRMC. 7 | % 8 | % Chat is a real, double >= 0 col vector, U is an orthonormal (real,double) 9 | % m-by-r matrix, sqlambda is a positive real number, I and J are uint32 10 | % vectors of the same size as Chat; together, Chat, I and J define an 11 | % m-by-n sparse matrix; n must be a uint32. 12 | % 13 | % I and J are assumed to be sorted just like the output of the FIND 14 | % function in Matlab. 15 | % 16 | % Achol is a cell containing n upper triangular r-by-r matrices: the 17 | % Cholesky factors of the diagonal blocks of A. See the notes 18 | % accompanying the software for details. 19 | % 20 | % Compile with: mex -lmwlapack -lmwblas -largeArrayDims buildAchol.c 21 | % 22 | % May 19, 2011 Nicolas Boumal, UCLouvain 23 | *=================================================================*/ 24 | 25 | #include "mex.h" 26 | #include "math.h" 27 | #include "matrix.h" 28 | #include "lapack.h" 29 | #include "blas.h" 30 | 31 | /* Input Arguments */ 32 | 33 | #define pChat prhs[0] 34 | #define pU prhs[1] 35 | #define psqlambda prhs[2] 36 | #define pI prhs[3] 37 | #define pJ prhs[4] 38 | #define pn prhs[5] 39 | 40 | /* Output Arguments */ 41 | 42 | #define pAchol plhs[0] 43 | 44 | /* Helper function */ 45 | /* mwSize max(mwSize a, mwSize b) { return a > b ? a : b; } */ 46 | #define max(a, b) ((a)>(b)?(a):(b)) 47 | 48 | 49 | void mexFunction( 50 | int nlhs, mxArray *plhs[], 51 | int nrhs, const mxArray* prhs[] ) 52 | { 53 | /* Counters and sizes*/ 54 | mwSize m, n, r, known, start, end; 55 | mwIndex i, j, k; 56 | 57 | /* Data arrays */ 58 | double *Chat, *U; 59 | uint32_T *I, *J, *nn; 60 | double sqlambda; 61 | 62 | /* Temporary computation variables */ 63 | mxArray *pscaledUi, *pAi; 64 | double *scaledUi, *Ai; 65 | mwSize nb; 66 | double coeff; 67 | 68 | /* BLAS/LAPACK stuff*/ 69 | ptrdiff_t N, M, R, NB, info = 0; 70 | double one = 1.0; 71 | double zero = 0.0; 72 | char *uplo = "U"; 73 | char *TRANS = "T"; 74 | char *NOTRANS = "N"; 75 | 76 | 77 | if(nrhs != 6 || nlhs != 1) 78 | mexErrMsgTxt("Invalid number of input/output parameter. Need 6 inputs and 1 output."); 79 | 80 | 81 | r = mxGetN(pU); 82 | m = mxGetM(pU); 83 | nn = (uint32_T*) mxGetData(pn); 84 | n = (mwSize) (nn[0]); 85 | 86 | known = max(mxGetM(pI), mxGetN(pI)); 87 | if(max(mxGetM(pJ), mxGetN(pJ)) != known || max(mxGetM(pChat), mxGetN(pChat)) != known) 88 | mexErrMsgTxt("I, J and Chat must be vectors of the same length."); 89 | 90 | 91 | sqlambda = mxGetScalar(psqlambda); 92 | 93 | Chat = mxGetPr(pChat); 94 | U = mxGetPr(pU); 95 | I = (uint32_T*) mxGetData(pI); 96 | J = (uint32_T*) mxGetData(pJ); 97 | 98 | 99 | N = (ptrdiff_t) n; 100 | M = (ptrdiff_t) m; 101 | R = (ptrdiff_t) r; 102 | 103 | 104 | /* dummy matrix, for memory allocation */ 105 | pscaledUi = mxCreateDoubleMatrix(m, r, mxREAL); 106 | scaledUi = mxGetPr(pscaledUi); 107 | 108 | /* Create a suitable cell */ 109 | pAchol = mxCreateCellMatrix(n, 1); 110 | for(i = 0; i < n; ++i) 111 | mxSetCell(pAchol, i, NULL); 112 | 113 | start = 0; 114 | end = 0; 115 | for(i = 0; i < n; ++i) 116 | { 117 | /* allocate space for an r-by-r matrix in each cell entry */ 118 | mxSetCell(pAchol, i, mxCreateDoubleMatrix(r, r, mxREAL)); 119 | pAi = mxGetCell(pAchol, i); 120 | Ai = mxGetPr(pAi); 121 | 122 | /* Compute the entries of the i-th diagonal block of A 123 | nb is the number of nonzero entries in the i-th column of Chat 124 | seen as a sparse matrix of size m-by-n with nonzero entries I, J. */ 125 | 126 | /* TODO TODO TODO 127 | It would be much better to have an additionnal guard element at 128 | the end of the J vector ... :( 129 | Actually, it would be even better to compute the Jc vector 130 | like the one you get in the Matlab sparse representation. 131 | And even better would be to precompute it just once. */ 132 | 133 | while(end < known && J[end] == i+1) { 134 | ++end; 135 | } 136 | 137 | nb = end-start; 138 | 139 | if(nb > 0) 140 | { 141 | /*///////////////////////////////////////////////////////////////////////*/ 142 | for(j = 0; j < nb; ++j) 143 | { 144 | coeff = sqrt(Chat[start+j]); 145 | for(k = 0; k < r; ++k) 146 | { 147 | scaledUi[j+k*m] = coeff * U[I[start+j]-1+k*m]; 148 | } 149 | } 150 | /*///////////////////////////////////////////////////////////////////////*/ 151 | 152 | /* compute the matrix product of the interesting part of 153 | scaledUi with itself (transposed): Ai = scaledUi.'*scaledUi */ 154 | NB = (ptrdiff_t) nb; 155 | dgemm(TRANS, NOTRANS, &R, &R, &NB, &one, 156 | scaledUi, &M, scaledUi, &M, &zero, Ai, &R); 157 | } 158 | else 159 | { 160 | for(j = 0; j < r*r; ++j) 161 | Ai[j] = 0.0; 162 | } 163 | 164 | start = end; 165 | 166 | /* add sqlambda*eye(r) to Ai */ 167 | for(j = 0; j < r; ++j) 168 | Ai[j*(r+1)] += sqlambda; 169 | 170 | /* remplace Ai by its Cholesky factor by calling Lapack's dpotrf. */ 171 | dpotrf(uplo, &R, Ai, &R, &info); 172 | 173 | /* write zeroes on the lower triangular parts of Ai (optional) */ 174 | for(j = 1; j < r; ++j) 175 | for(k = 0; k < j; ++k) 176 | Ai[j+r*k] = 0.0; 177 | 178 | if(info < 0) 179 | mexErrMsgTxt("dpotrf (in buildAchol): invalid argument."); 180 | if(info > 0) 181 | mexErrMsgTxt("dpotrf (in buildAchol): a leading minor is not positive definite."); 182 | } 183 | 184 | mxDestroyArray(pscaledUi); 185 | 186 | return; 187 | } 188 | 189 | 190 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_buildproblem.m: -------------------------------------------------------------------------------- 1 | function problem = xt_nb_buildproblem(I, J, X, C, m, n, r, lambda) 2 | % PROBLEM = BUILDPROBLEM(I, J, X, C, M, N, R, LAMBDA) 3 | % 4 | % Build a structure describing a low-rank matrix completion problem, to be 5 | % used with RTRMC. 6 | % 7 | % Inputs: 8 | % 9 | % I, J, X, C: Column vectors of equal length such that the k-th known entry 10 | % of the matrix to recover is at position (I(k), J(k)) and has 11 | % value X(k), with confidence C(k). If C is set to the empty 12 | % matrix, it will be set to ones(size(X)). 13 | % 14 | % M, N, R: The matrix to recover is M-by-N and assumed to be of rank R. 15 | % RTRMC works best if M <= N. If this is not the case, one 16 | % should build the problem as follows: 17 | % BUILDPROBLEM(J, I, X, C, N, M, R, LAMBDA), 18 | % i.e., build a problem structure for the transpose of the 19 | % matrix to recover, call RTRMC to solve the problem, which 20 | % yields factors U and W, then obtain the reconstructed matrix 21 | % as W'*U' instead of U*W. 22 | % 23 | % LAMBDA: A small smoothing factor, set in accordance with the noise 24 | % level. 25 | % IMPORTANT! If LAMBDA is to be changed later on, one should 26 | % call BUILDPROBLEM to obtain a new structure and not simply 27 | % change the value of LAMBDA in the existing problem structure, 28 | % seen as LAMBDA is used to precompute a number of other fields 29 | % in the problem structure. 30 | % 31 | % 32 | % Output: 33 | % 34 | % PROBLEM: A structure to be passed to RTRMC to solve the low-rank 35 | % matrix completion problem. 36 | % 37 | % Nicolas Boumal, UCLouvain, Sept. 6, 2011. 38 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 39 | % 40 | % Modified on March 6, 2014 to prevent a former bug linked to the ordering 41 | % of vectors I, J, X and C. The function will now return a proper problem 42 | % structure for use with the RTRMC algorithms. Fields I, J, X and C in this 43 | % problem structure may have been re-ordered (consistently). 44 | % 45 | % SEE ALSO: rtrmc initialguess 46 | 47 | if m > n 48 | warning('RTRMC:baddimensions', ... 49 | ['RTRMC is optimized to deal with matrices such that m <= n. ' ... 50 | 'Please transpose your matrix.']); 51 | end 52 | 53 | if ~(strcmpi(class(I), 'uint32') && strcmpi(class(J), 'uint32')) 54 | I = uint32(I); 55 | J = uint32(J); 56 | % warning('RTRMC:uint32', ... 57 | % 'The index vectors I and J were converted to uint32.'); 58 | end 59 | 60 | if isempty(C) 61 | C = ones(size(X)); 62 | end 63 | 64 | %% Some verification of the consistency of the data 65 | assert(length(J) == length(I), 'I and J must have the same length.'); 66 | assert(length(X) == length(I), 'I and X must have the same length.'); 67 | assert(length(C) == length(I), 'I and C must have the same length.'); 68 | assert(all(I >= 1) && all(I <= m), 'm is the number of rows, indexed by I.'); 69 | assert(all(J >= 1) && all(J <= n), 'n is the number of columns, indexed by J.'); 70 | assert(r <= min(m, n), 'The desired rank r may not be larger than min(m, n).'); 71 | 72 | %% Reorder the vectors I, J, X and C such that their entries are listed 73 | %% in the same order as they would be if they were returned by calling 74 | %% the function find() on a sparse matrix containing these data. 75 | %% This is necessary because of the "hack" used in the C-Mex function 76 | %% setsparseentries to speed up Matlab processing of sparse matrices. 77 | %% Added March 6, 2014. 78 | [~, order] = sort(sub2ind([m, n], I, J)); 79 | I = I(order); 80 | J = J(order); 81 | X = X(order); 82 | C = C(order); 83 | 84 | %% Input argument data 85 | problem.I = I; 86 | problem.J = J; 87 | problem.X = X; 88 | problem.C = C; 89 | problem.m = m; 90 | problem.n = n; 91 | problem.r = r; 92 | problem.lambda = lambda; 93 | 94 | %% Dependent data we 'precompute' 95 | problem.k = length(I); 96 | problem.sr = problem.k/(m*n); 97 | problem.Chat = C.^2 - lambda.^2; 98 | 99 | 100 | % mask is a sparse matrix with the same sparsity structure as X, C, 101 | % Chat etc. We use it as a place holder to accelerate some computations 102 | % and memory manipulations that Matlab has a hard time with. 103 | problem.mask = sparse(double(problem.I), double(problem.J), ... 104 | ones(problem.k, 1), m, n, problem.k); 105 | 106 | 107 | %% Identification of the problem structure 108 | problem.id = cputime; 109 | 110 | % This is a limitation due to the implementation of buildAchol.c, 111 | % it is not a mathematical limitation. This being said, it does not in 112 | % general make sense to have negative Chat entries, as this means that 113 | % some observed entries are less certain than the unobserved entries. 114 | % To make this right, simply make sure all entries in C are >= lambda. 115 | assert(all(problem.Chat >= 0), ... 116 | ['All entries in C (the weights) must be larger than lambda ' ... 117 | '(the regularization).']); 118 | 119 | end 120 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_cholsolvecell.c: -------------------------------------------------------------------------------- 1 | /*================================================================= 2 | % function X = cholsolve(R, B) 3 | % Computes X such that A{i}X(:,i) = B(:,i), where R{i} is the upper 4 | % triangular Cholesky factor of the real symmetric positive-definite 5 | % matrix A{i}, i.e., A{i} = R{i}.'*R{i}. 6 | % R is a cell array of m upper cholesky factors of real, symmetric, 7 | % positive-definite matrices A{i}, i=1..m, of size n-by-n; 8 | % B and X are n-by-m real matrices. 9 | % 10 | % Compile with: mex -lmwlapack -largeArrayDims cholsolvecell.c 11 | % 12 | % Feb. 17, 2011 Nicolas Boumal, UCLouvain 13 | *=================================================================*/ 14 | 15 | #include "mex.h" 16 | #include "string.h" 17 | #include "matrix.h" 18 | #include "lapack.h" 19 | 20 | /* Input Arguments */ 21 | 22 | #define R prhs[0] 23 | #define B prhs[1] 24 | 25 | /* Output Arguments */ 26 | 27 | #define X plhs[0] 28 | 29 | void mexFunction( 30 | int nlhs, mxArray *plhs[], 31 | int nrhs, const mxArray* prhs[] ) 32 | { 33 | mwIndex k1, k2; /* debug */ 34 | double *b; 35 | 36 | mwIndex i; 37 | mwSize m, n; 38 | mxArray *Ri; 39 | double *Rvals, *Bvals, *Xvals; 40 | char *uplo = "U"; 41 | 42 | /* !! very important to use the proper typedef since confusion between 43 | 32 and 64 bits for integers is quite common. */ 44 | ptrdiff_t info = 0; 45 | ptrdiff_t one = 1; 46 | ptrdiff_t N = 0; 47 | 48 | /* Check for proper number of arguments */ 49 | if (nrhs != 2) { 50 | mexErrMsgTxt("Two input arguments required."); 51 | } else if (nlhs != 1) { 52 | mexErrMsgTxt("Single output argument required."); 53 | } 54 | 55 | if(!mxIsCell(R)) 56 | mexErrMsgTxt("CHOLSOLVECELL: R must be a cell containing m upper triangular matrices of size n-be-n."); 57 | 58 | /* retrieve dimensions of the matrices */ 59 | n = mxGetM(B); 60 | m = mxGetN(B); 61 | if(m <= 0 || n <= 0 || mxGetNumberOfElements(R) != m) 62 | mexErrMsgTxt("CHOLSOLVECELL: inconsistent matrix / cell dimensions."); 63 | 64 | /* dpotrs will store the result in the B matrix we pass to it; 65 | hence we copy B in a new matrix X and pass X to dpotrs. 66 | As a result, B will be unaltered and dpotrs will write the 67 | system solutions in the output matrix directly. */ 68 | X = mxCreateDoubleMatrix(n, m, mxREAL); 69 | Xvals = mxGetPr(X); 70 | Bvals = mxGetPr(B); 71 | memcpy(Xvals, Bvals, m*n*sizeof(double)); 72 | 73 | /* Call LAPACK subroutine (this is where the magic happens) */ 74 | N = (ptrdiff_t) n; 75 | for(i = 0; i < m; ++i) 76 | { 77 | Ri = mxGetCell(R, i); 78 | if(mxGetM(Ri) != n || mxGetN(Ri) != n) 79 | mexErrMsgTxt("CHOLSOLVECELL: inconsistent matrix / cell dimensions."); 80 | Rvals = mxGetPr(Ri); 81 | b = Xvals+n*i; 82 | dpotrs(uplo, &N, &one, Rvals, &N, b, &N, &info); 83 | if(info < 0) 84 | mexErrMsgTxt("dpotrs (in cholsolvecell): invalid argument."); 85 | } 86 | 87 | return; 88 | } 89 | 90 | 91 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_initialguess.m: -------------------------------------------------------------------------------- 1 | function U0 = xt_nb_initialguess(problem) 2 | % FUNCTION U0 = INITIALGUESS(PROBLEM) 3 | % 4 | % Generates an initial guess of the column space of X, the matrix to be 5 | % recovered, based on the observed entries of X and the mask pattern. 6 | % 7 | % Input: 8 | % 9 | % PROBLEM: A structure describing the low-rank matrix completion problem 10 | % to solve. Such a structure may be built using BUILDPROBLEM. 11 | % 12 | % Output: 13 | % 14 | % U0: An m-by-r orthonormal matrix spanning a column space that should be 15 | % closer to the true column space of the matrix to be recovered than a 16 | % simple random guess. U0 can be fed to RTRMC as initial guess to be 17 | % improved. 18 | % 19 | % Nicolas Boumal, UCLouvain, Sept. 3, 2012. 20 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 21 | % 22 | % SEE ALSO: buildproblem rtrmc 23 | 24 | m = problem.m; 25 | n = problem.n; 26 | k = problem.k; 27 | r = problem.r; 28 | I = problem.I; 29 | J = problem.J; 30 | X = problem.X; 31 | 32 | [U0, ~, ~] = svds(sparse(double(I), double(J), X, m, n, k), r); 33 | 34 | end 35 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_installrtrmc.m: -------------------------------------------------------------------------------- 1 | % RTRMC installation script. 2 | % 3 | % BEFORE your run this script, try to simply execute main and see if 4 | % it works. If it doesn't, then it is probably because the C-Mex codes need 5 | % to be compiled. Launching this script should do the trick. If compilation 6 | % fails, then you probably need to set up your C-compiler correctly for 7 | % Matlab. Type "mex -setup" at the command prompt for help with this. 8 | % From there, instructions for different Matlab versions and operating 9 | % systems are easily reachable. 10 | % 11 | % If you have trouble installing/using this code, feel free to contact the 12 | % authors at: nicolasboumal@gmail.com 13 | % 14 | % 15 | % Please note that you will also need Manopt to use the RTRMC algorithms. 16 | % A version of Manopt should be packaged with this release. If so, simply 17 | % execute the importmanopt script in the corresponding folder. Preferably, 18 | % you can get a more up-to-date version and documentation on 19 | % 20 | % www.manopt.org 21 | % 22 | % 23 | % Nicolas Boumal, UCLouvain, October 15, 2014 24 | 25 | % Put this flag to 'true' if main failed. 26 | I_launched_main_and_it_failed = false; 27 | 28 | if ~I_launched_main_and_it_failed 29 | error(['Please first try to launch the script main. ' ... 30 | 'If main executes without errors, then '... 31 | 'there is no need to launch installrtrmc. '... 32 | 'If it fails, then edit the flag on line 26 and launch the '... 33 | 'present script again. ']); 34 | end 35 | 36 | mex -largeArrayDims spmaskmult.c 37 | mex -largeArrayDims setsparseentries.c 38 | 39 | % if ~isempty(strfind(computer, 'WIN')) 40 | % If these don't work, you may try to execute the three other lines... 41 | mex -largeArrayDims -lmwlapack cholsolvecell.c 42 | mex -largeArrayDims -lmwlapack -lmwblas spbuildmatrix.c 43 | mex -largeArrayDims -lmwlapack -lmwblas buildAchol.c 44 | % else 45 | % % ... right here. The three above have been found to work on at least 46 | % % one Linux computer, although it seems that the three lines 47 | % % right here should be the correct way to do it on Linux. 48 | % mex -largeArrayDims -llapack cholsolvecell.c 49 | % mex -largeArrayDims -llapack -lblas spbuildmatrix.c 50 | % mex -largeArrayDims -llapack -lblas buildAchol.c 51 | % end 52 | 53 | 54 | if exist('manoptsolve', 'file') ~= 2 55 | cd 'Manopt_2.0'; 56 | importmanopt; 57 | cd ..; 58 | fprintf(['Using Manopt 2.0. This might not be ' ... 59 | 'the most up-to-date version of Manopt. Please consider going to ' ... 60 | 'http://www.manopt.org to obtain the latest version.\n\n' ... 61 | 'Manopt 2.0 was added to your Matlab path.\n\n']); 62 | end 63 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Nicolas Boumal 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 3. All advertising materials mentioning features or use of this software 12 | must display the following acknowledgement: 13 | This product includes software developed by Nicolas Boumal. 14 | 4. Neither the name of Nicolas Boumal nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY NICOLAS BOUMAL ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL NICOLAS BOUMAL BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | CONTACT INFORMATION: 31 | 32 | Nicolas Boumal 33 | SST/ICTM/INMA 34 | Euler 35 | Avenue George Lemaitre 4-6, bte L4.05.01 36 | 1348 Louvain-la-Neuve 37 | Belgium 38 | 39 | nicolasboumal@gmail.com 40 | 41 | http://www.nicolasboumal.net 42 | http://sites.uclouvain.be/absil/RTRMC/ 43 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_lsqfit.m: -------------------------------------------------------------------------------- 1 | function [W, out2, out3] = xt_nb_lsqfit(problem, U, in3, in4) 2 | % [W, COMPUMEM] = LSQFIT(PROBLEM, U) 3 | % [W, COMPUMEM] = LSQFIT(PROBLEM, U, COMPUMEM) 4 | % [W, DW, COMPUMEM] = LSQFIT(PROBLEM, U, H) 5 | % [W, DW, COMPUMEM] = LSQFIT(PROBLEM, U, H, COMPUMEM) 6 | % 7 | % Given a low-rank matrix completion problem structure PROBLEM and a guess 8 | % at the column space U, returns a matrix W such that U*W is as close as 9 | % possible, in the least-squares sense, to the matrix to be recovered. 10 | % 11 | % If H is provided, DW is returned and is the directional derivative 12 | % of the mapping U -> W along H. 13 | % 14 | % COMPUMEM is an optional structure in which common computations between 15 | % LSQFIT and RTRMCOBJECTIVE will be stored so as to reduce the amount of 16 | % redundant computations. 17 | % 18 | % The algorithm implemented here is described in the following paper: 19 | % 20 | % Nicolas Boumal and Pierre-Antoine Absil, 21 | % RTRMC: A Riemannian trust-region method for low-rank matrix completion, 22 | % Accepted at NIPS 2011, Granada (Spain), Dec. 2011. 23 | % 24 | % Nicolas Boumal, UCLouvain, May 19, 2011. 25 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 26 | % 27 | % SEE ALSO: buildproblem rtrmcobjective 28 | 29 | Hprovided = false; 30 | 31 | if nargin == 2 32 | compumem = struct('dob', clock()); 33 | elseif nargin == 3 34 | % the third argument is either a compumem structure or a direction 35 | % H for the Hessian computation 36 | if isstruct(in3) 37 | compumem = in3; 38 | else 39 | H = in3; 40 | Hprovided = true; 41 | compumem = struct('dob', clock()); 42 | end 43 | elseif nargin == 4 44 | H = in3; 45 | compumem = in4; 46 | Hprovided = true; 47 | else 48 | error('Wrong number of input parameters (need 2, 3 or 4)'); 49 | end 50 | 51 | 52 | X = problem.X; 53 | C = problem.C; 54 | % m = problem.m; 55 | % n = problem.n; 56 | I = problem.I; 57 | J = problem.J; 58 | mask = problem.mask; 59 | Chat = problem.Chat; 60 | lambda = problem.lambda; 61 | 62 | % Factorization of the blocks of the block-diagonal, symmetric, 63 | % positive definite matrix A. 64 | if ~isfield(compumem, 'Achol') 65 | compumem.Achol = xt_nb_buildmatrix(problem, U); 66 | end 67 | Achol = compumem.Achol; 68 | 69 | % Solve LSQ system block by block. 70 | if ~isfield(compumem, 'W') 71 | rhs = xt_nb_multfullsparse(U.', (C.^2.*X), mask); 72 | compumem.W = xt_nb_blocksolve(Achol, rhs); 73 | end 74 | W = compumem.W; 75 | 76 | % Compute the directional derivative if it is required too. 77 | if Hprovided 78 | 79 | if ~isfield(compumem, 'UW') 80 | compumem.UW = spmaskmult(U, W, I, J); 81 | end 82 | UW = compumem.UW; 83 | 84 | if ~isfield(compumem, 'RU') 85 | compumem.RU = Chat.*(UW-X) - (lambda.^2)*X; 86 | end 87 | RU = compumem.RU; 88 | 89 | % Compumem shouldn't hold information relative to H. 90 | % But then we compute HW multiple times, which is a shame... 91 | % TODO: make this right. 92 | % if ~isfield(compumem, 'HW') 93 | % compumem.HW = spmaskmult(H, W, I, J); 94 | % end 95 | % HW = compumem.HW; 96 | HW = xt_nb_spmaskmult(H, W, I, J); 97 | 98 | % we still save it, but only for immediate usage in rtrmcobjective 99 | compumem.HW = HW; 100 | 101 | Q1 = xt_nb_multfullsparse(H.', RU, mask); 102 | Q2 = xt_nb_multfullsparse(U.', Chat.*HW, mask); 103 | 104 | dW = -xt_nb_blocksolve(Achol, Q1 + Q2); 105 | 106 | out2 = dW; 107 | out3 = compumem; 108 | 109 | else 110 | 111 | out2 = compumem; 112 | 113 | end 114 | 115 | end 116 | 117 | function Achol = xt_nb_buildmatrix(problem, U) 118 | % Returns the Cholesky factors of the diagonal blocks of the symmetric, 119 | % positive definite matrix A in a cell array. 120 | % A is defined in the accompanying paper. 121 | 122 | Chat = problem.Chat; 123 | lambda = problem.lambda; 124 | % I = problem.I; 125 | % J = problem.J; 126 | % m = problem.m; 127 | % n = problem.n; 128 | 129 | % Achol = buildAchol(Chat, U, lambda.^2, I, J, uint32(n)); 130 | 131 | mask = problem.mask; 132 | xt_nb_setsparseentries(mask, sqrt(Chat)); 133 | 134 | Achol = xt_nb_spbuildmatrix(mask, U, lambda^2); 135 | 136 | end 137 | 138 | function X = xt_nb_blocksolve(R, B) 139 | % R is a cell containing n upper triangular square matrices of size r. 140 | % B is a matrix of size r x n. X is a matrix of size r x n where the i-th 141 | % column is the solution of the equation A{i}x = B(:, i), with 142 | % A{i} = R{i}.'*R{i}. This is: the R{i} matrices are the upper triangular 143 | % Cholesky factors of the matrices A{i} (which we do not require/store). 144 | 145 | X = xt_nb_cholsolvecell(R, B); 146 | 147 | end 148 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_main.m: -------------------------------------------------------------------------------- 1 | % Test code for the RTRMC/RCGMC algorithm (low-rank matrix completion). 2 | % Algorithm by Nicolas Boumal and P.-A. Absil. 3 | % Code by Nicolas Boumal, UCLouvain, April 2, 2013. 4 | % 5 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 6 | % http://sites.uclouvain.be/absil/RTRMC/ 7 | % 8 | % See also: rtrmc buildproblem initialguess 9 | 10 | clear all; 11 | close all; 12 | clc; 13 | 14 | if exist('manoptsolve', 'file') ~= 2 15 | cd 'Manopt_2.0'; 16 | importmanopt; 17 | cd ..; 18 | warning('manopt:version', ['Using Manopt 2.0. This might not be ' ... 19 | 'the most up-to-date version of Manopt. Please consider going to ' ... 20 | 'http://www.manopt.org to obtain the latest version.\n\n' ... 21 | 'Manopt 2.0 was added to your Matlab path.\n\n']); 22 | end 23 | 24 | % If this script fails, try executing installrtrmc.m, to compile the mex files. 25 | % This will not be necessary if launching the present script works out of the box, 26 | % which there is a decent chance it should. 27 | 28 | %% Problem instance generation 29 | 30 | % Dimensions of the test problem 31 | m = 5000; % number of rows 32 | n = 5000; % number of columns 33 | r = 5; % rank 34 | k = 4*r*(m+n-r); % number of known entries 35 | 36 | % Generate an m-by-n matrix of rank true_rank in factored form: A*B 37 | true_rank = r; 38 | A = randn(m, true_rank)/true_rank.^.25; 39 | B = randn(true_rank, n)/true_rank.^.25; 40 | 41 | % Pick k (or about k) entries uniformly at random 42 | [I, J, k] = randmask(m, n, k); 43 | 44 | % Compute the values of AB at these entries 45 | % (this is a C-Mex function) 46 | X = spmaskmult(A, B, I, J); 47 | 48 | % Define the confidence we have in each measurement X(i) 49 | % IMPORTANT: 50 | % If you wish to use non-uniform weights (so, C is not a constant vector), 51 | % you need the latest compiled version of spbuildmatrix.c. You can obtain 52 | % it by executing: 53 | % mex -lmwlapack -lmwblas -largeArrayDims spbuildmatrix.c 54 | % You do not need to do that if you are using .mexw64 or .mexmaci64 files 55 | % (they are already up to date). 56 | % If you compile the file for a new platform, it would be great if you sent 57 | % us the output of that compilation, for inclusion in the package: 58 | % nicolasboumal@gmail.com. 59 | % Thanks! 60 | C = ones(size(X)); 61 | 62 | % Add noise if desired 63 | noisestd = 0; 64 | X = X + noisestd*randn(size(X)); 65 | 66 | 67 | %% Feeding the problem instance to RTRMC 68 | 69 | % Pick a value for lambda, the regularization parameter 70 | lambda = 0; 71 | 72 | 73 | perm = randperm(k); 74 | I = I(perm); 75 | J = J(perm); 76 | X = X(perm); 77 | C = C(perm); 78 | 79 | 80 | % Build a problem structure 81 | problem = buildproblem(I, J, X, C, m, n, r, lambda); 82 | 83 | 84 | % Compute an initial guess 85 | initstart = tic; 86 | U0 = initialguess(problem); 87 | inittime = toc(initstart); 88 | 89 | % [Optional] If we want to track the evolution of the RMSE as RTRMC 90 | % iterates, we can do so by specifying the exact solution in factored form 91 | % in the problem structure and asking RTRMC to compute the RMSE in the 92 | % options structure. See the subfunction computeRMSE in rtrmc.m to see how 93 | % to compute the RMSE on a test set if the whole matrix is not available in 94 | % a factorized A*B form (which is typical in actual applications). 95 | problem.A = A; 96 | problem.B = B; 97 | 98 | % Setup the options for the RTRMC algorithm 99 | % These are the algorithms shown in the papers: 100 | % RTRMC 2 : method = 'rtr', order = 2, precon = false 101 | % RTRMC 2p : method = 'rtr', order = 2, precon = true 102 | % RTRMC 1 : method = 'rtr', order = 1, precon = false 103 | % RCGMC : method = 'cg', order = 1, precon = false 104 | % RCGMCp : method = 'cg', order = 1, precon = true 105 | opts.method = 'rtr'; % 'cg' or 'rtr', to choose the optimization algorithm 106 | opts.order = 2; % for rtr only: 2 if Hessian can be used, 1 otherwise 107 | opts.precon = true; % with or without preconditioner 108 | opts.maxiter = 300; % stopping criterion on the number of iterations 109 | opts.maxinner = 50; % for rtr only : maximum number of inner iterations 110 | opts.tolgradnorm = 1e-8; % stopping criterion on the norm of the gradient 111 | opts.verbosity = 2; % how much information to display during iterations 112 | opts.computeRMSE = true; % set to true if RMSE is to be computed at each step 113 | 114 | 115 | % Call the algorithm here. The outputs are U and W such that U is 116 | % orthonormal and the product U*W is the matrix estimation. The third 117 | % output, stats, is a structure array containing lots of information about 118 | % the iterations made by the optimization algorithm. In particular, timing 119 | % information should be retrieved from stats, as in the code below. 120 | [U, W, stats] = rtrmc(problem, opts, U0); 121 | 122 | 123 | time = inittime + [stats.time]; 124 | rmse = [stats.RMSE]; 125 | 126 | switch opts.method 127 | case 'rtr' 128 | semilogy(time, rmse, '.-'); 129 | case 'cg' 130 | semilogy(time, rmse, '.-'); 131 | S = [stats.linesearch]; 132 | ls_evals = [S.costevals]; 133 | for i = 1 : length(ls_evals) 134 | text(time(i), rmse(i)*1.20, num2str(ls_evals(i))); 135 | end 136 | title(sprintf('Numbers indicate line search cost evaluations.')); 137 | end 138 | xlabel('Time [s]'); 139 | ylabel('RMSE'); 140 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_multfullsparse.m: -------------------------------------------------------------------------------- 1 | function A = xt_nb_multfullsparse(B, C, mask) 2 | % FUNCTION A = MULTFULLSPARSE(B, C, MASK) 3 | % 4 | % Efficient multiplication of a full and a sparse matrix. This code relies 5 | % on an ugly C-mex trick, and should not be used for anything else than 6 | % what it was written for. 7 | % 8 | % B is a full p-by-m matrix. 9 | % D is a sparse m-by-n matrix with entries C at positions specified by the 10 | % sparse mask matrix. Please note that the entries in mask *will* 11 | % be modified by this function: it is a dummy place holder. 12 | % A is the full p-by-n matrix BD. 13 | % Complexity: O( p*nnz(D) ). 14 | % 15 | % Nicolas Boumal, UCLouvain, May 20, 2011. 16 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 17 | % 18 | % SEE ALSO: multsparsefull 19 | 20 | xt_nb_setsparseentries(mask, C); 21 | A = B*mask; 22 | 23 | end 24 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_multsparsefull.m: -------------------------------------------------------------------------------- 1 | function A = xt_nb_multsparsefull(C, B, mask) 2 | % FUNCTION A = MULTSPARSEFULL(C, B, MASK) 3 | % 4 | % Efficient multiplication of a sparse and a full matrix. This code relies 5 | % on an ugly C-mex trick, and should not be used for anything else than 6 | % what it was written for. 7 | % 8 | % D is a sparse m-by-n matrix with entries C at positions specified by the 9 | % sparse mask matrix. Please note that the entries in mask *will* 10 | % be modified by this function: it is a dummy place holder. 11 | % B is a full n-by-p matrix. 12 | % A is the full m-by-p matrix DB. 13 | % Complexity: O( p*nnz(D) ). 14 | % 15 | % Nicolas Boumal, UCLouvain, May 20, 2011. 16 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 17 | % 18 | % SEE ALSO: multfullsparse 19 | 20 | xt_nb_setsparseentries(mask, C); 21 | A = mask*B; 22 | 23 | end 24 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_randmask.m: -------------------------------------------------------------------------------- 1 | function [I J kk] = xt_nb_randmask(m, n, k, fencemax) 2 | % FUNCTION [I J KK] = RANDMASK(M, N, K, FENCEMAX) 3 | % 4 | % This code is used only to generate random problem instances, not to solve 5 | % problems. 6 | % 7 | % Random mask: we randomly select K entries in an M-by-N matrix and return 8 | % their indices (row indices in I, column indices in J) in uint32 class. 9 | % The code is based on the assumption that K << MN, that an MN vector does 10 | % not fit in memory and that it is complicated to uniformly generate a 11 | % single integer in the range 1:MN. The code will usually select K entries, 12 | % but it sometimes abandons with slightly less than K. The number of 13 | % selected entries is returned in KK. 14 | % 15 | % FENCEMAX is the maximum number of tries (a higher number increases the 16 | % chance that we will get the correct number of samples in the mask, but 17 | % increases computation time too). 18 | % 19 | % Nicolas Boumal, UCLouvain, Sept. 6, 2011. 20 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 21 | 22 | if nargin < 4 || isempty(fencemax) 23 | fencemax = 5; 24 | end 25 | 26 | % If m*n is unmanageable, do the random picking dance 27 | if m*n > 1e7 28 | 29 | pos = zeros(0, 2, 'uint32'); 30 | kk = 0; 31 | fence = 1; 32 | while kk < k 33 | if fence > fencemax 34 | warning('RTRMC:randmask', ... 35 | ['Could not select exactly k = %d entries; ' ... 36 | 'selected %d instead.\n'], k, kk); 37 | break; 38 | end 39 | rows = randi(m, k-kk, 1, 'uint32'); 40 | cols = randi(n, k-kk, 1, 'uint32'); 41 | pos((kk+1):k, 1:2) = [rows, cols]; 42 | pos = unique(pos, 'rows'); 43 | kk = size(pos, 1); 44 | fence = fence + 1; 45 | end 46 | 47 | I = pos(:, 1); 48 | J = pos(:, 2); 49 | 50 | % Otherwise, generate it explicitly 51 | else 52 | 53 | idx = uint32(randsample(m*n, k)); 54 | [I J] = ind2sub([m n], idx); 55 | kk = length(I); 56 | 57 | end 58 | 59 | % Order the indices I and J properly (which weridly requires switching 60 | % back to doubles ... Matlab, seriously, typing much? 61 | M = sparse(double(I), double(J), ones(kk, 1), m, n, kk); 62 | [I J] = find(M); 63 | I = uint32(I); 64 | J = uint32(J); 65 | 66 | end 67 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_rtrmc.m: -------------------------------------------------------------------------------- 1 | function [U, W, stats] = xt_nb_rtrmc(problem, opts, U0) 2 | % Given a problem structure describing a low-rank matrix completion problem 3 | % instance, as obtained from the buildproblem function, will return U and 4 | % W, such that U is orthonormal and the product U*W is an estimate of the 5 | % sought matrix. The rank of the approximation U*W is given by problem.r. 6 | % The opts structure is optional: see in code for available options. 7 | % U0 is an initial guess of an orthonormal matrix whose columns span the 8 | % column space of the sought matrix. If not specified or left empty, this 9 | % will be created using the initialguess function. 10 | % 11 | % Algorithm timings should be read from the stats structure returned, as it 12 | % makes the distinction between time actually spent in the algorithm and 13 | % time spent computing debugging quantities such as the RMSE at each 14 | % iteration. 15 | % 16 | % By default, this function behaves like RTRMC 2p. Play with the options 17 | % opts.method, opts.order and opts.precon to obtain the other algorithms: 18 | % RTRMC 2 : method = 'rtr', order = 2, precon = false 19 | % RTRMC 2p : method = 'rtr', order = 2, precon = true 20 | % RTRMC 1 : method = 'rtr', order = 1, precon = false 21 | % RCGMC : method = 'cg', order = 1, precon = false 22 | % RCGMCp : method = 'cg', order = 1, precon = true 23 | % 24 | % Algorithm by Nicolas Boumal and P.-A. Absil 25 | % Code by Nicolas Boumal, UCLouvain, 2013. 26 | % 27 | % See also: buildproblem initialguess 28 | 29 | if exist('manoptsolve', 'file') ~= 2 30 | error(['Please add the Manopt toolbox to your path first. ' ... 31 | 'You can do so by calling the importmanopt script in ' ... 32 | 'the Manopt_2.0 folder packaged with this release, ' ... 33 | 'or by installing the toolbox from www.manopt.org.']); 34 | end 35 | 36 | % Gather values from the problem description 37 | m = problem.m; 38 | n = problem.n; 39 | r = problem.r; 40 | k = problem.k; 41 | X = problem.X; 42 | C = problem.C; 43 | I = problem.I; 44 | J = problem.J; 45 | mask = problem.mask; 46 | Chat = problem.Chat; 47 | lambda = problem.lambda; 48 | 49 | % Pick the standard initial guess if none is given. 50 | if nargin < 3 || isempty(U0) 51 | tic_initguess = tic(); 52 | U0 = initialguess(problem); 53 | time_initguess = toc(tic_initguess); 54 | else 55 | time_initguess = 0; 56 | end 57 | 58 | % Define the default options of the method. 59 | default_opts = struct('method', 'rtr', ... 60 | 'order', 2, ... 61 | 'precon', true, ... 62 | ... % 63 | ... % See Manopt's documentation for more options 64 | ... % 65 | 'maxiter', 300, ... 66 | 'maxinner', 500, ... 67 | 'mininner', 0, ... 68 | 'tolgradnorm', 1e-5, ... 69 | 'useRand', false, ... 70 | 'kappa', 0.1, ... 71 | 'theta', 1, ... 72 | 'rho_prime', 0.1, ... 73 | 'storedepth', 5, ... 74 | 'computeRMSE', false, ... 75 | 'verbosity', 2); 76 | 77 | % Merge them with the user supplied option values, if any. The default 78 | % options will be complemented with (and possibly overwrittin by) the 79 | % user supplied options. 80 | if ~exist('opts', 'var') || isempty(opts) 81 | opts = struct(); 82 | end 83 | opts = mergeOptions(default_opts, opts); 84 | 85 | % For compatibility with legacy test code ... 86 | if isfield(opts, 'maxouter') 87 | opts.maxiter = opts.maxouter; 88 | end 89 | 90 | % For RTRMC 1, we disable rho regularization, as it prevents fine 91 | % convergence. This deserves further investigation. 92 | if ~isfield(opts, 'rho_regularization') 93 | if strcmpi(opts.method, 'rtr') && opts.order == 1 94 | opts.rho_regularization = 0; 95 | end 96 | end 97 | 98 | % Setup the initial trust region radius and the maximum trust region 99 | % radius depending on the preconditioner. The preconditioner changes 100 | % the shape of the trust region, and hence also changes the lengths of 101 | % the vectors which are to be compared to Delta. 102 | % Mnorm is the 2-norm (the largest eigenvalue) of the preconditioner 103 | % before it is inverted. 104 | if opts.precon 105 | % We do not account for the time it takes to computes W0 here, 106 | % since it will be computed in the optimization algorithm anyway, 107 | % and timed there. 108 | W0 = xt_nb_lsqfit(problem, U0); 109 | Mnorm = sqrt(norm((W0*W0'), 2)/k); 110 | else 111 | Mnorm = 1; 112 | end 113 | if ~isfield(opts, 'Delta_bar') 114 | % Base length: the diameter of the Grassmann manifold. 115 | opts.Delta_bar = Mnorm * sqrt(r)*pi/2; 116 | end 117 | if ~isfield(opts, 'Delta0') 118 | opts.Delta0 = opts.Delta_bar / 8; 119 | end 120 | 121 | % Check some of the option values 122 | if opts.order ~= 1 && opts.order ~= 2 123 | warning('RTRMC:order', ... 124 | ['The RTRMC ''order'' option must be set to 1 (no Hessian) ' ... 125 | 'or 2 (with Hessian).']); 126 | opts.order = 2; 127 | end 128 | 129 | if m > n 130 | warning(['RTRMC is optimized for m <= n (matrices with more ' ... 131 | 'columns than rows). Please transpose the problem.']); %#ok 132 | end 133 | 134 | % Obtain a description of the Grassmann manifold for Manopt 135 | grass = grassmannfactory(m, r); 136 | optproblem.M = grass; 137 | 138 | % Specify the cost and the gradient functions (see below) 139 | optproblem.cost = @cost; 140 | optproblem.grad = @gradient; 141 | 142 | % Specify whether to use the Hessian or not. If not, "approximate" it 143 | % with the identity matrix. 144 | if opts.order == 2 145 | optproblem.hess = @hessian; 146 | elseif opts.order == 1 147 | optproblem.approxhess = @(U, H) H; 148 | end 149 | 150 | % Preconditioner for the Hessian. 151 | % Be careful: if W becomes rank-deficient or close, this will crash. 152 | % A rank-deficient W is the sign that r should be lowered. This is not 153 | % currently monitored in the algorithm, but could easily be. 154 | if opts.precon 155 | optproblem.precon = @precon; 156 | optproblem.sqrtprecon = @sqrtprecon; 157 | end 158 | 159 | % RTRMC can compute the RMSE at each iteration efficiently if required, 160 | % either for the full matrix X=AB if the factors A and B are provided, 161 | % or some test RMSE if data for that is provided (see recordrmse). 162 | % Time spent in statsfun is not included in the algorithm timing. 163 | if opts.computeRMSE 164 | opts.statsfun = @recordrmse; 165 | end 166 | 167 | 168 | % Do the magic. 169 | if strcmpi('rtr', opts.method) 170 | [U, bestcost, stats] = trustregions(optproblem, U0, opts); %#ok 171 | elseif strcmpi('cg', opts.method) 172 | if ~isfield(opts, 'beta_type') 173 | opts.beta_type = 'H-S'; 174 | end 175 | opts.linesearch = @linesearch; 176 | opts.ls_contraction_factor = .2; 177 | opts.ls_optimism = 1.1; 178 | opts.ls_suff_decr = 1e-4; 179 | opts.ls_max_steps = 25; 180 | [U, bestcost, stats] = conjugategradient(optproblem, U0, opts); %#ok 181 | end 182 | 183 | % Account for the initial guess computation time 184 | for i = 1 : length(stats) 185 | stats(i).time = stats(i).time + time_initguess; 186 | end 187 | 188 | % Return also the W factor of the factorization X ~ UW. 189 | % We do not account for the time it takes to computes W here, since it 190 | % was computed in the optimization algorithm anyway, and timed there. 191 | W = xt_nb_lsqfit(problem, U); 192 | 193 | 194 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 195 | 196 | %% Define the cost function 197 | function [f, store] = cost(U, store) 198 | 199 | if ~isfield(store, 'val') 200 | 201 | [W, store] = xt_nb_lsqfit(problem, U, store); 202 | store.W = W; 203 | 204 | UW = xt_nb_spmaskmult(U, W, I, J); 205 | store.UW = UW; 206 | 207 | store.val = ( .5*sum((C.*(UW-X)).^2) ... 208 | + .5*lambda.^2*sum(W(:).^2) ... 209 | - .5*lambda.^2*sum(UW.^2) ) / k; 210 | end 211 | f = store.val; 212 | 213 | end 214 | 215 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 216 | 217 | %% Define the gradient function 218 | function [grad, store] = gradient(U, store) 219 | 220 | if ~isfield(store, 'grad') 221 | 222 | % If the cost was never computed for this point before, call it 223 | % so we have the necessary ingredients to compute the gradient. 224 | if ~isfield(store, 'val') 225 | [~, store] = cost(U, store); 226 | end 227 | 228 | W = store.W; 229 | WWt = W*W.'; 230 | sqlaWWt = lambda.^2*WWt; 231 | store.WWt = WWt; 232 | store.sqlaWWt = sqlaWWt; 233 | 234 | UW = store.UW; 235 | RU = Chat.*(UW-X) - (lambda.^2)*X; 236 | store.RU = RU; 237 | 238 | store.grad = (xt_nb_multsparsefull(RU, W.', mask) + U*sqlaWWt)/k; 239 | 240 | end 241 | grad = store.grad; 242 | 243 | end 244 | 245 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 246 | 247 | %% Define the Hessian function 248 | function [hess, store] = hessian(U, H, store) 249 | 250 | [W, dW, store] = xt_nb_lsqfit(problem, U, H, store); 251 | UdW = xt_nb_spmaskmult(U, dW, I, J); 252 | 253 | if ~isfield(store, 'HW') 254 | store.HW = xt_nb_spmaskmult(H, W, I, J); 255 | end 256 | HW = store.HW; 257 | 258 | if ~isfield(store, 'grad') 259 | [~, store] = gradient(U, store); 260 | end 261 | RU = store.RU; 262 | sqlaWWt = store.sqlaWWt; 263 | 264 | hess = xt_nb_multsparsefull(Chat.*(HW + UdW), W.', mask); 265 | hess = hess - U*(U.'*hess); 266 | hess = hess + xt_nb_multsparsefull(RU, dW.', mask); 267 | hess = hess + H*sqlaWWt; 268 | hess = hess + U*(lambda.^2*(W*dW.')); 269 | hess = hess/k; 270 | 271 | end 272 | 273 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 274 | 275 | %% Define the preconditioner 276 | function [pH, store] = precon(U, H, store) 277 | 278 | if ~isfield(store, 'WWt') 279 | [W, store] = xt_nb_lsqfit(problem, U, store); 280 | store.W = W; 281 | WWt = W*W.'; 282 | store.WWt = WWt; 283 | end 284 | WWt = store.WWt; 285 | 286 | pH = H / (WWt/k); 287 | 288 | end 289 | 290 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 291 | 292 | %% The square root of the preconditioner is only useful to compute the 293 | % spectrum of the Hessian with and without preconditioning. See 294 | % Manopt's hessianspectrum and hessianextreme functions if you want 295 | % to compute that. 296 | function [sqrtpH, store] = sqrtprecon(U, H, store) 297 | 298 | if ~isfield(store, 'WWt') 299 | [W, store] = xt_nb_lsqfit(problem, U, store); 300 | store.W = W; 301 | WWt = W*W.'; 302 | store.WWt = WWt; 303 | end 304 | WWt = store.WWt; 305 | 306 | sqrtpH = H / (sqrtm(WWt)/sqrt(k)); 307 | 308 | end 309 | 310 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 311 | 312 | % Keep track of the RMSE 313 | function stats = recordrmse(optproblem, U, stats, store) %#ok 314 | if ~isfield(store, 'W') 315 | W = xt_nb_lsqfit(problem, U); 316 | else 317 | W = store.W; 318 | end 319 | [rmse, rmse_clipped] = computeRMSE(U, W, problem); 320 | stats.RMSE = rmse; 321 | stats.RMSE_clipped = rmse_clipped; 322 | stats.WWt_cond = cond(store.WWt); 323 | if opts.verbosity > 2 324 | fprintf('cond(W*W'') : %e\n', stats.WWt_cond); 325 | end 326 | end 327 | 328 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 329 | 330 | function [rmse, rmse_clipped] = computeRMSE(U, W, problem) 331 | 332 | if isfield(problem, 'A') && isfield(problem, 'B') 333 | 334 | A = problem.A; 335 | B = problem.B; 336 | m = problem.m; 337 | n = problem.n; 338 | rmse = sqrt(sqfrobnormfactors(U, W, A, B)/(m*n)); 339 | rmse_clipped = rmse; 340 | 341 | elseif isfield(problem, 'Xtest') && isfield(problem, 'Itest') && ... 342 | isfield(problem, 'Xmean') && isfield(problem, 'Jtest') 343 | 344 | Xtest = problem.Xtest; 345 | Itest = problem.Itest; 346 | Jtest = problem.Jtest; 347 | Xmean = problem.Xmean; 348 | Xpred = Xmean + spmaskmult(U, W, Itest, Jtest); 349 | Xpred_clipped = max(1, min(5, Xpred)); 350 | rmse = norm(Xpred - Xtest, 'fro')/sqrt(length(Xtest)); 351 | rmse_clipped = norm(Xpred_clipped - Xtest, 'fro')/sqrt(length(Xtest)); 352 | 353 | else 354 | rmse = NaN; 355 | rmse_clipped = NaN; 356 | end 357 | 358 | if opts.verbosity > 2 359 | fprintf('Test RMSE : %e\n', rmse); 360 | fprintf('Test RMSE clipped : %e\n', rmse_clipped); 361 | end 362 | 363 | end 364 | 365 | end 366 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_rtrmc_wrapper.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter] = xt_nb_rtrmc_wrapper(M, W, r, U, varargin) 2 | 3 | opts = au_opts( ... 4 | 'nu=0', ... % regularization parameter 5 | 'max_iter=300', ... % maximum number of iterations 6 | 'max_trials=50', ... % maximum number of trials for each iteration 7 | 'tol=1e-10', ... % function tolerance 8 | 'display=1', ... % whether to display progress 9 | varargin{:} ); 10 | 11 | 12 | % Test code for the RTRMC/RCGMC algorithm (low-rank matrix completion). 13 | % Algorithm by Nicolas Boumal and P.-A. Absil. Code by Nicolas Boumal, 14 | % UCLouvain, April 2, 2013. 15 | % 16 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 17 | % http://sites.uclouvain.be/absil/RTRMC/ 18 | % 19 | % See also: rtrmc buildproblem initialguess 20 | 21 | % clear all; 22 | % close all; 23 | % clc; 24 | 25 | % if exist('manopt_version', 'file') ~= 2 26 | % cd '../../'; 27 | % 28 | % importmanopt; 29 | % cd ..; 30 | % warning('manopt:version', ['Using Manopt 1.0.7. This is probably not ' ... 31 | % 'the most up-to-date version of Manopt. Please consider going to ' ... 32 | % 'http://www.manopt.org to obtain the latest version.\n\n' ... 33 | % 'Manopt 1.0.7 was added to your Matlab path.\n\n']); 34 | % end 35 | 36 | % If this script fails, try executing installrtrmc.m, to compile the mex 37 | % files. This will not be necessary if launching the present script works 38 | % out of the box, which there is a decent chance it should. 39 | 40 | %% Problem instance generation 41 | 42 | % Dimensions of the test problem 43 | % load ~/Dropbox/Research/Projects/Beyond' Wiberg'/Datasets/Dino_trimmed/dino_trimmed.mat; r = 4; 44 | % load ~/Dropbox/Research/Projects/Beyond' 45 | % Wiberg'/Datasets/Giraffe/giraffe.mat; r = 6; 46 | 47 | [m, n] = size(M); 48 | % k = sum(W(:)); 49 | 50 | % Fetch U if it has not been generated. 51 | if size(U, 1) == 1 && size(U, 2) == 1 52 | [U, ~] = qr(je_generate_sample(U, size(M, 1), size(M, 2), r), 0); 53 | end 54 | 55 | % m = 5000; % number of rows n = 5000; 56 | % % number of columns r = 10; % rank k = 57 | % 4*r*(m+n-r); % number of known entries 58 | 59 | % Generate an m-by-n matrix of rank true_rank in factored form: A*B 60 | % true_rank = r; 61 | % A = randn(m, true_rank)/true_rank.^.25; B = randn(true_rank, 62 | % n)/true_rank.^.25; 63 | 64 | % Pick k (or about k) entries uniformly at random 65 | [I, J] = find(W); 66 | % [I, J, k] = randmask(m, n, k); 67 | 68 | % Compute the values of AB at these entries (this is a C-Mex function) 69 | % M(W==0)=0; 70 | X = M(W==1); 71 | % X = spmaskmult(A, B, I, J); 72 | 73 | % Define the confidence we have in each measurement X(i) IMPORTANT: If you 74 | % wish to use non-uniform weights (so, C is not a constant vector), you 75 | % need the latest compiled version of spbuildmatrix.c. You can obtain it by 76 | % executing: 77 | % mex -lmwlapack -lmwblas -largeArrayDims spbuildmatrix.c 78 | % You do not need to do that if you are using .mexw64 or .mexmaci64 files 79 | % (they are already up to date). If you compile the file for a new 80 | % platform, it would be great if you sent us the output of that 81 | % compilation, for inclusion in the package: nicolasboumal@gmail.com. 82 | % Thanks! 83 | C = ones(size(X)); 84 | 85 | % Add noise if desired noisestd = 0; X = X + noisestd*randn(size(X)); 86 | 87 | 88 | %% Feeding the problem instance to RTRMC 89 | 90 | % Pick a value for lambda, the regularization parameter 91 | % lambda = 0; 92 | 93 | 94 | % perm = randperm(k); I = I(perm); J = J(perm); X = X(perm); C = C(perm); 95 | 96 | 97 | % Build a problem structure 98 | problem = xt_nb_buildproblem(I, J, X, C, m, n, r, opts.nu); 99 | 100 | 101 | % Compute an initial guess 102 | % initstart = tic; 103 | % U0 = initialguess(problem); 104 | % U0 = je_generate_sample(sample_num, m, n, r); 105 | % inittime = toc(initstart); 106 | 107 | % [Optional] If we want to track the evolution of the RMSE as RTRMC 108 | % iterates, we can do so by specifying the exact solution in factored form 109 | % in the problem structure and asking RTRMC to compute the RMSE in the 110 | % options structure. See the subfunction computeRMSE in rtrmc.m to see how 111 | % to compute the RMSE on a test set if the whole matrix is not available in 112 | % a factorized A*B form (which is typical in actual applications). 113 | 114 | % Setup the options for the RTRMC algorithm These are the algorithms shown 115 | % in the papers: 116 | % RTRMC 2 : method = 'rtr', order = 2, precon = false RTRMC 2p : method = 117 | % 'rtr', order = 2, precon = true RTRMC 1 : method = 'rtr', order = 1, 118 | % precon = false RCGMC : method = 'cg', order = 1, precon = false 119 | % RCGMCp : method = 'cg', order = 1, precon = true 120 | opts.method = 'rtr'; % 'cg' or 'rtr', to choose the optimization algorithm 121 | opts.order = 2; % for rtr only: 2 if Hessian can be used, 1 otherwise 122 | opts.precon = true; % with or without preconditioner 123 | opts.maxiter = opts.max_iter; % stopping criterion on the number of iterations 124 | opts.maxinner = opts.max_trials; % for rtr only : maximum number of inner iterations 125 | opts.tolcost = opts.tol; % stopping criterion on the cost function value 126 | % opts.tolgradnorm = opts.tol; % stopping criterion on the norm of the gradient 127 | opts.verbosity = opts.display * 2; % how much information to display during iterations 128 | opts.computeRMSE = true; % set to true if RMSE is to be computed at each step 129 | 130 | 131 | % Call the algorithm here. The outputs are U and W such that U is 132 | % orthonormal and the product U*W is the matrix estimation. The third 133 | % output, stats, is a structure array containing lots of information about 134 | % the iterations made by the optimization algorithm. In particular, timing 135 | % information should be retrieved from stats, as in the code below. 136 | [U, V, stats] = xt_nb_rtrmc(problem, opts, U); 137 | V = V'; 138 | 139 | % time = inittime + [stats.time]; 140 | % rmse = [stats.RMSE]; 141 | iter = [stats.iter]; 142 | iter = iter(end); 143 | 144 | cost = norm(W .* (M - U * V'), 'fro')^2; 145 | % switch opts.method 146 | % case 'rtr' 147 | % semilogy(time, rmse, '.-'); 148 | % case 'cg' 149 | % semilogy(time, rmse, '.-'); S = [stats.linesearch]; ls_evals = 150 | % [S.costevals]; for i = 1 : length(ls_evals) 151 | % text(time(i), rmse(i)*1.20, num2str(ls_evals(i))); 152 | % end title(sprintf('Numbers indicate line search cost 153 | % evaluations.')); 154 | % end xlabel('Time [s]'); ylabel('RMSE'); 155 | 156 | end 157 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_setsparseentries.c: -------------------------------------------------------------------------------- 1 | /*================================================================= 2 | % function setsparseentries(M, X) 3 | % M is a sparse matrix (real double). 4 | % X is a vector (real double). 5 | % After this function returns, the nonzero entries of M will be 6 | % equal to those of X. 7 | % 8 | % Compile with: mex setsparseentries.c -largeArrayDims 9 | % 10 | % May 20, 2011, Nicolas Boumal, UCLouvain 11 | *=================================================================*/ 12 | 13 | /* #include */ 14 | #include "string.h" 15 | #include "mex.h" 16 | #include "matrix.h" 17 | 18 | /* Input Arguments */ 19 | 20 | #define pM (prhs[0]) 21 | #define pX (prhs[1]) 22 | 23 | void mexFunction( 24 | int nlhs, mxArray *plhs[], 25 | int nrhs, const mxArray* prhs[] ) 26 | { 27 | if(!mxIsDouble(pM) || !mxIsDouble(pX)) 28 | mexErrMsgTxt("M and X must be of type DOUBLE."); 29 | if(!mxIsSparse(pM)) 30 | mexErrMsgTxt("M must be sparse."); 31 | if(mxGetNzmax(pM) < mxGetNumberOfElements(pX)) 32 | mexErrMsgTxt("M must be able to contain at least as many elements as X."); 33 | 34 | memcpy(mxGetPr(pM), mxGetPr(pX), mxGetNumberOfElements(pX) * sizeof(double)); 35 | 36 | return; 37 | } 38 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_spbuildmatrix.c: -------------------------------------------------------------------------------- 1 | /*================================================================= 2 | % function R = spbuildmatrix(Ct, U, lambda) 3 | % 4 | % Specific function to reduce the computation time in a bottleneck 5 | % portion of the Matlab code for our low-rank matrix completion 6 | % software. 7 | % 8 | % Ct is a *sparse* m-by-n real matrix, U is an orthonormal (real) 9 | % m-by-r matrix and lambda is a strictly positive real number. 10 | % R is a cell containing n upper triangular r-by-r matrices: the 11 | % Cholesky factors of the diagonal blocks of A.'*A. See the notes 12 | % accompanying the software for details. 13 | % 14 | % The equivalent Matlab code is the following: 15 | % 16 | % AtA = cell(n, 1); 17 | % for i = 1 : n 18 | % 19 | % I = find(Ct(:, i)); 20 | % scaledUi = (Ct(I, i)*ones(1, r)) .* U(I, :); 21 | % AtAi = scaledUi.'*scaledUi + lambda*eye(r); 22 | % 23 | % % We actually store the Cholesky factorization 24 | % AtA{i} = chol(AtAi); 25 | % 26 | % end 27 | % 28 | % 29 | % Compile with: mex -lmwlapack -lmwblas -largeArrayDims spbuildmatrix.c 30 | % 31 | % Feb. 17, 2011 Nicolas Boumal, UCLouvain 32 | % Bug correction on Oct. 15, 2014, for the case of non-uniform Ct. 33 | *=================================================================*/ 34 | 35 | #include "mex.h" 36 | #include "matrix.h" 37 | #include "lapack.h" 38 | #include "blas.h" 39 | 40 | /* Input Arguments */ 41 | 42 | #define Ct prhs[0] 43 | #define U prhs[1] 44 | #define lambda prhs[2] 45 | 46 | /* Output Arguments */ 47 | 48 | #define R plhs[0] 49 | 50 | void mexFunction( 51 | int nlhs, mxArray *plhs[], 52 | int nrhs, const mxArray* prhs[] ) 53 | { 54 | mwIndex i, j, k; 55 | mwSize m, n, r; 56 | mxArray *Ri; 57 | double *Rivals, *Ctvals, *Uvals; 58 | char *uplo = "U"; 59 | char *TRANS = "T"; 60 | char *NOTRANS = "N"; 61 | ptrdiff_t N, NB, M, RR, info = 0; 62 | double lambdaval; 63 | mwIndex *CtIr, *CtJc; 64 | mxArray *scaledUi; 65 | double *scaledUivals; 66 | double one = 1.0; 67 | double zero = 0.0; 68 | mwSize nb; 69 | mwIndex *Ctcol; 70 | double coeff; 71 | 72 | if(nrhs != 3 || nlhs != 1) 73 | mexErrMsgTxt("Invalid number of input/output parameter. Need 3 inputs and 1 output."); 74 | 75 | m = mxGetM(Ct); 76 | n = mxGetN(Ct); 77 | r = mxGetN(U); 78 | if(mxGetM(U) != m) 79 | mexErrMsgTxt("Dimensions mismatch in input arguments."); 80 | 81 | if(!mxIsSparse(Ct)) 82 | mexErrMsgTxt("Ct ought to be a sparse matrix."); 83 | 84 | lambdaval = mxGetScalar(lambda); 85 | 86 | CtIr = mxGetIr(Ct); 87 | CtJc = mxGetJc(Ct); 88 | Ctvals = mxGetPr(Ct); 89 | Uvals = mxGetPr(U); 90 | 91 | N = (ptrdiff_t) n; 92 | M = (ptrdiff_t) m; 93 | RR = (ptrdiff_t) r; 94 | 95 | /* dummy matrix, for memory allocation */ 96 | scaledUi = mxCreateDoubleMatrix(m, r, mxREAL); 97 | scaledUivals = mxGetPr(scaledUi); 98 | 99 | /* Create a suitable cell */ 100 | R = mxCreateCellMatrix(n, 1); 101 | for(i = 0; i < n; ++i) 102 | mxSetCell(R, i, NULL); 103 | 104 | for(i = 0; i < n; ++i) 105 | { 106 | /* allocate space for an r-by-r matrix in each cell entry */ 107 | mxSetCell(R, i, mxCreateDoubleMatrix(r, r, mxREAL)); 108 | Ri = mxGetCell(R, i); 109 | Rivals = mxGetPr(Ri); 110 | 111 | /* compute the entries of the matrix R{i} 112 | nb is the number of nonzero entries in the i-th column of Ct 113 | Ctcol is a pointer to an array of nb elements: the indices of 114 | rows where a nonzero element exists on column i of Ct. */ 115 | nb = CtJc[i+1]-CtJc[i]; 116 | /* mexPrintf("%d ", nb); */ 117 | Ctcol = CtIr+CtJc[i]; 118 | if(nb > 0) 119 | { 120 | for(j = 0; j < nb; ++j) 121 | { 122 | /* coeff = Ct(Ctcol[j], i) */ 123 | /* Correction on Oct. 15, 2014 */ 124 | coeff = Ctvals[CtJc[i]+j]; 125 | /* mexPrintf("%g ", coeff); */ 126 | for(k = 0; k < r; ++k) 127 | { 128 | /* scaledUi(j, k) = coeff * U(Ctcol[j], k) */ 129 | scaledUivals[j+k*m] = coeff * Uvals[Ctcol[j]+k*m]; 130 | } 131 | } 132 | /* mexPrintf("\n"); */ 133 | /* compute the matrix product of the interesting part of 134 | scaledUi with itself (transposed): Ri = scaledUi.'*scaledUi */ 135 | NB = (ptrdiff_t) nb; 136 | dgemm(TRANS, NOTRANS, &RR, &RR, &NB, &one, 137 | scaledUivals, &M, scaledUivals, &M, &zero, Rivals, &RR); 138 | } 139 | else 140 | { 141 | for(j = 0; j < r*r; ++j) 142 | Rivals[j] = 0.0; 143 | } 144 | 145 | /* add lambda*eye(r) to Ri */ 146 | for(j = 0; j < r; ++j) 147 | Rivals[j*(r+1)] += lambdaval; 148 | 149 | /* remplace R{i} by its Cholesky factor by calling Lapack's dpotrf. */ 150 | dpotrf(uplo, &RR, Rivals, &RR, &info); 151 | /* write zeroes on the lower triangular parts of Ri */ 152 | for(j = 1; j < r; ++j) 153 | for(k = 0; k < j; ++k) 154 | Rivals[j+r*k] = 0.0; 155 | 156 | if(info < 0) 157 | mexErrMsgTxt("dpotrf (in spbuildmatrix): invalid argument."); 158 | if(info > 0) 159 | mexErrMsgTxt("dpotrf (in spbuildmatrix): a leading minor is not positive definite."); 160 | } 161 | 162 | mxDestroyArray(scaledUi); 163 | 164 | return; 165 | } 166 | 167 | 168 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_spmaskmult.c: -------------------------------------------------------------------------------- 1 | /*================================================================= 2 | % function X = spmaskmult(A, B, I, J) 3 | % Computes A*B at entries (I(k), J(k)) and returns the result in 4 | % a k1-by-k2 real double matrix X the same size as I and J. 5 | % I and J must be UINT32 matrices of size k1-by-k2. 6 | % 7 | % A: m-by-r, real, double 8 | % B: r-by-n, real, double 9 | % I: k1-by-k2 row indices, uint32 10 | % J: k1-by-k2 column indices, uint32 11 | % 12 | % Complexity: O(k1k2r) 13 | % 14 | % Warning: no check of data consistency is performed. Matlab will 15 | % most likely crash if I or J go out of bounds. 16 | % 17 | % Compile with: mex spmaskmult.c -largeArrayDims 18 | % 19 | % May 19, 2011, Nicolas Boumal, UCLouvain 20 | *=================================================================*/ 21 | 22 | /* #include */ 23 | #include "mex.h" 24 | #include "matrix.h" 25 | 26 | /* Input Arguments */ 27 | 28 | #define pA prhs[0] 29 | #define pB prhs[1] 30 | #define pI prhs[2] 31 | #define pJ prhs[3] 32 | 33 | /* Output Arguments */ 34 | 35 | #define pX plhs[0] 36 | 37 | void mexFunction( 38 | int nlhs, mxArray *plhs[], 39 | int nrhs, const mxArray* prhs[] ) 40 | { 41 | uint32_T *I, *J; 42 | double *A, *B, *X; 43 | mwSize m, n, r, k1, k2, numit; 44 | mwIndex k, it; 45 | 46 | /* Check for proper number of arguments */ 47 | if (nrhs != 4) { 48 | mexErrMsgTxt("Four input arguments are required."); 49 | } else if (nlhs != 1) { 50 | mexErrMsgTxt("A single output argument is required."); 51 | } 52 | 53 | /* Check argument classes */ 54 | if(!mxIsUint32(pI) || !mxIsUint32(pJ)) { 55 | mexErrMsgTxt("I and J must be of class UINT32."); 56 | } 57 | if(!mxIsDouble(pA) || !mxIsDouble(pB)) { 58 | mexErrMsgTxt("A and B must be of class DOUBLE."); 59 | } 60 | if(mxIsComplex(pA) || mxIsComplex(pB)) { 61 | mexErrMsgTxt("A and B must be REAL."); 62 | } 63 | 64 | /* Check the dimensions of input arguments */ 65 | m = mxGetM(pA); 66 | r = mxGetN(pA); 67 | n = mxGetN(pB); 68 | k1 = mxGetM(pI); 69 | k2 = mxGetN(pI); 70 | 71 | if(mxGetM(pB) != r) 72 | mexErrMsgTxt("Matrix dimensions mismatch for A and B."); 73 | 74 | if(mxGetM(pJ) != k1 || mxGetN(pJ) != k2) 75 | mexErrMsgTxt("Matrix dimensions mismatch for I and J."); 76 | 77 | 78 | /* Get pointers to the data in A, B, I, J */ 79 | A = mxGetPr(pA); 80 | B = mxGetPr(pB); 81 | I = (uint32_T*) mxGetData(pI); 82 | J = (uint32_T*) mxGetData(pJ); 83 | 84 | /* Create a matrix for the ouput argument */ 85 | pX = mxCreateDoubleMatrix(k1, k2, mxREAL); 86 | if(pX == NULL) 87 | mexErrMsgTxt("SPMASKMULT: Could not allocate X. Out of memory?"); 88 | X = mxGetPr(pX); 89 | 90 | 91 | /* Compute */ 92 | numit = k1*k2; 93 | for(it = 0; it < numit; ++it) 94 | { 95 | /* Multiply row I(it) of A with col J(it) of B */ 96 | X[it] = A[ I[it]-1 ] * B[ r*(J[it]-1) ]; 97 | for(k = 1; k < r; ++k) 98 | { 99 | X[it] += A[ I[it]-1 + m*k ]*B[ k + r*(J[it]-1) ]; 100 | } 101 | } 102 | 103 | return; 104 | } 105 | 106 | 107 | -------------------------------------------------------------------------------- /Code/Algorithms/NB/xt_nb_sqfrobnormfactors.m: -------------------------------------------------------------------------------- 1 | function val = xt_nb_sqfrobnormfactors(A, B, X, Y) 2 | % FUNCTION VAL = SQFROBNORMFACTORS(A, B, X, Y) 3 | % 4 | % Inputs: 5 | % A, X: m-by-r matrices 6 | % B, Y: r-by-n matrices 7 | % 8 | % Output: 9 | % val = ||AB-XY||^2_F (squared Frobenius norm of the m-by-n matrix AB-XY) 10 | % 11 | % Complexity: 12 | % O((m+n)*r^2) 13 | % 14 | % Nicolas Boumal, UCLouvain, April 2013. 15 | % http://perso.uclouvain.be/nicolas.boumal/RTRMC/ 16 | % 17 | % SEE ALSO: rtrmc 18 | 19 | % Thank you to Bart Vandereycken for pointing out this nice linear 20 | % algebra trick to compute 'val' with good accuracy even when AB is 21 | % very close to XY! This is both cheap and numerically robust. 22 | 23 | % Notice that AB-XY = [A X] * [B' -Y']'. 24 | % Compute a thin QR factorization of each term: 25 | [Q1 R1] = qr([A X], 0); %#ok 26 | [Q2 R2] = qr([B' -Y'], 0); %#ok 27 | % Then, by invariance of the Frobenius norm under the orthonormal 28 | % transformations Q1 and Q2, we simply get: 29 | val = norm(R1*R2', 'fro')^2; 30 | % This is accurate up to 7 digits even close to 0, as compared with 31 | % quadruple precision computations. Thanks, Bart! :) 32 | 33 | end 34 | -------------------------------------------------------------------------------- /Code/Algorithms/PG/xt_pg_README.txt: -------------------------------------------------------------------------------- 1 | 2 | COPYRIGHT © 2011 Paulo F.U. Gotardo, Aleix M. Martinez 3 | 4 | Dept of Electrical and Computer Engineering 5 | The Ohio State University 6 | {gotardop,aleix}@ece.osu.edu 7 | 8 | This code is an implementation of the methods described in: 9 | 10 | P.F.U. Gotardo, A.M. Martinez, “Computing Smooth Time-Trajectories for 11 | Camera and Deformable Shape in Structure from Motion with Occlusion," 12 | IEEE Trans. on Pattern Analysis and Machine Intelligence, 2011. 13 | 14 | This software is distributed WITHOUT ANY WARRANTY. Use of this software is granted for research conducted at research institutions only. Commercial use of this software is not allowed. Corporations interested in the use of this software should contact the authors. If you use this code for a scientific publication, please reference the paper above. 15 | 16 | USAGE: 17 | 18 | Please see the commented code and demo files "demo_*.m" for usage information. These scripts were tested with MATLAB versions R2009b and R2010b. 19 | 20 | FEEDBACK: 21 | 22 | Your feedback is greatly welcome. Please send bug reports, suggestions, and/or new results to: 23 | 24 | gotardop@ece.osu.edu 25 | 26 | In the future, updated versions of this software will be available at: 27 | 28 | http://www.ece.osu.edu/~gotardop 29 | 30 | 31 | -------------------------------------------------------------------------------- /Code/Algorithms/PG/xt_pg_check_buildkron.m: -------------------------------------------------------------------------------- 1 | function xt_pg_check_buildkron() 2 | 3 | try 4 | xt_pg_kronmex( eye(2), eye(3) ); % simply attempt to run mex-file 5 | catch 6 | disp('ERROR running "kronmex" mex-file. Will now compile source code:') 7 | mex xt_pg_kronmex.c 8 | end 9 | 10 | end -------------------------------------------------------------------------------- /Code/Algorithms/PG/xt_pg_csf.m: -------------------------------------------------------------------------------- 1 | function [M, S, X, rmse, iter] = xt_pg_csf(W, r, X0, numIter, RMSE_TOL, verbose, B) 2 | %function [M,S,X,rmse,iter] = pgCSF ( W, r, X0, numIter, RMSE_TOL, verbose, B ) 3 | % 4 | % By Paulo Gotardo 5 | % 6 | % Computes factorization W = MS, with M = BX 7 | % 8 | % Inputs: 9 | % 10 | % W is the observation matrix (missing data encoded as NaN entries) 11 | % r is the factorization rank 12 | % X0 is the initial value of X (or the initial M0 when basis B is the identity) 13 | % numIter is the maximum number of iterations 14 | % RMSE_TOL is the parameter of the stopping (convergence) rule 15 | % verbose is boolean 16 | % B is the basis of M = BX (default basis is the identity matrix) 17 | % 18 | xt_pg_check_buildkron() 19 | 20 | if (nargin < 7), B = eye(size(W,1)); end % assume canonical basis I_m 21 | 22 | % Auxiliary variables 23 | VALID = isfinite(W); % mask of observed data 24 | % (missing data marked as NaN in W) 25 | rmse = zeros(numIter+1,1); % error at each iteration 26 | 27 | n = size(W,2); % number of points 28 | d = size(B,2); % number of DCT basis vectors 29 | nx = d*r; % number of unknowns 30 | Idr = speye(nx); % damping matrix 31 | delta = 1e-4; % initial damping parameter 32 | g = zeros(nx,1); % gradient vector 33 | H = zeros(nx,nx); % Hessian matrix 34 | 35 | % Initialize X 36 | if isempty(X0) 37 | X = [ eye(r,r) ; zeros(d-r,r) ]; % deterministic initialization 38 | else 39 | [X, ~] = qr(X0, 0); 40 | if (size(X,1) < d), X(d,:) = 0; end % enlarge 41 | end 42 | 43 | warning('off', 'MATLAB:nearlySingularMatrix'); % OK! Damping fixes Hessian 44 | 45 | % Compute initial factors 46 | M = B*X; % current estimate of M 47 | S = zeros(r,n); % current estimate of S 48 | R = zeros(size(W)); % residual values 49 | for j = 1:n 50 | mask = VALID(:,j); 51 | wj = W(mask,j); 52 | Mj = M(mask,:); 53 | %S(:,j) = pinv(Mj) * wj; 54 | S(:,j) = Mj \ wj; 55 | R(mask,j) = wj - Mj * S(:,j); 56 | end 57 | 58 | % Compute initial fit error (initial cost f(X)) 59 | rmse(1) = sqrt( nanmean( R(VALID(:)).^2 )); 60 | if (verbose) 61 | fprintf('\n\ni = 0 \t RMSE = %-15.10f \n', rmse(1) ) 62 | end 63 | 64 | % Main loop 65 | 66 | for iter = 1:numIter 67 | 68 | % (1) calculate Gradient and Jacobian (J'J) approx to Hessian (Gauss-Newton) 69 | g(:) = 0; H(:) = 0; 70 | for j = 1:n 71 | mask = VALID(:,j); 72 | Mj = M(mask,:); 73 | Bj = B(mask,:); 74 | sj = S(:,j); 75 | rj = R(mask,j); 76 | 77 | %PjBj = Bj - Mj * (pinv(Mj)*Bj); % (I-Pj)*Bj 78 | PjBj = Bj - Mj * (Mj\Bj); % (I-Pj)*Bj 79 | Jj = xt_pg_kronmex(sj', PjBj); 80 | g = g - (rj' * Jj)'; 81 | %H = H + Jj'*Jj; % slow 82 | H = H + xt_pg_kronmex(sj*sj', Bj'*PjBj); % faster 83 | 84 | %g = g - reshape((Bj'*rj)*sj', [], 1); 85 | end 86 | H = 0.5 * (H + H'); % force H symmetric 87 | 88 | % (2) Repeat solving for vec_dX until f(X-dX) < f(X) or converged 89 | while true 90 | %vec_dX = pinv(H + delta*Idr) * g; 91 | vec_dX = (H + delta*Idr) \ g; 92 | newX = X - reshape(vec_dX, d, r); 93 | % [newX,foo] = qr(newX,0); 94 | [newX, ~] = qr(newX,0); 95 | 96 | % Compute new factors 97 | M = B * newX; 98 | for j = 1:n 99 | mask = VALID(:,j); 100 | wj = W(mask,j); 101 | Mj = M(mask,:); 102 | %S(:,j) = pinv(Mj) * wj; 103 | S(:,j) = Mj \ wj; 104 | R(mask,j) = wj - Mj * S(:,j); 105 | end 106 | 107 | % Evaluate cost f(newX) 108 | R2 = R(VALID(:)).^2; 109 | max_err = sqrt(nanmax( R2(:) )); 110 | rmse(iter+1) = sqrt(nanmean( R2(:) )); 111 | 112 | % Damping termination tests 113 | if (rmse(iter+1) < rmse(iter)), OK = true; break, end 114 | 115 | % Continue damping of H? 116 | delta = delta * 10; 117 | if (delta > 1.0e30), OK = false; break, end 118 | end % end while 119 | % Error test (bailed out with no descent) 120 | if (~OK), disp('Error: cannot find descent direction!'), break, end 121 | 122 | % (3) Book-keeping; display new errors 123 | delta = max( delta / 100, 1.0e-20); 124 | X = newX; 125 | 126 | % (4) Display new error and test convergence 127 | if (verbose) 128 | fprintf('i = %-4d RMSE = %-9.6f (max %-9.6f) l = 1.0e%03d\n', ... 129 | iter, rmse(iter+1), max_err, fix(log10(delta)) ) 130 | end 131 | if (rmse(iter) - rmse(iter+1) < RMSE_TOL), break, end 132 | end 133 | 134 | % Truncate vector of RMSE values 135 | iter = iter + 1; 136 | rmse = rmse(iter); 137 | 138 | warning('on', 'MATLAB:nearlySingularMatrix'); % OK! Damping fixes Hessian 139 | 140 | % ---------------------------------------------------------------------------- 141 | 142 | end -------------------------------------------------------------------------------- /Code/Algorithms/PG/xt_pg_kronmex.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009, Mayowa 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in 13 | the documentation and/or other materials provided with the distribution 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mex.h" 29 | 30 | /* 31 | * Arguments: 32 | * double *MatC Pointer to matrix MatC, the 33 | * Kronecker product of the two matrix arguments in the 34 | * order they appear. The matrix C should be declared 35 | * double C[rowsa*rowsb][colsa*colsb] in the calling 36 | * routine. 37 | * double *A Pointer to the first matrix in the Kronecker product. 38 | * int rowsa The number of rows of A. 39 | * int colsa The number of cols of A. 40 | * double *B Pointer to the second matrix in the Kronecker product. 41 | * int rowsb The number of rows of B. 42 | * int colsb The number of cols of B. 43 | * 44 | * Return Values: Void // 45 | */ 46 | 47 | 48 | void kron(double *MatC, double *MatA, int rowsa, int colsa, double *MatB, int rowsb, int colsb) { 49 | int i, j, k, l, t, u, z=rowsa*rowsb, y=colsa*colsb; 50 | 51 | for (i = 0; i < z; i += rowsb){ 52 | for (j = 0; j < y; j +=colsb){ 53 | for (k = 0; k < rowsb; k++){ 54 | for (l = 0; l < colsb; l++){ 55 | t = i/rowsb; 56 | u = j/colsb; 57 | *(MatC + (j +l)*z + i + k) = *(MatB + l * rowsb + k) * *(MatA + u * rowsa + t); 58 | } 59 | } 60 | } 61 | } 62 | 63 | } 64 | 65 | 66 | void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { 67 | double *MatA, *MatB, *MatC; 68 | 69 | mwSize rowsa, colsa, rowsb, colsb; 70 | 71 | /* check for proper number of arguments */ 72 | if(nrhs!=2) 73 | mexErrMsgTxt("Two inputs required."); 74 | else if(nlhs > 2) 75 | mexErrMsgTxt("Too many output arguments."); 76 | 77 | /* create a pointer to the input vector r */ 78 | MatA = mxGetPr(prhs[0]); 79 | MatB = mxGetPr(prhs[1]); 80 | 81 | /* get the dimensions of the matrix input y */ 82 | rowsa = mxGetM(prhs[0]); 83 | colsa = mxGetN(prhs[0]); 84 | rowsb = mxGetM(prhs[1]); 85 | colsb = mxGetN(prhs[1]); 86 | 87 | /* set the output pointer to the output matrix */ 88 | plhs[0] = mxCreateDoubleMatrix(rowsa * rowsb , colsa * colsb, mxREAL); 89 | 90 | /* create a C pointer to a copy of the output matrix */ 91 | MatC = mxGetPr(plhs[0]); 92 | 93 | /* call the C subroutine */ 94 | kron(MatC, MatA, rowsa, colsa, MatB, rowsb, colsb); 95 | return; 96 | } 97 | -------------------------------------------------------------------------------- /Code/Algorithms/RC/xt_rc_alm.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter] = xt_rc_alm(M, W, r, U, V, varargin) 2 | % XT_RC_ALM 3 | % Cabral's Augmented Lagrangian Method (ALM) 4 | % Solves || W .* (M - Z) ||_F ^ 2 + (rho / 2) * || Z - U * V' ||_F ^ 2 5 | % s.t. Z = U V' by relaxing the constraint. 6 | % Newly implemented based on the author's description. 7 | 8 | %% Fetch option parameters. 9 | opts = au_opts( ... 10 | 'nu=0', ... % regularization parameter 11 | 'max_iter=300', ... % maximum number of iterations 12 | 'max_trials=50', ... % maximum number of trials for each iteration 13 | 'tol=1e-10', ... % function tolerance 14 | 'display=1', ... % whether to display progress 15 | varargin{:} ); 16 | 17 | %% Initialize parameters. 18 | [m, n] = size(M); 19 | cost_best = inf; 20 | error_best = inf; 21 | widx = W(:)~=0; 22 | M(~widx) = 100; 23 | gamma = 1.01; % penalty increasing proportion 24 | tau = 0.5; % minimum required error drop 25 | rho = 0.5; % initial penalty parameter 26 | Y = zeros(m, n); % initial Lagrangian dual variables 27 | Z = U * V'; % initially the constraint is satisfied. 28 | 29 | %% Algorithm body 30 | for iter = 1 : opts.max_iter 31 | 32 | % Update U, V and Z until || Z - U V' ||_F ^ 2 reaches some criteria. 33 | for trial = 1 : opts.max_trials 34 | 35 | Z2 = Z + Y / rho; 36 | if opts.nu ~= 0, 37 | U = Z2 * V / (V' * V + (opts.nu / rho) * speye(r)); 38 | V = Z2' * U / (U' * U + (opts.nu / rho) * speye(r)); 39 | else 40 | U = Z2 / V'; 41 | V = (U \ Z2)'; 42 | end 43 | X = U * V' - Y / rho; 44 | 45 | % Z(~widx) = X(~widx); 46 | % Z(widx) = (2 * M(widx) + rho * X(widx)) / (2 + rho); 47 | Z = W .* ((2 + rho) \ (2 * M + rho * X)) + (~W) .* X; 48 | 49 | % Stopping criteria: check the size of || Z - U V' ||_F ^ 2 50 | R = Z - U * V'; 51 | error = norm(R(:), 'fro') ^ 2; 52 | if error <= tau * error_best 53 | error_best = error; 54 | break 55 | end 56 | end 57 | 58 | % Update dual variables. 59 | Y = Y + rho * R; 60 | % Lambda = max(zeros(m, n), min(Lambda + rho * R, 1e+20 * ones(m, n))); 61 | 62 | % Increase the penalty proportion. 63 | rho = min(gamma * rho, 1e+20); 64 | 65 | % Compute cost. 66 | cost = norm(W .* (M - U * V'), 'fro') ^ 2; 67 | if opts.display, 68 | fprintf('[%04d] %.6d (rank: %02d)\n', iter, sqrt(cost / sum(W(:) ~= 0)), r); 69 | end 70 | 71 | % Stopping criteria: check the cost 72 | cost_alm = cost + trace(Y' * (Z - U * V')) + rho / 2 * error; 73 | if opts.nu, 74 | cost_alm = cost_alm + 0.5 * opts.nu * (norm(U, 'fro') ^ 2 + norm(V, 'fro') ^ 2); 75 | end 76 | if abs(cost_alm - cost_best) < opts.tol, break 77 | else cost_best = cost_alm; 78 | end 79 | end 80 | 81 | end -------------------------------------------------------------------------------- /Code/Algorithms/RC/xt_rc_rcalm.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter] = xt_rc_rcalm(M, W, r, U, V, varargin) 2 | % XT_RC_ALM 3 | % Cabral's Augmented Lagrangian Method (ALM) with rank continuation 4 | % Solves || W .* (M - Z) ||_F ^ 2 + (rho / 2) * || Z - U * V' ||_F ^ 2 5 | % s.t. Z = U V' by relaxing the constraint. 6 | % Initially, rank(U) and rank(V) is set to be 3 higher than predicted. 7 | % Newly implemented based on the author's description. 8 | 9 | %% Fetch option parameters. 10 | opts = au_opts( ... 11 | 'nu=0', ... % regularization parameter 12 | 'max_iter=300', ... % maximum number of iterations 13 | 'max_trials=50', ... % maximum number of trials for each iteration 14 | 'tol=1e-10', ... % function tolerance 15 | 'display=1', ... % whether to display progress 16 | 'r_init=0', ... % initial rank 17 | varargin{:} ); 18 | 19 | %% Initialize parameters. 20 | [m, n] = size(W); 21 | M(W==0) = 100; 22 | 23 | % Set initial rank. 24 | if ~opts.r_init, 25 | r_init = min(r + 2, min(m, n)); 26 | end 27 | [U, V, cost, iter] = xt_rc_alm(M, W, r_init, U, V, varargin{:}); 28 | Z = U * V'; 29 | 30 | %% Rank-reduce 31 | for rr = r_init - 1 : -1 : r 32 | [U, S, V] = svd(Z, 'econ'); 33 | s = diag(S); 34 | S = diag(sparse(sqrt(s(1 : rr, 1)))); 35 | U = U(:, 1 : rr) * S; 36 | V = V(:, 1 : rr) * S; 37 | % [U, ~] = qr(U, 0); 38 | [U, V, cost, iter_rr] = xt_rc_alm(M, W, rr, U, V, varargin{:}); 39 | iter = iter + iter_rr; 40 | Z = U * V'; 41 | 42 | end 43 | 44 | end 45 | 46 | -------------------------------------------------------------------------------- /Code/Algorithms/TO/xt_to_dw.m: -------------------------------------------------------------------------------- 1 | function [U, V, err, iter, err_log, lambda_log] = xt_to_dw(Y, H, r, Vini, tol, max_iter, disp_level) 2 | % The damped Wiberg algorithm for low-rank matrix factorization Y -> U V' 3 | % 4 | % [U, V] = damped_wiberg(Y, H, r) factorizes the data matrix Y (say m x n) 5 | % into the product of U (m x r) and V (n x r). Note that r specifies the 6 | % column size of U and V. The indicator H that has the same size as Y 7 | % specifies non-missing/missing components, i.e., H(i, j) = 0 if Y(i, j) is 8 | % missing and H(i, j) = 1 if Y(i, j) is non-missing. 9 | % 10 | % [U, V] = damped_wiberg(Y, H, r, Vini) uses Vini as initial values of V 11 | % instead of random initial values. 12 | % 13 | % [U, V] = damped_wiberg(Y, H, r, Vini, tol, max_iter) further uses tol 14 | % instead of the default torelance (1e-6) and max_iter instead of the 15 | % default maximum iteration counts (500). 16 | % 17 | % The implementation is based on: 18 | % [1] Takayuki Okatani, Takahiro Yoshida, Koichiro Deguchi: Efficient 19 | % algorithm for low-rank matrix factorization with missing components 20 | % and performance comparison of latest algorithms. Proc, ICCV 2011: 21 | % 842-849. 22 | 23 | if nargin < 4, Vini = randn(size(Y,2), r); 24 | end 25 | if nargin < 6, max_iter = 100; 26 | end 27 | if nargin < 5, tol = 1e-6; 28 | end 29 | if nargin < 7, disp_level = 1; 30 | end 31 | 32 | %========================================================================== 33 | % Check dimensions 34 | %========================================================================== 35 | %m = size(Y,1); 36 | %n = size(Y,2); 37 | [m, n] = size(Y); 38 | 39 | % Make the vector u & v from the matrix U & V 40 | Vt = Vini.'; 41 | v = Vt(:); 42 | 43 | %========================================================================== 44 | % Count the number of nonmissing components 45 | %========================================================================== 46 | p = 0; 47 | Findx = zeros(m+1, 1); 48 | count = 1; 49 | for i=1:m 50 | Findx(i) = count; 51 | for j=1:n 52 | if H(i,j) > 0, 53 | count = count + 1; 54 | p = p + 1; 55 | end 56 | end 57 | end 58 | Findx(m+1) = p+1; 59 | 60 | %disp(sprintf('Number of nonmissing components = %d\n', p)); 61 | 62 | % F = zeros(p, m*r); 63 | % G = zeros(p, n*r); 64 | y = zeros(p, 1); 65 | 66 | % F -> FQ*FR (QR-decomposition) 67 | Fpack = zeros(p, r); % dense matrix 68 | FQpack = zeros(p, r); % dense matrix 69 | FRpack = zeros(m*r, r); % submatrices are upper-triangular 70 | 71 | % Make y from Y 72 | q = 1; 73 | for i=1:m 74 | for j=1:n 75 | if H(i,j) > 0.5, 76 | y(q) = Y(i,j); 77 | q = q+1; 78 | end 79 | end 80 | end 81 | 82 | lambda = 0.1; 83 | 84 | %log 85 | err_log = zeros(max_iter,1); 86 | lambda_log = zeros(max_iter,1); 87 | 88 | %========================================================================== 89 | % Start of the loop 90 | %========================================================================== 91 | err_last = 0.; 92 | for iter = 1:max_iter 93 | 94 | % orthogonalize v 95 | 96 | % if 0 97 | % V1 = zeros(n, r); 98 | % for j=1:n 99 | % V1(j,:) = v((j-1)*r+1:j*r)'; 100 | % end 101 | % [V1, V1r] = qr(V1,0); 102 | % % v = V1'(:); 103 | % for j=1:n 104 | % v((j-1)*r+1:j*r) = V1(j,:)'; 105 | % end 106 | % end 107 | 108 | q = 1; 109 | for i=1:m 110 | for j=1:n 111 | if H(i,j) > .5, 112 | Fpack(q, :) = v((j-1)*r+1:(j-1)*r+r); 113 | q = q + 1; 114 | end 115 | end 116 | end 117 | 118 | % QR decomposition of F 119 | for i=1:m 120 | [FQpack(Findx(i):Findx(i+1)-1,1:r), FRpack((i-1)*r+1:(i-1)*r+r,1:r)] = qr(Fpack(Findx(i):Findx(i+1)-1,1:r), 0); 121 | end 122 | 123 | % 2.2 Compute u minimizing |Fu - y|^2 124 | [u, err, errvec] = update_u(Fpack, Findx, FQpack, FRpack, y, m, r); 125 | 126 | %disp(sprintf('norm u= %f norm v=%f', norm(u), norm(v))); 127 | 128 | % 3. Check convergence 129 | if disp_level ~= 0 130 | err_log(iter) = err; 131 | lambda_log(iter) = lambda; 132 | fprintf('[%04d] %.2e %.4e\n', iter, lambda, err); 133 | drawnow 134 | end 135 | if abs(err - err_last) < tol*err, break 136 | end 137 | err_last = err; 138 | 139 | % Make G'QFG = G'G - (Q1'G)'(Q1'G) 140 | Gyvec = zeros(n*r,1); 141 | % Gyvec1 = zeros(m*r,1); 142 | %GQG = zeros(n*r,n*r); 143 | QG = zeros(m*r,n*r);%sparse(m*r,n*r); 144 | 145 | 146 | for i=1:m 147 | q =1; 148 | FQpack2 = FQpack(Findx(i):Findx(i+1)-1,:)'; 149 | FQv = FQpack2(:); 150 | ui = u((i-1)*r+1:i*r); 151 | tmp2 = FQv * ui'; 152 | for j=1:n 153 | if H(i,j) ~= 0, 154 | % tmp2 = FQpack(Findx(i)-1+q,:)'*u((i-1)*r+1:i*r)'; 155 | % for l=1:r 156 | % for k=1:r 157 | % QG((i-1)*r+k,(j-1)*r+l) = tmp2(k,l); 158 | % end 159 | % end 160 | %QG((i-1)*r+1:i*r,(j-1)*r+1:j*r) = FQpack(Findx(i)-1+q,:)'*u((i-1)*r+1:i*r)'; 161 | QG((i-1)*r+1:i*r,(j-1)*r+1:j*r) = tmp2((q-1)*r+1:q*r,:); 162 | %Gyvec((j-1)*r+1:j*r) = Gyvec((j-1)*r+1:j*r) + y(Findx(i)-1+q) * ui; 163 | % Gyvec = G'*e; 164 | Gyvec((j-1)*r+1:j*r) = Gyvec((j-1)*r+1:j*r) + errvec(Findx(i)-1+q) * ui; 165 | q = q + 1; 166 | end 167 | end 168 | %Gyvec1((i-1)*r+1:i*r) = FQpack(Findx(i):Findx(i+1)-1,1:r)'*y(Findx(i):Findx(i+1)-1); 169 | %Gyvec1((i-1)*r+1:i*r) = FQpack2*y(Findx(i):Findx(i+1)-1); 170 | end 171 | 172 | % GQG = -QG'*QG; 173 | % Gyvec = Gyvec-QG'*Gyvec1; 174 | QGs = sparse(QG); 175 | GQG = -QGs'*QGs; 176 | %Gyvec = Gyvec-QGs'*Gyvec1; 177 | for j=1:n 178 | tmp1 = zeros(r,r); 179 | %q = 1; 180 | for i=1:m 181 | if H(i,j) > 0.5, 182 | tmp1 = tmp1 + u((i-1)*r+1:i*r)*u((i-1)*r+1:i*r)'; 183 | end 184 | end 185 | GQG((j-1)*r+1:j*r,(j-1)*r+1:j*r) = GQG((j-1)*r+1:j*r,(j-1)*r+1:j*r) + tmp1; 186 | end 187 | 188 | % Make M from v 189 | M = zeros(n*r, r*r); 190 | for i=1:r 191 | for j=1:r 192 | for k=1:n 193 | M((k-1)*r+j, (i-1)*r+j) = v((k-1)*r+i); 194 | end 195 | end 196 | end 197 | [Mq, ~] = qr(M,0); 198 | 199 | % 4.2 Determine dv minimizing |QF G dv - QF y|^2 200 | 201 | gauss_newton = 0; 202 | while 1 203 | % Amat = zeros(size(G,2),size(G,2)); 204 | if gauss_newton == 1, 205 | Amat = GQG + Mq*Mq'; 206 | else 207 | % Marquardt version 208 | %Amat = GQG + Mq*Mq' + lambda*diag(diag(GQG + Mq*Mq')); 209 | % original (Levenberg) 210 | Amat = GQG + Mq*Mq' + lambda*diag(ones(size(Mq,1),1)); 211 | 212 | %Amat = G'*QFG + Mq*Mq' + lambda*diag(ones(size(Mq,1),1)); 213 | %Amat = GQG + M*M' + lambda*diag(ones(size(Mq,1),1)); 214 | %Amat = GQG + lambda*diag(ones(size(Mq,1),1)); 215 | %Amat = GQG + Mq*Mq' + lambda*(diag(ones(size(Mq,1),1)) - Mq*Mq'); 216 | %bvec = zeros(size(Amat,1),1); 217 | %bvec = G'*QFy; 218 | end 219 | bvec = Gyvec; 220 | 221 | %{ 222 | R = chol(Amat); 223 | dv3 = R'\bvec; 224 | dv2 = R\dv3; 225 | %} 226 | dv2 = Amat \ bvec; 227 | 228 | % v1 = zeros(size(v)); 229 | v1 = v + dv2; 230 | 231 | % 2.1 Make F from v 232 | q = 1; 233 | for i=1:m 234 | for j=1:n 235 | if H(i,j) > 0, 236 | Fpack(q, :) = v1((j-1)*r+1:(j-1)*r+r); 237 | q = q + 1; 238 | end 239 | end 240 | end 241 | 242 | % QR decomposition of F 243 | for i=1:m 244 | [FQpack(Findx(i):Findx(i+1)-1,1:r), FRpack((i-1)*r+1:(i-1)*r+r,1:r)] = qr(Fpack(Findx(i):Findx(i+1)-1,1:r), 0); 245 | end 246 | 247 | % 2.2 Compute u minimizing |Fu - y|^2 248 | [u, err] = update_u(Fpack, Findx, FQpack, FRpack, y, m, r); 249 | 250 | if gauss_newton == 1 251 | break; 252 | end 253 | 254 | %disp(sprintf('[***] %e %e', lambda, err)); 255 | 256 | if err > err_last, 257 | lambda = lambda * 10; 258 | else 259 | lambda = lambda * 0.1; 260 | break; 261 | end 262 | end 263 | 264 | %disp(sprintf('lambda = %e', lambda)); 265 | 266 | % 4.3 Update v 267 | v = v + dv2; 268 | end 269 | 270 | % Recover U and V from u and v 271 | U = zeros(m, r); 272 | V = zeros(n, r); 273 | for i=1:m 274 | U(i,:) = u((i-1)*r+1:(i-1)*r+r); 275 | end 276 | for j=1:n 277 | V(j,:) = v((j-1)*r+1:(j-1)*r+r); 278 | end 279 | 280 | 281 | function [u, err, errvec] = update_u(Fpack, Findx, Q1pack, R1pack, y, m, r) 282 | % Solve |Fu-y|^2 -> min for u 283 | % R1 u = Q1' y 284 | % F = F(v) = (expansion of Q1pack)*(exp. of R1pack) 285 | 286 | u = zeros(m*r,1); 287 | 288 | for i=1:m 289 | %disp(sprintf('%e', R1pack((i-1)*r+1,1)/R1pack((i-1)*r+r,r))); 290 | 291 | x = R1pack((i-1)*r+1:i*r,1:r) \ Q1pack(Findx(i):Findx(i+1)-1,1:r)'*y(Findx(i):Findx(i+1)-1); 292 | u((i-1)*r+1:i*r) = x; 293 | end 294 | 295 | errvec = zeros(size(y)); 296 | for i=1:m 297 | errvec(Findx(i):Findx(i+1)-1) = y(Findx(i):Findx(i+1)-1) - Fpack(Findx(i):Findx(i+1)-1,1:r)*u((i-1)*r+1:i*r); 298 | end 299 | err = errvec.'*errvec; 300 | -------------------------------------------------------------------------------- /Code/Experiment/je_analyze_results.m: -------------------------------------------------------------------------------- 1 | function je_analyze_results(dataset, r, varargin) 2 | % JE_ANALYZE_RESULTS 3 | 4 | % These flags say which methods to run 5 | %% OPTIONS 6 | opts = au_opts(... 7 | 'sample_start=1;sample_end=0;num_samples=100', ... 8 | 'tol=1e-6', ... 9 | 'csv=0', ... 10 | 'display=1', ... 11 | 'output_filename=0', ... 12 | 'folder_suffix=0', ... 13 | 'display_reconstructions=0', ... 14 | 'save_reconstructions=0', ... 15 | varargin{:}); 16 | 17 | alg_names = { 18 | % Latest custom-coded algorithms 19 | 'ALS', 'New' % Alternation (Undamped RW3) 20 | 'DW', 'New' % Damped Wiberg (Damped RW2 + projection constraint) 21 | 'DRW1_UWOFF', '(NO UW)' 22 | 'DRW1', '(UW)' 23 | 'DRW1P_UWOFF', '(NO UW)' 24 | 'DRW1P', '(UW)' % Damped RW1 with projection constraint (Damped RW1 + projection constraint + retraction) 25 | 'DRW2_UWOFF', '(NO UW)' 26 | 'DRW2', '(UW)' 27 | 'DRW2P_UWOFF', '(NO UW)' 28 | 'DRW2P', '(UW)' % Damped RW2 with projection constraint (Damped RW2 + projection constraint + retraction) 29 | % External algorithms 30 | 'PG_CSF', 'Orig.' % Gotardo's CSF (Damped RW2) 31 | 'TO_DW', 'Orig.' % Okatani's Damped Wiberg (Damped RW2 + projection constraint) 32 | 'CH_LM_S', 'Mod.' % Chen's LM_S (Damped full Newton) 33 | 'CH_LM_S_GN', 'Mod.' % Chen's LM_S modified to use GN-matrix (Damped RW1) 34 | 'CH_LM_S_RW2', 'Mod.' % Chen's LM_S modified to use approximate GN-matrix (Damped RW2) 35 | 'CH_LM_M', 'Mod.' % Chen's LM_M (Reduced damped full Newton) 36 | 'CH_LM_M_GN', 'Mod.' % Chen's LM_M_GN (Reduced damped RW1) 37 | 'CH_LM_M_RW2', 'Mod.' % Chen's LM_M modified to use approximate GN-matrix (Reduced damped RW2) 38 | 'CO_LM_S', 'Orig.' % Chen's original implementation of LM_S 39 | 'CO_LM_M', 'Orig.' % Chen's original implementation of LM_M 40 | 'CO_LM_M_GN', 'Orig.' % Chen's original implementation of LM_M_GN 41 | 'NB_RTRMC', 'Orig.' % Boumal's original implementation of RTRMC 42 | 'RC_ALM', 'Unopt.' % Cabral's ALM (New but unoptimized implementation) 43 | 'RC_RCALM', 'Unopt.' % Cabral's ALM with rank continuation (New but unoptimized implementation) 44 | 'DB_BALM', 'Unopt.' % Del Bue's BALM (New but unoptimized implementation) 45 | 'DO_BALM', 'Al. Orig.' % Del Bue's "almost-original" implementation of BALM with few tweaks 46 | % Latest Ceres-solver LMs 47 | 'CE_LM', 'Ceres' % CERES LM 48 | 'CE_LMI', 'Ceres' % CERES LM INNER 49 | 'CE_ALM', 'Ceres' % 10 ALS + CE_LM 50 | 'CE_ALMI', 'Ceres' % 10 ALS + CE_LM_I 51 | 'CE_ARULM', 'Ceres' % 10 reg. ALS + reg. CE_LM + unreg. CE_LM 52 | 'CE_ARULMI', 'Ceres' % 10 reg. ALS + reg. CE_LMI + unreg. CE_LMI 53 | % New Ceres algorithms 54 | 'CE_L', 'Ceres' 55 | 'CE_EPI', 'Ceres' 56 | 'CE_LEPI', 'Ceres' 57 | 'CE_RW2', 'Ceres' 58 | 'CE_LRW2', 'Ceres' 59 | 'CE_LRW2_no_BQR', 'Ceres' 60 | 'CE_LRW2_ALS', 'Ceres' 61 | 'CE_LRW2_no_Init', 'Ceres' 62 | }; 63 | 64 | % Make the first letter of the dataset name uppercase. 65 | dataset = regexprep(lower(dataset),'(\<[a-z])','${upper($1)}'); 66 | 67 | % Add folder suffix 68 | file = 'Results'; 69 | if opts.folder_suffix ~= 0, file = [file, '_', opts.folder_suffix]; 70 | end 71 | 72 | if ~opts.output_filename, 73 | file = [file, '/', dataset, '/', lower(dataset), '_r', int2str(r)]; 74 | else 75 | file = [file, '/', dataset, '/', lower(opts.output_filename), '_r', int2str(r)]; 76 | end 77 | 78 | % If a previous version of the file exists, load it. 79 | if exist([file, '.mat'], 'file') == 2, load(file); 80 | else 81 | error('The specified results file does not exist.'); 82 | end 83 | 84 | best_min = nan; 85 | N = size(alg_names, 1); 86 | 87 | % Create a list of used algorithms and search for the best optimum. 88 | j = 1; 89 | alg_list.idx = zeros(N, 1); 90 | for i=1:N 91 | alg_name = alg_names{i,1}; 92 | if isfield(opts, (alg_name)) 93 | if opts.(alg_name) 94 | alg_list.idx(j) = i; 95 | j = j + 1; 96 | 97 | % If the specified algorithm does not exist, continue with the rest 98 | % of the for loop. 99 | if ~isfield([lrmf_results.(dataset)], (alg_name)), continue 100 | end 101 | 102 | if opts.sample_end == 0, 103 | alg = [lrmf_results.(dataset)(opts.sample_start:end).(alg_name)]; 104 | else 105 | alg = [lrmf_results.(dataset)(opts.sample_start:opts.sample_end).(alg_name)]; 106 | end 107 | if (isnan(best_min)) || (best_min > min([alg.cost])), best_min = min([alg.cost]); 108 | end 109 | end 110 | end 111 | end 112 | alg_list.idx = alg_list.idx(alg_list.idx ~= 0); 113 | N = length(alg_list.idx); 114 | 115 | % Initialize arrays list.algorithms = algorithms{alg_idx,1}; 116 | 117 | alg_list.runs.all = nan(N,1); 118 | alg_list.runs.conv = nan(N,1); 119 | alg_list.iters.all.mean = nan(N,1); 120 | alg_list.iters.all.median = nan(N,1); 121 | alg_list.iters.success.mean = nan(N,1); 122 | alg_list.iters.success.median = nan(N,1); 123 | alg_list.iters.fail.mean = nan(N,1); 124 | alg_list.iters.fail.median = nan(N,1); 125 | alg_list.runtime.all.mean = nan(N,1); 126 | alg_list.runtime.all.median = nan(N,1); 127 | alg_list.runtime.success.mean = nan(N,1); 128 | alg_list.runtime.success.median = nan(N,1); 129 | alg_list.runtime.fail.mean = nan(N,1); 130 | alg_list.runtime.fail.median = nan(N,1); 131 | alg_list.ips.all.mean = nan(N,1); 132 | alg_list.ips.all.median = nan(N,1); 133 | alg_list.RUSSO.conv = nan(N,1); 134 | alg_list.RUSSO.all = nan(N,1); 135 | alg_list.RUSSO.optima = cell(N, 1); 136 | alg_list.RUSSO.runtime = cell(N,1); 137 | alg_list.triple_RUSSO.conv = nan(N, 1); 138 | alg_list.triple_RUSSO.all = nan(N, 1); 139 | alg_list.triple_RUSSO.optima = cell(N, 1); 140 | alg_list.triple_RUSSO.runtime = cell(N,1); 141 | alg_list.TSS.mean = nan(N,1); 142 | alg_list.TSS.std = nan(N,1); 143 | alg_list.TSS.mean_est = nan(N,1); 144 | alg_list.TSS.std_est = nan(N,1); 145 | alg_list.TRUSSO.mean = nan(N, 1); 146 | alg_list.TRUSSO.std = nan(N, 1); 147 | 148 | for i=1:N 149 | alg_name = alg_names{alg_list.idx(i),1}; 150 | 151 | % If the specified algorithm exists, do the calculations. Otherwise, 152 | % continue with the rest of the for loop. 153 | if opts.(alg_name) && isfield([lrmf_results.(dataset)], (alg_name)) 154 | if opts.sample_end == 0 155 | alg = [lrmf_results.(dataset)(opts.sample_start:end).(alg_name)]; 156 | else 157 | alg = [lrmf_results.(dataset)(opts.sample_start:opts.sample_end).(alg_name)]; 158 | end 159 | alg_conv = [alg.cost] - best_min < opts.tol *best_min; 160 | alg_all_iters = [alg.iters]; 161 | alg_success_iters = alg_all_iters(alg_conv); alg_fail_iters = alg_all_iters(~alg_conv); 162 | alg_all_runtime = [alg.runtime]; 163 | alg_success_runtime = alg_all_runtime(alg_conv); 164 | alg_fail_runtime = alg_all_runtime(~alg_conv); 165 | 166 | alg_list.runs.all(i) = sum([alg.cost]>0); 167 | alg_list.runs.conv(i) = sum(alg_conv); 168 | alg_list.iters.all.mean(i) = mean(alg_all_iters); 169 | alg_list.iters.all.median(i) = median(alg_all_iters); 170 | alg_list.iters.success.mean(i) = mean(alg_success_iters); 171 | alg_list.iters.success.median(i) = median(alg_success_iters); 172 | alg_list.iters.fail.mean(i) = mean(alg_fail_iters); 173 | alg_list.iters.fail.median(i) = median(alg_fail_iters); 174 | alg_list.runtime.all.mean(i) = mean(alg_all_runtime); 175 | alg_list.runtime.all.median(i) = median(alg_all_runtime); 176 | alg_list.runtime.success.mean(i) = mean(alg_success_runtime); 177 | alg_list.runtime.success.median(i) = median(alg_success_runtime); 178 | alg_list.runtime.fail.mean(i) = mean(alg_fail_runtime); 179 | alg_list.runtime.fail.median(i) = median(alg_fail_runtime); 180 | alg_list.ips.all.mean(i) = mean(alg_all_iters ./ alg_all_runtime); 181 | alg_list.ips.all.median(i) = median(alg_all_iters ./ alg_all_runtime); 182 | 183 | p = alg_list.runs.conv(i) / alg_list.runs.all(i); 184 | 185 | % If no success run is found, estimate the lower bound of time to 2 186 | % successes. 187 | if p == 0, 188 | p = 1 / (alg_list.runs.all(i) + 1); 189 | alg_list.runtime.success.mean(i) = 0; 190 | end 191 | 192 | % RUSSO-X 193 | alg_list.RUSSO.optima{i} = nan(alg_list.runs.all(i) - 1, 1); 194 | alg_list.RUSSO.runtime{i} = nan(alg_list.runs.all(i) - 1, 1); 195 | if alg_list.runs.conv(i) >= 2, 196 | % Randperm RUSSO 197 | if alg_list.runs.all(i) < 10, 198 | % If the number of runs < 10, compute randperm runs up to 10. 199 | max_runs = min(10, factorial(alg_list.runs.all(i))); 200 | else 201 | max_runs = 100; 202 | end 203 | for j = 1 : max_runs 204 | % RUSSO optimum 205 | [alg_list.RUSSO.optima{i}(j), alg_list.RUSSO.runtime{i}(j)] = je_compute_russo([alg.cost], [alg.runtime], j, sprintf('tol=%e', opts.tol)); 206 | 207 | % Triple-RUSSO optimum 208 | num_extra_russos = 2; 209 | extra_russo_opts = nan(1, num_extra_russos); 210 | extra_russo_times = nan(1, num_extra_russos); 211 | for k = 1 : num_extra_russos 212 | [extra_russo_opts(k), extra_russo_times(k)] = je_compute_russo([alg.cost], [alg.runtime], j + k * max_runs, sprintf('tol=%e', opts.tol)); 213 | end 214 | alg_list.triple_RUSSO.optima{i}(j) = min([alg_list.RUSSO.optima{i}(j), extra_russo_opts]); 215 | alg_list.triple_RUSSO.runtime{i}(j) = sum([alg_list.RUSSO.runtime{i}(j), extra_russo_times]); 216 | end 217 | 218 | % Mean time for RUSSO 219 | alg_list.TRUSSO.mean(i) = mean(alg_list.RUSSO.runtime{i}); 220 | alg_list.TRUSSO.std(i) = std(alg_list.RUSSO.runtime{i}); 221 | % No replacement RUSSO 222 | % for j = 1 : alg_list.runs.all(i) - 1 223 | % alg_list.RUSSO.optima{i}(j) = je_compute_russo([alg(j:end).cost]); 224 | % end 225 | % Compute how many have converged to the global minimum. 226 | alg_list.RUSSO.conv(i) = sum( ... 227 | abs(alg_list.RUSSO.optima{i} - best_min) < ... 228 | opts.tol * best_min); 229 | alg_list.RUSSO.all(i) = sum(~isnan(alg_list.RUSSO.optima{i})); 230 | alg_list.triple_RUSSO.conv(i) = sum( ... 231 | abs(alg_list.triple_RUSSO.optima{i} - best_min) < ... 232 | opts.tol * best_min); 233 | alg_list.triple_RUSSO.all(i) = sum(~isnan(alg_list.triple_RUSSO.optima{i})); 234 | end 235 | 236 | % TSS 237 | succ = find(([alg.cost] - best_min) < opts.tol * best_min); 238 | if length(succ) < 2, 239 | alg_list.TSS.mean(i) = sum([alg.runtime]); 240 | else 241 | k = succ(length(succ) - 1) - 1; 242 | TSS = zeros(k, 1); 243 | 244 | for ii = 1 : k 245 | next_2s = succ(succ >= ii); 246 | next_2s = next_2s(1:2); 247 | TSS(ii) = sum([alg(ii:next_2s(end)).runtime]); 248 | end 249 | alg_list.TSS.mean(i) = mean(TSS); 250 | alg_list.TSS.std(i) = std(TSS); 251 | end 252 | 253 | % TSS estimate using geometric distribution 254 | alg_list.TSS.mean_est(i) = 2 * alg_list.runtime.success.mean(i); 255 | % If fail mean is not nan, then add the mean fail time bit. 256 | if ~isnan(alg_list.runtime.fail.mean(i)) 257 | alg_list.TSS.mean_est(i) = alg_list.TSS.mean_est(i)... 258 | + 2 * (1 - p) / p * alg_list.runtime.fail.mean(i); 259 | end 260 | 261 | alg_list.TSS.std_est(i) = 0; 262 | % If fail is not nan, then compute the variance. 263 | if ~isnan(alg_list.runtime.fail.mean(i)) 264 | alg_list.TSS.std_est(i) = alg_list.runtime.fail.mean(i) * ... 265 | sqrt(2 * (1 - p)) / p; 266 | end 267 | % MTSSest = alg_list.TSS.mean_est(i); 268 | % STSSest = alg_list.TSS.std_est(i); 269 | end 270 | end 271 | 272 | % a = zeros(2, 1); 273 | % a(1) = alg_list.runtime.t2s.mean(i); 274 | % a(2) = alg_list.runtime.t2s.std(i); 275 | 276 | if opts.display 277 | fprintf(['\n--- [ ', dataset,' - rank ', num2str(r), ' ] --- \n']); 278 | end 279 | 280 | T = table(... 281 | alg_names(alg_list.idx, 1), ... 282 | ... % alg_names(alg_list.idx, 2), ... 283 | [ alg_list.runs.conv alg_list.runs.all], ... 284 | alg_list.runs.conv ./ alg_list.runs.all * 100, ... 285 | alg_list.runtime.all.median, ... 286 | alg_list.TSS.mean, ... 287 | alg_list.TSS.std, ... 288 | [ alg_list.RUSSO.conv alg_list.RUSSO.all], ... 289 | alg_list.RUSSO.conv ./ alg_list.RUSSO.all * 100, ... 290 | alg_list.TRUSSO.mean, ... 291 | alg_list.TRUSSO.std, ... 292 | [ alg_list.triple_RUSSO.conv alg_list.triple_RUSSO.all], ... 293 | alg_list.triple_RUSSO.conv ./ alg_list.triple_RUSSO.all * 100, ... 294 | alg_list.iters.success.median, ... 295 | alg_list.iters.fail.median, ... 296 | alg_list.iters.all.median, ... 297 | alg_list.runtime.success.median, ... 298 | alg_list.runtime.fail.median, ... 299 | alg_list.ips.all.median, ... 300 | alg_list.iters.success.mean, ... 301 | alg_list.iters.fail.mean, ... 302 | alg_list.iters.all.mean, ... 303 | alg_list.runtime.success.mean, ... 304 | alg_list.runtime.fail.mean, ... 305 | alg_list.runtime.all.mean, ... 306 | alg_list.ips.all.mean, ... 307 | 'VariableNames',{'ALGORITHM', 'CONV_RUNS', 'CONV_RATE', 'TIME_MED', 'M_TSS', 'STD_TSS', 'RUSSO_CONV', 'RUSSO_RATE', 'M_TRUSSO', 'STD_TRUSSO', 'TRIRUSSO_CONV', 'TRIRUSSO_RATE', 'SI_MED', 'FI_MED', 'ITERS_MED', 'ST_MED', 'FT_MED', 'IPS_MED', 'SI_AVR', 'FI_AVR', 'ITERS_AVR', 'ST_AVR', 'FT_AVR', 'TIME_AVR', 'IPS_AVR'}); 308 | 309 | if opts.display, disp(T(:, [1 2 3 4 5 8 9 10 11 12 13])); 310 | end 311 | 312 | if opts.csv 313 | writetable(T, [file, '.csv']); 314 | end 315 | 316 | if opts.display 317 | fprintf('Best minimum: \t\t%.6f\n', best_min); 318 | fprintf('Point of convergence: \t%.6f\n\n', best_min + opts.tol * best_min); 319 | end 320 | 321 | end 322 | -------------------------------------------------------------------------------- /Code/Experiment/je_compute_russo.m: -------------------------------------------------------------------------------- 1 | function [russo_optimum, russo_time] = je_compute_russo(cost_seq, time_seq, rand_seq, varargin) 2 | %UNTITLED Summary of this function goes here 3 | % Detailed explanation goes here 4 | 5 | opts = au_opts( ... 6 | 'base=1348032014', ... 7 | 'width=10', ... 8 | 'tol=1e-6', ... 9 | varargin{:}); 10 | 11 | if nargin > 2, 12 | if numel(rand_seq) == 1, 13 | % If sample number given, generate a random permutation sequence. 14 | rng(opts.base + rand_seq * opts.width, 'twister'); 15 | rand_seq = randperm(numel(cost_seq)); 16 | end 17 | if numel(cost_seq) == numel(rand_seq), 18 | % If the actual random permutation sequence is given, use it to sort. 19 | cost_seq = cost_seq(rand_seq); 20 | if nargout > 1 21 | % If we need to compute meantime, then 22 | time_seq = time_seq(rand_seq); 23 | end 24 | end 25 | end 26 | 27 | so_far_min = cost_seq(1); 28 | for n = 2 : numel(cost_seq) 29 | cost_diff = cost_seq(n) - so_far_min; 30 | if abs(cost_diff) < opts.tol * so_far_min, 31 | % If the cost difference is less than the function tolerance value, 32 | % stop and output russo_opt. 33 | russo_optimum = so_far_min; 34 | if nargout > 1, russo_time = sum(time_seq(1 : n)); 35 | end 36 | return 37 | elseif cost_diff < 0, 38 | % Else if the cost difference is less than 0, this means a better 39 | % optimum is found. Update so_far_min. 40 | so_far_min = cost_seq(n); 41 | end 42 | end 43 | 44 | % If fail, time = total time taken, the russo optimum is nan. 45 | russo_time = sum(time_seq); 46 | russo_optimum = nan; 47 | 48 | end 49 | 50 | -------------------------------------------------------------------------------- /Code/Experiment/je_external_wrapper.m: -------------------------------------------------------------------------------- 1 | function [U, V, cost, iter] = je_external_wrapper(M, W, r, U, varargin) 2 | % JE_EXTERNAL_WRAPPER 3 | % An implementation of the Variable Projection algorithm on Low-rank 4 | % Matrix Completion Problem. 5 | 6 | % tic 7 | %% PRE-PROCESSING 8 | % Fetch all the options. 9 | opts = au_opts( ... 10 | 'nu=0', ... % regularization parameter 11 | 'max_iter=300', ... % maximum number of iterations 12 | 'max_trials=50', ... % maximum number of trials 13 | 'tol=1e-10', ... % function tolerance 14 | 'display=1', ... % whether to display progress 15 | 'alg=0', ... % which algorithm to use 16 | 'r_init=0', ... % initial rank (for rank continuation-based approaches) 17 | varargin{:} ); 18 | 19 | % Fetch U if it has not been generated. 20 | if size(U, 1) == 1 && size(U, 2) == 1 21 | if opts.r_init 22 | % If rank-continuation used, set U to be of rank r_init. 23 | U = je_generate_sample(U, size(M, 1), size(M, 2), opts.r_init); 24 | else 25 | % Otherwise, set U to be of rank r. 26 | U = je_generate_sample(U, size(M, 1), size(M, 2), r); 27 | end 28 | end 29 | 30 | %% ALGORITHM SELECTION 31 | if strcmpi(opts.alg, 'PG_CSF') 32 | % Gotardo's CSF (Damped RW2): almost original 33 | M(W==0) = nan; 34 | [U, V, ~, cost, iter] = xt_pg_csf(M, r, U, opts.max_iter, opts.tol, opts.display); 35 | elseif strcmpi(opts.alg, 'TO_DW') 36 | % Okatani's DW (Damped RW2 + constraint B): almost original 37 | % Tolerance is doubled as the script uses absolute cost value. 38 | [V, U, cost, iter] = xt_to_dw(M', W', r, U, 2 * opts.tol, opts.max_iter, opts.display); 39 | cost = sqrt(cost / sum(W(:) ~= 0)); 40 | elseif strcmpi(opts.alg, 'CH_LM_S') 41 | % Chen's LM_S (Damped RW0): modified 42 | % Tolerance is doubled as the script uses absolute cost value. 43 | [cost, iter, U] = xt_ch_lm_s(M', W', r, U, opts.max_iter, 2 * opts.tol, opts.display); 44 | cost = sqrt(cost / sum(W(:) ~= 0)); 45 | V = NaN; 46 | elseif strcmpi(opts.alg, 'CH_LM_S_GN') 47 | % Chen's LM_S_GN (Damped RW1): newly implemented based on CH_LM_S 48 | % Tolerance is doubled as the script uses absolute cost value. 49 | [cost, iter, U] = xt_ch_lm_s_gn(M', W', r, U, opts.max_iter, 2 * opts.tol, opts.display); 50 | cost = sqrt(cost / sum(W(:) ~= 0)); 51 | V = NaN; 52 | elseif strcmpi(opts.alg, 'CH_LM_S_RW2') 53 | % Chen's LM_S_RW2 (Damped RW2): newly implemented based on CH_LM_S 54 | % Tolerance is doubled as the script uses absolute cost value. 55 | [cost, iter, U] = xt_ch_lm_s_rw2(M', W', r, U, opts.max_iter, 2 * opts.tol, opts.display); 56 | cost = sqrt(cost(end) / sum(W(:) ~= 0)); 57 | V = NaN; 58 | elseif strcmpi(opts.alg, 'CH_LM_M') 59 | % Chen's LM_M (Damped reduced RW0): modified 60 | % Tolerance is doubled as the script uses absolute cost value. 61 | [cost, iter, U] = xt_ch_lm_m(M', W', r, U, opts.max_iter, 2 * opts.tol, opts.display); 62 | cost = sqrt(cost(end) / sum(W(:) ~= 0)); 63 | V = NaN; 64 | elseif strcmpi(opts.alg, 'CH_LM_M_GN') 65 | % Chen's LM_M_GN (Damped reduced RW1): modified 66 | % Tolerance is doubled as the script uses absolute cost value. 67 | [cost, iter, U] = xt_ch_lm_m_gn(M', W', r, U, opts.max_iter, 2 * opts.tol, opts.display); 68 | cost = sqrt(cost / sum(W(:) ~= 0)); 69 | V = NaN; 70 | elseif strcmpi(opts.alg, 'CH_LM_M_RW2') 71 | % Chen's LM_M_RW2 (Damped reduced RW2): newly implemented based on 72 | % CH_LM_M 73 | % Tolerance is doubled as the script uses absolute cost value. 74 | [cost, iter, U] = xt_ch_lm_m_rw2(M', W', r, U, opts.max_iter, 2 * opts.tol, opts.display); 75 | cost = sqrt(cost / sum(W(:) ~= 0)); 76 | V = NaN; 77 | elseif strcmpi(opts.alg, 'CO_LM_S') 78 | % Chen's LM_S (Damped RW0): original 79 | % CO_LM_S 80 | % Tolerance is doubled as the script uses absolute cost value. 81 | [U, ~] = qr(U, 0); 82 | [cost, iter, U] = xt_co_lm_s(M', W', r, U, opts.max_iter, 2 * opts.tol); 83 | cost = sqrt(cost(end) / sum(W(:) ~= 0)); 84 | V = NaN; 85 | elseif strcmpi(opts.alg, 'CO_LM_M') 86 | % Chen's LM_M (Damped reduced RW0): original 87 | % CO_LM_M 88 | % Tolerance is doubled as the script uses absolute cost value. 89 | [U, ~] = qr(U, 0); 90 | [cost, iter, U] = xt_co_lm_m(M', W', r, U, opts.max_iter, 2 * opts.tol); 91 | cost = sqrt(cost(end) / sum(W(:) ~= 0)); 92 | V = NaN; 93 | elseif strcmpi(opts.alg, 'CO_LM_M_GN') 94 | % Chen's LM_M_GN (Damped reduced RW1): original 95 | % CO_LM_M_GN 96 | % Tolerance is doubled as the script uses absolute cost value. 97 | [U, ~] = qr(U, 0); 98 | [cost, iter, U] = xt_co_lm_m_gn(M', W', r, U, opts.max_iter, 2 * opts.tol); 99 | cost = sqrt(cost(end) / sum(W(:) ~= 0)); 100 | V = NaN; 101 | elseif strcmpi(opts.alg, 'NB_RTRMC') 102 | % Boumal's RTRMC: cost function modified 103 | % NB_RTRMC 104 | % Tolerance is doubled as the script uses absolute cost value. 105 | [U, ~] = qr(U, 0); 106 | [U, V, cost, iter] = xt_nb_rtrmc_wrapper(M, W, r, U, ... 107 | ['max_iter=', num2str(opts.max_iter)], ... 108 | ['tol=', num2str(2 * opts.tol, '%e')], ... 109 | ['display=', num2str(opts.display)]); 110 | cost = sqrt(cost(end) / sum(W(:) ~= 0)); 111 | elseif strcmpi(opts.alg, 'RC_ALM') 112 | % Cabral's ALM: newly implemented based on author's description 113 | % RC_ALM 114 | % Tolerance is doubled as the script uses absolute cost value. 115 | M(W==0) = 100; 116 | [U, V] = je_alternation(M, W, r, U, nan, 'max_iter=0', 'display=0'); 117 | [U, V, cost, iter] = xt_rc_alm(M, W, r, U, V, ... 118 | ['max_iter=', num2str(opts.max_iter)], ... 119 | ['max_trials=', num2str(opts.max_trials)], ... 120 | ['tol=', num2str(2 * opts.tol, '%e')], ... 121 | ['display=', num2str(opts.display)], ... 122 | ['nu=', num2str(opts.nu, '%e')]); 123 | cost = sqrt(cost / sum(W(:) ~= 0)); 124 | elseif strcmpi(opts.alg, 'RC_RCALM') 125 | % Cabral's ALM with rank continuation: newly implemented based on author's description 126 | % RC_RCALM 127 | % Tolerance is doubled as the script uses absolute cost value. 128 | [U, ~] = qr(U, 0); 129 | [U, V] = je_alternation(M, W, opts.r_init, U, nan, 'max_iter=0', 'display=0'); 130 | 131 | [U, V, cost, iter] = xt_rc_rcalm(M, W, r, U, V, ... 132 | ['max_iter=', num2str(opts.max_iter)], ... 133 | ['max_trials=', num2str(opts.max_trials)], ... 134 | ['tol=', num2str(2 * opts.tol, '%e')], ... 135 | ['display=', num2str(opts.display)], ... 136 | ['nu=', num2str(opts.nu, '%e')]); 137 | cost = sqrt(cost / sum(W(:) ~= 0)); 138 | elseif strcmpi(opts.alg, 'DB_BALM') 139 | % Del Bue's BALM: newly implemented based on author's description 140 | % The projection matrix is replaced by QR decomposition. 141 | % DB_BALM 142 | % Tolerance is doubled as the script uses absolute cost value. 143 | [U, ~] = qr(U, 0); 144 | [U, V] = je_alternation(M, W, opts.r_init, U, nan, 'max_iter=0', 'display=0'); 145 | [U, V, cost, iter] = xt_db_balm(M, W, r, U, V, ... 146 | ['max_iter=', num2str(opts.max_iter)], ... 147 | ['max_trials=', num2str(opts.max_trials)], ... 148 | ['tol=', num2str(2 * opts.tol, '%e')], ... 149 | ['display=', num2str(opts.display)], ... 150 | ['nu=', num2str(opts.nu, '%e')]); 151 | elseif strcmpi(opts.alg, 'DO_BALM') 152 | % Del Bue's BALM: original 153 | % The projection matrix is replaced by QR decomposition. 154 | % DO_BALM 155 | [U, ~] = qr(U, 0); 156 | M(W==0) = 0; 157 | [U, V] = je_alternation(M, W, opts.r_init, U, nan, 'max_iter=0', 'display=0'); 158 | [V, U, ~, ~, iter] = xt_do_balm_missing_bilinear_alter(M', W', V, U', 0, ... 159 | @xt_do_proj_qr, opts.max_iter, opts.max_trials); 160 | U = U'; 161 | cost = sqrt(norm(W .* (M - U * V'), 'fro') ^ 2 / sum(W(:) ~= 0)); 162 | else 163 | error('No matching algorithm found.'); 164 | end 165 | 166 | end 167 | 168 | -------------------------------------------------------------------------------- /Code/Experiment/je_generate_sample.m: -------------------------------------------------------------------------------- 1 | function [U, V] = je_generate_sample(sample_id, m, n, r, base) 2 | % JE_GENERATE_SAMPLE 3 | % Generate a set of samples with input dimension. 4 | % 5 | 6 | if nargin < 5, base = 1348032014; 7 | end 8 | 9 | width = 10; 10 | 11 | rng(base + sample_id * width, 'twister'); 12 | 13 | U = randn(m, r); 14 | 15 | if nargout > 1, V = randn(n, r); 16 | end 17 | 18 | end 19 | 20 | -------------------------------------------------------------------------------- /Code/Experiment/je_run_experiment.m: -------------------------------------------------------------------------------- 1 | function je_run_experiment(dataset, r, varargin) 2 | % JE_RUN_EXPERIMENT 3 | 4 | %% OPTIONS 5 | % Extend varargin to include tolerance, maximum number of iterations and 6 | % display. 7 | warning('off', 'MATLAB:nearlySingularMatrix'); % turn off warning for better visual 8 | varargin = [ 9 | { 10 | 'tol=1e-10', ... 11 | 'max_iter=300', ... 12 | 'display=0', ... 13 | 'nu=0.1', ... 14 | 'max_als=10', ... 15 | 'output_filename=0', ... 16 | }, ... 17 | varargin, ... 18 | ]; 19 | 20 | % Convert varargin to opts. 21 | opts = au_opts(... 22 | 'sample_start=1;sample_end=0;num_samples=100', ... 23 | 'overwrite=0', ... 24 | varargin{:}); 25 | 26 | % Make the first letter of the dataset name uppercase. 27 | dataset = regexprep(lower(dataset),'(\<[a-z])','${upper($1)}'); 28 | if ~opts.output_filename, 29 | file = ['Results/', dataset, '/', lower(dataset), '_r', int2str(r)]; 30 | else 31 | file = ['Results/', dataset, '/', lower(opts.output_filename), '_r', int2str(r)]; 32 | end 33 | 34 | % If a previous version of the file exists, load it. 35 | clearvars -global lrmf_results 36 | if exist([file, '.mat'], 'file') == 2, load(file); 37 | end 38 | 39 | % Set lrmf_results as a global variable. 40 | global lrmf_results 41 | 42 | %% LIST OF ALGORITHMS 43 | if ~opts.sample_end 44 | opts.sample_end = opts.sample_start + opts.num_samples - 1; 45 | end 46 | 47 | data = load(['../Datasets/' dataset '/' lower(dataset)]); % Load the measurement matrix. 48 | if ~issparse(data.M), data.M(data.W==0) = 100; % So we get an alert if some algo accesses zeros in M. 49 | end 50 | 51 | % Write the measurement matrix and the weight matrix in binary. 52 | sample_id = lower(dataset); 53 | % If the Temp folder does not exist, create it. 54 | if exist('Temp', 'dir') ~= 7, mkdir('Temp'); 55 | end 56 | filename = ['Temp/', sample_id, '_r', num2str(r)]; 57 | 58 | if ~issparse(data.M) 59 | je_write_binary_matrix([filename, '_M.bin'], data.M); 60 | je_write_binary_matrix([filename, '_W.bin'], data.W); 61 | end 62 | 63 | algorithms = { 64 | % Latest custom-coded algorithms 65 | 'ALS' @(sample_num) je_alternation(data.M, data.W, r, sample_num, nan, varargin{:}, 'retraction=0') 66 | 'PF' @(sample_num) je_alternation(data.M, data.W, r, sample_num, nan, varargin{:}, 'retraction=1') 67 | 'DW' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'retraction=0') 68 | 'DRW1' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'alg_type=1', 'constraint=0') 69 | 'DRW1_UWOFF' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'alg_type=1', 'constraint=0', 'search_unique_weights=0') 70 | 'DRW1P' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'alg_type=1') 71 | 'DRW1P_UWOFF' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'alg_type=1', 'search_unique_weights=0') 72 | 'DRW2' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'constraint=0') 73 | 'DRW2_UWOFF' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'constraint=0', 'search_unique_weights=0') 74 | 'DRW2P' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0') 75 | 'DRW2P_UWOFF' @(sample_num) je_varpro(data.M, data.W, r, sample_num, nan, varargin{:}, 'nu=0.0', 'search_unique_weights=0') 76 | ... 77 | % Latest Ceres-solver LMs 78 | 'CE_LM' @(sample_num) lrmf_ceres_wrapper(... 79 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 80 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 81 | 'nu=0.0', 'max_als=0', ... 82 | 'use_jacobi_scaling=0'); 83 | ... 84 | 'CE_LMI' @(sample_num) lrmf_ceres_wrapper(... 85 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 86 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 87 | 'nu=0.0', 'max_als=0', ... 88 | 'use_jacobi_scaling=0', 'use_inner_iterations=1'); 89 | ... 90 | 'CE_ALM' @(sample_num) lrmf_ceres_wrapper(... 91 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 92 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 93 | 'nu=0.0', sprintf('max_als=%d', opts.max_als), ... 94 | 'use_jacobi_scaling=0'); 95 | ... 96 | 'CE_ALMI' @(sample_num) lrmf_ceres_wrapper(... 97 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 98 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 99 | 'nu=0.0', sprintf('max_als=%d', opts.max_als), ... 100 | 'use_jacobi_scaling=0', 'use_inner_iterations=1'); 101 | ... 102 | 'CE_ARULM' @(sample_num) lrmf_ceres_wrapper(... 103 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 104 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 105 | sprintf('nu=%d', opts.nu), sprintf('max_als=%d', opts.max_als), ... 106 | 'use_jacobi_scaling=0', 'reg_unreg=1'); 107 | ... 108 | 'CE_ARULMI' @(sample_num) lrmf_ceres_wrapper(... 109 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 110 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 111 | sprintf('nu=%d', opts.nu), sprintf('max_als=%d', opts.max_als), ... 112 | 'use_jacobi_scaling=0', 'reg_unreg=1', 'use_inner_iterations=1'); 113 | ... 114 | 'CE_L' @(sample_num) lrmf_ceres_wrapper(... 115 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 116 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 117 | 'nu=0.0', 'max_als=0', ... 118 | 'use_jacobi_scaling=0', ... 119 | 'use_levenberg_damping=1'); 120 | ... 121 | 'CE_EPI' @(sample_num) lrmf_ceres_wrapper(... 122 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 123 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 124 | 'nu=0.0', 'max_als=0', ... 125 | 'use_jacobi_scaling=0', 'use_inner_iterations=1', ... 126 | 'use_levenberg_damping=1', ... 127 | 'use_inner_iterations_for_v_only=1'); 128 | ... 129 | 'CE_LEPI' @(sample_num) lrmf_ceres_wrapper(... 130 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 131 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 132 | 'nu=0.0', 'max_als=0', ... 133 | 'use_jacobi_scaling=0', 'use_inner_iterations=1', ... 134 | 'use_levenberg_damping=1', ... 135 | 'use_inner_iterations_for_v_only=1', ... 136 | 'use_linear_inner_iterations=1'); 137 | ... 138 | 'CE_RW2' @(sample_num) lrmf_ceres_wrapper(... 139 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 140 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 141 | 'nu=0.0', 'max_als=0', ... 142 | 'use_jacobi_scaling=0', 'use_inner_iterations=1', ... 143 | 'use_levenberg_damping=1', ... 144 | 'use_traditional_damping_update=1', .... 145 | 'use_rw2_for_inner_iterations=1', ... 146 | 'use_linear_inner_iterations=0', ... 147 | 'use_inner_iterations_for_v_only=1', ... 148 | 'use_block_qr_for_rw2=1', ... 149 | 'initialize_with_inner_iteration=1'); 150 | ... 151 | 'CE_LRW2' @(sample_num) lrmf_ceres_wrapper(... 152 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 153 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 154 | 'nu=0.0', 'max_als=0', ... 155 | 'use_jacobi_scaling=0', 'use_inner_iterations=1', ... 156 | 'use_levenberg_damping=1', ... 157 | 'use_traditional_damping_update=1', .... 158 | 'use_rw2_for_inner_iterations=1', ... 159 | 'use_linear_inner_iterations=1', ... 160 | 'use_inner_iterations_for_v_only=1', ... 161 | 'use_block_qr_for_rw2=1', ... 162 | 'initialize_with_inner_iteration=1'); 163 | ... 164 | 'CE_LRW2_no_BQR' @(sample_num) lrmf_ceres_wrapper(... 165 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 166 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 167 | 'nu=0.0', 'max_als=0', ... 168 | 'use_jacobi_scaling=0', 'use_inner_iterations=1', ... 169 | 'use_levenberg_damping=1', ... 170 | 'use_traditional_damping_update=1', .... 171 | 'use_rw2_for_inner_iterations=1', ... 172 | 'use_linear_inner_iterations=1', ... 173 | 'use_inner_iterations_for_v_only=1', ... 174 | 'use_block_qr_for_rw2=0', ... 175 | 'initialize_with_inner_iteration=1'); 176 | ... 177 | 'CE_LRW2_ALS' @(sample_num) lrmf_ceres_wrapper(... 178 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 179 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 180 | 'nu=0.0', 'max_als=0', ... 181 | 'use_jacobi_scaling=0', 'use_inner_iterations=1', ... 182 | 'use_levenberg_damping=1', ... 183 | 'use_traditional_damping_update=1', .... 184 | 'use_rw2_for_inner_iterations=1', ... 185 | 'use_linear_inner_iterations=1', ... 186 | 'use_inner_iterations_for_v_only=0', ... 187 | 'use_block_qr_for_rw2=1', ... 188 | 'initialize_with_inner_iteration=1'); 189 | ... 190 | 'CE_LRW2_no_Init' @(sample_num) lrmf_ceres_wrapper(... 191 | data.M, data.W, r, sample_num, nan, varargin{:}, ... 192 | 'write_binary_mw=0', ['sample_id=', sample_id], 'retraction=0', ... 193 | 'nu=0.0', 'max_als=0', ... 194 | 'use_jacobi_scaling=0', 'use_inner_iterations=1', ... 195 | 'use_levenberg_damping=1', ... 196 | 'use_traditional_damping_update=1', .... 197 | 'use_rw2_for_inner_iterations=1', ... 198 | 'use_linear_inner_iterations=1', ... 199 | 'use_inner_iterations_for_v_only=1', ... 200 | 'use_block_qr_for_rw2=1', ... 201 | 'initialize_with_inner_iteration=0'); 202 | 203 | % External algorithms 204 | 'PG_CSF' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=PG_CSF'); 205 | 'TO_DW' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=TO_DW'); 206 | 'CH_LM_S' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CH_LM_S'); 207 | 'CH_LM_S_GN' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CH_LM_S_GN'); 208 | 'CH_LM_S_RW2' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CH_LM_S_RW2'); 209 | 'CH_LM_M' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CH_LM_M'); 210 | 'CH_LM_M_GN' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CH_LM_M_GN'); 211 | 'CH_LM_M_RW2' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CH_LM_M_RW2'); 212 | 'CO_LM_S' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CO_LM_S'); 213 | 'CO_LM_M' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CO_LM_M'); 214 | 'CO_LM_M_GN' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=CO_LM_M_GN'); 215 | 'NB_RTRMC' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=NB_RTRMC'); 216 | 'RC_ALM' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=RC_ALM'); 217 | 'RC_RCALM' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=RC_RCALM', sprintf('r_init=%d', r + 2)); 218 | 'DB_BALM' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=DB_BALM'); 219 | 'DO_BALM' @(sample_num) je_external_wrapper(data.M, data.W, r, sample_num, varargin{:}, 'alg=DO_BALM'); 220 | }; 221 | 222 | % Results now stored in struct array, so that e.g. time for LM_EU 223 | % are in lrmf_results.(dataset)(sample).LM_EU.time 224 | % To get an array of results from one algorithm, use 225 | % lm_eu = [lrmf_results.(dataset).LM_EU]; 226 | % [lm_eu.runtime; lm_eu.cost; lm_eu.iters]' 227 | 228 | %% EXPERIMENT 229 | for sample_num = opts.sample_start : opts.sample_end 230 | 231 | fprintf('Experiments running on [%s - rank %d] sample [%d] \n', dataset, r, sample_num); 232 | lrmf_results.(dataset)(sample_num).sample_num = sample_num; 233 | lrmf_results.(dataset)(sample_num).rank = r; 234 | 235 | for k = 1 : length(algorithms) 236 | 237 | % If the input files are accidentally deleted, write them again. 238 | if ~issparse(data.M) && (exist([file, '.mat'], 'file') ~= 2) 239 | je_write_binary_matrix([filename, '_M.bin'], data.M); 240 | je_write_binary_matrix([filename, '_W.bin'], data.W); 241 | end 242 | 243 | alg = algorithms{k,1}; 244 | alg_caller = algorithms{k,2}; 245 | if isfield(opts, (alg)) 246 | if opts.(alg) 247 | if ~opts.overwrite && ... 248 | isfield(lrmf_results.(dataset)(sample_num), alg) && ... 249 | isfield(lrmf_results.(dataset)(sample_num).(alg), 'cost') 250 | fprintf('Algorithm [%s] already run on [%s - rank %d] sample [%d]: %.6f\n', ... 251 | alg, dataset, r, sample_num, lrmf_results.(dataset)(sample_num).(alg).cost); 252 | else 253 | fprintf('Algorithm [%s] running on [%s - rank %d] sample [%d]', alg, dataset, r, sample_num); 254 | time_start = tic; 255 | [U, V, err, iter] = alg_caller(sample_num); 256 | runtime = toc(time_start); 257 | 258 | lrmf_results.(dataset)(sample_num).(alg).iters = iter; 259 | lrmf_results.(dataset)(sample_num).(alg).cost = err; 260 | lrmf_results.(dataset)(sample_num).(alg).runtime = runtime; 261 | lrmf_results.(dataset)(sample_num).(alg).U = U; 262 | lrmf_results.(dataset)(sample_num).(alg).V = V; 263 | 264 | fprintf(': %.6f, %03d iters, %.3f iters per sec\n', ... 265 | lrmf_results.(dataset)(sample_num).(alg).cost, ... 266 | lrmf_results.(dataset)(sample_num).(alg).iters, ... 267 | lrmf_results.(dataset)(sample_num).(alg).iters / ... 268 | lrmf_results.(dataset)(sample_num).(alg).runtime) 269 | 270 | if ~exist('Results', 'dir'), mkdir('Results'); 271 | end 272 | 273 | if ~exist(['Results/', dataset], 'dir'), mkdir(['Results/', dataset]); 274 | end 275 | 276 | save(file, 'lrmf_results'); 277 | end 278 | end 279 | end 280 | end 281 | end 282 | 283 | if ~issparse(data.M) 284 | delete([filename, '_M.bin']); 285 | delete([filename, '_W.bin']); 286 | end 287 | 288 | clearvars -global lrmf_results 289 | 290 | end 291 | -------------------------------------------------------------------------------- /Code/run_demo.m: -------------------------------------------------------------------------------- 1 | % Run the setup file prior to performing any experiment. 2 | run setup 3 | 4 | % Perform experiment on one algorithm with default settings ... 5 | % [ max_iter = 300, tol = 1e-10, sample_start = 1, sample_end = 100 ] 6 | % We will limit sample_end to 10. 7 | je_run_experiment('dino_trimmed', 4, 'DRW2P=1', 'sample_end=10'); 8 | 9 | % Perform experiment on multiple algorithms with specific settings 10 | % [ overwrite = 1 will re-run experiment on the specified sample range. ] 11 | je_run_experiment('dino_trimmed', 4, 'DRW2P=1', 'DRW1P=1', 'ALS=1', ... 12 | 'TO_DW=1', 'CH_LM_S_RW2=1', 'NB_RTRMC=1', ... % 'PG_CSF=1' 13 | 'sample_start=1', 'sample_end=3', 'max_iter=300', 'overwrite=1'); 14 | 15 | % Analyze algorithm performance 16 | % [ csv = 1 will write the csv file to 17 | % Results//.csv. ] 18 | je_analyze_results('dino_trimmed', 4, 'DRW2P=1', 'DRW1P=1', 'ALS=1', ... 19 | 'TO_DW=1', 'CH_LM_S_RW2=1', 'NB_RTRMC=1', ... % 'PG_CSF=1' 20 | 'csv=1'); 21 | 22 | %% LIST OF ALGORITHMS 23 | % % Latest custom-coded algorithms 24 | % 'ALS', 'New' % Alternation (Undamped RW3) 25 | % 'DW', 'New' % Damped Wiberg (Damped RW2 + projection constraint) 26 | % 'DRW1_UWOFF', '(NO UW)' 27 | % 'DRW1', '(UW)' 28 | % 'DRW1P_UWOFF', '(NO UW)' 29 | % 'DRW1P', '(UW)' % Damped RW1 with projection constraint (Damped RW1 + projection constraint + retraction) 30 | % 'DRW2_UWOFF', '(NO UW)' 31 | % 'DRW2', '(UW)' 32 | % 'DRW2P_UWOFF', '(NO UW)' 33 | % 'DRW2P', '(UW)' % Damped RW2 with projection constraint (Damped RW2 + projection constraint + retraction) 34 | % % External algorithms 35 | % 'PG_CSF', 'Orig.' % Gotardo's CSF (Damped RW2) 36 | % 'TO_DW', 'Orig.' % Okatani's Damped Wiberg (Damped RW2 + projection constraint) 37 | % 'CH_LM_S', 'Mod.' % Chen's LM_S (Damped full Newton) 38 | % 'CH_LM_S_GN', 'Mod.' % Chen's LM_S modified to use GN-matrix (Damped RW1) 39 | % 'CH_LM_S_RW2', 'Mod.' % Chen's LM_S modified to use approximate GN-matrix (Damped RW2) 40 | % 'CH_LM_M', 'Mod.' % Chen's LM_M (Reduced damped full Newton) 41 | % 'CH_LM_M_GN', 'Mod.' % Chen's LM_M_GN (Reduced damped RW1) 42 | % 'CH_LM_M_RW2', 'Mod.' % Chen's LM_M modified to use approximate GN-matrix (Reduced damped RW2) 43 | % 'CO_LM_S', 'Orig.' % Chen's original implementation of LM_S 44 | % 'CO_LM_M', 'Orig.' % Chen's original implementation of LM_M 45 | % 'CO_LM_M_GN', 'Orig.' % Chen's original implementation of LM_M_GN 46 | % 'NB_RTRMC', 'Orig.' % Boumal's original implementation of RTRMC 47 | % 'RC_ALM', 'Unopt.' % Cabral's ALM (New but not necessarily optimized) 48 | % 'RC_RCALM', 'Unopt.' % Cabral's ALM with rank continuation (New but not necessarily optimized) 49 | % 'DB_BALM', 'Unopt.' % Del Bue's BALM (New but not necessarily optimized) 50 | % % Latest Ceres-solver LMs 51 | % 'CE_LM', 'Ceres' % CERES LM 52 | % 'CE_LMI', 'Ceres' % CERES LM INNER 53 | % 'CE_ALM', 'Ceres' % 10 ALS + CE_LM 54 | % 'CE_ALMI', 'Ceres' % 10 ALS + CE_LM_I 55 | % 'CE_ARULM', 'Ceres' % 10 reg. ALS + reg. CE_LM + unreg. CE_LM 56 | % 'CE_ARULMI', 'Ceres' % 10 reg. ALS + reg. CE_LMI + unreg. CE_LMI 57 | -------------------------------------------------------------------------------- /Code/setup.m: -------------------------------------------------------------------------------- 1 | %% SETUP EXTERNAL LIBRARIES 2 | % AWFUL 3 | try 4 | au_sparse(int32([1 2 3 4]), int32([1 3 5 7]), randn(4,1)); 5 | catch 6 | disp('Setting up AWFUL...'); 7 | cd Toolboxes/awful/matlab 8 | addpath(genpath(pwd)); 9 | mex -largeArrayDims au_sparse.cxx 10 | cd ../../.. 11 | end 12 | 13 | % Manopt 14 | if exist('manopt_version', 'file') ~= 2 15 | disp('Setting up Manopt...'); 16 | cd 'Toolboxes/manopt'; 17 | importmanopt; 18 | cd ../.. 19 | end 20 | 21 | %% ADD PATHS 22 | disp('Adding paths...') 23 | cd Experiment 24 | addpath(genpath(pwd)); 25 | cd .. 26 | 27 | cd Algorithms 28 | addpath(genpath(pwd)); 29 | % cd JE 30 | % rmpath([pwd, '/ceres_lrmc/build']); 31 | % rmpath([pwd, '/ce_src/LRMC']); 32 | % rmpath([pwd, '/ce_src']); 33 | % cd .. 34 | 35 | %% COMPILE MEX FILES 36 | % JE 37 | cd JE 38 | disp('Compiling mex files for JE'); 39 | mex je_form_hessian.cpp 40 | mex je_add_hessian_layer.cpp 41 | cd .. 42 | 43 | % NB 44 | cd NB 45 | disp('Compiling mex files for NB'); 46 | mex -largeArrayDims xt_nb_spmaskmult.c 47 | mex -largeArrayDims xt_nb_setsparseentries.c 48 | mex -largeArrayDims -lmwlapack xt_nb_cholsolvecell.c 49 | mex -largeArrayDims -lmwlapack -lmwblas xt_nb_spbuildmatrix.c 50 | mex -largeArrayDims -lmwlapack -lmwblas xt_nb_buildAchol.c 51 | cd .. 52 | 53 | % PG 54 | cd PG 55 | disp('Compiling mex files for PG'); 56 | mex xt_pg_kronmex.c 57 | cd .. 58 | 59 | cd .. 60 | -------------------------------------------------------------------------------- /Documents/hong_awf_iccv15.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhh37/matrix-factorization/6798920053db949b19e06419cbd33a6b1ac126ed/Documents/hong_awf_iccv15.pdf -------------------------------------------------------------------------------- /Documents/hong_awf_iccv15_further.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhh37/matrix-factorization/6798920053db949b19e06419cbd33a6b1ac126ed/Documents/hong_awf_iccv15_further.pdf -------------------------------------------------------------------------------- /Documents/hong_awf_iccv15_poster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhh37/matrix-factorization/6798920053db949b19e06419cbd33a6b1ac126ed/Documents/hong_awf_iccv15_poster.pdf -------------------------------------------------------------------------------- /Documents/hong_awf_iccv15_supmat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhh37/matrix-factorization/6798920053db949b19e06419cbd33a6b1ac126ed/Documents/hong_awf_iccv15_supmat.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secrets of Matrix Factorization (ICCV 2015) 2 | This is a repository for the matrix factorization 3 | project published in ICCV 2015. 4 | 5 | ## Contributors 6 | - Je Hyeong Hong (jhh37@outlook.com) 7 | - Andrew Fitzgibbon (awf@microsoft.com) 8 | 9 | ## A how-to-pull guide 10 | Clone the repository using the following code 11 | ``` 12 | git clone --recursive https://github.com/jhh37/matrix-factorization 13 | ``` 14 | 15 | ## Code 16 | ### List of algorithms 17 | Below is a list of algorithms included. 18 | 1. [JE] Our implementation of DRW (Damped Ruhe and Wedin) algorithms 19 | 2. [JE] Our Ceres implementation of joint optimisation algorithms 20 | 3. [CH] Chen's LM_S, LM_M series (our modification) 21 | 4. [CO] Chen's LM_S, LM_M series (original: http://sist.sysu.edu.cn/~chenpei/) 22 | 5. [DB] Del Bue's BALM (our implementation but not necessarily fast) 23 | 6. [NB] Boumal's RTRMC (original: https://perso.uclouvain.be/nicolas.boumal/RTRMC/) 24 | 7. [PG] Gotardo's CSF (original: http://www2.ece.ohio-state.edu/~gotardop/) 25 | 8. [RC] Cabral's ALM series (our implementation but not necessarily fast) 26 | 9. [TO] Okatani's Damped Wiberg (DW) (original: http://www.vision.is.tohoku.ac.jp/us/download/) 27 | 28 | ### A how-to-run demo 29 | You may run the script [ run_demo.m ] inside the [ 30 | Code ] folder. 31 | 32 | ### Running experiments 33 | Before starting, there are some issues you need to 34 | watch out for: 35 | 36 | 1. Prior to running any experiment, you must 37 | have run [ Code/setup.m ], which adds relevant 38 | paths and compile required mex files. 39 | 2. All commands must run in the [ Code ] 40 | directory. 41 | 3. To run our Ceres-based algorithms (assuming 42 | Ceres-solver from http://ceres-solver.org/ is 43 | already installed), you need to compile the source 44 | (which is pulled as a submodule) present in [ 45 | Code/Algorithms/JE/lrmf_ceres ]. 46 | 1. The CMake file is given. 47 | 2. It may be convenient to compile in the [ 48 | Code/Algorithms/JE/lrmf_ceres/build ] directory 49 | since this is git-ignored. 50 | 3. Place the compiled executable inside the [ 51 | Code/Algorithms/JE ] folder with the name [ 52 | lrmf_ceres_exec ] or [ lrmf_ceres_exec.exe ]. 53 | 4. Reading sparse matrices is currently not 54 | supported by [ LRMF-Ceres ]. 55 | 56 | 57 | ## Video Spotlight 58 | Our 1-min video spotlight can be found at 59 | https://youtu.be/Kk0NIeNnc5M. 60 | 61 | ## Acknowledgement 62 | - The work was supported by Microsoft and Toshiba 63 | Research Europe. 64 | - We thank Roberto Cipolla, Christopher Zach, 65 | Bamdev Mishra and the anonymous reviewers for 66 | their comments. 67 | - The conference travel was funded by the British 68 | Machine Vision Association (BMVA), Cambridge 69 | University Engineering Department (Rex Moir Fund), 70 | Christ's College (University of Cambridge) and 71 | Cambridge Philosophical Society. 72 | --------------------------------------------------------------------------------