위의 두 번째 단계는 실제 프로그래밍을 하기에도 힘들고, 같은 연산이 반복될 때 사용되는 vectorization을 이용하지 못한 모양이다. 따라서 mini-batch 사이즈가 임의의 n개일 때는 vectorization form을 이용하게 되고, 이 포스트에서는 3개의 mini-batch에 대한 backpropagation을 다룬다.
먼저 forward propgation을 포함한 model은 다음과 같다.
그리고 각 node에서 Jacobian을 이용한 partial derivative를 표시하면 다음과 같다.
/home/shinks/anaconda3/envs/pytorch/lib/python3.6/site-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
return array(a, dtype, copy=False, order=order)
그러면 위의 과정을 Vectorization으로 구해보자.
다음 연산은 여러 단계로 나누는 것이 더 알아보기 좋으므로 Step으로 나눠서 알아보자.
X(f)=∑∞n=−∞x[n]∗e−j2πfn
전체 단계 중에서 다음과 같이 나눌 수 있다.
먼저 가장 크게 Vectorized operation이 진행되는 부분은 전체 f와 data를 곱하는 부분인 step1이다.
step1: fn
step2: −j2πfn
step3: e−j2πfn
step4: x[n]∗e−j2πfn
step5: ∑∞n=−∞x[n]∗e−j2πfn
In [5]:
tiled_data=np.tile(data,(n_f,1))print("tiled_data.shape:",tiled_data.shape)# (f 개수, data 개수)print("f_range.shape:",f_range.shape)step1=tiled_data*f_rangeprint("step1.shape:",step1.shape)
우리가 실제 프로그램을 만들 때 Vectorization의 이득을 가장 많이 보는 부분은 행렬의 곱셈이 아닐까 싶다. 전의 Notebook에서 하나의 matrix에 대한 연산을 진행했다면 이번 시간에는 두 행렬의 곱셈에 대하여 Vectorization이 얼마나 효과적일지 살펴보기 전에 먼저 Matrix와 Vector의 Multiplication을 살펴보고 다음 notebook에서 두 행렬의 곱셈을 다뤄보도록 하자.
먼저 Matrix와 Vector의 Multiplication은 다음과 같이 연산된다.
행렬 A가 (m,n)이고 B가 (n,1)일 때 A*B를 C라 하면 C의 i-th entry는 다음과 같이 구한다.
위와 같이 (2000,1000)의 행렬 A와 1000차원의 vector B을 만들었다. 물론 이 matrix와 vector가 크다고 느껴질 수 있지만, 실제 data를 다루게 된다면 절대 큰 data가 아닌 것을 알게 될 것이다. 2천명이 각각 1000개의 data를 가지고 있는 것은 오히려 적은 경우이다. 이 matrix와 vector에 대해서 multiplication을 이용하면 얼마나 빨라질지 살펴보도록 하자.
솔직히 위의 결과를 보고 저자도 놀랬다. 평소에 vectorization을 쓰다가 이 강의자료를 만들기 위해 for loop을 써봤는데, 고작 천 단위의 data에서도 500배 차이가 나는 것을 보고 더욱 vectorization을 잘 사용해야겠다는 생각이 들었다. 그러면 연산이 오래걸리겠지만 data를 조금 늘렸을 때도 살펴보고 가도록 하자.
다음은 2천명이 아니라 조금 더 키워서 10만명이 천개의 data를 가지고 있을 때이다. 솔직히 10만명이라고해도, 실제 data에 비하면 많이 작은편이지만 Matrix가 조금 더 커졌을 때 얼마나 영향을 미치는지 살펴보자.
In [14]:
m,n=100000,1000A=np.random.rand(m,n)B=np.random.rand(n,1)C=np.zeros(shape=(m,1))print(A.shape,B.shape,C.shape)tic=time.time()forrow_idxinrange(m):forcol_idxinrange(n):C[row_idx]+=A[row_idx,col_idx]*B[col_idx]toc=time.time()for_time=toc-ticprint("Elapsed Time:",for_time,'sec')tic=time.time()C=np.dot(A,B)toc=time.time()vec_time=toc-ticprint("Elapsed Time:",vec_time,'sec')print("Elapsed Time Ratio:",for_time/vec_time)
위의 결과에서 볼 수 있듯이, 약 10000배의 차이가 난다. 이 결과를 보고 많은 독자들이 vectorization을 배우지 않고 data science와 같이 큰 data에 접근하는 것이 얼마나 위험한 생각인지 깨달았으면 좋겠다. 2중 for loop을 썼을 땐 2분 19초가 걸렸지만 vectorization을 썼을 땐 0.027초로 27ms가 걸린다. 독자들도 꼭 차원이 높아지면 for loop을 최대한 피하면 좋겠다.
위에서 알 수 있듯이, x0.2의 그래프는 input의 0부터 0.2까지의 작은 부분이 output에서는 0부터 72정도까지를 차지하는 것을 알 수 있다. 그만큼 각 pixel을 이 filter에 통과시키면 어두운 부분을 expansion시켜주게 된다. 그럼 이 이미지 전체에 filtering을 해보도록 하자.
위의 결과를 확인하면 첫 번째 2중 for loop을 이용했을 때는 말 그대로 각 pixel에 모두 접근하여 연산을 진행하지만 두 번째 vectorization은 matrix 전체를 한 번에 연산한다. (976, 746)의 이미지면 요즘 다루는 이미지들에 비해 큰 편은 아니다. 그리고 이미지에는 총 728096개의 pixel이 있으므로 vectorization의 효과는 이미지가 더 커질수록 빛을 발할 것임을 알 수 있다. 위의 결과에서는 36배의 속도개선이 있었지만 우리가 만약 더 큰 이미지에 대한 dataset을 preprocessing한다면, 아니면 image가 아닌 video에 대한 preprocessing을 한다면 Vectorization 유무의 따른 차이는 실로 어마어마하게 된다.
이번 시간에는 마지막 1-dimensional vectorization을 다뤄보려고 한다. 왜 이렇게 많이 반복하는지 궁금한 독자들도 있겠지만 생각보다 다른 곳에서 Vectorization을 집중적으로 배워볼 기회가 흔치 않기 때문에 이 강의를 통해서라도 최대한 익숙해졌으면 하는 바람에서 3번째 예제까지 다뤄보려고 한다. 위에 보이는 것처럼 NumPy Master Class이지 않은가.
이번 시간에는 Fourier Transform을 이용하여 Vectorization을 연습해보려고 한다. 이때 모든 주파수에 대해 연산하면 2차원이 되므로 f=10일 때를 구해보도록 하자. 많은 독자들이 이미 이론적으로 충분히 배웠을 것이고, 모르는 독자들도 연산에만 집중해도 충분하다. 그리고 fft라는 NumPy의 method가 있지만 Vectorization의 방법론을 배우기 위하여 이 fft는 사용하지 않도록 하겠다.
이번에는 10배의 성능개선을 확인할 수 있다. 우리가 앞에서 너무 극단적인 예시를 많이 들어서 10배면 뚜렷한 개선이라고 느껴지지 않을 수 있지만, 현실에선 2배 차이만 나도 엄청난 성능개선이 아닌가. 10배 차이면 이 전 예제들보단 개선효과가 덜 하지만 충분히 가치있는 Vectorization이라고 할 수 있다.
이번 결과는 약 70배의 성능 차이를 보여줬다. 이번에도 충분히 비약적인 성능개선이 이뤄진 것을 알 수 있다.
위와 같이 1-dimensional data에 대해서 2가지 예를 들어봤는데, 모두 비약적인 성능개선을 확인할 수 있었다. 위의 내용이 어려운 독자들도 있을 것이고, 너무 당연하게 느껴지는 독자들도 있을 것이다. 하지만 어느 경우에나 이렇게 Vectorization을 습관화하게 된다면 다른 사람들보다 좋은 프로그램을 만들 수 있을 것이고, 남들과 비교하여 강점을 드러내는 부분이 될 테니 어렵더라도 꾸준히 잘 따라와줬으면 하는 바람이 있다.