다운로드
작성자: admin 작성일시: 2019-11-06 14:52:03 조회수: 478 다운로드: 10
카테고리: 머신 러닝 태그목록:

A.1 아인슈타인 표기법

아인슈타인 표기법(Einstein summation notation)은 복잡한 행렬이나 텐서의 연산을 쉽게 표기하기 위한 방법이다. 아인슈타인 표기법을 이용하면 행렬의 전치, 내적, 외적 등 다양한 연산을 통일된 방법으로 나타낼 수 있다.

아인슈타인 표기법에서는 여러 항에 동일한 첨자가 있고 이 첨자가 좌변에는 없으면 해당 첨자가 가질 수 있는 모든 값에 대해 항의 값을 전부 더하는 것으로 이해한다. 예를 들어 두 벡터 $a$와 $b$의 내적은 원래 다음과 같이 써야 한다.

$$ c = \sum_{i=1}^N a_i b_i $$

하지만 아인슈타인 표기법에서는 다음과 같이 쓴다.

$$ c = a_i b_i $$

numpy, tensorflow, pytorch 패키지에서는 einsum()이라는 함수로 구현되어 있다. 여기에서는 예제 코드로 numpy를 사용하지만 tensorflow, pytorch 패키지에서도 동일한 방법을 쓴다. 사용법은 다음과 같다.

einsum("첨자1,첨자2,첨자3->첨자", 텐서1,텐서2,텐서3)

첫번째 인수로 들어가는 문자열이 아인슈타인 표기법을 나타고 이후에 오는 인수들은 입력 텐서이다. 첨자1은 텐서1의 첨자, 첨자2는 텐서2의 첨자,... 이런 식으로 텐서와 첨자가 연결된다.

다음과 같은 행렬이 있다.

In [1]:
A = np.arange(25).reshape(5,5)
A
Out:
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])

이 행렬의 전치, 모든 원소의 합, 열의 합, 행의 합, 대각합을 구하는 공식을 아인슈타인 표기법으로 나타내고 einsum 함수로 구해보자.

행렬의 전치연산

전치연산은 아인슈타인 표기법으로 다음과 같다.

$$ B_{ij} = A_{ji} $$

einsum() 함수로는 다음과 같다.

In [2]:
np.einsum("ij->ji", A)
Out:
array([[ 0,  5, 10, 15, 20],
       [ 1,  6, 11, 16, 21],
       [ 2,  7, 12, 17, 22],
       [ 3,  8, 13, 18, 23],
       [ 4,  9, 14, 19, 24]])

numpy 연산으로는 다음과 같다.

In [3]:
A.T
Out:
array([[ 0,  5, 10, 15, 20],
       [ 1,  6, 11, 16, 21],
       [ 2,  7, 12, 17, 22],
       [ 3,  8, 13, 18, 23],
       [ 4,  9, 14, 19, 24]])

행렬의 모든 원소의 합

행렬의 모든 원소의 합은 아인슈타인 표기법으로 다음과 같다.

$$ A_{ij} $$

einsum() 함수로는 다음과 같다.

In [4]:
np.einsum("ij->", A)
Out:
300

numpy 연산으로는 다음과 같다.

In [5]:
A.sum()
Out:
300

행렬의 열의 합

행렬의 열의 합은 아인슈타인 표기법으로 다음과 같다.

$$ b_j = A_{ij} $$

einsum() 함수로는 다음과 같다.

In [6]:
np.einsum("ij->j", A)
Out:
array([50, 55, 60, 65, 70])

numpy 연산으로는 다음과 같다.

In [7]:
A.sum(axis=0)
Out:
array([50, 55, 60, 65, 70])

행렬의 행의 합

행렬의 행의 합은 아인슈타인 표기법으로 다음과 같다.

$$ b_i = A_{ij} $$

einsum() 함수로는 다음과 같다.

In [8]:
np.einsum("ij->i", A)
Out:
array([ 10,  35,  60,  85, 110])

numpy 연산으로는 다음과 같다.

In [9]:
A.sum(axis=1)
Out:
array([ 10,  35,  60,  85, 110])

행렬의 대각합

행렬의 대각합(trace)은 아인슈타인 표기법으로 다음과 같다.

$$ A_{ii} $$

einsum() 함수로는 다음과 같다.

In [10]:
np.einsum("ii->", A)
Out:
60

numpy 연산으로는 다음과 같다.

In [11]:
np.trace(A)
Out:
60

행렬과 벡터의 곱

위 행렬과 다음 벡터의 곱을 구해보자

In [12]:
b = np.arange(5)
b
Out:
array([0, 1, 2, 3, 4])

아인슈타인 표기법으로 다음과 같다.

$$ c_i = A_{ij}b_j $$

einsum() 함수로는 다음과 같다.

In [13]:
np.einsum("ij,j->i", A, b)
Out:
array([ 30,  80, 130, 180, 230])

numpy 연산으로는 다음과 같다.

In [14]:
A @ b
Out:
array([ 30,  80, 130, 180, 230])

행렬과 행렬의 곱

두 행렬의 곱은 아인슈타인 표기법으로 다음과 같다.

$$ c_{ij} = A_{ik}B_{kj} $$

einsum() 함수로는 다음과 같다.

In [15]:
np.einsum("ik,kj->ij", A, A)
Out:
array([[ 150,  160,  170,  180,  190],
       [ 400,  435,  470,  505,  540],
       [ 650,  710,  770,  830,  890],
       [ 900,  985, 1070, 1155, 1240],
       [1150, 1260, 1370, 1480, 1590]])

numpy 연산으로는 다음과 같다.

In [16]:
A @ A
Out:
array([[ 150,  160,  170,  180,  190],
       [ 400,  435,  470,  505,  540],
       [ 650,  710,  770,  830,  890],
       [ 900,  985, 1070, 1155, 1240],
       [1150, 1260, 1370, 1480, 1590]])

벡터와 벡터의 내적

벡터와 벡터의 내적은 아인슈타인 표기법으로 다음과 같다.

$$ a_i b_i $$

einsum() 함수로는 다음과 같다.

In [17]:
np.einsum("i,i->", b, b)
Out:
30

numpy 연산으로는 다음과 같다.

In [18]:
b @ b
Out:
30

벡터와 벡터의 외적

벡터와 벡터의 외적은 아인슈타인 표기법으로 다음과 같다.

$$ c_{ij} = a_ib_j $$

einsum() 함수로는 다음과 같다.

In [19]:
np.einsum("i,j->ij", b, b)
Out:
array([[ 0,  0,  0,  0,  0],
       [ 0,  1,  2,  3,  4],
       [ 0,  2,  4,  6,  8],
       [ 0,  3,  6,  9, 12],
       [ 0,  4,  8, 12, 16]])

numpy 연산으로는 다음과 같다.

In [20]:
np.outer(b, b)
Out:
array([[ 0,  0,  0,  0,  0],
       [ 0,  1,  2,  3,  4],
       [ 0,  2,  4,  6,  8],
       [ 0,  3,  6,  9, 12],
       [ 0,  4,  8, 12, 16]])

행렬과 행렬의 배치 곱

행렬과 행렬의 배치 곱(batch matrix multiplication)은 아인슈타인 표기법으로 다음과 같다.

$$ c_{aij} = a_{aik}b_{akj} $$

einsum() 함수로는 다음과 같다.

In [21]:
A = np.random.random(size=(3, 2, 3))
B = np.random.random(size=(3, 3, 2))
A, B
Out:
(array([[[0.03765499, 0.72764062, 0.22228691],
         [0.01675   , 0.9910837 , 0.30488014]],
 
        [[0.72477232, 0.71684858, 0.30615051],
         [0.40888526, 0.90617048, 0.9293682 ]],
 
        [[0.42863622, 0.35169843, 0.73815826],
         [0.15767769, 0.51296402, 0.28679549]]]),
 array([[[0.20049475, 0.0259528 ],
         [0.73779196, 0.76334064],
         [0.71847   , 0.98207391]],
 
        [[0.14333042, 0.65069056],
         [0.19743184, 0.51287024],
         [0.53827983, 0.12968039]],
 
        [[0.1197461 , 0.66051446],
         [0.64449687, 0.2108214 ],
         [0.88188052, 0.43038008]]]))
In [22]:
np.einsum('aik,akj->aij', A, B)
Out:
array([[[0.70410351, 0.77471709],
        [0.95361911, 1.05638401]],

       [[0.4102053 , 0.87895452],
        [0.73777275, 0.85132647]],

       [[0.92896344, 0.67495458],
        [0.60240434, 0.33572325]]])

numpy 연산으로는 다음과 같다.

In [23]:
result = []
for i in range(A.shape[0]):
    result.append(A[i, :, :] @ B[i, :, :])
np.stack(result)
Out:
array([[[0.70410351, 0.77471709],
        [0.95361911, 1.05638401]],

       [[0.4102053 , 0.87895452],
        [0.73777275, 0.85132647]],

       [[0.92896344, 0.67495458],
        [0.60240434, 0.33572325]]])

벡터 열의 그램 행렬

벡터 열 $b_1, b_2, \ldots, b_N$이 있을 때 그램 행렬(Gram matrix)은 다음과 같이 정의된다.

$$ G_{ij} = b_i^T b_j $$

즉, 그램 행렬의 $i,j$ 원소는 $i$번째 벡터와 $j$번째 벡터의 내적이다.

아인슈타인 표기법으로 다음과 같다. 이 식에서 $B$는 $b$를 행벡터로 가지는 행렬이다.

$$ G_{ij} = B_{ik}B_{jk} $$

einsum() 함수로는 다음과 같다.

In [24]:
A = np.random.random(size=(3, 2))
A
Out:
array([[0.68197442, 0.89298072],
       [0.60321239, 0.43886867],
       [0.61818685, 0.21527605]])
In [25]:
np.einsum("ik,jk->ij", A, A)
Out:
array([[1.26250368, 0.80327668, 0.61382498],
       [0.80327668, 0.5564709 , 0.46737588],
       [0.61382498, 0.46737588, 0.42849876]])

이미지 간의 그램 행렬

복수의 이미지가 있을 때 각 이미지를 벡터라고 가정하면 이미지들간의 내적으로 이루어진 그램 행렬을 구할 수 있다.

아인슈타인 표기법으로 다음과 같다.

$$ G_{cd} = A_{ijc}B_{ijd} $$

다음 예제 그림은 R, G, B 3개의 채널 이미지로 구성되어 있다. 각 채널 이미지의 그램행렬은 다음처럼 구한다.

In [26]:
from sklearn.datasets import load_sample_image
img = load_sample_image('china.jpg')  
np.einsum("ijc,ijd->cd", img, img)
Out:
array([[247, 131,  62],
       [131,  66, 118],
       [ 62, 118,  95]], dtype=uint8)

배치 이미지에 대해 각각 그램행렬을 구하면 다음과 같다.

In [27]:
batch_img = np.stack([img, img])
np.einsum("bijc,bijd->bcd", batch_img, batch_img)
Out:
array([[[247, 131,  62],
        [131,  66, 118],
        [ 62, 118,  95]],

       [[247, 131,  62],
        [131,  66, 118],
        [ 62, 118,  95]]], dtype=uint8)

질문/덧글

아직 질문이나 덧글이 없습니다. 첫번째 글을 남겨주세요!