[Home] AI로 돌아가기

퍼셉트론 수계산 예제
Perceptron Calculation
[YouTube]

01. 순전파 (Forward Propagation)

하나의 입력의 들어올 때 두개의 노드로 구성된 신경망을 통과하는 은닉층이 있다. 각각의 신경망은 식 \(\sigma_{ij} = \frac{1}{1+e^{-z_{ij}}}, z_{ij} = \omega_{ij} \cdot x + b_{ij}\)로 정의하자. 초기 가중치(w)와 편향(b)은 \(\omega_{11} = 0.4, \omega_{12} = 0.3, \omega_{21} = 0.2, \omega_{22} = 0.1, b_{11} = 0.1, b_{12} = 0.1, b_2 = 0.1\)의 값을 갖는다고 가정하자. 이때 초기 입력인 x값이 0.5를 넣을 때 실제 y값이 1이 나오도록 가중치와 편향을 수정해 보자. 예측을 위한 출력층과 손실함수를 정의하고 전체 신경망을 그림 4.2.8과 같이 구성한다.

...
그림 4.2.8 로지스틱회귀 모델의 신경망 구성 및 초기 값
신경망1 - 은닉층1 - 신경망2 - 출력층

신경망을 구성했다면 초기입력을 통해 순전파를 계산해 보자. 가중치와 편향을 통해 구한 선형 방정식 z에 우리가 정의한 \(\sigma\)는 하나의 신경망에서 정의된다. 이후 출력 값을 다른 신경망에 입력 값으로 넣어 전체 신경망이 비선형성을 띠도록 이루어진다.

\[z_{11} = \omega_{11} \cdot x + b_{11} = 0.4 \cdot 0.5 + 0.1 = 0.3\] \[z_{12} = \omega_{12} \cdot x + b_{12} = 0.3 \cdot 0.5 + 0.1 = 0.25\] \[\sigma_{11} = \sigma(z_{11}) = \sigma(0.3) = \frac{1}{1 + e^{-0.3}} = 0.574\] \[\sigma_{12} = \sigma(z_{12}) = \sigma(0.25) = \frac{1}{1 + e^{-0.25}} = 0.562\] \[z_2 = \omega_{21} \cdot x + \omega_{22} \cdot x + b_2 = 0.2 \cdot 0.574 + 0.1 \cdot 0.562 + 0.1 = 0.2148\] \[\hat{y} = \sigma(z_2) = \sigma(0.2184) = \frac{1}{1 + e^{-0.2148}} = 0.553\]

02. 역전파 (Backward Propagation)

\(\hat{y}\)는 신경망을 통해 구한 예측 값이다. 예측한 값과 실제 값과 오차를 mse(mean square error)로 구하여 오차를 줄여 나가도록 역전파를 통해 가중치를 수정해 보자.

손실 함수

\[L(mse) = \frac{1}{2}(y - \hat{y})^2 = \frac{1}{2}(1 - 0.553)^2 = 0.1\]

출력층 역전파

\[\frac{\partial L}{\partial \hat{y}} = \hat{y} - y = 0.553 - 1 = -0.447\] \[\frac{\partial \hat{y}}{\partial z_2} = \hat{y}(1 - \hat{y}) = 0.553 \cdot (1 - 0.553) = 0.247\] \[\frac{\partial L}{\partial z_2} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z_2} = -0.447 \cdot 0.247 = -0.110\]

은닉층→출력층(신경망2) 역전파

\[\frac{\partial L}{\partial \omega_{21}} = \frac{\partial L}{\partial z_2} \cdot \frac{\partial z_2}{\partial \omega_{21}} = -0.110 \cdot \sigma_{11} = -0.110 \cdot 0.574 = -0.063\] \[\frac{\partial L}{\partial \omega_{22}} = \frac{\partial L}{\partial z_2} \cdot \frac{\partial z_2}{\partial \omega_{22}} = -0.110 \cdot \sigma_{12} = -01110 \cdot 0.562 = -0.062\] \[\frac{\partial L}{\partial b_2} = \frac{\partial L}{\partial z_2} \cdot \frac{\partial z_2}{\partial b_2} = -0.110 \cdot 1 = -0.110\]

입력층→은닉층(신경망1) 역전파

Node1

\[\frac{\partial z_2}{\partial \sigma_{11}} = \omega_{21} = 0.2\] \[\frac{\partial L}{\partial \sigma_{11}} = \frac{\partial L}{\partial z_2} \cdot \frac{\partial z_2}{\partial \sigma_{11}} = -0.110 \cdot 0.2 = -0.022\] \[\frac{\partial \sigma_{11}}{\partial z_{11}} = \sigma_{11} \cdot (1 - \sigma_{11}) = 0.574 \cdot (1 - 0.574) = 0.245\] \[\frac{\partial L}{\partial z_{11}} = \frac{\partial L}{\partial \sigma_{11}} \cdot \frac{\partial \sigma_{11}}{\partial z_{11}} = -0.022 \cdot 0.245 = -0.005\] \[\frac{\partial L}{\partial \omega_{11}} = \frac{\partial L}{\partial z_{11}} \cdot \frac{\partial z_{11}}{\partial \omega_{11}} = -0.005 \cdot x = -0.005 \cdot 0.5 = -0.0025\] \[\frac{\partial L}{\partial b_{11}} = \frac{\partial L}{\partial z_{11}} \cdot \frac{\partial z_{11}}{\partial b_{11}} = -0.022 \cdot 1 = -0.005\]

Node2

\[\frac{\partial z_2}{\partial \sigma_{12}} = \omega_{22} = 0.1\] \[\frac{\partial L}{\partial \sigma_{12}} = \frac{\partial L}{\partial z_2} \cdot \frac{\partial z_2}{\partial \sigma_{12}} = -0.110 \cdot 0.1 = -0.011\] \[\frac{\partial \sigma_{12}}{\partial z_{12}} = \sigma_{12} \cdot (1 - \sigma_{12}) = 0.562 \cdot (1 - 0.562) = 0.247\] \[\frac{\partial L}{\partial z_{12}} = \frac{\partial L}{\partial \sigma_{12}} \cdot \frac{\partial \sigma_{12}}{\partial z_{12}} = -0.011 \cdot 0.247 = -0.003\] \[\frac{\partial L}{\partial \omega_{12}} = \frac{\partial L}{\partial z_{12}} \cdot \frac{\partial z_{12}}{\partial \omega_{12}} = -0.003 \cdot x = -0.003 \cdot 0.5 = -0.0015\] \[\frac{\partial L}{\partial b_{12}} = \frac{\partial L}{\partial z_{12}} \cdot \frac{\partial z_{12}}{\partial b_{12}} = -0.003 \cdot 1 = -0.003\]

모든 가중치와 편향에 대해 역전파를 계산하여 수정할 수 있다. 수정한 값들은 다시 순전파로 계산하고 손실함수가 0에 가까워질 때까지 계속 이과정을 반복하게 된다. 모든 딥러닝은 이러한 과정과 원리를 거쳐 학습을 하고 모델을 만든을 나간다. 이렇게 간단한 식과 2개의 신경망으로도 손으로 계산하기엔 매우 오래 걸리기 때문에 컴퓨터를 이용한 계산이 반드시 필요하다. 계산한 과정을 파이썬 코드를 작성해 보자.

03. 파이썬 코드 구현

Code 적성예보기
Code 적성예보기
import math

# 시그모이드 함수와 그 미분 함수 정의
def sigmoid(x):
    return 1 / (1 + math.exp(-x))

def sigmoid_derivative(x):
    return x * (1 - x)

# 입력값, 실제값, 초기 가중치와 편향 설정
x = 0.5
y = 1

# 초기 가중치
w1_1 = 0.4
w1_2 = 0.3
w2_1 = 0.2
w2_2 = 0.1

# 초기 편향
b1_1 = 0.1
b1_2 = 0.1
b2 = 0.1

# 순전파
# 은닉층
z1_1 = w1_1 * x + b1_1
a1_1 = sigmoid(z1_1)

z1_2 = w1_2 * x + b1_2
a1_2 = sigmoid(z1_2)

# 출력층
z2 = w2_1 * a1_1 + w2_2 * a1_2 + b2
y_hat = sigmoid(z2)

# 손실 함수 계산 (MSE)
loss = 0.5 * (y - y_hat) ** 2

# 역전파
# 출력층의 기울기 계산
dL_dy_hat = y_hat - y
dy_hat_dz2 = sigmoid_derivative(y_hat)
dL_dz2 = dL_dy_hat * dy_hat_dz2

# 은닉층에서 출력층으로의 가중치와 편향의 기울기
dL_dw2_1 = dL_dz2 * a1_1
dL_dw2_2 = dL_dz2 * a1_2
dL_db2 = dL_dz2

# 은닉층의 기울기 계산
dz2_da1_1 = w2_1
dL_da1_1 = dL_dz2 * dz2_da1_1
da1_1_dz1_1 = sigmoid_derivative(a1_1)
dL_dz1_1 = dL_da1_1 * da1_1_dz1_1

dz2_da1_2 = w2_2
dL_da1_2 = dL_dz2 * dz2_da1_2
da1_2_dz1_2 = sigmoid_derivative(a1_2)
dL_dz1_2 = dL_da1_2 * da1_2_dz1_2

# 입력층에서 은닉층으로의 가중치와 편향의 기울기
dL_dw1_1 = dL_dz1_1 * x
dL_dw1_2 = dL_dz1_2 * x
dL_db1_1 = dL_dz1_1
dL_db1_2 = dL_dz1_2

# 결과 출력
print(f"Loss: {loss:.3f}")
print(f"Gradients:")
print(f"dL_dw2_1: {dL_dw2_1:.3f}")
print(f"dL_dw2_2: {dL_dw2_2:.3f}")
print(f"dL_db2: {dL_db2:.3f}")
print(f"dL_dw1_1: {dL_dw1_1:.3f}")
print(f"dL_dw1_2: {dL_dw1_2:.3f}")
print(f"dL_db1_1: {dL_db1_1:.3f}")
print(f"dL_db1_2: {dL_db1_2:.3f}")
Loss: 0.094

Gradients:
dL_dw2_1: -0.061
dL_dw2_2: -0.060
dL_db2: -0.106
dL_dw1_1: -0.003
dL_dw1_2: -0.001
dL_db1_1: -0.005
dL_db1_2: -0.003

수기로 계산한 결과와 크게 다르지 않음을 확인 가능하며, 계산결과를 코드로 단숨에 출력 적임을 뜻한다. 딥러닝을 위해 코드를 작성하려면 결국 손으로 1번의 순전파와 역전 파 과정을 풀어야 한가? 만약 신경망이 100겹이 되고 각 신경망에 노드가 50개로 늘어난 다고 상상해 보라. 딥러닝을 위해 위의 코드처럼 수억 개의 모든 계산 과정을 하나하나 코드로 작성한다는 것은 불가능에 가깝다.

딥러닝의 모델 학습을 위해서는 이러한 미분을 통한 기울기 연산이 필연적이다. 때문 에 기울기를 구하는 과정을 일반화하여 간단하게 구현할 수 있도록 만든 라이브러리가 바로 파이토치(PyTorch)와 텐서플로우(TensorFlow)이다. 파이토치는 페이스북에서 개발한 라이브러리며, 텐서플로우는 구글에서 개발한 라이브러리이다. 두 패키지 모두 딥러닝을 위해 미분 과정을 식적 정의하지 않도록 자동화시켜도 패키지로 텐서(Tensor)라는 벡터를 이용해 연산하는 가능과 권리를 공유한다. 하지만 코드를 작성하는 방식의 두가지 조금 씩 다르고, 이로 인한 차이만 있다. 때서는 최하이 정의되지 않은 무작의 패터먼저 컴퓨터가 연산하기 위해 만드는 벡터 행렬 형태이다. 5장에서 다룰 Numpy의 배열과 동일하게 생각 하자.

파이토치 같은 패키지의 자동 미분으로 인해 계산 과정을 직접 구하지 않아도 불가 능해 보이는 신경망의 구현이 가능하다. 앞서 매우 길게 코드로 구현한 미분 과정은 파이 토치에서 아래와 같이 간단하게 만들어진다.

Code 적성예보기
import torch.nn as nn

# 신경망 정의
class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(1, 2)  # 입력층에서 은닉층으로 (1 -> 2)
        self.fc2 = nn.Linear(2, 1)  # 은닉층에서 출력층으로 (2 -> 1)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        x = self.sigmoid(self.fc1(x))
        x = self.sigmoid(self.fc2(x))
        return x

파이토치의 주요한 특징은 class, def 함수와 같은 객체를 정의해서 신경망을 만든다. 때문에 간단한 신경망 같은 경우엔 조금 코드가 복잡해 보일 수 있지만, 복잡한 모델의 경우 그 구조와 구성이 적합적이다. 텐서플로우를 사용하면 내부 함수들을 이용해 더 쉽 게 모델을 만들 수 있다.

Code 적성예보기
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

# 신경망 정의
model = Sequential([
    Dense(2, activation='sigmoid', input_shape=(1,)),  # 입력층에서 은닉층으로 (1 -> 2)
    Dense(1, activation='sigmoid')  # 은닉층에서 출력층으로 (2 -> 1)
])

이렇게 된 자는 딥러닝 모델을 만드는 데에 긴 코드가 필요하지 않다. 또한 생성형 AI 의 등장으로 코딩 자체의 중요성은 점점 더 낮아지고 있다. 때문에 앞으로 인공지능을 다 룬다면 이론을 통해 원리를 이해하고 적합한 문제에 대해 논리적 해결방성을 고민하는 것이 더욱 중요하다.