03.5 출력층 설계하기
신경망은 분류와 회귀 모두에 이용할 수 있습니다. 다만 둘 중 어떤 문제냐에 따라 출력층에서 사용하는 활성화 함수가 달라집니다. 일반적으로 회귀에는 항등 함수를, 분류에는 소프트맥스 함수를 사용합니다.
NOTE_ 기계학습 문제는 분류 classification와 회귀 regression로 나뉩니다. 분류는 데이터가 어느 클래스 class에 속하느냐는 문제입니다. 사진 속 인물의 성별을 분류하는 문제가 여기에 속합니다. 한편, 회귀 문제는 입력 데이터에서 (연속적인) 수치를 예측하는 문제입니다. 사진 속 인물의 몸무게(57.4kg?)를 예측하는 문제가 회귀입니다.*
* 옮긴이_ 분류와 달리 회귀라는 이름은 직관적이지 않죠. 그 이유를 알려면 이름의 기원을 찾아봐야 합니다. 19세기 후반 영국의 우생학자 프랜시스 골턴 경은 사람과 완두콩 등을 대상으로 그 키(크기)를 측정했습니다. 관찰 결과 키가 큰 부모의 자식은 부모보다 작고 작은 부모의 자식은 부모보다 큰, 즉 평균으로 회귀(regression)하는 경향이 있음을 알았습니다. 그 사이에는 선형 관계가 있어 부모의 키로부터 자식의 키를 예측할 수 있고, 그 예측 결과값이 연속적인 수치인 것이죠.
3.5.1 항등 함수와 소프트맥스 함수 구현하기
항등 함수 identity function는 입력을 그대로 출력합니다. 입력과 출력이 항상 같다는 뜻의 항등입니다. 그래서 출력층에서 항등 함수를 사용하면 입력 신호가 그대로 출력 신호가 됩니다. 항등 함수의 처리는 신경망 그림으로는 [그림 3-21]처럼 되겠죠. 항등 함수에 의한 변환은 은닉층에서의 활성화 함수와 마찬가지로 화살표로 그립니다.
그림 3-21 항등 함수

한편, 분류에서 사용하는 소프트맥스 함수 softmax function의 식은 다음과 같습니다.
\[y_k = \frac{\exp(a_k)}{\sum_{i=1}^n \exp(a_i)} \hfill \text{[식 3.10]}\]$\exp(x)$는 $e^x$을 뜻하는 지수 함수 exponential function입니다($e$는 자연상수). $n$은 출력층의 뉴런 수, $y_k$는 그중 $k$번째 출력임을 뜻합니다. [식 3.10]과 같이 소프트맥스 함수의 분자는 입력 신호 $a_k$의 지수 함수, 분모는 모든 입력 신호의 지수 함수의 합으로 구성됩니다.
이 소프트맥스 함수를 그림으로 나타내면 [그림 3-22]처럼 됩니다. 그림과 같이 소프트맥스의 출력은 모든 입력 신호로부터 화살표를 받습니다. [식 3.10]의 분모에서 보듯, 출력층의 각 뉴런이 모든 입력 신호에서 영향을 받기 때문합니다.
그림 3-22 소프트맥스 함수

지니의 소프트맥스 시식회
- 지니: “소프트맥스는 여러 입력값들을 지수 함수 마법으로 부풀린 뒤, 그 값을 전부 더한 전체 합(분모)으로 각각의 값(분자)을 나눠주는 비율 변환 마법이야. 초콜릿 파이 한 판을 나눠 갖는 것과 같아!”
- 도로시: “아! 파이 전체를 100%로 두고, 각 뉴런이 입력값 크기에 비례해서 몇 퍼센트씩 나눠 가질지 결정해 주는 거구나. 그래서 다 나누어 준 조각들을 다 합치면 정확히 파이 한 판(1.0)이 되는 거네!”
- 토토: “멍멍! (침을 흘리며) 많이 입력받은 뉴런이 제일 큰 조각을 가져간다 멍!”
지니가 소프트맥스 초콜릿 파이를 슬라이스하여 0~9번 접시에 골고루 나누어 주고 있어요. 도로시와 토토가 맛있겠다며 지켜보고 있네요.
그럼, 이상의 소프트맥스 함수를 구현해봅시다. 여기에서는 파이썬 인터프리터를 사용하여 결과를 하나씩 확인해가며 진행하겠습니다.
>>> a = np.array([0.3, 2.9, 4.0])
>>>
>>> exp_a = np.exp(a) # 지수 함수
>>> print(exp_a)
[ 1.34985881 18.17414537 54.59815003]
>>>
>>> sum_exp_a = np.sum(exp_a) # 지수 함수의 합
>>> print(sum_exp_a)
74.1221542102
>>>
>>> y = exp_a / sum_exp_a
>>> print(y)
[ 0.01821127 0.24519181 0.73659691]
이 구현은 [식 3.10]의 소프트맥스 함수를 그대로 파이썬으로 표현한 것이라 딱히 설명할 것은 없네요. 이제 이 논리 흐름을 파이썬 함수로 정의하여, 앞으로 필요할 때 사용할 수 있도록 해놓겠습니다.
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
3.5.2 소프트맥스 함수 구현 시 주의점
앞 절에서 구현한 softmax() 함수의 코드는 [식 3.10]을 제대로 표현하고 있지만, 컴퓨터로 계산할 때는 결함이 있습니다. 바로 오버플로 문제입니다. 소프트맥스 함수는 지수 함수를 사용하는데, 지수 함수란 것이 쉽게 아주 큰 값을 내뱉죠. 가령 $e^{10}$은 20,000이 넘고, $e^{100}$은 0이 40개 넘는 큰 값이 되고, $e^{1000}$은 무한대를 뜻하는 inf가 되어 돌아옵니다. 그리고 이런 큰 값끼리 나눗셈을 하면 결과 수치가 ‘불안정’해집니다.
WARNING_ 컴퓨터는 수 number를 4바이트나 8바이트와 같이 크기가 유한한 데이터로 다룹니다. 다시 말해 표현할 수 있는 수의 범위가 한정되어 너무 큰 값은 표현할 수 없다는 문제가 발생합니다. 이것을 오버플로 overflow라 하며, 컴퓨터로 수치를 계산할 때 주의할 점입니다.
지니의 수치 마법 경고
- 지니: “도로시, 지수 함수($e^x$)는 입력이 조금만 커져도 수치가 부글부글 끓어 넘치는(Overflow) 성질이 있어. $e^{1000}$이 되면 컴퓨터가 감당하지 못해서 ‘무한대’(
inf)나 ‘수치가 아님’(nan)이 되어 계산이 엉망이 된단다!”- 도로시: “와! 진짜 1000만 들어가도 솥이 넘치듯이 수치가 터져버리는구나. 이걸 막을 수 있는 마법의 숟가락은 없을까?”
- 지니: “후후, 비법은 바로 입력값 중 가장 큰 값($C’$)을 가져와서 모든 입력값에서 빼주는 거야. 값을 빼주면 지수 함수 승수가 음수나 0이 되니까 오버플로를 완벽하게 방지할 수 있단다!”
- 토토: “크르릉 멍! (끓어 넘치는 가마솥을 피하며) 최댓값을 빼서 온도를 낮추자 멍!”
지니가 부글부글 끓어 넘쳐 nan을 유발하는 지수 함수의 오버플로 현상을 국자로 진정시키려 땀을 흘리고 있어요.
이 문제를 해결하도록 소프트맥스 함수 구현을 개선해봅시다. 다음은 개선한 수식입니다.
\[\begin{aligned} y_k = \frac{\exp(a_k)}{\sum_{i=1}^n \exp(a_i)} &= \frac{C \exp(a_k)}{C \sum_{i=1}^n \exp(a_i)} \\ &= \frac{\exp(a_k + \log C)}{\sum_{i=1}^n \exp(a_i + \log C)} \\ &= \frac{\exp(a_k + C')}{\sum_{i=1}^n \exp(a_i + C')} \hfill \text{[식 3.11]} \end{aligned}\][식 3.11]의 전개 과정을 살펴보죠. 첫 번째 변형에서는 $C$라는 임의의 정수를 분자와 분모 양쪽에 곱했습니다(양쪽에 같은 수를 곱했으니 결국 똑같은 계산입니다). 그다음으로 $C$를 지수 함수 $\exp()$ 안으로 옮겨 $\log C$로 만듭니다. 마지막으로 $\log C$를 $C’$라는 새로운 기호로 바뀝니다.
[식 3.11]이 말하는 것은 소프트맥스의 지수 함수를 계산할 때 어떤 정수를 더해도(혹은 빼도) 결과는 바뀌지 않는다는 것입니다. 여기서 $C’$에 어떤 값을 대입해도 상관없지만, 오버플로를 막을 목적으로는 입력 신호 중 최댓값을 이용하는 것이 일반적입니다. 구체적인 예를 하나 보시죠.
>>> a = np.array([1010, 1000, 990])
>>> np.exp(a) / np.sum(np.exp(a)) # 소프트맥스 함수의 계산
array([ nan, nan, nan]) # 제대로 계산되지 않는다.
>>>
>>> c = np.max(a) # c = 1010 (최댓값)
>>> a - c
array([ 0, -10, -20])
>>>
>>> np.exp(a - c) / np.sum(np.exp(a - c))
array([ 9.99954600e-01, 4.53978686e-05, 2.06106005e-09])
이 예에서 보는 것처럼 아무런 조치 없이 그냥 계산하면 nan이 출력됩니다(nan은 not a number의 약자입니다). 하지만 입력 신호 중 최댓값(이 예에서는 $c$)을 빼주면 올바르게 계산할 수 있습니다. 이를 바탕으로 소프트맥스 함수를 다시 구현하면 다음과 같습니다.
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
3.5.3 소프트맥스 함수의 특징
softmax() 함수를 사용하면 신경망의 출력은 다음과 같이 계산할 수 있습니다.
>>> a = np.array([0.3, 2.9, 4.0])
>>> y = softmax(a)
>>> print(y)
[ 0.01821127 0.24519181 0.73659691]
>>> np.sum(y)
1.0
보는 바와 같이 소프트맥스 함수의 출력은 0에서 1.0 사이의 실수입니다. 또, 소프트맥스 함수 출력의 총합은 1입니다. 출력 총합이 1이 된다는 점은 소프트맥스 함수의 중요한 성질입니다.
지니의 마법 정리
- 지니: “도로시, 소프트맥스 출력들의 모든 합은 언제나 정확하게 1.0(100%)이야! 이 중요한 성질 덕분에 출력값을 각 클래스에 속할 ‘확률’로 안심하고 해석할 수 있는 거란다.”
- 도로시: “와! 다 합치면 항상 1.0이니까, ‘이 이미지가 숫자 2일 확률이 73.6%다’라고 예쁘게 확률적 추론을 내릴 수 있는 거구나! 정말 유용해!”
- 토토: “왈왈! (꼬리를 흔들며) 100%가 딱 맞춰지니까 마법이 완전하다 멍!”
도로시가 소프트맥스 함숫값의 모든 조각을 합해 ‘합 = 1.0’ 팻말을 들고 있고, 지니와 토토가 오색 꽃가루를 날리며 이를 축하하고 있어요.
이 성질 덕분에 소프트맥스 함수의 출력을 ‘확률’로 해석할 수 있습니다.
가령 앞의 예에서 y[0]의 확률은 0.018(1.8%), y[1]의 확률은 0.245(24.5%), y[2]의 확률은 0.737(73.7%)로 해석할 수 있습니다. 그리고 이 결과 확률들로부터 “2번째 원소의 확률이 가장 높으니, 답은 2번째 클래스다”라고 할 수 있습니다. 혹은 “74%의 확률로 2번째 클래스, 25%의 확률로 1번째 클래스, 1%의 확률로 0번째 클래스다”와 같이 확률적인 결론도 낼 수 있죠. 즉, 소프트맥스 함수를 이용함으로써 문제를 확률적(통계적)으로 대응할 수 있게 되는 것이죠.
여기서 주의점으로, 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않습니다. 이는 지수 함수 $y = \exp(x)$가 단조 증가 함수이기 때문이죠.* 실제로 앞의 예에서는 a의 원소들 사이의 대소 관계가 y의 원소들 사이의 대소 관계로 그대로 이어집니다. 예를 들어 a에서 가장 큰 원소는 2번째 원소이고, y에서 가장 큰 원소도 2번째 원소입니다.
신경망의 분류에서는 일반적으로 가장 큰 출력을 내는 뉴런에 해당하는 클래스로만 인식합니다. 그리고 소프트맥스 함수를 적용해도 출력이 가장 큰 뉴런의 위치는 달라지지 않습니다. 결과적으로 신경망으로 분류할 때는 출력층의 소프트맥스 함수를 생략해도 됩니다. 현업에서도 지수 함수 계산에 드는 자원 낭비를 줄이고자 출력층의 소프트맥스 함수는 생략하는 것이 일반적입니다.
NOTE_ 기계학습의 문제 풀이는 학습과 추론 inference의 두 단계를 거쳐 이뤄집니다. 학습 단계에서 모델을 학습하고(직업 훈련을 받고), 추론 단계에서 앞서 학습한 모델로 미지의 데이터에 대해서 추론(분류)을 수행합니다(현장에 나가 진짜 일을 합니다). 방금 설명한 대로, 추론 단계에서는 출력층의 소프트맥스 함수를 생략하는 것이 일반적입니다. 한편, 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용합니다(4장 참조).
3.5.4 출력층의 뉴런 수 정하기
출력층의 뉴런 수는 풀려는 문제에 맞게 적절히 정해야 합니다. 분류에서는 분류하고 싶은 클래스 수로 설정하는 것이 일반적입니다. 예를 들어 입력 이미지를 숫자 0부터 9 중 하나로 분류하는 문제라면 [그림 3-23]처럼 출력층의 뉴런을 10개로 설정합니다.
* 옮긴이_ 단조 증가 함수란 정의역 원소 $a, b$가 $a \le b$일 때, $f(a) \le f(b)$가 성립하는 함수입니다.
그림 3-23 출력층의 뉴런은 각 숫자에 대응한다.

[그림 3-23]의 예에서 출력층 뉴런은 위에서부터 차례로 숫자 0, 1, …, 9에 대응하며, 뉴런의 회색 농도가 해당 뉴런의 출력 값을 의미합니다. 이 예에서는 색이 가장 짙은 $y_2$ 뉴런이 가장 큰 값을 출력하는 것이죠. 그래서 이 신경망이 선택한 클래스는 $y_2$, 즉 입력 이미지를 숫자 ‘2’로 판단했음을 의미합니다.