somtoolbox2/som_seqtrain.m
4dbef185
 function [sMap, sTrain] = som_seqtrain(sMap, D, varargin)
 
 %SOM_SEQTRAIN  Use sequential algorithm to train the Self-Organizing Map.
 %
 % [sM,sT] = som_seqtrain(sM, D, [[argID,] value, ...])
 % 
 %  sM     = som_seqtrain(sM,D);
 %  sM     = som_seqtrain(sM,sD,'alpha_type','power','tracking',3);
 %  [M,sT] = som_seqtrain(M,D,'ep','trainlen',10,'inv','hexa');
 %
 %  Input and output arguments ([]'s are optional): 
 %   sM      (struct) map struct, the trained and updated map is returned
 %           (matrix) codebook matrix of a self-organizing map
 %                    size munits x dim or  msize(1) x ... x msize(k) x dim
 %                    The trained map codebook is returned.
 %   D       (struct) training data; data struct
 %           (matrix) training data, size dlen x dim
 %   [argID, (string) See below. The values which are unambiguous can 
 %    value] (varies) be given without the preceeding argID.
 %
 %   sT      (struct) learning parameters used during the training
 %
 % Here are the valid argument IDs and corresponding values. The values which
 % are unambiguous (marked with '*') can be given without the preceeding argID.
 %   'mask'        (vector) BMU search mask, size dim x 1
 %   'msize'       (vector) map size
 %   'radius'      (vector) neighborhood radiuses, length 1, 2 or trainlen
 %   'radius_ini'  (scalar) initial training radius
 %   'radius_fin'  (scalar) final training radius
 %   'alpha'       (vector) learning rates, length trainlen
 %   'alpha_ini'   (scalar) initial learning rate
 %   'tracking'    (scalar) tracking level, 0-3 
 %   'trainlen'    (scalar) training length
 %   'trainlen_type' *(string) is the given trainlen 'samples' or 'epochs'
 %   'train'      *(struct) train struct, parameters for training
 %   'sTrain','som_train '  = 'train'
 %   'alpha_type' *(string) learning rate function, 'inv', 'linear' or 'power'
 %   'sample_order'*(string) order of samples: 'random' or 'ordered'
 %   'neigh'      *(string) neighborhood function, 'gaussian', 'cutgauss',
 %                          'ep' or 'bubble'
 %   'topol'      *(struct) topology struct
 %   'som_topol','sTopo l'  = 'topol'
 %   'lattice'    *(string) map lattice, 'hexa' or 'rect'
 %   'shape'      *(string) map shape, 'sheet', 'cyl' or 'toroid'
 %
 % For more help, try 'type som_seqtrain' or check out online documentation.
 % See also  SOM_MAKE, SOM_BATCHTRAIN, SOM_TRAIN_STRUCT.
 
 %%%%%%%%%%%%% DETAILED DESCRIPTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %
 % som_seqtrain
 %
 % PURPOSE
 %
 % Trains a Self-Organizing Map using the sequential algorithm. 
 %
 % SYNTAX
 %
 %  sM = som_seqtrain(sM,D);
 %  sM = som_seqtrain(sM,sD);
 %  sM = som_seqtrain(...,'argID',value,...);
 %  sM = som_seqtrain(...,value,...);
 %  [sM,sT] = som_seqtrain(M,D,...);
 %
 % DESCRIPTION
 %
 % Trains the given SOM (sM or M above) with the given training data
 % (sD or D) using sequential SOM training algorithm. If no optional
 % arguments (argID, value) are given, a default training is done, the
 % parameters are obtained from SOM_TRAIN_STRUCT function. Using
 % optional arguments the training parameters can be specified. Returns
 % the trained and updated SOM and a train struct which contains
 % information on the training.
 %
 % REFERENCES
 %
 % Kohonen, T., "Self-Organizing Map", 2nd ed., Springer-Verlag, 
 %    Berlin, 1995, pp. 78-82.
 % Kohonen, T., "Clustering, Taxonomy, and Topological Maps of
 %    Patterns", International Conference on Pattern Recognition
 %    (ICPR), 1982, pp. 114-128.
 % Kohonen, T., "Self-Organized formation of topologically correct
 %    feature maps", Biological Cybernetics 43, 1982, pp. 59-69.
 %
 % REQUIRED INPUT ARGUMENTS
 %
 %  sM          The map to be trained. 
 %     (struct) map struct
 %     (matrix) codebook matrix (field .data of map struct)
 %              Size is either [munits dim], in which case the map grid 
 %              dimensions (msize) should be specified with optional arguments,
 %              or [msize(1) ... msize(k) dim] in which case the map 
 %              grid dimensions are taken from the size of the matrix. 
 %              Lattice, by default, is 'rect' and shape 'sheet'.
 %  D           Training data.
 %     (struct) data struct
 %     (matrix) data matrix, size [dlen dim]
 %  
 % OPTIONAL INPUT ARGUMENTS 
 %
 %  argID (string) Argument identifier string (see below).
 %  value (varies) Value for the argument (see below).
 %
 %  The optional arguments can be given as 'argID',value -pairs. If an
 %  argument is given value multiple times, the last one is
 %  used. The valid IDs and corresponding values are listed below. The values 
 %  which are unambiguous (marked with '*') can be given without the 
 %  preceeding argID.
 %
 %   'mask'       (vector) BMU search mask, size dim x 1. Default is 
 %                         the one in sM (field '.mask') or a vector of
 %                         ones if only a codebook matrix was given.
 %   'msize'      (vector) map grid dimensions. Default is the one
 %                         in sM (field sM.topol.msize) or 
 %                         'si = size(sM); msize = si(1:end-1);' 
 %                         if only a codebook matrix was given. 
 %   'radius'     (vector) neighborhood radius 
 %                         length = 1: radius_ini = radius
 %                         length = 2: [radius_ini radius_fin] = radius
 %                         length > 2: the vector given neighborhood
 %                                     radius for each step separately
 %                                     trainlen = length(radius)
 %   'radius_ini' (scalar) initial training radius
 %   'radius_fin' (scalar) final training radius
 %   'alpha'      (vector) learning rate
 %                         length = 1: alpha_ini = alpha
 %                         length > 1: the vector gives learning rate
 %                                     for each step separately
 %                                     trainlen is set to length(alpha)
 %                                     alpha_type is set to 'user defined'
 %   'alpha_ini'  (scalar) initial learning rate
 %   'tracking'   (scalar) tracking level: 0, 1 (default), 2 or 3
 %                         0 - estimate time 
 %                         1 - track time and quantization error 
 %                         2 - plot quantization error
 %                         3 - plot quantization error and two first 
 %                             components 
 %   'trainlen'   (scalar) training length (see also 'tlen_type')
 %   'trainlen_type' *(string) is the trainlen argument given in 'epochs'
 %                         or in 'samples'. Default is 'epochs'.
 %   'sample_order'*(string) is the sample order 'random' (which is the 
 %                         the default) or 'ordered' in which case
 %                         samples are taken in the order in which they 
 %                         appear in the data set
 %   'train'     *(struct) train struct, parameters for training. 
 %                         Default parameters, unless specified, 
 %                         are acquired using SOM_TRAIN_STRUCT (this 
 %                         also applies for 'trainlen', 'alpha_type',
 %                         'alpha_ini', 'radius_ini' and 'radius_fin').
 %   'sTrain', 'som_train' (struct) = 'train'
 %   'neigh'     *(string) The used neighborhood function. Default is 
 %                         the one in sM (field '.neigh') or 'gaussian'
 %                         if only a codebook matrix was given. Other 
 %                         possible values is 'cutgauss', 'ep' and 'bubble'.
 %   'topol'     *(struct) topology of the map. Default is the one
 %                         in sM (field '.topol').
 %   'sTopol', 'som_topol' (struct) = 'topol'
 %   'alpha_type'*(string) learning rate function, 'inv', 'linear' or 'power'
 %   'lattice'   *(string) map lattice. Default is the one in sM
 %                         (field sM.topol.lattice) or 'rect' 
 %                         if only a codebook matrix was given. 
 %   'shape'     *(string) map shape. Default is the one in sM
 %                         (field sM.topol.shape) or 'sheet' 
 %                         if only a codebook matrix was given. 
 %   
 % OUTPUT ARGUMENTS
 % 
 %  sM          the trained map
 %     (struct) if a map struct was given as input argument, a 
 %              map struct is also returned. The current training 
 %              is added to the training history (sM.trainhist).
 %              The 'neigh' and 'mask' fields of the map struct
 %              are updated to match those of the training.
 %     (matrix) if a matrix was given as input argument, a matrix
 %              is also returned with the same size as the input 
 %              argument.
 %  sT (struct) train struct; information of the accomplished training
 %  
 % EXAMPLES
 %
 % Simplest case:
 %  sM = som_seqtrain(sM,D);  
 %  sM = som_seqtrain(sM,sD);  
 %
 % To change the tracking level, 'tracking' argument is specified:
 %  sM = som_seqtrain(sM,D,'tracking',3);
 %
 % The change training parameters, the optional arguments 'train', 
 % 'neigh','mask','trainlen','radius','radius_ini', 'radius_fin', 
 % 'alpha', 'alpha_type' and 'alpha_ini' are used. 
 %  sM = som_seqtrain(sM,D,'neigh','cutgauss','trainlen',10,'radius_fin',0);
 %
 % Another way to specify training parameters is to create a train struct:
 %  sTrain = som_train_struct(sM,'dlen',size(D,1),'algorithm','seq');
 %  sTrain = som_set(sTrain,'neigh','cutgauss');
 %  sM = som_seqtrain(sM,D,sTrain);
 %
 % By default the neighborhood radius goes linearly from radius_ini to
 % radius_fin. If you want to change this, you can use the 'radius' argument
 % to specify the neighborhood radius for each step separately:
 %  sM = som_seqtrain(sM,D,'radius',[5 3 1 1 1 1 0.5 0.5 0.5]);
 %
 % By default the learning rate (alpha) goes from the alpha_ini to 0
 % along the function defined by alpha_type. If you want to change this, 
 % you can use the 'alpha' argument to specify the learning rate
 % for each step separately: 
 %  alpha = 0.2*(1 - log([1:100]));
 %  sM = som_seqtrain(sM,D,'alpha',alpha);
 %
 % You don't necessarily have to use the map struct, but you can operate
 % directly with codebook matrices. However, in this case you have to
 % specify the topology of the map in the optional arguments. The
 % following commads are identical (M is originally a 200 x dim sized matrix):
 %  M = som_seqtrain(M,D,'msize',[20 10],'lattice','hexa','shape','cyl');
 %
 %  M = som_seqtrain(M,D,'msize',[20 10],'hexa','cyl');
 %
 %  sT= som_set('som_topol','msize',[20 10],'lattice','hexa','shape','cyl');
 %  M = som_seqtrain(M,D,sT);
 %
 %  M = reshape(M,[20 10 dim]);
 %  M = som_seqtrain(M,D,'hexa','cyl');
 %
 % The som_seqtrain also returns a train struct with information on the 
 % accomplished training. This is the same one as is added to the end of the 
 % trainhist field of map struct, in case a map struct is given.
 %  [M,sTrain] = som_seqtrain(M,D,'msize',[20 10]);
 %
 %  [sM,sTrain] = som_seqtrain(sM,D); % sM.trainhist{end}==sTrain
 %
 % SEE ALSO
 % 
 %  som_make         Initialize and train a SOM using default parameters.
 %  som_batchtrain   Train SOM with batch algorithm.
 %  som_train_struct Determine default training parameters.
 
 % Copyright (c) 1997-2000 by the SOM toolbox programming team.
 % http://www.cis.hut.fi/projects/somtoolbox/
 
 % Version 1.0beta juuso 220997
 % Version 2.0beta juuso 101199
  
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Check arguments
 
 error(nargchk(2, Inf, nargin));  % check the number of input arguments
 
 % map 
 struct_mode = isstruct(sMap);
 if struct_mode, 
   sTopol = sMap.topol;
 else  
   orig_size = size(sMap);
   if ndims(sMap) > 2, 
     si = size(sMap); dim = si(end); msize = si(1:end-1);
     M = reshape(sMap,[prod(msize) dim]);
   else
     msize = [orig_size(1) 1]; 
     dim = orig_size(2);
   end
   sMap   = som_map_struct(dim,'msize',msize);
   sTopol = sMap.topol;
 end
 [munits dim] = size(sMap.codebook);
 
 % data
 if isstruct(D), 
   data_name = D.name; 
   D = D.data; 
 else 
   data_name = inputname(2); 
 end
 D = D(find(sum(isnan(D),2) < dim),:); % remove empty vectors from the data
 [dlen ddim] = size(D);                % check input dimension
 if dim ~= ddim, error('Map and data input space dimensions disagree.'); end
 
 % varargin
 sTrain = som_set('som_train','algorithm','seq','neigh', ...
 		 sMap.neigh,'mask',sMap.mask,'data_name',data_name);
 radius     = [];
 alpha      = [];
 tracking   = 1;
 sample_order_type = 'random';
 tlen_type  = 'epochs';
 
 i=1; 
 while i<=length(varargin), 
   argok = 1; 
   if ischar(varargin{i}), 
     switch varargin{i}, 
      % argument IDs
      case 'msize', i=i+1; sTopol.msize = varargin{i}; 
      case 'lattice', i=i+1; sTopol.lattice = varargin{i};
      case 'shape', i=i+1; sTopol.shape = varargin{i};
      case 'mask', i=i+1; sTrain.mask = varargin{i};
      case 'neigh', i=i+1; sTrain.neigh = varargin{i};
      case 'trainlen', i=i+1; sTrain.trainlen = varargin{i};
      case 'trainlen_type', i=i+1; tlen_type = varargin{i}; 
      case 'tracking', i=i+1; tracking = varargin{i};
      case 'sample_order', i=i+1; sample_order_type = varargin{i};
      case 'radius_ini', i=i+1; sTrain.radius_ini = varargin{i};
      case 'radius_fin', i=i+1; sTrain.radius_fin = varargin{i};
      case 'radius', 
       i=i+1; 
       l = length(varargin{i}); 
       if l==1, 
         sTrain.radius_ini = varargin{i}; 
       else 
         sTrain.radius_ini = varargin{i}(1); 
         sTrain.radius_fin = varargin{i}(end);
         if l>2, radius = varargin{i}; tlen_type = 'samples'; end
       end 
      case 'alpha_type', i=i+1; sTrain.alpha_type = varargin{i};
      case 'alpha_ini', i=i+1; sTrain.alpha_ini = varargin{i};
      case 'alpha',     
       i=i+1; 
       sTrain.alpha_ini = varargin{i}(1);
       if length(varargin{i})>1, 
         alpha = varargin{i}; tlen_type = 'samples'; 
         sTrain.alpha_type = 'user defined'; 
       end
      case {'sTrain','train','som_train'}, i=i+1; sTrain = varargin{i};
      case {'topol','sTopol','som_topol'}, 
       i=i+1; 
       sTopol = varargin{i};
       if prod(sTopol.msize) ~= munits, 
         error('Given map grid size does not match the codebook size.');
       end
       % unambiguous values
      case {'inv','linear','power'}, sTrain.alpha_type = varargin{i}; 
      case {'hexa','rect'}, sTopol.lattice = varargin{i};
      case {'sheet','cyl','toroid'}, sTopol.shape = varargin{i}; 
      case {'gaussian','cutgauss','ep','bubble'}, sTrain.neigh = varargin{i};
      case {'epochs','samples'}, tlen_type = varargin{i};
      case {'random', 'ordered'}, sample_order_type = varargin{i}; 
      otherwise argok=0; 
     end
   elseif isstruct(varargin{i}) & isfield(varargin{i},'type'), 
     switch varargin{i}(1).type, 
      case 'som_topol', 
       sTopol = varargin{i}; 
       if prod(sTopol.msize) ~= munits, 
         error('Given map grid size does not match the codebook size.');
       end
      case 'som_train', sTrain = varargin{i};
      otherwise argok=0; 
     end
   else
     argok = 0; 
   end
   if ~argok, 
     disp(['(som_seqtrain) Ignoring invalid argument #' num2str(i+2)]); 
   end
   i = i+1; 
 end
 
 % training length
 if ~isempty(radius) | ~isempty(alpha), 
   lr = length(radius);
   la = length(alpha);
   if lr>2 | la>1,
     tlen_type = 'samples';
     if     lr> 2 & la<=1, sTrain.trainlen = lr;
     elseif lr<=2 & la> 1, sTrain.trainlen = la;
     elseif lr==la,        sTrain.trainlen = la;
     else
       error('Mismatch between radius and learning rate vector lengths.')
     end
   end
 end
 if strcmp(tlen_type,'samples'), sTrain.trainlen = sTrain.trainlen/dlen; end 
 
 % check topology
 if struct_mode, 
   if ~strcmp(sTopol.lattice,sMap.topol.lattice) | ...
 	~strcmp(sTopol.shape,sMap.topol.shape) | ...
 	any(sTopol.msize ~= sMap.topol.msize), 
     warning('Changing the original map topology.');
   end
 end
 sMap.topol = sTopol; 
 
 % complement the training struct
 sTrain = som_train_struct(sTrain,sMap,'dlen',dlen);
 if isempty(sTrain.mask), sTrain.mask = ones(dim,1); end
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% initialize
 
 M        = sMap.codebook;
 mask     = sTrain.mask;
 trainlen = sTrain.trainlen*dlen;
 
 % neighborhood radius
 if length(radius)>2,
   radius_type = 'user defined';
 else
   radius = [sTrain.radius_ini sTrain.radius_fin];    
   rini = radius(1); 
   rstep = (radius(end)-radius(1))/(trainlen-1);
   radius_type = 'linear';
 end    
 
 % learning rate
 if length(alpha)>1, 
   sTrain.alpha_type ='user defined';
   if length(alpha) ~= trainlen, 
     error('Trainlen and length of neighborhood radius vector do not match.')
   end
   if any(isnan(alpha)), 
     error('NaN is an illegal learning rate.')
   end
 else
   if isempty(alpha), alpha = sTrain.alpha_ini; end
   if strcmp(sTrain.alpha_type,'inv'), 
     % alpha(t) = a / (t+b), where a and b are chosen suitably
     % below, they are chosen so that alpha_fin = alpha_ini/100
     b = (trainlen - 1) / (100 - 1);
     a = b * alpha;
   end
 end
                                    
 % initialize random number generator
 rand('state',sum(100*clock));
 
 % distance between map units in the output space
 %  Since in the case of gaussian and ep neighborhood functions, the 
 %  equations utilize squares of the unit distances and in bubble case
 %  it doesn't matter which is used, the unitdistances and neighborhood
 %  radiuses are squared.
 Ud = som_unit_dists(sTopol).^2;
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Action
 
 update_step = 100; 
 mu_x_1 = ones(munits,1);
 samples = ones(update_step,1);
 r = samples; 
 alfa = samples;
 
 qe = 0;
 start = clock;
 if tracking >  0, % initialize tracking
   track_table = zeros(update_step,1);
   qe = zeros(floor(trainlen/update_step),1);  
 end
 
 for t = 1:trainlen, 
 
   % Every update_step, new values for sample indeces, neighborhood
   % radius and learning rate are calculated. This could be done
   % every step, but this way it is more efficient. Or this could 
   % be done all at once outside the loop, but it would require much
   % more memory.
   ind = rem(t,update_step); if ind==0, ind = update_step; end
   if ind==1, 
     steps = [t:min(trainlen,t+update_step-1)];
     % sample order    
     switch sample_order_type, 
      case 'ordered', samples = rem(steps,dlen)+1;
      case 'random',  samples = ceil(dlen*rand(update_step,1)+eps);
     end
 
     % neighborhood radius
     switch radius_type, 
      case 'linear',       r = rini+(steps-1)*rstep;
      case 'user defined', r = radius(steps); 
     end    
     r=r.^2;        % squared radius (see notes about Ud above)
     r(r==0) = eps; % zero radius might cause div-by-zero error
     
     % learning rate
     switch sTrain.alpha_type,
      case 'linear',       alfa = (1-steps/trainlen)*alpha;
      case 'inv',          alfa = a ./ (b + steps-1);
      case 'power',        alfa = alpha * (0.005/alpha).^((steps-1)/trainlen); 
      case 'user defined', alfa = alpha(steps);
     end    
   end
   
   % find BMU
   x = D(samples(ind),:);                 % pick one sample vector
   known = ~isnan(x);                     % its known components
   Dx = M(:,known) - x(mu_x_1,known);     % each map unit minus the vector
   [qerr bmu] = min((Dx.^2)*mask(known)); % minimum distance(^2) and the BMU
 
   % tracking
   if tracking>0, 
     track_table(ind) = sqrt(qerr);
     if ind==update_step, 
       n = ceil(t/update_step); 
       qe(n) = mean(track_table);
       trackplot(M,D,tracking,start,n,qe);
     end
   end
   
   % neighborhood & learning rate
   % notice that the elements Ud and radius have been squared!
   % (see notes about Ud above)
   switch sTrain.neigh, 
   case 'bubble',   h = (Ud(:,bmu)<=r(ind));
   case 'gaussian', h = exp(-Ud(:,bmu)/(2*r(ind))); 
   case 'cutgauss', h = exp(-Ud(:,bmu)/(2*r(ind))) .* (Ud(:,bmu)<=r(ind));
   case 'ep',       h = (1-Ud(:,bmu)/r(ind)) .* (Ud(:,bmu)<=r(ind));
   end  
   h = h*alfa(ind);  
   
   % update M
   M(:,known) = M(:,known) - h(:,ones(sum(known),1)).*Dx;
 
 end; % for t = 1:trainlen
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Build / clean up the return arguments
 
 if tracking, fprintf(1,'\n'); end
 
 % update structures
 sTrain = som_set(sTrain,'time',datestr(now,0));
 if struct_mode, 
   sMap = som_set(sMap,'codebook',M,'mask',sTrain.mask,'neigh',sTrain.neigh);
   tl = length(sMap.trainhist);
   sMap.trainhist(tl+1) = sTrain;
 else
   sMap = reshape(M,orig_size);
 end
 
 return;
 
 
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% subfunctions
 
 %%%%%%%%
 function [] = trackplot(M,D,tracking,start,n,qe)
 
   l = length(qe);
   elap_t = etime(clock,start); 
   tot_t = elap_t*l/n;
   fprintf(1,'\rTraining: %3.0f/ %3.0f s',elap_t,tot_t)  
   switch tracking
    case 1, 
    case 2,       
     plot(1:n,qe(1:n),(n+1):l,qe((n+1):l))
     title('Quantization errors for latest samples')    
     drawnow
    otherwise,
     subplot(2,1,1), plot(1:n,qe(1:n),(n+1):l,qe((n+1):l))
     title('Quantization error for latest samples');
     subplot(2,1,2), plot(M(:,1),M(:,2),'ro',D(:,1),D(:,2),'b.'); 
     title('First two components of map units (o) and data vectors (+)');
     drawnow
   end  
   % end of trackplot