我有2个输入变量:
我正在估算p
的每个元素的错误发现率(FDR),表示如果当前p值(来自p
)将来自随机排列的p值将通过多少门槛。
我用ARRAYFUN写了这个函数,但是对于大的N(2 min N = 20K)需要很多时间,与for循环相比。
function pfdr = fdr_from_random_permutations(p, pr)
%# ... skipping arguments checks
pfdr = arrayfun( @(x) mean(sum(pr<=x))./sum(p<=x), p);
任何想法如何让它更快?
欢迎提供有关统计问题的评论。
测试数据可以生成为p = rand(N,1); pr = rand(N,M);
。
答案 0 :(得分:5)
首先,使用profiler进行分析。在尝试提高性能时,分析应始终是第一步。我们都可以猜测导致性能下降的原因,但确保正确部分的唯一方法是检查分析器报告。
我没有在您的代码上运行探查器,因为我不想生成测试数据;但我对于哪些工作是徒劳无功而有所了解。在您的函数mean(sum(pr<=x))./sum(p<=x)
中,您反复对p<=x
进行求和。总而言之,一次通话包括N
次比较和N-1
总结。因此,对于两者,当计算N
的所有N
值时,p
中的行为是二次的。
如果您单步执行p
的排序版本,则需要较少的计算和比较,因为您可以跟踪运行总和(即N
中的线性行为)。我想类似的方法可以应用于计算的其他部分。
修改强>: 我的想法的实现如上所述:
function pfdr = fdr(p,pr)
[N, M] = size(pr);
[p, idxP] = sort(p);
[pr] = sort(pr(:));
pfdr = NaN(N,1);
parfor iP = 1:N
x = p(iP);
m = sum(pr<=x)/M;
pfdr(iP) = m/iP;
end
pfdr(idxP) = pfdr;
如果您可以访问并行计算工具箱,parfor
循环将允许您获得一些性能。我使用了两个基本想法:mean(sum(pr<=x))
实际上等于sum(pr(:)<=x)/M
。另一方面,由于p
已排序,因此您可以将索引作为元素的数量(假设每个元素都是唯一的,否则您将不得不使用unique
做全面严谨的分析。)
正如您自己应该已经非常了解的那样,行m = sum(pr<=x)/M;
是主要的资源需求。通过使用p
的排序特性,可以与pr
类似地解决此问题。
我测试了我的代码(相同的结果和时间消耗)与你的代码。对于N=20e3; M=100
,运行代码大约需要63秒,在主计算机上运行大约需要43秒(在64位Arch Linux上的MATLAB 2011a,8 GiB RAM,Core i7 860)。对于较小的M
值,增益更大。但这种收益部分归因于并行化。
edit2:显然,我得到了与安德利非常相似的结果,如果我采用相同的方法,我的结果会非常相似。
然而,我意识到有一些内置函数可以或多或少地满足您的需求,即与确定经验累积密度函数非常相似。这可以通过构建直方图来完成:
function pfdr = fdr(p,pr)
[N, M] = size(pr);
[p, idxP] = sort(p);
count = histc(pr(:), [0; p]);
count = cumsum(count(1:N));
pfdr = count./(1:N).';
pfdr(idxP) = pfdr/M;
对于与上述相同的M
和N
,此代码在我的计算机上需要228毫秒。对于Andrey的参数,它需要104毫秒,所以在我的计算机上它变得有点慢,但我认为这个代码比复杂的循环更具可读性(就像我们的例子中的情况一样)。
答案 1 :(得分:5)
嗯,诀窍确实是对矢量进行排序。我赞扬了@EgonGeerardyn。此外,无需使用mean
。您可以在M
之后将所有内容分开。对p
进行排序时,查找小于当前x
的值的数量只是一个运行索引。 pr
是一个更有趣的案例 - 我使用了一个名为place
的运行索引来发现有多少元素小于x
。
编辑(2):以下是我提出的最快版本:
function Speedup2()
N = 10000/4 ;
M = 100/4 ;
p = rand(N,1); pr = rand(N,M);
tic
pfdr = arrayfun( @(x) mean(sum(pr<=x))./sum(p<=x), p);
toc
tic
out = zeros(numel(p),1);
[p,sortIndex] = sort(p);
pr = sort(pr(:));
pr(end+1) = Inf;
place = 1;
N = numel(pr);
for i=1:numel(p)
x = p(i);
while pr(place)<=x
place = place+1;
end
exp1a = place-1;
exp2 = i;
out(i) = exp1a/exp2;
end
out(sortIndex) = out/ M;
toc
disp(max(abs(pfdr-out)));
end
N = 10000/4 ; M = 100/4
的基准测试结果:
经过的时间是0.898689秒 经过时间为0.007697秒 2.220446049250313e-016
和N = 10000 ; M = 100
;
经过的时间是39.730695秒 经过时间为0.088870秒 2.220446049250313e-016
答案 2 :(得分:2)
继我和安德烈在this question之间的讨论之后,这个非常晚的答案只是向安德烈证明矢量化解决方案仍然比JIT的循环更快,它们有时候并不那么容易找到。
如果OP被认为不合适,我非常愿意删除这个答案。
现在,开始营业,这是安德烈的原始arrayfun
循环版本和Egon的矢量化版本:
function test
clc
N = 10000/4 ;
M = 100/4 ;
p = rand(N,1);
pr = rand(N,M);
%% first option
tic
pfdr = arrayfun( @(x) mean(sum(pr<=x))./sum(p<=x), p);
toc
%% second option
tic
out = zeros(numel(p),1);
[p2,sortIndex] = sort(p);
pr2 = sort(pr(:));
pr2(end+1) = Inf;
place = 1;
for i=1:numel(p2)
x = p2(i);
while pr2(place)<=x
place = place+1;
end
exp1a = place-1;
exp2 = i;
out(i) = exp1a/exp2;
end
out(sortIndex) = out/ M;
toc
%% third option
tic
[p2,sortIndex] = sort(p);
count = histc(pr2(:), [0; p2]);
count = cumsum(count(1:N));
out = count./(1:N).';
out(sortIndex) = out/M;
toc
end
我的笔记本电脑上的结果:
Elapsed time is 0.916196 seconds.
Elapsed time is 0.011429 seconds.
Elapsed time is 0.007328 seconds.
和N=1000; M = 100;
:
Elapsed time is 38.082718 seconds.
Elapsed time is 0.127052 seconds.
Elapsed time is 0.042686 seconds.
所以:矢量化速度快2-3倍。