Как лучше всего оптимизировать матричное умножение в IDL, в котором используется диагонально-ориентированная (не диагональная) матрица

Я ищу наиболее эффективный код IDL для замены оператора умножения матрицы IDL (#) для конкретной диагонально-ориентированной (не диагональной или диагонально-симметричной) матрицы с 3 различными значениями: единица на диагонали; единица плюс дельта справа от диагонали; единица минус такая же дельта влево.

Проблемная область

IDL (фиксированный; не подлежит обсуждению; извините); смазывание изображения на беззатворной ПЗС-системе формирования изображения.

Основная постановка проблемы

Дано

  • матрица 1024x1024 "EMatrix" с единицей по диагонали; (1-дельта) слева от диагонали; (1+дельта) вправо; дельта = 0,044.

  • другая матрица 1024x1024, Изображение

Запрос

  • какой самый быстрый код IDL для расчета (изображение # EMatrix)?

2014-09-16: см. обновление ниже

Задний план

Более крупная постановка задачи (из которой умножение матрицы кажется только самой медленной частью, и оптимизация всей процедуры не повредит):

Раздел 9.3.1.2 (страница PDF 47; внутренняя страница 34) и другие документы в том же каталоге (извините, как новичок, я могу опубликовать только две ссылки)

Моя работа до сих пор

Это сейчас (2014-09-26) примерно на порядок быстрее, чем оператор IDL # для матрицы 1024x1024.

Подробности

Наивная операция — O(n^3) и выполняет около миллиарда (2^30) умножений с двойной точностью и примерно такое же количество сложений; Далее Википедия сообщает мне, что алгоритм Штрассена сокращает это значение до O (n ^ 2,807), или ~ 282M+ умножений для n = 1024.

Разбивая это для простого случая 3x3, скажем, изображение и EMatrix

   image        EMatrix

[ 0  1  2 ]   [ 1  p  p ]
[ 3  4  5 ] # [ m  1  p ]
[ 6  7  8 ]   [ m  m  1 ]

где p представляет собой 1+дельта (1,044), а m представляет собой 1-дельта (0,956).

Из-за повторения m и p должно быть доступно упрощение: глядя на средний столбец для изображения, результат для трех строк должен быть

[1,4,7] . [1,p,p] = m*(0)   + 1*1 + p*(4+7)
[1,4,7] . [m,1,p] = m*(1)   + 1*4 + p*(7)
[1,4,7] . [m,m,1] = m*(1+4) + 1*7 + p*(0)

куда . представляет точечный (внутренний?) продукт, т.е. [1,4,7].[a,b,c] = (1a + 4b + 7c)

Исходя из этого, вот что я сделал до сих пор:

Средний член — это просто сам столбец, а суммы, умноженные на m и p, очень похожи на кумулятивные суммы смежных участков столбца, возможно, перевернутых (для m), сдвинутых на единицу и с первым элементом, установленным на ноль.

то есть для м:

; shift image down one:
imgminusShift01 = shift(image,0,1)

; zero the top row:
imgminusShift01[*,0] = 0

; Make a cumulative sum:
imgminusCum = total( imageShift01, 2, /cumulative)

Для p imgplusShift01Cum следует по существу тому же пути, но с поворотом (..., 7) до и после, чтобы переворачивать элементы вверх и вниз.

Когда у нас есть эти три матрицы (исходное изображение с его дочерними элементами imgminusShift01Cum и imgplusShift01Cum), желаемый результат будет

m * imgminusShift01Cum    +   1 * image   +   p * imgplusShift01Cum

Для выполнения сдвигов и поворотов я использую индексы, которые сдвигаются и поворачиваются сами по себе и сохраняются в общем блоке для последующих вызовов, что экономит еще 10-20%.

Резюме, пока

2014-09-16: см. обновление ниже

И это дает ускорение 5+.

Я ожидал немного большего, потому что я думаю, что умножил 3M и добавил 2M, так что, возможно, выделение памяти — дорогостоящая часть, и я должен делать что-то вроде REPLICATE_INPLACE (мой IDL ржавый — я мало что делал с 6.4 и ранний 7.0).

Или, может быть, матричное умножение IDL лучше теории?

Альтернативные подходы и другие мысли:

  • Можем ли мы что-то сделать с тем фактом, что EMatrix равна единице плюс матрица с нулями по диагонали и +/- дельта в верхнем и нижнем треугольниках?

  • Суммируя по столбцам, я последовательно обращаюсь к данным «неправильным» способом, но действительно ли это сэкономит время, если сначала перенести изображение (это всего 8 МБ)?

  • Очевидно, что выбор другого языка, получение массива графических процессоров или написание DLM были бы другими вариантами, но давайте пока оставим это в IDL.

advСПАСИБО (да, я такой старый ;-),

Брайан Карчич 20 июля 2014 г.

Обновление 2014-09-16

Подход Диего привел меня к гораздо более простому решению:

Я думаю, мы поняли; мой первый проход был слишком сложным, со всеми вращениями.

Используя обозначения Диего, но транспонированные, я ищу

[K] = [IMG] # [E]

Поскольку это умножает столбцы [IMG] на строки [E], между столбцами [IMG] нет взаимодействия, поэтому для анализа нам нужно посмотреть только один столбец [IMG], скалярные (внутренние) произведения которого , со строками [E], становятся одним столбцом результата [K]. Расширяя эту идею до одного столбца матрицы 3x3 с элементами x, y и z:

[Kx]   [x]   [1    1+d  1+d]
[Ky] = [y] # [1-d  1    1+d]
[Kz] = [z]   [1-d  1-d  1  ]

Рассмотрим конкретно элемент Ky выше (один элемент [K], соответствующий y в [IMG], разбитый на формулу, использующую только скаляры):

Ky = x * (1-d)  +  y * 1  + z * (1+d)

Обобщение для любого y в столбце любой длины:

Ky = sumx * (1-d)  +  y * 1  + sumz * (1+d)

Где скаляры sumx и sumz представляют собой суммы всех значений выше и ниже y соответственно в этом столбце [IMG]. Н.Б. sumx и sumz — это значения, относящиеся к элементу y.

Перестановка:

Ky = (sumx + y + sumz) - d * (sumx - sumz)

Ky = tot - d * (sumx - sumz)

куда

tot = (sumx + y + sumz) 

т. е. tot — это сумма всех значений в столбце (например, в IDL: tot = total(IMG,2)).

Так что до сих пор я в основном дублировал работу Диего; остальная часть этого анализа преобразует это последнее уравнение для Ky в форму, пригодную для быстрой оценки в IDL.

Решение уравнения tot для sumz:

sumz = tot - (y + sumx)

Замена обратно в Ky:

Ky = tot - (sumx - (tot - (y + sumx)))

Ky = tot - ((2 * sumx) + y - tot)

Ky = tot + (tot - ((2 * sumx) + y)

Использование sumxy для представления суммы всех значений в столбце сверху вниз до y включительно (IDL: [SUMXY] = total([IMG],2,/CUMULATIVE))

sumxy = sumx + y

а также

sumx = sumxy - y

Замена обратно в Ky:

Ky = tot + (tot - ((2 * (sumxy - y)) + y)

Ky = tot + (tot + y - (2 * sumxy))

Таким образом, если мы можем оценить tot и sumxy для каждого элемента [IMG], т.е. если мы можем оценить матрицы [TOT] и [SUMXY], и у нас уже есть [IMG] как матричная версия y, тогда это простая линейная комбинация этих матриц.

В IDL это просто:

[SUMXY] = TOTAL([IMG],2,/CUMULATIVE)

[TOT] = [SUMXY][*,N-1] # REPLICATE(1D0,1,N)

т.е. [TOT] — это последняя строка [SUMXY], продублированная для формирования матрицы из N строк.

И окончательный код выглядит так:

function lorri_ematrix_multiply,NxN,EMatrix

  NROWS = (size(NxN,/DIM))[1]

  SUMXY = TOTAL(NxN,2,/CUMULATIVE)

  TOT   = SUMXY[*,NROWS-1] # REPLICATE(1,NROWS,1d0)

  RETURN, TOT + ((EMatrix[1,0] - 1d0) * (TOT + NxN - (2d0 * SUMXY)))

end

который в нашей системе просто на порядок быстрее, чем [IMG]#[E].

Н.Б. дельта = (EMatrix[1,0] - 1d0)

Ву ху!


person Brian Carcich    schedule 20.07.2014    source источник
comment
Это не дополнения 2M, но и не 1G.   -  person Brian Carcich    schedule 21.07.2014
comment
Я только что запустил код на GDL и получил ускорение ~170. Может быть, GDL хуже наивного?   -  person Brian Carcich    schedule 21.07.2014


Ответы (1)


Шаг 1:

Я сразу перехожу к математической записи, так как думаю, что это может быть понятнее, чем объяснение словами:

      [ +1 +d +d +d +d ]   [ 1 0 0 0 0 ]       [ 0 1 1 1 1 ]        [ 0 0 0 0 0 ]
      [ -d +1 +d +d +d ]   [ 0 1 0 0 0 ]       [ 0 0 1 1 1 ]        [ 1 0 0 0 0 ]
[E] = [ -d -d +1 +d +d ] = [ 0 0 1 0 0 ] + d * [ 0 0 0 1 1 ] - d *  [ 1 1 0 0 0 ]
      [ -d -d -d +1 +d ] | [ 0 0 0 1 0 ]       [ 0 0 0 0 1 ]        [ 1 1 1 0 0 ]
      [ -d -d -d -d +1 ] | [ 0 0 0 0 1 ]       [ 0 0 0 0 0 ]        [ 1 1 1 1 0 ]
                         |
                         | [ 1 0 0 0 0 ]       [ 1 1 1 1 1 ]        [ 1 0 0 0 0 ]
                         | [ 0 1 0 0 0 ]       [ 0 1 1 1 1 ]        [ 1 1 0 0 0 ]
                         = [ 0 0 1 0 0 ] + d * [ 0 0 1 1 1 ] - d *  [ 1 1 1 0 0 ]
                         | [ 0 0 0 1 0 ]       [ 0 0 0 1 1 ]        [ 1 1 1 1 0 ]
                         | [ 0 0 0 0 1 ]       [ 0 0 0 0 1 ]        [ 1 1 1 1 1 ]
                         |     [ID]                [UT]                 [LT]
                         |
                         = [ID] + d * [UT] - d * [LT]

==>

[Img] # [E] = [E]##[Img] = [Img] + d * [UT] ## [Img] - d * [LT] ## [Img]

Теперь давайте посмотрим, что такое [LT] ## [Img]:

  • строка 1 совпадает с первой строкой [Img]
  • строка 2 представляет собой (по столбцам) сумму строк 1 и 2 [Img]
  • строка i - это (по столбцам) сумма всех первых i строк [Img]
  • row n — это (по столбцам) сумма всех строк [Img]

поэтому эффективный способ вычислить это:

TOTAL(Image, 2, /CUMULATIVE)

Аналогичным, но немного другим является результат [UT] ## [Img]:

  • строка 1 представляет собой (по столбцам) сумму всех строк [Img]
  • строка i - это (по столбцам) сумма всех последних i строк [Img]
  • строка n-1 представляет собой (по столбцам) сумму строк 1 и 2 в [Img]
  • строка n совпадает с первой строкой [Img]

so [UT] ## [Img] = REVERSE(TOTAL(REVERSE(Image,2), 2, /CUMULATIVE),2)

Потом:

[Img] # [E] = [E]##[Img] = Image + d * (REVERSE(TOTAL(REVERSE(Image,2), 2, /CUMULATIVE),2) - TOTAL(Image, 2, /CUMULATIVE))

Обратите внимание, что мы видим, что в итоге результаты по каждому столбцу поступают только из данных того же столбца.


Шаг 2:

Теперь давайте посмотрим на [K] = [UT] ## [Img] - [LT] ## [Img] и посмотрим, как он выглядит. Если для каждого общего столбца мы назовем элементы столбца r(1), r(2), r(3), ...., r(i), ..., r(n), мы увидим, что соответствующие [ K] элементов столбца R(i) выглядит следующим образом:

когда n четно (в случае 1024)

row 1     => R(1) = +r(1)                 -r(1) -r(2) .... -r(n-1) -r(n) = -SUM(j=2, n, r(n))
row 2     => R(1) = +r(1) +r(2)           -r(1) -r(2) .... -r(n-1)       = -SUM(j=3, n-1, r(n))
row 3     => R(1) = +r(1) +r(2) +r(3)     -r(1) -r(2) .... -r(n-2)       = -SUM(j=4, n-2, r(n))
    :        :       :     :     :    :    :     :     :    :               :
row i (i < n/2) 
          => R(1) = +r(1) ... +r(i)       -r(1) -r(2) .... -r(n-i+1)     = -SUM(j=i+1, n-i+1, r(n))
    :        :       :     :     :    :    :     :     :    :               :
row n/2   => R(1) = +r(1) ... +r(n/2)     -r(1) -r(2) .... -r(n/2+1)     = -r(n/2+1) 
row n/2+1 => R(1) = +r(1) ... +r(n/2+1)   -r(1) -r(2) .... -r(n/2)       = +r(n/2+1) 
    :        :       :     :     :    :    :     :     :    :               :
row i (i > n/2)
          => R(1) = +r(1) ... + r(i)      -r(1) -r(2) .... -r(n-i+1)     = +SUM(j=n-i+2, i, r(n))
                                                                         = -R(n-i+1)
    :        :       :     :     :    :    :     :     :    :               :
row n     => R(1) = +r(1) ... + r(n)      -r(1)                          = +SUM(j=2, n, r(n))
                                                                         = -R(1)

когда n нечетно. То же самое, но R((n+1)/2) будет равно 0. Я не буду вдаваться в подробности.

Важно то, что матрица [K] = [UT] ## [Img] - [LT] ## [Img] антисимметрична относительно своей горизонтальной линии деления пополам.

Это означает, что мы могли вычислить значения только в половинной матрице (скажем, в верхней части), а затем заполнить нижнюю часть путем зеркального отображения и смены знака. Обратите внимание, что эффективное вычисление верхней части можно выполнить, начиная с R(n/2) = r(n/2+1) и поднимаясь вверх, каждый раз уменьшая индекс (R(n/2 -1), R(n/2 -2), R(n/2 -3)...), используя R(i) = R(i+1) - r(i+1) - r(n-i+1), который можно переписать как R(i-1) = R(i) - r(i) - r(n-i+2).

Что касается вычислений, это примерно вдвое меньше выполненных вычислений, но с точки зрения фактической скорости его необходимо протестировать, чтобы увидеть, является ли реализация с явными операциями такой же быстрой, как внутренние реализации встроенных функций, таких как TOTAL(/CUMULATIVE) и подобные. Есть хорошие шансы, что это быстрее, так как здесь мы можем избежать также TRANSPOSE и/или REVERSE.

Дайте нам знать, как это происходит с небольшим профилированием!

person Diego Mazzaro    schedule 22.07.2014
comment
Diego, Спасибо за это, но я ищу самый быстрый код, и я не вижу, насколько ваш подход быстрее того, что у меня уже есть. Reverse(total(reverse())) будет довольно дорогим по времени. также total(,2) примерно в три раза дороже по времени, чем total(,1), поэтому transpose(total(transpose(),1)) может быть быстрее. Хм, если я сделаю обратное с массивом поиска, я могу сэкономить несколько циклов. Спасибо. - person Brian Carcich; 24.07.2014
comment
Кроме того, вам не хватает знака минус в окончательной формуле? - person Brian Carcich; 24.07.2014
comment
Дасс... минус... .Исправляю! ..также у вас здесь удалено одно (1M) умножение d * (‹REVTOTCUM› - ‹TOTCUM›) вместо d * ‹REVTOTCUM› - d * ‹TOTCUM›. Я полностью согласен работать с поиском.. ..или, может быть, лучше перенести Img и использовать TOTAL(‹*›,1). Однако я больше думаю... ..теперь я пытаюсь закончить думать и улучшить ответ! - person Diego Mazzaro; 24.07.2014
comment
Я отмечаю это как правильный ответ, потому что он содержит подход и обозначения, которые сняли пелену с моих глаз и привели меня к окончательному коду, который я поместил в постановку задачи, а также теперь он находится в конвейере обработки данных для New Horizons. данные. Плутон, вот и мы, спасибо Диего! - person Brian Carcich; 18.09.2014