Deep Learning

[밑바닥부터시작하는딥러닝1] Chapter 3. 신경망

씨주 2024. 4. 3. 23:49

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

 

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

✔️ 신경망의 예

그림 3-1. 신경망

은닉층 : 입력층, 출력층과 달리 사람 눈에 보이지 않기 때문에 은닉층

 

✔️ 퍼셉트론 복습

식 3-1.
그림 3-2. 편향을 명시한 퍼셉트론

앞 장에서 배웠던 퍼셉트론을 위와 같이 표현할 수 있다.

 

 

 

식 3-5.
그림 3-4. 활성화 함수의 처리 과정

식 3-1을 h(x)를 이용하여 식 3-5로 표현할 수 있다.

 

✔️ 활성화 함수의 등장

활성화함수 h(x) : 입력신호의 총합을 출력신호로 변환하는 함수

신호의 총합이 활성화를 일으키는지를 정하는 역할

 

3.2. 활성화 함수

✔️ 계단 함수

계단함수 : 위와 같이 임계값을 경계로 출력이 바뀌는 활성화 함수

그래서 퍼셉트론에서는 활성화 함수로 계단함수를 이용한다고 할 수 있다.

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

그림 3-6. 계단함수의 그래프

 

✔️ 시그모이드 함수

시그모이드함수 : 신경망에서 활성화 함수로 사용하는 함수

식 3-6. 시그모이드 함수

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

그림 3-7. 시그모이드 함수의 그래프

 

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

  계단함수 시그모이드 함수
연속성 연속적 X
(0을 기준으로 출력이 급격히 변함)
연속적
출력 0 or 1 실수(입력에 비례)
공통점 1. 입력이 작으면 출력이 0에 가깝고 입력이 커지면 출력이 1에 가까워진다.
   (즉 입력이 중요하면 큰 값 출력, 입력이 중요하지 않으면 작은 값 출력)
2. 입력이 아무리 작거나 커도 출력은 0과 1 사이다.

그림 3-8. 계단함수와 시그모이드 함수

 

✔️ 비선형 함수

계단함수와 시그모이드 함수 모두 비선형 함수이다.

신경망에서는 활성화 함수로 비선형 함수를 사용해야 한다. (선형함수를 이용하면 신경망의 층을 깊게 하는 의미가 없어지기 때문)

 

✔️ ReLU 함수

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

그림 3-9. ReLU 함수의 그래프
식 3-7.

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

 

 

3.4. 3층 신경망 구현하기

그림 3-15. 3층 신경망

위와 같은 3층 신경망 1층의 1번째 뉴런으로 가는 신호를 계산해보자.

 

✔️ 각 층의 신호 전달 구현하기

그림 3-17.
식 3-8.

1층의 1번째 뉴런으로 가는 신호를 식 3-8과 같이 계산할 수 있다.

여기서 행렬의 내적을 이용해 1층의 가중치 부분을 간소화하면 식 3-9와 같이 표현할 수 있다.

식 3-9.

 

이를 활성화함수의 처리가 된 그림으로 표현하면 그림 3-18과 같다.

이 때, 활성화 함수로 시그모이드 함수를 사용하기로 하자.

그림 3-18. 입력에서 1층으로의 신호 전달

A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)

 

이와 같이 1층에서 2층, 2층에서 출력층도 계산하자.

그림 3-19. 1층에서 2층으로의 신호 전달 / 그림 3-20. 2층에서 출력층으로의 신호 전달

A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
def identity_function(x):
    return x

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

이 때 항등 함수인 identity_function()을 정의하고 이를 출력층의 활성화 함수로 이용하였다.

(항듬함수는 입력을 그대로 출력하는 함수로 굳이 정의할 필요는 없으나 그동안의 흐름과 통일하기 위해 구현함)

 

출력층의 활성화함수는 σ()로 표시하여 은닉층의 활성화함수는 다름을 명시했다.

 

forward : 신호가 순방향(입력에서 출력방향)으로 전달되는 순전파

이 때까지 구현한 함수는 forward 이며, 뒷 장에서 역방향(backward)도 구현해볼 것이다.

 

3.5. 출력층 설계하기

신경망은 분류와 회귀 모두 이용할 수 있는데 출력층에서 사용하는 활성화 함수에 따라 달라진다.

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

 

✔️ 항등 함수

항등함수 : 입력을 그대로 출력

그림 3-21. 항등함수

 

✔️ 소프트맥스 함수

소프트맥스(softmax) 함수 : 분자는 입력 신호의 지수함수, 분모는 모든 입력 신호의 지수함수의 합

n은 출력층의 뉴런수, y_k는 k번째 출력

분모와 같이 출력층의 각 뉴런이 모든 입력 신호에 영향을 받기 때문에 그림 3-22와 같이 모든 입력신호로부터 화살표를 받는다.

식 3-10. 소프트맥스
그림 3-22. 소프트맥스함수

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

 

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

여기서 소프트맥스함수를 컴퓨터로 계산할 시 오버플로 문제가 생길 수 있다.

지수함수는 쉽게 큰 값을 출력하는데 e10은 20,000이 넘고 e1000은 무한대를 뜻하는 inf가 되어 돌아온다.

이런 큰 값끼리 나눗셈을 하면 결과수치가 불안정해지기 때문에 소프트맥스함수를 개선하여 구현할 수 있다.

 

아래와 같이 구현할 수 있는데 이때 C'는 어떤 값을 대입해도 상관없지만 오버플로를 막을 목적이므로 입력 신호 중 최댓값을 이용하는 것이 일반적이다.

식 3-11.

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

 

✔️ 소프트맥스 함수의 특징

소프트맥스 함수의 출력은 0과 1 사이의 실수이다.

또한, 소프트맥스 함수 출력의 총합은 1이다.

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

(아래의 출력값을 예시로 하면 74%의 확률로 2번째 클래스, 25%의 확률로 1번째 클래스)

a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y) # [0.01821127  0.24519181  0.73659691]
np.sum(y) # 1

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

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

따라서 신경망으로 분류할 때는 출력층의 소프트맥스 함수를 생략해도 되며, 현업에서는 지수 함수 계산에 드는 자원 낭비를 줄이기 위해 생략하는 것이 일반적이다.

 

✔️ 출력층의 뉴런 수 정하기

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

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

 

3.6. 손글씨 숫자 인식

책에서는 MNIST 데이터셋을 활용하여 손글씨 숫자 예측을 하는 과정을 설명하였으나 이는 생략하도록 하겠다.

 

✔️ 배치(batch) 처리

그림 3-26. 신경망 각 층의 배열 형상의 추이

그림 3-26은 원소 784개로 구성된 1차원 배열(원래는 28*28인 2차원 배열)이 입력되어 마지막에 원소가 10개인(숫자 0~9)인 1차원 배열로 출력되는 흐름이다.

이는 이미지 데이터를 1장만 입력했을 때의 흐름인데 이미지 여러 장을 한꺼번에 입력하는 경우를 생각해보자.

x의 형상을 100*784로 바꿔 이미지 100개를 묶어 하나의 입력데이터로 표현하면 될 것이다.

그림 3-27. 배치 처리를 위한 배열들의 형상 추이

출력 데이터는 100*10이 되며, 이미지 100개의 결과가 한 번에 출력됨을 나타낸다.

x[0], y[0]은 0번째 이미지와 추론 결과, x[1], y[1]은 1번째 이미지와 추론 결과이다.

이처럼 하나로 묶은 입력 데이터를 배치(batch)라고 한다.

 

배치처리를 할 경우 이미지 1장당 처리 시간을 대폭 줄일 수 있다.

데이터를 읽는 횟수가 줄어 빠른 CPU나 GPU로 순수 계산을 수행하는 비율이 높아지기 때문에 버스에 주는 부하를 줄일 수 있다.

즉, 배치처리를 수행함으로써 큰 배열로 이뤄지는 계산을 하게 되는데, 수치계산 라이브러리 대부분은 큰 배열을 효율적으로 처리할 수 있도록 최적화 되어있기 때문에 컴퓨터는 큰 배열을 한꺼번에 계산하는 것이 작은 배열을 여러번 계산하는 것보다 빠르다.

# batch 처리 전

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
# batch 처리 후

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

 

3.7. 정리

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

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

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

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

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

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

 

 

 

참고 git : https://github.com/geonsangyoo/DeepLearning/tree/master