% J.C. Spall, Aug. 1998 % twospsaconstrained % Code for evaluation of second-order SPSA (2SPSA) versus first-order % SPSA (1SPSA, as in Chap. 7 of ISSO). Code is for comparative evaluation purposes; hence, % it includes much that is not required for a basic implementation. Further, it is % in no way "optimized" for efficiency or generality; this is strictly research % code for the purpose of getting a basic idea of how 2SPSA works. % Code includes the capability for initializing 2SPSA by running 1SPSA for % N measurements. This code allows for averaging of the % SP gradients and Hessian estimates at EACH iteration after the initial (N) % measurements where only 1SPSA is used for estimating theta. We use "theta" % for the 2SPSA recursion. Code allows for checking for simple constraint % violation (componentwise constraints). % % UPDATE MAR. 2006: Feedback and weighting versions of this code are available from the author; % this can generally provide enhanced performance. (Reference: Spall, J. C. (2006), “Feedback and Weighting Mechanisms % for Improving Jacobian (Hessian) Estimates in the Adaptive Simultaneous Perturbation Algorithm,” Proceedings of the % American Control Conference, 14-16 June 2006, Minneapolis, MN, paper ThB09.1 in CD-ROM.) % clear all close all global z sigma p; %declaration of random var. used for normal noise %generation in loss fn. calls given seed above p=10; %value of numerator in a_k sequence for all iterations of 1SPSA %and first N-measurement-based iterations in the initialization of 2SPSA a1=.02; %value of numerator in a_k in 2SPSA part a2=1; A1=100; %stability constant for 1SPSA A2=0; %stability constant for 2SPSA c1=2; %numerator in c_k for 1SPSA c2=2*c1; %numerator in c_k for 2SPSA ctilda=2*c2; %numerator in ctilda_k for 2SPSA; alpha1=.602; %a_k decay rate for 1SPSA alpha2=1; %a_k decay rate for 2SPSA gamma1=.101; %c_k decay rate for 1SPSA gamma2=.1666701; %c_k decay rate for 2SPSA n=2000; %total no. of function measurements N=2; %no. of function meas. for 1SPSA initialization loss='lossrosenbr_noise'; %loss function for use in algorithm (usually with noise) lossfinaleval='lossrosenbr'; %loss function for "true" evaluation of algorithm (no noise) cases=1; % number of cases (replications) of 2SPSA and 1SPSA gH_avg=1; %no. of averaged gradients/Hessian in 2SPSA toleranceloss=2; %tolerance in loss-based blocking step avg=1; %no. of loss evals. per loss-based blocking step (1SPSA&2SPSA) tolerancetheta=1; %max. allowable change in elements of theta rand('seed',31415297) randn('seed',3111113) sigma=2; % %the loop 1:cases below is for doing multiple cases for use in averaging to %evaluate the relative performance. % %the first loop in 2SPSA below uses the standard 1SPSA form to initialize 2SPSA % %the second loop does 2SPSA following guidelines in Spall ASP (Chap. 7 of ISSO)) % %lines below initialize various recursions for the gradient/Hess. averaging %and for final error reporting based on the average of the solutions for %"cases" replications. % meanHbar=0; errtheta=0; losstheta=0; %cum. sum of loss values lossthetasq=0; %cum. sum of loss squared values theta_lo=-2.048*ones(p,1); %lower bounds on theta theta_hi=2.047*ones(p,1); %upper bounds on theta truetheta=ones(p,1); theta_0=-.52276*ones(p,1); %DUMMY STATEMENT FOR SETTING DIMENSIONS OF Hhat (AVOIDS OCCASIONAL %ERROR MESSAGES) Hhat=eye(p); for j=1:cases %INITIALIZATION OF PARAMETER AND HESSIAN ESTIMATES theta=theta_0; Hbar=500*eye(p); lossold=0; %lossold calculation is for use in loss-based blocking step below for i=1:avg lossold=lossold+feval(loss,theta); end %INITIAL N ITERATIONS OF 1SPSA PRIOR TO 2SPSA ITERATIONS for k=1:(N-avg)/(2+avg) %use of N-avg is to account for avg used in setting lossold a_k=a1/(k+A1)^alpha1; c_k=c1/k^gamma1; delta=2*round(rand(p,1))-1; thetaplus=theta+c_k*delta; thetaminus=theta-c_k*delta; yplus=feval(loss,thetaplus); yminus=feval(loss,thetaminus); ghat=(yplus-yminus)./(2*c_k*delta); % theta update thetalag=theta; theta=theta-a_k*ghat; % Two lines below invoke constraints theta=min(theta,theta_hi); theta=max(theta,theta_lo); % Steps below perform "blocking" step with "avg" no. of loss evaluations % This blocking is based on extra loss evaluation(s) lossnew=0; for i=1:avg lossnew=lossnew+feval(loss,theta); end if lossnew > lossold-avg*toleranceloss; %if avg=0, this statement is always false theta=thetalag; else %statements to follow are harmless when avg=0 lossold1=lossold; lossold=lossnew; end % Blocking below is based on magnitude of theta change (no loss evals.) if max(abs(thetalag-theta)) > tolerancetheta; theta=thetalag; lossold=lossold1; %only relevant if also using loss-based blocking end end caseiter=j %print-out of iteration number (for monitoring progress) % % START 2SPSA ITERATIONS FOLLOWING INITIALIZATION % for k=(N-avg)/(2+avg)+1:(N-avg)/(2+avg)+(n-N)/(gH_avg*4+avg) a_k=a2/(k+A2)^alpha2; c_k=c2/k^gamma2; ctilda_k=ctilda/k^gamma2; ghatinput=0; Hhatinput=0; % GENERATION OF AVERAGED GRADIENT AND HESSIAN (NO AVERAGING IF gH_avg=1) for m=1:gH_avg delta=2*round(rand(p,1))-1; thetaplus=theta+c_k*delta; thetaminus=theta-c_k*delta; yplus=feval(loss,thetaplus); yminus=feval(loss,thetaminus); ghat=(yplus-yminus)./(2*c_k*delta); % GENERATE THE HESSIAN UPDATE deltatilda=2*round(rand(p,1))-1; thetaplustilda=thetaplus+ctilda_k*deltatilda; thetaminustilda=thetaminus+ctilda_k*deltatilda; % LOSS FUNCTION CALLS yplustilda=feval(loss,thetaplustilda); yminustilda=feval(loss,thetaminustilda); ghatplus=(yplustilda-yplus)./(ctilda_k*deltatilda); ghatminus=(yminustilda-yminus)./(ctilda_k*deltatilda); % STATEMENT PROVIDING AN AVERAGE OF SP GRAD. APPROXS. PER ITERATION ghatinput=((m-1)/m)*ghatinput+ghat/m; deltaghat=ghatplus-ghatminus; for i=1:p Hhat(:,i)=deltaghat(i)./(2*c_k*delta); end Hhat=.5*(Hhat+Hhat'); Hhatinput=((m-1)/m)*Hhatinput+Hhat/m; end Hbar=((k-(N-avg)/(2+avg))/(k-(N-avg)/(2+avg)+1))*Hbar+Hhatinput/(k-(N-avg)/(2+avg)+1); % THE THETA UPDATE (FORM BELOW USES GAUSSIAN ELIMINATION TO AVOID DIRECT % COMPUTATION OF HESSIAN INVERSE) Hbarbar=sqrtm(Hbar*Hbar+.000001*eye(p)/k); thetalag=theta; % The main update step theta=theta-a_k*(Hbarbar\ghatinput); % Two lines below invoke constraints theta=min(theta,theta_hi); theta=max(theta,theta_lo); % Steps below perform "blocking" step with "avg" no. of loss evaluations lossnew=0; for i=1:avg lossnew=lossnew+feval(loss,theta); end if lossnew > lossold-avg*toleranceloss; theta=thetalag; else lossold1=lossold; lossold=lossnew; end if max(abs(thetalag-theta)) > tolerancetheta; theta=thetalag; lossold=lossold1; end end theta meanHbar=meanHbar+Hbar; errtheta=errtheta+(theta-truetheta)'*(theta-truetheta); lossthetasq=lossthetasq+feval(lossfinaleval,theta)^2; losstheta=losstheta+feval(lossfinaleval,theta); end meanHbar/cases % normalized results of 1SPSA and 2SPSA if norm(theta_0-truetheta)~= 0 ((errtheta/cases)^.5)/norm(theta_0-truetheta) end % standard dev. of mean of normalized loss values; these are by multiplied by % (cases/(cases-1))^.5 to account for loss of degree of freedom in standard % deviation calculation before using with t-test if cases > 1 (cases^(-.5))*((cases/(cases-1))^.5)*(lossthetasq/(cases*feval(lossfinaleval,theta_0)^2)-(losstheta/(cases*feval(lossfinaleval,theta_0)))^2)^.5 end % mean normalized terminal loss value losstheta/(cases*feval(lossfinaleval,theta_0))