신경망을 구현하기 전, 활성화 함수가 무엇인지와 왜 비선형 함수를 사용해야 하는지에 대해 살펴보겠습니다. 그 다음, 여러 종류의 활성화 함수(계단 함수, 시그모이드 함수, ReLU 함수 등)를 알아보고, 코드로 어떻게 구현하는지를 살펴보겠습니다. 또한, 퍼셉트론과 신경망의 차이점도 알아보겠습니다. 마지막으로, 출력층에서 사용되는 활성화 함수와 그들이 회귀와 분류 문제에서 어떻게 사용되는지에 대해 다룰 것입니다.
본 글을 통해 신경망의 기본적인 구조와 활성화 함수의 역할에 대해 이해할 수 있습니다.
활성화 함수
활성화 함수는 입력 신호의 총합을 받아서, 그것을 기반으로 한 출력 신호를 생성하는 함수입니다. 이때, 중요한 특징이 하나 있습니다.
"신경망에서 활성화함수는 비선형 함수로 사용해야한다."
선형함수는 입력값이 상수배만큼 변하는 함수를 말하며, 기본적으로 $f(x) = ax + b$형태의 직선함수로 표현됩니다.
그럼 여기서 "왜? 신경망에서 활성화함수를 선형함수로 사용하면 안 될까?"라는 의문을 가질 수 있습니다. 이를 이해하기 위해 극단적인 예시를 들어보겠습니다.
활성화 함수로 $h(x) = c(x)$를 사용하는 3층 네트워크를 구성해보면, 그 결과는 다음과 같습니다. $y(x) = h(h(h(x)))$ 이를 계산하면 $y = c^3x$가 됩니다. 이는 $y = ax$와 같은 형태의 선형함수 입니다. 결국, 선형함수를 활성화함수로 사용하게 되면, 여러 층으로 구성하는 신경망의 의미가 사라지게 됩니다.
활성화 함수로 사용되는 비선형 함수에는 여러 종류가 있으며, 대표적으로 계단함수, 시그모이드 함수, ReLU 함수 등이 있습니다.
계단함수
퍼셉트론을 구현할 때 사용한 것이 계단함수입니다. 계단 함수는 입력이 임계값을 초과하면 1을, 그렇지 않으면 0을 출력하는 매우 단순한 형태의 활성화 함수입니다. 이러한 특징 때문에 퍼셉트론에서 주로 사용됩니다. 여기서는 이 함수를 간단하게 코드로 구현해 보겠습니다.
def step_fucntion(x):
y = x > 0 #x는 numpy 사용
return y.astype('int') # False : 0, True : 1
이렇게 구현된 계단 함수는 퍼셉트론의 활성화 함수로 사용되어, 입력 신호의 총합이 어떤 값을 초과하면 1을 출력하고 그렇지 않으면 0을 출력하게 됩니다.
시그모이드함수
시그모이드 함수는 계단함수처럼 0과 1사이의 값을 갖습니다. 하지만 시그모이드 함수는 계단 함수와 달리 좀 더 부드럽고 연속적인 값을 가질 수 있습니다. 이 때문에 실제 신경망에서 자주 사용됩니다. 시그모이드 함수의 식은 다음과 같습니다.
$$h(x) = {1 \over 1 + \exp(-x)} = {1 \over 1 + e^{-x}}$$
이 함수를 코드로 구현하면 다음과 같습니다.
def sigmoid(x):
return 1 / (1 + np.exp(-x))
ReLU 함수
ReLU함수는 최근에 신경망에서 많이 사용되는 활성화 함수 중 하나입니다. 이 함수는 입력값이 0보다 큰 경우 그대로 출력하고, 0 이하인 경우 0을 출력합니다, 이러한 특성 덕분에 계산 효율성이 뛰어나고, 그래디언트 소실 문제를 줄여주기 때문에 많이 사용됩니다.
ReLU 함수의 수식은 다음과 같습니다.
$$h(x) = \left\{\begin{matrix} x ( x > 0 ) \\ 0 (x \leq 0)\end{matrix}\right.$$
이 함수를 코드로 구현하면 아래와 같습니다.
def reLU(x)
return np.maximum(0, x)
신경망
퍼셉트론 VS 신경망
퍼셉트론과 신경망은 굉장히 유사한 구조를 가지고 있습니다. 둘 다 입력층의 신호를 활성화 함수를 통과하여 출력 신호가 결정되는 구조 입니다. 그렇다면 둘의 주된 차이점은 무엇일까요?
먼저, 퍼셉트론은 일반적으로 단층 구조를 가지며, 활성화 함수로 계단 함수를 주로 사용합니다. 이로 인해 출력값은 0 또는 1을 나타냅니다. 따라서 복잡한 패턴이나 비선형성을 모델링하는데 한계가 있습니다.
반면, 신명망은 여러 개의 층을 가질 수 있으며, 다양한 활성화 함수를 사용할 수 있습니다. 이러한 활성화 함수로 인해 신경망은 연속적인 실수 값을 출력할 수 있으며, 이는 복잡한 패턴이나 비선형성을 더 잘 표현할 수 있게 합니다.
즉, 퍼셉트론은 비교적 단순한 문제나 선형적으로 분리 가능한 문제를 해결하는데 적합하며, 신경망은 더 복잡한 문제와 비선형 문제를 해결하는 데 유리합니다.
신경망 구현
위 그림은 '밑바닥부터 시작하는 딥러닝'책에서 가져온 그림입니다. 이 그림을 이용해서 2층 신경망을 단계별로 구현해보겠습니다.
먼저, 1층부터 구현해봅시다. 그림에서 볼 수 있듯이, 1층은 입력 벡터 X, 가중치 행렬 W1, 그리고 편향 벡터 B1을 사용하여 활성화 값 a1을 계산하고, 시그모이드 함수를 통해 z1 값을 얻습니다. 코드는 다음과 같습니다:
X = np.array([1.0, 0.5])
w1 = np.array([[0.1,0.3,0.5],[0.2, 0.4,0.6]])
B1 = np.array([1.0,1.0,1.0])
a1 = X @ w1 + B1
z1 = sigmoid(a1)
다음으로, 2층 구조를 구현해보겠습니다. 위 코드와 유사해서 추가적인 설명은 생략하겠습니다.
W2 = np.array([[0.1, 0.3],[0.2, 0.4],[0.3, 0.6]])
B2 = np.array([0.1, 0.1])
a2 = z1 @ W2 + B2
z2 = sigmoid(a2)
마지막으로 출력층의 구조를 구현하겠습니다.
def identity_function(x):
return x
B3 = np.array([1.0, 1.0])
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
a3 = z2 @ W3 + B3
Y = identity_function(a3)
Y
자! 그림을 참고해서 2층 신경망을 성공적으로 구현했습니다. 단계별로 하나씩 구현함으로써, 복잡해 보이더라도 쉽게 구현할 수 있음을 알게되었습니다.
여기서 언급된 identity_fucntion은 항등 함수인데, 이에 대한 자세한 설명은 바로 다음에 진행하겠습니다.
출력층
출력층에서의 활서화 함수의 선택은 문제의 성격에 따라 달라집니다. 회귀 문제의 경우, 항등 함수를 사용하여 연속적인 값을 출력할 수 있습니다. 반면, 분류 문제의 경우에는 시그모이드 함수(이진 분류) 또는 소프트맥스 함수(다중 클래스 분류)를 사용하여 각 클래스에 대한 확률을 출력할 수 있습니다.
회귀
일반적으로 회귀에서는 항등함수를 사용합니다. 항등함수는 입력값을 그대로 출력으로 전달하는 함수로 이전에 언급 identity_fucntion이 이에 해당합니다. 수학적으로는 $f(x)=x$로 표현할 수 있습니다. 이러한 특징 때문에, 회귀 문제에서 출력층의 활성화 함수로 사용될 때, 네트워크가 예측하려는 연속적인 값을 직접적으로 출력할 수 있게 됩니다.
분류
분류 문제에서 사용되는 활성화함수는 보통, 소프트맥스(softmax) 함수를 사용합니다. 소프트맥스 함수의 식은 다음과 같습니다.
$$y_k = \frac{\exp(a_k)}{\sum_{i=1}^{n}\exp(a_i)} = \frac{e^{a_k}}{\sum_{i=1}^{n}e^{a_i}} $$
소프트맥스 함수의 특징은 모든 출력 값이 0과 1사이에 있으며, 모든 출력 값들의 합은 1이라는 점입니다. 이러한 특징 덕분에, 각 출력 값을 해당 클래스에 속할 확률로 해석할 수 있습니다. 일반적으로 가장 높은 출력값을 가진 클래스를 선택하는 방법이 많이 사용됩니다.
위 함수를 코드로 구현하면 다음과 같습니다.
def softmax(x):
return np.exp(x) / np.sum(np.exp(x))
a = np.array([0.3,2.9,4.0])
softmax(a) # array([0.01821127, 0.24519181, 0.73659691])
하지만, 컴퓨터에서 위 수식을 이용하면 오버플로우 문제가 발생합니다. 이를 해결하기 위해, 다음 식을 사용합니다.
부모와 분자에 상수 C를 곱하면 간단하게 해결할 수 있습니다.
이를 코드로 구현하면 다음과 같습니다.
def new_softmax(x):
C = np.max(x)
return np.exp(x - C) / np.sum(np.exp(x - C))
a = np.array([1010,1000,999])
print(softmax(a)) #[nan nan nan] #오버플로우
print(new_softmax(a))#[9.99937902e-01 4.53971105e-05 1.67006637e-05]
마무리
퍼셉트론과 신경망은 매우 비슷하지만 활성화 함수에서 큰 차이를 보인다는 것을 확인할 수 있었습니다. 퍼셉트론은 주로 계단 함수를 활성화 함수로 사용해서 0 또는 1을 출력하고, 신경망은 여러 다른 활성화 함수를 사용하여 연속적인 실수를 출력할 수 있습니다.
이번 글에서는 신경망이 퍼셉트론과 어떻게 다른지, 그리고 다양한 활성화 함수(계단 함수, 시그모이드 함수, ReLU 함수, 항등 함수, 소프트맥스)에 대해 알아보았습니다. 특히, 회귀와 분류 문제에 대해 각기 다른 활성화 함수가 사용되며, 이 함수들이 신경망의 출력값을 어떻게 변환하는지에 대해 배웠습니다.
'딥러닝 머신러닝 > 밑바닥 딥러닝' 카테고리의 다른 글
학습 알고리즘 구현하기 (0) | 2023.09.18 |
---|---|
손실함수(Loss Function) (0) | 2023.09.08 |
퍼셉트론 구현하기 (0) | 2023.08.31 |
퍼셉트론이란 (0) | 2023.08.30 |