numpy는 python에서 진행되는 모든 데이터 분석과 AI 엔지니어링에 있어 가장 기초적이며 필수적으로 사용되는 패키지입니다. Numerical Python의 약자로, 일반적으로 과학 계산에서 많이 사용하는 선형대수의 계산식을 python에서 구현할 수 있도록 도와줍니다.
numpy는 패키지 자체의 기능으로도 많이 사용되지만 이후에 사용되는 scipy나 pandas의 base 객체로도 사용되며, numpy의 특유 문법이 그대로 딥러닝 프레임워크인 pytorch나 tensorflow에서도 사용되기 때문에 numpy 사용에 익숙해지는 것이 중요합니다.
import numpy as np
arr = np.array(['1', '2', 3.0, 4, 5.01], float)
type(arr)
-> numpy.ndarray
type(arr[0]) # 모두 정의된 float으로 변환
-> numpy.float64
# List
a = [1,2,3,4,5]
b = [5,4,3,2,1]
a is b
-> False
a[0] is b[-1] # 1 이라는 값을 참고하는 메모리 주소가 같기 때문에
-> True
# Array
a = np.array(a)
b = np.array(b)
a[0] is b[-1] # 값이 다른 메모리에 저장되어 있기 때문에
-> False
Rank | Name | Example |
---|---|---|
0 | scalar | 7 |
1 | vector | [10, 10] |
2 | matrix | [[10, 10], [15, 15]] |
3 | 3-tensor | [[[1, 5, 9], [2,6,10]], [[3, 7, 11], [4, 8, 12]]] |
n | n-tensor |
np.array([[1,2,3], [4.5, 5, 6]], dtype = np.float32).nbytes
-> 24 (6 * 4bytes)
np.array([[1,2,3], [4.5, 5, 6]], dtype = np.int8).nbytes
-> 6 (6 * 1bytes)
np.array([[1,2,3], [4.5, 5, 6]], dtype = np.np.float64).nbytes
-> 48 (6 * 8bytes)
❗️ 딥러닝 프레임워크에서 학습 데이터의 벡터를 받을 때는 보통 float32를 많이 사용하게 됩니다. float64가 float32에 비해 훨씬 정확하게 숫자를 나타낼 수 있고 더 큰 숫자를 저장할 수 있다는 장점이 있지만, float32보다 2배의 메모리를 차지하기 때문에 일반적으로 수천수만개의 파라미터를 다루는 딥러닝에서는 float32를 보통 사용합니다.
# 메모리 비교
float32 = np.array([[i, i+1, i+2] for i in range(10000000)], dtype = np.float32)
float64 = np.array([[i, i+1, i+2] for i in range(10000000)], dtype = np.float64)
int8 = np.array([[i, i+1, i+2] for i in range(10000000)], dtype = np.int8)
pd.DataFrame(float32).info()
-> memory usage: 114.4 MB
pd.DataFrame(float64).info()
-> memory usage: 228.9 MB
pd.DataFrame(int8).info()
-> memory usage: 28.6 MB
# type 비교
np.array([0.123123121092874917265781263489], dtype = np.float32)[0]
-> 0.123123124
np.array([0.123123121092874917265781263489], dtype = np.float64)[0]
-> 0.12312312109287492
np.array([0.123123121092874917265781263489], dtype = np.int8)[0]
-> 0
reshape, array의 element의 개수가 동일한 상태에서 shape의 size를 변경하는 방법
np.array([[1,2,3,4], [5,6,7,8]]).shape
-> (2, 4)
np.array([[1,2,3,4], [5,6,7,8]]).reshape(4, 2).shape
-> (4, 2)
np.array([[1,2,3,4], [5,6,7,8]]).reshape(-1, 1).shape
-> (8, 1)
np.array([[1,2,3,4], [5,6,7,8]]).reshape(1, -1, 2).shape
-> (1, 4, 2)
np.array([[1,2,3,4], [5,6,7,8]]).flatten().shape
-> (8, )
indexing
arr[행][열]
로 해당 값을 가져올 수 있음slicing
arr = np.array([[1,2,3,4], [5,6,7,8], [9, 10, 11, 12], [13, 14, 15, 16]])
-> array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16]])
arr[:, 2:] # 전체 행의 2열 이상
-> array([[ 3, 4],
[ 7, 8],
[11, 12],
[15, 16]])
arr[1, 1:3] # 1행의 1~2열
-> array([6, 7])
arr[1:3] # 1~2행
-> array([[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
arr[:, 1:2] # 1~2열
-> array([[ 2, 3],
[ 6, 7],
[10, 11],
[14, 15]])
arr[:, ::2] # 시작부터 2열씩
-> array([[ 1, 3],
[ 5, 7],
[ 9, 11],
[13, 15]])
arr[::2, ::3] # 시작부터 행은 2행씩, 열은 3열씩 겹치는 부분
-> array([[ 1, 4],
[ 9, 12]])
function | Description | function | Description |
---|---|---|---|
sum | 합계 | median | 중간값 |
mean | 평균 | unique | 고유값 |
std | 표준편차 | cumsum | 누적값 |
min | 최소값 | corrcoef | 상관계수 |
max | 최대값 |
a = np.array([1,2,3])
b = np.array([2,3,4])
np.vstack((a, b))
-> array([[1,2,3],
[2,3,4]])
np.hstack((a,b))
-> array([1, 2, 3, 2, 3, 4])
np.concatenate((a,b), axis = 0)
-> array([1, 2, 3, 2, 3, 4])
b = np.array([5,6])
b.shape
-> (2, )
b.reshape(-1, 2).shape
-> (1, 2)
b[np.newaxis, :].shape
-> (1, 2)
기본적으로 배열 간 사칙연산이 가능하다. 이 때, 사칙연산은 element-wise 연산이 적용된다.
a = np.array([[1,2,3], [4,5,6]])
b = np.array([[1,2,3], [4,5,6]])
a * b
-> array([[ 1, 4, 9],
[16, 25, 36]])
np.multiply(a,b)
-> array([[ 1, 4, 9],
[16, 25, 36]])
a = np.array([[1,2,3], [4,5,6]])
b = np.array([[5,4], [6,5], [1,2]])
np.dot(a,b)
-> array([[20, 20],
[56, 53]])
a.dot(b)
-> array([[20, 20],
[56, 53]])
transpose: 전치 행렬로 변환
broadcasting: shape이 다른 배열 간 연산을 지원하는 기능, 하지만 모든 경우에 성립되지는 않음
a = np.array([[1,2,3], [4,5,6]])
b = np.array([1,2])
a + b
-> ValueError: operands could not be broadcast together with shapes (2,3) (2,)
❗️timeit: jupyter 환경에서 코드의 퍼포먼스를 체크하는 함수
timeit.timeit(stmt = 'pass', setup = 'pass', timer = <default timer>, number = 1000000)
# 0~100,000,000 리스트로 생성하는 코드 1번 실행 시간 측정
timeit.timeit(stmt = '[i for i in range(100000000)]', number = 1)
-> 5.7855273390887305
# 0~100,000 리스트로 생성하는 코드 100번 실행 시간 측정
timeit.timeit(stmt = '[i for i in range(100000)]', number = 100)
-> 0.36549861798994243
arr = np.arange(10)
arr < 4
-> array([ True, True, True, True, False, False, False, False, False, False])
np.any(a > 5) # 하나라도 조건에 만족한다면 True
-> True
np.all(arr > 5), np.all(arr < 10) # 모두 조건에 만족한다면 True, 그렇지 않다면 False
-> (False, True)
a = np.array([1, 3, 0], float)
np.logical_and(a > 0, a < 3) # 두 조건의 and 조건
-> array([ True, False, False])
a = np.array([1, 3, 0], float)
np.logical_not(a < 2) # 기존 결과의 not 조건(역)
-> array([False, True, True])
a = np.array([1, 3, 0], float)
np.logical_or(a > 0 , a < 3)
-> array([ True, True, True])
np.where
np.where(condition, [x, y])
: condition을 만족했을 때는 x, 그렇지 않을 때 y 반환np.where(condition)
: condition을 만족하는 index를 반환np.isnan: None을 True로 반환, pandas의 isnull과 유사
np.isfinite: 무한대로 수렴하지 않는 값을 찾는 명령어
argmax & argmin: axis에 기반하여 array의 최대값 또는 최소값의 index를 반환하는 명령어
argsort: array를 정렬하여 본래의 index로 반환해주는 명령어
arr = np.array([[1,2,4,7], [9, 88, 6, 45], [9, 76, 3, 4]])
-> array([[ 1, 2, 4, 7],
[ 9, 88, 6, 45],
[ 9, 76, 3, 4]])
np.argmax(arr, axis = 1)
-> array([3, 1, 1])
np.argmin(arr, axis = 0)
-> array([0, 0, 2, 2])
arr = np.array([1,2,4,5,8,78,23,3])
arr.argosrt()
-> array([0, 1, 7, 2, 3, 4, 6, 5])
arr = np.array([[1,2,4,7], [9, 88, 6, 45], [9, 76, 3, 4]])
arr[arr > 10]
-> array([88, 45, 76])
# 2차원
arr = np.arange(1, 10, 0.5)
index = np.array([0,0,1,2,1,3,3,1,4])
arr[index] # index의 범위를 넘어가면 IndexError 발생
-> array([1. , 1. , 1.5, 2. , 1.5, 2.5, 2.5, 1.5, 3. ])
arr.take(index)
-> array([1. , 1. , 1.5, 2. , 1.5, 2.5, 2.5, 1.5, 3. ])
# matrix
arr = np.array([[1,2,4,7], [9, 88, 6, 45], [9, 76, 3, 4]])
index_x = np.array([0,0,1,2,1,1])
index_y = np.array([0,1,2,0,0,2])
arr[index_x, index_y]
-> array([1, 2, 6, 9, 9, 6])
data = np.loadtxt('./numpy.txt', delimiter = '\t')
np.savetxt('numpy.csv', data, delimiter = ',')
np.save('npy_file', arr = data)
이번 강의에서는 벡터의 기본 개념과 연산, 노름에 대해 소개합니다. 또한 두 벡터 사이의 거리와 각도, 그리고 내적에 대해서도 설명합니다.
벡터는 딥러닝에서 매우 중요한 선형대수학의 기본 단위이기 때문에 기본적인 개념을 확실하게 학습하는 것이 중요합니다. 벡터를 단순히 숫자의 표현으로 이해하는 것이 이닌 공간에서 어떤 의미를 가지고 있는 지를 이해하는 것이 중요합니다.
노름이나 내적 같은 개념 또한, 그 자체로 가지는 기하학적인 성질과 이것이 실제 머신러닝에서 어떻게 사용되는지를 고민하고 적용해보는 것이 딥러닝 공부의 시작이라고 생각됩니다.
벡터는 숫자를 원소로 가지는 리스트(list) 또는 배열(array)를 말합니다. 세로로 나열되어 있는 벡터를 열벡터, 가로로 나열되어 있는 벡터를 행벡터로 부릅니다.
❗스칼라곱: 스칼라가 1보다 크면 길이가 늘어나고, 1보다 작으면 길이가 줄어듬, 단 0보다 작으면 반대 방향으로 변함
벡터의 노름(norm)은 원점에서부터의 거리를 말합니다. 한 공간 사이에 표현되어 있는 벡터와 원점과의 거리를 의미하는데, 표현된 벡터와 원점과의 거리를 구하는 노름의 방법이 다양합니다. 보통 l1, l2 노름 방법이 존재한다.
def l1_norm(x):
x_norm = np.abs(x)
x_norm = np.sum(x_norm)
return x_norm
def l2_norm(x):
x_norm = x * x
x_norm = np.sum(x_norm)
x_norm = np.sqrt(x_norm)
return x_norm
노름을 계산하는 방법이 다른 이유는 노름의 종류에 따라 기하학적 성질이 달라지기 때문입니다. 머신러닝에선 각 성질들이 필요할 때가 다르므로 두 방법의 차이점을 인지하고 있어야 합니다.
L1, L2-노름을 이용해 두 벡터 사이의 거리를 계산할 수 있습니다. 노름의 방법에 따라 거리 계산 결과가 달라질 수 있습니다.
❗️코사인 유사도(― 類似度, 영어: cosine similarity)는 내적공간의 두 벡터간 각도의 코사인값을 이용하여 측정된 벡터간의 유사한 정도를 의미합니다.
# 두 벡터간 각도 구하기
def angle(x, y):
v = np.inner(x, y) / (l2_norm(x) * l2_norm(y)) # 코사인 theta
theta = np.arccos(v)
return theta
행렬의 개념과 연산, 그리고 벡터공간에서 가지는 의미에 대해서 다룹니다. 또한 연립방정식 풀기와 선형회귀분석에 응용하는 방법을 설명합니다.
벡터의 확장된 개념인 행렬은 행(row)벡터를 원소로 가지는 2차원 배열로 벡터와 다르게 계산되는 연산들에 주의해야 합니다. 행렬연산은 딥러닝에서 가장 핵심적인 연산이라고 볼 수 있을만큼 중요하고, 자주 사용되기 때문에 행렬 연산의 메커니즘, 그리고 이 때 가지는 기하학적 의미와 머신러닝에서 어떻게 사용되는지를 충분히 이해해야 합니다.
행렬, matrix는 벡터를 원소로 가지는 2차원 배열을 말합니다. 행렬은 n(행) x m(열)로 표현할 수 있습니다. 즉, 행렬은 행(row)과 열(column)이라는 인덱스를 가지게 됩니다.
행렬 곱셈은 벡터와 다른 방식으로 진행됩니다. 행렬 곱셈(matrix multiplication)은 번째 행벡터와 번째 열벡터 사이의 내적을 성분으로 가지는 행렬 계산을 의미합니다.
np.inner
를 사용함으로써, 번째 행벡터와 번째 행벡터 사이의 내적을 계산할 수 있음❗주의할 점은 수학에서 말하는 내적과는 다르므로 주의해야함!
a = np.array([[1,2,3], [4,5,6], [1,6,3]])
b = np.array([[1,2], [5,6], [8,1]])
np.dot(a, b)
-> array([[35, 17],
[77, 44],
[55, 41]])
np.inner(a, b) # a 배열의 행벡터와 b 배열의 행벡터의 사이즈가 일치하지 않기 때문에
-> ValueError: shapes (3,3) and (2,3) not aligned: 3 (dim 1) != 2 (dim 0)
역행렬이란 행렬 A의 연산을 역으로 되돌리는 행렬로, 행과 열 숫자가 같고 행렬식이 0이 아닌 경우에 계산됩니다.
np.linalg.inv(x)
를 통해 역행렬을 구할 수 있음np.linalg.pinv(x)
를 통해 무어펜로즈 역행렬을 구할 수 있음