OK now I can be in holidays, I’m done with the porting of example 8c in verbose, which also shows good user of denormalisation.
I hope you enjoy!
p
s.reboot;
//Preliminaries: we want some audio, a couple of FluidDataSets, some Buffers
(
~raw = FluidDataSet(s,\Mel40);
~norm = FluidDataSet(s,\Mel40n);
~retrieved = FluidDataSet(s,\ae2);
~audio = Buffer.read(s,File.realpath(FluidBufMelBands.class.filenameSymbol).dirname +/+ "../AudioFiles/Tremblay-ASWINE-ScratchySynth-M.wav");
~melfeatures = Buffer.new(s);
~stats = Buffer.alloc(s, 7, 40);
~datapoint = Buffer.alloc(s, 40);
~queryPoint = Buffer.alloc(s, 2);
~dQueryPoint = Buffer.alloc(s, 2);
~dpN =Buffer.alloc(s, 40);
~dpMLPn =Buffer.alloc(s, 40);
~dpMLP =Buffer.alloc(s, 40);
)
// process the melbands
FluidBufMelBands.process(s,~audio, features: ~melfeatures,action: {\done.postln;});
// Divide the time series in 100, and take the mean of each segment and add this as a point to the 'raw' FluidDataSet
(
{
var trig = LocalIn.kr(1, 1);
var buf = LocalBuf(40, 1);
var count = PulseCount.kr(trig) - 1;
var chunkLen = (~melfeatures.numFrames / 100).asInteger;
var stats = FluidBufStats.kr(source: ~melfeatures, startFrame: count * chunkLen, numFrames: chunkLen, stats: ~stats, trig: trig, blocking: 1);
var rd = BufRd.kr(40, ~stats, DC.kr(0), 0, 1);
var bufWr, dsWr;
40.do{|i|
bufWr = BufWr.kr(rd[i], buf, DC.kr(i));
};
dsWr = FluidDataSetWr.kr(~raw, buf: buf, trig: Done.kr(stats));
LocalOut.kr( Done.kr(dsWr));
FreeSelf.kr(count - 99);
Poll.kr(trig,count);
}.play;
)
// wait for the post window to acknoledge the job is done. Check the dataset if curious (loads of small numbers)
~raw.print;
// normalize the input
~normalizer = FluidNormalize(s);
~normalizer.fitTransform(~raw,~norm);
~norm.print; //a more decent range
//we can then run the AE - the server might become yellow :)
~mlp = FluidMLPRegressor(s,[9,2,9],activation: 1,outputActivation: 1,tapIn: 0,tapOut: 2,maxIter: 10000,learnRate: 0.1,momentum: 0.1,batchSize: 10,validation: 0.1);
~mlp.fit(~norm,~norm,{|x|x.postln;});// run this a few times, until you are happy with the error
//we can then retrieve the hidden layer #2 because of the tapOut parameter
~mlp.predict(~norm,~retrieved);
//check the structure of retrieved
~retrieved.print;
//let's normalise it for display
~normData = FluidDataSet(s,\ae2N);
~reducedarray = Array.new(100);
~normalView = FluidNormalize(s,0.1,0.9);
(
~normalView.fitTransform(~retrieved,~normData, action:{
~normData.dump{|x| 100.do{|i|
~reducedarray.add(x["data"][i.asString])
}};
});
)
~normData.print;
//make a basic KD tree to retrieve the nearest entry in 2D
~kdtree = FluidKDTree(s,numNeighbours: 1);
~kdtree.fit(~normData);
//prepare the normalizers and the neural net for inverse query
(
~normalView.invert = 1;
~normalizer.invert = 1;
~mlp.tapIn = 2;
~mlp.tapOut = -1;
)
//Visualise and query the 2D projection of our original 40D data
(
var w,v,myx,myy,vRNN, vRN, vMN, vM;
~arrayRawNn = Array.new(40);
~arrayRawN = Array.new(40);
~arrayMLPn = Array.new(40);
~arrayMLP = Array.new(40);
//initialise the mouse position holder
myx=130;
myy=130;
w = Window("AutoEncoder", Rect(64, 64, 770, 270));
v = View.new(w,Rect(0,0, 310, 310));
vRNN = MultiSliderView(w,Rect(270,8,240,115)).value_(~arrayRawNn).readOnly_(true).elasticMode_(1).isFilled_(true);
vRN = MultiSliderView(w,Rect(270,147,240,115)).value_(~arrayRawN).readOnly_(true).elasticMode_(1).isFilled_(true);
vMN = MultiSliderView(w,Rect(520,10,240,115)).value_(~arrayMLPn).readOnly_(true).elasticMode_(1).isFilled_(true);
vM = MultiSliderView(w,Rect(520,147,240,115)).value_(~arrayMLP).readOnly_(true).elasticMode_(1).isFilled_(true);
StaticText(w,Rect(275,120,490,30)).string_("above: normalised nearest neighbour\nbelow: original nearest neighbour").font_(Font("Monaco", 10));
StaticText(w,Rect(525,120,490,30)).string_("above: regressed values at coordinates\nbelow: denormalised regressed values").font_(Font("Monaco", 10));
//creates a function that reacts to mousedown
v.mouseMoveAction = {|view, x, y|
myx=x.clip(10,260);
myy=y.clip(10,260);
w.refresh;
Routine{
~queryPoint.setn(0,([myx,myy] - 10 / 250));//set the query point to the coordinate
~kdtree.kNearest(~queryPoint, action: {|nearest| //retrieve the nearest point
~norm.getPoint(nearest, ~dpN, action: { //get the normalised 40d
~raw.getPoint(nearest, ~datapoint, action: { // get the original 40d
~normalView.transformPoint(~queryPoint, ~dQueryPoint, action: { //denormalise the 2d coordinate to get the right range of values for the MLP
~mlp.predictPoint(~dQueryPoint, ~dpMLPn, action: { //predict from the middle (2d) to the normalised output (40d)
~normalizer.transformPoint(~dpMLPn, ~dpMLP, action: { //denormalised the 40d
~datapoint.getn(0,40,{|x|~arrayRawN = x; //retrieve the nearest
~dpN.getn(0,40,{|x|~arrayRawNn = x; // retrieve the normalised nearest
~dpMLPn.getn(0,40,{|x|~arrayMLPn = x; //retrieve the predicted normalised 40d
~dpMLP.getn(0,40,{|x|~arrayMLP = x; //retrieve the denormalised predicted 40d
AppClock.sched(0,{ // update the visualisation of the 4 arrays
vRNN.value=~arrayRawNn;
vRN.value=~arrayRawN * 15;
vMN.value=~arrayMLPn;
vM.value=~arrayMLP * 15;
});
});
});
});
});
});
});
});
});
});
});
}.play;
};
//custom redraw function
w.drawFunc = {
Pen.use {
~reducedarray.size.do{|i|
var coord = (~reducedarray[i] * 250) + 7;
var r = Rect(coord[0],coord[1],6,6);
Pen.fillColor = Color.blue;
Pen.fillOval(r);
};
};
Pen.color = Color.red;
Pen.addOval(Rect(myx-4, myy-4,8,8));
Pen.perform(\stroke);
Pen.color = Color.black;
Pen.addRect(Rect(10,10,250,250));
Pen.perform(\stroke);
};
w.refresh;
w.front;
)