본문 바로가기
  • 비둘기다
  • 비둘기다
  • 비둘기다
AI/Deep learning from Scratch

[머신러닝, 딥러닝] 신경망 (3) - 항등함수와 소프트맥스 함수

by parzival56 2023. 1. 22.

이번에는 지난번의 말미에 언급했던 항등함수와 소프트맥스 함수에 대해서 다뤄보려고 합니다.

 

먼저 항등 함수입니다.

항등이란 예전부터 수학에서 자주 등장하는 단어이듯, 뜻은 같다, A = A라는 뜻입니다. 이것에 알고리즘을 씌워 생각해 보면 입력과 출력이 항상 같다는 뜻이 됩니다. 여태까지 신경망의 형태를 보면 하나의 입력이 수많은 은닉층에 영향을 미치고 또한 많은 값들이 출력에 영향을 주는 것을 확인할 수 있었습니다.

그러나 항등 함수는 그 구조 특성상 출력층에서 입력 신호가 그대로 출력 신호로 가는 모습을 볼 수 있기 때문에 이전의 신경망의 모습과는 상이한 모습을 볼 수 있습니다.

위와 같은 항등함수는 주로 회귀에서 사용된다고 하였습니다. 그러나 구조를 보면 아시다시피 입력과 출력이 그대로 가기 때문에 코드를 짜는 것이 쉽기 때문에 패스하도록 하겠습니다.

 

 

소프트맥스 함수

다음은 분류에서 주로 사용되는 소프트맥스 함수입니다.

소프트맥스 함수는 전에 봤던 시그모이드 함수처럼 식이 존재합니다. 

exp()는 시그모이드에서도 나오다시피 자연상수 e의 지수함수 꼴이고 n은 출력층의 뉴런 수, k는 그중 몇 번째 출력인지를 의미합니다. 

위 식의 분모에서 볼 수 있듯이 소프트맥스 함수의 출력은 모든 압력 신호로부터 영향을 받기 때문에 위의 그림과 같이 모든 입력 신호가 모든 출력 신호에 영향을 주는 구조라고 할 수 있습니다.

 

소프트맥스 함수의 식을 바탕으로 간단한 코드입니다.

import numpy as np
a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a) # e의 지수함수 꼴로 배열을 바꿔주는 것
print(exp_a)

sum_exp_a = np.sum(exp_a) # 지수함수들의 합
print(sum_exp_a)

y = exp_a / sum_exp_a
print(y)

코드에서는 대부분의 연산 과정을 넘파일부터 얻어옴을 알 수 있습니다. 그만큼 넘파이가 다수의 수를 다루기에 편리하다는 것이죠. 

위 코드의 내용을 함수로 일반화를 시키면 이와 같습니다.

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

소프트맥스 함수를 사용할 시에 주의사항이 있습니다. 바로 오버플로 문제인데, 이는 매우 큰 값을 반환하는 지수함수끼리의 계산 때문에 발생합니다. 앞선 코드에서는 작은 값들로 계산을 해서 잘 나왔지만 만약 10, 100, 1000등의 큰 값을 넣으면 한 없이 큰 숫자가 반환되어 오류가 발생할 수 있습니다. 

이러한 문제점을 보완하기 위해 기존의 소프트맥스 함수식을 조정할 필요가 있습니다.

전개 과정을 살펴보도록 하겠습니다. 처음에는 임의의 정수 C를 분자와 분모에 곱해주었습니다. 이는 약분되면 똑같은 수식이기 때문에 상관없는 과정입니다. 그다음 이 C를 exp() 안으로 옮겨서 logC로 만듭니다. 마지막으로 logC는 C'이라는 기호로 치환하여 보기 쉽게 표현해 주면 끝입니다.

위 전개식이 말하고자 하는 것은 기존의 식에 어떠한 값을 더하거나 빼도 상관이 없다는 것과 오버플로를 막는 것이 목적이라는 것입니다. 여기서 C'에는 어떠한 값을 대입해도 상관은 없지만 이게 정말로 문제를 해결해 주는지를 알기 위해서는 인력신호 중 최댓값을 이용하는 것이 가장 빠르겠죠.

a = np.array([1010, 1000, 990])
b = np.exp(a) / np.sum(np.exp(a))
print(b)

c = np.max(a)
print(a-c)

d = np.exp(a-c) / np.sum(np.exp(a-c))
print(d)

코드에서 b라는 것을 만들어준 이유는 기존의 계산에서 overflow가 난다는 것을 보여주기 위함입니다. 만약 b를 출력하게 된다면 [nan, nan, nan]이 나오게 될 것입니다. nan은 not a number의 약자로 컴퓨터에서 풀이할 수 없는 영역의 수를 말합니다. 

이제 위의 overflow의 문제 해결을 반영한 소프트맥스 함수는 아래와 같을 것입니다. 

# overflow문제를 해결한 softmax 함수
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-c를 사용하였는가입니다. 아까 문제점을 보완하여 전개된 식을 살펴보며 이 식은 어떠한 값을 더하거나 빼도 결과는 같은 값을 가진다고 했습니다. 저희는 아까 기존의 함수식으로 큰 수를 입력받을 시에 오버플로 현상이 나타나는 것을 확인했습니다. 그러나 이제는 어떠한 값을 기존의 식에 더하거나 빼도 상관이 없기 때문에 값의 크기를 줄이기 위해서 해당 배열에 있는 가장 큰 값을 빼주는 과정입니다. 가장 큰 값을 빼줘야 다른 값들 또한 이상 없이 잘 나올 가능성이 크기 때문입니다. 

 

소프트맥스 함수는 값을 넣어보시면 아시겠지만 출력값이 항상 0에서 1.0 사이의 실수입니다. 또한 소프트맥스 함수의 출력의 총합은 1입니다. 출력의 총합이 1이 된다는 것은 소프트맥스 함수의 중요한 성질입니다. 이 성질 때문에 소프트맥스 함수의 출력을 확률로 해석할 수 있습니다. 

예시로 0번째가 0.018, 1번째가 0.245, 2번째가 0.737이었을 때 우리는 2번째 인덱스의 출력값이 73.7%의 확률로 가장 높다고 해석할 수 있습니다. 

exp() 함수 자체가 단조 증가함수이기 때문에 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않습니다. 예를 들어 두 번째 입력 값이 가장 크기 때문에 두 번째 출력 값이 가장 큰 것이 되는 것이죠.

 

 

이상으로 항등 함수와 소프트맥스 함수에 대해여 알아보았습니다. 기존의 소프트맥스 함수보단 오버플로를 보완한 식에 대해 자세하게 다뤄보았습니다.

댓글