GitHub Python Bar Review

Тензоры и нотация Эйнштейна¶

Две части:

  • математическая
  • программная

1. Математическая часть¶

Некоторые объекты линейной алгебры¶

Название Абстрактный язык Язык чисел (базис) Язык матриц
Базис $$\vec{e_0}, \vec{e_1}$$ $$\epsilon=\left\{\vec{e_0}, \vec{e_1}\right\}$$
Элемент $$\vec{x}$$ $$\vec{x}=\sum_{i=0}^{n-1} x_i \vec{e_i}$$ $$\vec{x}=\epsilon X$$$$\vec{x}=\left\{\vec{e_0}, \vec{e_1}\right\}\begin{bmatrix}x_0\\ x_1\end{bmatrix}$$


Линейная функция (форма) $$f(\vec{x})$$ $$f(\vec{x})=\sum_{i=0}^{n-1} x_i f(\vec{e_i})=\sum_{i=0}^{n-1} x_i f_i = (\vec{x},\vec{f})$$ $$f(\vec{x})=X^T F$$$$f(\vec{x})=\begin{bmatrix}x_0 & x_1\end{bmatrix}\begin{bmatrix}f_0\\ f_1\end{bmatrix}$$
Билинейная форма $$b(\vec{x},\vec{y})$$ $$b(\vec{x},\vec{y})=\sum_{i,j=0}^{n-1} x_i y_j b(\vec{e_i},\vec{e_j})=\sum_{i,j=0}^{n-1} x_i y_j b_{ij}$$ $$b(\vec{x},\vec{y})=X^T B Y$$$$b(\vec{x},\vec{y})=\begin{bmatrix}x_0 & x_1\end{bmatrix}\begin{bmatrix}b_{00} & b_{01}\\ b_{10} & b_{11}\end{bmatrix}\begin{bmatrix}y_0\\ y_1\end{bmatrix}$$
Оператор $$\vec{y} = \hat A \vec{x}$$ $$\hat A \vec{x}=\sum_{i=0}^{n-1} x_i \hat A\vec{e_i}=\sum_{i=0}^{n-1} x_i \sum_{j=0}^{n-1} a_{ji}\vec{e_j}$$ $$Y=AX$$$$Y=\begin{bmatrix}a_{00} & a_{01}\\ a_{10} & a_{11}\end{bmatrix}\begin{bmatrix}x_0\\ x_1\end{bmatrix}$$

Преобразование базиса¶

Название В базисе $\epsilon$ Переход $\epsilon \to\phi$ Обратный переход $\phi \to\epsilon$ Вместе с базисом
Базис $$\epsilon=\left\{\vec{e_0}, \vec{e_1}\right\}$$ $$\phi = \epsilon T $$ $$\epsilon = \phi T^{-1} $$ +
Элемент $$\vec{x}=\epsilon X_\epsilon$$


$$X_\phi=T^{-1}X_\epsilon$$ $$X_\epsilon=T X_\phi$$ -
Линейная функция (форма) $$f(\vec{x})=X_\epsilon^T F_\epsilon$$ $$F_\phi=T^{T}F_\epsilon$$ $$F_\epsilon=(T^{-1})^TF_\phi$$ +
Билинейная форма $$b(\vec{x},\vec{y})=X_\epsilon^T B_\epsilon Y_\epsilon$$ $$B_\phi = T^T B_\epsilon T$$ $$B_\epsilon = (T^{-1})^T B_\phi T^{-1}$$ +/-
Оператор $$Y_\epsilon=A_\epsilon X_\epsilon$$ $$A_\phi = T^{-1} A_\epsilon T$$ $$A_\epsilon = T A_\phi T^{-1}$$ +/-

Тензорный язык¶

Название Переход $\epsilon \to\phi$ Преобразование компоненты Преобразование компоненты в нотации Эйнштейна
Базис $$\phi = \epsilon T $$ $$\vec{f}_j=[T]_{ij}\vec{e}_i$$ $$\vec{f}_j=[T]^i_j\vec{e}_i$$
Элемент $$X_\phi=T^{-1}X_\epsilon$$ $$[x_\phi]_i=\sum_{j=0}^{n-1}[T^{-1}]_{ij} [x_\epsilon]_j$$ $$\underset{\phi}{x^j} = [T^{-1}]^j_i \underset{\epsilon}{x^i}$$
Линейная функция (форма) $$F_\phi=T^{T}F_\epsilon$$ $$[f_\phi]_i = \sum_{j=0}^{n-1}[T]_{ji} [f_\epsilon]_j$$ $$\underset{\phi}{f_j}=[T]^i_j \underset{\epsilon}{f_i}$$
Билинейная форма $$B_\phi = T^T B_\epsilon T$$ $$[B_\phi]_{ij}=\sum_{p=0}^{n-1}\sum_{k=0}^{n-1}[T]_{ip} [T]_{kj} [B_\epsilon]_{pk}$$ $$\underset{\phi}{B_{ij}}=[T]^p_i [T]^k_j \underset{\epsilon}{B_{pk}} $$
Оператор $$A_\phi = T^{-1} A_\epsilon T$$ $$[A_\phi]_{ij}=\sum_{p=0}^{n-1}\sum_{k=0}^{n-1} [T^{-1}]_{jp} [T]_{ik} [A_\epsilon]_{pk} $$ $$\underset{\phi}{A^j_i}=[T^{-1}]^j_p [T]^k_i \underset{\epsilon}{A^p_k} $$
Общий вид тензора¶

$$ \underset{\phi}{a^{p_1p_2 \ldots p_m}_{s_1s_2 \ldots s_l}} = [T^{-1}]^{p_1}_{i_1} \ldots [T^{-1}]^{p_m}_{i_m} [T]^{j_1}_{s_1} \ldots [T]^{j_l}_{s_l} \underset{\epsilon}{a^{i_1i_2 \ldots i_m}_{j_1j_2 \ldots j_l}} $$

Тензорные операции¶
  • Сложение
  • Свёртка
  • Умножение
    • Внешнее (тензорное)
    • Внутреннее (скалярное)
Нотация Эйнштейна¶
  1. Суммируем по одинаковым верхним и нижним индексам (немой индекс) от начала до $n$
  2. Дубли сверху и снизу в индексах запрещены
  3. Остальные индексы свободные
Классическая запись¶

$$ b_i = \sum_{i=0}^{n-1} A_{ij} v_j $$

Программная запись¶
In [ ]:
for i in range(n): # свободный
    for j in range(n): # немой
        b[i] += A[i, j] * v[j]
Запись в нотации Эйнштейна¶

$$ b^i = \sum_{i=0}^{n-1} A^i_j v^j = A^i_j v^j $$

2. Программная часть¶

In [49]:
import numpy as np
from numpyarray_to_latex.jupyter import to_jup

Формат¶

  • Ковариантность и контрвариантность игнорируется
  • Произведение по повторяющимся индексам (фиктивным)
  • Суммирование по отсутсвующим индексам в результате
  • Индексы можно менять местами
  • Шаблон
индексы первого тензора, индексы второго тензора, индексы третьего тензора, ... -> индексы результата

Элементарные операции¶

Создадим тензор

In [111]:
a = np.arange(6).reshape(2, 3)
to_jup(a)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00 \end{array} \right)$

Транспонирование¶

In [52]:
to_jup(np.einsum('ij->ji', a))
$\displaystyle \left( \begin{array}{} 0.00 & 3.00\\ 1.00 & 4.00\\ 2.00 & 5.00 \end{array} \right)$

След¶

In [127]:
a = np.arange(9).reshape(3, 3)
to_jup(a)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00\\ 6.00 & 7.00 & 8.00 \end{array} \right)$
In [141]:
np.einsum('ii->', a)
Out[141]:
12
In [129]:
np.trace(a)
Out[129]:
12

Бонус: диагональ

In [142]:
np.einsum('ii->i', a)
Out[142]:
array([0, 4, 8])

Суммирование элементов 1 тензора¶

Всех

In [54]:
np.einsum('ij->', a)
Out[54]:
15

По столбцам

In [55]:
to_jup(np.einsum('ij->j', a))
$\displaystyle \left( \begin{array}{} 3.00 & 5.00 & 7.00 \end{array} \right)$

По строкам

In [56]:
to_jup(np.einsum('ij->i', a))
$\displaystyle \left( \begin{array}{} 3.00 & 12.00 \end{array} \right)$

Свёртка тензора¶

Понижение размерности на 2

Простой пример¶

In [96]:
a = np.arange(6).reshape(2, 3)
b = np.arange(15).reshape(3, 5)
to_jup(a)
to_jup(b)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00 \end{array} \right)$
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00 & 3.00 & 4.00\\ 5.00 & 6.00 & 7.00 & 8.00 & 9.00\\ 10.00 & 11.00 & 12.00 & 13.00 & 14.00 \end{array} \right)$
In [95]:
to_jup(np.einsum('pq,qt->pt', a, b))
$\displaystyle \left( \begin{array}{} 25.00 & 28.00 & 31.00 & 34.00 & 37.00\\ 70.00 & 82.00 & 94.00 & 106.00 & 118.00 \end{array} \right)$

Пример посложнее¶

In [98]:
a = np.arange(66).reshape(2, 3, 11)
b = np.arange(210).reshape(3, 5, 2, 7)
np.einsum('pqr,qtsi->prtsi', a, b).shape
Out[98]:
(2, 11, 5, 2, 7)

Умножение¶

Матрица на вектор¶

In [59]:
a = np.arange(6).reshape(2, 3)
b = np.arange(3)
to_jup(a)
to_jup(b)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00 \end{array} \right)$
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00 \end{array} \right)$
In [60]:
to_jup(np.einsum('ik,k->i', a, b))
$\displaystyle \left( \begin{array}{} 5.00 & 14.00 \end{array} \right)$

Матрица на матрицу¶

In [61]:
a = np.arange(6).reshape(2, 3)
b = np.arange(15).reshape(3, 5)
to_jup(a)
to_jup(b)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00 \end{array} \right)$
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00 & 3.00 & 4.00\\ 5.00 & 6.00 & 7.00 & 8.00 & 9.00\\ 10.00 & 11.00 & 12.00 & 13.00 & 14.00 \end{array} \right)$
In [62]:
to_jup(np.einsum('ik,kj->ij', a, b))
$\displaystyle \left( \begin{array}{} 25.00 & 28.00 & 31.00 & 34.00 & 37.00\\ 70.00 & 82.00 & 94.00 & 106.00 & 118.00 \end{array} \right)$
Матрица на матрицу в батче¶
In [103]:
a = np.arange(2*6).reshape(2, 2, 3)
b = np.arange(2*15).reshape(2, 3, 5)
np.einsum('bik,bkj->bij', a, b).shape
Out[103]:
(2, 2, 5)

Адамара (поэлементное)¶

In [71]:
a = np.arange(6).reshape(2, 3)
b = np.arange(6,12).reshape(2, 3)
to_jup(a)
to_jup(b)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00 \end{array} \right)$
$\displaystyle \left( \begin{array}{} 6.00 & 7.00 & 8.00\\ 9.00 & 10.00 & 11.00 \end{array} \right)$
In [72]:
to_jup(np.einsum('ij,ij->ij', a, b))
$\displaystyle \left( \begin{array}{} 0.00 & 7.00 & 16.00\\ 27.00 & 40.00 & 55.00 \end{array} \right)$

Внунтренее (cкалярное)¶

Для векторов¶
In [63]:
a = np.arange(3)
b = np.arange(3,6)
to_jup(a)
to_jup(b)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00 \end{array} \right)$
$\displaystyle \left( \begin{array}{} 3.00 & 4.00 & 5.00 \end{array} \right)$
In [64]:
np.einsum('i,i->', a, b)
Out[64]:
14
Для матриц¶
In [65]:
a = np.arange(6).reshape(2, 3)
b = np.arange(6,12).reshape(2, 3)
to_jup(a)
to_jup(b)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00 \end{array} \right)$
$\displaystyle \left( \begin{array}{} 6.00 & 7.00 & 8.00\\ 9.00 & 10.00 & 11.00 \end{array} \right)$
In [68]:
np.einsum('ij,ij->', a, b)
Out[68]:
145

Внешнее (тензорное)¶

aka Кронокерово произведение

In [69]:
a = np.arange(3)
b = np.arange(3,7)
to_jup(a)
to_jup(b)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00 \end{array} \right)$
$\displaystyle \left( \begin{array}{} 3.00 & 4.00 & 5.00 & 6.00 \end{array} \right)$
In [70]:
to_jup(np.einsum('i,j->ij', a, b))
$\displaystyle \left( \begin{array}{} 0.00 & 0.00 & 0.00 & 0.00\\ 3.00 & 4.00 & 5.00 & 6.00\\ 6.00 & 8.00 & 10.00 & 12.00 \end{array} \right)$

Большие тензоры¶

Не надо выдумывать имена для всех индексов

In [168]:
a = np.arange(2*3*4*5*6*7).reshape(2, 3, 4, 5, 6, 7)
a.shape
Out[168]:
(2, 3, 4, 5, 6, 7)
In [169]:
np.einsum('...ij->...ji', a).shape
Out[169]:
(2, 3, 4, 5, 7, 6)

Особенности¶

Осторожно с типами данных¶

In [144]:
a = np.ones(300, dtype=np.int8)
np.einsum('i->', a) # должно быть 300
Out[144]:
44

След прямоугольной матрицы¶

In [147]:
a = np.arange(6).reshape(2, 3)
to_jup(a)
$\displaystyle \left( \begin{array}{} 0.00 & 1.00 & 2.00\\ 3.00 & 4.00 & 5.00 \end{array} \right)$
In [148]:
np.trace(a)
Out[148]:
4
In [149]:
np.einsum('ii->', a) 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[149], line 1
----> 1 np.einsum('ii->', a) 

File ~/miniconda3/envs/python-bar-review/lib/python3.11/site-packages/numpy/core/einsumfunc.py:1371, in einsum(out, optimize, *operands, **kwargs)
   1369     if specified_out:
   1370         kwargs['out'] = out
-> 1371     return c_einsum(*operands, **kwargs)
   1373 # Check the kwargs to avoid a more cryptic error later, without having to
   1374 # repeat default values here
   1375 valid_einsum_kwargs = ['dtype', 'order', 'casting']

ValueError: dimensions in operand 0 for collapsing index 'i' don't match (2 != 3)

View¶

In [158]:
a = np.arange(6)
a
Out[158]:
array([0, 1, 2, 3, 4, 5])
In [157]:
np.einsum('i', a)
Out[157]:
array([0, 1, 2, 3, 4, 5])
In [165]:
np.einsum('i', a).__array_interface__
Out[165]:
{'data': (105553164518192, False),
 'strides': None,
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (6,),
 'version': 3}
In [166]:
a.__array_interface__
Out[166]:
{'data': (105553164518192, False),
 'strides': None,
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (6,),
 'version': 3}

Performance¶

In [183]:
def calc():
    a = np.ones(64).reshape(2,4,8)
    for iteration in range(500):
        _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a)

%time calc()
CPU times: user 778 ms, sys: 4.11 ms, total: 782 ms
Wall time: 784 ms
In [184]:
def calc():
    a = np.ones(64).reshape(2,4,8)
    for iteration in range(500):
        _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize='optimal')

%time calc()
CPU times: user 150 ms, sys: 4.81 ms, total: 155 ms
Wall time: 153 ms
In [185]:
def calc():
    a = np.ones(64).reshape(2,4,8)
    for iteration in range(500):
        _ = np.einsum('ijk,ilm,njm,nlk,abc->',a,a,a,a,a, optimize='greedy')

%time calc()
CPU times: user 55.8 ms, sys: 4.15 ms, total: 60 ms
Wall time: 57.4 ms
In [187]:
def calc():
    a = np.ones(64).reshape(2,4,8)
    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)

%time calc()
CPU times: user 41.5 ms, sys: 2.49 ms, total: 44 ms
Wall time: 43.1 ms

Einops¶

In [214]:
from einops import rearrange, reduce, repeat

Flatten¶

In [206]:
a = np.arange(2*2*3).reshape(2, 2, 3)
a
Out[206]:
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]]])
In [198]:
rearrange(a, 'b h w -> b (h w)')
Out[198]:
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])

Split¶

In [207]:
a = np.arange(2*2*2*3).reshape(2, 2, 6)
rearrange(a, 'b q (h d) -> b q h d', d=2).shape
Out[207]:
(2, 2, 3, 2)
In [213]:
a = np.arange(2*2*2*3, dtype=np.float32).reshape(2, 2, 3, 2)
reduce(a, 'b q h d -> b q h', 'mean')
Out[213]:
array([[[ 0.5,  2.5,  4.5],
        [ 6.5,  8.5, 10.5]],

       [[12.5, 14.5, 16.5],
        [18.5, 20.5, 22.5]]], dtype=float32)

Repeat¶

In [219]:
a = np.arange(1*2*3).reshape(1, 2, 3)
repeat(a, 'b h d -> b 2 h d')
Out[219]:
array([[[[0, 1, 2],
         [3, 4, 5]],

        [[0, 1, 2],
         [3, 4, 5]]]])

Почитать¶

  • https://obilaniu6266h16.wordpress.com/2016/02/04/einstein-summation-in-numpy/
  • https://ajcr.net/Basic-guide-to-einsum/
  • https://ejenner.com/post/einsum/
  • https://rockt.github.io/2018/04/30/einsum
  • http://einops.rocks/1-einops-basics/
  • http://einops.rocks/2-einops-for-deep-learning/
In [190]:
%load_ext watermark
%watermark -d -u -v -iv
The watermark extension is already loaded. To reload it, use:
  %reload_ext watermark
Last updated: 2023-12-29

Python implementation: CPython
Python version       : 3.11.4
IPython version      : 8.14.0

numpy : 1.25.1
einops: 0.7.0

In [ ]: