Matlab Vectorization - chỉ số hàng ma trận không-0 đến ô


10

Tôi đang làm việc với Matlab.

Tôi có một ma trận vuông nhị phân. Đối với mỗi hàng, có một hoặc nhiều mục nhập của 1. Tôi muốn đi qua từng hàng của ma trận này và trả về chỉ mục của các số đó và lưu trữ chúng trong mục nhập của một ô.

Tôi đã tự hỏi nếu có một cách để làm điều này mà không lặp qua tất cả các hàng của ma trận này, vì vòng lặp thực sự chậm trong Matlab.

Ví dụ: ma trận của tôi

M = 0 1 0
    1 0 1
    1 1 1 

Cuối cùng, tôi muốn một cái gì đó như

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

Vì vậy, Alà một tế bào.

Có cách nào để đạt được mục tiêu này mà không cần sử dụng vòng lặp, với mục đích tính toán kết quả nhanh hơn không?


Bạn có muốn kết quả nhanh hay bạn muốn kết quả tránh forvòng lặp? Đối với vấn đề này, với các phiên bản hiện đại của MATLAB, tôi hoàn toàn nghi ngờ một forvòng lặp là giải pháp nhanh nhất. Nếu bạn gặp vấn đề về hiệu suất, tôi nghi ngờ bạn đang tìm sai giải pháp dựa trên lời khuyên đã lỗi thời.
Sẽ

@ Tôi muốn kết quả nhanh. Ma trận của tôi rất lớn. Thời gian chạy là khoảng 30 giây trong máy tính của tôi bằng cách sử dụng vòng lặp. Tôi muốn biết nếu có một số hoạt động vector hóa thông minh hoặc, mapReduce, vv có thể tăng tốc độ.
ftxx

1
Tôi nghi ngờ, bạn không thể. Vectorization hoạt động trên các vectơ và ma trận được mô tả chính xác, nhưng kết quả của bạn cho phép các vectơ có độ dài khác nhau. Vì vậy, giả định của tôi là, bạn sẽ luôn có một số vòng lặp rõ ràng hoặc một số vòng lặp ngụy trang như thế cellfun.
HansHirse

@ftxx lớn thế nào? Và có bao nhiêu 1s trong một hàng điển hình? Tôi sẽ không mong đợi một findvòng lặp sẽ lấy bất cứ thứ gì gần 30 giây cho bất cứ thứ gì đủ nhỏ để phù hợp với bộ nhớ vật lý.
Sẽ

@ftxx Vui lòng xem câu trả lời được cập nhật của tôi, tôi đã chỉnh sửa vì nó được chấp nhận với một cải tiến hiệu suất nhỏ
Wolfie

Câu trả lời:


11

Ở dưới cùng của câu trả lời này là một số mã điểm chuẩn, vì bạn đã làm rõ rằng bạn quan tâm đến hiệu suất thay vì tự ý tránh forcác vòng lặp.

Trong thực tế, tôi nghĩ rằng forcác vòng lặp có lẽ là lựa chọn hiệu quả nhất ở đây. Vì công cụ JIT "mới" (2015b) đã được giới thiệu (các nguồn ) forkhông phải là chậm - thực tế chúng được tối ưu hóa trong nội bộ.

Bạn có thể thấy từ điểm chuẩn rằng mat2celltùy chọn do ThomasIsCoding cung cấp ở đây rất chậm ...

So sánh 1

Nếu chúng ta thoát khỏi dòng đó để làm cho tỷ lệ rõ ràng hơn, thì splitapplyphương pháp của tôi khá chậm, tùy chọn tích lũy của obchardon tốt hơn một chút, nhưng các tùy chọn nhanh nhất (và có thể so sánh) đang sử dụng arrayfun(cũng như Thomas đề xuất) hoặc một forvòng lặp. Lưu ý rằng arrayfunvề cơ bản là một forvòng lặp ngụy trang cho hầu hết các trường hợp sử dụng, vì vậy đây không phải là một mối quan hệ đáng ngạc nhiên!

So sánh 2

Tôi khuyên bạn nên sử dụng một forvòng lặp để tăng khả năng đọc mã và hiệu suất tốt nhất.

Chỉnh sửa :

Nếu chúng ta giả định rằng lặp là cách tiếp cận nhanh nhất, chúng ta có thể thực hiện một số tối ưu hóa xung quanh findlệnh.

Đặc biệt

  • Làm cho Mhợp lý. Như sơ đồ dưới đây cho thấy, điều này có thể nhanh hơn đối với tương đối nhỏ M, nhưng chậm hơn với sự đánh đổi của chuyển đổi loại lớn M.

  • Sử dụng một logic Mđể lập chỉ mục một mảng 1:size(M,2)thay vì sử dụng find. Điều này tránh phần chậm nhất của vòng lặp ( findlệnh) và vượt xa chi phí chuyển đổi loại, làm cho nó trở thành tùy chọn nhanh nhất.

Dưới đây là khuyến nghị của tôi cho hiệu suất tốt nhất:

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

Tôi đã thêm điều này vào điểm chuẩn bên dưới, đây là so sánh các cách tiếp cận kiểu vòng lặp:

So sánh 3

Mã điểm chuẩn:

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
Đã thấy và nâng cấp. :-) Vẫn đang chờ Luis; anh ta chắc chắn có một số phép thuật MATLAB đen cho điều đó.
HansHirse

@Hans Haha yeah mặc dù túi thủ thuật thông thường của anh ta (mở rộng ngầm, lập chỉ mục thông minh, ...) thường giữ mọi thứ như ma trận, nút cổ chai ở đây được tóm tắt trong các tế bào
Wolfie

1
Lưu ý rằng những thời điểm này phụ thuộc mạnh mẽ vào độ thưa thớt của M. Ví dụ, nếu chỉ có 5% phần tử được điền M = randi([0,20],N) == 20;thì forvòng lặp chậm nhất và arrayfunphương thức của bạn thắng.
Sẽ

@HansHirse :-) Cách tiếp cận của tôi sẽ accumarraykhông có ind2sub, nhưng nó chậm hơn forvòng lặp
Luis Mendo

2

Bạn có thể thử arrayfunnhư bên dưới, quét qua các hàngM

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

}

hoặc (một cách tiếp cận chậm hơn bởi 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
Mặc dù arrayfunvề cơ bản là một vòng lặp ngụy trang, do đó, điều này có thể thất bại ở cả hai mặt 1) tránh các vòng lặp và 2) nhanh chóng, như OP
Wolfie

2

Chỉnh sửa : Tôi đã thêm một điểm chuẩn, kết quả cho thấy một vòng lặp for hiệu quả hơnaccumarray .


Bạn có thể sử dụng findaccumarray:

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

Ma trận được hoán vị ( A') vì findcác nhóm theo cột.

Thí dụ:

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)

Đầu ra:

C{1} = 
     1     4

C{2} = 
     2

C{3} =
     3     4

C{4} = 
     1     3     5

Điểm chuẩn:

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

Kết quả:

accumarray:
Elapsed time is 2.407773 seconds.

For loop:
Elapsed time is 1.671387 seconds.

Một vòng lặp for hiệu quả hơn accumarray...


Đây là khá nhiều phương pháp đã được đề xuất bởi obchardon , phải không?
Wolfie

Vâng, tôi hơi chậm, tôi thấy câu trả lời của anh ấy sau khi tôi đăng bài của tôi.
Eliahu Aaron

2

Sử dụng tích lũy :

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

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

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

1
Thời gian thực hiện trong Octave và MATLAB Online là khoảng 2 lần đơn giản cho vòng lặp như : MM{I} = find(M(I, :)).
HansHirse

2
@ Bạn có thể muốn xem câu trả lời của tôi
Wolfie

vâng, vì kích thước của mỗi ô không giống nhau, vấn đề này không thể được vector hóa hoàn toàn (hoặc có một mẹo mà tôi chưa thấy). Nó chỉ là một giải pháp ẩn vòng lặp for.
obchardon

Không cần ind2sub:[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
Luis Mendo

@LuisMendo cảm ơn, tôi đã chỉnh sửa câu trả lời của mình.
obchardon

2

Bạn có thể sử dụng strfind :

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

Tôi (lười biếng) thậm chí không nhìn vào các tài liệu, nhưng điều này sẽ nhanh hơn bằng cách sử dụng các stringloại thực tế , thay vì ký tự? Có rất nhiều tối ưu hóa cho chuỗi, vì vậy tại sao chúng tồn tại ...
Wolfie

@Wolfie Tôi nghĩ rằng mảng số tương tự như mảng char hơn là chuỗi nên việc chuyển đổi mảng số thành mảng ký tự nên đơn giản hơn so với chuyển đổi thành chuỗi.
rahnema1
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.