손실 함수 ( loss function )

ANN에서는 학습을 통해 최적의 가중치 매개변수를 결정하기 위한 지표로(기준으로) 손실 함수(loss function)를 사용한다.

 

손실 함수의 결과값(오차)을 가장 작게 만드는 것이 신경망 학습의 목표이고,

손실 함수의 결과값을 작게 만들기 위해서 가중치 매개변수를 조작해 나가는 과정이 학습,

각각의 가중치 매개변수를 어디로 얼마나 조절해야 손실 함수의 결과값이 적어질지를 결정할 때 참고하는 것이 미분값(기울기)이다.

예를들어 x가 가중치 매개변수, y가 손실 함수라고 할 때, 미분이 음수면 기울기가 음수니까 x를 h만큼 증가시켰을 때 y는 감소하므로 그 가중치 매개변수를 증가시켜 손실 함수의 값을 감소시킬 수 있다.
반대로 미분이 양수면 기울기가 양수이므로 가중치 매개변수를 감소시켜 손실 함수의 값을 감소시킬 수 있다.

 

 

손실 함수로는 보통 다음 두가지를 주로 사용한다.

  • 평균 제곱 오차(mean squared error, MSE)
    회귀에서 항등 함수의 손실 함수로 사용된다.

  • 교차 엔트로피 오차(cross entroypy error, CEE)
    분류에서 소프트맥스 함수의 손실함수로 사용된다.
 

평균 제곱 오차(mean squared error, MSE)

\\[E\ =\ \frac { 1 }{ n } \sum _{ k }^{ n }{ { { (y }_{ k }-{ t }_{ k }) }^{ 2 } } \\]

는 신경망의 출력, 
는 정답 레이블을 나타낸다.
는 one-hot encoding 형식이다.
 
평균 제곱 오차는 모든 출력층 뉴런의 값이 계산에 들어간다.
 
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] 에 대해서,
best case는 y = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]인 경우이며 이 때 MSE 값은 '0'이다.
worst case는 정답이 아닌 뉴런 중 하나가 '1'이고 나머지는 '0'인 경우이며 MSE 값은 '1'이다.
그러나 정답이 아닌 뉴런들의 총합이 1인 모든 경우가 worst는 아니다.
```python
def mse(y, t):
    return 0.5 * np.sum((y-t)**2)
    # n이 아니라 2로 나눈 이유는 one-hot encoding이라 np.sum((y-t)**2)의 최댓값이 2이기 때문.
```
 

차 엔트로피 오차(cross entroypy error, CEE)

\\[E\ =\ -\sum _{ k }^{ n }{ { t }_{ k }log{ y }_{ k } }\\]

 

가 one-hot encoding 형식이기 때문에는 출력 뉴런 
들 중 정답에 해당하는 위치의 뉴런에 -log를 취한 것이 된다.
 
교차 엔트로피 오차는 정답에 해당하는 위치의 뉴런 값만 계산에 들어간다.
 
best case는 평균 제곱 오차의 경우와 같다.
정답에 해당하는 위치의 뉴런이 0에 가까워 질수록 y값이 exponential하게 증가하게 된다.
worst case는 정답 위치의 뉴런 값이 0인 경우이며 이 때 교차 엔트로피 오차는 inf다. 
 
실제로 사용할 때는 작은 값 delta(=1e-7)을 더해 inf가 발생하지 않도록 한다.
```python
def cee(y, t):
    delta = 1e-7
    return -np.sum(t*np.log(y+delta))
```
 
보통은 마지막에 softmax_cross_entropy를 사용하게 되는데, 
각각의 분류 class가 not mutually exclusive 한 경우는 sigmoid_cross_entropy를 사용한다.
not mutually exclusive라는 것은 정답이 하나가 아니라 여러개 일 수 있는 작업을 말한다.
예를 들어 사진에 포함된 모든 객체를 잡아내는 작업의 경우, 이미지에 포함된 객체로는 개도 있을 수 있고, 책상도 있을 수 있다. 이를 multilabel classification이라 한다.

 

미니배치 학습 (mini-batch training)

이전 까지는 입력 이미지 1개에 대한 출력에 손실 함수를 적용했는데, 손실 함수는 모든 훈련 데이터에 적용해서 평균값을 구해야한다. 
각각의 출력을 더해 개수만큼 나눠줘도 되겠지만, 함수 자체에서 여러개의 훈련 데이터를 처리해 이에 대한 평균값을 리턴하도록 수정하는 편이 좋다. 
이를 평균 손실 함수라고 한다.
묶은 데이터 각각에 대해 손실 함수를 구한 후 모두 더하고 데이터 개수로 나눠 정규화해주면 된다.
 
모든 훈련 데이터에 대해 손실 함수를 구하는 작업은 시간이 오래걸린다.
이 경우 데이터를 일부만 추려 전체의 근사치로 사용한다. 이 일부를 미니배치(mini-batch)라고 부르며 이런 학습 방법을 미니배치 학습이라고 한다.
*#3에서 했던 배치 처리는 많은 양의 시험 데이터를 어떻게 나눠 처리할지를 다룬 것이고, 미니배치는 훈련 데이터가 너무 많아 모두 적용할 수 없을 때 일부만 뽑아 적용하는 것을 말한다.
 
평균 손실 함수는 손실 함수가 배치 데이터 각각에 대해 연산한 결과의 평균값을 리턴하도록 약간 수정해주면 된다.
t는 one-hot-encoding 되었음에 주의.
 
평균을 계산하도록 수정한 mse와 cee. np.sum에서 브로드캐스트가 일어나 모두 더해주기 때문에 y.shape[0]으로 나눠주기만 하면 된다. 
y가 배치가 아닌 일차원 배열일 경우를 대비해서 reshape해준다.
t가 ont-hot-label일 경우 이를 일반 인덱스로 변환해서 적용한다.
```python
#loss function
def mse(y, t):
    if y.ndim == 1:
        y = y.reshape(1, y.size)
        t = t.reshape(1, t.size)
    return 0.5*np.sum((y-t)**2)/y.shape[0]
 
def cee(y, t):
    delta = 1e-7
    if y.ndim == 1:
        y = y.reshape(1, y.size)
        t = t.reshape(1, t.size)
    
    if t.size == y.size:
        t = t.argmax(axis=1)
    return -np.sum(t*np.log(y[np.arange(y.shape[0]), t]))/y.shape[0]
```
 
``python y[np.arange(y.shape[0]), t] == y[np.arange(y.shape[0])][t]``이지만 ``c [][]``를 사용하면 IndexError가 발생한다.
``c [0][2]``와 같이 직접 숫자를 지정해서 사용하면 제대로 동작하는 걸로 봐서 자체 오류가 아닌가 싶다.
 

손실 함수를 사용하는 이유, 미분

정확도를 사용하지 않고 손실 함수를 사용하는 이유는, 손실 함수는 연속적인 값을 리턴하기 때문이다.
정확도는 10000개 중 9207개를 맞췄다면 0.9207이다. 9208개를 맞추면 0.9208이고. 0.92075같은 값은 표현할 수 없다. 
그러나 손실 함수를 사용하면 가능하다. 
중요한건 맞추느냐를 따질 것이냐, 못맞추느냐를 따질 것이냐가 아니라 연속적인 값을 표현할 수 있느냐다.
 
정확도를 사용하면 그래프가 계단식으로 그려지므로 대부분의 구간에서 기울기가 0이다. 그래서 손실 함수를 사용하는 것.
계단 함수를 사용하지 않는 이유도 이와 같다.