PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/code_oth/mds.m

http://research-code-base-animesh.googlecode.com/
MATLAB | 1067 lines | 646 code | 127 blank | 294 comment | 66 complexity | 81506a89dff561fac821079d48ac9208 MD5 | raw file
  1. %MDS - Multidimensional Scaling - a variant of Sammon mapping
  2. %
  3. % [W,J,stress] = MDS(D,Y,OPTIONS)
  4. % [W,J,stress] = MDS(D,N,OPTIONS)
  5. %
  6. % INPUT
  7. % D Square (M x M) dissimilarity matrix
  8. % Y M x N matrix containing starting configuration, or
  9. % N Desired output dimensionality
  10. % OPTIONS Various parameters of the minimization procedure put into
  11. % a structure consisting of the following fields: 'q', 'optim',
  12. % 'init','etol','maxiter', 'isratio', 'itmap', 'st' and 'inspect'
  13. % (default:
  14. % OPTIONS.q = 0
  15. % OPTIONS.optim = 'pn'
  16. % OPTIONS.init = 'cs'
  17. % OPTIONS.etol = 1e-6 (the precise value depends on q)
  18. % OPTIONS.maxiter = inf
  19. % OPTIONS.isratio = 0
  20. % OPTIONS.itmap = 'yes'
  21. % OPTIONS.st = 1
  22. % OPTIONS.inspect = 2).
  23. %
  24. % OUTPUT
  25. % W Multidimensional scaling mapping
  26. % J Index of points removed before optimization
  27. % stress Vector of stress values
  28. %
  29. % DESCRIPTION
  30. % Finds a nonlinear MDS map (a variant of the Sammon map) of objects
  31. % represented by a symmetric distance matrix D with zero diagonal, given
  32. % either the dimensionality N or the initial configuration Y. This is done
  33. % in an iterative manner by minimizing the Sammon stress:
  34. %
  35. % e = 1/(sum_{i<j} D_{ij}^(Q+2)) sum_{i<j} (D_{ij} - DY_{ij})^2 * D_{ij}^Q
  36. %
  37. % where DY is the distance matrix of Y, which should approximate D. If D(i,j)
  38. % = 0 for any different points i and j, then one of them is superfluous. The
  39. % indices of these points are returned in J.
  40. %
  41. % OPTIONS is an optional variable, using which the parameters for the mapping
  42. % can be controlled. It may contain the following fields:
  43. %
  44. % Q Stress measure to use (see above): -2,-1,0,1 or 2.
  45. % INIT Initialisation method for Y: 'randp', 'randnp', 'maxv', 'cs'
  46. % or 'kl'. See MDS_INIT for an explanation.
  47. % OPTIM Minimization procedure to use: 'pn' for Pseudo-Newton or
  48. % 'scg' for Scaled Conjugate Gradients.
  49. % ETOL Tolerance of the minimization procedure. Usually, it should be
  50. % MAXITER in the order of 1e-6. If MAXITER is given (see below), then the
  51. % optimization is stopped either when the error drops below ETOL or
  52. % MAXITER iterations are reached.
  53. % ISRATIO Indicates whether a ratio MDS should be performed (1) or not (0).
  54. % If ISRATIO is 1, then instead of fitting the dissimilarities
  55. % D_{ij}, A*D_{ij} is fitted in the stress function. The value A
  56. % is estimated analytically in each iteration.
  57. % ITMAP Determines the way new points are mapped, either in an iterative
  58. % manner ('yes') by minimizing the stress; or by a linear projection
  59. % ('no').
  60. % ST Status, determines whether after each iteration the stress should
  61. % INSPECT be printed on the screen (1) or not (0). When INSPECT > 0,
  62. % ST = 1 and the mapping is onto 2D or larger, then the progress
  63. % is plotted during the minimization every INSPECT iterations.
  64. %
  65. % Important:
  66. % 1. It is assumed that D either is or approximates a Euclidean distance
  67. % matrix, i.e. D_{ij} = sqrt (sum_k(x_i - x_j)^2).
  68. % 2. Missing values can be handled; they should be marked by 'NaN' in D.
  69. %
  70. % EXAMPLES:
  71. % opt.optim = 'scg';
  72. % opt.init = 'cs';
  73. % D = sqrt(distm(a)); % Compute the Euclidean distance dataset of A
  74. % w1 = mds(D,2,opt); % An MDS map onto 2D initialized by Classical Scaling,
  75. % % optimized by a Scaled Conjugate Gradients algorithm
  76. % n = size(D,1);
  77. % y = rand(n,2);
  78. % w2 = mds(D,y,opt); % An MDS map onto 2D initialized by random vectors
  79. %
  80. % z = rand(n,n); % Set around 40% of the random distances to NaN, i.e.
  81. % z = (z+z')/2; % not used in the MDS mapping
  82. % z = find(z <= 0.6);
  83. % D(z) = NaN;
  84. % D(1:n+1:n^2) = 0; % Set the diagonal to zero
  85. % opt.optim = 'pn';
  86. % opt.init = 'randnp';
  87. % opt.etol = 1e-8; % Should be high, as only some distances are used
  88. % w3 = mds(D,2,opt); % An MDS map onto 2D initialized by a random projection
  89. %
  90. % REFERENCES
  91. % 1. M.F. Moler, A Scaled Conjugate Gradient Algorithm for Fast Supervised
  92. % Learning', Neural Networks, vol. 6, 525-533, 1993.
  93. % 2. W.H. Press, S.A. Teukolsky, W.T. Vetterling and B.P. Flannery,
  94. % Numerical Recipes in C, Cambridge University Press, Cambridge, 1992.
  95. % 3. I. Borg and P. Groenen, Modern Multidimensional Scaling, Springer
  96. % Verlag, Berlin, 1997.
  97. % 4. T.F. Cox and M.A.A. Cox, Multidimensional Scaling, Chapman and Hall,
  98. % London, 1994.
  99. %
  100. % SEE ALSO
  101. % MAPPINGS, MDS_STRESS, MDS_INIT, MDS_CS
  102. %
  103. % Copyright: Elzbieta Pekalska, Robert P.W. Duin, ela@ph.tn.tudelft.nl, 2000-2003
  104. % Faculty of Applied Sciences, Delft University of Technology
  105. %
  106. function [w,J,err,opt,y] = mds(D,y,options)
  107. if (nargin < 3)
  108. options = []; % Will be filled by MDS_SETOPT, below.
  109. end
  110. opt = mds_setopt(options);
  111. if (nargin < 2) | (isempty(y))
  112. prwarning(2,'no output dimensionality given, assuming N = 2.');
  113. y = 2;
  114. end
  115. % No arguments given: return an untrained mapping.
  116. if (nargin < 1) | (isempty(D))
  117. w = mapping(mfilename,{y,opt,[],[],[],[]});
  118. w = setname(w,'MDS');
  119. return
  120. end
  121. % YREP contains representative objects in the projected MDS space, i.e.
  122. % for which the mapping exists. YREP is empty for the original MDS, since
  123. % no projection is available yet.
  124. yrep = [];
  125. if (isdataset(D) | isa(D,'double'))
  126. [m,mm] = size(D);
  127. % Convert D to double, but retain labels in LAB.
  128. if (isdataset(D)), lab = getlab(D); D = +D; else, lab = ones(m,1); end;
  129. if (ismapping(y))
  130. % The MDS mapping exists; this means that YREP has already been stored.
  131. pars = getdata(y); [k,c] = size(y);
  132. y = []; % Empty, should now be found.
  133. yrep = pars{1}; % There exists an MDS map, hence YREP is stored.
  134. opt = pars{2}; % Options used for the mapping.
  135. II = pars{3}; % Index of non-repeated points in YREP.
  136. winit = pars{4}; % The Classical Scaling map, if INIT = 'cs'.
  137. v = pars{5}; % Weights used for adding new points if ITMAP = 'no'.
  138. n = c; % Number of dimensions of the projected space.
  139. % Initialization by 'cs' is not possible when there is no winit
  140. % (i.e. the CS map) and new points should be added.
  141. if (strcmp(opt.init,'cs')) & (isempty(winit))
  142. prwarning(2,'OPTIONS.init = cs is not possible when adding points; using kl.');
  143. opt.init = 'kl';
  144. end
  145. % If YREP is a scalar, we have an empty mapping.
  146. if (max(size(yrep)) == 1)
  147. y = yrep;
  148. yrep = [];
  149. end
  150. % Check whether D is a matrix with the zero diagonal for the existing map.
  151. if (m == mm) & (length(intersect(find(D(:)<eps),1:m+1:(m*mm))) >= m)
  152. w = yrep; % D is the same matrix as in the projection process;
  153. return % YREP is then the solution
  154. end
  155. if (length(pars) < 6) | (isempty(pars{6}))
  156. yinit = [];
  157. else
  158. yinit = pars{6}; % Possible initial configuration for points to
  159. % be added to an existing map
  160. if (size(yinit,1) ~= size(D,1))
  161. prwarning(2,'the size of the initial configuration does not match that of the dissimilarity matrix, using random initialization.')
  162. yinit =[];
  163. end
  164. end
  165. else
  166. % No MDS mapping available yet; perform the checks.
  167. if (~issym(D,1e-12))
  168. prwarning(2,'D is not a symmetric matrix; will average.');
  169. D = (D+D')/2;
  170. end
  171. % Check the number of zeros on the diagonal
  172. if (any(abs(diag(D)) > 0))
  173. error('D should have a zero diagonal');
  174. end
  175. end
  176. else % D is neither a dataset nor a matrix of doubles
  177. error('D should be a dataset or a matrix of doubles.');
  178. end
  179. if (~isempty(y))
  180. % Y is the initial configuration or N, no MDS map exists yet;
  181. % D is a square matrix.
  182. % Remove identical points, i.e. points for which D(i,j) = 0 for i ~= j.
  183. % I contains the indices of points left for the MDS mapping, J those
  184. % of the removed points and P those of the points left in I which were
  185. % identical to those removed.
  186. [I,J,P] = mds_reppoints(D);
  187. D = D(I,I);
  188. [ni,nc] = size(D);
  189. % NANID is an extra field in the OPTIONS structure, containing the indices
  190. % of NaN values (= missing values) in distance matrix D.
  191. opt.nanid = find(isnan(D(:)) == 1);
  192. % Initialise Y.
  193. [m2,n] = size(y);
  194. if (max(m2,n) == 1) % Y is a scalar, hence really is a dimensionality N.
  195. n = y;
  196. [y,winit] = mds_init(D,n,opt.init);
  197. else
  198. if (mm ~= m2)
  199. error('The matrix D and the starting configuration Y should have the same number of columns.');
  200. end
  201. winit = [];
  202. y = +y(I,:);
  203. end
  204. % The final number of true distances is:
  205. no_dist = (ni*(ni-1) - length(opt.nanid))/2;
  206. else
  207. % This happens only if we add extra points to an existing MDS map.
  208. % Remove identical points, i.e. points for which D(i,j) = 0 for i ~= j.
  209. % I contains the indices of points left for the MDS mapping, J those
  210. % of the removed points and P those of the points left in I which were
  211. % identical to those removed.
  212. [I,J,P] = mds_reppoints(D(:,II));
  213. D = D(I,II);
  214. [ni,nc] = size(D);
  215. yrep = yrep(II,:);
  216. n = size(yrep,2);
  217. % NANID is an extra field in the OPTIONS structure, containing the indices
  218. % of NaN values (= missing values) in distance matrix D.
  219. opt.nanid = find(isnan(D(:)));
  220. % Initialise Y. if the new points should be added in an iterative manner.
  221. [m2,n] = size(yrep);
  222. if (~isempty(yinit)) % An initial configuration exists.
  223. y = yinit;
  224. elseif (strcmp(opt.init, 'cs')) & (~isempty(winit))
  225. y = D*winit;
  226. else
  227. y = mds_init(D,n,opt.init);
  228. end
  229. if (~isempty(opt.nanid)) % Rescale.
  230. scale = (max(yrep)-min(yrep))./(max(y)-min(y));
  231. y = y .* repmat(scale,ni,1);
  232. end
  233. % The final number of true distances is:
  234. no_dist = (ni*nc - length(opt.nanid));
  235. end
  236. % Check whether there is enough data left.
  237. if (~isempty(opt.nanid))
  238. if (n*ni+2 > no_dist),
  239. error('There are too many missing distances: it is not possible to determine the MDS map.');
  240. end
  241. if (strcmp (opt.itmap,'no'))
  242. opt.itmap = 'yes';
  243. prwarning(1,'Due to the missing values, the projection can only be iterative. OPTIONS are changed appropriately.')
  244. end
  245. end
  246. if (opt.inspect > 0)
  247. opt.plotlab = lab(I,:); % Labels to be used for plotting in MDS_SAMMAP.
  248. end
  249. if (isempty(yrep)) | (~isempty(yrep) & strcmp(opt.itmap, 'yes'))
  250. % Either no MDS exists yet OR new points should be mapped in an iterative manner.
  251. printinfo(opt);
  252. [yy,err] = mds_sammap(D,y,yrep,opt);
  253. % Define the linear projection of distances.
  254. v = [];
  255. if (isempty(yrep)) & (isempty(opt.nanid))
  256. if (rank(D) < m)
  257. v = pinv(D)*yy;
  258. else
  259. v = D \ yy;
  260. end
  261. end
  262. else
  263. % New points should be added by a linear projection of dissimilarity data.
  264. yy = D*v;
  265. end
  266. % Establish the projected configuration including the removed points.
  267. y = zeros(m,n); y(I,:) = +yy;
  268. if (~isempty(J))
  269. if (~isempty(yrep))
  270. y(J,:) = +yrep(II(P),:);
  271. else
  272. for k=length(J):-1:1 % J: indices of removed points.
  273. y(J(k),:) = y(P(k),:); % P: indices of points equal to points in J.
  274. end
  275. end
  276. end
  277. % In the definition step: shift the obtained configuration such that the
  278. % mean lies at the origin.
  279. if (isempty(yrep))
  280. y = y - ones(length(y),1)*mean(y);
  281. y = dataset(y,lab);
  282. else
  283. w = dataset(y,lab);
  284. return;
  285. end
  286. % In the definition step: the mapping should be stored.
  287. opt = rmfield(opt,'nanid'); % These fields are to be used internally only;
  288. opt = rmfield(opt,'plotlab'); % not to be set up from outside
  289. w = mapping(mfilename,'trained',{y,opt,I,winit,v,[]},[],m,n);
  290. w = setname(w,'MDS');
  291. return
  292. % **********************************************************************************
  293. % Extra functions
  294. % **********************************************************************************
  295. % PRINTINFO(OPT)
  296. %
  297. % Prints progress information to the file handle given by OPT.ST.
  298. function printinfo(opt)
  299. fprintf(opt.st,'Sammon mapping, error function with the parameter q=%d\n',opt.q);
  300. switch (opt.optim)
  301. case 'pn',
  302. fprintf(opt.st,'Minimization by Pseudo-Newton algorithm\n');
  303. case 'scg',
  304. fprintf(opt.st,'Minimization by Scaled Conjugate Gradients algorithm\n');
  305. otherwise
  306. error(strcat('Possible initialization methods: pn (Pseudo-Newton), ',...
  307. 'or scg (Scaled Conjugate Gradients).'));
  308. end
  309. return
  310. % **********************************************************************************
  311. % [I,J,P] = MDS_REPPOINTS(D)
  312. %
  313. % Finds the indices of repeated/left points. J contains the indices of
  314. % repeated points in D. This means that for each j in J, there is a point
  315. % k ~= j such that D(J(j),k) = 0. I contains the indices of the remaining
  316. % points, and P those of the points in I that were identical to those in J.
  317. % Directly used in MDS routine.
  318. function [I,J,P] = mds_reppoints(D)
  319. epsilon = 1e-20; % Differences smaller than this are assumed to be zero.
  320. [m,mm] = size(D);
  321. I = 1:m; J = []; P = [];
  322. if (m == mm) & (all(abs(D(1:m+1:end)) <= epsilon))
  323. K = intersect (find (triu(ones(m),1)), find(D < epsilon));
  324. if (~isempty(K))
  325. P = mod(K,m);
  326. J = fix(K./m) + 1;
  327. I(J) = [];
  328. end
  329. else
  330. [J,P] = find(D<=epsilon);
  331. I(J) = [];
  332. end
  333. return;
  334. % **********************************************************************************
  335. % MDS_SETOPT Sets parameters for the MDS mapping
  336. %
  337. % OPT = MDS_SETOPT(OPT_GIVEN)
  338. %
  339. % INPUT
  340. % OPT_GIVEN Parameters for the MDS mapping, described below (default: [])
  341. %
  342. % OUTPUT
  343. % OPT Structure of chosen options; if OPT_GIVEN is empty, then
  344. % OPT.q = 0
  345. % OPT.optim = 'pn'
  346. % OPT.init = 'cs'
  347. % OPT.etol = 1e-6 (the precise value depends on q)
  348. % OPT.maxiter = inf
  349. % OPT.itmap = 'yes'
  350. % OPT.isratio = 0
  351. % OPT.st = 1
  352. % OPT.inspect = 2
  353. %
  354. % DESCRIPTION
  355. % Parameters for the MDS mapping can be set or changed. OPT_GIVEN consists
  356. % of the following fields: 'q', 'init', 'optim', 'etol', 'maxiter','itmap',
  357. % 'isratio', 'st' and 'inspect'. OPTIONS can include all the fields or some
  358. % of them only. The fields of OPT have some default values, which can be
  359. % changed by the OPT_GIVEN field values. If OPT_GIVEN is empty, then OPT
  360. % contains all default values. For a description of the fields, see MDS.
  361. %
  362. % Copyright: Elzbieta Pekalska, ela@ph.tn.tudelft.nl, 2000-2003
  363. % Faculty of Applied Sciences, Delft University of Technology
  364. %
  365. function opt = mds_setopt (opt_given)
  366. opt.q = 0;
  367. opt.init = 'cs';
  368. opt.optim = 'pn';
  369. opt.st = 1;
  370. opt.itmap = 'yes';
  371. opt.maxiter = inf;
  372. opt.inspect = 2;
  373. opt.etol = inf;
  374. opt.isratio = 0;
  375. opt.nanid = []; % Here are some extra values; set up in the MDS routine.
  376. opt.plotlab = []; % Not to be changed by the user.
  377. if (~isempty(opt_given))
  378. if (~isstruct(opt_given))
  379. error('OPTIONS should be a structure with at least one of the following fields: q, init, etol, optim, maxiter, itmap, isratio, st or inspect.');
  380. end
  381. fn = fieldnames(opt_given);
  382. if (~all(ismember(fn,fieldnames(opt))))
  383. error('Wrong field names; valid field names are: q, init, optim, etol, maxiter, itmap, isratio, st or inspect.')
  384. end
  385. for i = 1:length(fn)
  386. opt = setfield(opt,fn{i},getfield(opt_given,fn{i}));
  387. end
  388. end
  389. if (isempty(intersect(opt.q,-2:1:2)))
  390. error ('OPTIONS.q should be -2, -1, 0, 1 or 2.');
  391. end
  392. if (opt.maxiter < 2)
  393. error ('OPTIONS.iter should be at least 1.');
  394. end
  395. if (isinf(opt.etol))
  396. switch (opt.q) % Different defaults for different stresses.
  397. case -2,
  398. opt.etol = 1e-6;
  399. case {-1,0}
  400. opt.etol = 10*sqrt(eps);
  401. case {1,2}
  402. opt.etol = sqrt(eps);
  403. end
  404. elseif (opt.etol <= 0) | (opt.etol >= 0.1)
  405. error ('OPTIONS.etol should be positive and smaller than 0.1.');
  406. end
  407. if (~ismember(opt.optim, {'pn','scg'}))
  408. error('OPTIONS.optim should be pn or scg.');
  409. end
  410. if (~ismember(opt.itmap, {'yes','no'}))
  411. error('OPTIONS.itmap should be yes or no.');
  412. end
  413. return
  414. % **********************************************************************************
  415. % MDS_SAMMAP Sammon iterative nonlinear mapping for MDS
  416. %
  417. % [YMAP,ERR] = MDS_SAMMAP(D,Y,YREP,OPTIONS)
  418. %
  419. % INPUT
  420. % D Square (M x M) dissimilarity matrix
  421. % Y M x N matrix containing starting configuration, or
  422. % YREP Configuration of the representation points
  423. % OPTIONS Various parameters of the minimization procedure put into
  424. % a structure consisting of the following fields: 'q', 'optim',
  425. % 'init','etol','maxiter', 'isratio', 'itmap', 'st' and 'inspect'
  426. % (default:
  427. % OPTIONS.q = 0
  428. % OPTIONS.optim = 'pn'
  429. % OPTIONS.init = 'cs'
  430. % OPTIONS.etol = 1e-6 (the precise value depends on q)
  431. % OPTIONS.maxiter = inf
  432. % OPTIONS.isratio = 0
  433. % OPTIONS.itmap = 'yes'
  434. % OPTIONS.st = 1
  435. % OPTIONS.inspect = 2).
  436. %
  437. % OUTPUT
  438. % YMAP Mapped configuration
  439. % ERR Sammon stress
  440. %
  441. % DESCRIPTION
  442. % Maps the objects given by a symmetric distance matrix D (with a zero
  443. % diagonal) onto, say, an N-dimensional configuration YMAP by an iterative
  444. % minimization of a variant of the Sammon stress. The minimization starts
  445. % from the initial configuration Y; see MDS_INIT.
  446. %
  447. % YREP is the Sammon configuration of the representation set. It is used
  448. % when new points have to be projected. In other words, if D is an M x M
  449. % symmetric distance matrix, then YREP is empty; if D is an M x N matrix,
  450. % then YMAP is sought such that D can approximate the distances between YMAP
  451. % and YREP.
  452. %
  453. % Missing values can be handled by marking them by NaN in D.
  454. %
  455. % SEE ALSO
  456. % MAPPINGS, MDS, MDS_CS, MDS_INIT, MDS_SETOPT
  457. %
  458. % Copyright: Elzbieta Pekalska, ela@ph.tn.tudelft.nl, 2000-2003
  459. % Faculty of Applied Sciences, Delft University of Technology
  460. %
  461. function [y,err] = mds_sammap(Ds,y,yrep,opt)
  462. if (nargin < 4)
  463. opt = []; % Will be filled by MDS_SETOPT, below.
  464. end
  465. if isempty(opt), opt = mds_setopt(opt); end
  466. if (nargin < 3)
  467. yrep = [];
  468. end
  469. % Extract labels and calculate distance matrix.
  470. [m,n] = size(y);
  471. if (~isempty(yrep))
  472. replab = getlab(yrep);
  473. yrep = +yrep;
  474. D = sqrt(distm(y,yrep));
  475. else
  476. D = sqrt(distm(y));
  477. yrep = [];
  478. replab = [];
  479. end
  480. if (isempty(opt.plotlab))
  481. opt.plotlab = ones(m,1);
  482. end
  483. it = 0; % Iteration number.
  484. eold = inf; % Previous stress (error).
  485. % Calculate initial stress.
  486. [e,a]= mds_samstress(opt.q,y,yrep,Ds,D,opt.nanid,opt.isratio); err = e;
  487. fprintf(opt.st,'iteration: %4i stress: %3.8d\n',it,e);
  488. switch (opt.optim)
  489. case 'pn', % Pseudo-Newton minimization.
  490. epr = e; % Previous values of E, EOLD for line search algorithm.
  491. eepr = e;
  492. add = 1e-4; % Avoid G2 equal 0; see below.
  493. BETA = 1e-3; % Parameter for line search algorithm.
  494. lam1 = 0;
  495. lam2 = 1; % LAM (the step factor) lies in (LAM1, LAM2).
  496. LMIN = 1e-10; % Minimum acceptable value for LAM in line search.
  497. lambest = 1e-4; % LAM = LAMBEST, if LAM was not found.
  498. % Loop until the error change falls below the tolerance or until
  499. % the maximum number of iterations is reached.
  500. while (abs(e-eold) >= opt.etol*(1 + abs(e))) & (it < opt.maxiter)
  501. % Plot progress if requested.
  502. if (opt.st > 0) & (opt.inspect > 0) & (mod(it,opt.inspect) == 0)
  503. % if (it == 0), figure(1); clf; end
  504. mds_plot(y,yrep,opt.plotlab,replab,e);
  505. end
  506. yold = y; eold = e;
  507. % Calculate the stress.
  508. [e,a] = mds_samstress (opt.q,yold,yrep,Ds,[],opt.nanid,opt.isratio);
  509. % Calculate gradients and pseudo-Newton update P.
  510. [g1,g2,cc] = mds_gradients (opt.q,yold,yrep,a*Ds,[],opt.nanid);
  511. p = (g1/cc)./(abs(g2/cc) + add);
  512. slope = g1(:)' * p(:);
  513. lam = 1;
  514. % Search for a suitable step LAM using a line search.
  515. while (1)
  516. % Take a step and calculate the delta error DE.
  517. y = yold + lam .* p;
  518. [e,a] = mds_samstress(opt.q,y,yrep,Ds,[],opt.nanid,opt.isratio);
  519. de = e - eold;
  520. % Stop if the condition for a suitable lam is fulfilled.
  521. if (de < BETA * lam * slope)
  522. break;
  523. end
  524. % Try to find a suitable step LAM.
  525. if (lam ~= 1)
  526. r1 = de - lam*slope;
  527. r2 = epr - eepr - lam2*slope;
  528. aa = (2*de + r1/lam^2 - r2/lam2^2)/(lam2-lam1)^3;
  529. bb = ((-lam2*r1)/lam^2 +(lam*r2)/lam2^2)/(lam2-lam1)^2;
  530. if (abs(aa) <= eps)
  531. lamtmp = -0.5 * slope/bb;
  532. else
  533. lamtmp = (-bb + sqrt(max(0,(bb^2 - 3*aa*slope))))/(3*aa);
  534. end
  535. % Prevent LAM from becoming too large.
  536. if (lamtmp > 0.5 * lam), lamtmp = 0.5 * lam; end
  537. else
  538. lamtmp = -0.5 * slope/(e - eold - slope);
  539. end
  540. % Prevent LAM from becoming too small.
  541. lam2 = lam;
  542. lam = max(lamtmp, 0.1*lam);
  543. epr = e;
  544. eepr = eold;
  545. if (lam < LMIN)
  546. y = yold + lambest .* p;
  547. [e,a] = mds_samstress(opt.q,y,yrep,Ds,[],opt.nanid,opt.isratio);
  548. break;
  549. end
  550. end
  551. it = it + 1;
  552. err = [err; e];
  553. fprintf(opt.st,'iteration: %4i stress: %3.8d\n',it,e);
  554. end
  555. case 'scg', % Scaled Conjugate Gradient minimization.
  556. sigma0 = 1e-8;
  557. lambda = 1e-8; % Regularization parameter.
  558. lambda1 = 0;
  559. % Calculate initial stress and direction of decrease P.
  560. [e,a] = mds_samstress(opt.q,y,yrep,Ds,D,opt.nanid,opt.isratio);
  561. g1 = mds_gradients(opt.q,y,yrep,a*Ds,D,opt.nanid); % Gradient
  562. p = -g1; % Direction of decrease
  563. gnorm2 = g1(:)' * g1(:);
  564. success = 1;
  565. % Sanity check.
  566. if (gnorm2 < 1e-15)
  567. prwarning(2,['Gradient is nearly zero: ' gnorm2 ' (unlikely); initial configuration is the right one.']);
  568. return
  569. end
  570. % Loop until the error change falls below the tolerance or until
  571. % the maximum number of iterations is reached.
  572. while (abs(eold - e) > opt.etol * (1 + e)) & (it < opt.maxiter)
  573. g0 = g1; % Previous gradient
  574. pnorm2 = p(:)' * p(:);
  575. eold = e;
  576. % Plot progress if requested.
  577. if (opt.inspect > 0) & (mod(it,opt.inspect) == 0)
  578. % if (it == 0), figure(1); clf; end
  579. mds_plot(y,yrep,opt.plotlab,replab,e);
  580. end
  581. if (success)
  582. sigma = sigma0/sqrt(pnorm2); % SIGMA: a small step from y to yy
  583. yy = y + sigma .*p;
  584. [e,a] = mds_samstress(opt.q,yy,yrep,Ds,[],opt.nanid,opt.isratio);
  585. g2 = mds_gradients(opt.q,yy,yrep,a*Ds,[],opt.nanid); % Gradient for yy
  586. s = (g2-g1)/sigma; % Approximation of Hessian*P directly, instead of computing
  587. delta = p(:)' * s(:); % the Hessian, since only DELTA = P'*Hessian*P is in fact needed
  588. end
  589. % Regularize the Hessian indirectly to make it positive definite.
  590. % DELTA is now computed as P'* (Hessian + regularization * Identity) * P
  591. delta = delta + (lambda1 - lambda) * pnorm2;
  592. % Indicate if the Hessian is negative definite; the regularization above was too small.
  593. if (delta < 0)
  594. lambda1 = 2 * (lambda - delta/pnorm2); % Now the Hessian will be positive definite
  595. delta = -delta + lambda * pnorm2; % This is obtained after plugging lambda1
  596. lambda = lambda1; % into the formulation a few lines above.
  597. end
  598. mi = - p(:)' * g1(:);
  599. yy = y + (mi/delta) .*p; % mi/delta is a step size
  600. [ee,a] = mds_samstress(opt.q,yy,yrep,Ds,[],opt.nanid,opt.isratio);
  601. % This minimization procedure is based on the second order approximation of the
  602. % stress function by using the gradient and the Hessian approximation. The Hessian
  603. % is regularized, but maybe not sufficiently. The ratio Dc (<=1) below indicates
  604. % a proper approximation, if Dc is close to 1.
  605. Dc = 2 * delta/mi^2 * (e - ee);
  606. e = ee;
  607. % If Dc > 0, then the stress can be successfully decreased.
  608. success = (Dc >= 0);
  609. if (success)
  610. y = yy;
  611. [ee,a] = mds_samstress(opt.q,yy,yrep,Ds,[],opt.nanid,opt.isratio);
  612. g1 = mds_gradients(opt.q,yy,yrep,a*Ds,[],opt.nanid);
  613. gnorm2 = g1(:)' * g1(:);
  614. lambda1 = 0;
  615. beta = max(0,(g1(:)'*(g1(:)-g0(:)))/mi);
  616. p = -g1 + beta .* p; % P is a new conjugate direction
  617. if (g1(:)'*p(:) >= 0 | mod(it-1,n*m) == 0),
  618. p = -g1; % No much improvement, restart
  619. end
  620. if (Dc >= 0.75)
  621. lambda = 0.25 * lambda; % Make the regularization smaller
  622. end
  623. it = it + 1;
  624. err = [err; e];
  625. fprintf (opt.st,'iteration: %4i stress: %3.8d\n',it,e);
  626. else % Dc < 0
  627. % Note that for Dc < 0, the iteration number IT is not increased,
  628. % so the Hessian is further regularized until SUCCESS equals 1.
  629. lambda1 = lambda;
  630. end
  631. % The approximation of the Hessian was poor or the stress was not
  632. % decreased (Dc < 0), hence the regularization lambda is enlarged.
  633. if (Dc < 0.25)
  634. lambda = lambda + delta * (1 - Dc)/pnorm2;
  635. end
  636. end
  637. end
  638. return;
  639. % **********************************************************************************
  640. %MDS_STRESS - Calculate Sammon stress during optimization
  641. %
  642. % E = MDS_SAMSTRESS(Q,Y,YREP,Ds,D,NANINDEX)
  643. %
  644. % INPUT
  645. % Q Indicator of the Sammon stress; Q = -2,-1,0,1,2
  646. % Y Current lower-dimensional configuration
  647. % YREP Configuration of the representation objects; it should be
  648. % empty when no representation set is considered
  649. % Ds Original distance matrix
  650. % D Approximate distance matrix (optional; otherwise computed from Y)
  651. % NANINDEX Index of the missing values; marked in Ds by NaN (optional; to
  652. % be found in Ds)
  653. %
  654. % OUTPUT
  655. % E Sammon stress
  656. %
  657. % DESCRIPTION
  658. % Computes the Sammon stress between the original distance matrix Ds and the
  659. % approximated distance matrix D between the mapped configuration Y and the
  660. % configuration of the representation set YREP, expressed as follows:
  661. %
  662. % E = 1/(sum_{i<j} Ds_{ij}^(q+2)) sum_{i<j} (Ds_{ij} - D_{ij})^2 * Ds_{ij}^q
  663. %
  664. % It is directly used in the MDS_SAMMAP routine.
  665. %
  666. % Copyright: Elzbieta Pekalska, Robert P.W. Duin, ela@ph.tn.tudelft.nl, 2000-2003
  667. % Faculty of Applied Sciences, Delft University of Technology
  668. %
  669. function [e,alpha] = mds_samstress (q,y,yrep,Ds,D,nanindex,isratio)
  670. % If D is not given, calculate it from Y, the current mapped points.
  671. if (nargin < 5) | (isempty(D))
  672. if (~isempty(yrep))
  673. D = sqrt(distm(y,yrep));
  674. else
  675. D = sqrt(distm(y));
  676. end
  677. end
  678. if (nargin < 6)
  679. nanindex = []; % Not given, so calculate below.
  680. end
  681. if (nargin < 7)
  682. isratio = 0; % Assume this is meant.
  683. end
  684. todefine = isempty(yrep);
  685. [m,n] = size(y); [mm,k] = size(Ds);
  686. if (m ~= mm)
  687. error('The sizes of Y and Ds do not match.');
  688. end
  689. if (any(size(D) ~= size(Ds)))
  690. error ('The sizes of D and Ds do not match.');
  691. end
  692. m2 = m*k;
  693. % Convert to double.
  694. D = +D; Ds = +Ds;
  695. % I is the index of non-NaN, non-zero (> eps) values to be included
  696. % for the computation of the stress.
  697. I = 1:m2;
  698. if (~isempty(nanindex))
  699. I(nanindex) = [];
  700. end
  701. O = [];
  702. if (todefine), O = 1:m+1:m2; end
  703. I = setdiff(I,O);
  704. % If OPTIONS.isratio is set, calculate optimal ALPHA to scale with.
  705. if (isratio)
  706. alpha = sum((Ds(I).^q).*D(I).^2)/sum((Ds(I).^(q+1)).*D(I));
  707. Ds = alpha*Ds;
  708. else
  709. alpha = 1;
  710. end
  711. % C is the normalization factor.
  712. c = sum(Ds(I).^(q+2));
  713. % If Q == 0, prevent unnecessary calculation (X^0 == 1).
  714. if (q ~= 0)
  715. e = sum(Ds(I).^q .*((Ds(I)-D(I)).^2))/c;
  716. else
  717. e = sum(((Ds(I)-D(I)).^2))/c;
  718. end
  719. return;
  720. % **********************************************************************************
  721. %MDS_GRADIENTS - Gradients for variants of the Sammon stress
  722. %
  723. % [G1,G2,CC] = MDS_GRADIENTS(Q,Y,YREP,Ds,D,NANINDEX)
  724. %
  725. % INPUT
  726. % Q Indicator of the Sammon stress; Q = -2,-1,0,1,2
  727. % Y Current lower-dimensional configuration
  728. % YREP Configuration of the representation objects; it should be
  729. % empty when no representation set is considered
  730. % Ds Original distance matrix
  731. % D Approximate distance matrix (optional; otherwise computed from Y)
  732. % nanindex Index of missing values; marked in Ds by NaN (optional;
  733. % otherwise found from Ds)
  734. %
  735. % OUTPUT
  736. % G1 Gradient direction
  737. % G2 Approximation of the Hessian by its diagonal
  738. %
  739. % DESCRIPTION
  740. % This is a routine used directly in the MDS_SAMMAP routine.
  741. %
  742. % SEE ALSO
  743. % MDS, MDS_INIT, MDS_SAMMAP, MDS_SAMSTRESS
  744. %
  745. % Copyright: Elzbieta Pekalska, Robert P.W. Duin, ela@ph.tn.tudelft.nl, 2000-2003
  746. % Faculty of Applied Sciences, Delft University of Technology
  747. %
  748. function [g1,g2,c] = mds_gradients(q,y,yrep,Ds,D,nanindex)
  749. % If D is not given, calculate it from Y, the current mapped points.
  750. if (nargin < 5) | (isempty(D))
  751. if (~isempty(yrep))
  752. D = sqrt(distm(y,yrep));
  753. else
  754. D = sqrt(distm(y));
  755. end
  756. end
  757. % If NANINDEX is not given, find it from Ds.
  758. if (nargin < 6)
  759. nanindex = find(isnan(Ds(:))==1);
  760. end
  761. % YREP is empty if no representation set is defined yet.
  762. % This happens when the mapping should be defined.
  763. todefine = (isempty(yrep));
  764. if (todefine)
  765. yrep = y;
  766. end
  767. [m,n] = size(y); [mm,k] = size(Ds);
  768. if (m ~= mm)
  769. error('The sizes of Y and Ds do not match.');
  770. end
  771. if (any(size(D) ~= size(Ds)))
  772. error ('The sizes of D and Ds do not match.');
  773. end
  774. m2 = m*k;
  775. % Convert to doubles.
  776. D = +D; Ds = +Ds;
  777. % I is the index of non-NaN, non-zero (> eps) values to be included
  778. % for the computation of the gradient and the Hessians diagonal.
  779. I = (1:m2)';
  780. if (~isempty(nanindex))
  781. I(nanindex) = [];
  782. end
  783. K = find(Ds(I) <= eps | D(I) <= eps);
  784. I(K) = [];
  785. % C is a normalization factor.
  786. c = -2/sum(Ds(I).^(q+2));
  787. % Compute G1, the gradient.
  788. h1 = zeros(m2,1);
  789. if (q == 0) % Prevent unnecessary computation when Q = 0.
  790. h1(I) = (Ds(I)-D(I)) ./ D(I);
  791. else
  792. h1(I) = (Ds(I)-D(I)) ./ (D(I).*(Ds(I).^(-q)));
  793. end
  794. h1 = reshape (h1',m,k);
  795. g2 = h1 * ones(k,n); % Here G2 is assigned only temporarily,
  796. g1 = c * (g2.*y - h1*yrep); % for the computation of G1.
  797. % Compute G2, the diagonal of the Hessian, if requested.
  798. if (nargout > 1)
  799. h2 = zeros(m2,1);
  800. switch (q)
  801. case -2,
  802. h2(I) = -1./(Ds(I).*D(I).^3);
  803. case -1,
  804. h2(I) = -1./(D(I).^3);
  805. case 0,
  806. h2(I) = - Ds(I)./(D(I).^3);
  807. case 1,
  808. h2(I) = - Ds(I).^2./(D(I).^3);
  809. case 2,
  810. h2(I) = -(Ds(I)./D(I)).^3;
  811. end
  812. h2 = reshape (h2',m,k);
  813. g2 = c * (g2 + (h2*ones(k,n)).*y.^2 + h2*yrep.^2 - 2*(h2*yrep).*y);
  814. end
  815. return;
  816. % **********************************************************************************
  817. %MDS_PLOT Plots the results of the MDS mapping in a 2D or 3D figure
  818. %
  819. % MDS_PLOT(Y,YREP,LAB,REPLAB,E)
  820. %
  821. % INPUT
  822. % Y Configuration in 2D or 3D space
  823. % YREP Configuration of the representation points in 2D or 3D space
  824. % LAB Labels of Y
  825. % REPLAB Labels of YREP
  826. % E Stress value
  827. %
  828. % DESCRIPTION
  829. % Used directly in MDS_SAMMAP routine.
  830. function mds_plot (y,yrep,lab,replab,e)
  831. % This is done in a rather complicated way in order to speed up drawing.
  832. K = min(size(y,2),3);
  833. if (K > 1)
  834. y = +y;
  835. col = 'brmk';
  836. sym = ['+*xosdv^<>p']';
  837. ii = [1:44];
  838. s = [col(ii-floor((ii-1)/4)*4)' sym(ii-floor((ii-1)/11)*11)];
  839. [nlab,lablist] = renumlab([replab;lab]); c = max(nlab);
  840. [ms,ns] = size(s); if (ms == 1), s = setstr(ones(m,1)*s); end
  841. yy = [yrep; y];
  842. cla;
  843. if (K == 2)
  844. hold on;
  845. for i = 1:c
  846. J = find(nlab==i); plot(yy(J,1),yy(J,2),s(i,:));
  847. end
  848. else
  849. for i = 1:c
  850. J = find(nlab==i); plot3(yy(J,1),yy(J,2),yy(J,3),s(i,:));
  851. if (i == 1), hold on; grid on; end
  852. end
  853. end
  854. title(sprintf('STRESS: %f', e));
  855. axis equal;
  856. drawnow;
  857. end
  858. return;