공부를 하는 입장이기 때문에, 내용에 오류가 있을 수 있습니다. 오류가 있다면 적극적으로 알려주시면 감사합니다!
1. 신경망 (Nerual Network)
신경망에서 가장 왼쪽 줄을 입력층(Input Layer), 가장 오른쪽 층을 출력층(Output Layer)라고 한다. 그리고 그 사이에 중간 단계에 있는 노드들을 은닉층(Hidden Layer)라고 한다. 다음층으로 이동할 때, 가중치를 거쳐서 이동하게 되는데, 일반적으로는 가중치를 가지고 있는 층만 계산하여서 ~~ 층 신경망이라고 하여 모델의 깊이를 정한다. 따라서 위에 예시는 노드의 층이 4층이지만 3층 신경망이라고 많이 부른다.
입력층은 말 그대로 원본 데이터를 받아들이는 층을 말한다. 출력층은 은닉층에서 추출된 결과를 바탕으로 최종결과를 예측하는 층을 말한다. 마지막으로 은닉층 같은 경우는 입력 데이터를 가중치와 활성화 함수로 변환하여 고차원적이고 유용한 표현으로 바꾸는 층을 말한다.
2. 활성화 함수 (Activation Function)
활성화 함수란 입력 신호의 총합을 출력신호로 변환해 주는 함수이다. '신경망의 예'라는 그림에서 일부분만 가져온 형태이다. 입력 값을 그 층에서의 가중치와 곱해서 편향과 함께 전부 더한다. 그러면 그 더한 값이 활성화 함수를 거쳐 출력 값 (다음층의 입력 값)으로 변하게 된다.
즉, 활성화 함수는 주로 각 뉴런의 출력값을 결정하고 신경망의 비선형성을 제공해 주는 역할을 한다. 이를 위하여 비선형함수인 시그모이드(sigmoid), 하이퍼볼릭 탄젠트(tanh), 렐루(Rectified Linear Unit, ReLU) 함수 등을 사용한다. 비선형 함수를 사용하는 이유는 만약 활성화 함수에서 선형함수를 사용할 경우 은닉층의 의미가 사라지기 때문이다.
예를 들어 3층 신경망에서 \( h(x) = cx \)를 활성화 함수로 사용한다고 하면, 출력 값은 \( y(x) = h(h(h(x))) \)로 표현할 수 있을 것이다. 이 식은 결국 \( y = c * c * c * x \)로 표현되고, 이는 본질적으로 학습능력이 선형 회귀와 다름이 없어진다. 따라서 입력과 출력의 복잡한 비선형적 관계를 학습하기 위해서는 은닉층이 깊어져야 하고, 그를 위해서는 활성화 함수를 비선형 함수로 사용해야 한다.
2.1 Sigmoid 함수
시그모이드 함수는 exp(-x)를 사용하여 구현한다.$$ h(x) = \frac{1}{1+e^{-x}} $$
시그모이드 함수도 계단 함수처럼 입력 값이 클수록 1에 가까워지고, 작을수록 0에 가까워진다는 것을 알 수 있다.
코드로는 다음과 같이 구현한다.
def sigmoid(x):
return 1 / (1 + np.exp(-x))
이후에 나오겠지만, 시그모이드는 미분했을 때, 매우 간단하게 나오도록 설계되어 있다. $$ {y}' = y (1 - y) $$
2.2 Relu 함수
최근에는 시그모이드 함수보다 렐루 함수를 많이 사용한다. 렐루 함수는 입력이 0을 넘으면 입력을 그대로 출력하고 입력이 0 이하면 0을 출력하는 함수이다. $$ h(x) = \left\{\begin{matrix} x (x > 0) \\0 (x \leq 0) \end{matrix}\right. $$
코드로는 다음과 같이 구현한다.
def relu(x):
return np.maximum(0, x)
3. 출력층
출력층에서는 다른 층들과 같이 가중치와 편향으로 입력신호를 구하지만, 사용하는 활성화 함수가 다르다. 기본적으로 신경망은 분류와 회귀 모델 둘 다 사용할 수 있다. 일반적으로는 회귀에는 항등 함수(Identity Function), 분류에는 소프트맥스 함수(Softmax Function)를 사용한다.
가중치와 편향값을 바탕으로 값을 추론(inference)하는 과정을 순정파(forward Propagation)이라고 한다. 이를 통해 출력 값을 얻어 정답과의 오차(loss)를 구해, 이를 바탕으로 학습을 진행한다. 추론 과정에서는 출력이 가장 큰 노드가 정답 노드라고 판단하는데, 출력층에서 활성화 함수(ex 소프트맥스 함수, 분류)를 거쳐도 그 출력들의 대소 관계가 변하지 않는다. 따라서 계산량을 줄이기 위해 '학습' 과정이 아닌, 실제로 '추론' 과정에서는 출력층에서 활성화 함수를 사용하지 않는 경우가 많다.
3.1 Identity 함수
항등함수는 입력 그대로 전달하는 역할을 한다. 따라서 입력 신호가 그대로 출력 신호가 된다.
def identity_function(x):
return x
3.2 Softmax 함수
분류에서 사용하는 소프트맥스 함수는 입력 벡터 \(a = \begin{bmatrix} a_{1} & a_{2} & a_{3} & ... & a_{k} \\\end{bmatrix}\)에 대해, 각 요소 \(a_{k}\)를 0~1로 정규화하여 \(y_{k}\)를 구하는 함수이다. 특정 클래스에 대한 조건부 확률(k번째 확률 / 전체 확률)로써 구할 수 있다. $$ y_k = \frac{e^{a_k}}{\sum_{i=1}^n e^{a_i}} $$
시그모이드 함수의 경우는 k가 2인 소프트맥스 함수인 것을 알 수 있다. (k는 2로 두고 전개한 다음 \(a\)중 하나를 0이라고 생각하고, 나머지 1개에 대한 확률로써 구하면 시그모이드 함수를 유도 할 수 있다.)
정의에 대한 코드는 다음과 같다.
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
이 함수를 사용하면, exp함수가 매우 큰 값을 가지기 때문에 오버플로우가 날 수 있다. 그래서 간단한 식 조작을 통해 이를 해결할 수 있다. $$ \frac{c * e^{a_k}}{c*\sum_{i=1}^n e^{a_i}} = \frac{e^{a_k + ln{c}}}{\sum_{i=1}^n e^{a_i + ln{c}}} = \frac{e^{a_k + A} }{\sum_{i=1}^n e^{a_i + A} } $$
지수함수는 x가 동일하게 변하면, 그 결과도 비율이 일정하게 변한다. 오버플로우(언더플로우)를 막는 방법으로 전체 항에 가장 큰 입력값을 빼주는 방식을 사용한다. 이 방식으로 지수함수가 너무 크거나 작은 경우를 막아 안정적으로 사용할 수 있다. $$ \frac{e^{a_k - \textrm{max}(a)}}{\sum_{i=1}^n e^{a_i - \textrm{max}(a)}} $$
이를 코드로 구현하면 다음과 같다.
def softmax(x):
x = x - np.max(x, axis=-1, keepdims=True) # 오버플로 대책
return np.exp(x) / np.sum(np.exp(x), axis=-1, keepdims=True)
이 코드는 오버플로우만 막는 것이 아니라 x가 차원이 높아져도 각 데이터에 대하여 해결할 수 있다.
4. 배치(batch)
컴퓨터는 행렬연산이 매우 빠르다. 하지만 파일을 읽거나 출력하는 것은 느리다. 따라서 학습할 때, 데이터를 하나씩 읽고 학습하는 것이 아닌, 데이터를 한 번에 묶어서 행렬로 계산을 하는 것이 효율적이다. 그리고 이렇게 묶은 입력 데이터를 배치라고 한다.
MNIST데이터의 경우 28x28 2D데이터이다. 그리고 학습할 때는 이를 1차원으로 바꾼 784(28x28)의 형태로 학습을 한다. 그리고 정답의 경우는 0부터 9까지의 확률이 나오게 된다. 그렇기 때문에 데이터를 배치로 묶어서 n개의 데이터를 사용하게 되면 당연히 정답도 n개의 정답이 나오게 된다.
'밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
Backpropagation (0) | 2024.12.24 |
---|---|
Neural Network Learning Example (0) | 2024.12.22 |
Neural Network Learning (0) | 2024.12.22 |
Neural Network example (1) | 2024.12.20 |
밑바닥부터 시작하는 딥러닝 (0) | 2024.12.18 |