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

时间:2014-10-26 12:05:57

标签: matlab machine-learning cluster-analysis k-means

所以我有一个奇怪的问题。我有一个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;
end

result = 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次迭代,并设法收敛。有谁知道我在这里弄乱了什么?谢谢。

2 个答案:

答案 0 :(得分:1)

让我们一步一步地完成您的代码,并讨论您对k - 意味着算法的了解。

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与您拥有的数据点之间的随机排列,然后我们随机选择{{ 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; end result = ctrs; - 意味着,我们基本上一直在迭代,直到上一次迭代中每个点的集群成员资格与当前迭代匹配,这就是你的k循环。现在,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的每一列,您计算每个点与每个簇之间的距离,其中这些距离位于i th的i th 列中集群。我将使用的一个优化技巧是避免在此使用dist并使用bsxfun在内部处理复制。因此,请改为:

repmat

现在,这一切看起来都是正确的。我自己也进行了一些测试,如果初始集群中心是唯一的,这一切似乎都有效。 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函数中执行min时,如果您有两个相同的聚类中心,则指定该点的聚类标签将是这两个数字中的最小值。这就是为什么你将拥有一个没有分数的集群,因为应该分配给这个集群的所有点都被分配给另一个集群。

因此,在随机化时,您可能有两个(或更多)初始聚类中心相同。即使要选择的索引的排列是唯一的,但实际的数据点本身可能在选择时是唯一的。我可以强加的一件事是循环遍历,直到你得到一组独特的初始簇而没有重复。因此,请尝试在代码的开头执行此操作。

assign_labels

这将确保您在继续使用代码之前拥有一组唯一的初始集群。现在,回到[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),:); end old_label = zeros(1,N); label = ones(1,N); iter = 0; %// While loop appears here 循环中的NaN内容。 老实说,如果您的数据没有任何for,那么在您计算平均值之后,任何维度都不会导致NaN。我建议你在你的代码中摆脱这个(对我来说)它看起来不太有用。 编辑:你现在可以删除NaN支票作为现在,初始集群中心应该是唯一的。


这应该有希望解决您遇到的问题。祝你好运!

答案 1 :(得分:0)

由于k-means的性质,“失去”群集并不像人们想象的那么特殊。

考虑重复。让我们假设您的所有前k点都相同,您的代码会发生什么?你需要仔细处理这个案子是有原因的。最简单的解决方案是将质心保留为原样,然后使用退化的簇。

鉴于你只有240分,但想要使用k = 100,不要期望太好的结果。大多数物体都是自己的...选择一个太大的k可能是你很多时候看到这种退化效应的原因。让我们假设在这240个中,只有不到100个是独一无二的...那么你就不能拥有100个非空集群...而且,无论如何,我会认为这种结果“过度拟合”。

如果您没有在Matlab中使用的工具箱,也许您应该继续使用免费软件。 Octave,R,Weka,ELKI,...有很多软件,其中一些在集群方面比纯Matlab更强大(特别是如果你没有工具箱)。

同样基准。你会对性能差异感到惊讶。