MATLAB: извлечение значений из 3D-матрицы по заданным индексам строк и столбцов с использованием sub2ind 3d

#arrays #matlab #vectorization

#массивы #matlab #векторизация

Вопрос:

У меня есть 3D-матрица A , в которой есть мои данные. В нескольких местоположениях, определенных индексами строк и столбцов, как показано в matrix row_col_idx , я хочу извлечь все данные по третьему измерению, как показано ниже:

 A = cat(3,[1:3;4:6], [7:9;10:12],[13:15;16:18],[19:21;22:24])  %matrix(2,3,4) 
row_col_idx=[1 1;1 2; 2 3]; 

idx = sub2ind(size(A(:,:,1)), row_col_idx(:,1),row_col_idx(:,2));
out=nan(size(A,3),size(row_col_idx,1));
for k=1:size(A,3) 
    temp=A(:,:,k);
    out(k,:)=temp(idx);          
end
out
  

Вывод этого кода выглядит следующим образом:

 A(:,:,1) =

     1     2     3
     4     5     6


A(:,:,2) =

     7     8     9
    10    11    12


A(:,:,3) =

    13    14    15
    16    17    18


A(:,:,4) =

    19    20    21
    22    23    24


out =

     1     2     6
     7     8    12
    13    14    18
    19    20    24
  

Результат соответствует ожиданиям. Однако фактические A и row_col_idx огромны, поэтому этот код требует больших вычислительных затрат. Можно ли изменить этот код, чтобы избежать цикла и temp матрицы?

Ответ №1:

Это может быть векторизовано с использованием линейной индексации и неявного расширения:

 out = A( row_col_idx(:,1)   ...
        (row_col_idx(:,2)-1)*size(A,1)   ...
        (0:size(A,1)*size(A,2):numel(A)-1) ).';
  

Приведенное выше строит матрицу индексации размером с выходные данные. Если это неприемлемо из-за ограничений памяти, этого можно избежать, изменив A :

 sz = size(A); % store size A
A = reshape(A, [], sz(3)); % collapse first two dimensions
out = A(row_col_idx(:,1)   (row_col_idx(:,2)-1)*sz(1),:).'; % linear indexing along
% first two dims of A
A = reshape(A, sz); % reshape back A, if needed
  

Комментарии:

1. Интересно, какова стоимость создания этой матрицы индексации … 🙂

2. @CrisLuengo Хорошая мысль. Я добавил вторую версию, которая позволяет избежать этого с помощью изменения формы

3. О, это здорово!

4. Второй метод представляется наиболее эффективным для моего реального случая с размером A(300,220,280) row_col_idx (33,2) и выполнением кода 1e4 раза. Пожалуйста, смотрите мой другой комментарий ниже.

Ответ №2:

Более эффективным методом является использование элементов row_col_idx вектора для выбора элементов A . Я сравнил два метода для большой матрицы, и, как вы можете видеть, вычисление происходит намного быстрее. Для A приведенного в вопросе, он дает тот же результат

 A = rand([2,3,10000000]);
row_col_idx=[1 1;1 2; 2 3];

idx = sub2ind(size(A(:,:,1)), row_col_idx(:,1),row_col_idx(:,2));
out=nan(size(A,3),size(row_col_idx,1));
tic;
for k=1:size(A,3)
    temp=A(:,:,k);
    out(k,:)=temp(idx);
end
time1 = toc;

%% More efficient method:
out2 = nan(size(A,3),size(row_col_idx,1));
tic;
for jj = 1:size(row_col_idx,1)
    out2(:,jj) = [A(row_col_idx(jj,1),row_col_idx(jj,2),:)];
end
time2 = toc;

fprintf('Time calculation 1: %dn',time1);
fprintf('Time calculation 2: %dn',time2);
  

Выдает в качестве выходных данных:

 Time calculation 1: 1.954714e 01
Time calculation 2: 2.998120e-01
  

Комментарии:

1. Относительное время двух методов, вероятно, будет зависеть от относительных размеров трех измерений A и длины row_col_idx . Кроме того, разница во времени на 2 порядка больше, чем я ожидал. Какую версию MATLAB вы использовали? Надеюсь, не октава?

2. С фактическим размером матрицы A(300,220,280) и матрицы row_col_idx (33,2) , и мне нужно выполнить это как минимум 1e4 раза. % Время вычисления 1: 1.720272e 02 (@ASE methods), % Время вычисления 2: 1.508412e 00 (@Nathan method), % Время вычисления 3: 1.050692e 00 (@Luis Mendo method1), % Время вычисления 4: 6.370173e-01 (@Luis Mendo method1).

3. К вашему сведению, я использовал версию 2019b