然后我们会在样本的原型上定义很多方法,这样每个样本都可以用这些方法。
// 计算样本间距离 采用欧式距离Sample.prototype.measureDistances = function(a, b, c, d, e, f, g, h, i, j, k) { for (var i in this.neighbors) { var neighbor = this.neighbors[i]; var a = neighbor.a - this.a; var b = neighbor.b - this.b; var c = neighbor.c - this.c; var d = neighbor.d - this.d; var e = neighbor.e - this.e; var f = neighbor.f - this.f; var g = neighbor.g - this.g; var h = neighbor.h - this.h; var i = neighbor.i - this.i; var j = neighbor.j - this.j; var k = neighbor.k - this.k; // 计算欧式距离 neighbor.distance = Math.sqrt(a*a + b*b + c*c + d*d + e*e + f*f + g*g + h*h + i*i + j*j + k*k); }};// 将邻居样本根据与预测样本间距离排序Sample.prototype.sortByDistance = function() { this.neighbors.sort(function (a, b) { return a.distance - b.distance; });};// 判断被预测样本类别Sample.prototype.guessType = function(k) { // 有两种类别 1和-1 var types = { '1': 0, '-1': 0 }; // 根据k值截取邻居里面前k个 for (var i in this.neighbors.slice(0, k)) { var neighbor = this.neighbors[i]; types[neighbor.trueType] += 1; } // 判断邻居里哪个样本类型多 if(types['1']>types['-1']){ this.type = '1'; } else { this.type = '-1'; } }
注意到我这里的数据有a-k共11个属性,样本有1和-1两种类型,使用truetype和type来预测样本类型和对比判断是否分类成功。
最后是样本集的原型上定义一个方法,该方法可以在整个样本集里寻找未知类型的样本,并生成他们的邻居集,调用未知样本原型上的方法来计算邻居到它的距离,把所有邻居按距离排序,最后猜测类型。
// 构建总样本数组,包含未知类型样本SampleSet.prototype.determineUnknown = function() { /* * 一旦发现某个未知类型样本,就把所有已知的样本 * 克隆出来作为该未知样本的邻居序列。 * 之所以这样做是因为我们需要计算该未知样本和所有已知样本的距离。 */ for (var i in this.samples) { // 如果发现没有类型的样本 if ( ! this.samples[i].type) { // 初始化未知样本的邻居 this.samples[i].neighbors = []; // 生成邻居集 for (var j in this.samples) { // 如果碰到未知样本 跳过 if ( ! this.samples[j].type) continue; this.samples[i].neighbors.push( new Sample(this.samples[j]) ); } // 计算所有邻居与预测样本的距离 this.samples[i].measureDistances(this.a, this.b, this.c, this.d, this.e, this.f, this.g, this.h, this.k); // 把所有邻居按距离排序 this.samples[i].sortByDistance(); // 猜测预测样本类型 this.samples[i].guessType(this.k); } }};
最后分别计算10倍交叉验证和留一法交叉验证的精度。
留一法就是每次只留下一个样本做测试集,其它样本做训练集。
K倍交叉验证将所有样本分成K份,一般均分。取一份作为测试样本,剩余K-1份作为训练样本。这个过程重复K次,最后的平均测试结果可以衡量模型的性能。
k倍验证时定义了个方法先把数组打乱随机摆放。
// helper函数 将数组里的元素随机摆放 function ruffle(array) { array.sort(function (a, b) { return Math.random() - 0.5; }) }
剩余测试代码好写,这里就不贴了。
测试结果为
用余弦距离等计算方式可能精度会更高。
3. 总结
knn算法非常简单,但却能在很多关键的地方发挥作用并且效果非常好。缺点就是进行分类时要扫描所有训练样本得到距离,训练集大的话会很慢。
可以用这个最简单的分类算法来入高大上的ML的门,会有点小小的成就感。
完整代码和文件在: https://github.com/Lunaticf/D…
参考: http://burakkanber.com/blog/m…