numpy.einsum#

numpy.einsum(индексы, *операнды, выход=None, dtype=None, порядок='K', приведение типов='safe', оптимизировать=False)[источник]#

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

Используя соглашение суммирования Эйнштейна, многие распространенные многомерные, линейные алгебраические операции с массивами могут быть представлены простым способом. В неявный mode einsum вычисляет эти значения.

В явный mode, einsum предоставляет дополнительную гибкость для вычисления других операций с массивами, которые могут не считаться классическими операциями суммирования Эйнштейна, путём отключения или принудительного суммирования по указанным индексным меткам.

См. примечания и примеры для уточнения.

Параметры:
индексыstr

Задаёт индексы для суммирования в виде разделённого запятыми списка меток индексов. Выполняется неявное (классическое суммирование Эйнштейна) вычисление, если явный индикатор ‘->’ не включён вместе с метками индексов точной формы вывода.

операндысписок array_like

Это массивы для операции.

выходndarray, необязательно

Если предоставлен, вычисление выполняется в этот массив.

dtype{data-type, None}, необязательный

Если указан, принудительно использует указанный тип данных для вычислений. Обратите внимание, что вам, возможно, также придется указать более либеральный приведение типов параметр для разрешения преобразований. По умолчанию None.

порядок{‘C’, ‘F’, ‘A’, ‘K’}, опционально

Управляет расположением памяти вывода. 'C' означает, что он должен быть C-смежным. 'F' означает, что он должен быть Fortran-смежным, 'A' означает, что он должен быть 'F', если все входы 'F', иначе 'C'. 'K' означает, что он должен быть как можно ближе к расположению входов, включая произвольно переставленные оси. По умолчанию 'K'.

приведение типов{‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}, опционально

Управляет тем, какие преобразования типов данных могут происходить. Установка этого значения в 'unsafe' не рекомендуется, так как это может негативно повлиять на накопления.

  • 'no' означает, что типы данных не должны преобразовываться вообще.

  • 'equiv' означает, что разрешены только изменения порядка байтов.

  • ‘safe’ означает, что разрешены только преобразования, которые могут сохранить значения.

  • 'same_kind' означает, что разрешены только безопасные преобразования или преобразования внутри одного типа, например, из float64 в float32.

  • ‘unsafe’ означает, что могут быть выполнены любые преобразования данных.

По умолчанию 'safe'.

оптимизировать{False, True, 'greedy', 'optimal'}, опционально

Определяет, должно ли происходить промежуточное оптимизация. Оптимизация не произойдет, если False, а True по умолчанию использует алгоритм 'жадный'. Также принимает явный список сокращений из np.einsum_path функция. См. np.einsum_path для получения дополнительных сведений. По умолчанию False.

Возвращает:
выводndarray

Вычисление на основе соглашения о суммировании Эйнштейна.

Смотрите также

einsum_path, dot, inner, outer, tensordot, linalg.multi_dot
einsum

Аналогичный подробный интерфейс предоставляется einops пакет для покрытия дополнительных операций: транспонирование, изменение формы/сглаживание, повторение/плитка, сжатие/расширение и редукции. The opt_einsum оптимизирует порядок свертки для выражений, подобных einsum, независимо от бэкенда.

Примечания

Соглашение суммирования Эйнштейна может использоваться для вычисления многих многомерных линейных алгебраических операций с массивами. einsum предоставляет краткий способ представления этих данных.

Неисчерпывающий список этих операций, которые могут быть вычислены с помощью einsum, показан ниже вместе с примерами:

  • След массива, numpy.trace.

  • Возвращает диагональ, numpy.diag.

  • Суммирования по осям массива, numpy.sum.

  • Транспонирования и перестановки, numpy.transpose.

  • Умножение матриц и скалярное произведение, numpy.matmul

    numpy.dot.

  • Векторные внутренние и внешние произведения, numpy.inner

    numpy.outer.

  • Трансляция, поэлементное и скалярное умножение,

    numpy.multiply.

  • Сжатия тензоров, numpy.tensordot.

  • Цепочка операций с массивами, в эффективном порядке вычислений,

    numpy.einsum_path.

Строка подстрочных индексов представляет собой разделённый запятыми список меток индексов, где каждая метка относится к измерению соответствующего операнда. Всякий раз, когда метка повторяется, она суммируется, поэтому np.einsum('i,i', a, b) эквивалентно np.inner(a,b). Если метка появляется только один раз, она не суммируется, поэтому np.einsum('i', a) создает представление a без изменений. Дальнейший пример np.einsum('ij,jk', a, b) описывает традиционное матричное умножение и эквивалентна np.matmul(a,b). Повторяющиеся метки индексов в одном операнде берут диагональ. Например, np.einsum('ii', a) эквивалентно np.trace(a).

В неявный режим(exception_class, ...) np.einsum('ij', a) не влияет на 2D массив, в то время как np.einsum('ji', a) берёт его транспонирование. Кроме того, np.einsum('ij,jk', a, b) возвращает матричное умножение, в то время как, np.einsum('ij,jh', a, b) возвращает транспонирование умножения, поскольку индекс 'h' предшествует индексу 'i'.

В явный режим вывод может быть напрямую управляем путем указания меток выходных индексов. Это требует идентификатора '->' и списка меток выходных индексов. Эта функция повышает гибкость, поскольку суммирование может быть отключено или принудительно включено при необходимости. Вызов np.einsum('i->', a) похож на np.sum(a) if a является одномерным массивом, и np.einsum('ii->i', a) похож на np.diag(a) if a является квадратным двумерным массивом. Разница в том, что einsum не позволяет вещание по умолчанию. Кроме того np.einsum('ij,jh->ih', a, b) явно задает порядок меток индексов вывода и поэтому возвращает матричное умножение, в отличие от примера выше в неявном режиме.

Чтобы включить и контролировать вещание, используйте многоточие. Стандартное вещание в стиле NumPy выполняется путем добавления многоточия слева от каждого члена, как np.einsum('...ii->...i', a). np.einsum('...i->...', a) похож на np.sum(a, axis=-1) для массива a любой формы. Чтобы взять след по первой и последней осям, можно сделать np.einsum('i...i', a), или чтобы выполнить матрично-матричное произведение с крайними левыми индексами вместо крайних правых, можно сделать np.einsum('ij...,jk...->ik...', a, b).

Когда есть только один операнд, оси не суммируются, и не предоставлен выходной параметр, возвращается представление в операнд вместо нового массива. Таким образом, взятие диагонали как np.einsum('ii->i', a) создает представление (изменено в версии 1.10.0).

einsum также предоставляет альтернативный способ указания подстрочных индексов и операндов как einsum(op0, sublist0, op1, sublist1, ..., [sublistout]). Если форма вывода не предоставлена в этом формате einsum будет вычисляться в неявном режиме, в противном случае будет выполняться явно. Примеры ниже имеют соответствующие einsum вызовы с двумя параметрическими методами.

Представления, возвращаемые einsum, теперь доступны для записи, когда входной массив доступен для записи. Например, np.einsum('ijk...->kji...', a) теперь будет иметь тот же эффект, что и np.swapaxes(a, 0, 2) и np.einsum('ii->i', a) вернет доступное для записи представление диагонали 2D-массива.

Добавлен optimize аргумент, который оптимизирует порядок свёртки выражения einsum. Для свёртки с тремя или более операндами это может значительно повысить вычислительную эффективность за счёт большего объёма памяти во время вычислений.

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

См. numpy.einsum_path для получения дополнительной информации.

Примеры

>>> a = np.arange(25).reshape(5,5)
>>> b = np.arange(5)
>>> c = np.arange(6).reshape(2,3)

След матрицы:

>>> np.einsum('ii', a)
60
>>> np.einsum(a, [0,0])
60
>>> np.trace(a)
60

Извлечь диагональ (требуется явная форма):

>>> np.einsum('ii->i', a)
array([ 0,  6, 12, 18, 24])
>>> np.einsum(a, [0,0], [0])
array([ 0,  6, 12, 18, 24])
>>> np.diag(a)
array([ 0,  6, 12, 18, 24])

Суммирование по оси (требует явной формы):

>>> np.einsum('ij->i', a)
array([ 10,  35,  60,  85, 110])
>>> np.einsum(a, [0,1], [0])
array([ 10,  35,  60,  85, 110])
>>> np.sum(a, axis=1)
array([ 10,  35,  60,  85, 110])

Для массивов более высокой размерности суммирование по одной оси можно выполнить с помощью многоточия:

>>> np.einsum('...j->...', a)
array([ 10,  35,  60,  85, 110])
>>> np.einsum(a, [Ellipsis,1], [Ellipsis])
array([ 10,  35,  60,  85, 110])

Вычислить транспонирование матрицы или переупорядочить любое количество осей:

>>> np.einsum('ji', c)
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> np.einsum('ij->ji', c)
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> np.einsum(c, [1,0])
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> np.transpose(c)
array([[0, 3],
       [1, 4],
       [2, 5]])

Векторные скалярные произведения:

>>> np.einsum('i,i', b, b)
30
>>> np.einsum(b, [0], b, [0])
30
>>> np.inner(b,b)
30

Умножение матрицы на вектор:

>>> np.einsum('ij,j', a, b)
array([ 30,  80, 130, 180, 230])
>>> np.einsum(a, [0,1], b, [1])
array([ 30,  80, 130, 180, 230])
>>> np.dot(a, b)
array([ 30,  80, 130, 180, 230])
>>> np.einsum('...j,j', a, b)
array([ 30,  80, 130, 180, 230])

Трансляция и скалярное умножение:

>>> np.einsum('..., ...', 3, c)
array([[ 0,  3,  6],
       [ 9, 12, 15]])
>>> np.einsum(',ij', 3, c)
array([[ 0,  3,  6],
       [ 9, 12, 15]])
>>> np.einsum(3, [Ellipsis], c, [Ellipsis])
array([[ 0,  3,  6],
       [ 9, 12, 15]])
>>> np.multiply(3, c)
array([[ 0,  3,  6],
       [ 9, 12, 15]])

Внешнее произведение векторов:

>>> np.einsum('i,j', np.arange(2)+1, b)
array([[0, 1, 2, 3, 4],
       [0, 2, 4, 6, 8]])
>>> np.einsum(np.arange(2)+1, [0], b, [1])
array([[0, 1, 2, 3, 4],
       [0, 2, 4, 6, 8]])
>>> np.outer(np.arange(2)+1, b)
array([[0, 1, 2, 3, 4],
       [0, 2, 4, 6, 8]])

Тензорное свертывание:

>>> a = np.arange(60.).reshape(3,4,5)
>>> b = np.arange(24.).reshape(4,3,2)
>>> np.einsum('ijk,jil->kl', a, b)
array([[4400., 4730.],
       [4532., 4874.],
       [4664., 5018.],
       [4796., 5162.],
       [4928., 5306.]])
>>> np.einsum(a, [0,1,2], b, [1,0,3], [2,3])
array([[4400., 4730.],
       [4532., 4874.],
       [4664., 5018.],
       [4796., 5162.],
       [4928., 5306.]])
>>> np.tensordot(a,b, axes=([1,0],[0,1]))
array([[4400., 4730.],
       [4532., 4874.],
       [4664., 5018.],
       [4796., 5162.],
       [4928., 5306.]])

Записываемые возвращаемые массивы (с версии 1.10.0):

>>> a = np.zeros((3, 3))
>>> np.einsum('ii->i', a)[:] = 1
>>> a
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

Пример использования многоточия:

>>> a = np.arange(6).reshape((3,2))
>>> b = np.arange(12).reshape((4,3))
>>> np.einsum('ki,jk->ij', a, b)
array([[10, 28, 46, 64],
       [13, 40, 67, 94]])
>>> np.einsum('ki,...k->i...', a, b)
array([[10, 28, 46, 64],
       [13, 40, 67, 94]])
>>> np.einsum('k...,jk', a, b)
array([[10, 28, 46, 64],
       [13, 40, 67, 94]])

Цепочка операций с массивами. Для более сложных сверток ускорение может быть достигнуто путем многократного вычисления 'жадного' пути или предварительного вычисления 'оптимального' пути и его многократного применения с использованием einsum_path вставка (с версии 1.12.0). Улучшения производительности могут быть особенно значительными с большими массивами:

>>> a = np.ones(64).reshape(2,4,8)

Базовый einsum: ~1520мс (протестировано на Intel i5 3.1 ГГц.)

>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a)

Субоптимальный einsum (из-за времени повторного расчета пути): ~330мс

>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a,
...         optimize='optimal')

Жадный einsum (более быстрое приближение оптимального пути): ~160ms

>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize='greedy')

Мы можем преобразовать в массив numpy, используя einsum (лучший шаблон использования в некоторых случаях): ~110 мс

>>> path = np.einsum_path('ijk,ilm,njm,nlk,abc->',a,a,a,a,a,
...     optimize='optimal')[0]
>>> for iteration in range(500):
...     _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize=path)