• 카테고리
  • 멤버
  • 아티클
  • 카테고리
  • 멤버
  • 깃헙
© 2020 COSADAMA,
All Rights Reserved.
Deep-Learning

Deep Learning(밑바닥부터 시작하는 딥러닝1)

by 나다경

Chapter2 신경망

가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력이 신경망의 중요한 성질임.

neuro

가장 왼쪽 줄을 입력층, 맨 오른쪽 줄을 출력층, 중간 줄을 은닉층이라고 함

3.1 퍼셉트론에서 신경망으로

$$ y = 0 (w1x1 + w2x2) <= theta $$ $$ y = 1 (w1x1 + w2x2) > theta $$ 을 간결한 형태로 표현하면

$$ y = h(b + w1x1 + w2x2) $$ $$ h(x) = 0 (x <= 0 ) $$ $$ h(x) = 1 (x > 0) $$

3.2 활성화 함수

여기서 $h(x)$라는 함수는 입력 신호의 총합을 출력 신호로 변환하는 함수이며 활성화 함수라고 함.

위의 활성화 함수는 임계값을 경계로 출력이 바뀌는데, 이를 계단 함수라고 함.

따라서 퍼셉트론에서는 활성화 함수로 계단 함수를 이용한다. 라 할 수 있음.

시그모이드 함수는 신경망에서 자주 이용하는 활성화 함수임.

$S(x) = \frac{1}{1 + e^{-x}}$

신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달함.

퍼셉트론과 신경망의 주된 차이는 활성화 함수 뿐임.

계단 함수 구현하기

def step_function(x):
    if x > 0 :
        return 1
    else:
        return 0

넘파이 배열 지원하기 위해 아래와 같이 수정함

def step_function(x):
    y = x > 0
    return y.astype(np.int)
import numpy as np
x = np.array([-1.0, 1.0, 2.0])
print(x) # [-1.  1.  2.]
y = x > 0
print(y) # [False  True  True]
y = y.astype(np.int)
print(y) # [0 1 1]
import numpy as np
import matplotlib.pylab as plt

def step_function(x):
    return np.array(x > 0, dtype = np.int)

x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x,y)
plt.ylim(-0.1, 1.1)
plt.show()

stepfunction

시그모이드 함수 구현하기

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
x = np.array([-1.0, 1.0, 2.0])
sigmoid(x) # array([0.26894142, 0.73105858, 0.88079708])
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x,y)
plt.ylim(-0.1, 1.1)
plt.show()

sigmoidfunction

시그모이드 함수와 계단 함수 비교

  • 차이점

시그모이드 함수는 부드러운 곡선이며 입력에 따라 출력이 연속적으로 변화함.

계단 함수는 0을 경계로 출력이 갑자기 바뀜.

퍼셉트론에서는 뉴런 사이에 0 혹은 1이 흘렀다면, 신경망에서는 연속적인 실수가 흐름.

  • 공통점

계단 함수와 시그모이드 함수는 입력이 중요하지만 큰 값을 출력하고 입력이 중요하지 않으면 작은 값을 출력함.

입력이 아무리 작거나 커도 출력은 0에서 1사이임.

비선형 함수임.

ReLU 함수

ReLU는 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력하는 함수임.

$$ h(x) = x (x > 0) $$ $$ h(x) = 0 (x <= 0) $$

def relu(x):
    return np.maximum(0,x)

3.3 다차원 배열의 계산

다차원 배열

import numpy as np
A = np.array([1, 2, 3, 4])
print(A) # [1 2 3 4]
# 차원 수
np.ndim(A)  # 1
# 배열의 형상
A.shape # (4,)
A.shape[0] # 4
B = np.array([[1,2], [3,4], [5,6]])
print(B) 
#[[1 2]
 [3 4]
 [5 6]]
np.ndim(B) # 2
B.shape # (3, 2)

2차원 배열은 특히 행렬이라고 함.

배열의 가로 방향을 행, 세로 방향을 열이라고 함.

행렬의 곱

np.dot()으로 계산함

A = np.array([[1,2],[3,4]])
A.shape # (2,2)
B = np.array([[5,6],[7,8]])
B.shape # (2,2)
np.dot(A, B) 
# array([[19, 22],
       [43, 50]])
A = np.array([[1,2,3],[4,5,6]])
A.shape # (2, 3)
B = np.array([[1,2],[3,4],[5,6]])
B.shape # (3, 2)
np.dot(A,B)
# array([[22, 28],
       [49, 64]])
C = np.array([[1,2],[3,4]])
C.shape
# (2, 2)
A.shape # (2, 3)
np.dot(A,C) # 에러 shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)

행렬 A의 1번째 차원의 원소 수(열 수)와 행렬 B의 0번째 차원의 원소 수(행 수)가 같아야만 함.

A = np.array([[1,2],[3,4],[5,6]])
A.shape # (3, 2)
B = np.array([7,8])
B.shape # (2,)
np.dot(A,B) # array([23, 53, 83])

신경망에서의 행렬 곱

X = np.array([1,2])
X.shape # (2,)
W = np.array([[1,3,5],[2,4,6]])
print(W)
# [[1 3 5]
 [2 4 6]]
W.shape # (2, 3)
Y = np.dot(X,W)
print(Y) # [ 5 11 17]

3.4 3층 신경망 구현하기

# 입력층 -> 1층
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])

print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X,W1) + B1

활성화 함수로 시그모이드 함수 사용

Z1 = sigmoid(A1)
print(A1) # [0.3 0.7 1.1]
print(Z1) # [0.57444252 0.66818777 0.75026011]
# 1층 -> 2층
W2 = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])
B2 = np.array([0.1,0.2])

print(Z1.shape) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
A2 = np.dot(Z1,W2) + B2
Z2 = sigmoid(A2)
# 2층 -> 출력층
def identity_function(x):
    return x

W3 = np.array([[0.1,0.3],[0.2,0.4]])
B3 = np.array([0.1,0.2])

A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3)

구현 정리

def init_network():
    network = {}
    network['W1'] = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
    network['b1'] = np.array([0.1,0.2,0.3])
    network['W2'] = np.array([[0.1,0.4],[0.5,0.5],[0.3,0.6]])
    network['b2'] = np.array([0.1,0.2])
    network['W3'] = np.array([[0.1,0.3],[0.2,0.4]])
    network['b3'] = np.array([0.1,0.2])
    
    return network

def forward(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 = identity_function(a3)
    
    return y

network = init_network()
x = np.array([1.0,0.5])
y = forward(network, x)
print(y) # [0.32138828 0.70996271]

3.5 출력층 설계하기

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

항등 함수 : 입력을 그대로 출력함, 출력층에서 항등함수를 사용하면 입력 신호가 그대로 출력 신호가 됨.

소프트맥스 함수 : 소프트맥스의 출력은 모든 입력 신호로부터 화살표를 받음. 출력층의 각 뉴런이 모든 입력 신호에서 영향을 받음.

a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a) # 지수 함수
print(exp_a) # [ 1.34985881 18.17414537 54.59815003]
sum_exp_a = np.sum(exp_a)
print(sum_exp_a) # 74.1221542101633
y = exp_a / sum_exp_a
print(y) # [0.01821127 0.24519181 0.73659691]
def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

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

오버플로 문제가 발생 -> $C$라는 임의의 정수를 분자와 분모 양쪽에 곱하고 지수함수 안으로 옮겨 $log C$로 만든 후 $C'$이라는 새로운 기호로 만듦

  • 소프트맥스의 지수 함수를 계산할 때 어떤 정수를 더하거나 빼도 결과는 바뀌지 않음
  • $C'$에 어떤 값을 대입해도 상관없지만, 오버플로우 방지를 위해 입력 신호 중 최댓값을 이용하는 것이 일반적임
a = np.array([1010, 1000, 990])
np.exp(a) / np.sum(np.exp(a)) # 소프트맥스 함수의 계산 # array([nan, nan, nan])

제대로 계산되지 않음

c = np.max(a) 
a - c # array([  0, -10, -20])
np.exp(a-c)/np.sum(np.exp(a-c)) # array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09])
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) # [0.01821127 0.24519181 0.73659691]
  • 소프트맥스 함수의 출력은 0에서 1.0 사이의 실수임.
np.sum(y) # 1.0
  • 소프트맥스 함수 출력의 총합은 1임

  • 소프트맥스 함수의 출력을 확률로 해석할 수 있음. 즉, 소프트맥스 함수를 이용함으로써 문제를 확률적(통계적)으로 대응할 수 있게 됨.

  • 주의점

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

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

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

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

3.6 손글씨 숫자 인식

import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist

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

# 각 데이터의 형상 출력
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)

load_mnist 함수는 읽은 MNIST 데이터를 (훈련 이미지, 훈련 레이블), (시험 이미지, 시험 레이블) 형식으로 반환함.

  • normalize : 입력 이미지의 픽셀 값을 0.0 ~ 1.0 사이의 값으로 정규화할지를 정함
  • flatten : 입력 이미지를 평탄하게, 1차원 배열로 만들지를 정함
  • one_hot_label : 레이블을 원-핫 인코딩 형태로 저장할지를 정함
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image

import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image # 5
print(img.shape) # (784,)
img = img.reshape(28,28)

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

신경망의 추론 처리

입력층 뉴런을 784개(이미지 크기가 28x28)로 구성하고 출력층 뉴런을 10개(0~9까지의 숫자를 구분)로 구성함

import pickle

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

init_network()에서는 pickle 파일인 sample_weight.pkl에 저장된 학습된 가중치 매개변수 를 읽음

가중치와 편향 매개변수가 딕셔너리 변수로 저장되어 있음

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))) # Accuracy:0.9352

predict() 함수는 각 레이블의 확률을 넘파이 배열로 반환함

np.argmax() 함수로 이 배열에서 값이 가장 큰(확률이 가장 높은) 원소의 인덱스를 구함

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

정규화 : 데이터를 특정 범위로 변환하는 처리

전처리 : 신경망의 입력 데이터에 특정 변환을 가하는 것

배치 처리

# 가중치 형상
x, _ = get_data()
network = init_network()
W1, W2, W3 = network['W1'], network['W2'], network['W3']

x.shape # (10000, 784)
W1.shape # (784, 50)
W2.shape # (50, 100)
W3.shape # (100, 10)

다차원 배열의 대응하는 차원의 원소 수가 일치함을 확인할 수 있음.

원소 784개로 구성된 1차원 배열이 입력되어 마지막에는 원소가 10개인 1차원 배열이 출력되는 흐름임.

이미지 100장을 한꺼번에 입력하는 경우 입력 데이터의 형상은 100x784, 출력 데이터의 형상은 100x10임.

배치 : 하나로 묶은 입력 데이터

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))) # Accuracy: 0.9352

x[i:i+batch_size]은 입력 데이터의 i번째부터 i+batch_size번째까지의 데이터를 묶는다는 의미임 e.g)x[0:100], x[100:200]

argmax()에 axis=1 인수를 추가한 것은 100x10 배열 중 1번째 차원을 구성하는 각 원소에서 최댓값의 인덱스를 찾도록 한 것임

데이터를 배치로 처리함으로써 효율적이고 빠르게 처리 가능

Previous Post
Deep Learning(밑바닥부터 시작하는 딥러닝1)
Next Post
nlp-intro2
  • 코사다마
  • 소개
  • 영입
  • 관련 사이트
  • 홈페이지
  • 커리큘럼
  • 깃헙
  • 문의
  • 이메일
  • 채널톡
  • 카카오톡
  • Built with ⛰ by Peniel Cho