Numpy Tutorial

JungJae Lee·2023년 9월 14일
0

딥러닝

목록 보기
8/11

넘파이(Numpy)

Numpy는 파이썬에서 과학 계산을 위한 핵심 라이브러리로, 다차원 배열 객체와 이러한 배열들을 다루는 도구를 제공합니다.

Numpy를 사용하려면 먼저 numpy 패키지를 가져와야 합니다:

import numpy as np

배열 (Arrays)

Numpy 배열은 동일한 유형의 값으로 구성된 grid로, 양의 정수 튜플에 의해 인덱싱됩니다. 배열의 차원 수는 배열의 Rank이며, 배열의 모양은 각 차원별로 배열의 크기를 제공하는 정수 튜플입니다.

중첩된 파이썬 리스트에서 numpy 배열을 초기화하고, 대괄호를 사용하여 요소에 접근할 수 있습니다.

a = np.array([1, 2, 3])  # 랭크 1의 배열 생성
print(type(a), a.shape, a[0], a[1], a[2])
a[0] = 5                 # 배열 요소 변경
print(a)
<class 'numpy.ndarray'> (3,) 1 2 3
[5 2 3]
b = np.array([[1,2,3],[4,5,6]])   # 랭크 2의 배열 생성
print(b)
[[1 2 3]
 [4 5 6]]

Numpy는 또한 다음과 같이 배열을 생성하는 많은 함수를 제공합니다:

a = np.zeros((2,2))  # 모든 요소가 0인 배열 생성
print(a)
[[0. 0.]
 [0. 0.]]
b = np.ones((1,2))   # 모든 요소가 1인 배열 생성
print(b)
[[1. 1.]]
c = np.full((2,2), 7) # 상수 배열 생성
print(c)
[[7 7]
 [7 7]]
d = np.eye(2)        # 2x2 단위 행렬 생성
print(d)
[[1. 0.]
 [0. 1.]]
e = np.random.random((2,2)) # 무작위 값으로 채워진 배열 생성
print(e)
[[0.8690054  0.57244319]
 [0.29647245 0.81464494]]

배열 인덱싱 (Array Indexing)

Numpy는 배열에 대한 여러 가지 인덱싱 방법을 제공합니다.

슬라이싱 (Slicing): 파이썬 리스트와 유사하게, numpy 배열도 슬라이싱할 수 있습니다. 배열이 다차원일 수 있으므로 각 차원에 대한 슬라이스를 지정해야 합니다:

# 다음과 같은 모양의 랭크 2 배열을 생성합니다 (3, 4)
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])

# 슬라이싱을 사용하여 첫 2개의 행과 1번째 및 2번째 열로 이루어진 하위 배열을 가져옵니다.
# b는 다음과 같은 모양의 배열입니다 (2, 2):
# [[2 3]
#  [6 7]]
b = a[:2, 1:3]
print(b)
[[2 3]
 [6 7]]

배열의 슬라이스는 동일한 데이터의 뷰(View)이므로 수정하면 원래 배열도 수정됩니다.

print(a[0, 1])   # 0번째 행, 1번째 열의 요소 출력
b[0, 0] = 77     # b[0, 0]은 a[0, 1]과 같은 데이터입니다.
print(a[0, 1])   # b를 수정하면 a도 수정됩니다.
2
77

정수 인덱싱과 슬라이스 인덱싱을 혼합해서 사용할 수도 있지만, 그렇게 하면 원래 배열보다 낮은 랭크의 배열이 생성됩니다. 이것은 MATLAB에서 배열 슬라이싱을 처리하는 방식과 매우 다릅니다.

# 다음과 같은 모양의 랭크 2 배열을 생성합니다 (3, 4)
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print(a)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

데이터의 중간 행에 대한 데이터에 접근하는 두 가지 방법입니다. 정수 인덱싱과 슬라이스를 혼합하면 원래 배열보다 낮은 랭크의 배열이 생성됩니다. 슬라이스만 사용하면 원래 배열과 동일한 랭크의 배열이 생성됩니다.

row_r1 = a[1, :]    # a의 두 번째 행에 대한 랭크 1 뷰(View)
row_r2 = a[1:2, :]  # a의 두 번째 행에 대한 랭크 2 뷰(View)
row_r3 = a[[1], :]  # a의 두 번째 행에 대한 랭크 2 뷰(View)
print(row_r1, row_r1.shape)
print(row_r2, row_r2.shape)
print(row_r3, row_r3.shape)
[5 6 7 8] (4,)
[[5 6 7 8]] (1, 4)
[[5 6 7 8]] (1, 4)

열에 대한 데이터에 접근할 때도 동일한 구분을 할 수 있습니다:

col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print(col_r1, col_r1.shape)
print()
print(col_r2, col_r2.shape)
[ 2  6 10] (3,)
[[ 2]
 [ 6]
 [10]] (3, 1)

정수 배열 인덱싱 (Integer Array Indexing): 슬라이싱을 사용하여 numpy 배열에 인덱싱할 때 결과 배열은 항상 원래 배열의 하위 배열(View)입니다. 반면, 정수 배열 인덱싱을 사용하면 다른 배열의 데이터를 사용하여 임의의 배열을 구성할 수 있습니다.

a = np.array([[1,2], [3, 4], [5, 6]])

# 정수 배열 인덱싱의 예시입니다.
# a의 원소 중 값이 2보다 큰 원소를 찾아냅니다. 결과는 a와 동일한 모양의 불리언 배열입니다.
bool_idx = (a > 2)
print(bool_idx)
[[False False]
 [ True  True]
 [ True  True]]

불리언 배열 인덱싱을 사용하여 a에서 값이 2보다 큰 원소를 선택할 수 있습니다.

# 불리언 배열 인덱싱을 사용하여 a의 값이 2보다 큰 원소를 선택하여 랭크 1 배열을 만듭니다.
print(a[bool_idx])

# 위의 예시를 한 문장으로 간략하게 작성할 수도 있습니다.
print(a[a > 2])
[3 4 5 6]
[3 4 5 6]

더 자세한 내용은 numpy 배열 인덱싱에 대한 문서를 참조하세요.

데이터 타입 (Datatypes)

Numpy 배열은 항상 동일한 유형의 요소로 구성됩니다. Numpy는 배열을 만들 때 데이터 타입을 추측하지만, 배열을 생성하는 함수에는 데이터 타입을 명시적으로 지정할 수 있는 옵션 인수도 포함됩니다. 예를 들어:

x = np.array([1, 2])  # Numpy가 데이터 타입을 자동으로 선택하게 합니다.
y = np.array([1.0, 2.0])  # Numpy가 데이터 타입을 자동으로 선택하게 합니다.
z = np.array([1, 2], dtype=np.int64)  # 특정 데이터 타입을 강제로 지정합니다.

print(x.dtype, y.dtype, z.dtype)
int64 float64 int64

모든 numpy 배열은 동일한 데이터 타입의 요소로 구성되며, 데이터 타입을 명시적으로 지정할 수 있습니다. numpy 데이터 타입에 대한 자세한 내용은 numpy 문서를 참조하세요.

배열 수학 (Array Math)

기본 수학 함수는 배열의 각 요소에 대해 작동하며, 연산자 오버로드로도 사용할 수 있으며 numpy 모듈의 함수로도 사용할 수 있습니다:

x = np.array([[1,2],[3,4]], dtype=np.float64)
y = np.array([[5,6],[7,8]], dtype=np.float64)

# 요소별 합; 두 방법 모두 배열을 생성합니다.
print(x + y)
print(np.add(x, y))
[[ 6.  8.]
 [10. 12.]]
[[ 6.  8.]
 [10. 12.]]
# 요소별 차; 두 방법 모두 배열을 생성합니다.
print(x - y)
print(np.subtract(x, y))
[[-4. -4.]
 [-4. -4.]]
[[-4. -4.]
 [-4. -4.]]
# 요소별 곱; 두 방법 모두 배열을 생성합니다.
print(x * y)
print(np.multiply(x, y))
[[ 5. 12.]
 [21. 32.]]
[[ 5. 12.]
 [21. 32.]]
# 요소별 나눗셈; 두 방법 모두 배열을 생성합니다.
# [[ 0.2         0.33333333]
#  [ 0.42857143  0.5       ]]
print(x / y)
print(np.divide(x, y))
[[0.2        0.33333333]
 [0.42857143 0.5       ]]
[[0.2        0.33333333]
 [0.42857143 0.5       ]]
# 요소별 제곱근; 배열을 생성합니다.
# [[ 1.          1.41421356]
#  [ 1.73205081  2.        ]]
print(np.sqrt(x))
[[1.         1.41421356]
 [1.73205081 2.        ]]

MATLAB과 달리 *는 요소별 곱셈이며 행렬 곱셈이 아닙니다. 행렬의 내적, 행렬과 벡터의 곱셈, 행렬 곱셈을 계산하려면 dot 함수를 사용합니다. dot 함수는 numpy 모듈의 함수로 사용하거나 배열 객체의 인스턴스 메서드로 사용할 수 있습니다:

Copy code
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])

v = np.array([9,10])
w = np.array([11, 12])

# 벡터의 내적; 두 방법 모두 스칼라 219를 생성합니다.
print(v.dot(w))
print(np.dot(v, w))
219
219

numpy의 dot 연산자(@)를 사용하여도 동일한 결과를 얻을 수 있습니다:

print(v @ w)
219
# 행렬 / 벡터 곱셈; 두 방법 모두 랭크 1 배열 [29 67]을 생성합니다.
print(x.dot(v))
print(np.dot(x, v))
print(x @ v)
[29 67]
[29 67]
[29 67]
# 행렬 / 행렬 곱셈; 두 방법 모두 랭크 2 배열을 생성합니다.
# [[19 22]
#  [43 50]]
print(x.dot(y))
print(np.dot(x, y))
print(x @ y)
[[19 22]
 [43 50]]
[[19 22]
 [43 50]]
[[19 22]
 [43 50]]

Numpy는 배열을 사용한 계산을 수행하기 위한 많은 유용한 함수를 제공합니다. 그 중에서도 가장 유용한 함수 중 하나는 sum입니다:

x = np.array([[1,2],[3,4]])

# 모든 원소의 합 계산; "10"을 출력합니다.
print(np.sum(x))

# 각 열의 합 계산; "[4 6]"을 출력합니다.
print(np.sum(x, axis=0))

# 각 행의 합 계산; "[3 7]"을 출력합니다.
print(np.sum(x, axis=1))

모든 수학 함수의 전체 목록은 numpy 문서에서 찾을 수 있습니다.

수학 함수를 배열에 대해 계산하는 것 외에도 데이터를 다시 모양을 변경하거나 다른 방식으로 조작해야 할 때가 자주 있습니다. 가장 간단한 예는 행렬의 전치(transpose)입니다. 행렬을 전치하려면 배열 객체의 T 속성을 사용하면 됩니다:

print(x)
print("전치\n", x.T)
[[1 2]
 [3 4]]
전치
[[1 3]
 [2 4]]

브로드캐스팅 (Broadcasting)

브로드캐스팅은 numpy가 산술 연산을 수행할 때 다른 모양의 배열과 함께 작업할 수 있게 하는 강력한 메커니즘입니다. 종종 더 작은 배열과 더 큰 배열이 있고 작은 배열을 여러 번 큰 배열에 적용하여 어떤 연산을 수행하려고 합니다.

예를 들어, 행렬의 각 행에 상수 벡터를 더하려고 한다고 가정해보겠습니다. 이를 다음과 같이 수행할 수 있습니다:

# x 행렬의 각 행에 v 벡터를 더하고 결과를 y 행렬에 저장합니다.
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x)   # x와 동일한 모양의 빈 행렬을 생성합니다.

# 명시적 루프를 사용하여 x 행렬의 각 행에 v 벡터를 더합니다.
for i in range(4):
    y[i, :] = x[i, :] + v

print(y)
[[ 2  2  4]
 [ 5  5  7]
 [ 8  8 10]
 [11 11 13]]

이렇게 작업은 잘 작동하지만, 행렬 x가 매우 큰 경우 파이썬에서 명시적 루프를 계산하는 것은 느릴 수 있습니다. 상수 벡터를 행렬 x의 각 행에 더하는 것은 사실 v를 수직으로 여러 번 쌓아서 vv 행렬을 형성한 다음 x와 vv의 요소별 합을 계산하는 것과 동일합니다. 이 접근 방식을 다음과 같이 구현할 수 있습니다:

vv = np.tile(v, (4, 1))  # v 벡터를 세로로 4번 복사하여 vv 행렬을 만듭니다.
print(vv)
[[1 0 1]
 [1 0 1]
 [1 0 1]
 [1 0 1]]

그런 다음 x와 vv의 요소별 합을 계산합니다:

y = x + vv  # x와 vv의 요소별 합 계산
print(y)
[[ 2  2  4]
 [ 5  5  7]
 [ 8  8 10]
 [11 11 13]]

Numpy 브로드캐스팅을 사용하면 v를 여러 번 복사하지 않고도 이 계산을 수행할 수 있습니다. 다음은 브로드캐스팅을 사용한 예제입니다:

# x 행렬에 v 벡터를 각 행에 더하는 작업을 수행합니다.
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = x + v  # 브로드캐스팅을 사용하여 x와 v의 요소별 합을 계산합니다.
print(y)
[[ 2  2  4]
 [ 5  5  7]
 [ 8  8 10]
 [11 11 13]]

브로드캐스팅을 사용하면 코드가 더 간결하고 이해하기 쉬워집니다. 또한 브로드캐스팅은 더 높은 차원의 배열에도 적용됩니다.

이외에도 Numpy에서는 다양한 수학 함수, 선형 대수, 통계, 난수 생성 등을 다룰 수 있습니다. 이러한 내용에 대한 자세한 정보는 Numpy 공식 문서를 참조하시기 바랍니다.

출처 CS231n Python, Numpy Tutorial, Colab Link

0개의 댓글