티스토리 뷰

AI/밑바닥부터 시작하는 딥러닝

신경망(2)

취뽀가자!! 2018. 9. 9. 20:51

신경망 2

출력층 설계하기

항등 함수

항등함수는 입력과 출력이 같은 함수입니다. 항등함수는 회괴에서 주로 사용합니다.

소프트맥스 함수

소프트맥스는 분류에서 사용합니다. 식은 아래와 같습니다.

exp(x)는 자연상수 e를 x제곱한 지수 함수 입니다. n은 출력층의 뉴런 수, y는 그중 k번째 출력임을 뜻합니다. 분자는 입력신호 a의 지수 함수, 분모는 모든 입력 신호의 지수 함수의 합으로 구성됩니다.


소프트맥스 함수를 구현해 보겠습니다. 

1
2
3
4
5
6
7
8
import numpy as np
 
def softmax(a):
    exp_a=np.exp(a)
    sum_exp_a=np.sum(exp_a)
    y=exp_a/sum_exp_a
 
    return y
cs

그런데 여기서 방금 구현한 softmax()함수는 식을 제대로 표현하고 있지만. 컴퓨터에서는 오버플로우 문제때문에 저 식 그대로 사용하면 안 됩니다. 따라서 이 문제를 해결하기 위해 소프트맥스 식을 개선해 보겠습니다.

식에 대한 설명을 해 보자면, 첫 번째 변형에서는 C라는 임의의 정수르 ㄹ분자와 분모 양쪽에 곱했습니다. 그 다음 C를 지수 함수 exp()안으로 옮겨 logC로 만듭니다. 마지막으로 logC를 C'라는 새로운 기호로 바꾼 것입니다.


여기서 중요한 점은 소프트맥스의 지수 함수를 계산할 때 어떤 정수를 더하거나 빼도 결과값은 변하지 않는다는 것입니다. C'에는 어떤 값을 대입해도 상관없지만 오버플로우를 막기 위해서는 일반적으로 입력 신호 중 최댓값을 이용하는 것이 일반적입니다. 코드를 한번 보겠습니다.

1
2
3
4
5
6
7
8
>>> a=np.array([1010,1000,999])
>>> np.exp(a)/np.sum(np.exp(a))
array([nan, nan, nan])
>>> c=np.max(a)
>>> a-c
array([  0-10-11])
>>> np.exp(a-c)/np.sum(np.exp(a-c))
array([9.99937902e-014.53971105e-051.67006637e-05])
cs

위 코드를 이용해 softmax()함수를 다시 짜보시기 바랍니다.

소프트맥스의 특징

소프트맥스의 출력은 0에서 1.0사이의 실수입니다. 그리고 출력의 총합은 1입니다. 총합이 1이 된다는 것은 출력의 결과를 확률로 볼 수 있기 때문에 매우 중요한 특징입니다. 즉, 소프트맥스를 이용함으로써 문제를 확률적(통계적)으로 대응할 수 있게 되는 것입니다.

그런데 소프트맥스를 사용할 때는 주의할 점이 있습니다. 소프트맥스는 사용해도 각 원소의 대소관계는 변하지 않습니다. 이는 y=exp(x)가 단조 증가 함수이기 때문이죠.

신경망을 이용한 분류에서는 일반적으로 가장 큰 출력을 내는 뉴런에 해당하는 클래스로만 인식합니다. 그리고 소프트맥스 함수를 적용해도 출력이 가장 큰 뉴런의 위치는 달라지지 않습니다. 결과적으로 신경망으로 분류할때는 출력층의 소프트맥스 함수를 생략해도 됩니다.(학습 단계에서만 사용하면 됨)

출력층의 뉴런 수 정하기

출력층의 뉴런 수는 폴려는 문제에 맞게 적절히 정해야 합니다. 분류에서는 분류하고 싶은 클래스 수로 설정하는 것이 일반적입니다.


위 그림에서 출력층 뉴런은 위에서부터 차례로 숫자 0~9에 대응하며, 위 그림에서는 y2가 가장 큰 값을 출력했기에 이 신경망이 선택한 클래스는 y2, 즉 입력 이미지를 숫자 2로 판단했음을 의미합니다.


손글씨 숫자 인식

MNIST 데이터셋

MNIST의 이미지 데이터는 28*28 크기의 회색조 이미지(1채널)이며, 각 필셀은 0에서 255까지 값을 취합니다.

아래는 데이터셋을 내려받아 이미지를 넘파이 배열로 변환해주는 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image
 
 
def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()
 
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
 
img = x_train[0]
label = t_train[0]
print(label)  # 5
 
print(img.shape)  # (784,)
img = img.reshape(2828)  # 형상을 원래 이미지의 크기로 변형
print(img.shape)  # (28, 28)
 
img_show(img)
cs

Image.fromarray()는 너파이로 저장된 이미지 데이터를 PIL용 데이터 객체로 변환하는 함수입니다.


신경망의 추론 처리

입력이 784개, 은닉층이 총 2개, 출력층은 10개인 신경망을 구성하겠습니다. 은닉층에서 첫 번째 은닉층은 50개의 노드를, 두 번째 은닉층은 100개의 뉴런을 배치할 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax
 
 
def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test
 
 
def init_network():
    with open("sample_weight.pkl"'rb') as f:
        network = pickle.load(f)
    return network
 
 
def predict(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']
 
    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = softmax(a3)
 
    return y
 
 
x, t = get_data()
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i])
    p= np.argmax(y) # 확률이 가장 높은 원소의 인덱스를 얻는다.
    if p == t[i]:
        accuracy_cnt += 1
 
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
 
cs

init_network()에서는 pickle 파일인 sample_weight.pkl에 저장된 학습된 가중치 매개변수를 읽습니다. 이 파일에는 가중치와 편향 매개변수가 딕셔너리 변수로 저장되어 있습니다.


load_mnist()에서 normalize를 True로 설정해 줌으로서 0~255 범위인 각 필셀의 값을 0.0~1.0 범위로 변환해 정규화 시켜줍니다. 그리고 신경마의 입력 데이터에 특정 변환을 가하는 것을 전처리라고 합니다 .


배치 처리

배치는 여러개로 나눠져 있는 데이터들을 하나로 묶은 것을 말합니다. 배치를 사용하는 이유는 크게 두 가지가 있습니다. 첫 번째는 수치 계산 라이브러리 대부분이 큰 배열을 효율적으로 처리할 수 있도록 고도로 최적화되어 있기 때문입니다. 두 번째는 커다란 신경망에서는 데이터 전송이 병목으로 작용하는 경우가 자주 있는데, 배치 처리를 함으로써 버스에 주는 부하를 줄인다는 것입니다.(정확히는 느린 I/O를 통해 데이터를 읽는 횟수가 줄어, 빠른 CPU나 GPU로 순수 계산을 ㅎ수행하는 비율이 높아집니다). 

그럼 배치 처리를 구현해 보겠습니다.(위 코드에서 달라지는 부분만 수정)
1
2
3
4
5
6
7
8
9
10
11
12
x, t = get_data()
network = init_network()
 
batch_size=100 #배치 크기
accuracy_cnt = 0
for i in range(0,len(x),batch_size):
    x_batch=x[i:i+batch_size]
    y_batch=predict(network,x_batch)
    p=np.argmax(y_batch,axis=1)
    accuracy_cnt+=np.sum(p==t[i:i+batch_size])
 
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
cs

여기서 argmax()는 최댓값의 인덱스를 가져옵니다. 인수로 준 axis=1은 100*10의 배열 중 1번째 차원을 구성하는 각 원소에서(1번째 차원을 축으로) 최댓값의 인덱스를 찾도록 한 것입니다. 아래는 예시입니다.

1
2
3
4
>>> import numpy as np
>>> x=np.array([[0.1,0.8,0.1],[0.3,0.1,0.6],[0.2,0.5,0.3],[0.8,0.1,0.1]])
>>> print(np.argmax(x,axis=1))
[1 2 1 0]
cs



'AI > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글

오차역전파(2)  (0) 2018.09.18
오차역전파법(1)  (0) 2018.09.17
신경망 학습  (0) 2018.09.13
신경망(1)  (0) 2018.09.03
퍼셉트론  (0) 2018.08.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함