如何在Matlab中加速对分位数的调用?

如何在Matlab中加速对分位数的调用?,matlab,optimization,vectorization,bsxfun,Matlab,Optimization,Vectorization,Bsxfun,我有一个MATLAB例程,有一个很明显的瓶颈。我对函数进行了分析,结果是2/3的计算时间用于函数级别: 函数levels获取一个浮点数矩阵,并将每列拆分为nLevels存储桶,返回一个与输入大小相同的矩阵,每个条目都由它所属存储桶的编号替换 为此,我使用quantile函数获取存储桶限制,并使用循环将条目分配给存储桶。以下是我的实现: function [Y q] = levels(X,nLevels) % "Assign each of the elements of X to an int

我有一个MATLAB例程,有一个很明显的瓶颈。我对函数进行了分析,结果是2/3的计算时间用于函数
级别

函数
levels
获取一个浮点数矩阵,并将每列拆分为
nLevels
存储桶,返回一个与输入大小相同的矩阵,每个条目都由它所属存储桶的编号替换

为此,我使用
quantile
函数获取存储桶限制,并使用循环将条目分配给存储桶。以下是我的实现:

function [Y q] = levels(X,nLevels)
% "Assign each of the elements of X to an integer-valued level"

p = linspace(0, 1.0, nLevels+1);

q = quantile(X,p);
if isvector(q)
    q=transpose(q);
end

Y = zeros(size(X));

for i = 1:nLevels
    % "The variables g and l indicate the entries that are respectively greater than
    % or less than the relevant bucket limits. The line Y(g & l) = i is assigning the
    % value i to any element that falls in this bucket."
    if i ~= nLevels % "The default; doesnt include upper bound"
        g = bsxfun(@ge,X,q(i,:));
        l = bsxfun(@lt,X,q(i+1,:));
    else            % "For the final level we include the upper bound"
        g = bsxfun(@ge,X,q(i,:));
        l = bsxfun(@le,X,q(i+1,:));
    end
    Y(g & l) = i;
end

我能做些什么来加快速度吗?代码可以矢量化吗?

如果我理解正确,您想知道每个桶中有多少物品。 使用:

n=历史(Y,nbins)

虽然我不确定这是否有助于加速。这样比较干净

编辑:评论如下:

您可以使用第二个输出参数histc

[n,bin]=histc(…)还返回索引矩阵bin。如果x是一个向量,n(k)=>sum(bin==k)。对于超出范围的值,bin为零。如果x是M×N矩阵,那么


我认为你应该使用
histc

[~,Y]=histc(X,q)

正如您在matlab的文档中所看到的:

描述 n=histc(x,边)统计向量x中落下的值的数量 边向量中的元素之间(必须包含 单调非减损值)。n是长度(边)向量 包含这些计数。x的任何元素都不可能是复杂的

这个怎么样

function [Y q] = levels(X,nLevels)

p = linspace(0, 1.0, nLevels+1);
q = quantile(X,p); 
Y = zeros(size(X));
for i = 1:numel(q)-1    
    Y = Y+ X>=q(i);
end
其结果如下:

>>X = [3 1 4 6 7 2];
>>[Y, q] = levels(X,2)

Y =

     1  1  2  2  2  1

q =

     1  3.5  7

您还可以修改逻辑行,以确保值小于下一个存储箱的起始值。但是,我认为这没有必要。

我做了一些改进(包括在另一个答案中受Aero Engy启发的改进),这些改进带来了一些改进。为了测试它们,我创建了一个包含一百万行和100列的随机矩阵来运行改进的函数:

>> x = randn(1000000,100);
首先,我运行了未修改的代码,结果如下:

请注意,在这40秒中,大约有14秒是用来计算分位数的——我不能期望改进例程的这一部分(我假设Mathworks已经对它进行了优化,尽管我猜假设会产生一个…)

接下来,我将例程修改为以下内容,这应该更快,并且具有行数更少的优点

function [Y q] = levels(X,nLevels)

p = linspace(0, 1.0, nLevels+1);
q = quantile(X,p);
if isvector(q), q = transpose(q); end

Y = ones(size(X));

for i = 2:nLevels
    Y = Y + bsxfun(@ge,X,q(i,:));
end
使用此代码的分析结果如下:

因此它比MathWorks快15秒,这代表了我的代码部分比MathWorks快150%

最后,根据Andrey的建议(同样在另一个答案中),我修改了代码,以使用
histc
函数的第二个输出,该函数将条目分配给箱子。它没有独立处理列,所以我不得不手动循环处理列,但它似乎表现得非常好。代码如下:

function [Y q] = levels(X,nLevels)

p = linspace(0,1,nLevels+1);

q = quantile(X,p);
if isvector(q), q = transpose(q); end
q(end,:) = 2 * q(end,:);

Y = zeros(size(X));

for k = 1:size(X,2)
    [junk Y(:,k)] = histc(X(:,k),q(:,k));
end
以及分析结果:

现在,我们在
分位数
函数之外的代码中只花了4.3秒,这比我最初编写的代码大约快了500%。我花了一点时间来写这个答案,因为我认为它已经成为一个很好的例子,说明了如何结合使用MATLAB profiler和StackExchange,从代码中获得更好的性能


我对这个结果很满意,当然我会继续很高兴听到其他答案。在这个阶段,主要的性能提升将来自于提高当前调用
分位数的代码部分的性能。我不知道该怎么做,但也许这里的其他人可以。再次感谢

您可以对列进行
排序
并对反向索引进行除法+四舍五入:

function Y = levels(X,nLevels)
% "Assign each of the elements of X to an integer-valued level"
[S,IX]=sort(X);
[grid1,grid2]=ndgrid(1:size(IX,1),1:size(IX,2));
invIX=zeros(size(X));
invIX(sub2ind(size(X),IX(:),grid2(:)))=grid1;
Y=ceil(invIX/size(X,1)*nLevels);
或者,您可以使用

function Y = levels(X,nLevels)
% "Assign each of the elements of X to an integer-valued level"
R=tiedrank(X);
Y=ceil(R/size(X,1)*nLevels);

令人惊讶的是,这两种解决方案都比
分位数
+
histc
解决方案略慢。

不,我想知道项目落在哪个桶中。例如,对于输入向量(3,1,4,6,7,2),如果我用
nLevels=2对其运行
levels
函数,我希望看到输出(1,1,2,2,1)。太好了,谢谢!我不得不在列上写一个循环,这样每个列都可以单独处理,但这似乎表现得更好。我会用细节更新我的答案。我一定是不清楚。我想知道我输入的每个条目都属于哪个桶。例如,如果我给出输入
x=[3 1 4 6 7 2]
nLevels=2
我希望看到输出
[1 2 2 1]
,因为第一、第二和第六个条目落在第一个存储桶中(即最小的一半,因为我们只有两个存储桶)其他人都掉进了第二个桶里。你用了什么样的探查器来获取那个档案?10x
>f=@(x)exp(x);个人资料;x=f(1);profile viewer
将整个矩阵的条目放入存储桶中,但不单独处理列。然而,你给了我一个想法来加速我的代码-我会让你知道它是否有效@ChrisTaylor我错过了你问题中假设对每个专栏进行操作的部分。修改上述内容以实现这种功能应该不难。如果你需要更多的帮助,请告诉我。谢谢,我在我自己的答案(下面)中使用了你的想法,在循环的每个阶段添加一系列逻辑。这给了我一个不错的加速,所以我非常感激!我已经投了赞成票,不客气。我打算建议在循环中使用martrix q对应的第I行的repmat来进行>=q测试。十> =repmat(q(i,:),大小(X,1),1);然而,我对bsxfun()并不熟悉,而且它似乎能够自行解决这些问题谢谢,这比安德烈的建议快了一点——现在看14秒而不是18秒。当我有时间的时候,我会在我的答案中添加配置文件结果。