Сжимающийся тензор в Matlab - PullRequest
2 голосов
/ 28 апреля 2019

Я ищу способ сжать два индекса тензора в Matlab.

Скажем, у меня есть тензор размерности [17,10,17,12] Я ищу функцию, которая суммирует первое и третье измерение с одинаковым индексом и оставляет матрицу размерности [10,12] (аналогично след в двух измерениях).

В настоящее время я изучаю тензорные сети и в основном использую функции "permute" и "reshape". Если кто-то сжимает несколько тензоров и не проявляет осторожности с самого начала, у него могут получиться индексы, которые он хочет сжать в одном тензоре вида [i, j, i, k].

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

EDIT:

Что-то с эффектом:

A = rand(17,10,17,12);
A_contracted = zeros(10,12);
for i = [1:10]
    for j = [1:12]
        for k = [1:17]
            A_contracted(i,j) = A_contracted(i,j) + A(k,i,k,j);
        end
    end

end

Ответы [ 4 ]

2 голосов
/ 29 апреля 2019

Вот способ сделать это:

A_contracted = permute(sum( ...
   A.*((1:size(A,1)).'==reshape(1:size(A,3), 1, 1, [])), [1 3]), [2 4 1 3]);

Выше используется неявное расширение и возможность работать по нескольким измерениям одновременно в sum, которые являются последними функциями Matlab.Для более старых версий Matlab

A_contracted = permute(sum(sum( ...
   A.*bsxfun(@eq, (1:size(A,1)).', reshape(1:size(A,3), 1, 1, [])),1),3), [2 4 1 3]);
2 голосов
/ 29 апреля 2019

[Я чувствую, что начинаю звучать как неработающая пластинка ...]

Вы всегда должны сначала реализовывать свой код как цикл, а затем пытаться оптимизировать, используя permute и reshape. Но обратите внимание, что permute необходимо копировать данные, поэтому стремится увеличить объем работы, а не уменьшить ее. Последние версии MATLAB больше не работают медленно с циклами, и, следовательно, копирование данных больше не всегда полезно для ускорения процесса.

Например, цикл в вопросе можно упростить до:

A_contracted = zeros(size(A,2),size(A,4));
for k = 1:size(A,1)
    A_contracted = A_contracted + squeeze(A(k,:,k,:));
end

(я также обобщил на произвольные размеры).

По сравнению с ответом Луиса я вижу, что векторизованный метод выиграл для небольших массивов, таких как в OP (17x10x17x12), с 0,09 мс против 0,19 мс. Но с очень маленькими временами вокруг это вероятно не стоит усилий Однако для больших массивов (я пробовал 17x100x17x120) я вижу, что метод цикла выигрывает 1,3 мс против 2,6 мс.

Чем больше данных, тем больше преимущество использования простых старых циклов. С 170x100x170x120 это 0,04 с против 0,45 с.


Тестовый код:

A = rand(17,100,17,120);
assert(all(method2(A)==method1(A),'all'))
timeit(@()method1(A))
timeit(@()method2(A))

function A_contracted = method1(A)
A_contracted = permute(sum( ...
   A.*((1:size(A,1)).'==reshape(1:size(A,3), 1, 1, [])), [1 3]), [2 4 1 3]);
end

function A_contracted = method2(A)
A_contracted = zeros(size(A,2),size(A,4));
for k = 1:size(A,1)
    A_contracted = A_contracted + squeeze(A(k,:,k,:));
end
end
1 голос
/ 01 мая 2019

Мой профессор предложил другое решение (в дальнейшем обозначаемое method3), включающее изменение формы и умножение матриц.

  1. взять единичную матрицу размера контрактного индекса
  2. преобразовать его в вектор
  3. измените тензор, который вы хотите заключить соответственно
  4. умножаем вектор и тензор
  5. изменить контрактный тензор

пример кода по сравнению с Луис (метод1) и Крис ответ (метод2):

A = rand(17,10,17,10);

timeit(@()method1(A))
timeit(@()method2(A))
timeit(@()method3(A))

function A_contracted = method1(A)
A_contracted = permute(sum( ...
   A.*((1:size(A,1)).'==reshape(1:size(A,3), 1, 1, [])), [1 3]), [2 4 1 3]);
end


function A_contracted = method2(A)
A_contracted = zeros(size(A,2),size(A,4));
for k = 1:size(A,1)
    A_contracted = A_contracted + squeeze(A(k,:,k,:));
end
end


function A_contracted = method3(A)
sa_1 = size(A,1);
Unity = eye(size(A, 1));
Unity = reshape(Unity, [1,sa_1*sa_1]);
A1 = permute(A, [1,3,2,4]);
A2 = reshape(A1, [sa_1*sa_1, size(A1, 3)* size(A1,4)]);
UnA = Unity*A2;
A_contracted = reshape(UnA, [size(A1,3), size(A1,4)]);
end

method3 преобладает для малых размеров на порядок по сравнению с method1 и method2 и превосходит method1 для больших размеров, но побеждает цикл for для больших размеров на один порядок.

method3 имеет (несколько личное) преимущество в том, что он более интуитивен для применения в моем курсе физики в том смысле, что сжатие на самом деле не в самом тензоре, а в отношении метрики. method3 может быть легко адаптирован для включения этой функции.

0 голосов
/ 28 апреля 2019

Довольно просто

squeeze(sum(sum(a,3),1))

Сумма sum(a,n) по n-му измерению массива, а squeeze удаляет все одноэлементные измерения

...