정리 노트

2일차(2022/07/05) 본문

[TIL]국민대X프로그래머스 여름방학 인공지능 과정

2일차(2022/07/05)

꿈만 꾸는 학부생 2022. 7. 5. 21:05
728x90

오늘은 Numpy 모듈이 무엇인지 보고 이를 사용하는 방법에 대해 배웠습니다.

Numpy를 쓰는 이유?

사실 numpy를 쓴다고 하면 '아 쓰나 보다' 하고 아무렇지 않게 생각했지 이걸 써야 하는 이유에 대해 물으면 잘 몰랐습니다. numpy를 사용하면 필요한 연산들은 C로 만들어져 있기 때문에 python의 list를 사용하는 것보다 numpy의 array를 사용하는 것이 더 빠르다고 합니다. 아래의 방법을 통해 연산 시간을 간단하게 확인할 수 있었습니다.

import numpy as np

my_list = range(1000)
%timeit [i**2 for i in my_list]    # %timeit은 timeit 모듈을 사용하겠다는 jupyter notebook 만의 문법입니다.

arr = np.arange(1000)
%timeit n**2

my_list를 사용했을 때는 한 번의 loop 마다 최대 224.1㎲ 걸렸고, arr를 사용했을 때는 한 번의 loop 마다 최대 1.38㎲ 걸렸습니다. 220배 넘게 차이가 났습니다. 이래서 numpy를 쓰나 봅니다.

numpy.array에서의 인덱싱

numpy.array에서도 파이썬의 리스트와 같이 인덱싱 기능을 제공합니다. 인덱싱 방식도 유사합니다.

둘의 차이는 numpy.array가 행렬의 범위로 커질 때 발생합니다. 파이썬의 이차원 배열에서는 []을 두 번 사용했다면, numpy.array에서의 행렬은 하나의 []안에서 쉼표로 행과 열을 구분합니다.

import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a[0, 0], a[2, 2], a[1, 2])

a[0, 0]은 0행 0열의 값, a[1, 2]는 1행 2열의 값, a[2, 2]는 2행 2열의 값을 나타내므로 print 결과는 각각 1, 9, 6입니다.

인덱싱을 활용해 특정 열 또는 특정 행의 정보만 추출해올 수 있습니다.

print(a[:, 1], a[:2])

a[:, 1]은 모든 행의 범위에서 1열만 추출하므로 [[2], [5], [8]]이 출력되고, a[:2]는 1행까지의 정보만 추출하므로 [[1, 2, 3], [4, 5, 6]]이 출력됩니다.

numpy.array의 Broadcasting

피연산자가 연산을 할 수 없는 size를 가지고 있다면 다른 피연산자에 맞춰 자신을 resizing 하는 것을 Broadcasting이라고 합니다. 아래의 예제들을 보면 이해하기 더 쉽습니다.

import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
x = np.array([1, 0, 1])    # 현재 x는 행 벡터
x = x[:, None]    # x를 열 벡터러 변환
print(a + x)

y = np.array([1, 0, -1])
print(a * y)

t = np.array([1, 2, 3])
t = t[:, None]
u = np.array([1, -1, 0])
print(t + u)

a는 3 * 3 행렬이고, x는 3 * 1 행렬(벡터)입니다. 이 둘은 element-wise 덧셈이 불가능합니다. 하지만 numpy.array에서의 broadcasting을 하면 x는 자신이 갖고 있는 값을 복사해 a와 같은 size로 resizing 합니다. 즉 x는 [[1, 1, 1], [0, 0, 0], [1, 1, 1]]의 형태가 됩니다. 따라서 a + x를 실행하면 [[2, 3, 4], [4, 5, 6], [8, 9, 10]]이 출력됩니다.

y는 1 * 3 행렬(벡터)입니다. y는 a와 element-wise 곱셈을 할 수 없는 size이지만 broadcasting을 통해 [[1, 0, -1], [1, 0, -1], [1, 0, -1]]의 형태가 됩니다. 따라서 a * y를 실행하면 [[1, 0, -3], [4, 0, -6], [7, 0, -9]]가 출력됩니다.

t와 u는 각각 3 * 1 행렬, 1 * 3 행렬입니다. 이 경우 두 행렬이 서로에 맞춰 resizing을 진행합니다. 만약 t가 먼저 broadcasting을 한다면, t는 [[1, 1, 1], [2, 2, 2], [3, 3, 3]]이 되고 u가 t에 맞춰 [[1, -1, 0], [1, -1, 0], [1, -1, 0]]로 됩니다. 결국 두 3 * 3 행렬의 element-wise 덧셈이 이루어져 [[2, 0, 1], [3, 1, 2], [4, 2, 3]]이 출력됩니다.

Numpy로 선형대수 하기

numpy에서는 선형 대수를 할 수 있도록 많은 지원을 해줍니다.

numpy.zeros(dim) 영행렬을 제공, dim은 숫자 또는 튜플(, )
numpy.ones(dim) 일행렬을 제공, dim은 숫자 또는 튜플(, )
numpy.diag((main_diagonals)) (main_diagonals)를 가지는 대각 행렬을 제공
numpy.eye(n) 사이즈가 n * n인 항등행렬을 제공
numpy.array.dot(numpy.array) or '@' 행렬 간의 곱 연산을 제공
numpy.array.trace() 행렬의 Main Diagonal의 합을 제공
numpy.linalg.det(numpy.array) 행렬의 행렬식을 제공
numpy.linalg.inv(numpy.array) 행렬 A에 대해 AB = BA = I를 만족하는 행렬 B 제공
numpy.linalg.eig(numpy.array) 정방행렬 A에 대해 Ax = (lambda)x를 만족하는 (lambda)와 x를 제공

아래에 사용 예시들을 적었습니다.

user@<your_name>:~$ python3
Python 3.8.10 (default, Mar 15 2022, 12:22:08) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> np.zeros(3)
array([0., 0., 0.])
>>> np.zeros((2, 1))
array([[0.],
       [0.]])
>>> np.ones(4)
array([1., 1., 1., 1.])
>>> np.ones((4, 4))
array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])
>>> np.diag((1, 2, 3, 4))
array([[1, 0, 0, 0],
       [0, 2, 0, 0],
       [0, 0, 3, 0],
       [0, 0, 0, 4]])
>>> np.diag((1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<__array_function__ internals>", line 5, in diag
  File "/usr/lib/python3/dist-packages/numpy/lib/twodim_base.py", line 285, in diag
    raise ValueError("Input must be 1- or 2-d.")
ValueError: Input must be 1- or 2-d.
>>> numpy.eye(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'numpy' is not defined
>>> np.eye(2)
array([[1., 0.],
       [0., 1.]])
>>> matrix1 = np.array([[0, 1, 2], [1, 2, 3]])
>>> matrix2 = np.array([[0, 1, 2], [1, 2, 3], [2, 3, 4]])
>>> matrix1 @ matrix2
array([[ 5,  8, 11],
       [ 8, 14, 20]])
>>> matrix1.trace()
2
>>> matrix2.trace()
6
>>> np.linalg.det(matrix1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<__array_function__ internals>", line 5, in det
  File "/usr/lib/python3/dist-packages/numpy/linalg/linalg.py", line 2122, in det
    _assertNdSquareness(a)
  File "/usr/lib/python3/dist-packages/numpy/linalg/linalg.py", line 213, in _assertNdSquareness
    raise LinAlgError('Last 2 dimensions of the array must be square')
numpy.linalg.LinAlgError: Last 2 dimensions of the array must be square
>>> np.linalg.det(matrix2)
0.0
>>> np.linalg.inv(matrix1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<__array_function__ internals>", line 5, in inv
  File "/usr/lib/python3/dist-packages/numpy/linalg/linalg.py", line 546, in inv
    _assertNdSquareness(a)
  File "/usr/lib/python3/dist-packages/numpy/linalg/linalg.py", line 213, in _assertNdSquareness
    raise LinAlgError('Last 2 dimensions of the array must be square')
numpy.linalg.LinAlgError: Last 2 dimensions of the array must be square
>>> matrix3 = [[3, 8], [7, 1]]
>>> np.linalg.inv(matrix3)
array([[-0.01886792,  0.1509434 ],
       [ 0.13207547, -0.05660377]])
>>> matrix3 @ np.linalg.inv(matrix3)
array([[1., 0.],
       [0., 1.]])
>>> np.linalg.eig(matrix2)
(array([ 6.87298335e+00, -8.72983346e-01, -7.69038628e-16]), array([[-0.30646053, -0.8598926 ,  0.40824829],
       [-0.54384383, -0.19382266, -0.81649658],
       [-0.78122713,  0.47224729,  0.40824829]]))
728x90

'[TIL]국민대X프로그래머스 여름방학 인공지능 과정' 카테고리의 다른 글

6일 차(2022/07/11)  (0) 2022.07.12
5일 차(2022/07/08)  (0) 2022.07.08
4일 차(2022/07/07)  (0) 2022.07.07
3일차(2022/07/06)  (0) 2022.07.06
1일차(2022/07/04)  (0) 2022.07.05