SOMTrain three-dimensional example


In this example of SOMTrain in action, we create a 3D neural net and fit it to a data set which is a 3D lumpy mattress in a 4D space - it should be OK for a 3D SOM to fit to the hills and valleys. This is similar to SOMTrain_2D_example


s.boot;

~numnodes = 15;

~traindur = 100000;

~dataspacelength = 100;

~dataspacechanwidth = 10;

// First create the data (the third dimension is just amplitude in this case, so we don't store it: ~dataspace is 2D)

~dataspace = Buffer.alloc(s, ~dataspacelength, ~dataspacechanwidth);

~data = ~dataspacechanwidth.collect{|index| ((0..~dataspacelength)*2*pi/~dataspacelength).sin * ((8 * index/~dataspacechanwidth).sin) }

//~data.heatMap(title: "training data (array)"); // You need the "heatMap" quark to run this line - visualises the data nicely

~dataspace.loadCollection(~data.flop.flatten);

//~dataspace.heatMap(title: "training data (buffer)");

// Allocate space for the SOM, and initialise it

~som = SOMTrain.allocBuf(s, ~numnodes, 3, 4);

SOMTrain.initBufGridRand(~som, ~numnodes, 3); // 3D grid, randomly oriented in the 4D space

// Also, just for demonstration purposes, we're going to write the error values to a buffer so we can see how they evolve

~errorvals = Buffer.alloc(s, ~traindur);

// Now train the SOM. To train it with data "randomly sampled" from the data buffer, we just wiggle the phase around and feed [phase, val] to the SOM.

(

x = {

var phase1, phase2, thirddim, val, remain, err, trig;

trig = 1;

phase1 = WhiteNoise.kr.range(0, ~dataspace.numFrames-1);

phase2 = WhiteNoise.kr.range(0, ~dataspace.numChannels);

thirddim = WhiteNoise.kr.range(0, 1);

val = Select.kr(phase2.floor, BufRd.kr(~dataspace.numChannels, ~dataspace, phase1, 1)) * thirddim;

phase1.poll(trig, label: "phase1");

phase2.poll(trig, label: "phase2");

val.poll(trig, label: "val");

// Here the ranges are reshaped to be +-1

# remain, err = SOMTrain.kr(~som, [phase1 * 0.02 - 1, phase2 * 0.2 - 1, thirddim, val], ~numnodes, 3, ~traindur, nhood: 0.5, gate: trig, initweight: 0.5);

remain.poll(trig, label: "remain");

err.poll(trig, label: "err");

BufWr.kr(err.sqrt, ~errorvals, ~traindur-remain);

Out.ar(0, Pan2.ar(K2A.ar(val) * 0.01));

}.play

)

// Once the training is finished...

x.free;


// How do the errors look? Should be a very noisy curve gradually decreasing, but won't necessarily stabilise

~errorvals.plot(maxval: nil, minval: nil);


// Now let's look at the mesh of points represented by the SOM. Do they fit the training data?

o = OctaveSC.new;

o.init;

(

~som.loadToFloatArray(action: {|arr|

var x, y, w, z;

x = arr[0,4..];

y = arr[1,5..];

w = arr[2,6..];

z = arr[3,7..];

"x range: % to %".format(x.minItem, x.maxItem).postln;

"y range: % to %".format(y.minItem, y.maxItem).postln;

"w range: % to %".format(w.minItem, w.maxItem).postln;

"z range: % to %".format(z.minItem, z.maxItem).postln;

x = x.clump(~numnodes);

y = y.clump(~numnodes);

w = w.clump(~numnodes);

z = z.clump(~numnodes);

o.("figure; hold on");

// Plot the SOM slice by slice

~numnodes.do{|offset|

o[\xtemp] = x[offset, offset + ~numnodes .. ];

o[\ytemp] = y[offset, offset + ~numnodes .. ];

o[\wtemp] = w[offset, offset + ~numnodes .. ];

o[\ztemp] = z[offset, offset + ~numnodes .. ];

o.("mesh(xtemp, ytemp, ztemp)");

}

});

);