加速用于FDR估计的MATLAB代码

时间:2012-01-25 18:36:57

标签: performance matlab vectorization

我有2个输入变量:

  • 带有 N 元素的p值( p )向量(未排序)
  • N x M 矩阵,其中p值通过随机排列( pr )与 M 迭代获得。 N 非常大,10K到100K或更多。 M 让我们说100。

我正在估算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);

3 个答案:

答案 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;

对于与上述相同的MN,此代码在我的计算机上需要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倍。