aboutsummaryrefslogtreecommitdiff
path: root/src/ann_som.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ann_som.c')
-rw-r--r--src/ann_som.c721
1 files changed, 721 insertions, 0 deletions
diff --git a/src/ann_som.c b/src/ann_som.c
new file mode 100644
index 0000000..3247e39
--- /dev/null
+++ b/src/ann_som.c
@@ -0,0 +1,721 @@
+/* ann_som :
+ part of the ARTIFICIAL NEURAL NETWORK external for PURE DATA
+ SELF-ORGANIZED MAP : instar learning-rule
+
+ (l) 0201:forum::für::umläute:2001
+ this software is licensed under the GNU General Public License
+*/
+
+#include "ann.h"
+#include <math.h>
+#ifdef NT
+#define sqrtf sqrt
+#endif
+
+#if 1
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#ifdef linux
+#include <unistd.h>
+#endif
+#ifdef NT
+#include <io.h>
+#endif
+#endif
+
+/* ****************************************************************************** */
+/* som : save and load messages... */
+
+#define INSTAR 1
+#define OUTSTAR 2
+#define KOHONEN 0
+
+/* learning-rule
+ INSTAR : instar learning-rule
+*/
+
+#define TRAIN 0
+#define TEST 1
+
+
+typedef struct _som {
+ t_object x_obj;
+ t_outlet *left, *right;
+
+ int rule; /* INSTAR, OUTSTAR, KOHONEN */
+ int mode; /* TRAIN, TEST */
+
+ t_symbol *filename;
+ int defaultfilename; /* TRUE if filename is still "default.som" */
+
+ int num_neurX, num_neurY; /* for 2dim-fields */
+ int num_neurons; /* num_neurX * num_neurY */
+ int num_sensors;
+
+ t_float **weights; /* the neural network (pointer to neuron (neuron is a pointer to an array of weights)) */
+ t_float **dist; /* squaredistances between neurons (for neighbourhood) (pointer to neuron (is a pointer to an array of distances))*/
+
+ t_float *workingspace; /* a for comparing data*/
+
+ double lr, lr_factor, lr_bias; /* learning rate: lr(n)=(lr(n-1)*lr_factor; LR=lr(n)+lr_bias */
+ double nb, nb_factor, nb_bias; /* neighbourhood */
+
+ /* something for reading/writing to files */
+ t_canvas *x_canvas;
+ t_symbol *x_dir;
+
+} t_som;
+
+static t_class *som_class;
+
+/* ----------------- private functions -------------------- */
+
+static void som_killsom(t_som *x)
+{
+ /* kill the weights-field */
+ int i=x->num_neurons;
+
+ while (i--) {
+ freebytes(x->weights[i], sizeof(x->weights[i]));
+ x->weights[i]=0;
+ }
+ freebytes(x->weights, sizeof(x->weights));
+ x->weights = 0;
+
+ /* kill the dist-field */
+ i=x->num_neurons;
+
+ while (i--) {
+ freebytes(x->dist[i], sizeof(x->dist[i]));
+ x->dist[i]=0;
+ }
+ freebytes(x->dist, sizeof(x->dist));
+ x->dist = 0;
+
+ /* kill the working-space */
+ freebytes(x->workingspace, sizeof(x->workingspace));
+ x->workingspace = 0;
+}
+
+static void som_makedist(t_som *x)
+{
+ int i, j;
+
+ x->dist = (t_float **)getbytes(x->num_neurons * sizeof(t_float *));
+
+ for (i=0; i<x->num_neurons; i++) {
+ int x1 = (i%x->num_neurX), y1 = (i/x->num_neurX);
+ x->dist[i]=(t_float *)getbytes(x->num_neurons * sizeof(t_float));
+
+ for (j=0; j<x->num_neurons; j++) {
+ int x2 = (j%x->num_neurX), y2 = (j/x->num_neurX);
+ x->dist[i][j] = sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
+ }
+ }
+}
+
+static int som_whosthewinner(t_som *x, t_float *senses)
+{
+ t_float min_dist = 0;
+ int min_n = x->num_neurons-1;
+ t_float *weight = x->weights[min_n];
+ int n = x->num_sensors;
+
+ while (n--) {
+ t_float f = senses[n] - weight[n];
+ min_dist += f*f;
+ }
+
+ n=x->num_neurons-1;
+ while (n--) {
+ int s = x->num_sensors;
+
+ t_float dist = 0;
+ weight = x->weights[n];
+
+ while (s--) {
+ t_float f;
+ f = senses[s] - weight[s];
+ dist += f*f;
+ }
+ if (dist<min_dist) {
+ min_dist = dist;
+ min_n = n;
+ }
+ }
+
+ return min_n;
+}
+
+static void som_createnewsom(t_som *x, int sens, int nx, int ny)
+{ /* create a new SOM */
+ int i, j;
+
+ /* clean up the old SOM */
+ som_killsom(x);
+
+
+ /* make new SOM */
+ x->num_neurons = nx * ny;
+ x->num_neurX = nx;
+ x->num_neurY = ny;
+ x->num_sensors = sens;
+
+ x->weights = (t_float **)getbytes(x->num_neurons * sizeof(t_float *));
+ for (i=0; i<x->num_neurons; i++) {
+ x->weights[i]=(t_float *)getbytes(x->num_sensors * sizeof(t_float));
+
+ for (j=0; j<x->num_sensors; j++) x->weights[i][j] = 0;
+ }
+
+ /* make new dist */
+ som_makedist(x);
+
+ /* make new workingspace */
+ x->workingspace = (t_float *)getbytes(x->num_sensors * sizeof(t_float));
+ for (i=0; i<x->num_sensors; i++) x->workingspace[i]=0.f;
+}
+
+/* ----------------- public functions ---------------------- */
+
+static void som_list(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* present the data */
+ int i = x->num_sensors;
+ // t_float *data = (t_float *)getbytes(sizeof(t_float) * i);
+ t_float *data = x->workingspace;
+ t_float *dummy = data;
+ int winner;
+
+ t_float learningrate = x->lr+x->lr_bias, neighbourhood = x->nb+x->nb_bias;
+
+ /* first: extract the data */
+ /* check if there is enough input data; fill up with zeros if not; if there's plenty, maybe forget about the rest */
+ if ((i = x->num_sensors - argc) > 0) {
+ dummy = data + argc;
+ while (i--) *dummy++ = 0;
+ i = x->num_sensors;
+ } else i = x->num_sensors;
+ dummy = data;
+ /* really get the data */
+ while (i--) *dummy++ = atom_getfloat(argv++);
+
+ /* second: get the winning neuron */
+ winner = som_whosthewinner(x, data);
+
+ if (x->mode == TRAIN) {
+ /* third: learn something */
+ /* update all the neurons that are within the neighbourhood */
+ i=x->num_neurons;
+ switch (x->rule) {
+ case OUTSTAR:
+ while (i--) {
+ t_float dist = x->dist[winner][i];
+ if (neighbourhood > dist) {
+ t_float factor = 1 - dist/neighbourhood;
+ t_float *weight=x->weights[i];
+ int s = x->num_sensors;
+
+ while (s--) weight[s] += learningrate*data[s]*(factor-weight[s]);
+ }
+ }
+ break;
+ case INSTAR:
+ while (i--) {
+ t_float dist = x->dist[winner][i];
+ if (neighbourhood > dist) {
+ t_float factor = learningrate * (1 - dist/neighbourhood);
+ t_float *weight=x->weights[i];
+ int s = x->num_sensors;
+
+ while (s--) weight[s] += (data[s]-weight[s])*factor;
+ }
+ }
+ break;
+ default:
+ /* KOHONEN rule */
+ while (i--) {
+ t_float dist = x->dist[winner][i];
+ if (neighbourhood > dist) {
+ t_float *weight=x->weights[i];
+ int s = x->num_sensors;
+
+ while (s--) weight[s] += (data[s]-weight[s])*learningrate;
+ }
+ }
+ }
+
+ /* update learning-rate and neighbourhood */
+ x->lr *= x->lr_factor;
+ x->nb *= x->nb_factor;
+ }
+
+ /* finally: do the output thing */
+ /* do the output thing */
+ outlet_float(x->x_obj.ob_outlet, winner);
+
+ // freebytes(data, sizeof(t_float)*x->num_sensors);
+}
+
+static void som_bang(t_som *x)
+{ /* re-trigger the last output */
+ error("som_bang: nothing to do");
+}
+
+static void som_init(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* initialize the neuron-weights */
+ int i, j;
+ t_float f;
+
+ switch (argc) {
+ case 0:
+ case 1:
+ f = (argc)?atom_getfloat(argv):0;
+ for (i=0; i<x->num_neurons; i++)
+ for (j=0; j<x->num_sensors; j++)
+ x->weights[i][j]=f;
+ break;
+ default:
+ if (argc == x->num_sensors) {
+ for (i=0; i<x->num_neurons; i++)
+ for (j=0; j<x->num_sensors; j++)
+ x->weights[i][j]=atom_getfloat(&argv[j]);
+ } else
+ error("som_init: you should pass a list of expected mean-values for each sensor to the SOM");
+ }
+}
+
+
+static void som_makenewsom(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* create a new SOM */
+ int sens, nx, ny;
+
+
+ /* check whether there is sufficient data to create a new SOM */
+ if ((argc != 2) && (argc !=3)) {
+ error("som_new: wrong number of arguments (only 2 or 3 parameters are allowed)");
+ return;
+ }
+
+ /* 3 arguments : #sensors #neurX #neurY :: 2D-field of neurons with neurX * neurY items
+ 2 arguments : #sensors #neurXY :: 2D-field of neurons with neurXY* neurXY items
+
+ to create more-dimensional fields, we now have to manually adjust the SOM-file (change the distances...)
+ LATER, we might do a function "ann_makedist"
+ */
+
+ sens = atom_getfloat(argv);
+ if (sens <= 0) {
+ error("some_new: number of sensors must be >= 1");
+ return;
+ }
+
+ if (argc==3) {
+ nx = atom_getint(argv+1);
+ ny = atom_getint(argv+2);
+ if ((nx<=0) || (ny<=0)) {
+ error("some_new: number of neurons must be >= 1");
+ return;
+ }
+ } else {
+ nx = atom_getint(argv+1);
+ if (nx<=0) {
+ error("some_new: number of neurons must be >= 1");
+ return;
+ }
+ ny = nx;
+ }
+
+ som_createnewsom(x, sens, nx, ny);
+}
+
+static void som_train(t_som *x)
+{ /* set the mode to TRAIN */
+ x->mode = TRAIN;
+}
+static void som_test(t_som *x)
+{ /* set the mode to TEST */
+ x->mode = TEST;
+}
+
+static void som_rule(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* set the learning rule */
+ int rule=-1;
+
+ if (argc>1) {
+ error("som_rule: only 1 argument may be specified");
+ return;
+ }
+ if (argc == 0) {
+ post("som_rule: you are currently training with the %s rule", (x->rule==INSTAR)?"INSTAR":(x->rule==OUTSTAR)?"OUTSTAR":"KOHONEN");
+ return;
+ }
+
+ if (argv->a_type==A_FLOAT) rule=atom_getint(argv);
+ else if (argv->a_type==A_SYMBOL) {
+ char *name=atom_getsymbol(argv)->s_name;
+ if (!strcmp(name, "instar") && !strcmp(name, "INSTAR")) rule=INSTAR;
+ else if (!strcmp(name, "outstar") && !strcmp(name, "OUTSTAR")) rule=OUTSTAR;
+ else if (!strcmp(name, "kohonen") && !strcmp(name, "KOHONEN")) rule=KOHONEN;
+ }
+
+ switch (rule) {
+ case KOHONEN:
+ case INSTAR:
+ case OUTSTAR:
+ x->rule=rule;
+ break;
+ default:
+ error("som_rule: you specified an invalid rule !");
+ }
+}
+
+
+static void som_learn(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* set a new LEARNINGRATE */
+ switch (argc) {
+ case 3:
+ x->lr_bias = atom_getfloat(&argv[2]);
+ case 2:
+ x->lr_factor = atom_getfloat(&argv[1]);
+ case 1:
+ x->lr = atom_getfloat(&argv[0]);
+ break;
+ default:
+ error("som_learn: you should pass up to 4 learning-rate parameters");
+ }
+}
+static void som_neighbour(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* set a new NEIGHBOURHOOD */
+ switch (argc) {
+ case 3:
+ x->nb_bias = atom_getfloat(&argv[2]);
+ case 2:
+ x->nb_factor = atom_getfloat(&argv[1]);
+ case 1:
+ x->nb = atom_getfloat(&argv[0]);
+ break;
+ default:
+ error("som_neighbour: you should pass up to 4 neighbourhood parameters");
+ }
+}
+
+static void som_read(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* read a som-file */
+
+ int fd;
+ char filnam[MAXPDSTRING];
+ char buf[MAXPDSTRING], *bufptr;
+
+ int neuronsX, neuronsY, sensors, rule=0;
+ double lr[3], nb[3];
+ t_float dummy;
+ char *text=0;
+ int i, j;
+ t_float *fp;
+
+ FILE *f=0;
+
+ text = (char *)getbytes(MAXPDSTRING*sizeof(char));
+
+ if (argc>0) {
+ x->filename = atom_gensym(argv);
+ x->defaultfilename = 0;
+ }
+ if (x->defaultfilename) error("som_read: reading from default file \"%s\"", x->filename->s_name);
+
+ if ((fd = open_via_path(canvas_getdir(x->x_canvas)->s_name,
+ x->filename->s_name, "", buf, &bufptr, MAXPDSTRING, 0)) < 0) {
+ error("%s: can't open", x->filename->s_name);
+ return;
+ }
+ else
+ close (fd);
+
+ /* open */
+ sys_bashfilename(x->filename->s_name, filnam);
+ dummy = 0;
+
+ while (f == 0) {
+ if (!(f = fopen(filnam, "r"))) {
+ error("msgfile_read: unable to open %s", filnam);
+ return;
+ }
+
+ /* read */
+
+ /* read header */
+ if ( (dummy=fscanf(f,"SOM:\n%d",&sensors)) != 1) {
+ error("som_read: error reading file\n");
+ break;
+ }
+ if ( (dummy=fscanf(f,"%d",&neuronsX)) != 1) {
+ error("som_read: error reading file\n");
+ break;
+ }
+ if ( (dummy=fscanf(f,"%d",&neuronsY)) != 1) {
+ error("som_read: error reading file\n");
+ break;
+ }
+ fscanf(f,"%s",text);
+ if (!strcmp("INSTAR", text)) rule = INSTAR;
+ else if (!strcmp("OUTSTAR", text)) rule = OUTSTAR;
+ else if (!strcmp("KOHONEN", text)) rule = KOHONEN;
+
+ for (i=0; i<3; i++)
+ if ( (fscanf(f,"%lf",&lr[i])) != 1) {
+ error("som_read: error reading file\n");
+ break;
+ }
+ for (i=0; i<3; i++)
+ if ( (fscanf(f,"%lf",&nb[i])) != 1) {
+ error("som_read: error reading file\n");
+ break;
+ }
+
+ /* we now have a valid SOM-definition
+ let's create a dummy SOM */
+
+ som_createnewsom(x, sensors, neuronsX, neuronsY);
+
+ x->rule = rule;
+
+ x->lr=lr[0];
+ x->lr_factor=lr[1];
+ x->lr_bias=lr[2];
+
+ x->nb=nb[0];
+ x->nb_factor=nb[1];
+ x->nb_bias=nb[2];
+
+ /* read the weights */
+
+ if ((fscanf(f,"\nweights:\n %f",&dummy)) != 1) {
+ break;
+ }
+
+ i=0;
+ while (i<x->num_neurons) {
+ j = x->num_sensors;
+ fp= x->weights[i];
+ while (j--) {
+ *fp++=dummy;
+ if ((fscanf(f,"%f",&dummy)) != 1) {
+ break;
+ }
+ }
+ j = x->num_sensors;
+ i++;
+ }
+
+ /* finally read the distances */
+ if ((fscanf(f,"\ndists:\n %f",&dummy)) != 1) {
+ break;
+ }
+
+ i=0;
+ while (i<x->num_neurons) {
+ j = x->num_neurons;
+ fp= x->dist[i];
+ while (j--) {
+ *fp++=dummy;
+ if ((fscanf(f,"%f",&dummy)) != 1) {
+ break;
+ }
+ }
+ j = x->num_sensors;
+ i++;
+ }
+
+ }
+
+ /* close file */
+ if (f) fclose(f);
+
+
+}
+
+static void som_write(t_som *x, t_symbol *s, int argc, t_atom *argv)
+{ /* write a som-file */
+ char filnam[MAXPDSTRING];
+ char buf[MAXPDSTRING];
+ char *text=0;
+ int textlen;
+
+ FILE *f=0;
+
+ int i;
+
+ if (argc>0) {
+ x->filename = atom_gensym(argv);
+ x->defaultfilename = 0;
+ }
+ if (x->defaultfilename) error("som_write: writing to default file \"%s\"", x->filename->s_name);
+
+ canvas_makefilename(x->x_canvas, x->filename->s_name, buf, MAXPDSTRING);
+ sys_bashfilename(x->filename->s_name, filnam);
+
+ while (f==0) {
+ /* open file */
+ if (!(f = fopen(filnam, "w"))) {
+ error("msgfile : failed to open %s", filnam);
+ } else {
+
+ /* write header information */
+ text=(char *)getbytes(sizeof(char)*MAXPDSTRING);
+ sprintf(text, "SOM:\n%d %d %d %s\n%.15f %.15f %.15f\n%.15f %.15f %.15f\nweights:\n",
+ x->num_sensors, x->num_neurX, x->num_neurY, (x->rule==INSTAR)?"INSTAR":(x->rule==OUTSTAR)?"OUTSTAR":"KOHONEN",
+ x->lr, x->lr_factor, x->lr_bias,
+ x->nb, x->nb_factor, x->nb_bias);
+ textlen = strlen(text);
+
+ if (fwrite(text, textlen*sizeof(char), 1, f) < 1) {
+ error("msgfile : failed to write %s", filnam); break;
+ }
+
+ /* write weights */
+ for (i=0; i<x->num_neurons; i++) {
+ int j=x->num_sensors;
+ t_float *weight = x->weights[i];
+ while (j--) {
+ sprintf(text, " %.15f", *weight++);
+ textlen=strlen(text);
+ if (fwrite(text, textlen*sizeof(char), 1, f) < 1) {
+ error("msgfile : failed to write %s", filnam); break;
+ }
+ }
+ if (fwrite("\n", sizeof(char), 1, f) < 1) {
+ error("msgfile : failed to write %s", filnam); break;
+ }
+ }
+
+ /* write dists */
+ if (fwrite("dists:\n", 7*sizeof(char), 1, f) < 1) {
+ error("msgfile : failed to write %s", filnam); break;
+ }
+ for (i=0; i<x->num_neurons; i++) {
+ int j=x->num_neurons;
+ t_float *dist = x->dist[i];
+ while (j--) {
+ sprintf(text, " %.15f", *dist++);
+ textlen=strlen(text);
+ if (fwrite(text, textlen*sizeof(char), 1, f) < 1) {
+ error("msgfile : failed to write %s", filnam); break;
+ }
+ }
+ if (fwrite("\n", sizeof(char), 1, f) < 1) {
+ error("msgfile : failed to write %s", filnam); break;
+ }
+ }
+ }
+ }
+ /* close file */
+ if (f) fclose(f);
+
+ freebytes(text, sizeof(text));
+}
+
+
+static void som_help(t_som *x)
+{
+ post("\nann_som\t:: self orgranized map");
+ post("<f1> <f2> <f3>... <fn>\t: train/test som with data"
+ "\nlearn\t\t:... "
+
+ "\nhelp\t\t: show this help");
+ post("creation: \"ann_som <som-file>\": <som-file> defines a file to be loeaded as a som");
+}
+
+
+static void som_print(t_som *x)
+{
+ char c = (x->defaultfilename)?'\0':'\"';
+ post("\nann_som\t:: self orgranized map");
+ post("rule=%s\tmode=%s", (x->rule==INSTAR)?"INSTAR":(x->rule==OUTSTAR)?"OUTSTAR":"KOHONEN", (x->mode==TEST)?"TEST":"TRAIN");
+ post("file = %c%s%c", c, x->filename->s_name,c );
+ post("neurons = %d*%d = %d\tsensors=%d", x->num_neurX, x->num_neurY, x->num_neurons, x->num_sensors);
+ post("learning-rate : lr=%.15f\tlr_x=%.15f\tlr_o=%.15f", x->lr, x->lr_factor, x->lr_bias);
+ post("neighbourhood : nb=%.15f\tnb_x=%.15f\tnb_o=%.15f\n", x->nb, x->nb_factor, x->nb_bias);
+
+}
+static void som_free(t_som *x)
+{
+ som_killsom(x);
+}
+
+static void *som_new(t_symbol *s, int argc, t_atom *argv)
+{
+ t_som *x = (t_som *)pd_new(som_class);
+
+ outlet_new(&x->x_obj, 0);
+
+ x->rule = INSTAR;
+ x->mode = TRAIN;
+
+ x->filename = gensym("default.som");
+ x->defaultfilename = 1;
+
+ x->num_neurX = 0;
+ x->num_neurY = 0;
+ x->num_neurons = 0;
+
+ x->num_sensors = 0;
+
+ x->weights = 0;
+ x->dist = 0;
+
+ x->lr = 1;
+ x->lr_factor = 0.999999999;
+ x->lr_bias = 0;
+
+ x->nb = 10;
+ x->nb_factor = 0.999999999;
+ x->nb_bias = 0.999999999;
+
+ x->x_canvas = canvas_getcurrent();
+
+
+ if ((argc==0) || (argv->a_type == A_SYMBOL)) {
+ /* load the som-file */
+ if (argc != 0) x->defaultfilename = 0;
+ som_read(x, s, argc, argv);
+ } else {
+ /* create a new som */
+ som_makenewsom(x, s, argc, argv);
+ }
+
+ return (x);
+}
+
+static void som_setup(void)
+{
+ som_class = class_new(gensym("ann_som"), (t_newmethod)som_new,
+ (t_method)som_free, sizeof(t_som), 0, A_GIMME, 0);
+
+ class_addlist(som_class, som_list);
+ class_addbang(som_class, som_bang);
+
+ class_addmethod(som_class, (t_method)som_makenewsom, gensym("new"), A_GIMME, 0);
+ class_addmethod(som_class, (t_method)som_init, gensym("init"), A_GIMME, 0);
+
+ class_addmethod(som_class, (t_method)som_learn, gensym("learn"), A_GIMME, 0);
+ class_addmethod(som_class, (t_method)som_neighbour, gensym("neighbour"), A_GIMME, 0);
+
+ class_addmethod(som_class, (t_method)som_train, gensym("train"), 0);
+ class_addmethod(som_class, (t_method)som_test, gensym("test"), 0);
+ class_addmethod(som_class, (t_method)som_rule, gensym("rule"), A_GIMME, 0);
+
+ class_addmethod(som_class, (t_method)som_read, gensym("read"), A_GIMME, 0);
+ class_addmethod(som_class, (t_method)som_write, gensym("write"), A_GIMME, 0);
+
+ class_addmethod(som_class, (t_method)som_print, gensym("print"), 0);
+ class_addmethod(som_class, (t_method)som_help, gensym("help"), 0);
+ class_sethelpsymbol(som_class, gensym("ann/som"));
+
+}
+
+void ann_som_setup(void)
+{
+ som_setup();
+}
+