Я изо всех сил пытаюсь понять, как именно einsum
работает. Я посмотрел на документацию и несколько примеров, но это не похоже на прилипание.
Вот пример, который мы рассмотрели в классе:
C = np.einsum("ij,jk->ki", A, B)
для двух массивов A
иB
Я думаю, что это заняло бы A^T * B
, но я не уверен (это принимает транспонирование одного из них правильно?). Может кто-нибудь рассказать мне, что именно здесь происходит (и вообще при использовании einsum
)?
python
arrays
numpy
multidimensional-array
numpy-einsum
Ланс пролив
источник
источник
(A * B)^T
, или эквивалентноB^T * A^T
.einsum
здесь . (Я с радостью перенесу наиболее важные фрагменты в ответ по переполнению стека, если это будет полезно).numpy
Документация неадекватна при объяснении деталей.*
это не матричное умножение, а поэлементное умножение. Осторожно!Ответы:
(Примечание: этот ответ основан на кратком сообщении в блоге о том, что
einsum
я написал некоторое время назад.)Что делает
einsum
?Представьте, что у нас есть два многомерных массива,
A
иB
. Теперь давайте предположим, что мы хотим ...A
сB
определенным образом, чтобы создать новый массив продуктов; а потом возможноТам хороший шанс , что
einsum
поможет нам сделать это быстрее и больше памяти, эффективно , что комбинации функций NumPy нравитсяmultiply
,sum
иtranspose
позволит.Как
einsum
работает?Вот простой (но не совсем тривиальный) пример. Возьмите следующие два массива:
Мы умножим
A
иB
поэлементно, а затем суммируем по строкам нового массива. В «нормальном» NumPy мы написали бы:Итак, здесь операция индексации
A
выстраивает в линию первые оси двух массивов, чтобы умножение можно было транслировать. Строки массива продуктов затем суммируются, чтобы вернуть ответ.Теперь, если мы хотим использовать
einsum
вместо этого, мы могли бы написать:Подписи строка
'i,ij->i'
является ключом здесь и нуждается в немного объяснить. Вы можете думать об этом в две половины. С левой стороны (слева от->
) мы пометили два входных массива. Справа от->
мы пометили массив, который мы хотим получить.Вот что происходит дальше:
A
имеет одну ось; мы пометили этоi
. ИB
имеет две оси; мы пометили ось 0 какi
и ось 1 какj
.При повторив метку
i
в обеих входных массивах, мы говорим ,einsum
что эти две оси должны быть умножены вместе. Другими словами, мы умножаем массивA
на каждый столбец массиваB
, как этоA[:, np.newaxis] * B
делает.Обратите внимание, что
j
в нашем желаемом выводе нет метки; мы только что использовалиi
(мы хотим получить массив 1D). По опуская этикетку, мы говорим ,einsum
чтобы подвести вдоль этой оси. Другими словами, мы суммируем ряды продуктов, точно так же, как это.sum(axis=1)
делает.Это в основном все, что вам нужно знать, чтобы использовать
einsum
. Это помогает немного поиграть; если мы оставим обе метки в выводе,'i,ij->ij'
мы вернемся к двумерному массиву продуктов (так же, какA[:, np.newaxis] * B
). Если мы говорим, что нет выходных меток,'i,ij->
мы возвращаем одно число (то же самое, что и делать(A[:, np.newaxis] * B).sum()
).Однако самое интересное в том
einsum
, что сначала не создается временный массив продуктов; это просто суммирует продукты, как это идет. Это может привести к большой экономии в использовании памяти.Немного больший пример
Чтобы объяснить скалярное произведение, вот два новых массива:
Мы вычислим скалярное произведение, используя
np.einsum('ij,jk->ik', A, B)
. Вот рисунок, показывающий маркировкуA
иB
и выходного массива, который мы получаем из функции:Вы можете видеть, что метка
j
повторяется - это означает, что мы умножаем строкиA
на столбцыB
. Кроме того, меткаj
не включена в выходные данные - мы суммируем эти продукты. Меткиi
иk
сохраняются для вывода, поэтому мы получаем двумерный массив.Это может быть еще более ясным , чтобы сравнить этот результат с массивом , где метка
j
находится не подводились. Ниже, слева вы можете увидеть трехмерный массив, полученный в результате записиnp.einsum('ij,jk->ijk', A, B)
(т.е. мы сохранили меткуj
):Суммирующая ось
j
дает ожидаемое произведение точек, показанное справа.Некоторые упражнения
Чтобы получить больше ощущений
einsum
, может быть полезно реализовать знакомые операции с массивами NumPy, используя нижнюю запись. Все, что включает в себя комбинации умножения и суммирования осей, может быть написано с использованиемeinsum
.Пусть A и B - два одномерных массива одинаковой длины. Например,
A = np.arange(10)
иB = np.arange(5, 15)
.Сумма
A
может быть записана:Поэлементное умножение
A * B
, можно записать так:Внутренний продукт или точечный продукт,
np.inner(A, B)
илиnp.dot(A, B)
, может быть записан:На внешнем произведении
np.outer(A, B)
можно записать:Для двумерных массивов
C
иD
при условии, что оси имеют совместимые длины (обе имеют одинаковую длину или одну из них имеет длину 1), вот несколько примеров:След
C
(сумма главной диагонали)np.trace(C)
можно записать так:Поэлементное умножение
C
и транспонированныеD
,C * D.T
можно записать:Умножая каждый элемент
C
на массивD
(чтобы сделать массив 4D)C[:, :, None, None] * D
, можно записать:источник
ij,jk
мог бы работать сам (без стрелок) для формирования матрицы умножения. Но, похоже, для наглядности лучше поставить стрелки, а затем и выходные размеры. Это в блоге.A
имеет длину 3, такую же, как длина столбцов вB
(тогда как строкиB
имеют длину 4 и не могут быть умножены на элементA
).->
семантики влияет: «В неявном режиме выбранные индексы важны, так как оси вывода переупорядочены в алфавитном порядке. Это означает, чтоnp.einsum('ij', a)
не влияет на двумерный массив, аnp.einsum('ji', a)
занимает его транспонирование».Понять идею
numpy.einsum()
очень легко, если вы понимаете ее интуитивно. В качестве примера давайте начнем с простого описания, включающего умножение матриц .Чтобы использовать
numpy.einsum()
, все, что вам нужно сделать, это передать так называемую строку индексов в качестве аргумента, а затем ваши входные массивы .Скажем , у вас есть два 2D массивов,
A
иB
, и вы хотите сделать матричное умножение. Итак, вы делаете:Здесь нижняя строка
ij
соответствует массиву,A
а нижняя строкаjk
соответствует массивуB
. Кроме того, самое важное, что следует отметить, это то, что количество символов в каждой строке индекса должно соответствовать размерам массива. (т.е. два символа для 2D-массивов, три символа для 3D-массивов и т. д.) И если вы повторяете символы между строками индекса (j
в нашем случае), то это означает, что вы хотите, чтобыein
сумма происходила по этим измерениям. Таким образом, они будут уменьшены. (то есть это измерение исчезнет )Подстрочный строка после этого
->
, будет наш результирующий массив. Если вы оставите это поле пустым, все будет суммировано, и в качестве результата будет возвращено скалярное значение. В противном случае результирующий массив будет иметь размеры в соответствии со строкой индекса . В нашем примере это будетik
. Это интуитивно понятно, потому что мы знаем, что для умножения матрицы количество столбцов в массивеA
должно соответствовать количеству строк в массиве,B
что и происходит здесь (т.е. мы кодируем это знание, повторяя символj
в строке индекса )Вот еще несколько примеров, иллюстрирующих использование / мощь
np.einsum()
в реализации некоторых общих тензорных или nd-массивных операций, кратко.входные
1) Матричное умножение (аналогично
np.matmul(arr1, arr2)
)2) Извлечь элементы по главной диагонали (аналогично
np.diag(arr)
)3) произведение Адамара (т.е. поэлементное произведение двух массивов) (аналогично
arr1 * arr2
)4) Поэлементное возведение в квадрат (аналогично
np.square(arr)
илиarr ** 2
)5) Трассировка (т.е. сумма элементов главной диагонали) (аналогично
np.trace(arr)
)6) Матрица транспонировать (аналогично
np.transpose(arr)
)7) Наружное произведение (векторов) (аналогично
np.outer(vec1, vec2)
)8) Внутренний продукт (векторов) (аналогично
np.inner(vec1, vec2)
)9) Сумма по оси 0 (аналогично
np.sum(arr, axis=0)
)10) Сумма по оси 1 (аналогично
np.sum(arr, axis=1)
)11) Пакетное умножение матриц
12) Сумма по оси 2 (аналогично
np.sum(arr, axis=2)
)13) Суммируйте все элементы в массиве (аналогично
np.sum(arr)
)14) Сумма по нескольким осям (т.е. маргинализация)
(аналогично
np.sum(arr, axis=(axis0, axis1, axis2, axis3, axis4, axis6, axis7))
)15) Продукты Double Dot (аналогично np.sum (hadamard-product), см. 3 )
16) 2D и 3D умножение массива
Такое умножение может быть очень полезно при решении линейной системы уравнений ( Ax = b ), где вы хотите проверить результат.
Напротив, если нужно использовать
np.matmul()
для этой проверки, мы должны сделать паруreshape
операций для достижения того же результата, как:Бонус : Читайте больше математики здесь: Суммирование Эйнштейна и определенно здесь: Тензорная запись
источник
Давайте сделаем 2 массива с разными, но совместимыми размерами, чтобы подчеркнуть их взаимодействие
Ваш расчет принимает «точку» (сумму произведений) от (2,3) с (3,4) для получения массива (4,2).
i
1-й тусклыйA
, последний изC
;k
последний изB
1-гоC
.j
«потребляется» суммированием.Это так же, как
np.dot(A,B).T
- это конечный результат, который транспонирован.Чтобы узнать больше о том, что происходит
j
, изменитеC
подписки наijk
:Это также может быть произведено с:
То есть добавить
k
измерение в конецA
иi
передB
, чтобы получить массив (2,3,4).0 + 4 + 16 = 20
и9 + 28 + 55 = 92
т. д .; Суммируйтеj
и перенесите, чтобы получить более ранний результат:источник
Я нашел NumPy: уловки торговли (Часть II) поучительными
Обратите внимание, что есть три оси, i, j, k, и что j повторяется (с левой стороны).
i,j
представлять строки и столбцы дляa
.j,k
дляb
.Чтобы рассчитать произведение и выровнять
j
ось, нам нужно добавить осьa
. (b
будет транслироваться вдоль (?) первой оси)j
отсутствует в правой части, поэтому мы суммируем поj
второй оси массива 3x3x3Наконец, индексы (в алфавитном порядке) обращены справа, поэтому мы транспонируем.
источник
Читая уравнения Эйнсума, я обнаружил, что наиболее полезно просто уметь мысленно сводить их к их императивным версиям.
Начнем со следующего (внушительного) утверждения:
Прорабатывая пунктуацию сначала, мы видим, что у нас есть два 4-буквенных двоеточия, разделенных запятыми -
bhwi
иbhwj
, перед стрелкой, и один 3-буквенный шарикbij
после нее. Следовательно, уравнение дает тензорный результат ранга 3 из двух тензорных входов ранга 4.Теперь пусть каждая буква в каждом двоичном объекте будет именем переменной диапазона. Позиция, в которой буква появляется в BLOB-объекте, является индексом оси, в которой она находится в этом тензоре. Следовательно, императивное суммирование, которое производит каждый элемент C, должно начинаться с трех вложенных циклов for, по одному для каждого индекса C.
Итак, по сути, у вас есть
for
цикл для каждого выходного индекса C. Пока мы оставим диапазоны неопределенными.Далее мы посмотрим на левую сторону - есть ли какие-то переменные диапазона, которые не появляются с правой стороны? В нашем случае - да,
h
иw
. Добавьте внутренний вложенныйfor
цикл для каждой такой переменной:Внутри самого внутреннего цикла у нас теперь определены все индексы, поэтому мы можем записать фактическое суммирование и перевод завершен:
Если вы уже смогли следовать коду, то поздравляю! Это все, что вам нужно, чтобы уметь читать уравнения Эйнсума. В частности, обратите внимание на то, как исходная формула einsum отображается в окончательный оператор суммирования в приведенном выше фрагменте. Циклы for и границы диапазона - просто пух, и это последнее утверждение - все, что вам действительно нужно, чтобы понять, что происходит.
Для полноты картины давайте посмотрим, как определить диапазоны для каждой переменной диапазона. Ну, диапазон каждой переменной - это просто длина измерения (й), которое она индексирует. Очевидно, что если переменная индексирует более одного измерения в одном или нескольких тензорах, то длины каждого из этих измерений должны быть равны. Вот код выше с полными диапазонами:
источник
Я думаю, что самый простой пример в документах tenorflow
Есть четыре шага, чтобы преобразовать ваше уравнение в систему обозначений Einsum. Возьмем это уравнение в качестве примера.
C[i,k] = sum_j A[i,j] * B[j,k]
ik = sum_j ij * jk
sum_j
термин, поскольку он неявный. Мы получилиik = ij * jk
*
на,
. Мы получилиik = ij, jk
->
знаком. Мы получилиij, jk -> ik
Интерпретатор einsum просто выполняет эти 4 шага в обратном порядке. Все отсутствующие в результате индексы суммируются.
Вот еще несколько примеров из документации
источник