지난 포스팅에선 NumPy
를 통한 배열(array) 핸들링을 어떻게 효과적으로 할 수 있을까에 대해서 알아보았다.
이번 포스팅 또한 data handling
에 근간이 되는 배열 연산에 대해서 알아보고자 한다.
Numpy에 관련해서 3편의 포스팅을 올리는 동안에 혹자는 “어차피 데이터 핸들링 다 pandas
로 처리하는데 왜 NumPy
를 배워? pandas
만 하면 되지” 이런 생각을 할 수 있으리라 생각한다. 하지만 pandas
의 기본적인 자료구조가 numpy
에서 파생되었고, 결국 데이터를 저장하는 방식이 numpy
방식이다. 그렇기때문에 pandas
활용성을 높여주는 아주 근간이 되는 라이브러리라고 생각한다.
서두가 길었다!
바로 코드를 통해 예제를 살펴보자.
Numpy 연산
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
# 연산을 위해 두개의 ndarray를 만들어주자.
arr1 = np.array([[1, 2, 3],
[4, 5, 6]]) # 2x3
arr2 = np.arange(10, 16, 1).reshape(2, 3).copy() # view 말고 원본을 원하니깐 copy() 메서드를 뒤에 호출함
# 두개를 더하면 어떨까?
# 1 2 3 10 11 12 => 11 13 15
# 4 5 6 13 14 15 => 17 19 21
# 이런 모습을 우리는 기대한다.
# 자 단순하게 더하기를 해주면된다.
# shape이 일치한다면*** 요소끼리 (element wise) 더해진다.
# shape이 일치하지 않는다면 에러를 리턴한다.
print(arr1 + arr2)
# 출력물:
# [[11 13 15]
# [17 19 21]]
그렇다면 위의 ndarray
더하기와 파이썬 list
의 더하기는 어떻게 다를까?
1
2
3
4
5
6
a = [1, 2, 3] # 파이썬의 리스트
b = [4, 5, 6]
print(a + b)
# 출력물:
# [1, 2, 3, 4, 5, 6] 이렇게 리스트가 이어지게 된다. (concatenate)
파이썬 리스트의 연산은 연결성(concatenateness)가 가장 큰 특징이다. 반면, ndarray
는 우리가 수학시간에 배웠던 vector calcuation을 따르는 경향이 있다.
음 여기까진 단순히 저거 구분만 하면 되겠네?
shape
만 맞춰주면 ndarray끼리의 연산은 문제 없겠네? 라고 생각할 수 있다.
문제는 행렬 곱 (matrix multiplication
)에서 나온다.
한번 살펴보자.
행렬 곱 (Matrix Multiplication)
선형대수학을 배웠거나 아직 고교과정에서 배웠던 matrix
에 대한 지식이 남아있다면 별로 문제될 거 없이 쉽게 해결할 수 있지만, 그렇지 않다면 어느정도 행렬 곱에 대한 공부를 하고 오길 바란다.
특히나 머신러닝같은 경우에는 2차원이상의 차원을 가장 많이 접할텐데 2차원에서 행렬에 관련된 연산은 필수적이기 때문이다.
거두절미하고 예제를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
a = np.array([[1, 2],
[3, 4],
[5, 6]]) # 3x2
b = np.array([[1, 2, 3],
[4, 5, 6]]) # 2x3
# 이렇게 3x2 매트릭스 a가 있고, 2x3 매트릭스 b가 있다.
# 행렬곱의 가장 기본 원칙은 (m x n) x (n x k) = (m x k) 인데, 무슨말이냐하면
# 앞선 행렬의 열의 개수가 곱하고자 하는 행렬의 행의 개수와 같아야한다.
# 따라서 우리의 경우에는
# (3x2) x (2x3) => (3x3) 의 행렬로 결과가 나오게 된다.
# 행렬곱 연산은 또 일반 곱하기 기호를 사용하여 하는것이 아니고
# numpy만의 함수가 있다.
# numpy의 matmul이라는 함수가 있다. (matrix multiplication의 약자임)
# 결괏값을 살펴보자.
print(np.matmul(a, b)) # 3x3
# 출력물은
# [[ 9 12 15]
# [19 26 33]
# [29 40 51]] 이다.
# 와우.. 저 요소들은 어디서 나왔나요?
# 음 이건 수학적 수식을 통해 쉽게 설명할 수 있는데
# 지금 당장은 설명을 스킵하겠다.
자 그럼 이번에는 행렬 곱을 boolean indexing
과 같이 사용해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
arr1 = np.array([[1, 2, 3], [20, 21, 22]]) # 2x3
# arr1:
# [[1 2 3]
# [20 21 22]]
arr2 = np.arange(10, 16, 1).reshape(2,3).copy() # 2x3
# view가 아닌 원본을 만들고자 뒤에 .copy()를 추가적으로 호출했다.
# arr2:
# [[10 11 12]
# [13 14 15]]
print(arr1 > arr2) # mask로 나옴
# 출력물:
# [[False False False]
# [True True True]]
# 이제 boolean indexing 을 사용해보자
print(arr1[ arr1 > arr2 ])
# 도움이 될진 모르겠지만, 필자는 해당 명령어를 사용할 때
# "파이썬아, arr1을 출력하는데 arr1 요소중 에서 arr2보다 큰 것만 출력해!"
# 이렇게 생각하면 쉽게 이해가 되었다.
# 출력물:
# [20 21 22]
“근데 왜 이렇게까지 해요?” 라고 생각할 수 있다.
거듭 말하지만, data handling의 가장 기초이며 머신러닝 모델을 많이 알고 뭐 완벽한 모델이 있다한들 데이터 정제를 제대로 하지 못하면 결국엔 빛좋은 개살구에 지나지 않는다. 왜냐고? 결괏값이 어차피 틀렸을테니깐.
Broadcasting
이번 포스팅에서 다뤄볼 마지막 주제는 broadcasting이다.
이친구가 하는 역할은 shape
을 “억지로” 일치시켜 행렬 연산을 하게끔 도와준다. 음.. 선형대수학을 제대로 배운 독자라면 “이게 뭔 🐶 소리야? 그렇게 얻은 값이 무슨 의미가 있는데? 수학적 논리를 거슬러서 얻은 값 아닌가?” 라고 생각할 수도 있다. 하지만 당장은 순수 수학의 입장에서 받아들이지말고 “아 신기한 기능도 있구만?ㅎㅎ” 이렇게 생각하며 넘어가도록 하자.
바로 예제 코드를 보도록하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 두개의 array가 주어졌다.
arr1 = np.array([[1, 2, 3],
[4, 5, 6]]) # 2x3
arr2 = np.array([1, 2, 3]) # 1차원 1x3
# 1 2 3 1 2 3
# 4 5 6
# 이렇게 shape이 각기 다른 친구들을 더한다면 어떻게 될까?
# 에러가 날까? 한번 확인해보자.
print(arr1 + arr2)
# 출력물:
# [[2 4 6]
# [5 7 9]]
# 아마 짐작했겠지만, [1 2 3] 이 2행으로 추가되어
# [[1 2 3]
# [1 2 3]] 이렇게 2x3 형태로 만들어져서 연산이 되었음을 알 수 있다.
# 이걸 broadcasting이라고 일컫는다.
그렇지만 broadcasting
을 모든 배열에 적용시킬 수 있는 것은 아니다. 가능한 케이스가 있고 또 불가능한 케이스가 있다.
(2x3) 행렬과 (1x2) 행렬을 연산할 수 있는가? 되게 애매하다. 행과 열 모두 다르기 때문이다. 실제로 이건 불가능하기도 하다.
Broadcasting은 한쪽의 행 또는 열이 연산하고자 하는 행렬의 행이나 열의 수와 일치해야함을 기억하자.
오늘 numpy
연산에 관한 포스팅은 여기서 마치도록 하겠다. 👋