Fixed classes and new help for KNNClassifier/Regressor
parent
9d139ba7c8
commit
ae86d967a7
@ -1,18 +1,27 @@
|
||||
FluidKNNClassifier : FluidManipulationClient {
|
||||
|
||||
*new {|server|
|
||||
var uid = UniqueID.next;
|
||||
^super.new(server,uid)!?{|inst|inst.init(uid);inst}
|
||||
}
|
||||
|
||||
init {|uid|
|
||||
id = uid;
|
||||
}
|
||||
|
||||
fit{|dataset, labelset, action|
|
||||
this.pr_sendMsg(\fit,[dataset.asString, labelset.asString], action);
|
||||
this.prSendMsg(\fit,[dataset.asSymbol, labelset.asSymbol], action);
|
||||
}
|
||||
|
||||
predict{ |dataset, labelset, k, action|
|
||||
this.pr_sendMsg(\predict,
|
||||
[dataset.asString, labelset.asString, k],
|
||||
action, [string(FluidMessageResponse,_,_)]
|
||||
);
|
||||
predict{ |dataset, labelset, k, uniform = 0, action|
|
||||
this.prSendMsg(\predict,
|
||||
[dataset.asSymbol, labelset.asSymbol, k, uniform],
|
||||
action);
|
||||
}
|
||||
|
||||
predictPoint { |buffer, k, action|
|
||||
this.pr_sendMsg(\predictPoint,
|
||||
[buffer.asUGenInput, k], action,
|
||||
predictPoint { |buffer, k, uniform = 0, action|
|
||||
this.prSendMsg(\predictPoint,
|
||||
[buffer.asUGenInput, k,uniform], action,
|
||||
[number(FluidMessageResponse,_,_)]
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,20 +1,29 @@
|
||||
FluidKNNRegressor : FluidManipulationClient {
|
||||
|
||||
*new {|server|
|
||||
var uid = UniqueID.next;
|
||||
^super.new(server,uid)!?{|inst|inst.init(uid);inst}
|
||||
}
|
||||
|
||||
init {|uid|
|
||||
id = uid;
|
||||
}
|
||||
|
||||
fit{|sourceDataset, targetDataset, action|
|
||||
this.pr_sendMsg(\fit,
|
||||
[sourceDataset.asString, targetDataset.asString],
|
||||
this.prSendMsg(\fit,
|
||||
[sourceDataset.asSymbol, targetDataset.asSymbol],
|
||||
action
|
||||
);
|
||||
}
|
||||
|
||||
predict{ |sourceDataset, targetDataset, k, action|
|
||||
this.pr_sendMsg(\predict,
|
||||
[sourceDataset.asString, targetDataset.asString, k],
|
||||
action,
|
||||
[string(FluidMessageResponse,_,_)]);
|
||||
predict{ |sourceDataset, targetDataset, k, uniform = 0, action|
|
||||
this.prSendMsg(\predict,
|
||||
[sourceDataset.asSymbol, targetDataset.asSymbol, k, uniform],
|
||||
action);
|
||||
}
|
||||
|
||||
predictPoint { |buffer, k, action|
|
||||
this.pr_sendMsg(\predictPoint, [buffer.asUGenInput, k], action,
|
||||
predictPoint { |buffer, k, uniform = 0, action|
|
||||
this.prSendMsg(\predictPoint, [buffer.asUGenInput, k,uniform], action,
|
||||
[number(FluidMessageResponse,_,_)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,161 @@
|
||||
TITLE:: FluidKNNClassifier
|
||||
summary:: Classify data with K Nearest Neighbours
|
||||
categories:: Classification, KNN
|
||||
related:: Classes/FluidKNNRegressor, Classes/FluidDataSet, Classes/FluidLabelSet
|
||||
|
||||
DESCRIPTION::
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: new
|
||||
Create a new KNNClassifier
|
||||
ARGUMENT:: server
|
||||
The server to make the model on
|
||||
|
||||
INSTANCEMETHODS::
|
||||
|
||||
METHOD:: fit
|
||||
Fit the model to a source link::Classes/FluidDataSet:: and a target link::Classes/FluidLabelSet:: . These need to be the sime size
|
||||
ARGUMENT:: dataset
|
||||
Source data
|
||||
ARGUMENT:: labelset
|
||||
Labels for the source data
|
||||
ARGUMENT:: action
|
||||
Run when done
|
||||
|
||||
METHOD:: predict
|
||||
Given a fitted model, predict labels for a link::Classes/FluidDataSet:: and write these to a link::Classes/FluidLabelSet::
|
||||
ARGUMENT:: dataset
|
||||
data to predict labels for
|
||||
ARGUMENT:: labelset
|
||||
place to write labels
|
||||
ARGUMENT:: k
|
||||
the number of neighours to consider
|
||||
ARGUMENT:: uniform
|
||||
true / false: whether the neighbours shold be weighted by distance
|
||||
ARGUMENT:: action
|
||||
Run when done
|
||||
|
||||
METHOD:: predictPoint
|
||||
Given a fitted model, predict labels for a data point in a link::Classes/Buffer:: and return these to the caller
|
||||
ARGUMENT:: buffer
|
||||
A data point
|
||||
ARGUMENT:: k
|
||||
Number of neighbours to consider
|
||||
ARGUMENT:: uniform
|
||||
true / false: whether the neighbours shold be weighted by distance
|
||||
ARGUMENT:: action
|
||||
Run when done, passes predicted label as argument
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
//A dataset of example points, and a label set of corresponding labels
|
||||
//+
|
||||
//A dataset of test data and a labelset for predicted labels
|
||||
(
|
||||
~source= FluidDataSet(s,\knnclassify_help_examples);
|
||||
~labels = FluidLabelSet(s,\knnclassify_help_labels);
|
||||
~test = FluidDataSet(s,\knnclassify_help_test);
|
||||
~mapping = FluidLabelSet(s,\knnclassify_help_mapping);
|
||||
)
|
||||
|
||||
//Make some clumped 2D points and place into a dataset
|
||||
(
|
||||
~examplepoints = [[0.5,0.5],[-0.5,0.5],[0.5,-0.5],[-0.5,-0.5]];
|
||||
~examplelabels = [\red,\orange,\green,\blue];
|
||||
~source.clear;
|
||||
~labels.clear;
|
||||
~tmpbuf = Buffer.alloc(s,2);
|
||||
fork{
|
||||
s.sync;
|
||||
~examplepoints.do{|x,i|
|
||||
(""++(i+1)++"/4").postln;
|
||||
~tmpbuf.setn(0,x);
|
||||
~source.addPoint(i,~tmpbuf);
|
||||
~labels.addLabel(i,~examplelabels[i]);
|
||||
s.sync
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
//Make some random, but clustered test points
|
||||
(
|
||||
~testpoints = (4.collect{64.collect{(1.sum3rand) + [1,-1].choose}.clump(2)}).flatten(1) * 0.5;
|
||||
~test.clear;
|
||||
fork {
|
||||
s.sync;
|
||||
~testpoints.do{|x,i|
|
||||
~tmpbuf.setn(0,x);
|
||||
~test.addPoint(i,~tmpbuf);
|
||||
s.sync;
|
||||
if(i==(~testpoints.size - 1)){"Generated test data".postln;}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
//Make a new KNN classifier model, fit it to the example dataset and labels, and then run preduction on the test data into our mapping label set
|
||||
(
|
||||
fork{
|
||||
~classifier = FluidKNNClassifier(s);
|
||||
s.sync;
|
||||
~classifier.fit(~source,~labels);
|
||||
~classifier.predict(~test, ~mapping, 1);
|
||||
s.sync;
|
||||
}
|
||||
)
|
||||
|
||||
//Dims of kmeans should match dataset
|
||||
~kmeans.cols
|
||||
|
||||
//Return labels of clustered points
|
||||
(
|
||||
~assignments = Array.new(~testpoints.size);
|
||||
fork{
|
||||
~testpoints.do{|x,i|
|
||||
~mapping.getLabel(i,action:{|l|
|
||||
~assignments.add(l);
|
||||
});
|
||||
s.sync;
|
||||
if(i==(~testpoints.size - 1)){"Got assignments".postln;}
|
||||
};
|
||||
~assignments.postln;
|
||||
}
|
||||
)
|
||||
|
||||
//Visualise: we're hoping to see colours neatly mapped to quandrants...
|
||||
(
|
||||
c = IdentityDictionary();
|
||||
|
||||
c.add(\red->Color.red);
|
||||
c.add(\blue->Color.blue);
|
||||
c.add(\green->Color.green);
|
||||
c.add(\orange-> Color.new255(255, 127, 0));
|
||||
|
||||
e = 200 * ((~examplepoints + 1) * 0.5).flatten(1).unlace;
|
||||
d = ((~testpoints + 1) * 0.5).flatten(1).unlace;
|
||||
// d = [20.collect{1.0.rand}, 20.collect{1.0.rand}];
|
||||
w = Window("scatter", Rect(128, 64, 200, 200));
|
||||
~colours = [Color.blue,Color.red,Color.green,Color.magenta];
|
||||
w.drawFunc = {
|
||||
Pen.use {
|
||||
e[0].size.do{|i|
|
||||
var r = Rect(e[0][i],e[1][i],10,10);
|
||||
Pen.fillColor = c[~examplelabels[i]];
|
||||
Pen.fillOval(r);
|
||||
};
|
||||
d[0].size.do{|i|
|
||||
var x = (d[0][i]*200);
|
||||
var y = (d[1][i]*200);
|
||||
var r = Rect(x,y,5,5);
|
||||
Pen.fillColor = c[~assignments[i].asSymbol].alpha_(0.3);
|
||||
Pen.fillOval(r);
|
||||
}
|
||||
}
|
||||
};
|
||||
w.refresh;
|
||||
w.front;
|
||||
)
|
||||
|
||||
|
||||
::
|
||||
@ -0,0 +1,108 @@
|
||||
TITLE:: FluidKNNRegressor
|
||||
summary:: Regression with K Nearest Neighbours
|
||||
categories:: Regression
|
||||
related:: Classes/FluidKNNClassifier, Classes/FluidDataSet
|
||||
|
||||
DESCRIPTION::
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: new
|
||||
Create a new KNN regressor on the server
|
||||
ARGUMENT:: server
|
||||
The server to run this model on.
|
||||
|
||||
INSTANCEMETHODS::
|
||||
|
||||
METHOD:: fit
|
||||
Map a source link::Classes/FluidDataSet:: to a target; they must be the same size, but can have different dimesionality
|
||||
ARGUMENT:: sourceDataset
|
||||
Source data
|
||||
ARGUMENT:: targetDataset
|
||||
Target data
|
||||
ARGUMENT:: action
|
||||
Run when done
|
||||
|
||||
|
||||
METHOD:: predict
|
||||
Apply learned mapping to a link::Classes/FluidDataSet:: and write to an output dataset
|
||||
ARGUMENT:: sourceDataset
|
||||
data to regress
|
||||
ARGUMENT:: targetDataset
|
||||
output data
|
||||
ARGUMENT:: k
|
||||
number of neigbours to consider in mapping, min 1
|
||||
ARGUMENT:: uniform
|
||||
Whether to weight neighbours by distance when producing new point
|
||||
ARGUMENT:: action
|
||||
Run when done
|
||||
|
||||
METHOD:: predictPoint
|
||||
Apply learned mapping to a data point in a link::Classes/Buffer::
|
||||
ARGUMENT:: buffer
|
||||
data point
|
||||
ARGUMENT:: k
|
||||
number of neigbours to consider in mapping, min 1
|
||||
ARGUMENT:: uniform
|
||||
Whether to weight neighbours by distance when producing new point
|
||||
ARGUMENT:: action
|
||||
Run when done
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
|
||||
//Make a simple mapping between a ramp and a sine cycle, test with an exponentional ramp
|
||||
(
|
||||
~source = FluidDataSet(s,\knn_regress_src);
|
||||
~target = FluidDataSet(s,\knn_regress_tgt);
|
||||
~test = FluidDataSet(s,\knn_regress_test);
|
||||
~output = FluidDataSet(s,\knn_regress_out);
|
||||
~tmpbuf = Buffer.alloc(s,1);
|
||||
)
|
||||
|
||||
//Make source, target and test data
|
||||
(
|
||||
~sourcedata = 128.collect{|i|i/128};
|
||||
~targetdata = 128.collect{|i| sin(2*pi*i/128) };
|
||||
fork{
|
||||
128.do{ |i|
|
||||
((i + 1).asString ++ "/128").postln;
|
||||
~tmpbuf.setn(0,i/128);
|
||||
~source.addPoint(i,~tmpbuf);
|
||||
s.sync;
|
||||
~tmpbuf.setn(0,sin(2*pi*i/128));
|
||||
~target.addPoint(i,~tmpbuf);
|
||||
s.sync;
|
||||
~tmpbuf.setn(0,(i/128)**2);
|
||||
~test.addPoint(i,~tmpbuf);
|
||||
s.sync;
|
||||
if(i==127){"Source, target and test generated".postln};
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Now make a regressor and fit it to the source and target, and predict against test
|
||||
//grab the output data whilst we're at it, so we can inspect
|
||||
(
|
||||
~outputdata = Array(128);
|
||||
fork{
|
||||
~regressor = FluidKNNRegressor(s);
|
||||
s.sync;
|
||||
~regressor.fit(~source,~target);
|
||||
~regressor.predict(~test,~output,1);
|
||||
s.sync;
|
||||
128.do{|i|
|
||||
~output.getPoint(i,~tmpbuf,{
|
||||
~tmpbuf.loadToFloatArray(action:{|x|
|
||||
~outputdata.addAll(x)
|
||||
})
|
||||
});
|
||||
s.sync;
|
||||
if(i==127){"Model fitted, output generated".postln};
|
||||
}
|
||||
}
|
||||
)
|
||||
//We should see a single cycle of a chirp
|
||||
~outputdata.plot;
|
||||
::
|
||||
Loading…
Reference in New Issue