Post

[Python] Numpy (2)


[Python] Numpy

  • 본 포스팅은 Numpy 라이브러리의 배열의 인덱싱, 연산, 그리고 Broadcasting에 관하여 다룹니다.
  • 정수 배열 인덱싱 / boolean 배열 인덱싱 / 배열의 자료형
  • 배열 연산 : add / subtract / multiply / divide / sqrt / dot
  • Broadcasting

Hello! Numpy

1
import numpy as np

정수 배열 인덱싱

Numpy 배열을 슬라이싱하면, 결과로 얻어지는 배열은 언제나 기존 배열의 부분 배열이다.
그러나 정수 배열 인덱싱을 한다면, 원본과 다른 배열을 만들 수 있다.

1
2
a = np.array([[1, 2], [3, 4], [4, 5]])
a
1
2
3
array([[1, 2],
       [3, 4],
       [4, 5]])
  • array_obj[[row1, row2, row3], [col1, col2, col3]]
1
a[[0, 1, 2], [0, 1, 0]]
1
array([1, 4, 4])
  • np.array([ array_obj[row1, col1], array_obj[row2, col2], array_obj[row3, col3] ])
1
np.array([a[0, 0], a[1, 1], a[2, 0]])
1
array([1, 4, 4])

다음과 같이 선언하여 객체에 넣게되면 튜플형태로 반환된다.

1
2
3
b = a[0, 0], a[1, 1], a[2, 0]
print (b)
print (type(b))
1
2
(1, 4, 4)
<class 'tuple'>

정수 배열 인덱싱을 가장 유용하게 사용하는 방법 중 하나는
행렬의 각 행에서 하나의 요소를 선택하거나 바꾸는 것이다

요소를 선택할 새로운 배열을 생성한다.

1
2
3
a = np.arange(1, 13)
a.shape = (4, 3)
a
1
2
3
4
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])

인덱스를 저장할 배열 생성

1
2
b = np.array([0, 2, 0, 1])
b
1
array([0, 2, 0, 1])

b에 저장된 인덱스를 이용해 각 행에서 하나의 요소를 선택한다.
다음의 예시는

1
a[[0, 1, 2, 3], [0, 2, 0, 1]] 

과 동일하다

1
a[np.arange(4), b]
1
array([ 1,  6,  7, 11])

이를 응용하여, b에 저장된 인덱스를 이용해 각 행에서 하나의 요소를 변경할 수 있다.

1
2
a[np.arange(4), b] += 10
a
1
2
3
4
array([[31,  2,  3],
       [ 4,  5, 36],
       [37,  8,  9],
       [10, 41, 12]])

boolean 배열 인덱싱

Boolean 배열 인덱싱을 통해 배열안에 있는 요소들을 선택할 수 있다.
해당 인덱싱은 특정 조건을 만족하게하는 요소만 선택하고자 할 때 자주 사용된다.

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

다음의 예시는 boolean배열 인덱싱의 예시이다.
2보다 큰 a의 요소를 찾고자 할때 다음과 같이 수행한다.

1
2
bool_idx = (a > 2)
bool_idx
1
2
3
array([[False, False],
       [ True,  True],
       [ True,  True]])

이를 기존 배열에서 True인 값을 가지는 요소로 구성되는 rank 1인 배열을 출력할 수 있다.

1
a[bool_idx]
1
array([3, 4, 4, 5])

위에서 설명된 것들을 한 단락으로 정리하면 다음과 같다.

1
a[a > 2]
1
array([3, 4, 4, 5])

자료형

Numpy가 자료형을 추측해서 선택한다.

1
2
3
# 정수형
x = np.array([1, 2])
x.dtype
1
dtype('int32')
1
2
3
# 실수형
x = np.array([1. , 2.])
x.dtype
1
dtype('float64')

특정 자료형을 명시적으로 지칭하여 사용할 수도 있다.

1
2
3
# 정수형으로 작성했지만, 자료형을 float으로 명시하여 float자료형의 value가 x값에 대입된다.
x = np.array([1, 2], dtype = np.float64)
x
1
array([1., 2.])

배열 연산

기본적인 수학 함수들은 배열의 각 요소별로 동작하며
연산자를 통해 동작하거나 numpy 함수 모듈을 통해 동작한다.

1
2
3
4
5
# 연습용 배열 생성
x = np.arange(4, dtype = np.float64).reshape(2,2)
y = np.arange(4, 8, dtype = np.float64).reshape(2,2)
print(x)
print(y)
1
2
3
4
[[0. 1.]
 [2. 3.]]
[[4. 5.]
 [6. 7.]]
  • Array 합
    • 연산자 : +
    • numpy 함수 : np.add()
1
x + y
1
2
array([[ 4.,  6.],
       [ 8., 10.]])
1
np.add(x, y)
1
2
array([[ 4.,  6.],
       [ 8., 10.]])
  • Array 차
    • 연산자 : -
    • numpy 함수 : np.subtract()
1
x - y
1
2
array([[-4., -4.],
       [-4., -4.]])
1
np.subtract(x, y)
1
2
array([[-4., -4.],
       [-4., -4.]])
  • Array 곱
    • 연산자 : *
    • numpy 함수 : np.multiply()
1
x * y
1
2
array([[ 0.,  5.],
       [12., 21.]])
1
np.multiply(x, y)
1
2
array([[ 0.,  5.],
       [12., 21.]])
  • Array 나눗셈
    • 연산자 : /
    • numpy 함수 : np.divide()
1
x / y
1
2
array([[0.        , 0.2       ],
       [0.33333333, 0.42857143]])
1
np.divide(x, y)
1
2
array([[0.        , 0.2       ],
       [0.33333333, 0.42857143]])
  • Array 제곱근
    • numpy 함수 : np.sqrt()
1
np.sqrt(x)
1
2
array([[0.        , 1.        ],
       [1.41421356, 1.73205081]])
1
np.sqrt(y)
1
2
array([[2.        , 2.23606798],
       [2.44948974, 2.64575131]])

또한 Numpy는 백터의 내적이나 백터 곱 그리고 열과 행을 합산하는 등의 유용한 함수를 제공한다.

1
2
3
4
5
6
# 백터의 내적/곱을 실습하기위한 배열 생성
x = np.array([[1,2], [3, 4]])
y = np.array([[5,6], [7, 8]])

v = np.array([9, 10])
w = np.array([11, 12])
  • dot()
    • 백터의 내적
    • 행렬과 백터 곱
1
2
3
# 백터의 내적
print(v.dot(w))
print(np.dot(v, w))
1
2
219
219
1
2
3
# rank 2 행렬과 rank 1 백터 곱
print(x.dot(v))
print(np.dot(x, v))
1
2
[29 67]
[29 67]
1
2
3
# rank 2 행렬과 rank 2 행렬 곱
print(x.dot(y))
print(np.dot(x, y))
1
2
3
4
[[19 22]
 [43 50]]
[[19 22]
 [43 50]]
  • sum()
    • 배열안의 요소들을 합산하고 싶을 때 사용한다.
    • axis = 0 : 각 열에 대한 합산을 연산하고 싶을 경우
    • axis = 1 : 각 행에 대한 합산을 연산하고 싶을 경우
1
2
# axis 옵션을 사용하지 않을 경우 단순히 모든 요소들을 합산한다.
np.sum(x)
1
10
1
2
# 열에 대한 합산
np.sum(x, axis = 0)
1
array([4, 6])
1
2
# 행에 대한 합산
np.sum(x, axis = 1)
1
array([3, 7])

Numpy Broadcasting

파이썬에서 말하는 Broadcasting은 방송이라는 명사적 의미보다는 spread라는 동사적의미가 강하다.
일반적으로는 다음과 같이 numpy로 생성된 사이즈가 다른 배열끼리는 연산이 불가능하다.

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

x + y
1
2
3
4
5
6
7
8
9
10
11
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-104-6295914b1607> in <module>
      2 y = np.array([[1, 2]])
      3 
----> 4 x + y


ValueError: operands could not be broadcast together with shapes (1,4) (1,2) 

위의 에러 문구를 읽어보면 broadcast가 되지 못했다는 의미를 가지고 있다.
즉, 어떤 조건을 만족한다면 사이즈가 다른 배열 끼리의 연산도 가능하다는 말이다.

이때, Broadcastingnumpy에서 사이즈가 다른 배열 간에도 산술 연산이 가능하게한다.
종종 작은 배열과 큰 배열이 있을 때, 큰 배열을 대상으로 작은 배열을 여러 번 연산하고자 할 때 사용한다.
예를 들자면, 행렬의 각 행에 상수 벡터를 더하는 것을 생각하면된다.

다음의 같은 결과들을 출력하는 순차적인 예제들을 살펴보자

행렬 x의 각 행에 백터 v를 더한 뒤,
그 결과를 행렬 y에 저장하고자 한다.

[Process1] 반복문을 이용한 방법

일반적으로는 for문을 이용해서 간단하게 결과를 출력할 수 있다.

1
2
3
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와 동일한 shape를 가지며 비어있는 행렬 생성

반복문을 통하여 행렬 x의 각 행에 v를 더한다

1
2
for i in range(4):
    y[i, :] = x[i, :] + v
1
y
1
2
3
4
array([[ 2,  2,  4],
       [ 5,  5,  7],
       [ 8,  8, 10],
       [11, 11, 13]])

하지만, 위의 방식대로하면 `x`가 만약 매우 큰 행렬이라면,
위처럼 반복문을 이용하면 매우 느려질 수 있다.

[Process2] tile함수를 이용한 방법

이때, 백터v를 행렬x의 각 행에 대하는 것을
v를 여러개 복사해 수직으로 쌓는 행렬을 만들고 앞과 같은 과정을 수행가능하다.

1
2
3
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
x
1
2
3
4
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])

v를 차곡차곡 tile함수를 이용하여 쌓는다.

1
2
vv = np.tile(v, (4, 1))
vv
1
2
3
4
array([[1, 0, 1],
       [1, 0, 1],
       [1, 0, 1],
       [1, 0, 1]])
1
x + vv
1
2
3
4
array([[ 2,  2,  4],
       [ 5,  5,  7],
       [ 8,  8, 10],
       [11, 11, 13]])

하지만, NumpyBroadcasting을 이용하면 위처럼 v의 복사본을 여러 개 쌓아서 만들지 않아도 같은 연산을 수행할 수 있다.

[Process3] Numpy의 Broadcasting을 이용한 방법

1
2
x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
v = np.array([1, 0, 1])

x의 사이즈가 (4, 3)이고, v의 사이즈가 (3, )지만 Broadcasting으로 인해 문제없이 아래와 같이 연산이 수행된다.

1
x + v
1
2
3
4
array([[ 2,  2,  4],
       [ 5,  5,  7],
       [ 8,  8, 10],
       [11, 11, 13]])

이때 vv의 복사본이 차곡차곡 쌓인 shape(4,3) 처럼 간주되어 x와 동일한 사이즈가 되며 두 배열 간의 요소별 덧셈연산이 가능해진 것이다.

12

또한, Broadcasting은 다음의 규칙을 따른다.

  • 두 배열이 동일한 차원을 가지고 있지 않다면, 낮은 차원의 배열이 높은 차원의 배열과 같은 차원의 배열로 인식된다.

  • 반환된 배열은 연산을 수행한 배열 중 rank가 가장 큰 배열과 같다.

  • broadcasting이 적용된 배열의 사이즈는 연산에 사용된 배열들의 사이즈에 대한 최소 공배수 값을 사용한다.

  • 요소가 하나인 배열은 어떤 배열에나 broadcasting을 적용할 수 있다. ex) 4X4 + 1

  • 하나의 배열이 차원이 1인 경우 broadcasting을 적용할 수 있다. ex) 4X4 + 1X4

  • 차원의 짝이 맞을 때 broadcasting을 적용할 수 있다. ex) 3X1 + 1X3


References

  • http://aikorea.org/cs231n/python-numpy-tutorial/#numpy-arrays
  • https://sacko.tistory.com/16
  • https://appia.tistory.com/184
  • https://076923.github.io/posts/Python-numpy-12/
This post is licensed under CC BY 4.0 by the author.