如果您检查该kmeans.m
函数,则余弦距离的代码归结为两个可能引发内存不足错误的关键部分。首先让我介绍一下涉及的主要变量:
X
:行是观察向量,列是维度(数据)
C
:行是质心,列是维度(簇质心)
1)
第一段代码是将数据行规范化为单位长度(之前在@John的已删除答案中指出了这一点,尽管出于错误的原因):
[n,p] = size(X); %# in your case, X is a matrix of size 1000000x1000
Xnorm = sqrt(sum(X.^2, 2)); %# norm of each instance vector
X = X ./ Xnorm(:,ones(1,p)); %# normalize to unit length
上面尝试使用 ONE-indexing 对操作进行矢量化,以将范数向量重复数据所具有的列数,然后进行逐元素除法。只需检查变量大小以了解这种方法的问题:
>> whos X Xnorm
Name Size Bytes Class Attributes
X 1017564x1000 83056640 double sparse
Xnorm 1017564x1 12210776 double sparse
因此Xnorm(:,ones(1,p))
将尝试分配一个大小的临时矩阵,12210776*1000 bytes = 11.3722 GB
这显然是导致内存不足错误的原因......
X
(对于那些感兴趣的人,内部需要双稀疏矩阵12*nnz(X) + 4*size(X,2) bytes
来存储,而完整的表示需要prod(size(X))*8 bytes
. 在您的情况下,大约需要 80MB 和 11.5GB 的内存!)
这条线可以用不同的(可能更慢)的方式编写,避免了通常是矢量化缺点的巨大空间需求。只需遍历每一行并除以范数。更好的是,我们可以使用专门为这种情况设计的 BSXFUN 函数(避免使用 REPMAT 和索引技巧):
X = bsxfun(@rdivide, X, Xnorm);
有趣的是,在 KMEANS 文件的其他地方有注释的代码部分,明确考虑了这个问题,因此选择使用较慢的 for 循环,但保证不会耗尽内存......
2)
第二个关键部分是实际计算距离的地方。感兴趣的代码如下:
n = size(X,1);
nclusts = size(C,1);
D = zeros(n,nclusts);
for i = 1:nclusts
D(:,i) = max(1 - X*C(i,:)', 0);
end
基本上,它计算每个数据实例与每个集群质心(一次一个质心,针对整个数据向量)的内积。同样,如果这会导致问题,您可以简单地将矢量化产品展开到逐步循环中,例如:
for i = 1:nclusts
for j = 1:n
D(j,i) = max(1 - dot(X(j,:),C(i,:)), 0);
end
end
所以你明白了;当您的矩阵非常大时,您必须小心会产生大量中间结果的操作,并在可能的情况下用在较小规模上操作的显式循环替换它们。
顺便说一句,使用欧几里德距离时您不会遇到同样的问题,因为它是用循环而不是单线矢量化解决方案编写的。这是计算距离函数的部分:
for i = 1:nclusts
D(:,i) = (X(:,1) - C(i,1)).^2;
for j = 2:p
D(:,i) = D(:,i) + (X(:,j) - C(i,j)).^2;
end
% D(:,i) = sum((X - C(repmat(i,n,1),:)).^2, 2); %# <--- commented code
end
尽管如此,我很惊讶 BSXFUN 再次没有被使用:
for i=1:nclusts
D(:,i) = sum(bsxfun(@minus, X, C(i,:)).^2, 2);
end
请注意,在完成之前我没有尝试对整个数据进行聚类。我在 4GB 的 32 位机器上运行(其中 MATLAB 由于架构限制只能访问 3GB),所以请报告建议的更改是否真的对您的 64 位/8GB 硬件产生影响;)