Matlab Vectorization - hücreye sıfır olmayan matris satır endeksleri


10

Matlab ile çalışıyorum.

Bir ikili kare matrisim var. Her satır için 1 veya daha fazla giriş vardır. Bu matrisin her satırından geçmek ve bu 1'lerin dizinini döndürmek ve bunları bir hücre girişinde saklamak istiyorum.

Matlab'da döngü gerçekten yavaş olduğu için bu matrisin tüm satırları üzerinde döngü yapmadan bunu yapmanın bir yolu olup olmadığını merak ediyordum.

Örneğin, matrisim

M = 0 1 0
    1 0 1
    1 1 1 

Sonra nihayetinde,

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

Yani Abir hücredir.

Sonucu daha hızlı hesaplamak amacıyla döngü için kullanmadan bu hedefe ulaşmanın bir yolu var mı?


Sonucun hızlı olmasını mı istiyorsunuz yoksa sonucun fordöngülerden kaçınmasını mı istiyorsunuz ? Bu sorun için, modern MATLAB sürümlerinde, bir fordöngünün en hızlı çözüm olduğundan şüpheleniyorum . Bir performans sorununuz varsa, eski önerilere dayanan çözüm için yanlış yerde aradığınızdan şüpheleniyorum.
Will

@ Sonuçların hızlı olmasını isteyeceğim. Matrisim çok büyük. For döngüsü kullanarak bilgisayarımda çalışma süresi yaklaşık 30s. Hızı artırabilecek bazı akıllı vektörizasyon işlemleri veya mapReduce, vb.
ftxx

1
Şüpheliyim, yapamazsın. Vektörizasyon doğru olarak tanımlanmış vektörler ve matrisler üzerinde çalışır, ancak sonucunuz farklı uzunluklardaki vektörlere izin verir. Dolayısıyla, benim varsayımım, her zaman açık bir döngü veya kılık değiştirmiş bir döngü olacağınızdır cellfun.
HansHirse

@ftxx ne kadar büyük? Ve 1tipik bir sırada kaç tane var ? Bir finddöngünün fiziksel belleğe sığacak kadar küçük bir şey için 30'lara yakın bir şey almasını beklemezdim .
Will

@ftxx Lütfen güncellenmiş cevabımı görün, küçük bir performans artışı ile kabul edildiğinden beri düzenledim
Wolfie

Yanıtlar:


11

Bu cevabın altında, fordöngülerden keyfi olarak kaçınmak yerine performansla ilgilendiğinizi açıkladınız .

Aslında, bence fordöngüler muhtemelen burada en performanslı seçenektir. "Yeni" (2015b) JIT motoru tanıtıldığından ( kaynak ) fordöngüler doğal olarak yavaş değildir - aslında dahili olarak optimize edilirler.

O Sen kriter görebilirsiniz mat2cellThomasIsCoding tarafından sunulan seçenek burada çok yavaş ...

Karşılaştırma 1

Ölçeği daha net hale getirmek için bu çizgiden kurtulursak splitapply, yöntemim oldukça yavaştır, obchardon'un akümülatör seçeneği biraz daha iyidir, ancak en hızlı (ve karşılaştırılabilir) seçenekler arrayfun(Thomas tarafından da önerildiği gibi) veya bir fordöngü kullanır. Bunun çoğu kullanım durumu için kılık değiştirmiş arrayfunbir fordöngü olduğuna dikkat edin , bu şaşırtıcı bir kravat değildir!

Karşılaştırma 2

Daha forfazla kod okunabilirliği ve en iyi performans için bir döngü kullanmanızı tavsiye ederim .

Düzenle :

Döngünün en hızlı yaklaşım olduğunu varsayarsak, findkomut etrafında bazı optimizasyonlar yapabiliriz .

özellikle

  • MMantıksal olun . Aşağıdaki çizimde görüldüğü gibi, bu nispeten küçük için daha hızlı olabilir M, ancak büyükler için tür dönüşümünün değişmesi ile daha yavaş olabilir M.

  • Kullanmak yerine Mdiziyi dizine eklemek için bir mantıksal kullanın . Bu, döngünün en yavaş kısmını ( komut) önler ve tür dönüştürme yükünü ağırlaştırır, bu da onu en hızlı seçenek haline getirir.1:size(M,2)findfind

İşte en iyi performans için önerim:

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

Bunu aşağıdaki karşılaştırmaya ekledim, işte döngü tarzı yaklaşımların karşılaştırması:

Karşılaştırma 3

Karşılaştırma kodu:

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
Zaten gördüm ve kaldırıldı. :-) Hala Luis'i bekliyorum; bunun için biraz siyah MATLAB büyüsü olduğundan emin.
HansHirse

@Hans Haha evet, her zamanki hile çantası (örtülü genişleme, akıllı indeksleme, ...) genellikle matris olarak tutar, ancak buradaki darboğaz hücrelerde özetleniyor
Wolfie

1
Bu sürelerin büyük ölçüde bağlılığına dikkat edin M. Örneğin, öğelerin yalnızca% 5'i doldurulursa M = randi([0,20],N) == 20;, fordöngü çok yavaş olur ve arrayfunyönteminiz kazanır.
Will

@HansHirse :-) Benim yaklaşımım olurdu accumarrayolmadan ind2sub, ancak daha yavaş gerçekleşiyor fordöngü
Luis Mendo

2

arrayfunAşağıdaki gibi deneyebilirsiniz .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

}

veya (daha yavaş bir yaklaşım 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
arrayfunTemelde kılık değiştirmiş bir döngü olmasına rağmen , bu OP'nin
beklediği

2

Edit : Ben bir kıyaslama ekledi, sonuçlar bir for döngüsü daha verimli olduğunu göstermektediraccumarray .


Sen kullanabilirsiniz findve accumarray:

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

Matris transposed ( A') çünkü findsütunlara göre gruplar.

Misal:

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)

Çıktı:

C{1} = 
     1     4

C{2} = 
     2

C{3} =
     3     4

C{4} = 
     1     3     5

Karşılaştırma:

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

Sonuç:

accumarray:
Elapsed time is 2.407773 seconds.

For loop:
Elapsed time is 1.671387 seconds.

Bir for döngüsü daha accumarray...


Bu zaten obchardon tarafından önerilen yöntem , değil mi?
Wolfie

Evet, biraz yavaştım, cevabını benimkini gönderdikten sonra gördüm.
Eliahu Aaron

2

Accumarray kullanma :

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

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

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

1
Octave ve MATLAB Online'da işletim süresi gibi döngüsü için basit bir 2x hakkındadır: MM{I} = find(M(I, :)).
HansHirse

2
@Hans görmek isteyebilirsiniz cevabım
Wolfie

Evet, her hücrenin boyutu aynı olmadığından, bu sorun tamamen vektörlenemez (veya görmediğim bir hile var). Bu sadece for döngüsünü gizleyen bir çözümdür.
obchardon

Gerek yok ind2sub:[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
Luis Mendo

@LuisMendo teşekkürler, cevabımı düzenledim.
obchardon

2

Strfind'i kullanabilirsiniz :

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

Ben (tembel) dokümanlar bile bakmadım, ama bu stringkarakter yerine gerçek türleri kullanarak daha hızlı olurdu ? Dizeler için çok sayıda optimizasyon var, bu yüzden neden
varlar

@Wolfie Sayısal dizilerin karakter dizilerinden daha karakter dizilerine benzediğini düşünüyorum, bu nedenle sayısal dizinin karakter dizisine dönüştürülmesi dizeye dönüştürülmeden daha basit olmalıdır.
rahnema1
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.