Matlab Vectorization - indeks baris matriks none-zero ke sel

10

Saya bekerja dengan Matlab.

Saya memiliki matriks persegi biner. Untuk setiap baris, ada satu atau lebih entri dari 1. Saya ingin menelusuri setiap baris dari matriks ini dan mengembalikan indeks 1s tersebut dan menyimpannya di entri sel.

Saya bertanya-tanya apakah ada cara untuk melakukan ini tanpa mengulang semua baris matriks ini, karena untuk loop sangat lambat di Matlab.

Sebagai contoh, matriks saya

M = 0 1 0
    1 0 1
    1 1 1 

Kemudian akhirnya, saya ingin sesuatu seperti

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

Begitu Ajuga sel.

Apakah ada cara untuk mencapai tujuan ini tanpa menggunakan for loop, dengan tujuan menghitung hasilnya lebih cepat?

ftxx
sumber
Apakah Anda ingin hasilnya cepat atau Anda ingin hasilnya menghindari forloop? Untuk masalah ini, dengan versi modern MATLAB, saya sangat curiga satu forloop menjadi solusi tercepat. Jika Anda memiliki masalah kinerja, saya curiga Anda mencari di tempat yang salah untuk solusi berdasarkan saran usang.
Will
@Apakah saya ingin hasilnya cepat. Matriks saya sangat besar. Waktu berjalan sekitar 30-an di komputer saya dengan menggunakan untuk loop. Saya ingin tahu apakah ada beberapa operasi vektorisasi pintar atau, mapReduce, dll yang dapat meningkatkan kecepatan.
ftxx
1
Saya curiga, Anda tidak bisa. Vektorisasi bekerja pada vektor dan matriks yang dijelaskan secara akurat, tetapi hasil Anda memungkinkan untuk vektor dengan panjang yang berbeda. Jadi, asumsi saya adalah, Anda akan selalu memiliki beberapa loop eksplisit atau semacam loop-in-disguise cellfun.
HansHirse
@ftxx seberapa besar? Dan berapa banyak 1dalam satu baris yang khas? Saya tidak akan mengharapkan findloop untuk mengambil sesuatu yang mendekati 30-an untuk sesuatu yang cukup kecil agar sesuai dengan memori fisik.
Will
@ftxx Silakan lihat jawaban saya yang diperbarui, saya telah mengedit sejak diterima dengan peningkatan kinerja kecil
Wolfie

Jawaban:

11

Di bagian bawah jawaban ini adalah beberapa kode pembandingan, karena Anda mengklarifikasi bahwa Anda tertarik pada kinerja daripada secara acak menghindari forloop.

Bahkan, saya pikir forloop mungkin merupakan opsi yang paling performant di sini. Karena mesin JIT "baru" (2015b) diperkenalkan ( sumber ), forloop pada dasarnya tidak lambat - bahkan mereka dioptimalkan secara internal.

Anda dapat melihat dari patokan bahwa mat2cellopsi yang ditawarkan oleh ThomasIsCoding di sini sangat lambat ...

Perbandingan 1

Jika kita menyingkirkan garis itu untuk membuat skala lebih jelas, maka splitapplymetode saya cukup lambat, opsi akumulator obchardon sedikit lebih baik, tetapi opsi tercepat (dan sebanding) baik menggunakan arrayfun(seperti juga disarankan oleh Thomas) atau forloop. Perhatikan bahwa arrayfunpada dasarnya ini adalah forlingkaran yang menyamar untuk sebagian besar kasus penggunaan, jadi ini bukan ikatan yang mengejutkan!

Perbandingan 2

Saya akan merekomendasikan Anda menggunakan forloop untuk meningkatkan keterbacaan kode dan kinerja terbaik.

Edit :

Jika kita menganggap bahwa perulangan adalah pendekatan tercepat, kita dapat membuat beberapa optimisasi di sekitar findperintah.

Secara khusus

  • Buat Mlogis. Seperti yang ditunjukkan plot di bawah ini, ini bisa lebih cepat untuk yang relatif kecil M, tetapi lebih lambat dengan trade-off konversi tipe untuk yang besar M.

  • Gunakan logis Muntuk mengindeks array 1:size(M,2)daripada menggunakan find. Ini menghindari bagian paling lambat dari loop ( findperintah) dan melebihi jenis overhead konversi, menjadikannya pilihan tercepat.

Inilah rekomendasi saya untuk kinerja terbaik:

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

Saya telah menambahkan ini ke patokan di bawah, di sini adalah perbandingan pendekatan loop-style:

Perbandingan 3

Kode pembandingan:

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
Wolfie
sumber
1
Sudah melihat dan tervvotasikan. :-) Masih menunggu Luis; dia yakin memiliki sihir MATLAB hitam untuk itu.
HansHirse
@Hans Haha ya walaupun kantong triknya yang biasa (ekspansi tersirat, pengindeksan pintar, ...) biasanya menyimpan banyak hal sebagai matriks, kemacetan di sini meringkas dalam sel
Wolfie
1
Perhatikan bahwa waktu ini sangat tergantung pada sparsity M. Misalnya, jika hanya 5% elemen yang terisi M = randi([0,20],N) == 20;maka forloop adalah yang paling lambat dan arrayfunmetode Anda menang.
Will
@HansHirse :-) Pendekatan saya akan accumarraytanpa ind2sub, tetapi lebih lambat dari forloop
Luis Mendo
2

Anda dapat mencoba arrayfunseperti di bawah ini, yang menyapu barisM

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

}

atau (pendekatan yang lebih lambat oleh 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

}
ThomasIsCoding
sumber
1
Meskipun arrayfunpada dasarnya adalah loop-in-disguise, jadi ini mungkin gagal di kedua front 1) menghindari loop dan 2) menjadi cepat, seperti yang diharapkan oleh OP
Wolfie
2

Sunting : Saya menambahkan patokan, hasilnya menunjukkan itu perulangan for lebih efisien daripadaaccumarray .


Anda dapat menggunakan finddan accumarray:

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

Matriks dipindahkan (A' ) karena finddikelompokkan berdasarkan kolom.

Contoh:

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)

Keluaran:

C{1} = 
     1     4

C{2} = 
     2

C{3} =
     3     4

C{4} = 
     1     3     5

Benchmark:

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

Hasil:

accumarray:
Elapsed time is 2.407773 seconds.

For loop:
Elapsed time is 1.671387 seconds.

A for loop lebih efisien daripada accumarray...

Eliahu Aaron
sumber
Ini cukup banyak metode yang sudah diusulkan oleh obchardon , bukan?
Wolfie
Ya, saya agak lambat, saya melihat jawabannya setelah saya memposting jawaban saya.
Eliahu Aaron
2

Menggunakan diakarray :

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

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

A = accumarray(ind,val,[],@(x) {x});
Obchardon
sumber
1
Waktu eksekusi di Octave dan MATLAB online adalah tentang 2x dari yang sederhana untuk loop seperti: MM{I} = find(M(I, :)).
HansHirse
2
@Mungkin Anda ingin melihat jawaban saya
Wolfie
ya, karena ukuran masing-masing sel tidak sama, masalah ini tidak dapat sepenuhnya vektor (atau ada trik yang belum saya lihat). Ini hanya solusi yang menyembunyikan loop for.
obchardon
Tidak perlu untuk ind2sub:[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
Luis Mendo
@LuisMendo terima kasih, saya sudah mengedit jawaban saya.
obchardon
2

Anda dapat menggunakan strfind :

A = strfind(cellstr(char(M)), char(1));
rahnema1
sumber
Saya (malas) bahkan belum mencari di dokumen, tetapi apakah ini lebih cepat menggunakan stringtipe yang sebenarnya , daripada karakter? Ada banyak optimisasi untuk string, karenanya mengapa mereka ada ...
Wolfie
@ Wolfolf Saya pikir array numerik lebih mirip dengan array char daripada string sehingga konversi array numerik ke array karakter harus lebih mudah daripada konversi ke string.
rahnema1