https://www.youtube.com/watch?v=qsIrQi0fzbY
다시 오신 것을 환영합니다. 벡터화는 간단히 말하자면 코드에서 for문을 없애는 일종의 예술과 같습니다. 딥러닝 시대에서 실제 딥러닝을 할 때 딥러닝 알고리즘이 빛을 발하게 해주는 큰 데이터 세트를 학습시킬 때가 많습니다. 따라서 코드가 빠르게 실행되는 게 중요하죠. 그렇지 않다면 큰 데이터 세트를 학습시킬 때 코드 실행 시간이 길어지고 결과를 내기까지 오래 기다려야 합니다. 딥러닝 시대에서 벡터화할 수 있는 능력은 중요한 기술이 되었다고 생각합니다.
예를 들어 시작하죠. 벡터화가 뭘까요? 로지스틱 회귀에선 $z = w^T x + b$를 계산해야 했습니다. $w$는 열 벡터이고 $x$도 마찬가지입니다. 특성이 많다면 굉장히 큰 벡터가 됩니다. $w$와 $x$는 모두 $\mathbb{R}^{(n_x)}$의 차원을 가진 벡터입니다.
벡터화되지 않은 구현일 때엔 $w^Tx$를 계산하기 위해
z = 0
for i in range (n_x):
z += w[i] * x[i]
z += b
이것은 벡터화되지 않은 구현이고 느립니다.
그에 반해 벡터화된 구현은 $w^T x$를 직접 계산합니다. 파이썬이나 NumPy에서 명령어는 np.dot(w, x)입니다. 이게 $w^T x$를 계산하죠. 그리고 $b$를 더해주면 됩니다.
z = np.dot(w,x)
z += b
이 방법이 훨씬 빠르다는 걸 발견할 것입니다.
예를 들어 설명해 보죠. 이건 제가 파이썬 코드를 작성할 Jupyter Notebook입니다. 먼저 NumPy 라이브러리를 np로 불러오죠. 예를 들어 a를 다음과 같은 하나의 배열로 지정합시다. 그 후엔 a를 출력하도록 하죠.
import numpy as np
a = np.array([1,2,3,4])
print a
코드를 작성한 뒤에 shift+enter를 누르면 코드가 실행됩니다. 배열 a를 만들고 출력했습니다.
[1 2 3 4]
벡터화 예시를 해봅시다. time 모듈을 불러와서 다른 연산들이 얼마나 걸리는지 재겠습니다. 배열 a를 np.random.rand를 사용해 만들겠습니다. 이 코드는 난수로 이루어진 백만 차원의 배열을 만들어줍니다. b도 마찬가지로 백만 차원의 배열입니다. tic을 현재 시간으로 지정합니다. c는 np.dot(a, b)이고 toc을 time.time()으로 지정해줍니다. 출력을 합시다. 이건 벡터화된 버전이니 그렇게 써주고요. toc에서 tic을 빼주고 1000을 곱하여 밀리초 단위로 표현해줍니다.
import time
a = np.random.rand(1000000)
b = np.random.rand(1000000)
tic = time.time()
c= np.dot(a,b)
toc = time.time()
print("Vectorized version : " + str(1000*(toc-tic)) + "ms")
shift+enter를 누르겠습니다.
Vectorized version : 1.478433ms
이 코드는 약 1.5밀리초 정도 걸렸네요. 1.5밀리초에서 3.5밀리초 정도 걸리네요. 실행할 때마다 조금씩 달라지지만 평균적으로 1.5밀리초가 걸립니다. 2밀리초 정도 걸릴 수도 있습니다.
여기에 벡터화되지 않은 구현을 더해봅시다. c를 0으로 설정해주고 tic을 time.time()으로 해줍니다. 이제 공식을 구현해 보죠. i가 1부터 백만까지일 때 c += a[i] * b[i]를 쓰고 toc을 time.time()으로 지정합니다. for문을 썼을 때의 시간인 1000 * (toc - tic)을 출력해줍니다. 뒤에 “ms”를 추가해 밀리초라는 걸 표기해줍니다. 추가로 c를 출력해 두 경우 모두 같은 값이라는 걸 확인합시다.
import time
a = np.random.rand(1000000)
b = np.random.rand(1000000)
tic = time.time()
c= np.dot(a,b)
toc = time.time()
print(c)
print("Vectorized version : " + str(1000*(toc-tic)) + "ms")
c = 0
tic = time.time()
for i in range (1000000):
c += a[i]*b[i]
toc = time.time()
print(c)
print("For loop : " + str(1000*(toc-tic)) + "ms")
shift+enter를 눌러 실행하겠습니다.
250286.989866
Vectorized version : 1.502752ms
250286.989866
For loop : 474.295139ms
벡터화 버전과 아닌 버전 모두 같은 값을 계산했습니다. 벡터화된 버전은 1.5밀리초가 걸렸지만 for문을 쓴 벡터화되지 않은 버전은 500밀리초 가까이 걸렸습니다. 벡터화되지 않은 버전은 벡터화 버전보다 약 300배 오래 걸린 거죠. 이 예에서 보시는 것처럼 코드를 벡터화하는 걸 기억한다면 코드가 300배 이상 빨라질 것입니다. 다시 한 번 실행해보죠.
249946.964024
Vectorized version : 1.505136ms
249946.964024
For loop : 481.311082ms
벡터화 버전은 1.5밀리초가 걸렸고 for문을 쓴 버전은 481밀리초가 걸렸습니다. 다시 해도 300배 정도 느리다는 걸 알 수 있죠. 300배 느리다는 건 1분 걸릴 코드가 5시간 걸린다는 말입니다. 코드를 벡터화한다면 딥러닝 알고리즘을 구현 시 결과를 훨씬 빨리 얻을 수 있습니다.
몇몇 분들은 많은 확장형 딥러닝 구현이 GPU에서 계산된다고 들었을 텐데요. 방금 Jupyter Notebook에서 실행한 예제들은 모두 CPU에서 계산됐습니다. GPU와 CPU 모두에게 가끔 SIMD라고 불리는 병렬 명령어가 있습니다. Single Instruction Multiple Data의 줄임말이죠. np.dot을 사용하거나 for문이 필요 없는 다른 함수를 사용할 때 파이썬 NumPy가 병렬화의 장점을 통해 계산을 훨씬 빠르게 할 수 있게 해줍니다. CPU와 GPU상의 계산에서 모두 적용되는 이야기입니다. GPU는 SIMD 계산을 엄청나게 잘 하지만 CPU도 그렇게 나쁘지는 않습니다. GPU보다는 부족하지만요.
여러분은 벡터화가 코드를 얼마나 빠르게 해주는지 보았습니다. 여기서 기억해야 할 점은 될 수 있는 한 for문을 쓰지 않는 겁니다. 다음 동영상에서 벡터화의 예를 더 보고 로지스틱 회귀를 벡터화해보겠습니다.

댓글