Loading [MathJax]/jax/output/CommonHTML/jax.js

이번 포스트에서는 두 번째 model인 y=θ1x+θ0에 대한 backpropagation을 다뤄본다.

 

마찬가지로 하나의 Sample에 대한 backpropagation부터 살펴보자.

 

 

Backpropagation for One Sample


Model

 

Parital Derivative

 

Chain Rule

 

Backpropagation Visualization

 

Parameter Update

 

 

Backpropagation for Two Samples


Model

 

Parital Derivative

 

Chain Rule

 

 

Backpropagation Visualization

Parameter Update

 

 

Backpropagation Vectorized form


Model

 

Parital Derivative

 

Chain Rule

 

 

Backpropagation Visualization

Parameter Update

 

Linear Regression을 위한 Backpropagation은 다음과 같은 모델들을 사용하여 순서대로 진행될 것이다.

  1. y=θx
  2. y=θ1x+θ0
  3. y=θ2x2+θ1x1+θ0

그리고 각 단계에서도 첫 번째로는 하나의 sample에 대한 backpropagation, 두 번째로는 2개의 sample에 대한 backpropagation, 마지막으로 Vectorized form의 backpropagation으로 진행될 것이다.

 

그러면 이번 포스트에서는 첫 번째 model부터 살펴보도록하자.

 

Backpropagation for One Sample


먼저 forward-backward propagation을 위한 node들을 먼저 만들어보면 다음과 같다.

 

 

즉, z1 node는 prediction value가 될 것이고, L는 loss를 구하게 된다. 그리고 각 node에서의 partial derivative를 구하면 다음과 같이 표시할 수 있다.

 

 

그리고 Chain rule에 의해 θ까지의 backpropagation을 계산하면 다음과 같다.

 

Lz1=Lz2z2z1=2z2(1)=2z2=2(yz1)

 

Lθ=Lz2z2z1z1θ=Lz1z1θ=2x(yz1)=2x(yθx)

 

따라서 Backpropagation은 다음과 같이 일어난다.

 

θ=θlrLθ=θlr(2x(yθx))=θ+lr2x(yθx)

 

Backpropagation for Two Samples


 

우리가 Learning 단계에서 mini-batch를 사용한다면 loss가 아닌 cost를 parameter update에 사용하게 된다. 

 

즉, loss들의 평균을 구하는 node가 추가된다. 그리고 mini-batch size가 2인 linear regression model은 다음과 같다.

 

그리고 각 node에서의 partial derivative를 표시하면

이 되고, Chain rule을 이용하여 backpropagation을 계산하면 다음과 같다.

 

Jz(1)2=JL(1)L(1)z(1)2=122z(1)2

 

Jz(2)2=JL(2)L(2)z(2)2=122z(2)2

 

Jz(1)1=JL(1)L(1)z(1)2z(1)2z(1)1=Jz(1)2z(1)2z(1)1=122z(1)2=122(y(1)z(1)1)

 

Jz(2)1=JL(2)L(2)z(2)2z(2)2z(2)1=Jz(2)2z(2)2z(2)1=122z(2)2=122(y(2)z(2)1)

 

Jθ=JL(1)L(1)z(1)2z(1)2z(1)1z(1)1θ=Jz(1)1z(1)1θ=122x(1)(y(1)z(1)1)=122x(1)(y(1)θx(1))

 

Jθ=JL(2)L(2)z(2)2z(2)2z(2)1z(2)1θ=Jz(2)1z(2)1θ=122x(2)(y(2)z(2)1)=122x(2)(y(2)θx(2))

 

이를 visualization하면 다음과 같다.

 

따라서 parameter update는 다음과 같이 된다.

 

θ=θlr(122x(1)(y(1)θx(1))122x(2)(y(2)θx(2)))=θ+lr122i=12x(i)(y(i)θx(i))

 

 

Backpropagation Vectorized form


위의 두 번째 단계는 실제 프로그래밍을 하기에도 힘들고, 같은 연산이 반복될 때 사용되는 vectorization을 이용하지 못한 모양이다. 따라서 mini-batch 사이즈가 임의의 n개일 때는 vectorization form을 이용하게 되고, 이 포스트에서는 3개의 mini-batch에 대한 backpropagation을 다룬다.

 

먼저 forward propgation을 포함한 model은 다음과 같다.

 

 

그리고 각 node에서 Jacobian을 이용한 partial derivative를 표시하면 다음과 같다.

 

 

그리고 각 node에서 실제로 backpropagationdmf 계산하면 다음과 같다.

따라서 Backpropagation은 다음과 같이 전체적으로 표시할 수 있다.

 

Plus Node


Node

Partial Derivatives

Chain Rule

Backpropagation

 

Minus Node


Node

Partial Derivatives

Chain Rule

Backpropagation

 

Multiplication Node


Node

Partial Derivatives

Chain Rule

Backpropagation

 

Square Node


Node

Partial Derivatives

Chain Rule

Backpropagation

 

Average Node


Node

Partial Derivatives

Chain Rule

Backpropagation

4_6_2dim_Vectorization_Example3

NumPy Master Class

Chapter4 Vectorization

Notebook6 2-dim Vectorization Example3

이번 2차원 Vectorization에서는 notebook3에서 다뤘던 DTFT를 하나의 주파수가 아닌 주파수 영역에서 구해보려고 한다. 먼저 Foureir Transform의 식을 임의의 주파수에서 구하는 식을 보면 다음과 같다.

X(f)=n=x[n]ej2πfn

그러면 우리의 신호를 다음과 같이 만들어보자.

In [1]:
import numpy as np
import matplotlib.pyplot as plt
import time

from scipy.io import wavfile
fs, data = wavfile.read('./test_audio.wav')
data = (data[:,0] + data[:,1])/2
fig, ax = plt.subplots(figsize = (15,6))
ax.plot(data)
/home/shinks/anaconda3/envs/pytorch/lib/python3.6/site-packages/ipykernel_launcher.py:6: WavFileWarning: Chunk (non-data) not understood, skipping it.
  
Out[1]:
[<matplotlib.lines.Line2D at 0x7f3fd85919e8>]

이 audio에 대하여 -20Hz부터 20Hz까지의 DTFT를 2중 for loop을 이용하여 구하고, frequency response는 하나의 for loop을 이용해 구해보자.

In [2]:
n_f, n_s = 100, data.shape[0]

f_range = np.linspace(-20, 20, n_f).reshape(n_f,1)
Xf = np.zeros(shape = (f_range.shape), dtype = np.complex)
In [4]:
tic = time.time()
PI = np.pi

for f_idx in range(n_f):
    f = f_range[f_idx]
    for s_idx in range(n_s):
        s = data[s_idx]
        Xf[f_idx] += s * np.exp(-2j*PI*f*s_idx)
        
for f_idx in range(n_f):
    Xf[f_idx] = np.abs(Xf[f_idx])
    
toc = time.time()
for_time = toc - tic
print("Elapsed Time:", for_time, 'sec')

plt.figure(figsize = (10,5))
plt.plot(Xf)
plt.yscale('log')
Elapsed Time: 152.4426200389862 sec
/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]ej2πfn

전체 단계 중에서 다음과 같이 나눌 수 있다.

먼저 가장 크게 Vectorized operation이 진행되는 부분은 전체 f와 data를 곱하는 부분인 step1이다.

step1: fn

step2: j2πfn

step3: ej2πfn

step4: x[n]ej2πfn

step5: n=x[n]ej2π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_range
print("step1.shape:", step1.shape)
tiled_data.shape: (100, 268237)
f_range.shape: (100, 1)
step1.shape: (100, 268237)

위와 같이 broadcasting을 이용하여 f의 값들을 차례대로 data들에 곱해준다.

In [6]:
step2 = -2j*PI*step1
print("step2.shape:", step2.shape)
step2.shape: (100, 268237)
In [7]:
step3 = np.exp(step2)
print("step3.shape:", step3.shape)
step3.shape: (100, 268237)
In [8]:
print("step3.shape:", step3.shape)
print("data.shape:", data.shape)

step4 = step3*data
print("step4.shape:", step4.shape)
step3.shape: (100, 268237)
data.shape: (268237,)
step4.shape: (100, 268237)
In [9]:
step5 = np.abs(np.sum(step4, axis = 1))
print(step5.shape)
(100,)

위의 과정에서 frequency response를 구하기 위해 절대값까지 같이 취해준다.

위의 과정을 모두 종합하면 다음과 같다.

In [10]:
tic = time.time()
PI = np.pi

DTFT = np.abs(np.sum((np.exp(-2j*PI*(np.tile(data, (n_f, 1)))*f_range))*data, axis = 1))

toc = time.time()
vec_time = toc - tic
print("Elapsed Time:", vec_time, 'sec')

plt.figure(figsize = (10,5))
plt.plot(DTFT)
plt.yscale('log')
Elapsed Time: 1.9441606998443604 sec
In [11]:
print("Elapsed Time Ratio:", for_time / vec_time)
Elapsed Time Ratio: 78.41050384939372

즉, 하나의 test audio file에 대해 FFT를 할 때도 78배의 연산속도 차이가 났다.

위의 data들의 shape을 살펴보면 다음과 같다.

In [12]:
print("data.shape:", data.shape)
print("f_range.shape:", f_range.shape)
data.shape: (268237,)
f_range.shape: (100, 1)

위와 같이 하나의 30초짜리 audio file도 26만 point가 나오므로 실제로 우리가 다루게 되는 data는 훨씬 더 클 것을 예상할 수 있다.

따라서 vectorization이 더더욱 중요해질 것은 분명해 보인다.

4_5_2dim_Vectorization_Example2

NumPy Master Class

Chapter4 Vectorization

Notebook5 2-dim Vectorization Example2

우리가 실제 프로그램을 만들 때 Vectorization의 이득을 가장 많이 보는 부분은 행렬의 곱셈이 아닐까 싶다. 전의 Notebook에서 하나의 matrix에 대한 연산을 진행했다면 이번 시간에는 두 행렬의 곱셈에 대하여 Vectorization이 얼마나 효과적일지 살펴보기 전에 먼저 Matrix와 Vector의 Multiplication을 살펴보고 다음 notebook에서 두 행렬의 곱셈을 다뤄보도록 하자.

먼저 Matrix와 Vector의 Multiplication은 다음과 같이 연산된다.

행렬 A가 (m,n)이고 B가 (n,1)일 때 A*B를 C라 하면 C의 i-th entry는 다음과 같이 구한다.

ci=ai1b1+ai2b2+...+ainbn=nk=1aikbk

먼저 다음과 같이 matrix와 vector를 만들어보자.

In [3]:
import numpy as np
import time
In [8]:
m, n = 2000, 1000
A = np.random.rand(m,n)
B = np.random.rand(n,1)
C = np.zeros(shape = (m,1))
print(A.shape, B.shape, C.shape)
(2000, 1000) (1000, 1) (2000, 1)

위와 같이 (2000,1000)의 행렬 A와 1000차원의 vector B을 만들었다. 물론 이 matrix와 vector가 크다고 느껴질 수 있지만, 실제 data를 다루게 된다면 절대 큰 data가 아닌 것을 알게 될 것이다. 2천명이 각각 1000개의 data를 가지고 있는 것은 오히려 적은 경우이다. 이 matrix와 vector에 대해서 multiplication을 이용하면 얼마나 빨라질지 살펴보도록 하자.

In [9]:
tic = time.time()

for row_idx in range(m):
    for col_idx in range(n):
        C[row_idx] += A[row_idx, col_idx]*B[col_idx]
        
toc = time.time()
for_time = toc - tic

print("Elapsed Time:", for_time, 'sec')
Elapsed Time: 5.3392298221588135 sec
In [10]:
tic = time.time()

C = np.dot(A,B)

toc = time.time()
vec_time = toc - tic

print("Elapsed Time:", vec_time, 'sec')
Elapsed Time: 0.010550737380981445 sec
In [11]:
print("Elapsed Time Ratio:", for_time/vec_time)
Elapsed Time Ratio: 506.0527647843084

솔직히 위의 결과를 보고 저자도 놀랬다. 평소에 vectorization을 쓰다가 이 강의자료를 만들기 위해 for loop을 써봤는데, 고작 천 단위의 data에서도 500배 차이가 나는 것을 보고 더욱 vectorization을 잘 사용해야겠다는 생각이 들었다. 그러면 연산이 오래걸리겠지만 data를 조금 늘렸을 때도 살펴보고 가도록 하자.

다음은 2천명이 아니라 조금 더 키워서 10만명이 천개의 data를 가지고 있을 때이다. 솔직히 10만명이라고해도, 실제 data에 비하면 많이 작은편이지만 Matrix가 조금 더 커졌을 때 얼마나 영향을 미치는지 살펴보자.

In [14]:
m, n = 100000, 1000
A = 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()

for row_idx in range(m):
    for col_idx in range(n):
        C[row_idx] += A[row_idx, col_idx]*B[col_idx]
        
toc = time.time()
for_time = toc - tic

print("Elapsed Time:", for_time, 'sec')

tic = time.time()

C = np.dot(A,B)

toc = time.time()
vec_time = toc - tic

print("Elapsed Time:", vec_time, 'sec')

print("Elapsed Time Ratio:", for_time/vec_time)
(100000, 1000) (1000, 1) (100000, 1)
Elapsed Time: 259.21062207221985 sec
Elapsed Time: 0.026585817337036133 sec
Elapsed Time Ratio: 9749.958738756513

위의 결과에서 볼 수 있듯이, 약 10000배의 차이가 난다. 이 결과를 보고 많은 독자들이 vectorization을 배우지 않고 data science와 같이 큰 data에 접근하는 것이 얼마나 위험한 생각인지 깨달았으면 좋겠다. 2중 for loop을 썼을 땐 2분 19초가 걸렸지만 vectorization을 썼을 땐 0.027초로 27ms가 걸린다. 독자들도 꼭 차원이 높아지면 for loop을 최대한 피하면 좋겠다.

4_4_2dim_Vectorization_Example1

NumPy Master Class

Chapter4 Vectorization

Notebook4 2-dim Vectorization Example1

이제 2차원 정보의 Vectorization을 다뤄보도록 하자. Example1으로는 Image Enhancement의 종류인 Power-Law Transformation이다. 먼저 이 Power-Law Transformation이 필요한 이유를 설명하기 위해

gonzalez - digital image proccesing의 Chapter3에 나오는 이미지를 가져와보도록 하자.

In [24]:
import numpy as np
import time
import matplotlib.pyplot as plt

img = plt.imread('./test_image1.tif')
img = img/255
H,W = img.shape
print("img.shape:", img.shape)
plt.figure(figsize = (5,8))
plt.imshow(img, 'gray')
img.shape: (976, 746)
Out[24]:
<matplotlib.image.AxesImage at 0x7f45375e4ac8>

위의 이미지는 어두운 부분을 더 자세히 보고 싶은 욕구가 저절로 들게 만드는 이미지이다. 이때 우리는 power function의 특징을 이용하여 다음과 같은 모양의 filter를 이용할 것이다.

In [22]:
x_range = np.linspace(0,1,500)
y = np.power(x_range, 0.2)

plt.figure(figsize = (8,8))
plt.plot(x_range, y)
plt.plot(x_range[100], y[100], 'ro', markersize = 10)
plt.plot([x_range[100], x_range[100]], [0, y[100]], 'r:', linewidth = 2)
plt.plot([0, x_range[100]], [y[100], y[100]], 'r:', linewidth = 2)
plt.xlabel("input pixel")
plt.ylabel("output pixel")
Out[22]:
Text(0, 0.5, 'output pixel')

위에서 알 수 있듯이, x0.2의 그래프는 input의 0부터 0.2까지의 작은 부분이 output에서는 0부터 72정도까지를 차지하는 것을 알 수 있다. 그만큼 각 pixel을 이 filter에 통과시키면 어두운 부분을 expansion시켜주게 된다. 그럼 이 이미지 전체에 filtering을 해보도록 하자.

In [35]:
tic = time.time()

t_img = np.empty(shape = (H,W))
for h in range(H):
    for w in range(W):
        t_img[h,w] = np.power(img[h,w], 0.6)
        
toc = time.time()
for_time = toc - tic

img_stack = np.hstack((img, t_img))
plt.figure(figsize = (10,8))
plt.imshow(img_stack, 'gray')
print("Elapsed Time:", for_time, 'sec')
Elapsed Time: 1.3127350807189941 sec

위에서 주목할 점은 2중 for loop이다. 이는 for loop이 하나일 때보다 훨씬 더 프로그램 속도에 영향을 미치게 된다. 다음은 이 image를 한 번에 filtering하는 코드이다. 이렇게 작은 이미지에서 얼마나 차이가 나는지 확인해보자.

In [33]:
tic = time.time()

t_img = np.power(img, 0.6)

toc = time.time()
vec_time = toc - tic

img_stack = np.hstack((img, t_img))
plt.figure(figsize = (10,8))
plt.imshow(img_stack, 'gray')
print("Elapsed Time:", vec_time, 'sec')
Elapsed Time: 0.03403663635253906 sec
In [34]:
print("Elapsed Time Ratio: ", for_time/vec_time)
Elapsed Time Ratio:  36.55002801905296

위의 결과를 확인하면 첫 번째 2중 for loop을 이용했을 때는 말 그대로 각 pixel에 모두 접근하여 연산을 진행하지만 두 번째 vectorization은 matrix 전체를 한 번에 연산한다. (976, 746)의 이미지면 요즘 다루는 이미지들에 비해 큰 편은 아니다. 그리고 이미지에는 총 728096개의 pixel이 있으므로 vectorization의 효과는 이미지가 더 커질수록 빛을 발할 것임을 알 수 있다. 위의 결과에서는 36배의 속도개선이 있었지만 우리가 만약 더 큰 이미지에 대한 dataset을 preprocessing한다면, 아니면 image가 아닌 video에 대한 preprocessing을 한다면 Vectorization 유무의 따른 차이는 실로 어마어마하게 된다.

4_3_1dim_Vectorization_Example3

NumPy Master Class

Chapter4 Vectorization

Notebook3 1-dim Vectorization Example3

이번 시간에는 마지막 1-dimensional vectorization을 다뤄보려고 한다. 왜 이렇게 많이 반복하는지 궁금한 독자들도 있겠지만 생각보다 다른 곳에서 Vectorization을 집중적으로 배워볼 기회가 흔치 않기 때문에 이 강의를 통해서라도 최대한 익숙해졌으면 하는 바람에서 3번째 예제까지 다뤄보려고 한다. 위에 보이는 것처럼 NumPy Master Class이지 않은가.

이번 시간에는 Fourier Transform을 이용하여 Vectorization을 연습해보려고 한다. 이때 모든 주파수에 대해 연산하면 2차원이 되므로 f=10일 때를 구해보도록 하자. 많은 독자들이 이미 이론적으로 충분히 배웠을 것이고, 모르는 독자들도 연산에만 집중해도 충분하다. 그리고 fft라는 NumPy의 method가 있지만 Vectorization의 방법론을 배우기 위하여 이 fft는 사용하지 않도록 하겠다.

먼저 Foureir Transform의 식은 다음과 같다.

X(10)=n=x[n]ej2π10n

그러면 우리의 신호를 다음과 같이 만들어보자.

In [12]:
import numpy as np
import matplotlib.pyplot as plt
import time

from scipy.io import wavfile
fs, data = wavfile.read('./test_audio.wav')
data = (data[:,0] + data[:,1])/2
fig, ax = plt.subplots(figsize = (15,6))
ax.plot(data)
/home/shinks/anaconda3/envs/pytorch/lib/python3.6/site-packages/ipykernel_launcher.py:6: WavFileWarning: Chunk (non-data) not understood, skipping it.
  
Out[12]:
[<matplotlib.lines.Line2D at 0x7f153ab8ab38>]

test audio file을 불러와 stereo를 mono로 바꾼 모습이다.

먼저 for loop을 이용해 Fourier Transform을 구해보자.

cf) X(10)=n=x[n]ej2π10n

In [19]:
tic = time.time()
PI = np.pi

DTFT_10 = 0
for i in range(len(data)):
    DTFT_10 += data[i] * np.exp(-2j*PI*10*i)

toc = time.time()
for_time = toc - tic
print("Elapsed Time:", for_time, 'sec')
print("DTFT_10:", DTFT_10)
Elapsed Time: 0.9304685592651367 sec
DTFT_10: (-5771361-0.001979697650612379j)
In [20]:
tic = time.time()
PI = np.pi

DTFT_10 = np.sum(data * np.exp(-2j*PI*10*np.arange(len(data))))
toc = time.time()
vec_time = toc - tic
print("Elapsed Time:", vec_time, 'sec')
print("DTFT_10:", DTFT_10)
Elapsed Time: 0.08798646926879883 sec
DTFT_10: (-5771361-0.0019796976506123694j)
In [21]:
print("Elapsed Time Ratio:", for_time/vec_time)
Elapsed Time Ratio: 10.575132369757371

이번에는 10배의 성능개선을 확인할 수 있다. 우리가 앞에서 너무 극단적인 예시를 많이 들어서 10배면 뚜렷한 개선이라고 느껴지지 않을 수 있지만, 현실에선 2배 차이만 나도 엄청난 성능개선이 아닌가. 10배 차이면 이 전 예제들보단 개선효과가 덜 하지만 충분히 가치있는 Vectorization이라고 할 수 있다.

4_2_1dim_Vectorization_Example2

NumPy Master Class

Chapter4 Vectorization

Notebook2 1-dim Vectorization Example2

이번 notebook에서는 1-dimensional vectorization에 익숙해져야 더 높은 차원의 vectorization을 쉽게 이용할 수 있으므로 2가지 연습을 더 해보려고 한다.

1. Mean Square Error
2. Binary Cross Entropy
In [1]:
import numpy as np
import matplotlib.pyplot as plt
import time

Mean Square Error

우리가 흔히 MSE라고 부르는 Mean Square Error는 다음과 같은 식을 통해 구한다.

MSE=1NNi=1(x(i)y(i))2

먼저 다음과 같이 Error를 구할 data를 만들어보자.

In [10]:
n_data = 5000000
x_data = np.random.randn(n_data)
y_data = np.random.randn(n_data)

print("x_data[:5]:", x_data[:5])
print("y_data[:5]:", y_data[:5])
x_data[:5]: [ 1.00570714 -0.75556159 -0.48778013  0.31380528  0.60775238]
y_data[:5]: [-0.70677337  0.34879298  0.86662903  0.47356055 -0.30250752]

그리고 먼저 위의 MSE를 for loop을 이용해서 구해보면 다음과 같다.

In [11]:
tic = time.time()

square_sum = 0
for i in range(n_data):
    square_sum += (x_data[i] - y_data[i])*(x_data[i] - y_data[i])
MSE = square_sum / n_data
print("MSE: ", MSE)

toc = time.time()
for_time = toc - tic
print("Elapsed Time:", for_time, 'sec')
MSE:  1.999963383633311
Elapsed Time: 4.2392377853393555 sec

그리고 Vectorization을 이용하면 다음과 같다.

In [13]:
tic = time.time()

MSE = np.mean(np.power(x_data - y_data, 2))
print("MSE: ", MSE)

toc = time.time()
vec_time = toc - tic
print("Elapsed Time:", vec_time, 'sec')
MSE:  1.9999633836334298
Elapsed Time: 0.06562089920043945 sec

이 두 결과를 비교하면 다음과 같다.

In [14]:
print("Elapsed Time Ratio: ", for_time/vec_time)
Elapsed Time Ratio:  64.60194598051113

저번처럼 200배의 차이는 나지 않지만 1분 VS 1시간 관점에서 보면 충분히 비약적인 발전이 있음을 알 수 있다.


Binary Cross Entropy

다음 예제는 Binary Cross Entropy(BCE)이고, 이 BCE는 두 확률분포가 얼마나 닮아있는지 수치적으로 구하는 값이다.

BCE=1NNi=1(y(i)logx(i)+(1y(i))(log(1x(i))))

가 된다. 식의 의미를 모르는 독자들은 굳이 의미까지 이해할 필요는 없다.

BCE는 확률분포가 필요하기 때문에 다음과 같이 0과 1사이의 data를 만들어보자.

In [15]:
n_data = 5000000
x_data = np.random.rand(n_data)
y_data = np.random.rand(n_data)

print("x_data[:5]:", x_data[:5])
print("y_data[:5]:", y_data[:5])
x_data[:5]: [0.81366947 0.80375486 0.81354654 0.53809821 0.93671709]
y_data[:5]: [0.94083929 0.54304959 0.22789704 0.92122947 0.23008166]

그리고 이 data에 대하여 Vectorization 전과 후를 비교해보자.

In [19]:
tic = time.time()

log_sum = 0
for i in range(n_data):
    log_sum += y_data[i]*np.log(x_data[i]) + (1-y_data[i])*np.log(1-x_data[i])
BCE = -1*log_sum/n_data

print("BCE: ", BCE)
toc = time.time()
for_time = toc - tic
print("Elapsed Time:", for_time, 'sec')
BCE:  0.9997791517460576
Elapsed Time: 18.42511510848999 sec
In [20]:
tic = time.time()

BCE = -1*np.mean(y_data*np.log(x_data) + (1-y_data)*np.log(1-x_data))

print("BCE: ", BCE)
toc = time.time()
vec_time = toc - tic
print("Elapsed Time:", for_time, 'sec')
BCE:  0.9997791517461639
Elapsed Time: 18.42511510848999 sec
In [21]:
print("Elapsed Time Ratio: ", for_time/vec_time)
Elapsed Time Ratio:  70.86290579920114

이번 결과는 약 70배의 성능 차이를 보여줬다. 이번에도 충분히 비약적인 성능개선이 이뤄진 것을 알 수 있다.

위와 같이 1-dimensional data에 대해서 2가지 예를 들어봤는데, 모두 비약적인 성능개선을 확인할 수 있었다. 위의 내용이 어려운 독자들도 있을 것이고, 너무 당연하게 느껴지는 독자들도 있을 것이다. 하지만 어느 경우에나 이렇게 Vectorization을 습관화하게 된다면 다른 사람들보다 좋은 프로그램을 만들 수 있을 것이고, 남들과 비교하여 강점을 드러내는 부분이 될 테니 어렵더라도 꾸준히 잘 따라와줬으면 하는 바람이 있다.

+ Recent posts