[Numpy] #1 넘파이란, 객체생성및처리 기초문법 공부하기 1
왜 넘파이를 써야하나요?
- 검색해보면, "행렬이나 일반적으로 대규모 다차원 배열을 쉽게 처리할 수 있도록 지원하는 파이썬의 라이브러리이다. 데이터 구조 외에도 수치 계산을 위해 효율적으로 구현된 기능을 제공한다." by 위키피디아
- 행렬, 백터 등의 수치 연산에 활용되기에 "선형대수" 라이브러리이며, 내부적으로 c로 짜여있어 연산속도가 빠르대요.
개발, 머신러닝을 공부하는 사람 입장에서 보자면, 기존 파이썬 코드보다 Numpy 코드가 가독성이 좋다.
import numpy as np
list_py = [1,2,3] # [1,2,3]
print(type(list_py)) # <class 'list'>
list_np = np.array([1,2,3]) # [1 2 3]
print(type(list_np)) # <class 'numpy.ndarray'>
- np만 붙이면 수월해진다.
수치 연산, 특히 행렬의 수치 연산을 기존 파이썬 코드로 짜다보면 복잡해지고, 오류의 가능성이 높아진다.
예를 들어
a = 1
b = 2
a + b
a - b
a * b
a / b
- 파이썬의 기본적인 연산은 각각의 Object 끼리의 연산을 지원한다.
그리고, list끼리의 연산 시
list1_py = [1,2,3]
list2_py = [4,5,6]
print(list1_py + list2_py) # [1, 2, 3, 4, 5, 6]
print(list1_py - list2_py) # error
print(list1_py * list2_py) # error
print(list1_py / list2_py) # error
+는 concat 기능을 수행하고, 나머지 연산자들은 error가 발생하다.
이건 python이 각각의 Object끼리의 사칙연산을 전제로 연산자 클래스 지원하기에 당연하다.
그런데, 이러한 python 문법은 머신러닝 코드 작성 시 코드를 굉장히 지저분하게 만든다.
import numpy as np
# y = 2x + 8 (단, 0<=x<20)에서 y의 값에 해당하는 list
# python
result_py = []
for n in range(0,20):
result_py.append((2*n) + 8)
print(result_py)
# numpy
result_np = 2 * np.arange(0,20) + 8
print(result_np)
# [1,2,3]과 [4,5,6]에서 같은 index끼리를 합한 list
# python
a_py = [1,2,3]
b_py = [4,5,6]
result_py = []
for idx in range(len(a_py)):
result_py.append(a_py[idx] + b_py[idx])
print(result_py)
# python
a_np = np.array([1,2,3])
b_np = np.array([4,5,6])
result_np = a_np + b_np
print(result_np)
위의 예시가 numpy의 장점이 드러나는 적절한 예는 아닐 수 있지만,
간단하게 index를 반대로 취하는 경우라던가, 3차원, 4차원 등의 다차원 list에 대한 연산 등을 상상해본다면
기존 python의 코드는 점점 지저분해지고, 그만큼 오류가 생기기 쉬울 것이다.
그렇기에 수치 연산을 위해, 특히 수학적 개념을 구현하고자 할 때, numpy는 당연한 선택이다.
넘파이 어떻게 쓰나요?
이 포스트에서는 간단하게 객체 생성, 객체 처리를 기준으로 이야기해보자
- 객채 생성
import numpy as np
a = np.array([1,2,3])
b = np.arange(0,10)
print(a, type(a)) # [1 2 3] <class 'numpy.ndarray'>
print(b, type(b)) # [0 1 2 3 4 5 6 7 8 9] <class 'numpy.ndarray'>
기본적으로 numpy를 활용하기 위해서는 numpy.ndarray의 형태로 객체를 구성해야 합니다.
np.array()의 경우는 파이썬의 list 클래스와 같은 형태로 만들어지고
a_data = [4,5,6]
a = np.array(a_data)
print(a, type(a)) # [4 5 6] <class 'numpy.ndarray'>
np.arange()의 경우는 시작점과 끝점을 정해 순차적인 행렬을 만들어낸다
a = np.arange(0, 5)
print(a) # [0 1 2 3 4]
b = np.arange(0, 5, 1.5)
print(b) # [0. 1.5 3. 4.5]
- np.arange(arg1, arg2)
요소들의 범위 : arg1 <= x < arg2, 간격 : default=1
고로, np.arange(0, 5)
∴ [0, 1, 2, 3, 4]이라는 list를 가지게 된다
- np.arange(arg1, arg2, arg3)
요소들의 범위 : arg1 <= x < arg2, 간격 : arg3
고로, np.arange(0, 5, 1.5)
= [0, 0+1.5, 0+1.5+1.5, 0+1.5+1.5+1.5]
∴ [0, 1.5, 3, 4.5]이라는 list를 가지게 된다
* 순차로 더한 값이 arg2를 넘으면 집어넣지 않네용
추가적으로
a = np.linspace(0,1,5)
print(a) # [0. 0.25 0.5 0.75 1. ]
b = np.linspace(0,1,10)
print(b) # [0. 0.11111111 0.22222222 0.33333333 0.44444444 0.55555556
# 0.66666667 0.77777778 0.88888889 1. ]
c = np.linspace(0,1,4)
print(c) # [0. 0.33333333 0.66666667 1. ]
- np.linspace(arg1, arg2, arg3)
요소들의 범위 : arg1 <= x <= arg2, 요소의개수: arg3
요소의 개수에 따라서 간격을 정해 간격에 따라 순차적인 행렬을 만들어낸다.
- 객채 처리
간단하게, 행렬 간의 간단한 연산을 해보며 마무리하겠다.
a = np.array([1,2,3])
b = np.array([4,5,6])
print(a+b) # [5 7 9]
print(a-b) # [-3 -3 -3]
print(a*b) # [ 4 10 18]
print(a/b) # [0.25 0.4 0.5 ]
print(a//b) # [0 0 0]
print(a**b) # [ 1 32 729]
x = np.arange(1,11)
print(x) # [ 1 2 3 4 5 6 7 8 9 10]
y_2x5b = 2*x+5 # y = 2x + 5
print(y_2x5b) # [ 7 9 11 13 15 17 19 21 23 25]
y_5x3b = 5*x+3 # y = 5x + 3
print(y_5x3b) # [ 8 13 18 23 28 33 38 43 48 53]
# y = 7x + 8 = (2+5)x + (5+3)
y_7x8b_1 = (2+5)*x+(5+3)
print(y_7x8b_1) # [15 22 29 36 43 50 57 64 71 78]
y_7x8b_2 = y_2x5b + y_5x3b
print(y_7x8b_2) # [15 22 29 36 43 50 57 64 71 78]
위와 같이 요소 간의 사칙연산을 통해 방정식을 이끌어 낼 수도 있다.
numpy의 진수는 다차원 배열, 행렬을 처리하는 데에 있다.
다음 포스트에서는 그와 관련해서 shape와 같은 속성과 속성들을 활용한 메소드들을 알아보자.