본문 바로가기
유전자 알고리즘으로 인공 신경망을 학습시켜보자. 이것도 돼?

와. 이거 대단한 용어들이 난무하는 글이 될 수도 있겠습니다만. 실은 별건 아닙니다. 어쨌든 지금의 목표는 기계가 어떤 식으로 학습을 하는가를 이해하는 것이니까 유전자 알고리즘을 이용하면 심지어 인공 신경망도 학습시킬 수 있다는 것을 이해하는 것이 목표입니다. 

그렇긴 한데, 인공 신경망 자체를 설명하려면 매우 긴 이야기가 될 거라서, 그것만큼은 딥러닝을 다룰 때 자세하게 다루는 것이 좋겠다는 생각이 듭니다. 자꾸 이상한 방향으로 흘러갈 수 있으니까요. 

그래도 아주 간단하게 인공신경망이 어떤 식의 모형인지를 간략하게 다루고 곧바로 신경망 모형을 학습 시켜보겠습니다. 

인공신경망은 각각의 데이터와 그 데이터가 흐르는 방향을 모형으로 하는데,

 

이런 식입니다. 사실 멋진 그림이긴 하지만, 인공신경망은 별건 아니고, 실제 구현체는 행렬입니다. 어떤 식이냐면 각각의 데이터가 흐를 때 어떤 행렬을 곱해서 나온 값이 다음 데이터가 되는 것인데요, 이 행렬이라는 것이 모형의 Parameter입니다. 보통 Weight라고 부르고 W로 표현을 많이 하게 되는데, 실제로 어떤 식으로 생겼냐면,

 

이런 식으로 생겼고요, 이런 신경망은 2개 입력, 3×2의 행렬(1), 3개의 중간값 - 보통 hidden layer라고 부릅니다. -, 그리고 1×3의 행렬(2), 마지막으로 출력값 1×1을 의미합니다. 뭐 그다지 어려운 개념은 아닙니다. "선형대수"라고 하기엔 너무 거창하고 간단한 "행렬곱" 정도의 이해 편에서 대~충 본 적이 있었다는 점을 - 기억나지 않겠지만 - 기억해내 주세요.

여기에 신경망의 동그라미의 궁둥이에 뭔가 어두운 색깔로 달린 게 있는데 이게 Sigmoid함수입니다. Logistic 회귀에서 보았던 Logistic 함수를 Sigmoid함수로 특정해서 사용합니다. 0.5?라는건 0.5보다 크면 1, 0.5보다 작으면 0으로 판단해서 결과값을 낸다는 의미입니다. 이 부분이 있기 때문에 Non Linear 특성을 포함해서 XOR문제를 풀 수 있게 해 줍니다. 나중에 살펴보겠지만, 단층 Perceptron으로는 XOR 문제를 풀 수가 없는데, Non Linear 특성을 포함한 후에 다층으로 Perceptron을 쌓으면 XOR문제를 풀 수가 있게 됩니다. 어쨌든 이 부분은 딥러닝을 할 때 더 자세히 보는 것으로 하고요.

신경망에 입력을 넣고 쭉쭉 계산하면서 진행한 후에 출력이 나오는 것을 Forward Propagation이라고 하는데요, Forward Propagation은 행렬로 계산됩니다. 신경망의 화살표 →가 행렬을 통과하는 것이긴 한데, 실제로는 행렬곱을 하는 것입니다. 

 

결국 행렬곱으로 최종 ŷ값을 계산해 냅니다. 그러니까 이 신경망이 XOR문제를 풀수 있도록 학습시킨다는 것은 적절한 Weight행렬 2개, 즉 (1), (2)를 찾아내는 것과 같은 말입니다. 

XOR문제를 풀어내는 신경망 모형을 만들어 내 보는 것이 지금의 목표니까 어떻게 Weight를 찾아낼지에 대해 생각해 보면, 최적해 Solution을 구하는 컨셉을 기억하고 있겠지요?

 

이때 유전자의 모양새인 Solution을 어떤 식으로 정하면 좋은가 하면, (1), (2) 두 개의 행렬을 이어 붙여서 길게 늘어뜨려 붙여 만들어서 유전자를 만들어 생각해 보면,

$$ [ W_{11(1)}, W_{12(1)}, W_{21(1)}, W_{22(1)}, W_{31(1)}, W_{32(1)}, W_{11(2)}, W_{12(2)}, W_{13(2)}] $$

이런 식으로 첫 번째 행렬과 두 번째 행렬을 연결해서 이어 붙여서 표현할 수 있고 이게 유전자 배열과 같은 모양새입니다. 가장 Cost가 작은 유전자 배열을 찾아내면 그게 바로 가장 Cost가 작은 Weight 행렬들이 되는 거겠죠.

자, 그러면 어떤 식으로 접근할지는 알았으니까, Cost를 정의해 보시죠. Cost는 Forward Propagation을 했을 때의 결과가 실제 기대하는 정답과 얼마나 차이가 나는지를 Error로 정의하면 되겠습니다. 

일단 현재의 Solution을 입력받으면 
⓵ 0~5까지의 6개는 3×2의 (1) Weight 행렬이고, 6~9까지 3개는 1×3의 (2) Weight 행렬이 붙어서 들어온 것이겠죠? 그러니까 이걸 나눠서 2개의 행렬을 준비합니다. 
⓶ 그러면 XOR의 구분을 제대로 하는지 보기 위해서 입력 (1, 1), (0, 1), (1, 0), (0, 0)에 대하여 Forward Propagation을 계산합니다. $Sigmoid(W_{(2)}\cdot Sigmoid(W_{(1)} \cdot X))$ 이런 식으로 행렬 곱을 하면 되겠죠. 
⓷ 각각의 입력에 대하여 정답인 0, 1, 1, 0과 얼마나 다른지 확인합니다. 그게 error입니다. 이 error를 다룰 때 작은 값과 큰 값에 대해서 과장된 큰 차이를 두기 위해서 Error를 Exponential에 지수로 하여 계산합니다. 

단순하죠? Cost를 코드로 구현해 보면,

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def xor(x1, x2):
    return x1^x2

def cost(sol): # sol length -> 12 
    weights = []
    weights.insert(0, (np.array(sol[0:6]).reshape(3,2))) # (1)번 Weights
    weights.insert(1, (np.array(sol[6:9]).reshape(1,3))) # (2)번 weights
    error = 0
    for (x1, x2) in [(1, 1), (0, 1), (1, 0), (0, 0)]: # 네가지 경우에 대해서 Error의 누적합 
        x1x2 = np.asarray((x1, x2)).reshape(2,1)
        sigmoid_vectorized = np.vectorize(sigmoid)
        y = ((weights[1] @ sigmoid_vectorized((weights[0] @ x1x2))))[0]  # network out
        error += math.exp(abs(xor(x1, x2) - sigmoid(y[0]))) # Sigmoid(출력)와 XOR(정답)의 차이
    return error

 

이런 식으로 간단하게 구현할 수 있습니다. 짜잔. 그렇게 어렵진 않죠? 그러면 이제까지와 같은 방법으로 최적해를 구해 봅시다. 이때 Weight가 가질 수 있는 범위는 -50~50으로 제한하면 적절하지 않을까 생각합니다.  

def find_weights_learning() :
    domain = [(-50,50)]*9 # 9개 initial 입력값
    solution = geneticoptimize(domain, cost, earlystop=10) # 이전에 구현했던 유전 알고리즘
    
find_weights_learning()

 

이걸 실행해서 Solution을 보면 [-33, 37, 17, 47, 35, -14, -17, 29, -20]이 Cost가 가장 작은 해로 찾아집니다. 너무 담담하게 결과를 툭 이야기하는 모양새가 익사이팅 하지는 않군요. 조금 그런가 싶게 Cost의 변화를 보면

 

이렇습니다. 최종 cost는 1.0003061646539055입니다. - 유전자 알고리즘으로 신경망을 학습시켰고, XOR이 되는 다른 유전자 해도 있을 수 있으니까 실행할 때마다 다를 수는 있습니다. -

어쨌든 이 Solution을 조금 쉽게 보기 위해서 이 solution을 행렬의 모양으로 변환해서 보면,

$$ (1)=\underset{
\begin{array}{c}
(3 \times 2)
\end{array}
}{ \begin{bmatrix}
-33 & 37 \\
17 & 49 \\
35 & -14 
\end{bmatrix}
},\,\,\,\  
(2)=\underset{
\begin{array}{c}
(1 \times 3)
\end{array}
}{
\begin{bmatrix}
-17 & 29 & -20 
\end{bmatrix}  
}
$$

이런 식인데요, 유전 알고리즘으로 찾아낸 신경망모형의 최종 모양새를 눈으로 볼 수 있게 나타내 보면

 

이런 식이 됩니다. 아, 그렇군요.라고만 할게 아니라 Forward Propagation을 직접 계산해서 맞는지 한번 보시죠.

$$
\hat{y} = 
Sigmoid \left(
\begin{bmatrix}
-17 & 29 & -20 
\end{bmatrix}  
\left(
Sigmoid
\left(
\begin{bmatrix}
-33 & 37 \\
17 & 49 \\
35 & -14 
\end{bmatrix}
\begin{bmatrix}
x_1 \\
x_2 \\
\end{bmatrix}
\right)
\right)
\right)
$$

참고로, 일반적인 표현에서는 Sigmoid를 σ로 표현합니다. 그렇게 다시 표현하면,

$$
\hat{y} = 
\sigma \left(
\begin{bmatrix}
-17 & 29 & -20 
\end{bmatrix}  
\left(
\sigma
\left(
\begin{bmatrix}
-33 & 37 \\
17 & 49 \\
35 & -14 
\end{bmatrix}
\begin{bmatrix}
x_1 \\
x_2 \\
\end{bmatrix}
\right)
\right)
\right)
$$

요걸 계산하면 되는데, (1,1), (1,0), (0,1), (0,0) 일 때를 진리표로 만들어보면  - ŷ이 Sigmoid 출력이고, ȳ가 0.5 이상이면 1, 0.5 이하면 0으로 판단한 결과입니다  -

 

이런 식의 결과를 계산할 수가 있습니다. 어때요, 출력 ȳ와 정답 XOR(x₁, x₂)가 같죠. 자, 이제부터는 어떤 문제라도 Cost와 Optimizer만 잘 정의하면 뭔가 해를 구할 수 있을 것 같은 자신감이 불끈 솟아나지 않나요? 사실 이걸 알게 된 후부터는 뭔가 신세계가 펼쳐지고 기계학습이라는 게 더 이상 두렵지 않으며, 이해 쌉가능 할거라 생각합니다. 흐업!

미리 맛보기로 이야기한다면 인공신경망에서 Cost를 Cross Entropy로 설계한 후에 Optimizer를 Gradient Decent로 설계하면 가장 효율적인 학습이 된다는 스토리로 이어지는데, 이건 인공신경망에 이야기에서 더 자세히 할 테니 너무 걱정 마세요.

왜 XOR을 굳이 예시돌 들여다보았느냐 하면 선형모형으로는 XOR 문제를 풀 수가 없거든요. 인공신경망은 그걸 풀수가 있는데 말이죠. 그래서 처음으로 비선형 문제를 미리 풀어볼까 해서 한번 들여다봤습니다. 헤헤.

친절한 데이터 사이언스 강좌 글 전체 목차 (정주행 링크) -



댓글





친절한 데이터 사이언스 강좌 글 전체 목차 (정주행 링크) -