Matlab Векторизация - ненулевые индексы строки матрицы для ячейки

10

Я работаю с Matlab.

У меня есть двоичная квадратная матрица. Для каждой строки есть одна или несколько записей 1. Я хочу просмотреть каждую строку этой матрицы и вернуть индекс этих 1 и сохранить их в записи ячейки.

Мне было интересно, есть ли способ сделать это без циклического прохождения по всем строкам этой матрицы, так как цикл for действительно медленный в Matlab.

Например, моя матрица

M = 0 1 0
    1 0 1
    1 1 1 

Тогда в конце концов, я хочу что-то вроде

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

Так Aи клетка.

Есть ли способ достичь этой цели без использования цикла for с целью более быстрого вычисления результата?

ftxx
источник
Вы хотите, чтобы результат был быстрым или вы хотите, чтобы результат избегал forциклов? Для этой проблемы, с современными версиями MATLAB, я сильно подозреваю, что forцикл является самым быстрым решением. Если у вас есть проблемы с производительностью, я подозреваю, что вы ищете не то место для решения, основанного на устаревших советах.
Будет
@ Я хочу, чтобы результаты были быстрыми. Моя матрица очень большая. Время выполнения составляет около 30 секунд на моем компьютере с помощью цикла for. Я хочу знать, есть ли какие-нибудь умные операции векторизации или, mapReduce, и т.д., которые могут увеличить скорость.
ftxx
1
Я подозреваю, ты не можешь. Векторизация работает с точно описанными векторами и матрицами, но ваш результат учитывает векторы различной длины. Таким образом, я предполагаю, что у вас всегда будет какой-то явный цикл или какой-то скрытый цикл cellfun.
HansHirse
@ftxx насколько большой? А сколько 1в типичном ряду? Я не ожидал бы, что findцикл займет что-то около 30 с для чего-то достаточно маленького, чтобы поместиться в физической памяти.
Будет
@ftxx Пожалуйста, смотрите мой обновленный ответ, я редактировал, так как он был принят с небольшим улучшением производительности
Вольф

Ответы:

11

Внизу этого ответа приведен код тестирования, поскольку вы пояснили, что вы заинтересованы в производительности, а не в том, чтобы произвольно избегать forциклов.

На самом деле, я думаю, что forциклы, вероятно, наиболее эффективный вариант здесь. Поскольку был введен «новый» (2015b) механизм JIT ( циклы источника ) forне являются изначально медленными - фактически они оптимизируются внутренне.

Из теста видно, что mat2cellопция, предлагаемая ThomasIsCoding здесь , очень медленная ...

Сравнение 1

Если мы избавимся от этой линии, чтобы сделать шкалу более четкой, то мой splitapplyметод будет довольно медленным, опция accmarray в obchardon немного лучше, но самые быстрые (и сопоставимые) варианты используют либо 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
Wolfie
источник
1
Уже видел и проголосовал. :-) Всё ещё жду Луиса; для этого у него наверняка есть какая-то черная магия MATLAB.
HansHirse
@ Ханс Хаха, да, хотя его обычный пакет трюков (неявное расширение, умное индексирование, ...) обычно хранит вещи в виде матриц, узкое место здесь суммируется в ячейках
Вольф
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

}
ThomasIsCoding
источник
1
Хотя arrayfunэто в основном замаскированная петля, поэтому она может потерпеть неудачу на обоих фронтах: 1) избегать петель и 2) быть быстрыми, на что надеялся ОП
Вольф
2

Редактировать : я добавил тест, результаты показывают, что цикл for более эффективен, чем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.

Цикл for более эффективен, чем accumarray...

Элиаху Аарон
источник
Это в значительной степени метод, уже предложенный obchardon , не так ли?
Вольф
Да, я был немного медленным, я увидел его ответ после того, как отправил свой.
Элиаху Аарон
2

Используя accmarray :

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

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

A = accumarray(ind,val,[],@(x) {x});
obchardon
источник
1
Время выполнения в октаве и MATLAB онлайн составляет около ого простого цикла , как: MM{I} = find(M(I, :)).
HansHirse
2
@ Возможно, ты захочешь увидеть мой ответ
Вольф
да, поскольку размеры каждой ячейки не одинаковы, эта проблема не может быть полностью векторизована (или есть хитрость, которую я не видел). Это только решение, которое скрывает цикл for.
obchardon
Не нужно ind2sub:[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
Луис Мендо
@ LuisMendo спасибо, я отредактировал свой ответ.
obchardon
2

Вы можете использовать strfind :

A = strfind(cellstr(char(M)), char(1));
rahnema1
источник
Я (лениво) даже не заглядывал в документы, но будет ли это быстрее с использованием реальных stringтипов, а не символов? Есть много оптимизаций для строк, поэтому они существуют ...
Вольф
@Wolfie Я думаю, что числовые массивы больше похожи на массивы символов, чем строки, поэтому преобразование числового массива в массив символов должно быть более простым, чем преобразование в строку.
rahnema1