MATLAB: свертка матричной функции - PullRequest
4 голосов
/ 23 апреля 2011

Я написал этот код для выполнения 1-й свертки двумерной матричной функции (k - мой временной индекс, кенд порядка 10e3). Есть ли более быстрый или чистый способ сделать это, возможно, с использованием встроенных функций?

for k=1:kend
  C(:,:,k)=zeros(3);
  for l=0:k-1
      C(:,:,k)=C(:,:,k)+A(:,:,k-l)*B(:,:,l+1);
  end
end

Ответы [ 3 ]

4 голосов
/ 25 апреля 2011

НОВОЕ РЕШЕНИЕ:

Это более новое решение, построенное на более старом решении, которое решило ранее заданную формулу.Код в вопросе на самом деле является модификацией этой формулы, в которой перекрытие между двумя матрицами в третьем измерении многократно смещается (это похоже на свертку в третьем измерении данных).Предыдущее решение, которое я дал, вычисляло только результат для последней итерации кода в вопросе (т.е. k = kend).Итак, вот полное решение, которое должно быть намного более эффективным, чем код в вопросе для kend порядка 1000:

kend = size(A,3);                         %# Get the value for kend
C = zeros(3,3,kend);                      %# Preallocate the output
Anew = reshape(flipdim(A,3),3,[]);        %# Reshape A into a 3-by-3*kend matrix
Bnew = reshape(permute(B,[1 3 2]),[],3);  %# Reshape B into a 3*kend-by-3 matrix
for k = 1:kend
  C(:,:,k) = Anew(:,3*(kend-k)+1:end)*Bnew(1:3*k,:);  %# Index Anew and Bnew so
end                                                   %#   they overlap in steps
                                                      %#   of three

Даже при использовании только kend = 100для меня это решение оказалось примерно в 30 раз быстрее, чем рассматриваемое, и примерно в 4 раза быстрее, чем решение на основе чистых циклов (которое включало бы 5 циклов !).Обратите внимание, что приведенное ниже обсуждение точности с плавающей точкой по-прежнему применимо, поэтому вполне нормально и ожидается, что вы увидите небольшие различия между решениями порядка относительной точности с плавающей точкой .


СТАРЫЕ РЕШЕНИЯ:

Исходя из этой формулы, на которую вы ссылаетесь в комментарии:

enter image description here

кажется, что вы на самом делехочу сделать что-то отличное от кода, который вы указали в вопросе.Предполагая, что A и B являются матрицами 3 на 3 на k, результат C должен быть матрицей 3 на 3, а формула из вашей ссылки, записанная как набор вложенных циклов for, будетвыглядит следующим образом:

%# Solution #1: for loops
k = size(A,3);
C = zeros(3);
for i = 1:3
  for j = 1:3
    for r = 1:3
      for l = 0:k-1
        C(i,j) = C(i,j) + A(i,r,k-l)*B(r,j,l+1);
      end
    end
  end
end

Теперь можно выполнить эту операцию без циклов for, изменив и реорганизовав A и B соответствующим образом:

%# Solution #2: matrix multiply
Anew = reshape(flipdim(A,3),3,[]);        %# Create a 3-by-3*k matrix
Bnew = reshape(permute(B,[1 3 2]),[],3);  %# Create a 3*k-by-3 matrix
C = Anew*Bnew;                            %# Perform a single matrix multiply

Вы могли бы даже переработать код, который у вас есть в вашем вопросе, чтобы создать решение с одним циклом, который выполняет матричное умножение ваших подматриц 3 на 3:

%# Solution #3: mixed (loop and matrix multiplication)
k = size(A,3);
C = zeros(3);
for l = 0:k-1
  C = C + A(:,:,k-l)*B(:,:,l+1);
end

Итак, теперь вопросКакой из этих подходов быстрее / чище?

Ну, «чище» очень субъективно, и я, честно говоря, не могу сказать вам, какой из приведенных выше фрагментов кода облегчает понимание того, что за операцияделается.Все циклы и переменные в первом решении усложняют отслеживание происходящего, но оно явно отражает формулу.Второе решение разбивает все это на простую матричную операцию, но трудно понять, как это соотносится с исходной формулой.Третье решение похоже на золотую середину между этими двумя.

Итак, давайте сделаем скорость тай-брейка.Если я рассчитываю вышеупомянутые решения для ряда значений k, я получаю эти результаты (в секундах, необходимых для выполнения 10 000 итераций данного решения, MATLAB R2010b):

 k   |  loop  | matrix multiply |  mixed
-----+--------+-----------------+--------
 5   | 0.0915 |      0.3242     | 0.1657
 10  | 0.1094 |      0.3093     | 0.2981
 20  | 0.1674 |      0.3301     | 0.5838
 50  | 0.3181 |      0.3737     | 1.3585
 100 | 0.5800 |      0.4131     | 2.7311   * The matrix multiply is now fastest
 200 | 1.2859 |      0.5538     | 5.9280

Ну, получаетсяиз этого следует, что при меньших значениях k (около 50 или менее) решение for-loop на самом деле выигрывает, еще раз демонстрируя, что циклы for не так «злы», как это считалось в более старых версиях MATLAB.При определенных обстоятельствах они могут быть более эффективными, чем умная векторизация.Однако, когда значение k больше, чем около 100, векторизованное решение умножения матриц начинает выигрывать, масштабируясь с увеличением k намного лучше, чем решение для цикла.Смешанное решение для цикла / матрицы-умножения масштабно жестоко по причинам, в которых я не совсем уверен.

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

2 голосов
/ 23 апреля 2011

Вы изучили метод Matlab conv ?

Я не могу сравнить его с предоставленным вами кодом, потому что то, что вы предоставили, вызывает у меня проблему при попытке получить доступ к нулевому элементуA. (Когда k=1, k-1=0.)

1 голос
/ 23 апреля 2011

Рассматривали ли вы использовать БПФ для свертки? Операция свертки представляет собой просто точечное умножение в частотной области . Вы должны будете принять некоторые меры предосторожности с конечными последовательностями, так как вы будете в конечном итоге с круговой сверткой, если вы не будете осторожны (но об этом просто позаботиться).

Вот простой пример для одномерного случая.

>> a=rand(4,1);
>> b=rand(3,1);
>> c=conv(a,b)

c =

    0.1167
    0.3133
    0.4024
    0.5023
    0.6454
    0.3511

То же самое с использованием БПФ

>> A=fft(a,6);
>> B=fft(b,6);
>> C=real(ifft(A.*B))

C =

    0.1167
    0.3133
    0.4024
    0.5023
    0.6454
    0.3511

Свертка M точечного вектора и N точечного вектора приводит к M+N-1 точечному вектору. Итак, я добавил каждый из векторов a и b в нули, прежде чем брать БПФ (об этом автоматически заботится, когда я беру * 4+3-1=6 точечное БПФ).

EDIT

Хотя уравнение, которое вы показали, похоже на круговую свертку, это не совсем так. Таким образом, вы можете отказаться от подхода FFT и встроенных conv* функций. Чтобы ответить на ваш вопрос, вот та же операция, выполненная без явных циклов:

dim1=3;dim2=dim1;
dim3=10;
a=rand(dim1,dim2,dim3);
b=rand(dim1,dim2,dim3);


mIndx=cellfun(@(x)(1:x),num2cell(1:dim3),'UniformOutput',false);
fun=@(x)sum(reshape(cell2mat(cellfun(@(y,z)a(:,:,y)*b(:,:,z),num2cell(x),num2cell(fliplr(x)),'UniformOutput',false)),[dim1,dim2,max(x)]),3);
c=reshape(cell2mat(cellfun(@(x)fun(x),mIndx,'UniformOutput',false)),[dim1,dim2,dim3]);
  • mIndx здесь - ячейка, в которой i -я ячейка содержит вектор 1:i. Это ваш l индекс (как уже отмечали другие, пожалуйста, не используйте l в качестве имени переменной).
  • Следующая строка - анонимная функция, которая выполняет операцию свертки, используя тот факт, что индекс k - это просто перевернутый индекс l. Операции выполняются на отдельных ячейках, а затем собираются.
  • Последняя строка фактически выполняет операции над матрицами.

Ответ такой же, как и в случае с петлями. Тем не менее, вы обнаружите, что зацикленное решение на порядок быстрее (я усреднил 0,007 с для моего кода и 0,0006 с для цикла). Это потому, что цикл довольно прост, в то время как с такого рода вложенной конструкцией, существует много накладных расходов на вызовы функций и повторное изменение формы, которые замедляют его.

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

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

...