Neural Network
Neural Networks
신경망을 이루는 가장 첫 번째 층을 입력층, 가장 마지막 층을 출력층, 그 사이에 있는 층을 은닉층이라 부른다.
우선 퍼셉트론에서 보았던 식을 간결한 형태로 작성해보자. 조건 분기의 동작을 하나의 함수로 나타내면 다음과 같다.
[3.1]은 입력 신호의 총합이 $h(x)$라는 함수를 거쳐 변환되며, 그 값이 $y$의 출력이 됨을 보여준다. [3.2]의 $h(x)$함수는 입력이 0을 넘으면 1을, 그렇지 않으면 0을 돌려준다.
활성화 함수 activation function
위의 $h(x)$처럼 입력 신호의 총합을 출력 신호로 변환하는 함수를 활성화 함수라 한다. 활성화 함수는 입력 신호의 총합이 활성화를 일으키는지를 정하는 역할을 한다. 식 [3.1]은 가중치가 곱해진 입력 신호의 총합을 계산하고, 활성화 함수에 입력해 결과를 나타낸다. 이 과정을 수식으로 나타내면 다음과 같다.
\[a=b+w_1x_1+w_2x_2\qquad[3.3]\\ y= h(a) \qquad[3.4]\]3.3은 가중치가 달린 입력 신호와 편향의 총합을 계산하고, 3.4는 그 값을 함수에 넣어 $y$를 출력한다.
위의 활성화 처리 과정이 포함된 내용을 도식으로 표현하면 다음과 같다.
가중치 신호를 조합한 결과가 a라는 노드가 되고, 활성화 함수 h()를 통과하여 y라는 노드로 변환되는 과정이 나타난다.
[3.2]와 같이 임계값을 경계로 출력이 바뀌는 함수를 계단 함수라 부른다. 따라서 “퍼셉트론에서는 활성화 함수로 계단 함수를 이용한다”고 할 수 있다. 신경망에서 이용하는 활성화 함수는 다양한데, 그 중 일부에 대해서 알아보자.
시그모이드 함수 sigmoid function
\[h(x)=\frac{1}{1+exp(-x)} \qquad[3.5]\]시그모이드 함수에서 $exp(-x)$는 $e^{-x}$를 뜻한다. 신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 다음 뉴런으로 전달한다.
시그모이드 함수와 계단 함수의 공통점과 차이점은 무엇일까? 가장 먼저 ‘매끄러운 정도’의 차이가 있다. 시그모이드 함수는 부드러운 곡선이며 연속적으로 변화하는 반면, 계단 함수는 0을 경계로 출력이 갑자기 바뀌어버린다. 이 말인 즉슨, 퍼셉트론은 0과 1이 흐르는 반면, 신경망에서는 연속적인 실수가 흐른다는 의미이다. 이 둘의 공통점은 입력이 착을 때의 출력은 0에 가깝고, 클 때의 출력은 1에 가까워지는 구조인 것이다.
비선형 함수
위 두 함수의 또다른 공통점으로, 둘 다 비선형 함수라는 것이다. 이때 무언가 입력했을 때 출력이 입력의 상수배 만큼 변하는 함수를 선형 함수, 그렇지 않은 것을 비선형 함수라고 한다.
신경망에선 활성화 함수로 선형 함수를 사용해서는 안되는데, 선형 함수를 이용하면 신경망의 층을 깊게 하는 의미가 없어지기 때문이다. 정확히는 ‘은닉층이 없는 네트워크’로도 같은 기능을 할 수 있다. 예를 들어 $h(x) = cx$를 활성화 함수로 사용한 3층 네트워크를 떠올려보자.
$y(x)=h(h(h(x)))=c\times c\times c\times x =ax(a=c^3)$으로 똑같은 선형 함수로 표현할 수 있다.
ReLU함수 Rectified Linear Unit
\[h(x)= \begin{cases} x(x>0) \\ 0(x\le 0) \qquad [3.6] \end{cases}\]ReLU함수는 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력하는 함수이다.
다차원 배열의 계산
숫자가 한 줄로 늘어선 것이나 직사각형으로 늘어놓은것, 3차원으로 늘어놓은것 등 N차원으로 나열하는 것을 통틀어 다차원 배열이라고 한다. 개 중 2차원 배열은 행렬matrxi라고 부르며 가로 방향을 행row, 세로 방향을 열column라고 부른다.
다차원 배열을 사용하여 효율적으로 신경망을 구성 할 수 있는데, numpy를 통해 쉽게 계산 할 수 있다.
행렬의 곱
행렬 곱은 왼쪽 행렬의 행과 오른쪽 행렬의 열을 원소별로 곱한 후 그 값들을 더해 계산한다.
>>> A = np.array([[1,2],[3,4]])
>>> A.shape
(2, 2)
>>> B = np.array([[5,6],[7,8]])
>>> B.shape
(2, 2)
>>> np.dot(A,B)
array([[19, 22],
[43, 50]])
>>> np.dot(B,A)
array([[23, 34],
[31, 46]])
위 예시를 보면 알 수 있듯 A, B의 곱과 B, A의 곱은 다른 값을 반환한다.
행렬을 계산할 때는 ‘행렬의 형상’에 주의해야 하는데, 앞선 행렬의 1번째 차원(열)의 원소 수와 나중 행렬의 0번째 차원(행)의 원소 수가 같아야만 행렬의 곱 계산이 가능하다. 간단하게 말하면 ‘대응하는 차원의 원소 수를 일치시켜야 한다.’
\[A:3\times2\quad B:2\times4 = C:3\times 4\]3층 신경망
입력층은 2개, 첫 번째 은닉층(1층)은 3개, 두 번째 은닉층(2층)은 2개, 출력층(3층)은 2개의 뉴런으로 구성된 3층 신경망을 제작해보자.
def init_network()
network = {}
network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
def forward(network, x): ##입력신호를 출력으로 변환하는 처리과정
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 = a3
return y
network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print (y)
위 코드에서 init_network() 함수는 가중치와 편향을 초기화 하고 딕셔너리 변수 network에 저장한다. 또한 각 층에 필요한 매개변수를 저장한다. forward() 함수는 입력 신호를 출력으로 변환하는 처리 과정을 구현하고 있다.
출력층의 활성화 함수는 풀고자 하는 문제의 성질에 맞게 정의한다. 예를 들어 회귀 문제는 항등 함수, 2클래스 분류 문제는 시그모이드 함수, 다중 클래스 분류에는 소프트 맥스 함수를 사용하는 것이 일반적이다.
출력층 설계
회귀 문제에서 사용하는 항등함수identity function는 입력을 그대로 출력한다.
분류에서 사용하는 소프트맥스 함수softmax function의 식은 다음과 같다.
\[y_k=\frac{exp(a_k)}{\sum_{i=1}^{n}exp(a_i)}\]$exp(x)$는 $e^x$을 뜻하는 지수함수이며, $n$은 출력층의 뉴런 수, $y_k$는 그 중 k번째 출력임을 뜻한다. 소프트맥스 함수의 분자는 입력신호 $a_i$의 지수함수, 분모는 모든 입력 신호의 지수 함수의 합으로 구성된다.
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
컴퓨터로 계산된 소프트맥스 함수는 오버플로 문제를 가지고 있다. 지수 함수는 쉽게 아주 큰 값을 반환하고, 이런 큰 값끼리 나눗셈을 하면 결과 수치가 불안정해진다. 이렇듯 표현할 수 있는 수의 범위가 한정되어 범위 밖의 큰 값은 표현할 수 없는 문제를 ‘오버플로’라 한다.
오버플로 문제를 해결하기 위해서 임의의 정수를 분모와 분자에 곱해보자
소프트맥스의 지수 함수를 계산할 때 어떤 정수를 더하거나 빼도 결과는 바뀌지 않으므로 입력 신호의 최댓값을 때 최적화 할 수 있다.
def softmax(a):
C = np.max(a)
exp_a = np.exp(a - C)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
소프트맥스 함수의 특징
>>>a = np.array([0.3, 2.9, 4.0)]
>>>y = softmax(a)
>>>print(y)
[0.01821127 0.24519181 0.73659691]
>>>np.sum(y)
1.0
위의 예제와 같이 소프트맥스 함수의 출력은 0~1.0 사이의 실수이며, 그 총합은 1이다. 이런 성질로 인해 소프트맥스 함수의 출력을 ‘확률’로 해석할 수 있다. 다만, 지수함수$y = exp(x)$가 단조 증가 함수이기에 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않는다.
결과적으로 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용하지만, 추론 단계에서는 생략해도 무방하다.
Enjoy Reading This Article?
Here are some more articles you might like to read next: