%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Matlab 5 code for making a Self Organising Feature Map grid (SOFM)
% and letting it learn the form of a letter A
%
% by Rajeev Raizada
% Dept. of Cognitive & Neural Systems
% Boston University
% rajeev@cns.bu.edu
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Represent the SOFM as a 2-D grid of x,y coordinates
% i.e. 3 dimensions in all: Rows, Cols, Slices
% 3rd-Dimension slice 1: x-coords
% 3rd-Dimension slice 2: y-coords
num_rows = 15;
num_cols = 15;
a = 0.2; % Exponent in eta and G reduction (Hertz, Krogh and Palmer, p.237)
%%%%% Initialise with small x and y coords, centred on the origin
dx = 0.1;
dy = 0.1;
m = cat(3,dx*(1-2*rand(num_rows,num_cols)),dy*(1-2*rand(num_rows,num_cols)));
% The "cat" command joins up two slices along 3rd matrix dimension
%%%%% Plot the shape which will be mapped (a letter A in this case);
%%%%%%% Plot an A
%%%%%%% NB: It's better to have this in a separate function file,
%%%%%%% but it's harder to e.mail the program that way.
%%%%%%% Same for making the A input below
figure(1);
clf;
line([-0.8 -0.4],[-1 1]);
line([-0.8 -0.5],[-1 -1]);
line([-0.5 -0.38],[-1 -0.4]);
line([-0.02 0.1],[-0.4 -1]);
line([-0.02 -0.38],[-0.4 -0.4]);
line([0 0.4],[1 -1]);
line([0 -0.4],[1 1]);
line([-0.1 -0.2],[0 0.5]);
line([-0.3 -0.2],[0 0.5]);
line([-0.3 -0.1],[0 0]);
line([0.1 0.4],[-1 -1]);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The main loop
for cycle=1:5000,
eta = cycle^(-a); % Learning rate (how much nodes move)
G = 0.5 + 10*cycle^(-a); % Gaussian width parameter
%%% NB: G<0.5 is boring because the Gaussian only covers one node
%%%%% Give an input (in this case for an A-shape)
x = 1-2*rand;
y = 1-2*rand;
while ~( (x>-0.4 & x<0 & y>-0.4 & y<0) | ... % Middle bar
((y>-5*x - 0.5) & (y<-5*x + 1) ) | ...% Right diagonal
((y>5*x + 1.5) & (y<5*x + 3) ) ) ...% Left diagonal
x = 1-2*rand;
y = 1-2*rand;
end;
inp = cat(3, x*ones(num_rows,num_cols), y*ones(num_rows,num_cols));
% Take the input x,y coords, and make each fill a slice
% of a matrix the same size as m, so that they can be subtracted
%%%%% Find winning node
dist_mat_xy = (m - inp).^2;
% First slice of this contains (squared) distances of x-coords,
% second slice contains (squared) distances of y-coords
dist_mat = sum(dist_mat_xy,3);
% Sum across x and y slices to get total distance
[win_rows,win_cols] = find(dist_mat==min(min(dist_mat)));
% Finds the row and column of minimal distance grid point(s)
rand_idx = ceil(length(win_rows)*rand);
win_row = win_rows(rand_idx);
win_col = win_cols(rand_idx);
% If two or more grid points tie for having shortest dist,
% we need to pick one of them at random to be the winner.
% These lines pick a random integer index, and pick the
% entry from the winners vectors with this index
%%% Calculate city-block distance from winner in grid
[col_idx,row_idx] = meshgrid(1:num_cols,1:num_rows);
% This makes matrices of indices
grid_dist = abs(row_idx-win_row) + abs(col_idx-win_col);
%%% Calculate Gaussian movement-strength function for each node
f_1dim = eta * exp(-(grid_dist/G).^2);
f = cat(3,f_1dim,f_1dim); % Make a slice for x and y coords
%%%% Plot the map
if max(cycle == [1 10 30 50 100 200 400 600 800 1000 3000 5000]),
%%%%%%%%%% Do the plotting
figure(1);
if (cycle>1),delete(h);end; % This wipes the old grid plot
hold on;
h=plot(m(:,:,1),m(:,:,2),'r-',m(:,:,1)',m(:,:,2)','r-');
% This draws the new SOFM grid
hold off;
title(['Input presentation number ' num2str(cycle) ...
' Neighbourhood size ' num2str(G) ...
' Learning rate ' num2str(eta) ]);
drawnow;
%eval(['print ' num2str(cycle) 'A.ps']);
% This would make a PostScript file
end;
%%% Move nodes
m = m + f.*(inp-m);
end; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Go to next cycle