[밑시딥 1] Chapter 3. 신경망

Seong Woong Kim·2023년 11월 14일

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

퍼셉트론의 한계점은 가중치를 설정하는 작업 (원하는 결과를 출력하도록 가중치 값을 적절히 정하는 작업)은 여전히 사람이 수동으로 한다는 것

신경망은 위 문제를 해결해준다.

  • 가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력이 있다.

3.1.1 신경망의 예

💡 이 책에서는 입력층에서부터 출력층 방향으로 차례로 0층, 1층, 2층이라고 함.

  • 그림에서 0층이 입력층, 1층이 은닉층, 2층이 출력층이 됨.
  • 은닉층의 뉴런은 (입력층과 출력층과 달리) 사람 눈에 보이지 않음
    • "은닉"

💡 위 그림의 신경망은 모두 3층으로 구성되지만, 가중치를 갖는 층은 2개이기 때문에 '2층 신경망'이라고도 함

  • 책에서는 실제로 가중치를 갖는 층의 개수 (입력층, 은닉층, 출력층의 합계에서 1을 뺀 값)을 기준

3.1.2 퍼셉트론 복습

편향을 명시한 퍼셉트론의 입력 신호를 수식을 하나의 함수로 표현하면 다음과 같다.

y=h(b+w1x1w2x2)y = h(b\, + \, w_1x_1 \, w_2x_2)
h(x)={0,  (x0)1,  (x>0)h(x) = \begin{cases} 0,\; (x \leq 0) \\ 1,\; (x \gt 0) \end{cases}
  • 첫번째 식은 입력 신호의 총합이 h(x)h(x)라는 함수를 거쳐 변환되어, 그 변환된 값이 y의 출력이 됨을 보여줌
  • 두번째 식은 h(x)h(x) 함수가 입력이 0을 넘으면 1을 반환하고 그렇지 않으면 0을 반환한다.

결과적으로 위 2개의 식이 하는 일은 동일하다.


3.1.3 활성화 함수의 등장

입력 신호의 총합을 출력 신호로 변환하는 함수를 일반적으로 활성화 함수activation  function^{activation \;function}이라고 함.

  • 활성화 함수는 입력 신호의 총합이 활성화를 일으키는지를 정하는 역할을 함.

가중치가 곱해진 입력 신호의 총합을 계산하고, 그 합을 활성화 함수에 입력해 결과를 내는 2개의 식을 적으면 다음과 같다.

a=b  +  w1x1  +  w2x2a = b \;+ \; w_1x_1 \;+\;w_2x_2
y=h(a)y = h(a)
  • 가중치가 달린 입력 신호와 편향의 총합을 계산하고 이를 aa라고 한다.
  • 그리고 aa를 함수 h()h()에 넣어 y를 출력하는 흐름이다.

💡

  • 일반적으로 단순 퍼셉트론은 단층 네트워크에서 계단 함수(임계값을 경계로 출력이 바뀌는 함수)를 활성화 함수로 사용한 모델을 가리킴
  • 다층 퍼셉트론은 신경망(여러 층으로 구성되고 시그모이드 함수 등의 매끈한 활성화 함수를 사용하는 네트워크)를 가리킴


3.2 활성화 함수

계단 함수step  function^{step \; function}

  • 임계값을 경계로 출력이 바뀌는 함수
  • 퍼셉트론에서는 활성화 함수로 계단 함수를 이용한다. 라고 할 수 있음

활성화 함수를 계단 함수에서 다른 함수로 변경하는 것이 신경망의 세계로 나아가는 열쇠

3.2.1 시그모이드 함수

신경망에서 자주 이용하는 활성화 함수인 시그모이드 함수sigmoid  function^{sigmoid \; function}의 식은 다음과 같다.

h(x)=11+exp(x)h(x) = \frac{1}{1\,+\,exp(-x)}
  • exp(-x): exe^{-x}를 의미
  • e: 자연상수로 2.7182..의 값을 갖는 실수

시그모이드 함수에 1.0과 2.0을 입력하면 hh(1.0) = 0.731 ... hh(2.0) = 0.880..처럼 특정 값으을 출력함

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

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

3.2.2 계단 함수 구현

def step_function(x):
	y = x > 0
    # numpy 배열을 인수로 넣을 수 있게 하는 방법
	return y.astype(np.int)

# x = np.array([-1.0, 1.0, 2.0])
# y = x > 0
# y를 출력하면 0보다 큰 x값은 True로, 0보다 작거나 같은 값은 False로 나온다.
# boolean값을 int형으로 변환시키면 True는 0, False는 1이다.
import numpy as np
x = np.array([-1.0, 1.0, 2.0])
x
>>> array([-1., 1., 2.])
y = x > 0
y
>>> array([False, True, True], dtype=bool)
y = y.astype(np.int)
y
>>> array([0, 1, 1])

3.2.3 계단 함수의 그래프

## 계단 함수 그래프
import numpy as np
import matploblib.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) # y축의 범위 지정
plt.show()

  • 계단 함수는 0을 경계로 출력이 0에서 1 (또는 1에서 0)로 바뀜
    • 값이 바뀌는 형태가 계단처럼 생김

3.2.4 시그모이드 함수 구현

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) # y축 범위 지정
plt.show()


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

차이점

  • 계단 함수
    • 0과 1 中 하나의 값만 반환 -> 뉴런 사이에 0 혹은 1만 흐름
  • 시그모이드
    • 입력에 따라 출력이 연속적으로 변화함 -> 신경망에서 연속적인 실수가 흐름

공통점

  • 큰 관점에서 보면 둘이 같은 모양을 하고 있음
  • 둘다 입력이 작을 때의 출력은 0에 가깝고 (혹은 0이고), 입력이 커지면 출력이 1에 가까워지는(혹은 1이 되는) 구조
    • 입력이 아무리 작거나 커도 출력은 0에서 1 사이

3.2.6 비선형 함수

중요한 공통점으로 두 함수 모두 비선형 함수이다.

💡
활성화 함수를 설명할 때 비선형 함수와 선형 함수라는 용어가 자주 등장

  • 선형 함수: 함수에 무언가 입력했을 때 출력이 입력의 상수배만큼 변하는 함수
    • f(x)=ax+bf(x) = ax + b, 이때 aabb는 상수
    • 곧은 1개의 직선이 됨
  • 비선형 함수
    • '선형이 아닌' 함수
    • 직선 1개로는 그릴 수 없는 함수

신경망에서는 활성화 함수로 비선형 함수를 사용해야 함.

신경망에서는 선형 함수를 사용해서는 안된다.

그 이유는 선형 함수를 이용하면 신경망의 층을 깊게 하는 의미가 없어지기 때문

  • 선형 함수의 문제는 층을 아무리 깊게 해도 은닉층이 없는 네트워크로도 같은 기능을 할 수 있는 것이 문제

    선형 함수인 h(x)h(x) = cxcx를 3층 네트워크로 쌓는다고 가정

    • 식으로 나타내면 y(x)=h(h(h(x)))y(x) = h(h(h(x)))가 됨
    • y(x)y(x) = axax 와 같은 식임. (a=c3a=c^3)
    • 즉, 은닉층이 없는 네트워크로 표현할 수 있음
      • 그렇게 되면, 여러 층으로 구성하는 이점을 살릴 수 없음
      • 그래서 층을 쌓는 혜택을 얻고 싶다면 활성화 함수로는 반드시 비선형 함수를 사용해야 함.

3.2.7 ReLU 함수

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

식은 다음과 같다

h(x)={x(x>0)0(x0)h(x) = \begin{cases} x \quad(x \gt 0) \\ 0 \quad(x \leq 0) \end{cases}
# relu 구현
def relu(x):
	return np.maximum(0, x)


3.3 다차원 배열의 계산

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

3.3.1 다차원 배열

'''1차원 배열'''
import numpy as np
A = np.array([1, 2, 3, 4])
np.ndim(A)  # 배열의 차원 수
>>> 1 
A.shape     # 배열의 형상 (1차원이라도 다차원처럼 tuple로 반환-> 통일)
>>> (4,)
A.shape[0]
>>> 4

'''2차원 배열'''
B = np.array([[1,2], [3,4], [5,6]])
np.ndim(B)
>>> 2
B.shape
>>> (3,2)

2차원 배열은 특히 행렬matrix^{matrix} 이라 부르고, 배열의 가로 방향을 row^{row} , 세로 방향을 column^{column}이라고 한다.


3.3.2 행렬의 곱

행렬 곱은 왼쪽 행렬의 행(가로)과 오른쪽 행렬의 열(세로)을 원소별로 곱하고, 그 값들을 더해서 계산

'''위 행렬곱 파이썬 구현'''
import numpy as np
A = np.array([[1,2], [3,4]])  # shape: 2x2 행렬
B = np.array([[5,6], [7,8]])  # shape: 2x2 행렬
np.dot(A, B)
>>> array([[19, 22],
		   [43, 50]])	

np.dot() : 입력이 1차원 배열이면 벡터를, 2차원 배열이면 행렬 곱을 계산한다.
np.dot(A, B)np.dot(B, A)는 다른 값이 될 수 있다.

행렬 곱을 할 때는, 행렬 A의 1번째 차원의 원소 수(열 수)행렬 B의 0번째 차원의 원소 수 (행 수)가 동일해야 함.

  • 이 값이 다르면 행렬 곱 수행 X

3.3.3 신경망에서의 행렬 곱

XW의 대응하는 차원의 원소 수가 같아야 함.

import numpy as np
X = np.array([1, 2])  #x1, x2
X.shape
>>> (2,)

W = np.array([[1, 3, 5], [2, 4, 6]]) #x1: 1,3,5 | x2:2,4,6
W.shape
>>> (2, 3)

Y = np.dot(X, W)
>>> [5  11  17]

행렬 곱으로 한꺼번에 계산해주는 기능은 신경망을 구현할 때 매우 중요하다.



3.4 3층 신경망 구현하기

3.4.1 표기법 설명

입력층의 뉴런 x2x_2에서 다음 층의 뉴런 a1(1)a_1^{(1)}으로 향하는 선 위에 가중치를 표시하고 있음

가중치와 은닉 층 뉴런의 오른쪽 위의 (1)^{(1)}의 의미는 1층의 가중치, 1층의 뉴런임을 뜻함.


3.4.2 각 층의 신호 전달 구현

입력층에서 1층으로 신호 전달

a1(1)a_1^{(1)}은 가중치를 곱한 신호 두 개와 편향을 합해서 다음과 같이 계산한다.

a1(1)=w11(1)x1+w12(1)x2+b1(1)a_1^{(1)} = w_{11}^{(1)}x_1+w_{12}^{(1)}x_2+b_1^{(1)}

여기에서 행렬의 곱을 이용하면 1층의 '가중치 부분'을 간소화 가능

A(1)=XW(1)+B(1)A^{(1)} = XW^{(1)} + B^{(1)}

이때 행렬 A(1),  X,  W(1)A^{(1)}, \; X, \; W^{(1)}은 각각 다음과 같다.

A(1)=(a1(1)  a2(1)  a3(1)),X=(x1,  x2),B(1)=(b1(1)  b2(1)  b3(1))A^{(1)} = (a_1^{(1)} \;a_2^{(1)} \;a_3^{(1)}), \quad X = (x_1, \; x_2), \quad B^{(1)} = (b_1^{(1)} \;b_2^{(1)} \;b_3^{(1)})
W(1)=(w11(1)w21(1)w31(1)w12(1)w22(1)w32(1))W^{(1)} = \begin{pmatrix} w_{11}^{(1)} & w_{21}^{(1)}&w_{31}^{(1)}\\ w_{12}^{(1)} & w_{22}^{(1)}&w_{32}^{(1)}\\ \end{pmatrix}

은닉층에서의 가중치 합(가중 신호와 편향의 총합)을 aa로 표기하고 활성화 함수 h()h()로 변환된 신호를 zz로 표현한다.

## 입력층에서 1층으로의 신호 전달 ##

import numpy as np
X = np.array([1.0], [0.5])   # shape: (2,)
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) # shape: (3,2)
B1 = np.array([0.1, 0.2, 0.3])  # shape: (3,)

A1 = np.dot(X, W1) + B1  # shape: (1,3)
Z1 = sigmoid(A1)  # shape: (3,)
A1
>>> [0.3, 0.7, 1.1]
'''넘파이 배열을 받아 같은 수의 원소로 구성된 넘파이 배열 반환'''
Z1
>>> [0.57444252, 0.66818777, 0.75026011]

1층에서 2층으로의 신호 전달

## 1층에서 2층으로의 신호 전달 ##

W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])  #shape: (3,2)
B2 = np.array([0.1, 0.2])  #shape: (2,)

A2 = np.dot(Z1, W2) + B2  #shape: (1,2)
Z2 = sigmoid(A2)		  #shape: (1,2)

2층에서 출력층으로의 신호 전달

출력층의 활성화 함수로 항등 함수인 identity_function()을 정의

출력층의 활성화 함수를 σ()\sigma()로 표시하여 은닉층의 활성화 함수 h()h()와는 다름을 명시

def identity_function(x):
	return x
    
W3 = np.array([[0.1, 0.3],[0.2, 0.4]])  #shape: (2,2)
B3 = np.array([0.1, 0.2])			    #shape: (2,) 1x2

A3 = np.dot(Z2, W3) + B3				#shape: (1,2)
Y = identity_function(A3)  # 혹은 Y = A3

💡
출력층의 활성화 함수는 풀고자 하는 문제의 성질에 맞게 정함

  • Regression: 항등함수
  • Binary Classfication: 시그모이드
  • Multi class Classification: 소프트맥스
  • Multi label Classification: 시그모이드

3.4.3 3층 신경망 구현 정리

def init_network():
  network = {}
  # 1층으로 가는 가중치 + 편향
  network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) # 2x3
  network['b1'] = np.array([0.1, 0.2, 0.3]) # 1x3
  # 2층으로 가는 가중치 + 편향
  network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) # 3x2
  network['b2'] = np.array([0.1, 0.2]) # 1x2
  # 3층으로 가는 가중치 + 편향
  network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]]) # 2x2
  network['b3'] = np.array([0.1, 0.2]) # 1x2
  
  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]) #1*2
y = forward(network, x)
print(y)
>>> [0.31682708  0.69627909]


3.5 출력층 설계

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

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

일반적으로 회귀에는 항등함수를 분류에는 소프트맥스 함수를 출력층의 활성화 함수로 사용한다.


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

항등 함수identity  function^{identity\;function}은 입력을 그대로 출력한다.

  • 입력과 출력이 항상 같다는 뜻
  • 입력 신호가 그대로 출력 신호가 됨.

소프트맥스 함수softmax  fucntion^{softmax\;fucntion}의 식은 다음과 같다.

yk=exp(ak)i=1nexp(ai)y_k = \frac{exp(a_k)}{\displaystyle\sum_{i=1}^{n}{exp(a_i)}}
  • exp(x) : exe^x를 뜻하는 지수 함수 (ee는 자연상수)
  • n : 출력층의 뉴런 수
  • y_k : 그중 kk번째 출력

소프트맥스 함수의 분자는 입력 신호의 지수함수, 분모는 모든 입력 신호의 지수 함수의 합으로 구성된다.

  • 분모에서 보듯, 출력층의 각 뉴런이 모든 입력 신호에서 영향을 받기 때문

def softmax(a):
  exp_a = np.exp(a)
  sum_exp_a = np.sum(exp_a)
  y = exp_a / sum_exp_a
  
  return y

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

지수함수를 사용하는 소프트맥스 함수는 오버플로의 문제가 발생해 수치가 '불안정'해질 수 있는 문제점이 있다.

오버플로(overflow) : 표현할 수 있는 수의 범위가 한정되어 너무 큰 값은 표현할 수 없다.

소프트맥스 함수 구현 개선

  • CC라는 임의의 정수를 분자와 분모 양쪽에 곱함
    • 양쪽에 같은 수를 곱했으니 똑같은 계산
  • 그 다음, CC를 지수 함수 exp() 안으로 옮겨 logClogC로 만듦
  • 마지막으로 locClocCCC^{'}라는 새로운 기호로 변경
    • 여기서 CC^{'}에 어떤 값을 대입해도 상관없지만, 오버플로를 막을 목적으로는 입력 신호 중 최댓값을 이용하는 것이 일반적
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
  • 이 성질 덕분에 소프트 맥스 함수의 출력을 확률로 해석할 수 있음

소프트맥스 함수를 적용해도 각 원소의 대소 관계는 불변함

  • 지수 함수 yy = exp(xx)가 단조 증가 함수이기 때문
  • a의 원소들 사이의 대소 관계가 y의 원소들 사이의 대소 관계로 그대로 이어짐

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


3.5.4 출력층의 뉴런 수 정하기

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

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


3.6 손글씨 숫자 인식

💡
기계학습과 마찬가지로 신경망도 두 단계를 거쳐서 문제를 해결함

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

신경망의 순전파(forward propagation): 이미 학습된 매개변수를 사용하여 입력 데이터를 분류하는 추론 과정

3.6.1 MNIST 데이터셋

  • 0부터 9까지의 숫자 이미지로 구성
  • Train: 60,000 / Test: 10,000
  • 28 x 28 크기의 회색조 이미지(1채널)
  • 각 픽셀은 0에서 255까지의 값을 취함
  • 실제 의미하는 숫자가 레이블로 붙어 있음

밑바닥부터 시작하는 딥러닝 깃허브 참조

import sys, os
#sys.path.append(os.pardir)
#from dataset.mnist import load_mnist
from mnist import load_mnist #mnist.py만을 다운로드 받았다면

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
'''
normalize: 입력 이미지의 픽셀값을 0.0~1.0 사이의 값으로 정규화할지 정함
  - 이처럼 데이터를 특정 범위로 변환하는 처리를 정규화라고 함.
flatten: 입력 이미지를 1차원 배열로 만들지를 정함
- (False: 1*28*28 3차원 배열로 True: 784개의 원소로 이루어진 1차원 배열로)
one-hot-label: 원-핫 인코딩 형태로 저장할지를 정함
- (False: 숫자 형태의 레이블을 저장 True: 레이블을 원-핫 인코딩하여 저장)
'''

# 각 데이터의 형상
'''
1x28x28 3차원 배열의 픽셀의 한 이미지가 flatten된 경우, 
784개의 픽셀이 각 컬럼이 됨. (1차원 배열)
'''
print(x_train.shape)  # (60000, 784)
print(t_train.shape)  # (60000,)
print(x_test.shape)  # (10000, 784)
print(t_test.shape)  # (10000,)

시각화

import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
import numpy as np
from PIL import Image


def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    #Image.fromarray: 넘파이로 저장된 이미지 데이터를 PIL용 데이터 객체로 변환해야
    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)		  # (784,)
img = img.reshape(28, 28) # 원래 이미지의 모양으로 변형
#flatten=True는 1차원 넘파이 배열로 저장되어 있다
#이미지를 표시할 때 원래 형상인 28*28크기로 다시 변형
print(img.shape)		  
img_show(img)

3.6.2 신경망의 추론 처리

  • 입력층 뉴런을 784개, 출력층 뉴런을 10개로 구성
    • 입력층 뉴런이 784개인 이유는 이미지 크기가 28x28=784
    • 출력층 뉴런이 10개인 이유는 이 문제가 0에서 9까지의 숫자를 구분하기 문제이기 때문
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():
    #sample_weight.pkl파일에 저장된 학습된 가중치 매개변수를 읽는다
    #가중치와 편향 매개변수가 딕셔너리 변수로 저장되어 있다.
    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]) #각 레이블의 확률을 넘파이 배열로 반환 e.g. [0.1, 0.2, ... ,0.04]
    p = np.argmax(y) #배열에서 값이 가장 큰(확률이 가장 높은) 원소의 인덱스를 구함
    if p == t[i]:
        accuracy_cnt += 1

print("Accuracy: "+str(float(accuracy_cnt) / len(x)))

3.6.3 배치 처리

하나로 묶은 입력 데이터를 배치batch^{batch}라고 한다.

(이미지 100개를 묶어 predict() 함수에 한 번에 넘긴 경우)

  • 1층2층에 뉴런 수가 각각 50, 100인 은닉층이 있음.
  • x[0]y[0]에는 0번째 이미지와 그 추론 결과가, x[1]y[1]에는 1번째의 이미지와 그 결과가 저장된다.

💡
배치 처리의 장점
1. 이미지 1장당 처리 시간을 대폭 줄여준다.

  • 수치 계산 라이브러리 대부분이 큰 배열을 효율적으로 처리할 수 있도록 고도로 최적화되어 있기 때문
  1. 커다란 신경망에서는 데이터 전송이 병목으로 작용하는 경우가 자주 있는데, 배치 처리를 함으로써 버스에 주는 부하를 줄임
  • 느린 I/O를 통해 데이터를 읽는 횟수가 줄어, 빠른 CPU나 GPU로 순수 계산을 수행하는 비율이 높아짐.
# Batch 처리
# 위 코드에서 달라진 부분

x, t = get_data()
network = init_network()

batch_size = 100 #배치 크기
accuracy_cnt = 0

#range: 0부터 len(x)까지 batch_size간격으로 증가하는 리스트 반환
for i in range(0, len(x), batch_size):
  x_batch = x[i:i+batch_size] #0~100, 100~200, ...
  y_batch = predict(network, x_batch) #각 레이블의 확률을 넘파이 배열로 반환
  p = np.argmax(y_batch, axis=1) #배열에서 값이 가장 큰(확률이 가장 높은) 원소의 인덱스를 구함
  '''
  "axis=1": 1번째 차원을 구성하는 각 원소에서 최댓값의 인덱스를 찾도록
  x = np.array([[0.1, 0.8, 0.1], [0.3, 0.1, 0.6]])
  y = np.argmax(x, axis=1)
  >>> [1 2]
  '''
  accuracy_cnt += np.sum(p == t[i:i+batch_size])
    
print(f"Accuracy: {str(float(accuracy_cnt) / len(x)))}"

3.7 정리

  • 신경망에서는 활성화 함수로 시그모이드 함수와 ReLU 함수 같은 매끄럽게 변화하는 함수를 이용
  • 넘파이의 다차원 배열을 잘 사용하면 신경망을 효율적으로 구현할 수 있음
  • 기계학습 문제는 크게 회귀와 분류로 나눌 수 있음
  • 출력층의 활성화 함수로는 회귀에서는 주로 항등 함수를, 분류에서는 주로 소프트맥스 함수를 이용
  • 분류에서는 출력층의 뉴런 수를 분류하려는 클래스 수와 같게 설정
  • 입력 데이터를 묶은 것을 배치라 하며, 추론 처리를 이 배치 단위로 진행하면 결과를 훨씬 빠르게 얻을 수 있다.
profile
성장과 연구하는 자세를 추구하는 AI 연구개발자

0개의 댓글