| daniele@169 | 1 function [Dhat cost W] = rotatematrix(D,Phi,method,param) | 
| daniele@169 | 2 % | 
| daniele@169 | 3 % | 
| daniele@169 | 4 % | 
| daniele@169 | 5 % REFERENCE | 
| daniele@169 | 6 % M.D. Plumbley, Geometrical Methods for Non-Negative ICA: Manifolds, Lie | 
| daniele@169 | 7 % Groups and Toral Subalgebra, Neurocomputing | 
| daniele@170 | 8 | 
| daniele@170 | 9 %% Parse inputs and set defaults | 
| daniele@169 | 10 if ~nargin, testrotatematrix; return, end | 
| daniele@169 | 11 | 
| daniele@170 | 12 if ~exist('param','var')  || isempty(param),  param  = struct; end | 
| daniele@170 | 13 if ~exist('method','var') || isempty(method), method = 'conjgradLie'; end | 
| daniele@170 | 14 if ~isfield(param,'nIter'), param.nIter = 100; end	%number of iterations | 
| daniele@170 | 15 if ~isfield(param,'eps'), param.eps = 1e-9; end		%tolerance level | 
| daniele@170 | 16 if ~isfield(param,'step'), param.step = 0.01; end | 
| daniele@169 | 17 | 
| daniele@170 | 18 J = @(W) 0.5*norm(D-W*Phi,'fro');		%cost function | 
| daniele@169 | 19 | 
| daniele@170 | 20 % Initialise variables | 
| daniele@170 | 21 cost = zeros(param.nIter,1);			%cost at each iteration | 
| daniele@170 | 22 W	 = eye(size(Phi,1));				%rotation matrix | 
| daniele@170 | 23 grad = ones(size(W));					%gradient | 
| daniele@170 | 24 t	 = param.step;						%step size | 
| daniele@170 | 25 Gprev = 0;								%previous gradient | 
| daniele@170 | 26 Hprev = 0;								%previous Lie search direction | 
| daniele@170 | 27 iIter = 1;								%iteration counter | 
| daniele@169 | 28 | 
| daniele@170 | 29 %% Main algorithm | 
| daniele@170 | 30 while iIter<=param.nIter && norm(grad,'fro')>eps | 
| daniele@170 | 31 	cost(iIter) = J(W);				%calculate cost | 
| daniele@170 | 32 	grad = (W*Phi-D)*Phi';			%calculate gradient | 
| daniele@169 | 33 	switch method | 
| daniele@169 | 34 		case 'unconstrained'		% gradient descent | 
| daniele@169 | 35 			eta = param.step; | 
| daniele@169 | 36 			W = W - eta*grad;		% update W by steepest descent | 
| daniele@169 | 37 		case 'tangent'				% self correcting tangent | 
| daniele@169 | 38 			eta = param.step; | 
| daniele@170 | 39 			W = W - 0.5*eta*(grad - W*grad'*W); | 
| daniele@170 | 40 			[U , ~, V] = svd(W); | 
| daniele@170 | 41 			W = U*V'; | 
| daniele@170 | 42 		case 'steepestlie'			%steepest descent in Lie algebra | 
| daniele@169 | 43 			eta = param.step; | 
| daniele@170 | 44 			B = 2*skew(grad*W');	% calculate gradient in Lie algebra | 
| daniele@169 | 45 			W = expm(-eta*B)*W;		% update W by steepest descent | 
| daniele@170 | 46 		case 'linesearchlie'		% line search in Lie algebra | 
| daniele@170 | 47 			B = 2*skew(grad*W');	% calculate gradient in Lie algebra | 
| daniele@169 | 48 			H = -B;					% calculate direction as negative gradient | 
| daniele@170 | 49 			t = searchline(J,H,W,t);% line search in one-parameter Lie subalgebra | 
| daniele@169 | 50 			W = expm(t*H)*W;		% update W by line search | 
| daniele@170 | 51 		case 'conjgradlie'			% conjugate gradient in Lie algebra | 
| daniele@170 | 52 			G = 2*skew(grad*W');	% calculate gradient in Lie algebra | 
| daniele@169 | 53 			H = -G + polakRibiere(G,Gprev)*Hprev; %calculate conjugate gradient direction | 
| daniele@170 | 54 			t = searchline(J,H,W,t);% line search in one-parameter Lie subalgebra | 
| daniele@169 | 55 			W = expm(t*H)*W;		% update W by line search | 
| daniele@170 | 56 			Hprev = H;				% save search direction | 
| daniele@170 | 57 			Gprev = G;				% save gradient | 
| daniele@169 | 58 	end | 
| daniele@170 | 59 	iIter = iIter+1;				% update iteration counter | 
| daniele@169 | 60 end | 
| daniele@170 | 61 Dhat = W*Phi;						%rotate matrix | 
| daniele@172 | 62 cost(iIter:end) = cost(iIter-1);	%zero-pad cost vector | 
| daniele@169 | 63 end | 
| daniele@169 | 64 | 
| daniele@170 | 65 %% Support functions | 
| daniele@169 | 66 function gamma = polakRibiere(G,Gprev) | 
| daniele@170 | 67 %Polak-Ribiere rule for conjugate direction calculation | 
| daniele@169 | 68 gamma = G(:)'*(G(:)-Gprev(:))/(norm(Gprev(:))^2); | 
| daniele@169 | 69 if isnan(gamma) || isinf(gamma) | 
| daniele@169 | 70 	gamma = 0; | 
| daniele@169 | 71 end | 
| daniele@169 | 72 end | 
| daniele@169 | 73 | 
| daniele@169 | 74 function t = searchline(J,H,W,t) | 
| daniele@170 | 75 %Line search in one-parameter Lie subalgebra | 
| daniele@169 | 76 t = fminsearch(@(x) J(expm(x*H)*W),t); | 
| daniele@169 | 77 end | 
| daniele@169 | 78 | 
| daniele@169 | 79 function B = skew(A) | 
| daniele@170 | 80 %Skew-symmetric matrix | 
| daniele@169 | 81 B = 0.5*(A - A'); | 
| daniele@169 | 82 end | 
| daniele@169 | 83 | 
| daniele@169 | 84 | 
| daniele@170 | 85 %% Test function | 
| daniele@169 | 86 function testrotatematrix | 
| daniele@169 | 87 clear, clc, close all | 
| daniele@170 | 88 n = 256;							%ambient dimension | 
| daniele@170 | 89 m = 512;							%number of atoms | 
| daniele@170 | 90 param.nIter = 300;				%number of iterations | 
| daniele@170 | 91 param.step  = 0.001;			%step size | 
| daniele@170 | 92 param.mu    = 0.01;			%regularization factor (for tangent method) | 
| daniele@170 | 93 methods = {'unconstrained','tangent','linesearchlie','conjgradlie'}; | 
| daniele@169 | 94 | 
| daniele@170 | 95 Phi = randn(n,m);				%initial dictionary | 
| daniele@170 | 96 Qtrue = expm(skew(randn(n)));	%rotation matrix | 
| daniele@170 | 97 D = Qtrue*Phi;					%target dictionary | 
| daniele@170 | 98 | 
| daniele@170 | 99 cost = zeros(param.nIter,length(methods)); | 
| daniele@170 | 100 for iIter=1:length(methods) | 
| daniele@170 | 101 	tic | 
| daniele@170 | 102 	[~, cost(:,iIter)] = rotatematrix(D,Phi,methods{iIter},param); | 
| daniele@170 | 103 	time = toc; | 
| daniele@170 | 104 	sprintf('Method %s completed in %f seconds \n',methods{iIter},time) | 
| daniele@170 | 105 end | 
| daniele@169 | 106 | 
| daniele@169 | 107 figure, plot(cost) | 
| daniele@169 | 108 set(gca,'XScale','log','Yscale','log') | 
| daniele@170 | 109 legend(methods) | 
| daniele@169 | 110 grid on | 
| daniele@169 | 111 xlabel('number of iterations') | 
| daniele@169 | 112 ylabel('J(W)') | 
| daniele@169 | 113 end |