신경망(neural network)

1. 신경망이란?


이전까지 퍼셉트론을 이용하여 여러가지 함수를 표현할 수 있었지만 가중치를 수동으로 구해서 설정해야한다는 단점이 있었고,

이를 해결하기 위해 신경망이라는 개념이 도입되었다.




신경망의 구조를 나타내면 위와 같다.

왼쪽부터 차례로 입력층(input layer), 은닉층(hidden layer), 출력층(output layer) 이라 한다.


2. 활성화 함수(activation function)

활성화 함수에 대해 설명하기 위해 퍼셉트론 구현에 사용했던 식을 변형하였다.

$$y = h\left(b + w_1x_1 + w_2x_2\right) \\ h\left(x\right) = \begin{cases} 0\ \left(x \le 0 \right) \\ 1\ \left(x > 0\right) \end{cases}$$

이때 $h(x)$를 활성화 함수라고 한다.

위의 $h(x)$는 계단 함수(step function)라고 한다.




 신경망에서는 퍼셉트론에서와 달리 시그모이드 함수(sigmoid function), ReLU 함수(rectified linear unit function)를 주로 이용한다.


3. 시그모이드 함수(sigmoid function)

시그모이드 함수의 식은 다음과 같다.

$$h(x) = \frac{1}{1 + e^{-x}}$$

이 함수의 개형은 다음과 같다.


sigmoid 함수는 step 함수와 다르게 매끄러운 형상이다.

입력이 커짐에 따라 1에 수렴하므로 입력의 중요도에 따라 값이 1에 수렴하게 된다.


3. ReLU 함수(Rectified Linear Unit function)

ReLU 함수는 입력이 0보다 작으면 0을 출력하고 0보다 크면 입력 그대로 출력하는 함수이다.

$$h(x) = \begin{cases} x\ (x > 0) \\ 0\ (x \le 0) \end{cases}$$


4. 소프트맥스 함수(Softmax function)

출력층에서 사용하는 함수는 항등함수와 소프트맥스 함수가 있다.

항등함수는 입력을 그대로 출력하는 함수이며 회귀 문제에서 사용된다.
반면 분류에서는 소프트맥스 함수가 사용된다.

소프트맥스 함수란 다음과 같이 정의된다.

$$y_k = \frac{exp(a_k)} {\sum_{i = 1}^n exp(a_i)}$$

여기서 $n$은 출력층의 뉴런 수이고 $y_k$는 $k$번째 출력을 의미한다.

수식에서도 알 수 있듯이 소프트맥스 함수 출력의 총 합은 1이므로 이 출력을 확률로서 생각할 수 있다.
이 성질을 이용하여 다수의 클래스 분류에 사용된다.
하지만 입력과 출력에서 각 원소의 대소관계는 변하지 않아 추론 단계에서는 이 함수를 생략하는것이 일반적이다.
한편, 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 이용한다.

아래의 입력에서 소프트맥스 함수를 적용해보면 다음과 같다.
$$a = \begin{pmatrix} 0.3 & 2.9 & 4.0 \end{pmatrix}$$

import numpy as np

def softmax(x):
    y = np.exp(x)/np.sum(np.exp(x))
    return y

a = np.array([0.32.94.0])
y = softmax(a)
print(y)

>>> [0.01821127 0.24519181 0.73659691]

하지만 소프트맥스 함수는 지수함수를 사용하기 때문에 오버플로우가 발생할 수 있다.
이를 해결하기 위해 다음과 같은 과정을 거쳐 식을 수정한다.

$$\begin{matrix} y_k = \frac{\exp(a_k)} {\sum_{i = 1}^n \exp(a_i)} &=& \frac{C \exp(a_k)} {C \sum_{i = 1}^n \exp(a_i)} \\ &=& \frac{\exp(a_k + \log C)} {\sum_{i = 1}^n \exp(a_i + \log C)}  \\ &=& \frac{\exp(a_k + C^\prime)} {\sum_{i = 1}^n \exp(a_i + C^\prime)} \end{matrix}$$

위 식에서 적절한 $C^\prime$을 찾아 대입하면 되는데 일반적으로 입력 신호 중 최댓값을 이용한다.

입력 신호가 다음과 같을 때 수정된 python 코드이다.
$$a = \begin{pmatrix} 1010 & 1000 & 990 \end{pmatrix}$$

import numpy as np

def softmax(x):
    C = np.max(x)
    y = np.exp(x-C)/np.sum(np.exp(x-C))
    return y

a = np.array([10101000990])
y = softmax(a)
print(y)

>>> [9.99954600e-01 4.53978686e-05 2.06106005e-09]

5. 3층 신경망 구현하기


위 그림과 같이 은닉층이 2개인 3층 신경망을 구현해볼 것이다.

각 과정의 계산량이 많아지기 때문에 행렬 연산을 이용할 것이다.

행렬의 원소에서 각각 첨자의 의미는 다음과 같다.

$${w}^{(a)}_{bc}$$

위는 a번째 층의 가중치이며 그 이전 층의 c번째 뉴런에서 b번째 뉴런으로 향하는 가중치라는 표기이다.

따라서 정리하면 다음과 같다.

$$\mathbf{A}^{(1)} = \mathbf{X} \mathbf{W}^{(1)} + \mathbf{B}^{(1)}$$

$$\mathbf{A}^{(1)} = \left(a^{(1)}_1, a^{(1)}_2, a^{(1)}_3 \right) \\ \mathbf{X}^{(1)} = \left(x^{(1)}_1, x^{(1)}_2 \right) \\ \mathbf{B}^{(1)} = \left(b^{(1)}_1, b^{(1)}_2, b^{(1)}_3 \right) \\ \mathbf{W}^{(1)} = \begin{pmatrix} w^{(1)}_{11} & w^{(1)}_{12} & w^{(1)}_{13} \\ w^{(1)}_{21} & w^{(1)}_{22} & w^{(1)}_{23} \end{pmatrix}$$

임의의 수로 예시를 들어 python으로 구동해보자

$$\mathbf{X} = \begin{pmatrix} 1.0 & 0.5 \end{pmatrix},\ \mathbf{B}^{(1)} = \begin{pmatrix} 0.1 & 0.2 & 0.3 \end{pmatrix},\ \mathbf{B}^{(2)} = \begin{pmatrix}0.1 & 0.2 \end{pmatrix},\ \mathbf{B}^{(3)} = \begin{pmatrix} 0.1& 0.2 \end{pmatrix} \\ \mathbf{W}^{(1)} = \begin{pmatrix} 0.1 & 0.3 & 0.5 \\ 0.2 & 0.4 & 0.6 \end{pmatrix},\ \mathbf{W}^{(2)} = \begin{pmatrix} 0.1 & 0.4 \\ 0.2 & 0.5 \\ 0.3 &0.6 \end{pmatrix},\ \mathbf{W}^{(3)} = \begin{pmatrix} 0.1 & 0.3 \\ 0.2 & 0.4 \end{pmatrix}$$

이때 $mathbf{W}^{(1)}, mathbf{W}^{(2)}, mathbf{W}^{(3)}$은 모두 이전 행렬의 행과 다음 행렬의 열의 크기가 같아야 한다.

import numpy as np

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

def identity(x):                    # Identity Function
    return x

def init_network():                 # Initial Network
    network = {}
    network['W1'] = np.array([[0.10.30.5], [0.20.40.6]])
    network['b1'] = np.array([0.10.20.3])
    network['W2'] = np.array([[0.10.4], [0.20.5], [0.30.6]])
    network['b2'] = np.array([0.10.2])
    network['W3'] = np.array([[0.10.3], [0.20.4]])
    network['b3'] = np.array([0.10.2])

    return network

def forward(networkx):            # Forward Direction
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = identity(a3)

    return y

network = init_network()
x = np.array([1.00.5])
y = forward(network, x)
print(y)

>>> [0.31682708 0.69627909]

1층과 2층에(은닉층)서는 시그모이드 함수를 사용했으나,
출력층에서는 항등 함수를 이용하였다.

출력층의 활성화 함수는 회귀에서는 항등함수,
2클래스 분류에서는 시그모이드 함수,
다중 클래스 분류에서는 소프트맥스 함수를 사용한다.

Comments