【问题标题】:Accelerate Matlab nested for loop with bsxfun使用 bsxfun 加速 Matlab 嵌套 for 循环
【发布时间】:2016-08-10 05:09:26
【问题描述】:

我有一个图n x nW 被描述为它的邻接矩阵和一个n 每个节点的组标签(整数)向量。

我需要为每对组计算c 组中的节点和d 组中的节点之间的链接(边)数。为此,我写了一个嵌套的for loop,但我确信这不是计算我在代码中调用mcd 的矩阵的最快方法,即计算组c 之间的边数的矩阵和d。 是否可以通过bsxfun 让这个操作更快?

function mcd = interlinks(W,ci)
%// W is the adjacency matrix of a simple undirected graph
%// ci are the group labels of every node in the graph, can be from 1 to |C|
n = length(W); %// number of nodes in the graph
m = sum(nonzeros(triu(W))); %// number of edges in the graph
ncomms = length(unique(ci)); %// number of groups of ci

mcd = zeros(ncomms); %// this is the matrix that counts the number of edges between group c and group d, twice the number of it if c==d

for c=1:ncomms
    nodesc = find(ci==c); %// nodes in group c
    for d=1:ncomms
        nodesd = find(ci==d); %// nodes in group d
        M = W(nodesc,nodesd); %// submatrix of edges between c and d
        mcd(c,d) = sum(sum(M)); %// count of edges between c and d
    end
end

%// Divide diagonal half because counted twice
mcd(1:ncomms+1:ncomms*ncomms)=mcd(1:ncomms+1:ncomms*ncomms)/2;

例如这里的图片中的邻接矩阵是

W=[0 1 1 0 0 0;
   1 0 1 1 0 0;
   1 1 0 0 1 1;
   0 1 0 0 1 0;
   0 0 1 1 0 1;
   0 0 1 0 1 0];

组标签向量为ci=[ 1 1 1 2 2 3],得到的矩阵mcd为:

mcd=[3 2 1; 
     2 1 1;
     1 1 0];

这意味着例如组 1 与自身有 3 个链接,与组 2 有 2 个链接,与组 3 有 1 个链接。

【问题讨论】:

  • 您能否进一步解释一下 3-by-3 矩阵与您的图像有何关系?您的示例中的[ 1 1 1 2 2 3] 是您的W 吗?如果是这样,创建mcd 所需的ci 是什么?
  • 属性? n 有多大? W是稀疏的吗? ci 是否包含从 1 到 ncomms 的整数值?
  • 在我的示例中,矩阵 W 是所描绘图的邻接矩阵,ci 是图顶点的成员索引。 n 有多大并不重要,如果 W 稀疏,我只想避免双重嵌套循环。是的,ci 包含从 1 到 ncomms 的整数,代表 i-th 顶点的组
  • 刚刚编辑了问题以显示完整的邻接矩阵,如图所示。

标签: matlab optimization vectorization bsxfun


【解决方案1】:

这个怎么样?

C = bsxfun(@eq, ci,unique(ci)');
mcd = C*W*C'
mcd(logical(eye(size(mcd)))) = mcd(logical(eye(size(mcd))))./2;

我想这就是你想要的。

【讨论】:

  • P.S 正是我想要的,这个实现非常快并且效果很好,但我不明白它背后的逻辑。
  • 太棒了!这很简单,我会尽力解释。第一个矩阵 C 是一个 ncommsX lenght(ci) 矩阵,由 1 和 0 组成。其中每一行都有一个 1,其中 ci 等于其中一个唯一值。现在假设您要计算 dsm(1,2):您可以取第一个,您可以将 W 乘以左侧第一行和右侧第二行。这基本上添加(乘以 1 或零,然后添加)W 中的所有位置,但只有 C(1,:) 等于 1 的行和 C(2,:) 等于 1 的列. 通过做矩阵乘法,你可以一次完成。
  • 我强烈建议您尝试一个简单的矩阵乘法示例,感受一下它的工作原理。
  • 光滑!而且非常快。 (对于大量标签,我的功能仍然更快:P)
  • 顺便说一句,在我的方法中,大部分时间都是在生成矩阵 C 特别是唯一函数。矩阵乘法非常快。很想听听改进那部分的方法....
【解决方案2】:

IIUC 并假设 ci 是一个排序数组,看来您基本上是在进行块求和,但块大小不规则。因此,您可以使用沿行和列使用cumsum 的方法,然后在ci 中的移位位置处进行微分,这基本上会给您按块求和。

实现看起来像这样 -

%// Get cumulative sums row-wise and column-wise
csums = cumsum(cumsum(W,1),2)

%/ Get IDs of shifts and thus get cumsums at those positions
[~,idx] = unique(ci) %// OR find(diff([ci numel(ci)]))
csums_indexed = csums(idx,idx)

%// Get the  blockwise summations by differentiations on csums at shifts 
col1 = diff(csums_indexed(:,1),[],1)
row1 = diff(csums_indexed(1,:),[],2)
rest2D = diff(diff(csums_indexed,[],2),[],1)
out = [[csums_indexed(1,1) ; col1] [row1 ; rest2D]]

【讨论】:

    【解决方案3】:

    如果你不反对 mex 函数,你可以使用我下面的代码。

    测试代码

    n = 2000;
    n_labels = 800;
    W = rand(n, n);               
    
    W = W * W' > .5;              % generate symmetric adjacency matrix of logicals
    Wd = double(W);
    ci = floor(rand(n, 1) * n_labels ) + 1; % generate ids from 1 to 251
    
    [C, IA, IC] = unique(ci);
    
    disp(sprintf('base avg fun time = %g ',timeit(@() interlinks(W, IC))));
    disp(sprintf('mex avg fun time = %g ',timeit(@() interlink_mex(W, IC))));
    
    %note this function requires symmetric (function from @aarbelle)
    disp(sprintf('bsx avg fun time = %g ',timeit(@() interlinks_bsx(Wd, IC'))));
    
    x1 = interlinks(W, IC);
    x2 = interlink_mex(W, IC);
    x3 = interlinks_bsx(Wd, IC');
    
    disp(sprintf('norm(x1 - x2) = %g', norm(x1 - x2)));
    disp(sprintf('norm(x1 - x3) = %g', norm(x1 - x3)));
    

    测试结果

    使用这些设置的测试结果:

    base avg fun time = 4.94275 
    mex avg fun time = 0.0373092 
    bsx avg fun time = 0.126406 
    norm(x1 - x2) = 0
    norm(x1 - x3) = 0
    

    基本上,对于较小的n_labels,bsx 函数效果很好,但您可以将其设置得足够大,以使 mex 函数更快。

    c++代码

    把它扔到像interlink_mex.cpp 这样的文件中,然后用mex interlink_mex.cpp 编译。你需要在你的机器上安装一个 c++ 编译器等等......

    #include "mex.h"
    #include "matrix.h"
    #include <math.h>
    
    //  Author: Matthew Gunn
    
    void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
      if(nrhs != 2)
        mexErrMsgTxt("Invalid number of inputs.  Shoudl be 2 input argument.");
    
      if(nlhs != 1)
        mexErrMsgTxt("Invalid number of outputs.  Should be 1 output arguments.");
    
      if(!mxIsLogical(prhs[0])) {
        mexErrMsgTxt("First argument should be a logical array (i.e. type logical)");
      }
      if(!mxIsDouble(prhs[1])) {
        mexErrMsgTxt("Second argument should be an array of type double");
    
      }
    
      const mxArray *W = prhs[0];
      const mxArray *ci = prhs[1];
    
      size_t W_m = mxGetM(W);
      size_t W_n = mxGetN(W);
    
      if(W_m != W_n)
        mexErrMsgTxt("Rows and columns of W are not equal");
    
      //  size_t ci_m = mxGetM(ci);
      size_t ci_n = mxGetNumberOfElements(ci);
    
    
      mxLogical *W_data = mxGetLogicals(W);
      //  double *W_data = mxGetPr(W);
      double *ci_data = mxGetPr(ci);
    
      size_t *ci_data_size_t = (size_t*) mxCalloc(ci_n, sizeof(size_t));
      size_t ncomms = 0;
    
      double intpart;
      for(size_t i = 0; i < ci_n; i++) {
        double x = ci_data[i];
        if(x < 1 || x > 65536 || modf(x, &intpart) != 0.0) {
           mexErrMsgTxt("Input ci is not all integers from 1 to a maximum value of 65536 (can edit source code to change this)");
    
         }
        size_t xx = (size_t) x;
        if(xx > ncomms)
          ncomms = xx;
        ci_data_size_t[i] = xx - 1;
      }
    
      mxArray *mcd = mxCreateDoubleMatrix(ncomms, ncomms, mxREAL);
      double *mcd_data = mxGetPr(mcd);
    
    
      for(size_t i = 0; i < W_n; i++) {
        size_t ii = ci_data_size_t[i];
        for(size_t j = 0; j < W_n; j++) {  
          size_t jj = ci_data_size_t[j];
          mcd_data[ii + jj * ncomms] += (W_data[i + j * W_m] != 0);
        }    
      }
      for(size_t i = 0; i < ncomms * ncomms; i+= ncomms + 1) //go along diagonal
        mcd_data[i]/=2; //divide by 2
    
      mxFree(ci_data_size_t);
      plhs[0] = mcd;
    }
    

    【讨论】:

    • 顺便说一句,刚刚注意到如果length(unique(ci))不等于max(ci),我的代码和你的代码不太等价,也就是说,我设置了ncomms = max(ci)。这就是为什么我在测试代码中添加了唯一调用以使用 IC 变量作为标签 id。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-21
    • 2016-04-14
    • 2014-06-10
    • 2021-04-27
    相关资源
    最近更新 更多