Vettorializzazione di Matlab - nessuno zero indici di matrice nella cella


10

Sto lavorando con Matlab.

Ho una matrice quadrata binaria. Per ogni riga, ci sono una o più voci di 1. Voglio passare attraverso ciascuna riga di questa matrice e restituire l'indice di questi 1 e memorizzarli nella voce di una cella.

Mi chiedevo se c'è un modo per farlo senza passare in rassegna tutte le righe di questa matrice, poiché il ciclo è davvero lento in Matlab.

Ad esempio, la mia matrice

M = 0 1 0
    1 0 1
    1 1 1 

Poi alla fine, voglio qualcosa del genere

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

Quindi Aè una cellula.

Esiste un modo per raggiungere questo obiettivo senza utilizzare for loop, con l'obiettivo di calcolare il risultato più rapidamente?


Vuoi che il risultato sia veloce o vuoi che il risultato eviti i forloop? Per questo problema, con le versioni moderne di MATLAB, sospetto fortemente che un forloop sia la soluzione più veloce. Se hai un problema di prestazioni, sospetto che tu stia cercando la soluzione sbagliata in base a consigli obsoleti.
Sarà il

@Voglio che i risultati siano veloci. La mia matrice è molto grande. Il tempo di esecuzione è di circa 30 secondi nel mio computer usando il ciclo for. Voglio sapere se ci sono alcune operazioni di vettorializzazione intelligenti, mapReduce, ecc. Che possono aumentare la velocità.
ftxx,

1
Sospetto, non puoi. La vettorializzazione funziona su vettori e matrici accuratamente descritti, ma il risultato consente vettori di lunghezze diverse. Quindi, la mia ipotesi è che avrai sempre qualche ciclo esplicito o qualche tipo di "travestimento" cellfun.
HansHirse,

@ftxx quanto è grande? E quanti 1s in una riga tipica? Non mi aspetto che un findloop prenda qualcosa di vicino ai 30s per qualcosa di abbastanza piccolo da adattarsi alla memoria fisica.
Sarà il

@ftxx Si prega di vedere la mia risposta aggiornata, l'ho modificata da quando è stata accettata con un lieve miglioramento delle prestazioni
Wolfie,

Risposte:


11

In fondo a questa risposta c'è un codice di benchmarking, poiché hai chiarito che sei interessato alle prestazioni piuttosto che evitare arbitrariamente i forloop.

In effetti, penso che i forloop siano probabilmente l'opzione più performante qui. Da quando è stato introdotto il "nuovo" motore (2015b) JIT, i loop (di origine ) fornon sono intrinsecamente lenti - in realtà sono ottimizzati internamente.

Dal benchmark puoi vedere che l' mat2cellopzione offerta da ThomasIsCoding qui è molto lenta ...

Confronto 1

Se ci liberiamo di quella linea per rendere più chiara la scala, allora il mio splitapplymetodo è abbastanza lento, l' opzione accumarray di obchardon è un po 'migliore, ma le opzioni più veloci (e comparabili) stanno usando arrayfun(come suggerito anche da Thomas) o un forloop. Si noti che arrayfunè praticamente un forcircuito nascosto sotto mentite spoglie per la maggior parte dei casi d'uso, quindi non è un legame sorprendente!

Confronto 2

Consiglierei di utilizzare un forloop per una maggiore leggibilità del codice e le migliori prestazioni.

Modifica :

Se assumiamo che il looping sia l'approccio più veloce, possiamo fare alcune ottimizzazioni attorno al findcomando.

In particolare

  • Rendi Mlogico. Come mostra la trama sotto, questo può essere più veloce per relativamente piccoli M, ma più lento con il compromesso della conversione del tipo per grandi M.

  • Utilizzare un logico Mper indicizzare un array 1:size(M,2)invece di utilizzare find. Ciò evita la parte più lenta del ciclo (il findcomando) e supera il sovraccarico di conversione del tipo, rendendolo l'opzione più rapida.

Ecco la mia raccomandazione per le migliori prestazioni:

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

Ho aggiunto questo al benchmark di seguito, ecco il confronto degli approcci in stile loop:

Confronto 3

Codice di benchmarking:

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
Già visto e votato. :-) Sto ancora aspettando Luis; ha sicuramente della magia nera MATLAB per questo.
HansHirse,

@Hans Haha sì, sebbene la sua solita borsa di trucchi (espansione implicita, indicizzazione intelligente, ...) di solito mantiene le cose come matrici, il collo di bottiglia qui si riassume in celle
Wolfie,

1
Si noti che questi tempi dipendono fortemente dalla scarsità di M. Se, ad esempio, viene popolato solo il 5% di elementi, M = randi([0,20],N) == 20;il forloop è di gran lunga il più lento e il tuo arrayfunmetodo vince.
Sarà il

@HansHirse :-) Il mio approccio sarebbe stato accumarraysenza ind2sub, ma è più lento del forciclo
Luis Mendo,

2

Puoi provare arrayfuncome di seguito, che scorre tra le file diM

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

}

o (un approccio più lento di 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
Anche se arrayfunè fondamentalmente un travestimento, quindi questo potrebbe non riuscire su entrambi i fronti di 1) evitando loop e 2) essere veloce, come sperato dall'OP
Wolfie

2

Modifica : ho aggiunto un benchmark, i risultati mostrano che un ciclo for è più efficiente diaccumarray .


Puoi usare finde accumarray:

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

La matrice viene trasposta ( A') perché findraggruppa per colonna.

Esempio:

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)

Produzione:

C{1} = 
     1     4

C{2} = 
     2

C{3} =
     3     4

C{4} = 
     1     3     5

Prova delle prestazioni:

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

Risultato:

accumarray:
Elapsed time is 2.407773 seconds.

For loop:
Elapsed time is 1.671387 seconds.

Un ciclo for è più efficiente di accumarray...


Questo è praticamente il metodo già proposto da Obchardon , no?
Wolfie,

Sì, ero un po 'lento, ho visto la sua risposta dopo aver pubblicato la mia.
Eliahu Aaron,

2

Utilizzando accumarray :

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

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

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

1
Tempo di esecuzione in ottava e MATLAB online è di circa 2 volte di un semplice ciclo for come: MM{I} = find(M(I, :)).
HansHirse,

2
@Hans potresti voler vedere la mia risposta
Wolfie,

sì, poiché le dimensioni di ogni cella non sono le stesse, questo problema non può essere completamente vettorializzato (o c'è un trucco che non ho visto). È solo una soluzione che nasconde il ciclo for.
obchardon,

Non c'è bisogno di ind2sub:[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
Luis Mendo,

@LuisMendo grazie, ho modificato la mia risposta.
obchardon,

2

Puoi usare strfind :

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

(Pigramente) non ho nemmeno cercato nei documenti, ma sarebbe più veloce usando i stringtipi reali , piuttosto che i caratteri? Ci sono molte ottimizzazioni per le stringhe, quindi perché esistono ...
Wolfie,

@ Wolfolf Penso che gli array numerici siano più simili agli array di caratteri che alle stringhe, quindi la conversione dell'array numerico in array di caratteri dovrebbe essere più semplice della conversione in stringa.
rahnema1,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.