신경망 출력 계산

신경망 각 층의 출력 계산은 행렬곱으로 처리할 수 있다.

2017/03/11 - [Coding/python] - [python] numpy, matplotlib

``python np.dot()``을 사용하면 신경망의 출력을 쉽게 계산할 수 있다.

* 행렬곱 == 스칼라곱 ( scalar product, dot product ) == 내적 ( inner product )이다.

행렬 A와 행렬 B의 각 행과 열을 행벡터, 열벡터로 보면 결국 두 벡터의 스칼라곱을 수행하는 것이기 때문.

```python

X = np.array([1, 2])

W = np.array([[1, 3, 5], [2, 4, 6]])

Y = np.dot(X, W) + B

```

층이 깊어질 수록 이를 계산하기가 난해하기 때문에, 이같은 기능을 지원하는 라이브러리가 있어야 한다.


ANN.py :

신경망은 이런식으로 생겼다.
```python
class TwoLayerNet():
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        self.params = {}
        #### W는 특정 분포를 가진 정규분포로 초기화
        self.params['W1'] = weight_init_std * \
        np.random.randn(input_size, hidden_size)
        #### b는 0으로 초기화
        self.params['b1'] = np.zeros(hidden_size)
        ...

        #### OrderedDict를 사용한다.
        self.layers = OrderedDict()
        #### Affine layer와 활성화 함수를 번갈아 가면서 사용
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['ReLU1'] = ReLU()
        ....
        self.layers['Affinen'] = Affine(self.params['Wn'], self.params['bn'])
        
        self.lastLayer = SoftmaxWithLoss()
    
    def predict(self, x):
        각 계층의 forward 호출 및 결과 리턴        
        return x

    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)

    def accuracy(self, x, t):
        정확도 계산
        return accuracy

    def gradient(self, x, t):
        역전파, 역전파 결과를 grads에 저장
        return grads
```



출력층 설계 ( 분류와 회귀 )

ML 문제는 분류(classification)와 회귀(regression)로 나뉜다.

  • 분류 : 데이터가 어느 클래스에 속하느냐에 관련된 문제.
  • 회귀 : 입력 데이터에서 (연속적인) 수치를 예측하는 문제.

예를 들어 고양이와 머핀을 구분하는 문제는 분류고, 고양이의 크기를 예측하는 문제는 회귀다.


분류냐 회귀냐에 따라 출력층에서 사용하는 활성화 함수가 달라진다.

일반적으로 분류에는 소프트맥스 함수를, 회귀에는 항등 함수를 사용한다.


소프트맥스 함수 ( softmax function )

또는 normalized exponential function이라고도 한다.

    (=             )

(n은 출력층의 뉴런 수, 는 그 중 k번째 출력을 의미한다.)


k번째 출력은 k번째 입력의 지수 함수를 모든 입력에 대한 지수 함수의 합으로 나눈 값이다.

따라서 softmax의 출력은 모든 입력값에 영향을 받는다.


```python

def softmax(x):   #x는 일차원 배열.

    return np.exp(x)/np.sum(np.exp(x))

```

브로드캐스트를 잘 이해해야 한다. 

softmax()함수의 파라미터로 np배열이 넘어간다는 것, 즉 한번만 호출되는 함수라는 것을 유의해야 한다.

  1. 분모의 np.exp(x)에서 x의 모든 원소들에 대해 exp를 계산한 np배열이 반환된다.
  2. 1에서 반환한 np배열의 합을 구한다.
  3. 분자의 np.exp(x)에서 x의 모든 원소들에 대해 exp를 계산한 np배열이 반환된다.
  4. 3에서 반환한 np배열을 2의 np배열의 합으로 나눈다.


overflow 예방

그러나 지수함수를 사용하기 때문에 오버플로 발생 가능성을 따져보아야 한다.

x에 1000만 넣어도 inf가 되어버리기 때문에, 정확한 값이 계산되리라는 보장이 없다.

그래서 약간 개선된 수식을 사용한다.


 

(C/C를 곱하고, 지수 함수니까 C를 logC로 바꿔 지수로 올릴 수 있다. 그 다음 logC를 C'로 치환.)

오버플로우를 막기위해 입력 신호 중 최댓값으로 빼준다.

```python

def softmax(x):

    x = x - np.max(x)

    return np.exp(x)/np.sum(np.exp(x))

```


그러나 이렇게 계산해도 데이터 하나가 너무 클 경우 np.sum(np.exp(x)) 값도 너무 커져서 나머지 값을 0으로 만들 가능성이 있음에 주의해야 한다. 그래서 softmax에는 normalize된 출력값을 사용해야 결과를 제대로 얻을 수 있다.


batch 처리

x가 일차원 배열이 아니라 이차원 배열의 배치 데이터라고 가정하면 np.sum(np.exp(x))는 모든 배치 데이터 중 최고 값을 뽑아 모든 배치 데이터에 적용하게 되므로, 코드를 수정해야 한다. 
각각의 배치 데이터에서 np.sum(np.exp())를 계산하도록 하는 코드.
```python
def softmax(x):
    if x.ndim == 1:
        x = x.reshape(1, x.size)

    out = np.zeros_like(x, float)

    for i in range(x.shape[0]):
        x[i] = x[i] - np.max(x[i])    
        out[i] = np.exp(x[i])/np.sum(np.exp(x[i]))

    return out
```

다음과 같은 방법도 사용 가능하다. 예제 소스코드다.
```python
def softmax2(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T 

    x = x - np.max(x)
    return np.exp(x) / np.sum(np.exp(x))
```

소프트맥스 함수의 특징

  • 소프트맥스 함수의 출력은 0 ~ 1.0 사이의 실수이며, 소프트맥수 함수 출력의 총합은 1이다.

결과적으로 다 더하면  형태가 되니까, 당연히 1이 나온다.

이 성질 덕분에 소프트맥수 함수의 출력을 '확률'로 해석할 수 있다.

확률 이론에서 소프트맥스 함수의 출력은 카테고리 분류에 사용될 수 있다. 

즉, 소프트맥스 함수의 출력은 가능한 n개의 서로 다른 출력들에 대한 확률 분포다.

  • 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않는다.
분모가 모두 같고 분자에 단조 증가 함수(여기서는 지수 함수)를 적용했기 때문이다.


신경망을 이용한 분류에서는 출력이 가장 큰 뉴런에 해당하는 클래스를 입력 데이터에 대한 정답으로 간주한다.

```python

p = np.argmax(y)

```

그런데 소프트맥스 함수를 적용해도 출력이 가장 큰 뉴런은 변하지 않는다.

결과적으로 신경망으로 분류 시는 출력층의 소프트맥스 함수를 생략하게 된다.

이는 분류 시에만 그런거고, 학습 시에는 소프트맥스 함수를 사용하게 된다.

( ML은 학습 + 추론 두 단계로 이루어져 있다. 학습 단계에서는 사용하고, 실제로 분류작업을 실행하는 추론 단계에서는 생략한다. )


  • W의 원소가 하나만 변경되어도 Softmax의 출력이 달라지게 되어 Loss값이 달라진다.
    = Softmax의 출력은 모든 입력값에 영향을 받는다.

Softmax 출력의 총 합은 항상 1이기 때문에 입력 원소의 변화는 입력 원소가 전체에서 차지하는 비율의 변화가 된다. 따라서 Softmax의 출력이 달라지게 된다.

즉 Affine - Softmax - Loss로 이어지기 때문에 Affine에서 W가 변경되면 Softmax의 출력도 변경되고, Loss도 달라지게 된다.

이 덕분에 각각의 W 원소에 대한 기울기를 구할 수 있어 학습이 제대로 이루어질 수 있다.

Softmax 계층 없이 바로 Affine - Loss로 이어진다면 어떤 W 원소의 변화가 Loss의 출력에 전혀 영향을 주지 못할 수 있으며 이 경우 그 W 원소의 기울기는 0이 나오게 된다.

출력층의 뉴런 수

출력층의 뉴런 수는 분류하고 싶은 클래스 수로 설정하는 것이 일반적이다.

0부터 9까지의 숫자를 분류해야 하는 경우, 출력은 10개가 된다.