머신러닝 노트(1-4)

신경망 네트워크(Neural Network)

신경망 네트워크는 개개의 뉴런들로 구성되어 있다. 이전에 뉴런 노드 하나에 대한 입력과 출력은 다음과 같았다.

graph LR X1([x1]) --> N((N)) X2([x2]) --> N X3([x3]) --> N N --> R(\hat y)

이를 신경망으로 확장하면 다음과 같다.

Neural Network Image

앞으로 여기의 첫번째 층을 [1]로 표기하고 두번째 층을 [2]로 표현할 것이다.

로지스틱 회귀에서와 유사하게, 여기서도 다음의 식을 통해 $z^{[1]}$ 을 계산하고, 이를 이용해 $a^{[1]}$ 를 계산한다.

\[z^{[1]} = W^{[1]} x + b^{[1]} \\ a^{[1]} = \sigma (z^{[1]})\]

다음 층에서는 다시 이를 입력으로 받아 다음과 같이 $z^{[1]}, a^{[1]}$ 를 계산한다.

\[z^{[2]} = W^{[1]} a^{[1]} + b^{[1]} \\ a^{[1]} = \sigma (z^{[1]})\]

여기서 확인할 수 있는 점은, 하나의 노드에 대해 $z, a$ 값을 구했던 로지스틱 회귀와 달리 신경망에서는 다수의 $z, a$ 를 반복적으로 구한다는 것이다. 경사하강법을 위한 역전파에 대해서는 순방향 전파와 반대 방향으로, 우선 $da^{[2]}, dz^{[2]}$ 를 계산한 후에 연쇄법칙으로 $dW^{[2]}, db^{[2]}$ 를 구한다.

신경망 네트워크의 구성

신경망 네트워크는 다음 세 요소로 구성된다.

  • 입력층(Input Layer)
  • 은닉층(Hidden Layer)
  • 출력층(output Layer)

이전의 그림을 다시 보자.

Neural Network Image

$x_1, x_2, x_3$ 는 신경망의 입력인 입력층이다. 다음 그림에서 은닉층은 입력층의 입력을 받는 두번 째 층이 된다. 그리고 마지막 하나의 노드으로 이루어진 층을 출력층이라고 한다. 지도학습에서 데이터 셋은 입력 데이터인 $X$ 와 그 결과인 $y$ 로 구성된 $(X, y)$ 형태의 튜플이다. 그러므로 은닉층의 실제 값은 훈련세트에 기록되어 있지않다. 입력값과 그에 해당하는 출력값은 우리가 알고 있지만 개개의 은닉층이 가져야하는 출력값이 무엇인지 우리는 알지 못한다는 것이다. 은닉층이라는 이름은 훈련 데이터셋에서 이를 알 수 없다는 의미다.

입력값은 $a^{[0]}$ 와 같이도 표현이 가능하다. 이렇게 일반화 하면 활성값 $a$ 는 신경망의 층들이 다음 층들로 전달하는 값을 의미하게 된다. 다음층인 은닉층은 활성값 $a^{[1]}$ 을 계산한다. 이때 은닉층의 활성값은 각 노드를 벡터화 한 것으로 구성되므로, 다음과 같이 4차원 벡터로 표현할 수 있을 것이다.

\[a^{[1]} = \begin{bmatrix} a^{[1]}_1\\ a^{[1]}_2\\ a^{[1]}_3\\ a^{[1]}_4\\ \end{bmatrix}\]

그리고 그 다음 층인 출력층은 실수인 $a^{[2]}$ 를 계산한다.

이렇게 구성된 신경망은 2층 신경망이라고 불린다. 신경망의 층을 셀때 입력층은 세지 않고, 입력층을 0번 층으로 부르기 때문이다.

신경망 네트워크 출력의 계산

이전 로지스틱 회귀에서 배웠듯이 하나의 뉴런 노드는 다음의 두 연산으로 구성된다.

\[z = w^T x + b \\ a = \sigma (z)\]

신경망 네트워크에서도 개개의 노드는 다음의 계산을 수행한다. 먼저 입력은 다음과 같다.

\[x = \begin{bmatrix} x_1\\ x_2\\ x_3\\ \end{bmatrix}\]

첫번째 노드는 다음과 같은 연산을 수행한다.

\[z^{[1]}_1 = W^{[1]T}_1 x + b^{[1]}_1 \\ a^{[1]}_1 = \sigma (z^{[1]}_1)\]

여기서 대괄호로 감싸진 숫자는 해당 뉴런이 속한 층을 의미하고, 아랫첨자는 층 내에서의 노드 번호이다. 두번째, 세번째, 네번째 노드도 이와 동일한 연산을 수행한다. 그러므로 하나의 은닉층의 연산은 다음과 같이 표현 가능할 것이다.

\[z^{[1]}_1 = W^{[1]T}_1 x + b^{[1]}_1,\ a^{[1]}_1 = \sigma (z^{[1]}_1)\\ z^{[1]}_2 = W^{[1]T}_2 x + b^{[1]}_2,\ a^{[1]}_2 = \sigma (z^{[1]}_2)\\ z^{[1]}_3 = W^{[1]T}_3 x + b^{[1]}_3,\ a^{[1]}_3 = \sigma (z^{[1]}_3)\\ z^{[1]}_4 = W^{[1]T}_4 x + b^{[1]}_4,\ a^{[1]}_4 = \sigma (z^{[1]}_4)\]

이를 벡터화 해보자. 가중치는 다음과 같이 벡터화를 할 수 있을 것이다.

\[W^{[1]T} := \begin{bmatrix} ――\ W^{[1]T}_1\ ――\\ ――\ W^{[1]T}_2\ ――\\ ――\ W^{[1]T}_3\ ――\\ ――\ W^{[1]T}_4\ ――\\ \end{bmatrix}\]

그렇다면 $z^{[1]}$ 는 다음과 같이 표현 가능하다.

Note. 다음 내용은 선형대수의 기초적인 내용을 숙지한 것을 가정하고 있음. 선형 대수는 관련 교과서 혹은 유튜버 3Blue1Brown선형대수학의 본질 시리즈 를 보기를 권함. 대수는 머신러닝 뿐만 아니라 컴퓨터의 다양한 분야에 광범위하게 사용되므로 선형대수를 제대로 이해하는 것도 좋음.

\[z^{[1]} = W^{[1]T} x + b^{[1]} \\ = \begin{bmatrix} ――\ W^{[1]T}_1\ ――\\ ――\ W^{[1]T}_2\ ――\\ ――\ W^{[1]T}_3\ ――\\ ――\ W^{[1]T}_4\ ――\\ \end{bmatrix} \ \begin{bmatrix} x_1\\ x_2\\ x_3\\ \end{bmatrix} + \begin{bmatrix} b^{[1]}_1\\ b^{[1]}_2\\ b^{[1]}_3\\ b^{[1]}_4\\ \end{bmatrix} \\ \ \\ = \begin{bmatrix} W^{[1]T}_1\ x + b^{[1]}_1\\ W^{[1]T}_2\ x + b^{[1]}_2\\ W^{[1]T}_3\ x + b^{[1]}_3\\ W^{[1]T}_4\ x + b^{[1]}_4\\ \end{bmatrix} = \begin{bmatrix} z^{[1]}_1\\ z^{[1]}_2\\ z^{[1]}_3\\ z^{[1]}_4\\ \end{bmatrix}\]

다시 $a^{[1]}$ 를 구하기 위해 다음과 같이 표현할 수 있다.

\[a^{[1]} = \begin{bmatrix} a^{[1]}_1\\ a^{[1]}_2\\ a^{[1]}_3\\ a^{[1]}_4\\ \end{bmatrix} = \sigma (z^{[1]})\]

출력층은 이렇게 구한 $a^{[1]}$ 을 다시 입력으로 받아 다음과 같이 은닉층과 동일한 연산을 한다.

\[z^{[2]} = W^{[2]} a^{[1]} + b^{[2]}\\ a^{[2]} = \sigma (z^{[2]})\]

많은 샘플에 대한 벡터화

신경망의 연산에 대한 벡터화를 했으니 이제 전제 테이터셋에 대해 벡터화를 해보자. \(z^{[1]} = W^{[1]} x + b^{[1]}\\ a^{[1]} = \sigma (z^{[1]})\\ z^{[2]} = W^{[2]} a^{[1]} + b^{[2]}\\ a^{[2]} = \sigma (z^{[2]})\)

이 식은 단일 입력 $x$ 에 대해 결과를 구하는 연산이므로 다음과 같이 표현할 수 있다.

\[x \mapsto a^{[2]} = \hat y\]

그러므로 여러 데이터셋에 대한 입력은 다음과 같이 표현 가능하다.

\[x^{(1)} \mapsto a^{[2](1)} = \hat y^{(1)}\\ x^{(2)} \mapsto a^{[2](2)} = \hat y^{(2)}\\ \vdots\\ x^{(m)} \mapsto a^{[2](m)} = \hat y^{(m)}\]

만약 벡터화되지 않은 방법으로 계산하려면 for, while 과 같은 반복문을 사용한 코드로 학습을 수행해야 할 것이다. 이는 학습 속도를 낮추므로 전체 데이터 셋에 대해 벡터화를 하는 것이 좋을 것이다.

벡터화를 하는 방법은 생각보다 단순하다. 열벡터로 표현되었던 개개의 입력값을 쌓아 행렬로 다음과 같이 표현하는 것이다.

\[X = \begin{bmatrix} |&|&\cdots&|\\ |&|&\cdots&|\\ x^{(1)}&x^{(2)}&\cdots&x^{(m)}\\ |&|&\cdots&|\\ |&|&\cdots&|\\\end{bmatrix}\]

이를 이용해 다음과 같이 순방향 전파 연산을 해보자.

\[Z^{[1]} = W^{[1]} X + b^{[1]}\\ A^{[1]} = \sigma (Z^{[1]})\]

$X$ 가 $3 \times m$ 행렬이므로 $Z^{[1]}, A^{[1]}$ 는 다음과 같은 행렬이다.

\[Z^{[1]} = \begin{bmatrix} |&|&\cdots&|\\ |&|&\cdots&|\\ z^{[1](1)}&z^{[1](2)}&\cdots&z^{[1](m)}\\ |&|&\cdots&|\\ |&|&\cdots&|\\\end{bmatrix},\ \\ \ \\ A^{[1]} = \sigma (Z^{[1]}) = \begin{bmatrix} |&|&\cdots&|\\ |&|&\cdots&|\\ a^{[1](1)}&a^{[1](2)}&\cdots&a^{[1](m)}\\ |&|&\cdots&|\\ |&|&\cdots&|\\\end{bmatrix}\]

여기서 각각의 열은 각각의 데이터셋에 대응된다.

활성화 함수

활성화함수는 하이퍼파라미터의 일부로서 머신러닝 모델을 설계할 때 선택하는 중요한 요소 중 하나이다. 지금까지는 시그모이드 함수를 사용했지만 시그모이드 외에도 다양한 활성화 함수가 있다. 먼저 지금까지 사용한 시그모이드 함수의 식은 다음과 같다.

\[a = \sigma (z) = \frac{1}{1 + e^{-z}}, \\ a \in (0, 1)\]

다른 활성화 함수로는 $tanh$ 함수가 있다. 식은 다음과 같다.

\[a = tanh (z) = \frac{e^{z} - e^{-z}}{e^{z} + e^{-z}}, \\ a \in (-1, 1)\]

은닉층 노드의 활성화함수를 $tanh$ 으로 놓으면 거의 항상 시그모이드 함수보다 좋은 결과를 얻을 수 있다. $tanh$ 의 결과값은 $a \in (-1, 1)$ 이고, 평균값이 0에 더 가깝기 때문에 학습 모델을 훈련할 때 평균을 0으로 설정하면 데이터의 중심을 0.5 대신 0으로 맞추고, 이는 다음 층의 학습을 쉽게하는 효과가 있기 때문이다. 하지만 출력층의 활성화 함수로 $tanh$ 을 쓰는 것은 좋은 선택지가 아닐 수 있는데, 그 이유는 $y \in {0, 1}$ 이라면 $\hat y$ 은 $\hat y \in [0, 1]$ 인 것이 더 좋기 때문이다. 이와 같이 다른 층에 다른 활성화 함수를 사용할 수 있다.

시그모이드와 $tanh$ 의 단점은 $z$ 가 충분히 크거나 작으면 함수의 미분계수가 작아진다는 것이다. 이는 경사하강법이 느려지는 원인이 된다. 그래서 머신러닝에서는 ReLU라고 불리는 정류 선형 유닛(Regular Linear Unit)함수가 인기있다. ReLU 함수는 다음과 같이 정의된다.

\[f_{ReLU}(z) = \begin{cases} 0\ (z < 0)\\ z\ (z \geq 0)\\ \end{cases} = max\{0, z\}\]

비록 ReLU는 $z = 0$ 일때 미분 불가능하지만, 현실적으로 컴퓨터에서 $z$ 가 정확히 0이 될 가능성은 매우 낮기 때문에 문제가 없다고 봐도 무방하다. 머신러닝 모델을 설계할 때 일반적으로 이진분류의 출력층에는 시그모이드 함수를 사용하고, 다른 경우에는 ReLU가 활성화 함수의 기본값으로 많이 사용된다. ReLU의 단점 중 하나는 $z < 0$ 일 때 도함수가 0이라는 것이다. 그래서 다른 버전인 Leaky ReLU가 있다. Leaky ReLU는 $z$ 가 음수일 때도 약간의 기울기를 준다. ReLU, Leaky ReLU의 장점은 시그모이드, $tanh$ 과는 달리 대부분의 $z$ 에 대해 기울기가 0과 매우 다르다는 것이다. 이는 신경망의 학습을 더 빠르게 한다. 다만 일반적으로 $z$ 의 값은 양수이기 때문에 ReLU가 더 자주 사용된다.

비선형 활성화 함수

지금까지 소개했던 활성화 함수는 모두 비선형이다. 비선형 활성화 함수를 사용하는 이유를 알아보자. 1번째 은닉층의 활성화함수 $g^{[1]}$ , 출력층의 활성화 함수 $g^{[2]}$ 에 대해 다음과 같이 모델을 표현해보자.

\[z^{[1]} = W^{[1]} x + b^{[1]}\\ a^{[1]} = g^{[1]}(z^{[1]})\\ z^{[2]} = W^{[2]} a^{[1]} + b^{[2]}\\ a^{[2]} = g^{[2]}(z^{[2]})\]

$g^{[1]}, g^{[2]}$ 가 모두 선형함수라고 해보자. 편의를 위해 두 함수가 모두 항등함수라고 해보자. 그렇다면 다음과 같이 식을 표현할 수 있을 것이다.

\[a^{[2]} = W^{[2]}\ (W^{[1]} x + b^{[1]}) + b^{[2]}\]

이를 정리해보자. 행렬에 대해선 왼쪽 분배법칙이 성립하므로, 다음과 같이 식이 정리될 것이다.

\[a^{[2]} = \left(W^{[2]} W^{[1]} \right) x + \left(W^{[2]} b^{[1]} + b^{[2]} \right)\]

식을 살펴보면 $W^{[2]} W^{[1]}$ 은 하나의 가중치 행렬로 표현가능하고, $W^{[2]} b^{[1]} + b^{[2]}$ 은 하나의 편향 벡터로 볼 수 있음을 확인할 수 있다. 이는 은닉층과 출력층, 두 개의 층으로 구성된 뉴런 층이 하나의 뉴런 층과 동일해짐을 의미한다. 선형 활성화 함수를 활성화 함수로 사용한다면 뉴런 층이 얼마나 많든지에 관계없이 은닉층이 없는 것과 동일하다는 것이다. 선형 활성화 함수를 은닉층에 사용하는 것은 은닉층을 없애는 것과 동일한 효과를 가져온다. 다만 항등함수를 활성화 함수로 사용하는 경우가 있는데, 회귀 문제에 대해 머신러닝을 하는 경우이다. 회귀 문제에서는 데이터들의 관계를 수식으로 최대한 잘 설명하는 것이 관건이므로, 항등함수를 사용하여 $y \in \mathbb{R}$ 이도록 하는 것이 좋다. 하지만 이는 출력층의 경우로, 은닉층에서 이를 사용하는 것은 쓸모가 없다.

활성화 함수의 미분

다양한 활성화 함수의 도함수를 구해보자. 활성화 함수의 도함수를 구하는 것은 수치해석적 미분이 아닌 기호적 미분을 수행하므로, 미세하지만 더 정확하고 일반적으로 속도가 더 빠르다.

  • 시그모이드 함수
    \(\sigma (z) = \frac{1}{1 + e^{-z}}\\ \ \\ \frac{d}{dz} \sigma (z) = \frac{1}{1 + e^{-z}} \left(1 - \frac{1}{1 + e^{-z}} \right)\\ \ \\ = \sigma (z) \left(1 - \sigma (z)\right)\)

  • $tanh$
    \(tanh (z) = \frac{e^{z} - e^{-z}}{e^{z} + e^{-z}}\\ \ \\ \frac{d}{dz} tanh (z) = 1 - \left(tanh(z) \right)^2\)

  • ReLU / Leaky ReLU
    \(f_{ReLU}(z) = \begin{cases} 0\ (z < 0)\\ z\ (z \geq 0)\\ \end{cases} = max\{0, z\}\\ \ \\ \frac{d}{dz} f_{ReLU}(z) = \begin{cases} 0\ (z < 0)\\ 1\ (z \geq 0)\\ \end{cases}\)

    $z = 0$ 인 경우, 좌미분계수와 우미분계수가 다르므로 엄밀하게는 미분계수가 정의되지 않는다. 다만 이 경우는 앞서 언급했듯이 거의 없으므로, 실제로는 미분계수가 0 혹은 1이라고 해도 무방하다. 이와 유사하게 Leaky ReLU에서도 다음과 같이 도함수가 정의된다.

    \[f_{Leaky\ ReLU}(z) = \begin{cases} 0.01z\ (z < 0)\\ z\ (z \geq 0)\\ \end{cases} = max\{001z, z\}\\ \ \\ \frac{d}{dz} f_{Leaky\ ReLU}(z) = \begin{cases} 0.01\ (z < 0)\\ 1\ (z \geq 0)\\ \end{cases}\]

신경망 네트워크와 경사하강법

이제 신경망 네트워크에서 경사하강법을 구현하는 방법을 알아보자. 현재 신경망의 마라미터와 피처(Feature)는 다음과 같다고 하자.

\[Parameters: W^{[1]},\ b^{[1]},\ W^{[2]},\ b^{[2]}\\ Features\ Dimensions: n_x = n^{[0]},\ n^{[1]},\ n^{[2]} = 1\]

각 가중치 행렬과 입력 행렬은 행렬곱이 가능해야 하고, 행렬곱 결과는 다시 편향벡터와 크기가 같아 덧셈 연산이 정의되어야 한다. 그러므로, $W^{[1]}$ 행렬은 $n^{[1]} \times n^{[0]}$ 행렬이 되어야 하고, $W^{[2]}$ 행렬은 $n^{[2]} \times n^{[1]} = n^{[2]} \times 1$ 행렬이 되어야 한다.

그리고 이에 대한 비용함수는 다음과 같이 정의될 것이다.

\[J \left(W^{[1]},\ b^{[1]},\ W^{[2]},\ b^{[2]} \right) = \frac{1}{m} \sum^{n}_{i = 1} L(\hat y,\ y)\]

초기에 각 파라미터를 초기화한 후, 경사하강법이 반복될 때마다 각 파라미터 값이 갱신되고, 그에 따라 예측값이 갱신된다. 먼저 다음과 같이 비용함수의 도함수를 구한다.

\[dw^{[1]} = \frac{dJ}{dW^{[1]}},\ db^{[1]} = \frac{dJ}{dW^{[1]}},\ \cdots\]

그리고 학습률 $\alpha$ 에 대해 다음과 같이 파라미터를 갱신한다.

\[W^{[1]} := W^{[1]} - \alpha\ dW^{[1]}\\ b^{[1]} := b^{[1]} - \alpha\ db^{[1]}\\ W^{[2]} := W^{[2]} - \alpha\ dW^{[2]}\\ b^{[2]} := b^{[2]} - \alpha\ db^{[2]}\]

이 과정을 비용함수가 최솟값이 될 때까지 반복한다. 이제 여기에 역전파를 적용해보자.

\[dZ^{[2]} = A^{[2]} - Y\\ dW^{[2]} = \frac{1}{m} dZ^{[2]}A^{[1]T}\\ db^{[2]} = \frac{1}{m} \sum^{n}_{i = 1} {dZ^{[2]}}_i\\ \ \\ dZ^{[1]} = W^{[2]T}\ dZ^{[2]} * g^{[1]} \left(Z^{[1]} \right)\\ dW^{[1]} = \frac{1}{m} dZ^{[1]}X^{T}\\ db^{[1]} = \frac{1}{m} \sum^{n}_{i = 1} {dZ^{[1]}}_i\]

이렇게 행렬로 표현된 식은 여러 뉴런 층의 역전파를 행렬로 묶어서 연산한 것에 불과하므로, 겉보기에 식은 복잡해 보여도 개개의 원소 단위로 풀어서 생각해보면 이해가 가능할 것이다.

랜덤 초기화

파라미터인 가중치와 편향을 초기화하는 것은 중요하다. 가중치를 0으로 초기화된다면 제대로 작동하지 않을 것이기 때문이다. 가중치를 모두 0으로 초기화하면 어떤 데이터 셋에 대해서도 한 층의 뉴런들의 값이 동일하게 된다. 대칭이 되는 것이다. 이때 역전파를 계산하면 $dz^{[1]}$ 의 값도 각 뉴런마다 동일하게 되고, 그에 따라 모든 뉴런이 동일한 값으로 계속 갱신된다.

다음의 예를 살펴보자. 입력 피처의 차원이 2이고 은닉층의 뉴런이 2개인 이진분류 모델에 대해 가중치가 모두 0으로 초기화되면 다음과 같이 표현할 수 있다.

\[W^{[1]} = \begin{bmatrix} 0&0\\ 0&0\\ \end{bmatrix},\ b^{[1]} = \begin{bmatrix} 0\\ 0\\ \end{bmatrix}\]

이와 같으면 어떤 입력에 대해서도 $a^{[1]}_1 = a^{[1]}_2$ 이고, 그에 따라 $dz^{[1]}_1 = dz^{[1]}_2$ 가 된다. 이 경우 경사하강법을 통해 가중치가 갱신되어도 그 변화량이 같고, 결과적으로 가중치가 경사하강법을 거친 후에도 서로 동일하다. 이러한 결과는 경사하강법을 아무리 많이 반복해도 유지되므로, 두 뉴런의 가중치는 항상 동일하다.

\[dw = \begin{bmatrix} u&v\\ u&v\\ \end{bmatrix}\]

정리하면, 각 노드의 가중치가 모두 0으로 초기화되면, 한 은닉층에 하나의 뉴런만 있는 효과를 낳는다. 이 문제를 해결하기 위한 방법은 각 가중치를 무작위로 설정하는 것이다. 그러면 초기값이 다르기 때문에 매 경사하강 마다 가중치의 갱신도 다르게 되고, 앞서 나타난 문제가 해결된다.

하지만 한편으로, 무작위로 가중치를 조정하다가 가중치의 값이 너무 커지게 되어도 문제가 발생한다. 가중치의 값이 커지면 활성화 함수에 대입되는 $Z^{[1]}$ 의 값도 커지게 되고, 시그모이드 함수와 $tanh$ 함수에 큰 값이 대입될 수 있게 된다. 이는 기울기의 소실로 이어지고, 다시 경사하강법을 이용한 학습 속도가 느려지는 원인이 된다. 그래서 가중치를 초기화 할때 충분히 작은 값을 곱해 이러한 문제를 막는다. 혹은 정규분포난수(난수의 분포가 정규분포인 난수)를 사용하기도 한다.

본 노트는 Andrew Ng의 머신러닝 수업을 정리한 것임. Andrew Ng, Machine learning lecture, Youtube Link

이전 포스트 다음 포스트