所以我有一个奇怪的问题。我有一个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次迭代,并设法收敛。有谁知道我在这里弄乱了什么?谢谢。
答案 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更强大(特别是如果你没有工具箱)。
同样基准。你会对性能差异感到惊讶。