Понимание einsum включает в
Я изо всех сил пытаюсь понять, как именно einsum строительство. Я просмотрел документацию и несколько примеров, но это, похоже, не прилипает.
вот пример, который мы рассмотрели в классе:
C = np.einsum("ij,jk->ki", A, B)
для двух массивовA и B
Я думаю, что это займет A^T * B, но я не уверен (это транспонирование от одной из них?). Может ли кто-нибудь провести меня через то, что здесь происходит (и вообще при использовании einsum)?
4 ответов:
(Примечание: этот ответ основан на коротком блоге о
einsumя писал некоторое время назад.)что значит
einsumделать?представьте, что у нас есть два многомерных массива,
AиB. Теперь давайте предположим, что мы хотим...
- умножение
AСBопределенным образом создать новый массив продуктов; а затем, возможно,- sum этот новый массив вдоль конкретные топоры; и тогда, возможно,
- транспонировать оси нового массива в определенном порядке.
есть хороший шанс, что
einsumпоможет нам сделать это быстрее и эффективнее памяти, что комбинации функций NumPy, какmultiply,sumиtransposeпозволит.как
einsumработы?вот простой (но не совсем тривиальный) пример. Возьмите следующие два массивы:
A = np.array([0, 1, 2]) B = np.array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])мы умножим
AиBпо элементам, а затем суммировать по строкам нового массива. В "нормальном" NumPy мы бы написали:>>> (A[:, np.newaxis] * B).sum(axis=1) array([ 0, 22, 76])так вот, операция индексирования на
Aвыравнивает первые оси двух массивов так,что умножение может быть передано. Затем строки массива продуктов суммируются, чтобы вернуть ответ.теперь, если мы хотим использовать
einsumвместо этого мы могли бы напишите:>>> np.einsum('i,ij->i', A, B) array([ 0, 22, 76])The подпись строка
'i,ij->i'является ключевым здесь и нуждается в небольшом объяснении. Вы можете думать об этом в двух половинах. С левой стороны (слева от->) мы обозначили два входных массива. Справа от->, мы обозначили массив, который мы хотим в конечном итоге.вот что происходит дальше:
Aимеет одну ось; мы назвалиi. ИBимеет две оси; мы обозначили ось 0 какiи ось 1 Какj.By повторять метка
iв обоих входных массивов, мы говоримeinsumчто эти две оси должны быть умножил вместе. Другими словами, мы умножаем массивAС каждым столбцом массиваB, какA[:, np.newaxis] * Bделает.обратите внимание, что
jне отображается как метка в нашем желаемом выходе; мы имеем просто использоватьi(мы хотим в конечном итоге с 1D массивом). На опущение этикетка, мы говоримeinsumдо sum вдоль этой оси. Другими словами, мы суммируем строки продуктов, так же, как.sum(axis=1)делает.это в основном все, что вам нужно знать, чтобы использовать
einsum. Это помогает немного поиграть; если мы оставим обе метки на выходе,'i,ij->ij', мы получаем обратно 2D массив продуктов (так же, какA[:, np.newaxis] * B). Если мы скажем нет выходные метки,'i,ij->, мы возвращаем один номер (то же самое, что и(A[:, np.newaxis] * B).sum()).великая вещь о
einsumоднако, это не создает временный массив продуктов в первую очередь; он просто суммирует продукты, как это происходит. Это может привести к большой экономии в использовании памяти.немного больше, например
чтобы объяснить точечный продукт, вот два новых массива:
A = array([[1, 1, 1], [2, 2, 2], [5, 5, 5]]) B = array([[0, 1, 0], [1, 1, 0], [1, 1, 1]])мы будем вычислять точечный продукт с помощью
np.einsum('ij,jk->ik', A, B). Вот такая картинка показывая маркировкуAиBи выходной массив, который мы получаем из функции:вы можете видеть эту метку
jповторяется-это означает, что мы умножаем строкиAС колонкиB. Кроме того, этикеткаjне входит в выход-мы суммируем эти продукты. Этикеткиiиkхранятся для вывода, поэтому мы получаем обратно 2D матрица.это может быть еще яснее, чтобы сравнить этот результат с массивом, где метка
jи не подведены. Ниже, слева вы можете увидеть 3D массив, который является результатом записиnp.einsum('ij,jk->ijk', A, B)(т. е. мы сохранили меткуj):подводя оси
jдает ожидаемый точечный продукт, показанный справа.упражнения
чтобы получить больше чувства для
einsum, это может быть полезно реализовать знакомые операции массива NumPy с использованием нотации подстрочного индекса. Все, что включает в себя комбинации умножения и суммирования осей можно записать с помощьюeinsum.пусть A и B-два 1D массива одинаковой длины. Например,
A = np.arange(10)иB = np.arange(5, 15).
в сумме
Aможно написать:np.einsum('i->', A)поэлементное умножение,
A * B, можно записать:np.einsum('i,i->i', A, B)внутренний продукт или точечный продукт,
np.inner(A, B)илиnp.dot(A, B), можно написать:np.einsum('i,i->', A, B) # or just use 'i,i'внешний продукт,
np.outer(A, B), можно написать:np.einsum('i,j->ij', A, B)для 2D массивов,
CиD, при условии, что оси являются совместимыми длинами (обе одинаковой длины или одна из них имеет длину 1), Вот несколько примеров:
след
C(сумма главной диагонали),np.trace(C), можно написать:np.einsum('ii', C)поэлементное умножение
Cи транспонированиеD,C * D.T, можно написать:np.einsum('ij,ji->ij', C, D)умножение каждого элемента
Cв массивеD(чтобы сделать массив 4D),C[:, :, None, None] * D, можно написать:np.einsum('ij,kl->ijkl', C, D)
ухватившись за идею
numpy.einsum()очень легко, если вы это интуитивно понимаете. Вот простое описание с матричным умножением в качестве примера.
использовать
numpy.einsum(), вы должны пройти так называемый строка подстрочные в качестве аргумента, за которым следуют входные массивы.допустим, у вас есть два 2D массива,
AиB, и вы хотите сделать умножение матрицы. Итак, вы делаете:np.einsum("ij, jk -> ik", A, B)здесь индекс строки
ijсоответствует массивAв то время как индекс строкиjkсоответствует массивB. Кроме того, самое главное отметить, что количество символов в каждом элементе индекс строки должны соответствует размерам массива. (т. е. два символа для 2D-массивов, три символа для 3D-массивов и т. д.) И если вы повторяете символы между индекс строки (jв нашем случае), то это означает, что вы хотитеeinsum произойдет вдоль этих измерений. Таким образом, они будут суммированы.The индекс строки после этого
->, будет наш результирующий массив. Если вы оставьте его пустым, тогда все будет суммировано и скаляр будет возвращен в результате. Иначе результирующий массив будет иметь размеры согласно индекс строки. В нашем примере, это будетik. Это интуитивно понятно, потому что мы знаем, что для умножения матрицы количество столбцов в массивеAдолжно соответствовать количеству строк в массивеBчто здесь происходит (т. е. мы кодируем этого знания по повторяя символjна индекс строки)
вот несколько примеров, иллюстрирующих использование
np.einsum()в реализации некоторых общих тензора или nd-array операции.входы
In [197]: vec Out[197]: array([0, 1, 2, 3]) In [198]: A Out[198]: array([[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]]) In [199]: B Out[199]: array([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]])1) умножение матриц (аналогично
np.matmul(arr1, arr2))In [200]: np.einsum("ij, jk -> ik", A, B) Out[200]: array([[130, 130, 130, 130], [230, 230, 230, 230], [330, 330, 330, 330], [430, 430, 430, 430]])2) извлечение элементов по главной диагонали (аналог
np.diag(arr))In [202]: np.einsum("ii -> i", A) Out[202]: array([11, 22, 33, 44])3) продукт Адамара (т. е. поэлементное произведение двух массивов) (аналог
arr1 * arr2)In [203]: np.einsum("ij, ij -> ij", A, B) Out[203]: array([[ 11, 12, 13, 14], [ 42, 44, 46, 48], [ 93, 96, 99, 102], [164, 168, 172, 176]])4) поэлементный квадрат (аналог
np.square(arr)илиarr ** 2)In [210]: np.einsum("ij, ij -> ij", B, B) Out[210]: array([[ 1, 1, 1, 1], [ 4, 4, 4, 4], [ 9, 9, 9, 9], [16, 16, 16, 16]])5) Трассировка (т. е. сумма главных диагональных элементов) (аналог
np.trace(arr))In [217]: np.einsum("ii -> ", A) Out[217]: 1106) транспонировать матрицу (аналог
np.transpose(arr))In [221]: np.einsum("ij -> ji", A) Out[221]: array([[11, 21, 31, 41], [12, 22, 32, 42], [13, 23, 33, 43], [14, 24, 34, 44]])7) внешнее произведение (векторов) (аналог
np.outer(vec1, vec2))In [255]: np.einsum("i, j -> ij", vec, vec) Out[255]: array([[0, 0, 0, 0], [0, 1, 2, 3], [0, 2, 4, 6], [0, 3, 6, 9]])8) внутреннее произведение (векторов) (аналог
np.inner(vec1, vec2))In [256]: np.einsum("i, i -> ", vec, vec) Out[256]: 149) сумма вдоль оси 0 (аналог
np.sum(arr, axis=0))In [260]: np.einsum("ij -> j", B) Out[260]: array([10, 10, 10, 10])10) сумма вдоль оси 1 (аналог
np.sum(arr, axis=1))In [261]: np.einsum("ij -> i", B) Out[261]: array([ 4, 8, 12, 16])11) Умножение Матрицы Серии
In [287]: BM = np.stack((A, B), axis=0) In [288]: BM Out[288]: array([[[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]], [[ 1, 1, 1, 1], [ 2, 2, 2, 2], [ 3, 3, 3, 3], [ 4, 4, 4, 4]]]) In [289]: BM.shape Out[289]: (2, 4, 4) # batch matrix multiply using einsum In [292]: BMM = np.einsum("bij, bjk -> bik", BM, BM) In [293]: BMM Out[293]: array([[[1350, 1400, 1450, 1500], [2390, 2480, 2570, 2660], [3430, 3560, 3690, 3820], [4470, 4640, 4810, 4980]], [[ 10, 10, 10, 10], [ 20, 20, 20, 20], [ 30, 30, 30, 30], [ 40, 40, 40, 40]]]) In [294]: BMM.shape Out[294]: (2, 4, 4)
12) Сумма вдоль оси 2 (аналог
np.sum(arr, axis=2))In [330]: np.einsum("ijk -> ij", BM) Out[330]: array([[ 50, 90, 130, 170], [ 4, 8, 12, 16]])13) суммируйте все элементы в массиве (аналогично
np.sum(arr))In [335]: np.einsum("ijk -> ", BM) Out[335]: 48014) сумма по нескольким осям (т. е. маргинализация)
(похоже наnp.sum(arr, axis=(axis0, axis1, axis2, axis3, axis4, axis6, axis7)))# 8D array In [354]: R = np.random.standard_normal((3,5,4,6,8,2,7,9)) # marginalize out axis 5 (i.e. "n" here) In [363]: esum = np.einsum("ijklmnop -> n", R) # marginalize out axis 5 (i.e. sum over rest of the axes) In [364]: nsum = np.sum(R, axis=(0,1,2,3,4,6,7)) In [365]: np.allclose(esum, nsum) Out[365]: True15) Двойные Продукты Точки (аналог np.сумма (Адамар-продукт) cf. 3)
In [772]: A Out[772]: array([[1, 2, 3], [4, 2, 2], [2, 3, 4]]) In [773]: B Out[773]: array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) In [774]: np.einsum("ij, ij -> ", A, B) Out[774]: 12416) 2D и 3D умножение массива
такой умножение может быть очень полезно при решении линейной системы уравнений ( Ax = b), где вы хотите, чтобы проверить результат.
# inputs In [115]: A = np.random.rand(3,3) In [116]: b = np.random.rand(3, 4, 5) # solve for x In [117]: x = np.linalg.solve(A, b.reshape(b.shape[0], -1)).reshape(b.shape) # 2D and 3D array multiplication :) In [118]: Ax = np.einsum('ij, jkl', A, x) # indeed the same! In [119]: np.allclose(Ax, b) Out[119]: Trueнаоборот, если нужно использовать
np.matmul()для этой проверки, мы должны сделать паруreshapes для достижения этого, как:# reshape 3D array `x` to 2D, perform matmul # then reshape the resultant array to 3D In [123]: Ax_matmul = np.matmul(A, x.reshape(x.shape[0], -1)).reshape(x.shape) # indeed correct! In [124]: np.allclose(Ax, Ax_matmul) Out[124]: Trueбонус: Подробнее математика здесь:Эйнштейн-Суммирование и здесь: Тензорной Нотации
позволяет сделать 2 массива, с различными, но совместимыми размерами, чтобы выделить их взаимодействие
In [43]: A=np.arange(6).reshape(2,3) Out[43]: array([[0, 1, 2], [3, 4, 5]]) In [44]: B=np.arange(12).reshape(3,4) Out[44]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])ваш расчет, принимает "точка" (сумма произведений) из (2,3) с (3,4) для получения (4,2) массива.
iэто 1-й тусклый изA, последнийC;kпоследниеB, 1-йC.j"потребляется" суммированием.In [45]: C=np.einsum('ij,jk->ki',A,B) Out[45]: array([[20, 56], [23, 68], [26, 80], [29, 92]])это то же самое, что
np.dot(A,B).T- это конечный результат, который транспонированный.чтобы увидеть больше того, что происходит с
jизменитьCиндексыijk:In [46]: np.einsum('ij,jk->ijk',A,B) Out[46]: array([[[ 0, 0, 0, 0], [ 4, 5, 6, 7], [16, 18, 20, 22]], [[ 0, 3, 6, 9], [16, 20, 24, 28], [40, 45, 50, 55]]])это также может быть произведено с:
A[:,:,None]*B[None,:,:]то есть добавить
kизмерение до концаAиiпередB, что приводит к массиву (2,3,4).
0 + 4 + 16 = 20,9 + 28 + 55 = 92, etc; Sum onjи транспонировать, чтобы получить более ранний результат:np.sum(A[:,:,None] * B[None,:,:], axis=1).T # C[k,i] = sum(j) A[i,j (,k) ] * B[(i,) j,k]
нашел NumPy: трюки торговли (Часть II) поучи
мы используем -> для указания порядка выходного массива. Поэтому подумайте о "ij, i->j" как о левой стороне (LHS) и правой стороне (RHS). Любое повторение меток на LHS вычисляет элемент продукта, а затем суммирует. Изменяя метку на стороне RHS (output), мы можем определить ось, по которой мы хотим двигаться относительно входного массива, т. е. суммирование вдоль оси 0, 1 и так далее.
i,j представляют строки и столбцы дляimport numpy as np >>> a array([[1, 1, 1], [2, 2, 2], [3, 3, 3]]) >>> b array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) >>> d = np.einsum('ij, jk->ki', a, b)a.j,kнаb.для того, чтобы рассчитать продукт и выровнять
jось нам нужно добавить ось кa. (bбудет транслироваться по(?) первая ось)a[i, j, k] b[j, k] >>> c = a[:,:,np.newaxis] * b >>> c array([[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]], [[ 0, 2, 4], [ 6, 8, 10], [12, 14, 16]], [[ 0, 3, 6], [ 9, 12, 15], [18, 21, 24]]])
jотсутствует с правой стороны, поэтому мы суммируемjчто вторая ось массива 3x3x3>>> c = c.sum(1) >>> c array([[ 9, 12, 15], [18, 24, 30], [27, 36, 45]])наконец, индексы (в алфавитном порядке) перевернуты с правой стороны, поэтому мы транспонируем.
>>> c.T array([[ 9, 18, 27], [12, 24, 36], [15, 30, 45]]) >>> np.einsum('ij, jk->ki', a, b) array([[ 9, 18, 27], [12, 24, 36], [15, 30, 45]]) >>>


Comments