Activation Functions and Their Derivatives(Sigmoid, tanh, ReLU, LeakyReLU) 비선형 함수를 사용하는 이유, 활성화 함수의 미분
Non-Linear Activation Function
먼저, 활성화 함수를 사용하는 이유는 네트워크에서 다음 뉴런의 사용 여부를 결정하기 위함입니다. 이는 해당 노드가 사용자의 목적에 맞는 정보를 제공하는지 판단하는 과정이라고 해석할 수 있습니다.
그렇다면 이 판단 과정에 꼭 비선형 함수를 사용해야 하는 이유는 무엇일까요? 비선형 활성화 함수를 사용하는 것은 층을 깊게 하는 의미가 있습니다. 이때 선형 활성화 함수를 사용한다면, 여러 층을 쌓은 것은 효용이 없습니다. 선형함수인 h(x) = ax를 활성화 함수로 3-Layer NN를 구성할 경우, y(x) = h(h(h(x))) = a*a*ax입니다. 이 때 a^3을 b로 치환할 경우 y(x) = bx로, 한 층을 쌓은 선형 함수의 형태와 같게 됩니다.
이 글에서는 가장 널리 알려진 활성화 함수인 Sigmoid, tanh, ReLU 등에 대하여 간단히 알아보고자 합니다.
Sigmoid
특징
- -∞ ~ ∞의 Input을 0~1의 확률값으로 변환함
- 최대 기울기가 0.25로 학습이 빠르지 않음. (x = 0일 때 σ'(x) = σ(x)(1-σ(x)) = 0.5 * 0.5 = 0.25)
- 특정 영역(-2~+2)를 벗어나면 학습이 느려짐. 이는 기울기가 0에 수렴하기 때문(Saturated)
- Non Zero Centered, 따라서 평균값이 양수쪽으로 이동하게 됨.
- Gradient Vanishing, 0~1의 값을 계속해서 곱하게 되므로 결국 값이 아주 작아지게 됨.
- exponential 계산의 연산량이 많음. exp(3.8)과 같은 경우 복잡한 계산이 이어지게 됨.
def sigmoid(z):
return 1 / (1 + np.exp(-z))
z = np.arange(-10, 10, 0.1)
a = sigmoid(z)
plt.title("sigmoid", fontsize=25)
plt.plot(z, a)
plt.show()
tanh
특징과 장단점의 대부분을 Sigmoid 함수와 공유하지만, Zero-Centric 함수입니다. 따라서 함수의 학습이 느려지는 문제를 개선할 수 있었습니다.
def tanh(z):
return (np.exp(z)-np.exp(-z)) / (np.exp(z)+np.exp(-z))
z = np.arange(-10, 10, 0.1)
a = tanh(z)
plt.title("tanh", fontsize=25)
plt.plot(z, a)
plt.show()
Sigmoid와 tanh의 도함수를 비교하면, tanh 미분계수의 최댓값이 1로 Sigmoid보다 큰 것을 확인할 수 있습니다. 그러나 입력의 절대값이 커질수록, 두 함수 모두 Gradient Vanishing 문제가 발생하리라는 것을 짐작할 수 있습니다.
ReLU(Rectified Linear Unit)
f(x) = max(0, x)로 정의되는 함수입니다. 앞서 살펴본 Sigmoid와 tanh의 Gradient Vanishing문제를 해결하여, 지금도 널리 사용되는 활성화 함수입니다. x가 양수일 때 학습이 정체되지 않고, 최대값만 비교하면 되므로 연산량 적어 효율적입니다. 하지만, x가 음수일 때는, 기울기가 0이기 때문에 학습이 전혀 되지 않고 뉴런이 죽을 수 있는 단점이 존재합니다(Dying ReLU). 또한 Zero-Centered가 아니기도 합니다.
x = 0에서는 미분이 불가능하지만, 실제 사용할 때에는 x = 0일 때의 미분계수를 1로 두고 사용합니다.
def relu(z):
return np.maximum(0, z)
z = np.arange(-10, 10, 0.1)
a = relu(z)
plt.title("ReLU", fontsize=25)
plt.plot(z, a)
plt.show()
LeakyReLU
ReLU의 Dying ReLU 문제를 개선한 함수입니다. x<0 일 때에도 미분값이 0이 되지 않도록 작은 값을 곱해주게 됩니다.
def leakyrelu(z):
return np.maximum(0.1*z, z)
# 그래프 개형 확인을 위해 0.1곱해줌. 실제로는 더 작은 0.01 등 사용
z = np.arange(-10, 10, 0.1)
a = leakyrelu(z)
plt.title("LeakyReLU", fontsize=25)
plt.plot(z, a)
plt.show()
이 외에도 ReLU를 베이스로 하는 PReLU - (f(x) = max(ax, x), a파라미터를 학습에 포함시켜, 기울기를 update), ELU - (x=0에서 꺾이는 부분을 exp를 사용하여 smoothing함) 등이 새로이 만들어져 활성화 함수로 활용되고 있습니다.