본문 바로가기
프로그래밍 놀이터/머신러닝, AI, 딥러닝

[머신러닝] #3 신경망 #2

by 돼지왕 왕돼지 2018. 7. 4.
반응형

[머신러닝] #3 신경망 #2


"Deep Learning from Scratch” 책을 보고 제가 필요한 내용만 정리한 내용입니다.


자세한 내용은 책을 구매해서 보세요~

Accuracy, activation function, argmax, back propagation, backpropagation, batch, classification, differentiable, exp, exponentional function, flatten, forward propagation, Gradient descent, i/o 속도, identity function, INF, inference, logistic function, mnist, mnist 데이터셋, network weight update, normalize, one-hot encoding, one_hot_label, Overflow, Pickle, PIL, pre-processing, python image library, python pickle, python 피클, regression, reshape, softmax function, step function 미분, whitening, [머신러닝] #3 신경망 #2, 기계학습, 단조 증가 함수, 데이터 전송 병목, 매개변수 학습, 미분가능, 배치, 배치 성능 개선, 백색화, 분류, 분류 소프트맥스 함수, 불안정, 소프트맥스 오버플로, 소프트맥스 함수 구현, 소프트맥스 함수 구현 개선, 소프트맥스 함수 출력, 소프트맥스 함수의 특징, 소프트맥스 확률, 손글씨 숫자 인식, 손글씨 이미지 집합, 수치 예측, 시험 이미지, 신경망의 순전파, 신경망의 추론 처리, 오버플로, 원 핫 인코딩, 자연상수, 전처리, 정규화, 정확도, 추론 과정, 추론 출력 소프트맥스, 출력층 설계하기, 출력층의 뉴런 수 정하기, 평균, 표준편차, 학습 데이터, 학습 출력 소프트맥스, 학습과 추론, 항등 함수 구현, 회귀, 회귀 항등함수, 훈련 데이터, 훈련 이미지


3.5. 출력층 설계하기


-

신경망은 분류와 회귀 모두에 이용할 수 있다.

어떤 문제냐에 따라 출력층에서 사용하는 활성화 함수가 달라진다.

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



-

기계학습 문제는 분류(classification)과 회귀(regression)로 나뉜다.

분류는 데이터가 어느 클래스(class)에 속하냐는 문제이다.

사진 속 인물의 성별을 분류하는 문제가 여기에 속한다.

한편 회귀는 입력 데이터에서 (연속적인) 수치를 예측하는 문제이다.

사진 속 인물의 몸무게(57.4kg?)를 예측하는 문제가 회귀이다.




3.5.1. 항등 함수와 소프트맥스 함수 구현하기


-

항등 함수(identity function)는 입력을 그대로 출력한다.



-

분류에서 사용하는 소프트맥스 함수(softmax function)는 아래와 같다.

Accuracy, activation function, argmax, back propagation, backpropagation, batch, classification, differentiable, exp, exponentional function, flatten, forward propagation, Gradient descent, i/o 속도, identity function, INF, inference, logistic function, mnist, mnist 데이터셋, network weight update, normalize, one-hot encoding, one_hot_label, Overflow, Pickle, PIL, pre-processing, python image library, python pickle, python 피클, regression, reshape, softmax function, step function 미분, whitening, [머신러닝] #3 신경망 #2, 기계학습, 단조 증가 함수, 데이터 전송 병목, 매개변수 학습, 미분가능, 배치, 배치 성능 개선, 백색화, 분류, 분류 소프트맥스 함수, 불안정, 소프트맥스 오버플로, 소프트맥스 함수 구현, 소프트맥스 함수 구현 개선, 소프트맥스 함수 출력, 소프트맥스 함수의 특징, 소프트맥스 확률, 손글씨 숫자 인식, 손글씨 이미지 집합, 수치 예측, 시험 이미지, 신경망의 순전파, 신경망의 추론 처리, 오버플로, 원 핫 인코딩, 자연상수, 전처리, 정규화, 정확도, 추론 과정, 추론 출력 소프트맥스, 출력층 설계하기, 출력층의 뉴런 수 정하기, 평균, 표준편차, 학습 데이터, 학습 출력 소프트맥스, 학습과 추론, 항등 함수 구현, 회귀, 회귀 항등함수, 훈련 데이터, 훈련 이미지

exp(x) 는 e 의 x 승을 뜻하는 지수함수(exponentional function)이다. e 는 자연상수이다.

i 는 출력층의 뉴런 수, σ(xj) 는 그 중 j 번째 출력을 의미한다.


분모는 모든 입력 신호의 지수 함수의 합으로 구성되고, 분자는 입력 신호의 지수 함수이다.


ex)

def softmax(a): # 소스코드를 볼 때 a 가 matrix 라는 점을 생각하고 봐야 한다

    exp_a = np.exp(a)

    sum_exp_a = np.sum(exp_a)


    y = exp_a / sum_exp_a

    return y




3.5.2. 소프트맥스 함수 구현 시 주의점


-

소프트맥스 함수는 지수 함수를 사용하기 때문에 오버플로(overflow) 문제[를 봉착할 수 있다.

가령 e^10 은 20,000 이 넘고, e^100 은 0이 40개 넘는 큰 값이 되고, e^1000 은 무한대를 뜻하는 inf 가 되어 돌아온다.

그리고 이런 큰 값끼리 나눗셈을 하면 결과 수치가 ‘불안정’ 해진다.

( 컴퓨터는 수를 4바이트나 8바이트와 같이 크기가 유한한 데이터로 다루는데, 값이 컴퓨터로 표현할 수 있는 범위를 넘어서서 표현할 수 없는 문제를 '오버플로' 라고 한다.)



-

이 문제를 해결하도록 소프트맥스 함수 구현을 개선할 수 있다.


개선과정 식은 책을 참고하자.

개선의 결론은 지수 함수를 계산할 때 어떤 정수를 더하거나 빼도 결과는 바뀌지 않는다는 것이다.

어떤 값을 더하거나 빼도 되지만, 보통 오버플로를 막을 목적으로 입력 신호 중 최댓값을 이용하는 것이 일반적이다.


ex)

a = np.array([1010, 1000, 990])

np.exp(a) / np.sum(np.exp(a)) # overflow 로 nan (not a number ) 가 출력된다.


c = np.max(a)

ma = a - c

np.exp( ma ) / np.sum(np.exp( ma ))



-

개선된 softmax function 은 다음과 같다.

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. 소프트맥스 함수의 특징


-

소프트맥스 함수의 출력은 0에서 1.0 사이의 실수이다.

또, 소프트맥스 함수 출력의 총합은 1이 된다는 것은 아주 중요한 성질이다.

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


예를 들어 앞의 예에서 y[0] 의 확률은 1.8%, y[1] 의 확률은 24.5%, y[2] 의 확률은 73.7% 로 해석할 수 있다.

이 결과 확률들로부터 2번째 원소의 확률이 가장 높으니 답은 2번째 클래스라고 할 수 있다.

혹은 74% 확률로 2번째 클래스, 25% 확률로 1번째 클래스, 1% 확률로 0번째 클래스다와 같은 확률적인 결론도 낼 수 있다.


주의점은 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않는다.

이는 지수 함수 y = exp(x) 가 단조 증가 함수이기 때문이다.

실제 앞의 예에서 a 원소들의 대소관계가 y 원소들 사이의 대소 관계로 그대로 이어진다.


신경망을 이용한 분류에서는 일반적으로 가장 큰 출력을 내는 뉴런에 해당하는 클래스로만 인식한다.

그리고 소프트맥스 함수를 적용해도 출력이 가장 큰 뉴런의 위치는 달라지지 않는다.

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

현업에서도 지수 함수 계산에 드는 자원 낭비를 줄이고자 출력층의 소프트맥스 함수는 생략하는 것이 일반적이다.



-

기계학습의 문제 풀이는 학습과 추론(inference)의 두 단계를 거쳐 이뤄진다.

학습 단계에서 모델을 학습하고(직업 훈련을 받고), 추론 단계에서 앞서 학습한 모델로 미지의 데이터에 대해서 추론(분류)를 수행한다.(현장에 나가 진짜 일을 함)

방금 설명한 대로, 추론 단계에서는 출력층의 소프트맥스 함수를 생략하는 것이 일반적이다.

한편, 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용한다. (추후 다룬다.)




3.5.4. 출력층의 뉴런 수 정하기


-

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

분류에서는 분류하고 싶은 클래스 수로 설정하는 것이 일반적이다.

예를 들어 입력 이미지를 숫자 0부터 9 중 하나로 분류하는 문제라면 출력층의 뉴런을 10개로 설정해야 한다.






3.6. 손글씨 숫자 인식


-

이 예에서는 이미 학습된 매개변수를 사용하여 학습 과정은 생략하고, 추론 과정만 구현한다.

이 추론 과정을 신경망의 순전파(forward propagation)라고 한다.



-

기계학습과 마찬가지로 신경망도 두 단계를 거쳐 문제를 해결한다.

먼저 훈련 데이터(학습 데이터)를 사용해 가중치 매개변수를 학습하고, 추론 단계에서는 앞서 학습한 매개변수를 사용하여 입력 데이터를 분류한다.




3.6.1. MNIST 데이터셋


-

MNIST 는 기계학습 분야에서 아주 유명한 데이터셋으로 손글씨 숫자 이미지 집합이다.

MNIST 데이터셋은 0부터 9까지의 숫자 이미지로 구성된다.

훈련 이미지가 60,000장, 시험 이미지가 10,000장 준비되어 있다.

일반적으로 이들 훈련 이미지를 사용하여 모델을 학습하고, 학습한 모델로 시험 이미지들을 얼마나 정확하게 분류하는지를 평가한다.


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

각 이미지에 또한 7, 2, 1 과 같이 그 이미지가 실제 의미하는 숫자가 레이블로 붙어 있다.



-

mnist 파일 로드에 사용되는 인수들은 다음과 같다.

normalize 를 하면 이미지 픽셀 값을 0.0~1.0 사이 값으로 정규화한다.

flatten 은 1X28X28 대신 784 개 원소를 가진 1차원 배열로 변환한다.

one_hot_label 은 원-핫 인코딩(one-hot encoding)형태로 저장할지를 정하는데, 이는 정답을 뜻하는 원소만 1이고(hot 하고) 나머지는 모두 0인 배열이다.



-

파이썬에는 pickle(피클)이라는 편리한 기능이 있다.

이는 프로그램 실행 중에 특정 객체를 파일로 저장하는 기능이다.

(mnist 의 파일 로드에서 pickle 을 사용해서 최초에는 online 에서 받아오고 그 이후에는 pickle 을 사용한다.)



-

이미지 표시에는 PIL(Python Image Library) 모듈을 사용한다.

from PIL import Image


def img_show(img):

    pil_img = Image.fromarray(np.uint8(img))

    pil_img.show()



-

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




3.6.2. 신경망의 추론 처리


-

MNIST 의 신경망은 입력층 뉴럭을 784개(pixel 수), 출력층 뉴런을 10개(숫자 수)로 구성한다.

은닉층은 총 2개로 첫번째 은닉층에는 50개의 뉴런을, 두번째 은닉층에는 100개의 뉴런을 배치할 것이다.

여기서 50과 100은 임의로 정한 값이다.



-

get_data(), init_network(), predict() 세 개의 함수를 정의하자

def get_data(): 

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

    return x_test, t_test # t 는 정답을 표시함


def init_network():

    with open(“sample_weight.pkl”, ‘rb’) as f: # pkl 은 피클 파일, weight, bias 가 딕셔너리 변수로 저장되어 있다.

        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



-

이제 신경망에 의한 추론을 수행해보고 정확도(accuracy)를 평가해보자

x, y = 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.9352, 93.52% 의 정확도를 보인다



-

데이터를 특정 범위로 변환하는 처리를 정규화(normalization)라고 하고,

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



-

현업에서도 신경망(딥러닝)에 전처리를 활발히 사용한다.

전처리를 통해 식별 능력을 개선하고 학습 속도를 높이는 등의 사례가 많이 제시되고 있다.

앞의 예에서는 픽셀의 값을 255로 나누는 단순한 정규화를 수행했지만, 현업에서는 데이터 전체의 분포를 고려해 전처리하는 경우가 많다.

예를 들어 데이터 전체 평균과 표준편차를 이용하여 데이터들이 0을 중심으로 분포하도록 이동하거나 데이터의 확산 범위를 제한하는 정규화를 수행한다.

그 외에도 전체 데이터를 균일하게 분포시키는 데이터 백색화(whitening) 등도 있다.




3.6.3. 배치 처리


-

그림 1개를 전달하면 다음과 같이 된다.


  X          W1              W2            W3         ->     Y

784    784 X 50     50 X100    100 X 10            10



그림 100개를 넘기면 다음과 같이 된다.


  X                   W1              W2            W3         ->           Y

100 X 784    784 X 50     50 X100    100 X 10            100 X 10



-

위와 같이 하나로 묶은 입력 데이터를 배치(batch)라고 한다. 곧 묶음이란 의미이다.



-

배치 처리는 컴퓨터로 계산할 때 큰 이점을 준다.

이미지 1장당 처리 시간을 대폭 줄여준다.

성능이 좋아지는 이유는, 첫째 수치 계산 라이브러리 대부분이 큰 배열을 효율적으로 처리할 수 있도록 고도로 최적화되어 있다는 것이다.

두번째 이유는 커다란 신경망에서는 데이터 전송이 병목으로 작용하는 경우가 자주 있는데, 배치 처리를 함으로써 버스에 주는 부하를 줄일 수 있다는 것이다. (정확히는 느린 I/O 를 통해 데이터를 읽는 횟수가 줄어, 빠른 CPU 나 GPU 로 순수 계산을 수행하는 비율이 높아진다.)

즉, 배치 처리를 수행함으로써 큰 배열로 이뤄진 계산을 하게 되는데, 컴퓨터에서는 큰 배열을 한꺼번에 계산하는 것이 분할된 작은 배열을 여러 번 계산하는 것보다 빠르다.



-

앞선 예를 배치 처리한 코드는 아래와 같다.

x, t = get_data()

network = init_network()


batch_size = 100

accuracy_cnt = 0


for i in range(0, len(x), batch_size): # range(start, end, step)

    x_batch = x[i:i+batch_size]

    y_batch = predict(network, x_batch)

    p = np.argmax(y_batch, axis=1) # 1번째 차원을 구성하는 원소에서 최댓값의 인덱스를 찾음. ( 인덱스는 0부터 시작하고, 2차원 배열이기 때문에 10 에 해당하는 column 중에서 최댓값을 찾는다. )

    accuracy_cnt += np.sum(p == t[i:i+batch_size]) # 배열끼리 == 는 True/False 배열을 return 한다





3.7. 정리


-

신경망에서는 활성화 함수로 시그모이드 함수와 ReLU 함수 같은 매끄럽게 변화하는 함수를 이용한다.

넘파이의 다차원 배열을 잘 사용하면 신경망을 효율적으로 구현할 수 있다.

기계학습 문제는 크게 회귀와 분류로 나눌 수 있다.

출력층의 활성화 함수로는 회귀에서는 주로 항등 함수를, 분류에서는 주로 소프트맥스 함수를 이용한다.

분류에서는 출력층의 뉴런 수를 분류하려는 클래스 수와 같게 설정한다.

입력 데이터를 묶은 것을 배치라 하며, 추론 처리를 이 배치 단위로 진행하면 결과를 훨씬 빠르게 얻을 수 있다.





기타


-

step function vs. sigmoid  ( https://stackoverflow.com/questions/34469595/step-function-versus-sigmoid-function )


step function 은 linearly separable 한 입력에 대해 잘 작동하고, sigmoid 는 그렇지 않은 경우에도 적용 가능하다.

(나중에 다룰) back propagation 을 사용하기 위해서 activation function 은 미분가능(differentiable) 해야 한다.

미분가능을 요구하는 이유는 network weight 를 update 하기 위해 gradient descent 를 사용하기 때문이다. ( 나중에 배우겠지.. )

여튼, step function 은 미분가능하지 않아(x=0 에서 불가능하고, 그 외의 x값에서는 0이다.) backpropagation 을 적용할 수 없고, sigmoid 는 미분가능해서 backpropagation 적용이 가능하다.


그런 연유로 step function 은 잘 쓰이지 않고 sigmoid 류의 함수가 더 많이 쓰인다. ( logistic function 도 sigmoid 와 비슷한 류의 activation function 이다. )




반응형

댓글