과학 계산을 위한 NumPy Einsum

NumPyBeginner
지금 연습하기

소개

과학 계산에서 효율적인 선형 대수 연산을 수행하는 것은 필수적입니다. 수치 계산을 위한 기본적인 Python 라이브러리인 NumPy 는 이러한 연산을 위한 다양한 도구를 제공합니다. 이러한 도구 중 einsum (아인슈타인 표기법) 은 복잡한 배열 연산을 간결하게 표현하는 데 특히 강력한 함수로 두각을 나타냅니다.

이 튜토리얼은 einsum이 무엇인지, 그리고 Python 코드에서 이를 효과적으로 사용하는 방법을 이해하도록 안내합니다. 이 랩이 끝나면 einsum 함수를 사용하여 다양한 배열 연산을 수행하고 기존 NumPy 함수에 비해 갖는 장점을 이해할 수 있게 될 것입니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 고급 레벨의 실험이며 완료율은 50%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

NumPy Einsum 기본 이해

아인슈타인 표기법 (einsum) 은 간결한 표기법을 사용하여 많은 배열 연산을 표현할 수 있는 강력한 NumPy 함수입니다. 이는 복잡한 방정식을 단순화하기 위해 물리학과 수학에서 일반적으로 사용되는 아인슈타인 합 표기법을 따릅니다.

Python 셸 열기

Python 셸을 열어 시작해 보겠습니다. 데스크톱에서 터미널을 열고 다음을 입력합니다.

python3

Python 프롬프트 (>>>) 가 표시되면 Python 대화형 셸에 들어간 것입니다.

NumPy 가져오기

먼저 NumPy 라이브러리를 가져와야 합니다.

import numpy as np
Open Python Shell

Einsum 이란 무엇인가?

NumPy 의 einsum 함수를 사용하면 배열의 어떤 인덱스 (차원) 에 대해 연산을 수행해야 하는지 설명하는 문자열 표기법을 사용하여 배열 연산을 지정할 수 있습니다.

einsum 연산의 기본 형식은 다음과 같습니다.

np.einsum('notation', array1, array2, ...)

여기서 표기법 문자열은 수행할 연산을 설명합니다.

간단한 예시: 벡터 내적

간단한 예시로 시작해 보겠습니다. 두 벡터의 내적을 계산해 보겠습니다. 수학적 표기법에서 두 벡터 u 와 v 의 내적은 다음과 같습니다.

\sum_i u_i \times v_i

einsum을 사용하여 이를 계산하는 방법은 다음과 같습니다.

## Create two random vectors
u = np.random.rand(5)
v = np.random.rand(5)

## Print the vectors to see their values
print("Vector u:", u)
print("Vector v:", v)

## Calculate dot product using einsum
dot_product = np.einsum('i,i->', u, v)
print("Dot product using einsum:", dot_product)

## Verify with NumPy's dot function
numpy_dot = np.dot(u, v)
print("Dot product using np.dot:", numpy_dot)
Dot Product

표기법 'i,i->'는 다음을 의미합니다.

  • i는 첫 번째 배열 (u) 의 인덱스를 나타냅니다.
  • 두 번째 i는 두 번째 배열 (v) 의 인덱스를 나타냅니다.
  • 화살표 -> 뒤에 아무것도 없다는 것은 스칼라 결과 (모든 인덱스에 대한 합) 를 원한다는 것을 나타냅니다.

Einsum 표기법 이해

einsum 표기법은 다음 일반 패턴을 따릅니다.

'index1,index2,...->output_indices'
  • index1, index2: 각 입력 배열의 차원에 대한 레이블
  • output_indices: 출력 배열의 차원에 대한 레이블
  • 입력 배열에서 반복되는 인덱스는 합산됩니다.
  • 출력에 나타나는 인덱스는 결과에 유지됩니다.

예를 들어, 표기법 'ij,jk->ik'에서:

  • i, j는 첫 번째 배열의 차원입니다.
  • j, k는 두 번째 배열의 차원입니다.
  • j는 두 입력 배열 모두에 나타나므로 이 차원에 대해 합산합니다.
  • i, k는 출력에 나타나므로 이러한 차원이 유지됩니다.

이것은 정확히 행렬 곱셈의 공식입니다!

자주 사용되는 Einsum 연산

einsum의 기본 사항을 이해했으므로 이 강력한 함수를 사용하여 수행할 수 있는 몇 가지 일반적인 연산을 살펴보겠습니다.

행렬 전치

행렬을 전치한다는 것은 행과 열을 바꾸는 것을 의미합니다. 수학적 표기법에서 A 가 행렬이면 전치 행렬 A^T 는 다음과 같이 정의됩니다.

A^T_{ij} = A_{ji}

einsum을 사용하여 행렬 전치를 수행하는 방법을 살펴보겠습니다.

## Create a random matrix
A = np.random.rand(3, 4)
print("Original matrix A:")
print(A)
print("Shape of A:", A.shape)  ## Should be (3, 4)

## Transpose using einsum
A_transpose = np.einsum('ij->ji', A)
print("\nTransposed matrix using einsum:")
print(A_transpose)
print("Shape of transposed A:", A_transpose.shape)  ## Should be (4, 3)

## Verify with NumPy's transpose function
numpy_transpose = A.T
print("\nTransposed matrix using A.T:")
print(numpy_transpose)

표기법 'ij->ji'는 다음을 의미합니다.

  • ij는 입력 행렬의 인덱스를 나타냅니다 (i 는 행, j 는 열).
  • ji는 출력 행렬의 인덱스를 나타냅니다 (j 는 행, i 는 열).
  • 기본적으로 인덱스의 위치를 바꾸는 것입니다.

행렬 곱셈

행렬 곱셈은 선형 대수에서 기본적인 연산입니다. 두 행렬 A 와 B 의 곱 C 는 다음과 같이 정의됩니다.

C_{ik} = \sum_j A_{ij} \times B_{jk}

einsum을 사용하여 행렬 곱셈을 수행하는 방법은 다음과 같습니다.

## Create two random matrices
A = np.random.rand(3, 4)  ## 3x4 matrix
B = np.random.rand(4, 2)  ## 4x2 matrix

print("Matrix A shape:", A.shape)
print("Matrix B shape:", B.shape)

## Matrix multiplication using einsum
C = np.einsum('ij,jk->ik', A, B)
print("\nResult matrix C using einsum:")
print(C)
print("Shape of C:", C.shape)  ## Should be (3, 2)

## Verify with NumPy's matmul function
numpy_matmul = np.matmul(A, B)
print("\nResult matrix using np.matmul:")
print(numpy_matmul)

표기법 'ij,jk->ik'는 다음을 의미합니다.

  • ij는 행렬 A 의 인덱스를 나타냅니다 (i 는 행, j 는 열).
  • jk는 행렬 B 의 인덱스를 나타냅니다 (j 는 행, k 는 열).
  • ik는 출력 행렬 C 의 인덱스를 나타냅니다 (i 는 행, k 는 열).
  • 반복되는 인덱스 j는 합산됩니다 (행렬 곱셈).

요소별 곱셈

요소별 곱셈은 두 배열의 해당 요소를 곱하는 것을 포함합니다. 모양이 같은 두 행렬 A 와 B 의 요소별 곱 C 는 다음과 같습니다.

C_{ij} = A_{ij} \times B_{ij}

einsum을 사용하여 요소별 곱셈을 수행하는 방법은 다음과 같습니다.

## Create two random matrices of the same shape
A = np.random.rand(3, 3)
B = np.random.rand(3, 3)

print("Matrix A:")
print(A)
print("\nMatrix B:")
print(B)

## Element-wise multiplication using einsum
C = np.einsum('ij,ij->ij', A, B)
print("\nElement-wise product using einsum:")
print(C)

## Verify with NumPy's multiply function
numpy_multiply = A * B
print("\nElement-wise product using A * B:")
print(numpy_multiply)

표기법 'ij,ij->ij'는 다음을 의미합니다.

  • ij는 행렬 A 의 인덱스를 나타냅니다.
  • ij는 행렬 B 의 인덱스를 나타냅니다.
  • ij는 출력 행렬 C 의 인덱스를 나타냅니다.
  • 인덱스가 합산되지 않으므로 해당 요소를 곱하는 것입니다.

고급 Einsum 연산

이제 기본적인 einsum 연산에 익숙해졌으므로 몇 가지 더 고급 응용 프로그램을 살펴보겠습니다. 이러한 연산은 einsum 함수의 진정한 힘과 유연성을 보여줍니다.

대각선 추출

행렬의 대각선 요소를 추출하는 것은 선형 대수에서 일반적인 연산입니다. 행렬 A 의 경우 대각선 요소는 다음과 같은 벡터 d 를 형성합니다.

d_i = A_{ii}

einsum을 사용하여 대각선을 추출하는 방법은 다음과 같습니다.

## Create a random square matrix
A = np.random.rand(4, 4)
print("Matrix A:")
print(A)

## Extract diagonal using einsum
diagonal = np.einsum('ii->i', A)
print("\nDiagonal elements using einsum:")
print(diagonal)

## Verify with NumPy's diagonal function
numpy_diagonal = np.diagonal(A)
print("\nDiagonal elements using np.diagonal():")
print(numpy_diagonal)

표기법 'ii->i'는 다음을 의미합니다.

  • ii는 A 의 대각선 요소에 대한 반복 인덱스를 나타냅니다.
  • i는 이러한 요소를 1D 배열로 추출한다는 의미입니다.

행렬 트레이스 (Trace)

행렬의 트레이스는 대각선 요소의 합입니다. 행렬 A 의 경우 트레이스는 다음과 같습니다.

\text{trace}(A) = \sum_i A_{ii}

einsum을 사용하여 트레이스를 계산하는 방법은 다음과 같습니다.

## Using the same matrix A from above
trace = np.einsum('ii->', A)
print("Trace of matrix A using einsum:", trace)

## Verify with NumPy's trace function
numpy_trace = np.trace(A)
print("Trace of matrix A using np.trace():", numpy_trace)

표기법 'ii->'는 다음을 의미합니다.

  • ii는 대각선 요소에 대한 반복 인덱스를 나타냅니다.
  • 빈 출력 인덱스는 모든 대각선 요소를 합산하여 스칼라를 얻는다는 의미입니다.

배치 행렬 곱셈

einsum은 고차원 배열에서 연산을 수행할 때 진가를 발휘합니다. 예를 들어, 배치 행렬 곱셈은 두 배치에서 행렬 쌍을 곱하는 것을 포함합니다.

모양이 (n, m, p) 인 행렬 배치 A 와 모양이 (n, p, q) 인 행렬 배치 B 가 있는 경우 배치 행렬 곱셈은 모양이 (n, m, q) 인 결과 C 를 제공합니다.

C_{ijk} = \sum_l A_{ijl} \times B_{ilk}

einsum을 사용하여 배치 행렬 곱셈을 수행하는 방법은 다음과 같습니다.

## Create batches of matrices
n, m, p, q = 5, 3, 4, 2  ## Batch size and matrix dimensions
A = np.random.rand(n, m, p)  ## Batch of 5 matrices, each 3x4
B = np.random.rand(n, p, q)  ## Batch of 5 matrices, each 4x2

print("Shape of batch A:", A.shape)
print("Shape of batch B:", B.shape)

## Batch matrix multiplication using einsum
C = np.einsum('nmp,npq->nmq', A, B)
print("\nShape of result batch C:", C.shape)  ## Should be (5, 3, 2)

## Let's check the first matrix multiplication in the batch
print("\nFirst result matrix from batch using einsum:")
print(C[0])

## Verify with NumPy's matmul function
numpy_batch_matmul = np.matmul(A, B)
print("\nFirst result matrix from batch using np.matmul:")
print(numpy_batch_matmul[0])

표기법 'nmp,npq->nmq'는 다음을 의미합니다.

  • nmp는 배치 A 의 인덱스를 나타냅니다 (n 은 배치, m 은 행, p 는 열).
  • npq는 배치 B 의 인덱스를 나타냅니다 (n 은 배치, p 는 행, q 는 열).
  • nmq는 출력 배치 C 의 인덱스를 나타냅니다 (n 은 배치, m 은 행, q 는 열).
  • 반복되는 인덱스 p는 합산됩니다 (행렬 곱셈).

왜 Einsum 을 사용하는가?

NumPy 가 이미 이러한 연산을 위한 특수 함수를 제공하는데 왜 einsum을 사용해야 하는지 궁금할 수 있습니다. 다음과 같은 몇 가지 장점이 있습니다.

  1. 통합 인터페이스 (Unified Interface): einsum은 많은 배열 연산을 위한 단일 함수를 제공합니다.
  2. 유연성 (Flexibility): 여러 단계가 필요한 연산을 표현할 수 있습니다.
  3. 가독성 (Readability): 표기법을 이해하면 코드가 더 간결해집니다.
  4. 성능 (Performance): 많은 경우 einsum 연산은 최적화되고 효율적입니다.

복잡한 텐서 연산의 경우 einsum은 종종 가장 명확하고 직접적인 구현을 제공합니다.

요약

이 랩에서는 배열 연산을 위해 아인슈타인 합산 규칙 (Einstein summation convention) 을 구현하는 NumPy 의 강력한 einsum 함수를 살펴보았습니다. 배운 내용을 요약해 보겠습니다.

  1. 기본 Einsum 개념: einsum 표기법을 사용하여 배열 연산을 표현하는 방법을 배웠습니다. 여기서 인덱스는 배열 차원을 나타내고 반복되는 인덱스는 합산을 나타냅니다.

  2. 일반적인 연산: einsum을 사용하여 몇 가지 기본적인 연산을 구현했습니다.

    • 벡터 내적 (Vector dot product)
    • 행렬 전치 (Matrix transpose)
    • 행렬 곱셈 (Matrix multiplication)
    • 요소별 곱셈 (Element-wise multiplication)
  3. 고급 응용 프로그램: 더 복잡한 연산을 탐구했습니다.

    • 대각선 추출 (Diagonal extraction)
    • 행렬 트레이스 (Matrix trace)
    • 배치 행렬 곱셈 (Batch matrix multiplication)

einsum 함수는 NumPy 에서 배열 연산에 대한 통합적이고 유연한 접근 방식을 제공합니다. np.dot, np.matmul, np.transpose와 같은 특수 함수가 특정 연산에 사용 가능하지만, einsum은 광범위한 연산에 대한 일관된 인터페이스를 제공하며, 이는 고차원 배열로 작업할 때 특히 유용합니다.

과학 컴퓨팅 및 데이터 과학 여정을 계속 진행하면서 einsum은 간결하고 읽기 쉬운 코드로 복잡한 배열 연산을 수행하기 위한 강력한 도구가 될 것입니다.