CLOSE

Recommanded Free YOUTUBE Lecture: <% selectedImage %>

## Contents

예제코드들은 code에서 다운로드 할 수 있다. 테스트에 사용한 python 버전은 아래와 같다.
```(my_env) yundream@yundream:~/workspace/python/numpy\$ python
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux

### 예제

NumPy는 모든 것을 벡터(vectorization)로 다룬다. 만약 파이썬에 익숙하다면, 사고방식의 변화 때문에 어려움을 겪을 수 있다. 앞으로 당신은 vector, array, views, ufuncs 와 같은 것들을 다루게 될 것이다.

#### 객체지향적 접근

간단한 랜덤워크(random walk)예제를 만들었다. 객체지향적인 접근에서는 RandomWalk 클래스를 정의하고, 랜덤한 현재 위치를 반환하는 walk 메서드를 작성한다. 아래 코드를 보자. 멋지다. 하지만 느릴 것이다.
```import random
import timeit

class RandomWalker:
def __init__(self):
self.position=0

def walk(self, n):
self.position = 0
for i in range(n):
yield self.position
self.position += 2*random.randint(0,1) -1
walker = RandomWalker()```

timeit패키지를 이용 벤치마크를 돌려봤다.
```if __name__ == "__main__":
from tools import timeit
walker = RandomWalker()
timeit("[position for position in walker.walk(n=10000)]", globals())```

```\$ python randomwalk.py
10 loops, best of 3: 11 msec per loop```

#### 절차지향적 접근

이런 간단한 문제를 푸는데 굳이 클래스를 만들 필요는 없을 것이다. 그래서 그냥 walk 메서드만 구현을 했다.
```def random_walk(n):
position = 0
walk = [position]
for i in range(n):
position += 2*random.randint(0, 1)-1
walk.append(position)
return walk

if __name__ == "__main__":
from tools import timeit
timeit("random_walk(n=10000)", globals())```
벤치마크 결과다.
`10 loops, best of 3: 9.24 msec per loop`
클래스를 포기하는 것으로 약간의 CPU 시간을 아꼈다. 생각만큼 크게 아낀 것은 아니다. 아마도 내부적으로 객체지향을 구현하면서 약간의 사이클을 소비했기 때문일 것이다.

#### Vectorized 접근

그러나 효과적인 루프작업을 위한 iterators(반복자)를 제공하는 itertools 모듈을 이용해서 실행시간을 줄일 수 있다.
```def random_walk_faster(n=1000):
from itertools import accumulate
# Only available from Python 3.6
steps = random.choices([-1,+1], k=n)
return +list(accumulate(steps))

walk = random_walk_faster(1000)```
순차적으로 루프를 도는 대신에, 데이터를 백터에 저장해서 연산하도록 코드를 수정했다. 결과적으로 루프를 제거했고 더 빠른 작업 수행이 가능해졌다.

```>>> from tools import timeit
>>> timeit("random_walk_faster(n=10000)", globals())
10 loops, best of 3: 2.21 msec per loop```
이전 코드에 비해서 85% 정도의 성능을 개선했다. 이제 NumPy 벡터화를 이용해서, 이 과정을 더욱 효율적으로 그리고 더 간단하게 만들어보자.

```import numpy as np
import random

def random_walk_fatest(n=1000):
steps = np.random.choice([-1,+1],n)
return np.cumsum(steps)

walk = random_walk_fatest(1000)```
실행해보자. 500배 이상 빨라진 것을 확인 할 수 있다.
```>>> from tools import timeit
>>> timeit("random_walk_fastest(n=10000)", globals())
1000 loops, best of 3: 14 usec per loop```

### 가독성과 성능

다음 장으로 넘어가기 전에 NumPy에 익숙해지면 발생할 수 있는 잠재적인 문제점에 대해서 경고할게 있다. NumPy는 매우 강력한 라이브러리로 이것을 이용해서 놀라운 애플리케이션들을 개발 할 수 있다. 하지만 주석을 제대로 달지 않을 경우 가독성의 댓가를 치룰 수 있다. 작성 시점에 주석을 달지 않으면 몇 주(혹은 며칠)후에 무엇을 하려고 만든 코드인지 알 수 없게 될 것이다. 예를들어 아래의 두 코드가 무슨 일을 하는지 설명 할 수 있을까 ? 아마도 첫번째 코드는 말 할 수 있지만, 두 번째 코드를 쉽게 설명 할 수 없을 것이다.
```def function_1(seq, sub):
return [i for i in range(len(seq) - len(sub) +1) if seq[i:i+len(sub)] == sub]

def function_2(seq, sub):
target = np.dot(sub, sub)
candidates = np.where(np.correlate(seq, sub, mode='valid') == target)
check = candidates[:, np.newaxis] + np.arange(len(sub))
mask = np.all((np.take(seq, check) == sub), axis=-1)