如何使k均值算法起作用

问题描述 投票:-1回答:1

我在javascript中有一个非常基本的k-means实现(我知道,但它需要在浏览器中运行)。我想了解的是-如何使它更具功能性?

当前充满了循环,并且很难遵循以下代码:

export default class KMeans {
  constructor(vectors, k) {
    this.vectors = vectors;
    this.numOfVectors = vectors.length;
    this.k = k || bestGuessK(this.numOfVectors);
    this.centroids = randomCentroids(this.vectors, this.k);
  }

  classify(vector, distance) {
    let min = Infinity;
    let index = 0;

    for (let i = 0; i < this.centroids.length; i++) {
      const dist = distance(vector, this.centroids[i]);
      if (dist < min) {
        min = dist;
        index = i;
      }
    }

    return index;
  }

  cluster() {
    const assigment = new Array(this.numOfVectors);
    const clusters = new Array(this.k);

    let movement = true;

    while (movement) {
      // update vector to centroid assignments
      for (let i = 0; i < this.numOfVectors; i++) {
        assigment[i] = this.classify(this.vectors[i], euclidean);
      }

      // update location of each centroid
      movement = false;
      for (let j = 0; j < this.k; j++) {
        const assigned = [];

        for (let i = 0; i < assigment.length; i++) {
          if (assigment[i] === j) assigned.push(this.vectors[i]);
        }

        if (!assigned.length) continue;
        const centroid = this.centroids[j];
        const newCentroid = new Array(centroid.length);

        for (let g = 0; g < centroid.length; g++) {
          let sum = 0;
          for (let i = 0; i < assigned.length; i++) {
            sum += assigned[i][g];
          }
          newCentroid[g] = sum / assigned.length;

          if (newCentroid[g] !== centroid[g]) {
            movement = true;
          }
        }
        this.centroids[j] = newCentroid;
        clusters[j] = assigned;
      }
    }

    return clusters;
  }
}
javascript algorithm functional-programming k-means
1个回答
0
投票

当然可以。

您可以从这里开始:

  classify(vector, distance) {
    let min = Infinity;
    let index = 0;

    for (let i = 0; i < this.centroids.length; i++) {
      const dist = distance(vector, this.centroids[i]);
      if (dist < min) {
        min = dist;
        index = i;
      }
    }

    return index;
  }

为什么这是成员函数?纯函数const classify = (centroids, vector, distance) => {...}会更干净吗?

然后是一个实现,让我们稍微更改distance签名。如果我们将其咖喱制成const distance = (vector) => (centroid) => {...},则可以写[]

const classify = (centroids, vector, distance) =>
  minIndex (centroids .map (distance (vector)))

并且如果distance API不在我们的控制范围之内,这并不难:

const classify = (centroids, vector, distance) =>
  minIndex (centroids .map (centroid => distance (vector, centroid)))

当然,我们还没有写minIndex,但是我们已经分解了问题,以使用更有意义的抽象。而且minIndex并不难写。您可以像原始的classify函数一样强制执行此操作,也可以使用类似的方法:

const minIndex = (xs) => xs.indexOf (Math.min (...xs))

请注意,distance在这里是一个有点误导的名称。我不得不更仔细地阅读它,因为我假设这样的名字可以代表……,还有一段距离。相反,它是一个用于计算距离的函数。名称metric或类似distanceFunctiondistanceFndistanceImpl的名称可能会更明显。


现在让我们继续此位:

const newCentroid = new Array(centroid.length);

for (let g = 0; g < centroid.length; g++) {
  let sum = 0;
  for (let i = 0; i < assigned.length; i++) {
    sum += assigned[i][g];
  }
  newCentroid[g] = sum / assigned.length;

  if (newCentroid[g] !== centroid[g]) {
    movement = true;
  }
}

此代码有两个职责:创建newCentroid数组,如果任何值已更改,则更新movement的值。

让我们分开这两个。

首先,创建新的质心。我们可以将嵌套的for循环清理为类似以下内容:

const makeNewCentroid = (centroid, assigned) =>
  centroid .map ((c, g) => mean (assigned .map ((a) => a[g])))

这取决于mean函数,我们将其与所需的sum函数一起编写,如下所示:

const sum = (ns) =>  ns .reduce ((t, n) => t + n, 0)
const mean = xs => sum (xs) / xs.length

然后我们需要更新movement。我们可以轻松地基于centroidsnewCentroids

movement = centroids.some((c, i) => c !== newCentroids[i])

显然,您可以按照这种方式继续。每个for循环都应具有基本目的。找到该目的,然后查看Array.prototype方法之一是否可以更好地表达它。对于上面我们工作的第二部分,我们发现了两个目的,并将它们分为两个单独的块。

这应该使您有一个更好的开始。没有魔术子弹。但是,如果您考虑不可变数据的纯功能以及关注点之间的强烈分离,通常可以朝功能方向发展。

© www.soinside.com 2019 - 2024. All rights reserved.