development

NumPy의 이해

big-blog 2020. 6. 10. 07:54
반응형

NumPy의 이해


einsum작동 방식을 정확히 이해하기 위해 고심하고 있습니다. 설명서와 몇 가지 예를 살펴 보았지만 충실하지 않은 것 같습니다.

다음은 수업 시간에 진행 한 예입니다.

C = np.einsum("ij,jk->ki", A, B)

두 개의 배열 AB

나는 이것이 걸릴 것이라고 생각 A^T * B하지만 확실하지 않습니다 (그중 하나의 조옮김을하고 있습니까?). 아무도 여기서 무슨 일이 일어나고 있는지 (그리고 일반적으로을 사용할 때 einsum) 정확하게 나를 안내 할 수 있습니까 ?


(참고 :이 답변은 짧은 기반으로 블로그 게시물 에 대해 einsum내가 얼마 전에 썼다.)

무엇을 einsum합니까?

우리는이 다차원 배열을 가지고 있다고 상상 A하고 B. 이제 우리가 원한다고 가정 해 봅시다 ...

  • 곱셈 AB제품의 새로운 배열을 생성하는 특정 방법으로; 그리고 아마도
  • 특정 축을 따라이 새 배열을 합산 하십시오. 그리고 아마도
  • 트랜스 특정 순서로 새로운 배열 축을.

좋은 기회가있어 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]])

우리는 곱합니다 AB요소 현명하고 새로운 배열의 행을 따라 요약. "정상"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])

서명 문자열은 'i,ij->i'여기 열쇠와 설명이 조금 필요합니다. 두 부분으로 생각할 수 있습니다. 왼쪽 () 왼쪽에 ->두 개의 입력 배열에 레이블을 지정했습니다. 의 오른쪽에는 ->끝으로 배열을 표시 했습니다 .

다음은 다음과 같은 일입니다.

  • A하나의 축을 가지고; 라벨이 붙어 있습니다 i. 그리고 B두 개의 축이 있습니다. 우리는 축 0을 레이블로 지정 i하고 축 1을 레이블로 지정 했습니다 j.

  • 두 입력 배열에서 레이블 반복함으로써이 두 축을 해야한다는 것을 알 있습니다. 다시 말해, 배열 마찬가지로 배열의 각 열을 곱하는 입니다.ieinsumABA[:, np.newaxis] * B

  • 공지 사항 j우리의 원하는 출력의 레이블로 표시되지 않습니다; 우리는 방금 사용했습니다 i(1D 배열로 끝내고 싶습니다). 에 의해 생략 라벨을, 우리는 이야기하고 einsum하는 요약 이 축을 따라. 다시 말해, 우리는 제품의 행을 합산합니다 .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). 다음 은 함수에서 얻은 Aand B및 출력 배열 의 레이블을 보여주는 그림입니다 .

여기에 이미지 설명을 입력하십시오

라벨 j이 반복되는 것을 볼 수 있습니다. 이는의 행 A과의 열을 곱한다는 의미 입니다 B. 또한 레이블 j은 출력에 포함되지 않습니다. 우리는 이러한 제품을 요약합니다. 레이블 ik출력을 위해 유지되므로 2D 배열을 다시 얻습니다.

이 결과를 레이블 j합산 되지 않은 배열과 비교하는 것이 더 명확 할 수 있습니다 . 아래의 왼쪽에는 글의 결과 인 3D 배열이 있습니다 np.einsum('ij,jk->ijk', A, B)(예 j: label을 유지했습니다 ).

여기에 이미지 설명을 입력하십시오

합산 축은 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 배열의 경우 CD축이 호환 가능한 길이 (동일한 길이 또는 길이가 1 인 경우)는 다음과 같습니다.

  • C(주 대각선의 합)의 흔적은 다음과 np.trace(C)같이 쓸 수 있습니다.

    np.einsum('ii', C)
    
  • 요소 - 지혜의 곱셈 C과의 전치 D, C * D.T, 쓸 수있다 :

    np.einsum('ij,ji->ij', C, D)
    
  • 4D 배열을 만들기 위해 C배열에 각 요소를 곱하면 다음과 같이 쓸 수 있습니다.DC[:, :, 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 배열의 경우 2 문자, 3D 배열의 경우 3 문자 등) 그리고 아래 첨자 문자열 사이에 문자를 반복하면 ( 이 경우) 합계 가 해당 차원을 따라 발생 한다는 것을 의미 합니다. 따라서 합산됩니다. (즉, 해당 차원은 사라질 것입니다 ) jein

뒤에 있는 첨자 문자열-> 은 결과 배열입니다. 비워두면 모든 것이 합산되고 스칼라 값이 결과로 반환됩니다. 그렇지 않으면 결과 배열은 아래 첨자 문자열 에 따라 치수를 갖습니다 . 이 예에서는 ik입니다. 우리는 행렬 곱셈의 경우 배열의 열 수가 배열 A의 행 수와 일치해야 한다는 것을 알고 있기 때문에 직관적입니다 B(예 : 첨자 문자열j 에서 char 반복 하여이 지식을 인코딩 합니다 )


다음은 np.einsum()일반적인 텐서 또는 nd-array 연산을 간결하게 구현 하는 데 사용 / 파워를 나타내는 몇 가지 예입니다 .

입력

# a vector
In [197]: vec
Out[197]: array([0, 1, 2, 3])

# an array
In [198]: A
Out[198]: 
array([[11, 12, 13, 14],
       [21, 22, 23, 24],
       [31, 32, 33, 34],
       [41, 42, 43, 44]])

# another array
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]: 110

6) 매트릭스 전치 (와 유사 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]: 14

9) 축 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]: 480

14) 여러 축에 대한 합계 (즉, 주 변화)
(와 유사 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]: True

15) Double Dot Products ( np.sum (hadamard-product) 와 유사 , 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]: 124

16) 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()검증 에 사용해야 하는 경우 reshape다음과 같은 결과를 얻기 위해 몇 가지 작업을 수행해야합니다.

# 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

Bonus: Read more math here : Einstein-Summation and definitely here: Tensor-Notation


Lets make 2 arrays, with different, but compatible dimensions to highlight their interplay

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]])

Your calculation, takes a 'dot' (sum of products) of a (2,3) with a (3,4) to produce a (4,2) array. i is the 1st dim of A, the last of C; k the last of B, 1st of C. j is 'consumed' by the summation.

In [45]: C=np.einsum('ij,jk->ki',A,B)
Out[45]: 
array([[20, 56],
       [23, 68],
       [26, 80],
       [29, 92]])

This is the same as np.dot(A,B).T - it's the final output that's transposed.

To see more of what happens to j, change the C subscripts to 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]]])

This can also be produced with:

A[:,:,None]*B[None,:,:]

That is, add a k dimension to the end of A, and an i to the front of B, resulting in a (2,3,4) array.

0 + 4 + 16 = 20, 9 + 28 + 55 = 92, etc; Sum on j and transpose to get the earlier result:

np.sum(A[:,:,None] * B[None,:,:], axis=1).T

# C[k,i] = sum(j) A[i,j (,k) ] * B[(i,)  j,k]

I found NumPy: The tricks of the trade (Part II) instructive

We use -> to indicate the order of the output array. So think of 'ij, i->j' as having left hand side (LHS) and right hand side (RHS). Any repetition of labels on the LHS computes the product element wise and then sums over. By changing the label on the RHS (output) side, we can define the axis in which we want to proceed with respect to the input array, i.e. summation along axis 0, 1 and so on.

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)

세 개의 축 i, j, k가 있으며 j가 반복됩니다 (왼쪽). i,j의 행과 열을 나타냅니다 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]])
>>>

참고 URL : https://stackoverflow.com/questions/26089893/understanding-numpys-einsum

반응형