В поисках эффективного способа вычисления - Matlab
У меня есть скалярная функция f([x,y],[i,j])= exp(-norm([x,y]-[i,j])^2/sigma^2), которая получает два 2-мерных векторы в качестве входных данных (здесь норма реализует евклидову норму). Значения x, i находятся в диапазоне 1:w, а значения y,j - в диапазоне 1:h. Я хочу создать массив ячеек X так, чтобы X{x,y} содержал матрицу awxh, такую что X{x,y}(i,j) = f([x,y],[i,j]). Очевидно, что это можно сделать, используя 4 вложенных цикла:
for x=1:w;
for y=1:h;
X{x,y}=zeros(w,h);
for i=1:w
for j=1:h
X{x,y}(i,j)=f([x,y],[i,j])
end
end
end
end
Это, однако, крайне неэффективно. Я был бы очень признателен за эффективный способ создания X.
3 ответа
Один из способов сделать это - удалить 2 самые внутренние петли и заменить их векторной версией. Судя по вашей функции f это не должно быть слишком плохо
Сначала нам нужно построить две матрицы, содержащие от 1 до w в каждой строке и от 1 до h в каждом столбце, например:
wMat=repmat(1:w,h,1);
hMat=repmat(1:h,w,1)';
Это будет представлять две внутренние петли, и транспонирование позволит нам получить все комбинации. Теперь мы можем векторизовать вычисление (f([x,y],[i,j])= exp(-norm([x,y]-[i,j])^2/sigma^2)):
for x=1:w;
for y=1:h;
temp1=sqrt((x-wMat).^2+(y-hMat).^2);
X{x,y}=exp(temp1/(sigma^2));
end
end
Где мы вычислили евклидову норму для всех пар узлов во внутренних циклах одновременно.
Некоторое обсуждение и код
Хитрость здесь в том, чтобы выполнить norm-calculations
с числовыми массивами и сохранить результаты в версии массива ячеек как можно позже. Для выполнения norm-calculations
Вы можете воспользоваться помощью ndgrid
, bsxfun
и немного permute + reshape
чтобы придать ему "форму", необходимую для окончательной версии массива ячеек. Итак, вот векторизованный подход для выполнения этих задач -
%// Create x-y/i-j values to be used for calculation of function values
[xi,yi] = ndgrid(1:w,1:h);
%// Get the norm values
normvals = sqrt(bsxfun(@minus,xi(:),xi(:).').^2 + ...
bsxfun(@minus,yi(:),yi(:).').^2);
%// Get the actual function values
vals = exp(-normvals.^2/sigma^2);
%// Get the values into blocks of a 4D array and then re-arrange to match
%// with the shape of numeric array version of X
blks = reshape(permute(reshape(vals, w*h, h, []), [2 1 3]), h, w, h, w);
arranged_blks = reshape(permute(blks,[2 3 1 4]),w,h,w,h);
%// Finally get the cell array version
X = squeeze(mat2cell(arranged_blks,w,h,ones(1,w),ones(1,h)));
Бенчмаркинг и время выполнения
После улучшения исходного зацикленного кода с предварительным выделением для X
и функциональный f
, runtime-benchmarks
были выполнены с ним против предложенного векторизованного подхода с размерами данных как w, h = 60
и результаты выполнения, полученные таким образом, были:
----------- With Improved loopy code
Elapsed time is 41.227797 seconds.
----------- With Vectorized code
Elapsed time is 2.116782 seconds.
Это наводит на мысль о том, что 20x
Ускорение с предложенным решением!
Для чрезвычайно больших размеров данных
Если вы имеете дело с огромными размерами данных, по сути, вы не даете достаточно памяти для bsxfun
работать и bsxfun
Известно, что он использует много памяти для создания эффективного векторизованного решения. Таким образом, для таких случаев с огромным размером данных вы можете использовать следующий циклический подход для замены normvals
расчеты, которые были перечислены в предыдущем bsxfun
решение на основе -
%// Get the norm values
nx = numel(xi);
normvals = zeros(nx,nx);
for ii = 1:nx
normvals(:,ii) = sqrt( (xi(:) - xi(ii)).^2 + (yi(:) - yi(ii)).^2 );
end
Мне кажется, что когда вы пробегаете цикл для x=w, y=h
, вы рассчитываете все значения, которые вам нужны одновременно. Поэтому вам не нужно пересчитывать их. Как только у вас есть это:
for i=1:w
for j=1:h
temp(i,j)=f([x,y],[i,j])
end
end
Тогда, например X{1,1}
просто temp(1,1)
, X{2,2}
просто temp(1:2,1:2)
, и так далее. Если вы можете векторизовать расчет f
(norm
вот только евклидова норма этого вектора?) тогда станет еще проще.