본문 바로가기
학교 공부/컴퓨터비전

밑바닥부터 시작하는 딥러닝 Chapter3-2 : 신경망

by 대니스 2024. 5. 2.

3.5 출력층 설계하기

분류 (classification)

-       데이터가 어느 클래스에 속하느냐는 문제이다

-       소프트맥스 함수를 사용한다

회귀 (regression)

-       입력 데이터에 (연속적인) 수치를 예측하는 문제이다

-       항등 함수를 사용한다

 

항등함수 (identity function)

-       입력을 그대로 출력한다

소프트맥스 함수 (softmax function)

-       소프트맥스 함수의 식은 아래와 같다

n은 출력층의 뉴런 수, yk는 그 중 k번째 출력

-       하지만 이대로 계산하면 지수함수에서 오버플로가 발생하기 때문에 아래와 같은 식으로 계산해야 오류가 나지 않는다

-       이와 같이 계산하면 오버플로를 막을 수 있다

import numpy as np

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=np.array([0.3,2.9,4.0])
y=softmax(a)
print(y)
print(np.sum(y))

-       출력의 합이 1임으로 소프트맥스 함수의 출력을 확률로 해석할 수 있다

-       소프트맥스 함수를 적용해도 지수함수는 단조 증가 함수이기 때문에 가장 큰 출력을 내는 뉴런의 위치는 달라지지 않는다. 따라서 신경망으로 분류할 때는 출력층의 소프트맥스  함수를 생략해도 된다

-       출력층의 뉴런 수는 풀려는 문제에 맞게 적절히 정해야 한다

3.6  손글씨 숫자 인식

신경망의 순전파 (forward propagation)

-       이미 학습된 매개변수를 사용하여 학습과정을 생략하고, 추론하는 과정

MNIST 데이터셋

-       기계학습 분야에서 아주 유명한 데이터셋으로, 간단한 실험부터 논문으로 발표되는 연구까지 다양한 곳에서 이용하고 있다

 

https://github.com/WegraLee/deep-learning-from-scratch

 

GitHub - WegraLee/deep-learning-from-scratch: 『밑바닥부터 시작하는 딥러닝』(한빛미디어, 2017)

『밑바닥부터 시작하는 딥러닝』(한빛미디어, 2017). Contribute to WegraLee/deep-learning-from-scratch development by creating an account on GitHub.

github.com

>       다운로드를 받고 그 안에 dataset파일을 자신의 파이썬 코드와 같은 경로에 있어야 한다

from dataset.mnist import load_mnist

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

print(x_train.shape)
print(t_train.shape)
print(x_test.shape)
print(t_test.shape)

load_mnist 함수

-        load_mnist 함수로 MNIST 데이터셋을 읽는다

-        load_mnist 함수는 읽은 MNIST 데이터를 "(훈련 이미지, 훈련 테이블),(시험 이미지, 시험 레이블)" 형식으로 변환한다

-        normalize, flatten, one_hot_label 세 가지를 설정할 수 있는 세 인수는 모두 bool 값이다     

-        normalize는 입력 이미지의 픽셀 값을 0.0~1.0 사이의 값으로 정규화할지 결정한다

-        flatten은 입력 이미지를 1차원 배열로 만들지를 결정한다. False이면 입력 이미지를 1x28x28 3차원 배열로 정한다

-       one-hot-label은 레이블을 원-핫 인코딩(one-hot-encoding) 형태로 저장할지를 결정한다

>       원-핫 인코딩은 정답을 뜻하는 원소만 1이고 나머지는 모두 0인 배열이다

 

데이터 확인 및 MNIST 이미지를 화면 표시

from dataset.mnist import load_mnist
from PIL import Image
import numpy as np

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)

print(img.shape)
img=img.reshape(28,28)
print(img.shape)

img_show(img)

-        flatten=True로 설정해 읽어 들인 이미지는 1차원 넘파이 배열로 저장되어 있기 때문에 이미지를 표시할 때는 원래 형상인 28x28 크기로 다시 변형해야 한다

-        reshape() 메서드에 원하는 형상을 인수로 저장하면 넘파이 배열의 형상을 바꿀 수 있다

-        넘파이로 저장된 이미지 데이터를 PIL용 데이터 객체로 변환해야 하며, 이 변환은 Image.fromarray()가 수행한다

 

신경망의 추론 처리

-        신경망은 입력층 뉴런을 28*28=784개, 출력층 뉴런을 0~9까지의 숫자를 구분하는 문제이기 때문에 10개로 구상한다

-         은닉층을 총 두 개로, 첫 번째 은닉층에는 50개의 뉴런을, 두 번째 은닉층에는 100개의 뉴런을 배치한다 (50과 100은 임의로 정한 값이다)

       

from dataset.mnist import load_mnist
from PIL import Image
import numpy as np
import pickle

def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False, one_hot_label=False)
    return x_test,t_test

def init_network():
    with open("sample_weight.pkl",'rb') as f: # 같은 위치에 sample_weight.pkl이 있어야 한다
        network=pickle.load(f) # pickle은 파이썬 객체 자체를 파일로 저장된 것이다

    return network

def sigmoid(x):
    return 1/(1+np.exp(-x))

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

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)))

교제에서는 0.93... 나와야하는데 오버플로인해 이와 같이 나온다

-         MNIST 데이터셋을 얻고 네트워크를 생성한다

-         for문을 돌며 x에 저장된 이미지 데이터를 1장씩 꺼내 predict()함수로 분류한다

-        이미지가 숫자 '0'일 확률이 0.1, '1'일 확률 0.3 ... 식으로 해석하여 np.argmax() 함수로 이 배열에서 값이 가장 큰 원소의 인덱스를 구한다. 즉 예측 결과를 구하는 것이다

-        신경망이 예측한 답변과 정답 레이블을 비교하여 맞힌 숫자를 세고, 전체 이미지 숫자로 나눠 정확도를 구한다

 

정규화 (normalization)

-        normalize를 True로 설정하여 0~255 범위인 각 픽셀의 값을 0.0~1.0 범위로 변환하는 것처럼 데이터를 특정 범위로 변환하는 처리를 정규화라고 한다

전처리 (pre_processing)

-        신경망의 입력 데이터에 특정 변환을 가하는 것을 전처리라고 한다

배치(batch)

-        하나로 묶은 입력 데이터를 배치라고 한다

배치 처리의 이점

-        수치 계산 라이브러리 대부분이 큰 배열을 효율적으로 처리할 수 있도록 고도로 최적화되어 있다

-        커다란 신경망에서는 데이터 전송이 병목으로 작용하는 경우가 자주 있는데, 배치 처리를 함으로써 버스에 주는 부하를 줄인다

>        이러한 이유로 큰 배열을 한꺼번에 계산하는 것이 분할된 작은 배열을 여러 번 계산하는 것보다 빠르다

from dataset.mnist import load_mnist
from PIL import Image
import numpy as np
import pickle

def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False, 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 sigmoid(x):
    return 1/(1+np.exp(-x))

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

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()

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)))

-        range() 함수는 인수가 2개인 range(start, end)처럼 인수를 2개 지정해 호출하면 start에서 end-1까지의 정수로 이뤄진 리스트를 반환하고 range(start,end,step)처럼 인수를 3개 지정하면 start에서 end-1까지 step간격으로 증가하는 리스트를 반환한다

-        x[i:i+batch_size]은 입력 데이터의 i번째부터 i+batch_size번째까지의 데이터를 묶는다

-        argmax()는 최댓값의 인수값을 가져오지만 axis=1이라는 인수를 추가하는 것으로 100x10의 배열 중 1번째 차원을 구성하는 각 원소에서 최댓값의 인덱스를 찾도록 한 것이다.

-        배치 단위로 분류한 결과를 실제 답과 비교하기 위해 ==연산자를 사용해 넘파이 배열끼리 비교하여 True/False로 구성된 bool 배열을 만들고 True의 갯수를 센다