Matlab Vectorization - ไม่มีเมทริกซ์ดัชนีแถวเข้าสู่เซลล์


10

ฉันทำงานกับ Matlab

ฉันมีเมทริกซ์จตุรัสไบนารี่ สำหรับแต่ละแถวมี 1 รายการหรือมากกว่าเป็น 1 ฉันต้องการผ่านแต่ละแถวของเมทริกซ์นี้และส่งกลับดัชนีของ 1s เหล่านั้นและเก็บไว้ในรายการของเซลล์

ฉันสงสัยว่ามีวิธีการทำเช่นนี้โดยไม่ต้องวนซ้ำแถวทั้งหมดของเมทริกซ์นี้หรือไม่เนื่องจากการวนซ้ำช้ามากใน Matlab

ตัวอย่างเช่นเมทริกซ์ของฉัน

M = 0 1 0
    1 0 1
    1 1 1 

ในที่สุดฉันก็ต้องการสิ่งที่ต้องการ

A = [2]
    [1,3]
    [1,2,3]

ดังนั้นAเซลล์

มีวิธีการที่จะบรรลุเป้าหมายนี้โดยไม่ใช้ลูปโดยมีจุดประสงค์ในการคำนวณผลลัพธ์ได้เร็วขึ้นหรือไม่?


คุณต้องการผลลัพธ์ที่รวดเร็วหรือคุณต้องการผลลัพธ์เพื่อหลีกเลี่ยงการforวนซ้ำหรือไม่? สำหรับปัญหานี้ด้วย MATLAB รุ่นใหม่ฉันสงสัยอย่างยิ่งว่าการforวนซ้ำจะเป็นวิธีแก้ปัญหาที่เร็วที่สุด หากคุณมีปัญหาด้านประสิทธิภาพฉันสงสัยว่าคุณกำลังมองหาวิธีการแก้ปัญหาที่ไม่ถูกต้องตามคำแนะนำที่ล้าสมัย
จะ

@ ฉันจะต้องการผลลัพธ์ที่รวดเร็ว เมทริกซ์ของฉันใหญ่มาก เวลาทำงานอยู่ที่ประมาณ 30 วินาทีในคอมพิวเตอร์ของฉันโดยใช้ลูป ฉันต้องการทราบว่ามีการดำเนินการ vectorization ที่ฉลาดหรือ mapReduce ฯลฯ ที่สามารถเพิ่มความเร็วได้หรือไม่
ftxx

1
ฉันสงสัยว่าคุณทำไม่ได้ Vectorization ทำงานบนเวกเตอร์และเมทริกซ์ที่อธิบายอย่างถูกต้อง แต่ผลลัพธ์ของคุณอนุญาตเวกเตอร์ที่มีความยาวต่างกัน ดังนั้นข้อสันนิษฐานของฉันคือคุณจะมีลูปที่ชัดเจนหรือลูปที่ซ่อนcellfunอยู่เสมอ
HansHirse

@ftxx ใหญ่แค่ไหน? และมีจำนวนเท่าไหร่1ในแถวปกติ? ฉันไม่คิดว่าจะfindวนรอบอะไรกับยุค 30 สำหรับสิ่งเล็ก ๆ พอที่จะพอดีกับความทรงจำทางกายภาพ
จะ

@ftxx โปรดดูคำตอบที่อัปเดตของฉันฉันได้แก้ไขเนื่องจากได้รับการยอมรับพร้อมการปรับปรุงประสิทธิภาพเล็กน้อย
Wolfie

คำตอบ:


11

ที่ด้านล่างของคำตอบนี้คือรหัสการเปรียบเทียบเนื่องจากคุณชี้แจงว่าคุณสนใจในประสิทธิภาพมากกว่าที่จะหลีกเลี่ยงการforวนซ้ำโดยพลการ

อันที่จริงฉันคิดว่าforลูปน่าจะเป็นตัวเลือกที่มีประสิทธิภาพมากที่สุดที่นี่ เนื่องจากเครื่องมือ "JIT" ใหม่ (2015b) เปิดตัว JIT ( แหล่งที่มา ) forจะไม่ช้าโดยเนื้อแท้ - ในความเป็นจริงพวกเขาได้รับการปรับปรุงภายใน

คุณสามารถดูจากเกณฑ์มาตรฐานว่าmat2cellตัวเลือกที่นำเสนอโดย ThomasIsCoding ที่นี่ช้ามาก ...

การเปรียบเทียบ 1

ถ้าเราได้รับการกำจัดของสายที่จะทำให้ขนาดที่ชัดเจนแล้วฉันsplitapplyวิธีการค่อนข้างช้า obchardon ของตัวเลือก accumarrayเป็นบิตดีกว่า แต่ที่เร็วที่สุด (และเทียบเท่า) ตัวเลือกมีทั้งที่ใช้arrayfun(ในขณะที่ยังมีข้อเสนอแนะโดยโทมัส) หรือforห่วง โปรดทราบว่าarrayfunโดยทั่วไปแล้วจะมีforวงวนปลอมตัวสำหรับผู้ใช้งานส่วนใหญ่ดังนั้นนี่ไม่ใช่การผูกที่น่าแปลกใจ!

เปรียบเทียบ 2

ฉันขอแนะนำให้คุณใช้การforวนซ้ำเพื่อเพิ่มความสามารถในการอ่านรหัสและประสิทธิภาพที่ดีที่สุด

แก้ไข :

หากเราคิดว่าการวนซ้ำนั้นเป็นวิธีที่เร็วที่สุดเราสามารถทำการปรับให้เหมาะสมรอบ ๆfindคำสั่ง

เฉพาะ

  • ทำให้เป็นMตรรกะ ในฐานะที่เป็นด้านล่างแสดงพล็อตนี้อาจจะเร็วขึ้นสำหรับค่อนข้างเล็กแต่ช้าลงด้วยการออกจากประเภทการแปลงขนาดใหญ่MM

  • ใช้ตรรกะMที่จะสร้างดัชนีอาร์เรย์แทนการใช้1:size(M,2) findวิธีนี้จะหลีกเลี่ยงส่วนที่ช้าที่สุดของลูป ( findคำสั่ง) และเทียบกับค่าใช้จ่ายในการแปลงประเภททำให้เป็นตัวเลือกที่เร็วที่สุด

นี่คือคำแนะนำของฉันสำหรับประสิทธิภาพที่ดีที่สุด:

function A = f_forlooplogicalindexing( M )
    M = logical(M);
    k = 1:size(M,2);
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = k(M(r,:));
    end
end

ฉันได้เพิ่มสิ่งนี้ลงในเกณฑ์มาตรฐานด้านล่างนี่คือการเปรียบเทียบวิธีการวนลูป:

เปรียบเทียบ 3

รหัสการเปรียบเทียบ:

rng(904); % Gives OP example for randi([0,1],3)
p = 2:12; 
T = NaN( numel(p), 7 );
for ii = p
    N = 2^ii;
    M = randi([0,1],N);

    fprintf( 'N = 2^%.0f = %.0f\n', log2(N), N );

    f1 = @()f_arrayfun( M );
    f2 = @()f_mat2cell( M );
    f3 = @()f_accumarray( M );
    f4 = @()f_splitapply( M );
    f5 = @()f_forloop( M );
    f6 = @()f_forlooplogical( M );
    f7 = @()f_forlooplogicalindexing( M );

    T(ii, 1) = timeit( f1 ); 
    T(ii, 2) = timeit( f2 ); 
    T(ii, 3) = timeit( f3 ); 
    T(ii, 4) = timeit( f4 );  
    T(ii, 5) = timeit( f5 );
    T(ii, 6) = timeit( f6 );
    T(ii, 7) = timeit( f7 );
end

plot( (2.^p).', T(2:end,:) );
legend( {'arrayfun','mat2cell','accumarray','splitapply','for loop',...
         'for loop logical', 'for loop logical + indexing'} );
grid on;
xlabel( 'N, where M = random N*N matrix of 1 or 0' );
ylabel( 'Execution time (s)' );

disp( 'Done' );

function A = f_arrayfun( M )
    A = arrayfun(@(r) find(M(r,:)),1:size(M,1),'UniformOutput',false);
end
function A = f_mat2cell( M )
    [i,j] = find(M.');
    A = mat2cell(i,arrayfun(@(r) sum(j==r),min(j):max(j)));
end
function A = f_accumarray( M )
    [val,ind] = ind2sub(size(M),find(M.'));
    A = accumarray(ind,val,[],@(x) {x});
end
function A = f_splitapply( M )
    [r,c] = find(M);
    A = splitapply( @(x) {x}, c, r );
end
function A = f_forloop( M )
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = find(M(r,:));
    end
end
function A = f_forlooplogical( M )
    M = logical(M);
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = find(M(r,:));
    end
end
function A = f_forlooplogicalindexing( M )
    M = logical(M);
    k = 1:size(M,2);
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = k(M(r,:));
    end
end

1
เห็นแล้วและโหวตแล้ว :-) ยังคงรอ Luis; เขาแน่ใจว่ามีเวทมนตร์ MATLAB สีดำสำหรับสิ่งนั้น
HansHirse

@Hans Haha ใช่แม้ว่าถุงปกติของเขา (การขยายโดยปริยาย, การสร้างดัชนีที่ฉลาด, ... ) มักจะทำให้สิ่งต่าง ๆ เป็นเมทริกซ์คอขวดที่นี่สรุปในเซลล์
Wolfie

1
โปรดทราบว่าครั้งนี้มีขึ้นอยู่กับ sparsity Mของ ยกตัวอย่างเช่นหากมีองค์ประกอบเพียง 5% เท่านั้นที่จะถูกเติมM = randi([0,20],N) == 20;เข้าไปในforลูปนั้นช้าที่สุดและarrayfunวิธีการของคุณก็ชนะ
จะ

@HansHirse :-) วิธีการของฉันจะได้รับaccumarrayโดยไม่ต้องind2subแต่มันจะช้ากว่าforลูป
ลูอิสเมนโด

2

คุณสามารถลองarrayfunเช่นด้านล่างซึ่งกวาดผ่านแถวของM

A = arrayfun(@(r) find(M(r,:)),1:size(M,1),'UniformOutput',false)

A =
{
  [1,1] =  2
  [1,2] =

     1   3

  [1,3] =

     1   2   3

}

หรือ (ช้าลงโดยmat2cell)

[i,j] = find(M.');
A = mat2cell(i,arrayfun(@(r) sum(j==r),min(j):max(j)))

A =
{
  [1,1] =  2
  [2,1] =

     1
     3

  [3,1] =

     1
     2
     3

}

1
แม้ว่าarrayfunโดยทั่วไปแล้วจะเป็นวงปิดบังดังนั้นสิ่งนี้อาจล้มเหลวทั้งสองด้านของ 1) การหลีกเลี่ยงการวนซ้ำและ 2) การเร็วตามที่หวังโดย OP
Wolfie

2

แก้ไข : ผมเพิ่มมาตรฐานผลที่แสดงให้เห็นว่าสำหรับห่วงจะมีประสิทธิภาพมากกว่าaccumarray


คุณสามารถใช้findและaccumarray:

[c, r] = find(A');
C = accumarray(r, c, [], @(v) {v'});

เมทริกซ์ถูกย้าย ( A') เนื่องจากfindกลุ่มตามคอลัมน์

ตัวอย่าง:

A = [1 0 0 1 0
     0 1 0 0 0
     0 0 1 1 0
     1 0 1 0 1];

%  Find nonzero rows and colums
[c, r] = find(A');

%  Group row indices for each columns
C = accumarray(r, c, [], @(v) {v'});

% Display cell array contents
celldisp(C)

เอาท์พุท:

C{1} = 
     1     4

C{2} = 
     2

C{3} =
     3     4

C{4} = 
     1     3     5

เกณฑ์มาตรฐาน:

m = 10000;
n = 10000;

A = randi([0 1], m,n);

disp('accumarray:')
tic
[c, r] = find(A');
C = accumarray(r, c, [], @(v) {v'});
toc
disp(' ')

disp('For loop:')
tic
C = cell([size(A,1) 1]);
for i = 1:size(A,1)
    C{i} = find(A(i,:));
end
toc

ผลลัพธ์:

accumarray:
Elapsed time is 2.407773 seconds.

For loop:
Elapsed time is 1.671387 seconds.

ห่วงสำหรับมีประสิทธิภาพมากกว่าaccumarray...


นี่เป็นวิธีที่เสนอโดย obchardonแล้วใช่ไหม?
Wolfie

ใช่ฉันช้าฉันเห็นคำตอบของเขาหลังจากที่โพสต์ของฉัน
Eliahu Aaron

2

ใช้สะสม :

M = [0 1 0
     1 0 1
     1 1 1];

[val,ind] = find(M.');

A = accumarray(ind,val,[],@(x) {x});

1
เวลาดำเนินการใน Octave และ MATLAB ออนไลน์เป็นเรื่องเกี่ยวกับ 2x MM{I} = find(M(I, :))ง่ายสำหรับวงเช่น:
HansHirse

2
@ ฮันคุณอาจต้องการที่จะเห็นคำตอบของฉัน
Wolfie

ใช่เนื่องจากขนาดของแต่ละเซลล์ไม่เท่ากันปัญหานี้ไม่สามารถเป็นเวกเตอร์ได้อย่างสมบูรณ์ (หรือมีเคล็ดลับที่ฉันไม่ได้เห็น) มันเป็นเพียงทางออกที่ซ่อนห่วง
obchardon

ไม่ต้องการind2sub:[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
Luis Mendo

@ LuisMendo ขอบคุณฉันได้แก้ไขคำตอบแล้ว
obchardon

2

คุณสามารถใช้strfind :

A = strfind(cellstr(char(M)), char(1));

ฉัน (ขี้เกียจ) ยังไม่ได้ดูในเอกสาร แต่สิ่งนี้จะเร็วกว่าหากใช้งานจริงstringมากกว่าตัวอักษร? มีการปรับตัวมากมายสำหรับสตริงดังนั้นทำไมพวกเขาจึงมีอยู่ ...
Wolfie

@ Wolfie ฉันคิดว่าอาเรย์ตัวเลขนั้นคล้ายกับ char char arrays มากกว่าสายอักขระดังนั้นการแปลงอาเรย์ตัวเลขไปเป็นอาเรย์ตัวอักษรควรจะตรงไปตรงมามากกว่าการแปลงเป็นสตริง
rahnema1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.