diff --git a/release-packaging/Classes/FluidKMeans.sc b/release-packaging/Classes/FluidKMeans.sc index a38d613..c131850 100644 --- a/release-packaging/Classes/FluidKMeans.sc +++ b/release-packaging/Classes/FluidKMeans.sc @@ -1,70 +1,114 @@ FluidKMeans : FluidRealTimeModel { - var clusters, maxiter; + var clusters, maxiter; *new {|server, numClusters = 4, maxIter = 100| ^super.new(server,[numClusters,maxIter]) - .numClusters_(numClusters) - .maxIter_(maxIter); + .numClusters_(numClusters) + .maxIter_(maxIter); } - numClusters_{|n| clusters = n.asInteger} - numClusters{ ^clusters } + numClusters_{|n| clusters = n.asInteger} + numClusters{ ^clusters } - maxIter_{|i| maxiter = i.asInteger} - maxIter{ ^maxiter } + maxIter_{|i| maxiter = i.asInteger} + maxIter{ ^maxiter } - prGetParams{^[this.numClusters,this.maxIter,-1,-1];} + prGetParams{^[this.numClusters,this.maxIter,-1,-1];} - fitMsg{ |dataSet| ^this.prMakeMsg(\fit,id,dataSet.id);} + fitMsg{ |dataSet| ^this.prMakeMsg(\fit,id,dataSet.id);} - fit{|dataSet, action| - actions[\fit] = [ - numbers( FluidMessageResponse, _, this.numClusters ,_), - action - ]; - this.prSendMsg(this.fitMsg(dataSet)); - } + fit{|dataSet, action| + actions[\fit] = [ + numbers( FluidMessageResponse, _, this.numClusters ,_), + action + ]; + this.prSendMsg(this.fitMsg(dataSet)); + } - fitPredictMsg{|dataSet, labelSet| - ^this.prMakeMsg(\fitPredict, id, dataSet.id, labelSet.id) - } + fitPredictMsg{|dataSet, labelSet| + ^this.prMakeMsg(\fitPredict, id, dataSet.id, labelSet.id) + } fitPredict{|dataSet, labelSet,action| - actions[\fitPredict] = [ - numbers(FluidMessageResponse, _, this.numClusters, _), - action - ]; + actions[\fitPredict] = [ + numbers(FluidMessageResponse, _, this.numClusters, _), + action + ]; this.prSendMsg(this.fitPredictMsg(dataSet,labelSet)); } - predictMsg{|dataSet, labelSet| - ^this.prMakeMsg(\predict, id, dataSet.id, labelSet.id) - } + predictMsg{|dataSet, labelSet| + ^this.prMakeMsg(\predict, id, dataSet.id, labelSet.id) + } predict{ |dataSet, labelSet, action| - actions[\predict] = [ - numbers(FluidMessageResponse, _, this.numClusters, _), - action - ]; + actions[\predict] = [ + numbers(FluidMessageResponse, _, this.numClusters, _), + action + ]; this.prSendMsg(this.predictMsg(dataSet,labelSet)); } - predictPointMsg{|buffer| - ^this.prMakeMsg(\predictPoint, id, this.prEncodeBuffer(buffer)) - } + predictPointMsg{|buffer| + ^this.prMakeMsg(\predictPoint, id, this.prEncodeBuffer(buffer)) + } predictPoint { |buffer, action| actions[\predictPoint] = [number(FluidMessageResponse,_,_),action]; this.prSendMsg(this.predictPointMsg(buffer)) } - kr{|trig, inputBuffer,outputBuffer| - ^FluidKMeansQuery.kr(K2A.ar(trig), - this, clusters, maxiter, - this.prEncodeBuffer(inputBuffer), - this.prEncodeBuffer(outputBuffer)); + fitTransformMsg{|srcDataSet, dstDataSet| + ^this.prMakeMsg(\fitTransform, id, srcDataSet.id, dstDataSet.id) + } + + fitTransform{|srcDataSet, dstDataSet,action| + actions[\fitTransform] = [nil,action]; + this.prSendMsg(this.fitTransformMsg(srcDataSet,dstDataSet)); + } + + transformMsg{|srcDataSet, dstDataSet| + ^this.prMakeMsg(\transform, id, srcDataSet.id, dstDataSet.id) + } + + transform{ |srcDataSet, dstDataSet, action| + actions[\transform] = [nil,action]; + this.prSendMsg(this.transformMsg(srcDataSet,dstDataSet)); + } + + transformPointMsg{ |sourceBuffer, targetBuffer| + ^this.prMakeMsg(\transformPoint, id, + this.prEncodeBuffer(sourceBuffer), + this.prEncodeBuffer(targetBuffer), + ["/b_query", targetBuffer.asUGenInput]); } + + transformPoint { |sourceBuffer, targetBuffer, action| + actions[\transformPoint] = [nil,{action.value(targetBuffer)}]; + this.prSendMsg(this.transformPointMsg(sourceBuffer, targetBuffer)); + } + + getMeansMsg{|dataSet| ^this.prMakeMsg(\getMeans, id, dataSet.asUGenInput) } + + getMeans{ |dataSet, action| + actions[\getMeans] = [nil, action]; + this.prSendMsg(this.getMeansMsg(dataSet)); + } + + setMeansMsg{|dataSet| ^this.prMakeMsg(\setMeans, id, dataSet.asUGenInput) } + + setMeans{ |dataSet, action| + actions[\setMeans] = [nil, action]; + this.prSendMsg(this.setMeansMsg(dataSet)); + } + + kr{|trig, inputBuffer,outputBuffer| + ^FluidKMeansQuery.kr(K2A.ar(trig), + this, clusters, maxiter, + this.prEncodeBuffer(inputBuffer), + this.prEncodeBuffer(outputBuffer)); + } } FluidKMeansQuery : FluidRTQuery {} diff --git a/release-packaging/HelpSource/Classes/FluidKMeans.schelp b/release-packaging/HelpSource/Classes/FluidKMeans.schelp index 28f0ef3..0ce4e39 100644 --- a/release-packaging/HelpSource/Classes/FluidKMeans.schelp +++ b/release-packaging/HelpSource/Classes/FluidKMeans.schelp @@ -65,6 +65,46 @@ A link::Classes/FluidLabelSet:: to contain assignments. ARGUMENT:: action A function to run when complete, taking an array of the counts for each category as its argument. +METHOD:: fitTransform +Run link::Classes/FluidKMeans#*fit:: and link::Classes/FluidKMeans#*predict:: in a single pass: i.e. train the model on the incoming link::Classes/FluidDataSet:: and then return the learned clustering to the passed link::Classes/FluidLabelSet:: +ARGUMENT:: srcDataSet +a link::Classes/FluidDataSet:: containing the data to fit and predict. +ARGUMENT:: dstDataSet +a link::Classes/FluidLabelSet:: to retrieve the predicted clusters. +ARGUMENT:: action +A function to run when the server responds + +METHOD:: transformPoint +Given a trained object, return the cluster ID for a data point in a link::Classes/Buffer:: +ARGUMENT:: sourceBuffer +a link::Classes/Buffer:: containing a data point. +ARGUMENT:: targetBuffer +a link::Classes/Buffer:: containing a data point. +ARGUMENT:: action +A function to run when the server responds, taking the ID of the cluster as its argument. + +METHOD:: transform +Report cluster assignments for previously unseen data. +ARGUMENT:: srcDataSet +A link::Classes/FluidDataSet:: of data points. +ARGUMENT:: dstDataSet +A link::Classes/FluidLabelSet:: to contain assignments. +ARGUMENT:: action +A function to run when complete, taking an array of the counts for each category as its argument. + +METHOD:: getMeans +Report cluster assignments for previously unseen data. +ARGUMENT:: dataSet +A link::Classes/FluidDataSet:: of data points. +ARGUMENT:: action +A function to run when complete, taking an array of the counts for each category as its argument. + +METHOD:: setMeans +Report cluster assignments for previously unseen data. +ARGUMENT:: dataSet +A link::Classes/FluidDataSet:: of data points. +ARGUMENT:: action +A function to run when complete, taking an array of the counts for each category as its argument. EXAMPLES:: code:: @@ -147,6 +187,38 @@ w.front; ~kmeans.predictPoint(~inbuf,{|x|x.postln;}); :: +subsection:: Accessing the means + +We can get and set the means for each cluster, their centroid. + +code:: +~centroids = FluidDataSet(s); +~kmeans.getMeans(~centroids, {~centroids.print}); + + + +~centroids.load(Dictionary.newFrom([\cols, 2, \data, Dictionary.newFrom([\0, [0.5,0.5], \1, [-0.5,0.5], \2, [0.5,-0.5], \3, [-0.5,-0.5]])])); +~centroids.print +~kmeans.setMeans(~centroids, {~kmeans.predict(~dataSet,~clusters,{~clusters.dump{|x|var count = 0.dup(4); x["data"].keysValuesDo{|k,v|count[v[0].asInteger] = count[v[0].asInteger] + 1;};count.postln}})}); + +~kmeans.free +~kmeans = FluidKMeans(s); +~kmeans.predict(~dataSet,~clusters) + +:: + +subsection:: Cluster-distance Space + +You can get the euclidian distance of a given point to each cluster. + +code:: +b = Buffer.sendCollection(s,[0.5,0.5]) +c = Buffer(s) + +~kmeans.transformPoint(b,c,{|x|x.query;x.getn(0,x.numFrames,{|y|y.postln})}) + +:: + subsection:: Queries in a Synth This is the equivalent of predictPoint, but wholly on the server