Векторизація Matlab - індекси рядкових матричних рядків до комірки


10

Я працюю з Matlab.

У мене є двійкова квадратна матриця. Для кожного рядка є один або кілька записів з 1. Я хочу пройти кожен рядок цієї матриці і повернути індекс цих 1 і зберегти їх у записі комірки.

Мені було цікаво, чи є спосіб зробити це, не перебираючи всі рядки цієї матриці, оскільки цикл дійсно повільний у Matlab.

Наприклад, моя матриця

M = 0 1 0
    1 0 1
    1 1 1 

Тоді зрештою я хочу щось подібне

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

Так Aце клітина.

Чи є спосіб досягти цієї мети без використання циклу з метою швидшого обчислення результату?


Ви хочете, щоб результат був швидким, або ви хочете, щоб результат уникнув forциклів? З цієї проблеми, в сучасних версіях MATLAB, я сильно підозрюю, що forцикл є найшвидшим рішенням. Якщо у вас є проблеми з роботою, я підозрюю, що ви шукаєте рішення в неправильному місці на основі застарілих порад.
Буде

@ Я хочу, щоб результати були швидкими. Моя матриця дуже велика. Час роботи на моєму комп’ютері становить близько 30-х років, використовуючи цикл. Я хочу знати, чи є якісь розумні операції векторизації, або, MapReduce тощо, які можуть підвищити швидкість.
ftxx

1
Я підозрюю, ви не можете. Векторизація працює на точно описаних векторах та матрицях, але ваш результат дозволяє вектори різної довжини. Таким чином, моє припущення полягає в тому, що ви завжди матимете явний цикл або подібний цикл cellfun.
HansHirse

@ftxx наскільки великий? А скільки 1с у типовому ряду? Я б не очікував, що findцикл візьме щось близько 30-х років для чогось малого, щоб поміститися у фізичну пам'ять.
Буде

@ftxx Будь ласка, дивіться мою оновлену відповідь, яку я редагував, оскільки вона була прийнята з незначним покращенням продуктивності
Wolfie

Відповіді:


11

У нижній частині цієї відповіді є деякий код бенчмаркінгу, оскільки ви уточнили, що вас цікавить ефективність, а не довільно уникати forциклів.

Насправді, я думаю, forпетлі - це, мабуть, найефективніший варіант тут. З моменту впровадження «нового» (2015b) двигуна JIT ( джерело )for петлі по суті не повільні - адже вони оптимізовані всередині країни.

З еталону видно, що mat2cellопція, запропонована ThomasIsCoding тут , дуже повільна ...

Порівняння 1

Якщо ми позбудемося цього рядка, щоб зробити splitapplyясність чіткішою, то мій метод досить повільний, варіант обхардона акумуляторного варіанту трохи кращий, але найшвидші (і порівнянні) варіанти або використовуються arrayfun(як це запропонував Томас), або forцикл. Зауважте, що arrayfunв основному це forпетля, замаскована для більшості випадків використання, тому це не дивно, що це стосується!

Порівняння 2

Я рекомендую вам використовувати forцикл для підвищення читабельності коду та найкращої продуктивності.

Редагувати :

Якщо припустити, що циклічне підключення - це найшвидший підхід, ми можемо зробити деякі оптимізації навколо findкоманди.

Конкретно

  • Зробити Mлогічним. Як показано на рисунку нижче, це може бути швидше відносно невеликим M, але повільніше, коли компроміс конверсії типів для великихM .

  • Використовуйте логічне, 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
Вже бачив і підтримував. :-) Ще чекаю Луїса; він впевнений, що для цього є якась чорна магія MATLAB.
HansHirse

@ Ханс Ха-ха, хоча його звичайна сумка з хитрощами (неявне розширення, розумне індексування, ...) зазвичай зберігає речі як матриці, вузьке місце тут підсумовується у клітинках
Wolfie

1
Зауважте, що ці часи сильно залежать від рідкості 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) швидкість, на що сподівається ОП
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

Так, я був трохи повільним, я побачив його відповідь після того, як я розмістив свою.
Еліаху Аарон

2

Використання Acumarray :

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

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

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

1
Час виконання в октаві і MATLAB онлайн становить близько ого простого циклу , як: MM{I} = find(M(I, :)).
HansHirse

2
@Ханс, можливо, ви захочете побачити мою відповідь
Wolfie

так, оскільки розміри кожної комірки не однакові, цю проблему не можна повністю векторизувати (або є хитрість, якої я не бачив). Це лише рішення, яке приховує цикл for.
obchardon

Не потрібно ind2sub:[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
Луїс Мендо

@LuisMendo спасибі, я відредагував свою відповідь.
obchardon

2

Ви можете використовувати strfind :

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

Я (ліниво) навіть не шукав у документах, але чи буде це швидше, використовуючи фактичні stringтипи, а не символи? Існує багато оптимізацій для рядків, отже, чому вони існують ...
Wolfie

@Wolfie Я думаю, що числові масиви більше схожі на масиви char, ніж рядки, тому перетворення числового масиву в масив символів повинно бути більш простим, ніж перетворення в рядок.
rahnema1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.