신경망을 쓰는 이유
1개 이상의 은닉층을 가진 (polynomial이 아닌 비선형 활성 함수여야 함) 뉴럴 네트워크는 어떠한 연속 함수든 근사할 수 있다는 이론인 Universal approximation theorem이 있습니다.
이를 위해서 "비선형 활성함수"를 가지는 뉴럴 네트워크를 사용하는 것이고요, 선형 활성함수는 아무리 여러 층을 쌓아도 한층만 쌓고 선형 활성함수를 씌운 것과 결과적으로 같게 됩니다.
은닉층을 "깊게" 쌓는 이유는 데이터로부터 특성을 더 잘 잡아내기 위해서 입니다. Visualizing and Understanding Convolutional Networks라는 유명한 논문의 figure를 보시면 각 은닉층마다 잡아내는 피쳐들이 다른 것을 확인하실 수 있습니다.
라고 tensorflow 포럼 같은곳에 올라와 있다.
활성화 함수
위 함수처럼 입력신호의 총합을 출력신호로 변환하는 함수를 활성화 함수라고 한다.
(입력 신호가 얼마나 활성화를 일으키는지 이런 식으로 기억하면 될 듯)
이 활성화 함수를 통해 2단계를 만들어 보면
활성화 함수는 퍼셉트론에서 신경망으로 가는 길잡이라고 한다.
위 활성화 함수는 임계값(θ)을 경계로 출력이 바뀌는데 이것을 계단 함수라고 한다.
=> 퍼셉트론에서는 활성화 함수로 계단 함수를 이용한다.
시그모이드 함수
신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 다음 뉴런에 전달한다.
계단함수 구현
1
2
3
4
5
|
def step_function(x):
if x > 0:
return 1
else:
return 0
|
cs |
이렇게하면 실수만 입력값을 받는다.
하지만 배열을 인수로 받을수 없기때문에
1
2
3
|
def step_function(x):
y = x > 0
return y.astype(np.int)
|
cs |
배열의 자료형을 변환할때 astype이라는 매서드를 사용한다
위 시그모이드와 계단함수를 비교해보면
모양은 같으나 계단함수는 비연속, 시그모이드는 연속함수라는 것을 알 수 있다.
이외에도 활성화 함수로 ReLU 함수가 있다. 요즘엔 이걸 많이 사용한다고 한다.
1
2
|
def relu(x):
return np.maximum(0, x)
|
cs |
함수로 구현~
다차원 배열의 계산 (np.dot(x,y))
3층 신경망 구현
A = (a1, a2, a3) , X=(x1,x2), B = (b1, b2, b3)
W = w11, w21, w31
w12, w12, w32
이것을 식으로 구현하면
X = np.array([1.0, 0.5])
W1 = np.array([0.1, 0.3, 0.5], [0.2, 0.4, 0.6])
B1 = np.array([0.1, 0.2, 0.3])
A1 = np.dot(X, W1) +B1
으로 된다.
이것을 활성화 함수 처리하기위해 시그모이드 함수에 위 값을 넣는다
Z1 =sigmoid(A1)
print(A1) # 출력 [0.3, 0.7, 1.1]
print(Z1) # 출력 [0.57.... , 0.6681... , 0.7502...]
2층도 마찬가지로 같은데
마지막층은 다르다.
수식으로 표현하면
A3 = np.dot(Z2, W3) +B3
Y = identity_function(A3) # Y = A3
마지막에 시그모이드를 사용하지 않고 항등함수를 사용하는데 전과 마찬가지인 흐름(같은 과정을 만들기 위함인것 같다.)을 가지기 위해서 사용한다.
출력층 설계
신경망은 분류(classification), 회귀(regresstion)에 사용되는데 이것은 마지막 출력층에서 사용하는 활성화 함수에 따라 달라진다. 회귀에는 항등함수, 분류에는 소프트맥스 함수를 사용한다.
여기서 회귀는 과거의 값을 통해 어떤 시점의 값을 예측하는것이고, 분류는 특정값을 분류(ex 성별)를 하는 것이다.
항등함수는 위에서 봤듯이 y=x 이런식인것을 알았고
소프트 맥스함수를 보면
exp는 전에 말했던대로 지수함수 이고 n은 출력층의 뉴런수, yk는 그중 k 번째 출력이라는 뜻이다.
분자는 입력신호(ak)의 지수함수, 분모는 모든 입력신호의 지수함수의 합으로 구성된다.
1
2
3
4
5
6
|
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
|
cs |
이것을 함수로 표현
이런식으로 하면 될것 같지만 지수함수가 큰값을 출력하여 오버플로를 일으킨다.
이것을 개선하기위해 C라는 상수를 넣어준다.
1
2
3
4
5
6
7
|
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
|
cs |
이것을 코드로 구현하면 이런식으로 된다.
소프트맥스 함수의 특징
- 출력은 0~1.0 사이의 실수
- 출력의 총합은 1 (이 성질때문에 출력을 확률로 해석)
- 주의점: 대소관계는 변하지 않는다. (ex. a에서 가장큰원소는 2번째면, y에서 가장큰 원소도 2번째이다.
신경망을 이용한 분류에서는 일반적으로 가장 큰 출력을 내는 뉴런에 해당하는 클래스로만 인식한다.
그리고 함수를 적용해도 출력이 가장 큰 뉴런의 위치는 달라지지 않는다.
그래서 소프트맥스함수를 생략해도 된다. ( 그래서 현업에서 자원낭비를 방지하기위해서 생략하는경우가 일반적이다.
출력값 : 분류에서는 분류하고 싶은 클래스 수로 설정한다
손글씨 숫자 인식
이번에는 이미 학습된 매개변수를 사용하여 학습과정은 생략하고 추론과정만 구현 할것이다.
이 추론과정을 순전파(forward propagation)라고한다.
- 기계 학습과 마찬가지로 신경망도 두단계를 거쳐 문제 해결한다.
훈련데이터를 사용해 가중치 매개변수를 학습하고, 추론 단계에서는 앞서 학습한 매개변수를 사용하여 입력데이터 를 분류한다.
MNIST 데이터셋
0~9 까지의 숫자이미지로 구성된 데이터셋이다.
교재에서 제공하는 MNIST데이터셋을 이용해보았다.
load_mnist 함수를 설명하자면
normalize : 입력이미지의 픽셀값을 0~1사이의 값으로 정규화할지 (false는 0~255)
flatten : 입력이미지를 평탄하게 즉 1차원으로 만들지 정함(false는 3차원)
one_hot_label: 원-핫인코딩 형태로 저장할지 정함(ex. [0,0,0,1,0,0,0,0,0] 에서 정답만 1인 배열)
출력이 잘되는것을 확인 하였다
그럼 이제 데이터 셋을 통해 추론을 수행하는 신경망을 구성하려고 한다.
이 신경망은 입력층 뉴런 784개, 출력층 뉴런 10개로 구성된다.
784개인 이유는 이미지 크기가 28X28=784, 10개인 이유는 출력숫자가 0~9까지 있기 때문이다.
은닉층은 총 두개로 첫 번째 은닉층에는 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
|
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)
|
cs |
여기서 init_network()는 pickle 파일인 sample_weight.pkl에 저장된 '학습된 가중치 매개변수'를 읽는다.
이 파일에는 가중치와 편향 매개변수가 딕셔너리 변수로 저장되어 있다.
이제 신경망에 의한 추론을 수행해 보고 정확도 평가를 해보자
과정을 살펴보면
MNIST 데이터셋을 얻고 network 생성한다.
for문에서 이미지 데이터를 한장씩 꺼내어 predict 함수로 분류한다.
( predict는 각 레이블의 확률을 넘파이 배열로 넘긴다.)
np.argmax() 함수(예측결과)를 통해 배열에서 가장큰 원소의 인덱스를 구한다.
마지막으로 예측한 답변과 정답레이블을 비교하여 맞춘 숫자를 세고 이를 전체 이미숫자로 나눠 정확도를 구한다.
정규화(normalization) : 데이터를 특정 범위로 변환하는 처리
전처리(pre-processing) : 신경망의 입력데이터에 특정한 변환 가하는 것. 이를통해 식벽능력 개선, 학습속도 향상
백색화(whitening) : 전체 데이터를 균일하게 분포 시키는 것
배치처리
입력데이터와 가중치 매개변수의 '형상'을 보자
이처럼 하나로 묶은 입력데이터를 배치(batch)라고 한다.
- 배치처리 : 이미지 1장당 처리시간을 줄여주는 것.
계산 라이브러리 대부분이 큰배열을 효율적으로 처리할수 있도록 되어 있고,
커다란 신경망에서는 데이터 전송이 병목으로 작용하는 경우가 자주 있는데 이때 배치처리를 함으로써 버스에 주는 부하를 줄일수 있다.(느린 I/O를 이용하여 데이터 읽는 횟수가 줄어, cpu나 gpu에서 계산하는 비율을 높인다!)
즉, 큰배열을 한꺼번에 계산하는것이 분할된 작은 배열을 연산하는것보다 훨씬 효율적이다.
코드를 보면
for문에서 batch_size만큼 데이터를 묶어서 값을 넘겨주고
argmax를 통해 최댓값의 인덱스를 찾도록 하여 그 인덱스와 같은것을 찾아 맞으면 더해준값을 통해 정확도를 계산한다.
신경망의 순전파를 보았는데 퍼셉트론과 뉴런에서 뉴런으로 정보를 전달한다는 것은 같다는 것을 알수 있었다.
하지만 계단함수, 시그모이드 함수라는 다른 활성 함수를 사용한다는 점에서 달랐다.
단순히 함수에 어떤 것을 넣느냐에 따라 모델이 달라지는건지..
전체적인 흐름은 비슷하였다.
여기서 정확도를 올리려면 그냥 함수만 바꾸면 될것같은데 어떻게 될까나...
'DATA Science > DeepLearing from scratch' 카테고리의 다른 글
신경망 학습 - 학습 알고리즘 구현 (0) | 2021.05.13 |
---|---|
신경망 학습 (2) | 2021.05.12 |
퍼셉트론 (0) | 2021.05.11 |
Matplotlib - 그래프 (0) | 2021.05.11 |
Numpy - 행렬연산 (2) | 2021.05.11 |
댓글