K-Means质心被边缘化导致没有数据点 [Matlab]

我遇到了一个有点奇怪的问题。我有一个包含240个点的数据库,并试图使用k-means将其聚类成100个簇。我使用的是Matlab,但没有统计工具箱的访问权限,所以我不得不自己编写k-means函数。这个函数非常简单,应该不会太难,对吧?但看起来我的代码似乎有些问题:

function result=Kmeans(X,c)[N,n]=size(X);index=randperm(N);ctrs = X(index(1:c),:);old_label = zeros(1,N);label = ones(1,N);iter = 0;while ~isequal(old_label, label)    old_label = label;    label = assign_labels(X, ctrs);    for i = 1:c        ctrs(i,:) = mean(X(label == i,:));        if sum(isnan(ctrs(i,:))) ~= 0            ctrs(i,:) = zeros(1,n);        end    end    iter = iter + 1;endresult = ctrs;function label = assign_labels(X, ctrs)[N,~]=size(X);[c,~]=size(ctrs);dist = zeros(N,c);for i = 1:c    dist(:,i) = sum((X - repmat(ctrs(i,:),[N,1])).^2,2);end[~,label] = min(dist,[],2);

看起来问题出在重新计算质心时,有些质心没有分配到数据点,我不确定该如何处理这个问题。经过一些研究,我发现如果提供任意初始质心,可能会发生这种情况,但在这个案例中,初始质心是从数据点本身中选取的,所以这似乎不太合理。我尝试将这些质心重新分配到随机数据点,但这导致代码无法收敛(至少在让它运行了一整夜后,代码从未收敛)。基本上,它们被重新分配,但这导致其他质心被边缘化,并重复发生。我不确定我的代码哪里出了问题,但我用R的k-means函数对同一数据集进行了k=100的1000次迭代,它成功收敛了。有人知道我在这里搞砸了什么吗?谢谢你。


回答:

让我们逐步分析你的代码,并讨论你所做的与我所了解的k-means算法相关的内容。

function result=Kmeans(X,c)[N,n]=size(X);index=randperm(N);ctrs = X(index(1:c),:);old_label = zeros(1,N);label = ones(1,N);

这看起来是一个接受大小为N x n的数据矩阵的函数,其中N是你数据集中点的数量,而n是你数据集中一个点的维度。这个函数还接受c:你希望输出的簇的数量。index提供了一个从1到你拥有的数据点数量的随机排列,然后我们从这个排列中随机选择c个点,你用这些点来初始化你的簇中心。


iter = 0;while ~isequal(old_label, label)    old_label = label;    label = assign_labels(X, ctrs);    for i = 1:c        ctrs(i,:) = mean(X(label == i,:));        if sum(isnan(ctrs(i,:))) ~= 0            ctrs(i,:) = zeros(1,n);        end    end    iter = iter + 1;endresult = ctrs;

对于k-means,我们基本上会一直迭代,直到前一次迭代中每个点的簇成员资格与当前迭代匹配,这就是你while循环所做的。现在,label决定了你数据集中每个点的簇成员资格。现在,对于每个存在的簇,你确定什么是平均数据点,然后将这个平均数据点分配为每个簇的新簇中心。出于某种原因,如果你的簇中心的任何维度出现NaN,你会将你的新簇中心设置为全零。这在我看来非常不正常,我稍后会提供建议。 编辑:现在我明白你为什么这样做了。这是因为如果你有任何空簇,你会简单地将这个簇中心设为全零,因为你无法找到空簇的平均值。这可以通过我对重复初始簇的建议来解决,详见本文末尾。


function label = assign_labels(X, ctrs)[N,~]=size(X);[c,~]=size(ctrs);dist = zeros(N,c);for i = 1:c    dist(:,i) = sum((X - repmat(ctrs(i,:),[N,1])).^2,2);end[~,label] = min(dist,[],2);

这个函数接受一个数据集X和当前迭代的簇中心,它应该返回一个标签列表,指示每个点属于哪个簇。这看起来也正确,因为对于dist的每一列,你都在计算每个点到每个簇的距离,这些距离在第i列对应于第i个簇。我会使用的一个优化技巧是避免在这里使用repmat,而是使用bsxfun,它内部处理复制。因此,改为这样做:

function label = assign_labels(X, ctrs)[N,~]=size(X);[c,~]=size(ctrs);dist = zeros(N,c);for i = 1:c    dist(:,i) = sum(bsxfun(@minus, X, ctrs(i,:)).^2, 2);end[~,label] = min(dist,[],2);

现在,这一切看起来都是正确的。我也自己进行了一些测试,看起来一切正常,前提是初始簇中心是唯一的k-means的一个小问题是我们隐式假设所有簇中心是唯一的。如果它们不是唯一的,那么你会遇到两个(或更多)簇具有完全相同的初始簇中心的问题……那么数据点应该被分配到哪个簇呢?当你在assign_labels函数中进行min操作时,如果有两个相同的簇中心,点被分配到的簇标签将是这两个数字中的最小值。这就是为什么你会有一个没有点的簇,因为所有应该分配给这个簇的点都被分配给了另一个簇。

因此,你可能会有两个(或更多)初始簇中心在随机化时是相同的。尽管选择的索引排列是唯一的,但实际的数据点本身在选择时可能不是唯一的。我可以强制执行的一件事是循环遍历排列,直到你得到一组没有重复的唯一初始簇。因此,尝试在你的代码开头这样做。

[N,n]=size(X);index=randperm(N);ctrs = X(index(1:c),:);while size(unique(ctrs, 'rows'), 1) ~= c    index=randperm(N);    ctrs = X(index(1:c),:);endold_label = zeros(1,N);label = ones(1,N);iter = 0;%// While循环在这里出现

这将确保在你继续执行代码之前,你有一组唯一的初始簇。现在,回到for循环中的NaN问题。老实说,如果你的数据一开始没有NaN,我实在不明白计算平均值后任何维度怎么会出现NaN。我建议你从代码中删除这个,因为(对我来说)它看起来不太有用。 编辑:现在你可以删除NaN检查,因为初始簇中心现在应该是唯一的了。


这应该可以解决你遇到的问题。祝你好运!

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注