Neural Network Introduction

들어가며

최근 Machine Learning 분야에서 가장 뜨거운 분야는 누가 뭐래도 Deep Learning이다. 엄청나게 많은 사람들이 관심을 가지고 있고, 공부하고 응용하고 있지만, 체계적으로 공부할 수 있는 자료가 많이 없다는 것이 개인적으로 조금 안타깝다. 이제 막 각광받기 시작한지 10년 정도 지났고, 매년 새로운 자료들이 쏟아져나오기 때문에 책이나 정리된 글을 찾기가 쉽지가 않다. 그러나 Deep Learning은 결국 artificial neural network를 조금 더 복잡하게 만들어놓은 모델이고, 기본적인 neural network에 대한 이해만 뒷받침된다면 자세한 내용들은 천천히 탑을 쌓는 것이 가능하다고 생각한다. 이 글에서는 neural network의 가장 기본적인 model에 대해 다루고, model paramter를 update하는 algorithm인 backpropagation에 대해서 다룰 것이다. 조금 더 advanced한 topic들은 이 다음 글에서 다룰 예정이다. 이 글의 일부 문단은 이전 글들을 참고하였다.

Motivation of Neural Network

이름에서부터 알 수 있듯 neural network는 사람의 뇌를 본 따서 만든 머신러닝 모델이다 (참고: 원래 neural network의 full name은 artificial neural network이지만, 일반적으로 neural network라고 줄여서 부른다). 본격적으로 neural network에 대해 설명을 시작하기 전에 먼저 인간보다 컴퓨터가 훨씬 잘 할 수 있는 일들이 무엇이 있을지 생각해보자.

  • 1부터 10000000까지 숫자 더하기
  • 19312812931이 소수인지 아닌지 판별하기
  • 주어진 10000 by 10000 matrix 의 determinant값 계산하기
  • 800 페이지 짜리 책에서 ‘컴퓨터’ 라는 단어가 몇 번 나오는지 세기

반면 인간이 컴퓨터보다 훨씬 잘 할 수 있는 일들에 대해 생각해보자

  • 다른 사람과 상대방이 말하고자하는 바를 완벽하게 이해하면서 내가 하고 싶은 말을 상대도 이해할 수 있도록 전달하기
  • 주어진 사진이 고양이 사진인지 강아지 사진인지 판별하기
  • 사진으로 찍어보낸 문서 읽고 이해하기
  • 주어진 사진에서 얼마나 많은 물체가 있는지 세고, 사진에 직접 표시하기

컴퓨터가 잘 할 수 있는 0과 1로 이루어진 사칙연산이다. 기술의 발달로 인해 지금은 컴퓨터가 예전보다도 더 빠른 시간에, 그리고 더 적은 전력으로 훨씬 더 많은 사칙연산을 처리할 수 있다. 반면 사람은 사칙연산을 컴퓨터만큼 빠르게 할 수 없다. 인간의 뇌는 오직 빠른 사칙연산만을 처리하기 위해 만들어진 것이 아니기 때문이다. 그러나 인지, 자연어처리 등의 그 이상의 무언가를 처리하기 위해서는 사칙연산 그 너머의 것들을 할 수 있어야하지만 현재 컴퓨터로는 인간의 뇌가 할 수 있는 수준으로 그런 것들을 처리할 수 없다.

예를 들어 아래와 같이 주어진 사진에서 각각의 물체를 찾아내는 문제를 생각해보자 (출처: 링크). 사람에게는 너무나 간단한 일이지만, 컴퓨터가 처리하기에는 너무나 어려운 일이다. 어떻게 어디부터 어디까지가 ‘tv or monitor’라고 판단할 수 있을까? 컴퓨터에게 사진은 단순한 0과 1로 이루어진 픽셀 데이터에 지나지 않기 때문에 이는 아주 어려운 일이다.

그렇기 때문에 자연언어처리, 컴퓨터 비전 등의 영역에서는 인간과 비슷한 성능을 내는 시스템을 만들 수만 있다면 엄청난 기술적 진보가 일어날 수 있을 것이다. 그렇기 때문에 인간의 능력을 쫓아가는 것 이전에, 먼저 인간의 뇌를 모방해보자라는 아이디어를 낼 수 있을 것이다. Neural Network는 이런 모티베이션으로 만들어진 간단한 수학적 모델이다. 우리는 이미 인간의 뇌가 엄청나게 많은 뉴런들과 그것들을 연결하는 시냅스로 구성되어있다는 사실을 알고 있다. 또한 각각의 뉴런들이 activate되는 방식에 따라서 다른 뉴런들도 activate 되거나 activate되지 않거나 하는 등의 action을 취하게 될 것이다. 그렇다면 이 사실들을 기반으로 다음과 같은 간단한 수학적 모델을 정의하는 것이 가능하다.

Model of Neural Network: neuron, synapse, activation function

먼저 뉴런들이 node이고, 그 뉴런들을 연결하는 시냅스가 edge인 네트워크를 만드는 것이 가능하다. 각각의 시냅스의 중요도가 다를 수 있으므로 edge마다 weight를 따로 정의하게 되면 아래 그림과 같은 형태로 네트워크를 만들 수 있다. (출처: 위키)

보통 neural network는 directed graph이다. 즉, information propagation이 한 방향으로 고정된다는 뜻이다. 만약 undirected edge를 가지게 되면, 혹은 동일한 directed edge가 양방향으로 주어질 경우, information propagation이 recursive하게 일어나서 결과가 조금 복잡해진다. 이런 경우를 recurrent neural network (RNN)이라고 하는데, 과거 데이터를 저장하는 효과가 있기 때문에 최근 음성인식 등의 sequencial data를 처리할 때 많이 사용되고 있다. 이번 ICML 2015에서도 RNN 논문이 많이 발표되고 있고, 최근들어 연구가 활발한 분야이다. 이 글에서는 일단 가장 간단한 ‘multi layer perceptron (MLP)’라는 구조만 다룰 것인데, 이 구조는 directed simple graph이고, 같은 layer들 안에서는 서로 connection이 없다. 즉, self-loop와 parallel edge가 없고, layer와 layer 사이에만 edge가 존재하며, 서로 인접한 layer끼리만 edge를 가진다. 즉, 첫번째 layer와 네번째 layer를 직접 연결하는 edge가 없는 것이다. 앞으로 layer에 대한 특별한 언급이 없다면 이런 MLP라고 생각하면 된다. 참고로 이 경우 information progation이 ‘forward’로만 일어나기 때문에 이런 네트워크를 feed-forward network라고 부르기도 한다.

다시 일반적인 neural network에 대해 생각해보자. 실제 뇌에서는 각기 다른 뉴런들이 activate되고, 그 결과가 다음 뉴런으로 전달되고 또 그 결과가 전달되면서 최종 결정을 내리는 뉴런이 activate되는 방식에 따라 정보를 처리하게 된다. 이 방식을 수학적 모델로 바꿔서 생각해보면, input 데이터들에 대한 activation 조건을 function으로 표현하는 것이 가능할 것이다. 이것을 activate function이라고 정의한다. 가장 간단한 activation function의 예시는 들어오는 모든 input 값을 더한 다음, threshold를 설정하여 이 값이 특정 값을 넘으면 activate, 그 값을 넘지 못하면 deactivate되도록 하는 함수일 것이다. 일반적으로 많이 사용되는 여러 종류의 activate function이 존재하는데, 몇 가지를 소개해보도록 하겠다. 편의상 t=iwixi라고 정의하겠다. (참고로, 일반적으로는 weight 뿐 아니라 bais도 고려해야한다. 이 경우 t=i(wixi+bi)로 표현이 되지만, 이 글에서는 bais는 weight와 거의 동일하기 때문에 무시하고 진행하도록 하겠다. - 예를 들어 항상 값이 1인 x0를 추가한다면 w0가 bais가 되므로, 가상의 input을 가정하고 weight와 bais를 동일하게 취급하여도 무방하다.)

  • sigmoid function: f(t)=11+et

  • tanh function: f(t)=etetet+et

  • absolute function: f(t)=t

  • ReLU function: f(t)=max(0,t)

보통 가장 많이 예시로 드는 activation function으로 sigmoid function이 있다. (출처는 위의 위키와 같음)

이 함수는 미분이 간단하다거나, 실제 뉴런들이 동작하는 것과 비슷하게 생겼다는 등의 이유로 과거에는 많이 사용되었지만, 별로 practical한 activation function은 아니고, 실제로는 ReLU를 가장 많이 사용한다 (2012년 ImageNet competition에서 우승했던 AlexNet publication을 보면, ReLU와 dropout을 쓰는 것이 그렇지 않은 것보다 훨씬 더 우수한 결과를 얻는다고 주장하고 있다. 이에 대한 자세한 내용은 다른 포스트를 통해 보충하도록 하겠다). 참고로 neuron을 non-linearity라고 부르기도 하는데, 그 이유는 activation function으로 linear function을 사용하게 되면 아무리 여러 neuron layer를 쌓는다고 하더라도 그것이 결국 하나의 layer로 표현이 되기 때문에 non-linear한 activation function을 사용하기 때문이다.

따라서 이 모델은 처음에 node와 edge로 이루어진 네트워크의 모양을 정의하고, 각 node 별 activation function을 정의한다. 이렇게 정해진 모델을 조절하는 parameter의 역할은 edge의 weight가 맡게되며, 가장 적절한 weight를 찾는 것이 이 수학적 모델을 train할 때의 목표가 될 것이다.

Inference via Neural Network

먼저 모든 paramter가 결정되었다고 가정하고 neural network가 어떻게 결과를 inference하는지 살펴보도록하자. Neural network는 먼저 주어진 input에 대해 다음 layer의 activation을 결정하고, 그것을 사용해 그 다음 layer의 activation을 결정한다. 이런 식으로 맨 마지막까지 결정을 하고 나서, 맨 마지막 decision layer의 결과를 보고 inference를 결정하는 것이다 (아래 그림 참고, 빨간 색이 activate된 뉴런이다).

이때, classification이라고 한다면 마지막 layer에 내가 classification하고 싶은 class 개수만큼 decision node를 만든 다음 그 중 하나 activate되는 값을 선택하는 것이다. 예를 들어 0부터 9까지 손글씨 데이터를 (MNIST라는 유명한 dataset이 있다) classification해야한다고 생각해보자. 그 경우는 0부터 9까지 decision이 총 10개이므로 마지막 decision layer에는 10개의 neuron이 존재하게 되고 주어진 데이터에 대해 가장 activation된 크기가 큰 decision을 선택하는 것이다.

Backpropagation Algorithm

마지막으로 이제 weight를 어떻게 찾을 수 있는지 weight paramter를 찾는 알고리즘에 대해 알아보자. 먼저 한 가지 알아두어야 할 점은 activation function들이 non-linear하고, 이것들이 서로 layer를 이루면서 복잡하게 얽혀있기 때문에 neural network의 weight optimization이 non-convex optimization이라는 것이다. 따라서 일반적인 경우에 neural network의 paramter들의 global optimum을 찾는 것은 불가능하다. 그렇기 때문에 보통 gradient descent 방법을 사용하여 적당한 값까지 수렴시키는 방법을 사용하게 된다.

Neural network (이 글에서는 multi-layer feed-forward network)의 parameter를 update하기 위해서는 backpropagation algorithm이라는 것을 주로 사용하는데, 이는 단순히 neural network에서 gradient descent를 chain rule을 사용하여 단순화시킨 것에 지나지 않는다 (Gradient descent에 대해서는 이전에 쓴 Convex Optimization글에서 자세히 다루고 있으니 참고하면 좋을 것 같다). 모든 optimization 문제는 target function이 정의되어야 풀 수 있다. Neural network에서는 마지막 decision layer에서 우리가 실제로 원하는 target output과 현재 network가 produce한 estimated output끼리의 loss function을 계산하여 그 값을 minimize하는 방식을 취한다. 일반적으로 많이 선택하는 loss에는 다음과 같은 함수들이 있다. 이때 우리가 원하는 d-dimensional target output을 t=[t1,,td]로, estimated output을 x=[x1,,xd] 로 정의해보자.

  • sum of squares (Euclidean) loss: i=1d(xiti)2

  • softmax loss: i=1d[tilog(exij=1dexj)+(1ti)log(1exij=1dexj)]

  • cross entropy loss: i=1d[tilogxi(1ti)log(1xi)]

  • hinge loss: max(0,1tx), 이때 은 내적을 의미한다.

상황에 따라 조금씩 다른 loss function을 사용하지만, classification에 대해서는 보통 softmax loss가 gradient의 값이 numerically stable하기 때문에 softmax loss를 많이 사용한다. 이렇게 loss function이 주어진다면, 이 값을 주어진 paramter들에 대해 gradient를 구한 다음 그 값들을 사용해 parameter를 update하기만 하면 된다. 문제는, 일반적인 경우에 대해 이 paramter 계산이 엄청 쉬운 것만은 아니라는 것이다.

Backpropagtaion algorithm은 chain rule을 사용해 gradient 계산을 엄청 간단하게 만들어주는 알고리즘으로, 각각의 paramter의 grdient를 계산할 때 parallelization도 용이하고, 알고리즘 디자인만 조금 잘하면 memory도 많이 아낄 수 있기 때문에 실제 neural network update는 이 backpropagtaion 알고리즘을 사용하게 된다.

Gradient descent method를 사용하기 위해서는 현재 parameter에 대한 gradient를 계산해야하지만, 네트워크가 복잡해지면 그 값을 바로 계산하는 것이 엄청나게 어려워진다. 그 대신 backpropataion algorithm에서는 먼저 현재 paramter를 사용하여 loss를 계산하고, 각각의 parameter들이 해당 loss에 대해 얼마만큼의 영향을 미쳤는지 chain rule을 사용하여 계산하고, 그 값으로 update를 하는 방법이다. 따라서 backpropagation algorithm은 크게 두 가지 phase로 나눌 수가 있는데, 하나는 propagation phase이며, 하나는 weight update phase이다. propagation phase에서는 training input pattern에서부터 에러, 혹은 각 뉴런들의 변화량을 계산하며, weight update phase에서는 앞에서 계산한 값을 사용해 weight를 update시킨다.

Phase 1: Propagation
  1. Forward propagation: input training data로부터 output을 계산하고, 각 ouput neuron에서의 error를 계산한다. (input -> hidden -> output 으로 정보가 흘러가므로 ‘forward’ propagation이라 한다.)
  2. Back propagation: output neuron에서 계산된 error를 각 edge들의 weight를 사용해 바로 이전 layer의 neuron들이 얼마나 error에 영향을 미쳤는지 계산한다. (output -> hidden 으로 정보가 흘러가므로 ‘back’ propagation이라 한다.)
Phase 2: Weight update
  1. Chain rule을 사용해 paramter들의 gradient를 계산한다.

이때, chain rule을 사용한다는 의미는 아래 그림에서 나타내는 것처럼, 앞에서 계산된 gradient를 사용해 지금 gradient 값을 update한다는 의미이다. (그림은 bengio의 deep learning book Ch6 에서 가져왔다.)

두 그림 모두 zx를 구하는 것이 목적인데, 직접 그 값을 계산하는 대신, y layer에서 이미 계산한 derivative인 zy와 y layer와 x에만 관계있는 yx를 사용하여 원하는 값을 계산하고 있다. 만약 x 아래에 x이라는 parameter가 또 있다면, zx와 xx을 사용하여 zx을 계산할 수 있는 것이다. 때문에 우리가 backpropagation algorithm에서 필요한 것은 내가 지금 update하려는 paramter의 바로 전 variable의 derivative와, 지금 paramter로 바로 전 variable을 미분한 값 두 개 뿐이다. 이 과정을 output layer에서부터 하나하나 내려오면서 반복된다. 즉, output -> hidden k, hidden k -> hidden k-1, … hidden 2 -> hidden 1, hidden 1 -> input의 과정을 거치면서 계속 weight가 update되는 것이다. 예를 들어서 decision layer와 가장 가까운 weight는 직접 derivative를 계산하여 구할 수 있고, 그보다 더 아래에 있는 layer의 weight는 그 바로 전 layer의 weight와 해당 layer의 activation function의 미분 값을 곱하여 계산할 수 있다. 이해가 조금 어렵다면 아래의 예제를 천천히 읽어보기를 권한다.

이 과정을 맨 위에서 아래까지 반복하면 전체 gradient를 구할 수 있고, 이 gradient를 사용해 parameter들을 update할 수 있다. 이렇게 한 번의 iteration이 진행되고, 충분히 converge했다고 판단할 때 까지 이런 iteration을 계속 반복하는 것이 feed-forward network의 parameter를 update하는 방법이다.

이를 그림으로 표현하면 아래와 같다. (출처: 링크)

이렇듯 backpropagation은 직접 weight를 바로 변화시키는 것이 아니라 오직 error만을 보고 gradient descent method based approach를 사용해 error를 minimize하는 방향으로 계속 weight를 update시키는 것이다. 또한 한 번 error가 연산된 이후에는 output layer에서부터 그 이전 layer로 ‘역으로’ 정보가 update되기 때문에 이를 backpropagation, 한국어로는 역전사라고 하는 것이다.

Stochastic Gradient Descent

Gradient를 계산했으니 이제 직접 Gradient Descent를 써서 parameter만 update하면 된다. 그러나 문제가 하나 있는데, 일반적으로 neural network의 input data의 개수가 엄청나게 많다는 것이다. 때문에 정확한 gradient를 계산하기 위해서는 모든 training data에 대해 gradient를 전부 계산하고, 그 값을 평균 내어 정확한 gradient를 구한 다음 ‘한 번’ update해야한다. 그러나 이런 방법은 너무나도 비효율적이기 때문에 Stochastic Gradient Descent (SGD) 라는 방법을 사용해야한다.

SGD는 모든 데이터의 gradient를 평균내어 gradient update를 하는 대신 (이를 ‘full batch’라고 한다), 일부의 데이터로 ‘mini batch’를 형성하여 한 batch에 대한 gradient만을 계산하여 전체 parameter를 update한다. Convex optimization의 경우, 특정 조건이 충족되면 SGD와 GD가 같은 global optimum으로 수렴하는 것이 증명되어있지만, neural network는 convex가 아니기 때문에 batch를 설정하는 방법에 따라 수렴하는 조건이 바뀌게 된다. Batch size는 일반적으로 메모리가 감당할 수 있을 정도까지 최대한 크게 잡는 것 같다.

Backpropagation Algorithm: example

이전에 chain rule로 gradient를 계산한다고 언급했었는데, 실제 이 chain rule이 어떻게 적용되는지 아래의 간단한 예를 통해 살펴보도록하자. 이때 계산의 편의를 위해 각각의 neuron은 sigmoid loss를 가지고 있다고 가정하도록 하겠다.

이때 각각의 neuron의 input으로 들어가는 값을 ino5, output으로 나가는 값을 outh3와 같은 식으로 정의해보자 (이렇게 된다면 in과 out은 outh3=σ(inh3) 으로 표현 가능하다. - 이때 σ는 sigmoid function). 먼저 error를 정의하자. error는 가장 간단한 sum of square loss를 취하도록 하겠다. 우리가 원하는 target을 t라고 정의하면 loss는 E=12(t5outo5)2+12(t6outo6)2가 될 것이다 (1/2는 미분한 값을 깔끔하게 쓰기 위해 붙인 상관없는 값이므로 무시해도 좋다). 그리고 우리가 원하는 값들은 Ew13,Ew14,,Ew46이 될 것이다. 이제 가장 먼저 Ew35 부터 계산해보자.

Ew35=Eouto5outo5ino5ino5w35.

즉, 우리가 원하는 derivative를 계산하기 위해서는 세 개의 다른 derivative (Eouto5,outo5ino5,ino5w35)를 계산해야한다. 각각을 구하는 방법은 다음과 같다.

  • Eouto5: error를 E=12(t5outo5)2+12(t6outo6)2라고 정의했으므로, Eouto5=outo5t5이다. - 이때 outo5와 t5는 weight update이전 propagation step에서 계산된 값이다.

  • outo5ino5o5는 sigmoid activation function을 사용하므로 outo5=σ(ino5)이다. 또한 sigmoid function의 미분 값은 σ(x)x=σ(x)(1σ(x))으로 주어지므로, 이 값을 대입하면 outo5ino5=outo5(1outo5)가 된다. - 역시 여기에서도 미리 계산한 outo5를 사용한다.

  • ino5w35o5로 들어온 값의 총 합은 앞선 layer의 output과 o5로 들어오는 weight를 곱하면 되므로 ino5=w35outh3+w45outh4이고, 이것을 통해 ino5w35=outh3가 됨을 알 수 있다. - outh3 역시 이전 propagation에서 계산된 값이다.

따라서 Ew35의 derivative 값은 위의 세 값을 모두 곱한 것으로 계산 할 수 있다. 그림으로 표현하면 아래와 같은 그림이 될 것이다. 즉, ‘backward’ 방향으로 derivative에 대한 정보를 ‘propagation’하면서 parameter의 derivative를 계산하는 것이다. 마찬가지 방법으로 w36,w45,w46에 대한 derivative도 계산할 수 있다.

그럼 이번에는 그 전 layer의 paramter들 중 하나인 w13의 derivative를 계산해보자. 이번에 계산할 과정도 위와 비슷한 그림으로 표현해보면 아래와 같다.

그러면 이제 Ew13을 구해보자.

Ew13=Eouth3outh3inh3inh3w13.

마찬가지로 각각을 구하는 방법에 대해 적어보자.

  • Eouth3E=12(t5outo5)2+12(t6outo6)2를 E=Eo5+Eo6로 decompose 하면 이 미분 식은 Eo5outh3+Eo6outh3로 쓸 수 있다. 각각의 계산은 다음과 같다.

    • Eo5outh3=Eo5ino5ino5outh3으로 쓸 수 있다. 이 중 앞의 값인 Eo5ino5은 이미 전 과정에서 계산했던 Eouto5과 outo5ino5의 곱으로 계산가능하다. 뒤의 값은 ino5outh3=w35이므로 간단하게 계산할 수 있다.

    • Eo6outh3도 위와 같은 방법으로 연산이 가능하다.

  • outh3inh3outo5ino5와 같다. 따라서 outh3(1outh3)이다.

  • inh3w13ino5w35와 같다. 따라서 outi1이다.

이렇게 Eouth3에서는 앞에서 계산했던 값들을 재활용하고, 아래의 값들은 activation function과 network의 topological property에 맞는 derivative를 곱하는 방식으로 Ew13을 구할 수 있다.

이렇듯 backpropagation algorithm은 forward propagation을 통해 필요한 값들을 미리 저장해두고, backward propagation이 진행되면서 위에서부터 loss에 대한 derivative를 하나하나 계산해나가면서 다음 layer에서 바로 전 layer에서 계산한 값들과 각 neuron 별로 추가적으로 필요한 derivative들을 곱해나가면서 weight의 derivative를 계산하는 알고리즘이다.

이렇게 한 번 전체 gradient를 계산한 다음에는 learning rate를 곱하여 전체 parameter의 값을 update한 다음, 다시 처음부터 이 과정을 반복한다. 보통 에러가 감소하는 속도를 관측하면서 ‘이 정도면 converge한 것 같다’ 하는 수준까지 돌린다.

익숙해지려면 다소 시간이 걸리지만, 개념적으로 먼저 ‘error를 먼저 계산하고, 그 값을 아래로 전달해나가면서 바로 전 layer에서 계산한 미분값들을 사용해 현재 layer의 미분값을 계산한 다음, 그 값을 사용해 다음 layer의 미분값을 계산한다.’ 라고 개념만 이해해두고 다시 차근차근 chain rule을 계산해나가면서 계산하면 조금 편하게 익숙해 질 수 있을 것이다.

Backpropagation Algorithm: In Practice

실제 backpropagtion을 계산해야한다고 가정해보자. 편의상 l번째 hidden layer를 yl 이라고 해보자. 이 경우 각 layer에 대해 backpropagation algorithm을 위해 계산해야할 것은 총 두 가지 이다. Loss를 E라고 적었을 때 먼저 layer l의 parameter θl의 gradient인 Ewl을 구해야한다. 이 값은 Ewl=Eylylwl을 통해 계산한다. 이때, Eyl=Eyl+1yl+1yl이므로 Eyl은 바로 전 layer에서 넘겨준 Eyl+1의 값을 사용하여 계산하게 된다. 정리하면 실제 계산해야하는 값은 yl+1yl,ylwl 두 가지이고, 이 값들을 사용해 Eyl,Ewl을 return하게 된다. 앞의 값은 다음 layer에 넘겨줘서 다음 input으로 사용하고, 두 번째 값은 저장해두었다가 gradient descent update할 때 사용한다.

두 가지 예를 들어보자. 먼저 Inner Product layer 혹은 fully connected layer이다. 이 layer가 inner product layer라고 불리는 이유는 input yl에 대해 output yl+1이 간단한 inner product 들이 모여있는 형태로 표현되기 때문이다. 예를 들어 yl+1,i를 l+1 번째 layer의 i 번째 node라고 한다면, yl+1,i=jwijyl,j으로 표현할 수 있음을 알 수 있다. 그런데 이 값은 사실 vector w와 yl의 inner product로 표현됨을 알 수 있다. 그렇기 때문에 fully connected layer를 inner product라고 부른다. 다시 본론으로 돌아와서 inner product의 output은 input과 weight의 matrix-vector multiplication인 yl+1=Wlyl으로 표현할 수 있다.

따라서 yl+1yl=Wl이고, ylWl=yl이다. 이 값을 통해 실제 return하는 값은 Eyl=Eyl+1Wl와 Ewl=Eyl+1yl이 된다.

두 번째로 많이 사용하는 ReLU non-linearity의 gradient를 계산해보자. 이때 activation function은 마치 하나의 layer가 더 있는 것처럼 생각할 수 있다. 즉 yl+1=max(0,yl)로 표현할 수 있을 것이다. Parameter는 없으니까 생략하면 만약 yl0라면 yl+1yl=1이고, 아니라면 0이 될 것이다. 따라서 yl0라면 Eyl=Eyl+1이 되고, 0보다 작다면 0이 될 것이다.

정리

Deep learning을 다루기 위해서는 가장 먼저 aritifitial neural network의 model에 대한 이해와 gradient descent라는 update rule에 대한 이해가 필수적이다. 이 글에서는 가장 기초적이라고 생각하는 feed-forward network의 model을 먼저 설명하고, paramter를 update하는 gradient descent algorithm의 일종인 backpropagation에 대한 개념적인 설명을 다루었다. 조금 어려울 수 있는 내용이니 다른 글들을 계속 참고하면서 보면 좋을 것 같다.

Reference

출처 : http://sanghyukchun.github.io/


Data-driven marketing, Data-driven decision making, 데이터 중심적 사고 등 데이터를 중심으로 사고하는 것에 대한 중요성이  Business World를 중심으로 급격히 성장하고 있다. 따라서 다양한 분야에서 스스로가 데이터 중심의 사고를 하고 있는지 점검하고, 이를 강화하는 방법이 있는지에 대해 궁금해 하는 사람들이 많을 것이다. 이번 포스팅에서 소개하는 글은 Data-driven의 특성을 정의하고, 사례와 인용을 통해 각각의 특성들이 실제 비즈니스에서 어떻게 응용 되는지 소개하고 있다. (원문)

당신은 데이터 중심적(data driven)인가? 

데이터 중심적 (data driven) 이라는 용어는 오늘날 가장 부각되는 어휘이다. Data Driven은 필자의 최근 저서의 이름이기도 하며, 최근의 학술 연구들은 스스로를 “data driven” 이라고 언급하는 회사들이 그렇지 않은 회사들에 비해 수익성이 좋다는 것을 보여주고 있다. 따라서 데이터 중심적으로 변화하는 것은 노력할 만한 가치가 있는 일이다.

지금까지의 노력에도 불구하고, 필자도 아직 리더가 자신의 조직이 더 잘하기 위해 필요한 것을 발견할 수 있는 벤치마크나 기준을 찾지 못했다.

개인적으로 “data-driven”의 핵심은 조직의 차트를 오르고 내리게 만드는 의사결정을 더욱 잘하게 만드는 데 있다고 본다. 몇 년전, 운 좋게도 많은 의사 결정자 및 의사 결정 그룹과 일할 기회가 있었다. 그들 중 몇몇은 훌륭한반면, 몇몇은 끔찍했지만, 그 일을 하면서, “data-driven의 12가지 특성”을 도출할 수 있었다.


 

데이터 중심적 (Data-Driven) 사고를 하는 사람들의 특성

  • 가능한 가장 낮은 단계에서 의사 결정을 수행
  • 가능한한 여러 상황의 다양한 데이터를 수집
  • 깊이 이해할 수 있도록 데이터를 발전
  • 변화에 대한 인지의 발전
  • 불확실성에 대한 합리적 처리
  • 데이터와 그 영향을 이해하는 능력과 직관의 통합
  • High-quality 데이터와 이를 개선하기 위한 투자의 중요성에 대한 인식
  • 훌륭한 실험자 및 연구자의 자질
  • 의사 결정의 기준이 상황에 따라 변할 수 있다는 점에 대한 인식
  • 의사 결정은 단지 첫 단추에 불과하다는 점의 인식
  • 새로운 스킬, 새로운 데이터, 새로운 기술 (빅데이터, 예측 모델, 메타데이터 관리 등)을 조직 내 주입하기 위한 노력
  • 실수로 부터의 배움

 

이 모든 특성들은 중요하다. 대부분은 그 이유가 자명하지만, 몇몇은 추가적인 설명이 필요하다. 첫번째는 데이터 중심의 회사가 가장 낮은 단계에서 의사 결정을 해야한다는 것이다. 필자와 대화를 나눈 한 임원은 이에 대해 다음과 같이 설명했다. “내 목표는 일년에 6개의 결정만 하는 것 입니다. 이는 내가 가장 중요한 6개를 골라내야 한다는 것이고, 나에게 보고하는 사람은 반드시 데이터와 이에 대한 확신을 갖고 있어야 하며, 그들은 나머지에 대한 의사 결정을 해야 합니다.” 조직 하부에서 의사 결정하는 것을 유도하면 고위직은 가장 중요한 의사 결정을 하는데 필요한 시간을 확보할 수 있다. 또 중요한 것은 하급 직원들에게 의사 결정 권한이 떨어지면, 그들이 이를 위해 더 많은 시간과 관심을 쏟는다는 점이다. 이는 조직적 역량을 올바르게 강화시키고, 업무 환경을 보다 즐겁게 만든다.

두번째, 데이터 중심적 사고를 하는 사람들은 변화에 대한 타고난 감각을 갖고 있다. 가장 단순한 프로세스, 사람들의 응답, 또는 가장 통제 되는 상황조차도 달라진다. 그들은 관리도를 이용하지 않지만, 무슨 일이 일어나고 있는지 이해하려면 변화를 이해해야 한다는 점을 알고 있다. 한 중간급 관리자는 이를 다음과 같이 설명했다. “제가 첫 관리 업무를 맡았을때, 저는 결과물을 놓고 매주 고뇌했습니다. 몇주는 약간 올랐지만, 나머지는 다 하락 했습니다. 저는 상승에 대한 공을 차지하고자 노력했고, 침체가 되면 또 괴로워했습니다. 제 상사는 저에게 이를 그만두라고 했습니다. 확실히 상황은 악화 되었습니다. 모든 것들은 요동친다는 것을 배우는 데는 오랜 시간이 걸렸습니다.”

세번째, 데이터 중심적 사람들은 데이터와 데이터 소스에 대한 높은 수요가 있을 때에 존재한다. 그들은 그들이 내린 결정이 기반이 된 데이터보다 낫지 않다는 것을 안다. 따라서 데이터의 품질과 믿을 만한 데이터 소스를 만드는데 투자한다. (참고) 그 결과, 시간에 민감한 이슈가 발생했을 때를 대비한 준비가 되어 있다. 고품질의 데이터는 다양성을 이해하는 것과 불확실성을 감소시키는 것을 쉽게 해준다. 성공은 실행에 의해 측정 되고 고품질 데이터를 통해 직원들이 의사 결정자의 논리를 이해하고 쉽게 따르게 만든다.

더 나아가, 한 번의 실행은 더 많은 데이터를 얻게 한다. 따라서 데이터 중심적 사람들은 지속적으로 그들의 결정을 재평가하고, 정제한다. 특히 이들은 의사 결정이 틀렸다는 증거가 제시 될 때, 다른 사람들보다 빠르게 대응한다. 이는 데이터 중심적 사람들이 급회전을 한다는 것을 의미하진 않는다. 그들은 결정이 지속 가능하지 않다는 것을 안다.

 

 

이제 거울을 들여다보자. 상단의 리스트를 들여다보고 각각의 특성에 대해 점수를 매겨보자. 정기적으로 잘 따르는 항목은 1점, 전부는 아니지만 대부분 따르는 항목은 0.5점. 한 두번 한 것이라면 점수를 주면 안된다.

만약 당신이 7점 미만이라면, 더 올려야 할 필요가 있다. 각각의 사람이나 조직은 서로 다르기 떄문에 의사 결정을 조직의 하부로 보내는 것부터 시작하기를 추천한다. 이에 따른 이익은 이미 언급하였다. 모든 것을 제어하고 싶어하는 매니저들에겐 아마 매우 힘들고 반직관적일 것이다. 하지만 충분히 시도할 가치가 있다.

둘째로 고품질의 데이터에 투자해라. (참고) 실로 당신의 데이터와 그 출처에 대한 높은 신뢰가 없다면 데이터 중심의 사고를 할 수 없다. 당신의 직관을 반으로 감소시키는 것을 목표로 해라.

마지막 한단계는 스스로를 들여다보는 것이다. 당신의 조직을 위해 똑같은 일을 하는 팀을 참여 시켜라.

<Harvard Business Review>

출처 : http://eunwoopark.com/wp/category/bigdata/ : 사라짐.

'데이터 사이언트' 카테고리의 다른 글

R - 데이터 고급 분석과 통계 프로그래밍  (0) 2016.05.10

(번역) 서버리스 아키텍처

이 글은 마틴 파울러의 웹사이트에 올라온 Serverless Architectures을 번역한 글입니다. 원문이 계속 업데이트 되기 때문에 번역본과 원문을 함께 보시면 더욱 도움이 될 겁니다.


2016년 6월 17일

마이크 로버츠 Mike Roberts
마이크는 뉴욕에 사는 엔지니어링 리더이다. 요즘엔 팀 매니지먼트가 주요 업무이긴 하지만 여전히 클로주어 Clojure 쪽에서 코딩도 하고 소프트웨어 아키텍처 쪽에서도 활발한 의견 개진을 하고 있다. 그는 지금 사람들이 서버리스 아키텍처에 대해 주목하는 현상에 대해 꽤 긍정적이다.
 
아래 태그들을 통해 비슷한 문서들을 찾을 수 있다:
application architecture


서버리스는 요즘 소프트웨어 아키텍처 세상에서는 아주 핫한 토픽입니다. 책들도 나왔고, 오픈소스 프레임워크도 있고, 수많은 벤더들이 프레임워크를 내놨죠. 게다가 아예 서버리스만을 주제로 하는 컨퍼런스까지 생겼습니다. 그런데, 도대체 서버리스가 뭘까요? 그리고 어째서 이 서버리스를 고려해야 (혹은 고려하지 말아야) 할까요? 이 계속 업데이트 되는 문서를 통해 저는 당신이 이러한 질문들에 대한 답을 구할 수 있는 빛을 찾기를 바랍니다.

서버리스란 무엇인가?

소프트웨어 업계에서 늘상 그렇듯이, 서버리스에 대한 명확한 관점은 없습니다. 그리고 아래 두 가지의 다르지만 겹치는 부분이 있는 이러한 견해들 역시도요:

  1. 서버리스는 서버단 로직이나 상태 등을 관리하기 위한 써드파티 애플리케이션 혹은 클라우드 서비스에 현저히 또는 온전히 의존하는 애플리케이션들을 설명하기 위해 쓰였습니다. 주로 리치 클라이언트 애플리케이션(예를 들자면 단일 페이지 웹 애플리케이션이나 모바일 앱 같은 것들)을 가리키는데, 클라우드에서 접근 가능한 ParseFirebase 같은 데이터베이스라든가, Auth0, AWS Cognito 같은 인증 서비스들 같은 거대한 생태계를 사용하는 것들입니다. 예전에는 이러한 서비스들을 (Mobile) Backend as a Service라고 불렀으니, 여기에서는 이들을 그냥 BaaS라고 부르도록 하죠.

  2. 또한 서버리스는 개발자들이 서버단 로직을 개발자들이 짜긴 하지만, 전통적인 아키텍처와는 달리 상태를 저장하지 않는 Stateless 컴퓨팅 컨테이너에 넣고 돌리는 애플리케이션을 의미하기도 합니다. 이러한 애플리케이션은 보통 이벤트 기반으로 작동하고, 한 번 쓰고 버리고, 써드파티에 의해 관리되죠(ThoughWorks는 최근 자사 포스트에서 이렇게 정의했습니다). 이런 방식으로 생각해 볼 수 있는 한가지 방법은 Functions as a Service(또는 FaaS)입니다. AWS 람다는 현재 이 FaaS계의 가장 인기있는 구현체지요. 하지만 다른 것들도 더 있습니다. 여기서는 바로 이 FaaS서버리스의 의미로 사용하도록 하겠습니다.

저는 주로 두 번째 얘기를 할텐데요, 조금 더 새롭기도 하고 우리가 흔히 기술적인 아키텍처에 대해 생각하는 것과 현격한 차이가 있기도 합니다. 게다가 요즘 서버리스라는 것에 대한 수많은 얘기들이 오고가기 때문이기도 하구요.

하지만, 이러한 개념들이 사실은 모두 관련이 있고 하나로 모여들고 있습니다. Auth0가 하나의 좋은 예가 될 수 있겠네요. 처음에 BaaS 형태인 Authentication as a Service로 시작했다가 지금은 Auth0 Webtask를 통해 FaaS 영역으로 들어왔습니다.

게다가 BaaS 형태의 애플리케이션을 개발하는 많은 경우, 특히 모바일 앱과 반대로 리치 웹 앱을 개발하는 경우, 어느 정도 서버단의 커스텀 기능들이 여전히 필요합니다. 특히 당신이 사용하고 있는 BaaS 서비스와 어느 정도 통합을 한다면 FaaS 가 이런 경우 좋은 솔루션이 될 수 있습니다. 이러한 기능들의 좋은 예로는 데이터 유효성 검사(악성 클라이언트로부터 보호하기 위한)라든가 많은 계산 용량을 필요로 하는 작업들(이미지나 비디오 프로세싱 같은 것들)이 있겠지요.

몇 가지 예제

UI 주도 애플리케이션

서버단에 로직이 있는 전통적인 쓰리티어 클라이언트 시스템을 봅시다. 전자상거래 시스템들 같은 것이 좋은 예가 되겠네요. 예를 들자면 온라인 애완동물 용품 사이트 같은 것.

전통적으로 이런 아키텍처는 이런 식으로 생겼습니다. 서버단에 자바로 구현했고, 클라이언트단에는 HTML과 자바스크립트로 구현하죠.

이런 아키텍처에서 클라이언트는 상대적으로 그닥 똑똑하지 않습니다. 대부분의 로직들 – 인증, 페이지 네비게이션, 검색, 트랜잭션 등은 서버단에서 구현을 해놨으니까요.

서버리스 아키텍처에서는 이렇게 보일 겁니다:

엄청나게 간단하게 그린 모델인데요, 그럼에도 불구하고 여전히 수많은 변화들이 일어난 것을 볼 수 있습니다. 여기서 잠깐! 이건 단순히 서버리스 개념을 보여주기 위한 도구로서 만든 그림이지 이게 이런 식으로 아키텍처를 이전해야 한다고 추천하는 건 아니라는 것을 기억해 두세요!

  1. 최초 애플리케이션에서 인증 로직 부분을 빼고 써드파티 BaaS 서비스로 교체했습니다.

  2. 또다른 BaaS의 예로, 상품 리스트 출력을 위해서 클라이언트단이 직접 데이터베이스를 접속하게 했습니다. 이 데이터베이스는 AWS의 Dynamo DB 같이 전적으로 써드파티 데이터베이스가 됩니다. 클라이언트단에서 데이터베이스에 접속할 수 있는 다른 보안 프로파일을 적용하는 방식으로 다른 서버 리소스에서도 데이터베이스에 접근할 수 있게도 할 수 있습니다.

  3. 앞서 언급한 두 가지 포인트는 굉장히 중요한 이 세번째 포인트를 암시합니다 – 쇼핑몰 서버단에 있던 로직들이 이제는 클라이언트단으로 옮겨갔다는 거죠. 예를 들자면 사용자 세션 추적이라든가, 페이지 네비게이션 같은 애플리케이션의 UX 구조를 이해하는 로직이라든가 데이터베이스에서 읽어들인 자료를 사용자 뷰에 맞는 형식으로 변환하는 것들이라든가 하는 것들 말입니다. 이렇게 되면 사실 클라이언트단은 이제 단일 페이지 애플리케이션이 되는 셈입니다.

  4. UX관련 기능들중 어떤 것들은 서버단에 계속 두고 싶을 거예요. 예를 들자면 많은 계산 용량을 필요로 한다든가 대용량 데이터에 접근을 해야 한다든가 하는 것들이죠. 검색 기능을 예로 들 수 있을텐데요, 검색 기능을 위해서 항상 서버를 돌리기 보다는 API 게이트웨이(나중에 다시 설명합니다)를 이용한 FaaS 펑션을 구현해서 HTTP 리퀘스트에 응답하게끔 하면 됩니다. 그렇게 함으로써 우리는 클라이언트단과 서버단에 기능을 두고 상품 데이터가 있는 같은 데이터베이스에서 읽어들이게 할 수 있습니다.

    원래 서버단 기능들을 자바로 구현했고, 이 포스트에서 선택한 FaaS 제공자로서 AWS 람다 서비스를 자바를 지원하기 때문에, 온라인 쇼핑몰의 검색 기능 관련 코드를 서버단에서 람다로 코드를 다시 쓰지 않고도 쉽게 옮길 수 있습니다.

  5. 마지막으로 상품구매 기능을 다른 FaaS 펑션으로 대체할 수 있습니다. 보안상의 이유로 클라이언트단으로 옮기기 보다는 서버단에 이 기능들을 놓는 것이 낫기 때문입니다. 물론 API 게이트웨이를 그 앞에 놓았습니다.

메시지 주도 애플리케이션

다른 예를 하나 더 들자면 백엔드에서 돌아가는 데이터 프로세싱 서비스가 될 겁니다. 지금 당신이 사용자 중심의 애플리케이션을 하나 개발하고 있다고 치죠. 이 애플리케이션은 UI 리퀘스트에 재빨리 반응을 해야 합니다. 하지만 동시에 현재 일어나는 모든 종류의 액티비티들을 로그로 저장하고 싶어합니다. 온라인 광고 시스템을 한 번 생각해 봅시다 – 사용자가 광고를 클릭할 때 사용자를 재빨리 해당 광고의 타겟으로 보내고 싶습니다. 동시에 사용자 클릭이 발생했다는 것을 잡아내서 광고주에게 과금할 수 있어야 합니다.

전통적으로 이런 아키텍처는 보통 광고 서버가 동기적으로 사용자에게 반응(여기서는 그 반응이 어떤 것인지에 대해서는 상관하지 않습니다)하는 동시에 채널을 통해 메시지를 보내서 비동기적으로 클릭 프로세서를 실행시켜 데이터베이스를 업데이트합니다. 데이터베이스 업데이트에는 광고주 예산에서 광고만큼 금액을 집행하는 것들이 있을 수 있겠죠.

그런데, 서버리스 세상에서는 위의 모델이 아래와 같이 바뀝니다.

앞서 예를 든 것과는 차이가 그렇게 많이 나는 것처럼 보이지는 않네요. 우리는 여기서 계속 돌아가는 서버단의 프로세스를 이벤트 주도 형태의 콘텍스트 안에서 돌아가는 FaaS로 바꿨습니다. FaaS 서비스 제공자는 우리에게 서로 연결되어 있는 메시지 브로커(Message Broker)와 FaaS 환경을 제공합니다.

또한 이 FaaS 환경은 동시에 일어나는 클릭들도 펑션 코드를 클릭 이벤트 숫자에 맞게 감지해서 처리합니다. 기존 애플리케이션의 프로세스에 이런 병렬코드 진행 부분이 없었다면 이 새로운 개념을 적용시켜야 할 수도 있습니다.

Function as a Service 뒤집어보기

우리는 이미 FaaS에 대해 여러번 언급을 해 왔습니다. 이제부터는 도대체 그게 뭔지 좀 더 파고 들어갈 때가 됐습니다. 우선 아마존의 람다 서비스에 대한 설명을 좀 보도록 하죠. 번호를 군데군데 매겨놨는데요, 잠시 후에 설명하도록 하겠습니다.

AWS 람다는 서버를 만든다거나 관리할 필요 없이 당신의 코드를 실행시킬 수 있다. (1) … 람다와 함께라면 당신은 어떤 형태의 애플리케이션이나 백엔드 서비스에서도 코드를 돌릴 수 있다. (2) – 관리 비용은 전혀 필요가 없다. 그저 당신의 코드를 업로드하면 람다가 실행에 필요한 모든 것들을 알아서 관리해 주고 (3), 필요하면 스케일링도 해주면서 (4) 계속 높은 가용성을 유지시켜 준다. 당신은 다른 AWS 서비스로부터 자동으로 트리거링을 받게끔 코드를 작성할 수도 있고 (5) 어떤 웹이나 모바일 앱등에서도 이를 직접 호출하여 실행시킬 수 있다. (6)

  1. 기본적으로 FaaS는 당신이 서버 시스템들 없이 또는 서버 애플리케이션 없이 백엔드 코드를 실행시키는 것입니다. 서버 애플리케이션이라는 구절이 바로 핵심적인 차이인데, 이것은 다른 현대적인 아키텍처의 흐름, 컨테이너라든가 PaaS(Platform as a Service) 등을 의미합니다.

    만약 다시 위의 클릭 처리 예제로 돌아간다면, FaaS는 클릭 처리를 담당하는 서버(물리적 머신일 수도 있지만 어쨌거나 실제 그 용도로 쓰이는 애플리케이션)를 서버 프로비저닝이 필요하거나 항상 돌아가야 하는 애플리케이션이 아닌 다른 무언가로 바꾸는 것입니다.

  2. FaaS는 굳이 특정 프레임워크나 라이브러리에 의존해서 코딩하는 것을 필요로 하지 않습니다. FaaS 펑션은 아무 언어 혹은 환경에서도 작동하는 하나의 애플리케이션입니다. 예를 들어, AWS 람다 펑션은 자바스크립트라든가, 파이쎤 혹은 자바, 클로저, 스칼라 등의 아무 JVM 언어들로 구현할 수 있습니다. 또한 당신의 람다 펑션은 설치 아티팩트와 함께 묶여있기만 한다면 다른 프로세스를 통해서 아무 언어나 실행시킬 수 있습니다. 유닉스 혹은 리눅스 환경에서 컴파일이 가능하다면 말이죠(나중에 Apex를 다뤄보겠습니다). FaaS 펑션은 상태라든가 굉장히 제한적인 아키텍처를 갖고 있습니다. 상태라든가 실행 시간 같은 것들을 고려해야 한다면 말이지요. 이건 잠시 후에 다시 설명하기로 하죠.

    다시 앞서의 클릭 프로세스로 돌아가 봅시다. FaaS로 옮겨갈 때 코드를 변경해야 하는 유일한 부분은 바로 main 메소드 혹은 startup 코드 부분입니다. 이 부분은 필요가 없고, 대신 최상위 계층의 (메시지 리스너 인터페이스를 구현한) 메시지 핸들러로 변경하면 됩니다. 이 부분이 유일한 코드 변경점이죠. 코드의 나머지 부분은 (예를 들어 데이터베이스에 접근한다든가 하는 부분) FaaS 세상에서도 변함없이 똑같습니다.

  3. 이제 우리는 실행시킬 서버 애플리케이션이 없습니다. 그래서 설치 과정 역시도 전통적인 애플리케이션과 굉장히 달라지게 됩니다. 그저 코드를 FaaS 제공자로 업로드하면 나머지는 그쪽에서 다 알아서 하게 되죠. 지금 현재로서는 이것은 새로 수정한 코드 혹은 새로 만든 코드를 .zip 파일 혹은 JAR 파일로 묶어서 올리는 것을 의미하구요, 개별 FaaS 서비스 제공자의 내부 API를 통해 이 수정 사항을 실행시키게끔 호출하는 것으로 보면 됩니다.

  4. 수평적 스케일링은 이제 완전히 자동화가 됐구요, 서비스 제공자가 다 알아서 합니다. 만약에 당신의 시스템이 100개의 리퀘스트를 동시에 처리해야 한다면, 내 쪽에서 별도의 설정 같은 것을 하지 않아도 서비스 제공자가 알아서 다 합니다. 이렇게 당신의 펑션을 실행하는 컴퓨팅 컨테이너는 일시적으로 FaaS 제공자가 관리하고 파기하는 형태여서 순전히 런타임에서만 잠깐 필요한 정도가 됩니다.

    다시 우리의 클릭 프로세서 예제로 돌아가보죠. 사용자가 평소보다 한 열 배 정도는 광고 클릭을 더 많이 한다고 가정해 봅시다. 기존의 클릭 프로세싱 애플리케이션은 이걸 처리할 수 있을까요? 다시 말해서 당신의 코드는 한 번에 여러 개의 메시지를 처리할 수 있을까요? 심지어 우리가 할 수 있다고 해도 애플리케이션의 실행 인스턴스가 하나만 있다면 이걸 감당할 만큼 충분할까요? 만약 다중 프로세스를 돌릴 수 있다면 우리는 이걸 자동으로 오토 스케일링 설정을 해 놓아야 할까요 아니면 수동으로 그때그때 설정해야 할까요? FaaS라면 당신은 그저 펑션을 작성할 때 병렬 프로그래밍을 가정하고 작성하면 됩니다. 그러면 FaaS 제공자는 스케일링이 필요할 경우 알아서 다 해주죠.

  5. FaaS에 있는 펑션들은 서비스 제공자가 정의한 이벤트 타입에 의해 실행될 수 있습니다. 아마존 AWS의 경우에는 S3 파일 업로드 이번트, 스케줄링 작업에 따른 시간, Kinesis와 같은 메시지 버스에 메시지가 추가되는 이벤트 같은 것들이 있습니다. 이럴 경우 당신의 펑션은 보통 연결되어 있는 특정 이벤트에 대응하는 파라미터 값들을 제공해야 합니다. 예시로 든 클릭 프로세서의 경우에는 이미 FaaS에 대응하는 메시지 브로커를 사용하고 있다고 가정합니다. 만약 그렇지 않다면 바꿔야 하구요, 이 경우에는 메시지 생성하는 로직을 수정할 필요가 있습니다.

  6. 대부분의 서비스 제공자들은 펑션들이 HTTP 리퀘스트에 응답을 보내게끔 구현되어 있습니다. 예를 들자면 API 게이트웨이 같은 형식으로 말이지요. 이런 것들에는 AWS API 게이트웨이, Webtask 등이 있습니다. 우리는 앞서 예시로 든 애완동물 온라인 쇼핑몰에서 검색 기능과 구매 기능에 이용하고 있죠.

상태

FaaS 펑션을 내 로컬 머신 혹은 로컬 인스턴스에서 돌릴 때는 굉장히 제한적입니다. 즉, 어떤 펑션을 실행시킬 때 당신이 생성한 어떤 프로세스 혹은 호스트 상태가 다음에 이어지는 펑션으로 어떤 식으로든* 전달되지 않는다고 가정해야 합니다. 이것은 RAM 안에 저장된 상태도 포함하구요, 로컬 디스크에 뭔가를 저장하는 어떤 형태의 상태 역시도 포함합니다. 다시 말해서 설치 유닛 관점에서 *FaaS 펑션은 상태를 저장하지 않습니다(Stateless).

이것은 애플리케이션 아키텍처에 지대한 영향을 줍니다. FaaS가 유일한 건 아니지만요 – 12요소 앱 개념은 정확하게 똑같은 제한점을 갖고 있습니다.

그렇다면 이러한 제한요소를 인정한다고 할 때, 어떤 대안이 있을까요? 보통 FaaS 펑션은 원래 상태 저장기능이 없어서(Stateless) 단순히 입력값을 다른 출력값으로 변경시킨다거나 또는 데이터베이스, Redis 같은 크로스플랫폼 캐시, 혹은 S3 같은 네트워크 파일 스토리지 같은 것들을 통해 리퀘스트 전반에 걸쳐 상태를 저장시키고 그걸 이용해서 좀 더 사용자 요청을 처리합니다.

실행 기간

FaaS 펑션은 개별 실행에 있어 보통 제한시간이 있습니다. 현재 AWS 람다의 경우에는 5분 이상 걸리는 펑션은 실행에 실패하게끔 되어 있구요, 만약 5분 이상 걸릴 경우 자동으로 폐기됩니다.

이것은 오랜 시간을 필요로 하는 작업이라면 새롭게 아키텍처를 변경하지 않는 이상 FaaS 펑션에는 적합하지 않다는 것을 의미합니다. 다시 말해서 전통적으로는 하나의 큰 펑션으로 만들어서 그 안에서 모든 것을 다 처리하는 펑션으로 만들었다면 이제 FaaS에서는 이것을 잘게 쪼개서 각각 별도로 처리하는 형태로 구조를 변경해야 한다는 것이죠.

초기 실행 지연

현재 FaaS 펑션이 리퀘스트에 응답하는데 걸리는 시간은 여러 가지 요소들에 의해 결정되긴 하지만 대략 10ms 에서 2분 정도 사이가 될 겁니다. 딱히 좋은 얘기는 아닌 것 같기는 한데, 조금 더 구체적으로 들어가 보도로 하죠. AWS 람다의 예를 들어 봅시다.

만약에 자바스크립트나 파이썬으로 펑션을 구현했고 그 펑션의 크기가 대략 1천 줄 미만의 코드량으로 그다지 크지 않다면, 실행에 필요한 시간은 아무리 많아야 10ms 에서 100ms를 넘지 않을 겁니다. 펑션의 크기가 커진다면 아무래도 종종 시간이 오래 걸리겠죠.

만약 람다 펑션을 JVM 위에서 구현했다면 종종 10초 이상 걸리는 응답시간을 보일 겁니다. 아무래도 JVM이 구동되기 위해 필요한 시간이겠죠. 하지만, 이것은 아래와 같은 상황에서만 일어나는 상황입니다.

  • 펑션을 자주 실행시키지 않는 경우 – 각 실행 주기가 10분을 넘는 경우
  • 갑자기 트래픽이 늘어나는 경우 – 초당 10개의 리퀘스트를 처리하다가 갑자기 초당 100개의 리퀘스트를 처리하는 식으로 짧은 시간 안에 급격하게 트래픽이 증가하는 경우

전자 같은 경우에는 매 5분 정도마다 핑 리퀘스트를 날려서 계속 서버가 살아있게 하는 식의 핵으로 해결할 수 있습니다.

그렇다면 후자의 경우에 이런 것들이 문제가 될 수 있을까요? 애플리케이션이 트래픽을 처리하는 스타일에 따라 달라질 겁니다. 예전 팀에서는 자바로 비동기 방식의 메시지 처리 람다 애플리케이션을 만들어서 하루에도 수백만개의 메시지를 처리했습니다. 초기 실행 지연 같은 것에는 아무런 걱정이 없었지요. 그건 지연시간이 낮은 트레이딩 애플리케이션을 개발한다면 딱히 이 상황에서는 FaaS를 고려할 이유가 없습니다. 무슨 언어로 개발하든지간에요.

이런 문제가 당신이 개발한 애플리케이션에서 생길지 아닐지는 모르겠지만, 실제 운영 환경에서와 같은 트래픽으로 테스트를 해 볼 필요는 있어요. 그래야 실제 퍼포먼스를 측정할 수 있죠. 만약 당신의 유즈 케이스가 지금 잘 동작하지 않는다면 한 두어달 쯤 후에 다시 시도해 볼 수 있습니다. FaaS 서비스 공급자가 개발해야 할 영역이거든요.

API 게이트웨이

FaaS가 갖는 특징들 중 하나는 앞서 살짝 언급한 API 게이트웨이입니다. API 게이트웨이는 HTTP 서버로서 설정을 통해 라우팅 정보와 엔드포인트를 정의하고 각각의 라우트는 FaaS 펑션에 연결 시킵니다. API 게이트웨이가 리퀘스트를 받았을 때, 리퀘스트와 일치하는 라우팅 정보를 찾아서 그에 맞는 FaaS 펑션을 실행시킵니다. 보통 API 게이트웨이는 HTTP 리퀘스트 파라미터로부터 FaaS 펑션에 필요한 입력 인자를 매핑합니다. 그렇게 함으로써 API 게이트웨이는 FaaS 펑션의 결과값을 HTTP 응답객체에 실어서 최초 요청자에게 반환합니다.

AWS는 API 게이트웨이 서비스를 갖고 있구요, 다른 제공자 역시도 비슷한 기능을 보유하고 있습니다.

API 게이트웨이는 단순히 리퀘스트를 라우팅하는 기능 이외에도 인증 절차를 수행하고, 입력값에 대한 유효성 검사를 수행하며 응답 객체와 매핑을 시키는 등의 역할을 하기도 합니다. 당신의 거미줄 같은 감각은 어쩌면 이게 실제로 좋은 생각인지 아닌지 궁금해 할 수도 있습니다. 잠시 후에 다시 얘기해 보도록 하죠.

API 게이트웨이와 FaaS 조합의 한가지 유즈 케이스는 HTTP를 앞세운 마이크로서비스 형태가 될 겁니다. 서버리스는 여기서 스케일링과 관리 그리고 FaaS 펑션이 가져다 주는 여러가지 잇점을 담당하죠.

현 시점에서 API 게이트웨이 도구는 아직 처절할 정도로 성숙하지 않았습니다. 그렇긴 해도 API 게이트웨이와 함께 애플리케이션을 개발하는 것이 그다지 어렵거나 한 것은 아닙니다.

도구들

API 게이트웨이 도구들이 아직 성숙하지 않았다는 것은 이미 언급했구요, 이것은 전반적으로 서버리스 FaaS 시장에 있어서 공통적인 현상입니다. 하지만 예외는 있죠. 그 예가 바로 Auth0의 Webtask인데요 개발자 UX에서 엄청난 강점을 갖고 있습니다. Tomasz Janczuk은 최근에 있었던 서버리스 컨퍼런스에서 굉장히 좋은 데모를 보여준 적이 있습니다.

디버깅과 모니터링 역시 이 서버리스 애플리케이션에서는 해결해야 할 숙제들입니다. 이 포스트의 뒷부분에서 다뤄보도록 하죠.

오픈 소스

서버리스 FaaS 애플리케이션의 주요 잇점들 중 하나는 바로 투명한 실행 환경 공급에 있습니다. 아직 오픈 소스들은 현재 여기에 그다지 관련이 있지는 않습니다. 도커와 같은 컨테이너들 말이죠. 조만간 우리는 유명한 FaaS / API 게이트웨이 플랫폼이 회사내 on-premise에서 돌아간다거나 개발자의 컴퓨터에서 돌아간다거나 하는 것들을 볼 수 있을 겁니다. IBM의 OpenWhisk는 좋은 예가 될 수 있는데요, 이것이 어떤 대안이 될지 아닐지 지켜보는 것도 꽤 흥미로울 겁니다.

실행 환경 구성과는 별개로 FaaS 펑션을 정의하고, 설치하고 실행시키는데 도와주는 도구들과 프레임워크들은 이미 오픈 소스로 많이 나와 있습니다. 예를 들어 서버리스 프레임워크는 실제로 동작하는 API 게이트웨이와 람다를 AWS에서 제공하는 형태보다 훨씬 더 쉽게 사용할 수 있게 해줍니다. 자바스크립트를 좀 지나치게 쓰긴 했는데, 만약 자바스크립트와 API 게이트웨이 조합으로 애플리케이션을 개발한다면 꼭 한 번 봐 둘만 합니다.

또다른 예로는 Apex가 있습니다. 이 프로젝트는 AWS 람다 펑션들을 손쉽게 만들고, 설치하고, 관리하자라는 슬로건을 갖고 있습니다. Apex가 갖는 재미있는 요소들 중 하나는 아마존에서 직접 지원하지 않는 언어들을 람다 펑션 차원에서 지원하게끔 해준다는 겁니다. 예를 들자면 Go 언어 같은 것들이죠.

서버리스가 아닌 것은?

직금까지 이 글에서 저는 서버리스Backend as a Service (BaaS)Functions as a Service (FaaS)의 합집합이라고 정의했습니다. 또한 주로 FaaS 쪽을 중점으로 해서 이야기를 풀어나갔지요.

이제 가장 중요한, 무엇이 이득이고 무엇이 손해인지에 대해 얘기하기 전에 이 서버리스의 정의에 대해 조금만 더 살펴보고자 합니다. 적어도 무엇이 서버리스아닌지에 대해 얘기해 보죠. (최근의 저를 포함해서) 몇몇 사람들이 이러한 것들에 대해 혼동했던 것을 봐 왔고, 좀 더 명확하게 하는 것도 좋은 생각 같습니다.

PaaS와 비교

앞서 잠깐 서버리스 FaaS 펑션은 12요소 애플리케이션과 비슷하다고 했는데요, 그렇다면 Heroku와 같은 또다른 PaaS라고 할 수도 있을까요? 간단하게 대답하기 위해 Adrian Cockcroft의 트윗을 인용하겠습니다.

만약 당신의 PaaS가 20ms 이내에 인스턴스를 실행시켜서 0.5초 동안 원하는 기능을 실행시킬 수 있다면 그땐 그걸 서버리스라고 부르세요.

다른 말로, 대부분의 PaaS 애플리케이션들은 매 리퀘스트마다 애플리케이션 전체를 올렸다 내렸다할 수 있게끔 설계되지 않았습니다. 반면에 FaaS 플랫폼은 정확하게 그렇게 하죠.

좋습니다. 만약 제가 훌륭한 12요소 애플리케이션의 개발자라면 딱히 코딩을 하는데 있어서 별 차이점은 없을 거예요. 사실입니다. 하지만 가장 큰 차이는 어떻게 당신의 애플리케이션을 운영하는가에 있습니다. 우린 모두 데브옵스 관점에 충실한 엔지니어들이고 개발에 대해 생각하는 것 만큼 운영에 대해서도 생각하고 있습니다, 그렇죠?

운영 측면에서 FaaS와 PaaS의 핵심적인 차이는 바로 스케일링입니다. 대부분의 PaaS에서 당신은 여전히 스케일링을 고민해야 하죠. 헤로쿠의 예를 들자면 다이노스 Dynos 몇개를 돌리고 싶은가를 고민해봐야 합니다. FaaS 애플리케이션에서 이부분은 완전히 투명합니다. 심지어 당신이 PaaS 애플리케이션을 스케일링 완전 자동화로 설정한다 하더라도 개별 리퀘스트 수준에서 이런 스케일링을 하진 앟아요(물론 당신이 굉장히 특별하게 트래픽 프로필을 설정해 놓았다면 얘긴 달라집니다). 따라서 FaaS 애플리케이션은 이렇게 비용이 연계가 될 때 굉장히 효율적입니다.

이런 잇점들이 있다면 왜 계속 PaaS를 쓰려고 하죠? PaaS를 쓸 이유들이 여러가지 있겠지만 아마도 도구들 그리고 API 게이트웨이의 성숙도가 가장 큰 이유들이 될 겁니다. 더군다나 PaaS에 구현한 12요소 애플리케이션들은 최적화를 위해 앱 내 읽기전용 캐시를 사용하겠죠. 이것은 FaaS 펑션에서는 사용할 수 없는 기능입니다.

#NoOps

서버리스NoOps를 의미하는 것은 아닙니다. 서버리스 토끼구멍을 얼마나 깊이 파고 들어가는가에 따라 아마도 내부 시스템 관리자가 없다는 것을 의미할 거예요. 여기서 우리는 두가지 중요한 것을 고려해야 합니다.

먼저 Ops는 서버 관리 이상의 그 무언가를 의미합니다. 적어도 모니터링, 설치, 보안, 네트워킹 등을 의미하기도 하죠. 그리고 종종 시스템 스케일링과 어느 정도의 운영 시스템 디버깅까지를 포함하기도 합니다. 이런 문제들은 서버리스 애플리케이션으로 간다고 해도 여전히 존재하고 이를 해결할 전략이 필요하죠. 어떤 면에서는 Ops서버리스 환경에서 좀 더 어려운 일이 될 수도 있습니다. 왜냐하면 모든 것들이 전부 새롭기 때문이죠.

다음으로 시스템 관리자가 여전히 필요하다면 서버리스를 위해서는 아웃소싱을 하면 그만입니다. 딱히 나쁘진 않아요 실제로 우린 여러번 아웃소싱을 해 왔으니까요. 하지만 구체적으로 당신이 무엇을 하려고 하는가에 따라 이건 좋을 수도 있고 나쁠 수도 있습니다. 어느 시점에서 당신은 시스템 관리자가 당신의 애플리케이션을 지원할 필요가 있다는 것을 알아야 할 지 모릅니다.

Charity Majors는 이와 관련해서 최근 있었던 서버리스 컨퍼런스에서 좋은 발표를 해 줬습니다. 저는 온라인에 이 발표가 올라오면 꼭 확인해 보기를 권장합니다. 그 전까지는 이 글이 글을 읽어보면 좋겠네요.

Stored Procedures as a Service

또다른 흥미로운 주제는 서버리스 FaaS가 Stored Procedures as a Service라는 겁니다. (이 글에서 사용한 것들을 포함해서) FaaS 펑션의 많은 예제들이 주로 데이터베이스에 접근하기 위한 코드들이기 때문이 아닐까 생각합니다. 만약에 겨우 이정도가 우리가 FaaS를 사용하는 이유라고 한다면 이 네이밍은 적당할 지도 모르겠군요. 하지만 이건 FaaS의 서브셋에 불과할 뿐더러 만약 이런 용도로만 사용한다면 뭐랄까 조금은 맞지 않습니다.

이것은 어찌 보면 Stored Procedure가 갖는 동일한 문제를 FaaS 역시도 가질 수 있다는 것을 고려해 볼 필요가 있습니다. Camille이 트윗에서 언급한 것과 같은 기술적 부채들도 포함해서 말이지요.

만약에 서버리스 서비스가 마치 Stored Procedure 처럼 변한다면 이건 곧바로 엄청난 기술적 부채가 될 거라는 걸 생각해 보자구.

Stored Procedure를 사용하는 것에서 오는 수많은 교훈들이 있습니다. 그것들은 FaaS에서 반드시 되돌아보고 적용이 가능할지 아닐지를 결정해야 할 것들이지요. Stored Procedure들은:

  1. 종종 벤더 종속적인 언어를 요구하거나, 적어도 벤더 종속적인 프레임워크 혹은 언어로 확장할 필요가 있습니다.
  2. 데이터베이스 콘텍스트 안에서 실행시켜야 하기 때문에 테스트가 어렵습니다.
  3. 일급 애플리케이션으로서 다루기 까다롭고 버전 관리도 힘듭니다.

이러한 제약사항들이 모두 Stored Procedure를 구현하는데 있어서 적용되는 것은 아닐 겁니다. 하지만 지금까지 제 경험상 수많은 문제들을 읽으켰던 것은 사실이예요. 그렇다면 이것을 FaaS에 어떻게 적용을 시킬 수 있는지 살펴봅시다.

1번 항목은 FaaS 구현에 있어서 큰 걸림돌은 아닙니다. 그냥 그런 부분들을 걷어내면 그만이죠.

2번 항목에서 우리는 코드만 쓰기 때문에, 단위 테스트는 다른 코드들과 마찬가지로 쉽습니다. 통합 테스트는 다른 (그리고 정당한) 문제예요. 이건 나중에 얘기해 봅시다.

3번 항목에서 다시금 FaaS 펑션은 코드일 뿐이기 때문에 버전 관리도 괜찮습니다. 하지만 애플리케이션 패키징 측면에서 봤을 때 아직 어떤 성숙한 패턴이 나오지는 않았어요. 앞서 언급했던 서버리스 프레임워크는 자체적으로 이런 패키징 형태를 제공합니다. AWS는 2016년 5월에 열렸던 서버리스 컨퍼런스에서 패키징 관련해서 Flourish라는 이름으로 작업중이라고 발표했습니다. 하지만 이건 뭐 나와 봐야 아는 거겠죠.

이 문서는 지속적으로 진화합니다. 저는 수시로 이 문서를 업데이트할 예정입니다. 그렇게 해서 좀 더 많은 서버리스 아키텍처와 관련한 장단점들을 포함한 주제들을 이 문서에 담길 희망합니다. 아마도 향후 일이년 이내에 좀 더 서버리스 관련 주제들이 발전하지 않을까 싶네요.

이 주제와 관련해서 우리가 어떻게 업데이트 하는지를 알고 싶다면 우리 사이트의 RSS 피드, 제 트위터 피드 또는 마틴 파울러의 트위터 피드를 주목해 주세요.


알림

이 글을 쓰는데 도움을 주신 분들께 감사 드립니다: Obie Fernandez, Martin Fowler, Paul Hammant, Badri Janakiraman, Kief Morris, Nat Pryce, Ben Rady.

이 새 기술에 적당히 반론도 해 주시고 격려도 해주신 Internet Media의 제 전 팀원들께 감사 드립니다: John Chapin, Pete Gieser, Sebastián Rojas and Philippe René.

마지막으로 이 주제와 관련해 여러 생각들을 피력해 주신 모든 분들, 특히 제가 언급한 분들께 감사 드립니다.

리비전

2016년 6월 17일: 서버리스가 아닌 것은? 섹션 추가
2016년 6월 16일: Functions as a Service 뒤집어 보기 섹션 추가
2016년 6월 15일: 첫번째 버전 발행 – 몇가지 예제들


+ Recent posts