본 글은 「밑바닥부터 시작하는 딥러닝」 책을 공부하고 정리한 내용입니다.
이전 글(퍼셉트론)에 이어서 퍼셉트론과 공통점이 많은 신경망에 대해 소개해드리겠습니다.
신경망은 인간이 가중치값을 수동으로 설정해줘야 하는 퍼셉트론과 다르게 가중치값도 자동으로 기계가 학습할 수 있게 합니다.
신경망의 그림입니다. 구조는 입력층, 은닉층, 출력층으로 나눠져 있는데 은닉층의 뉴런(노드)은 사람 눈에 보이지 않습니다.
전 글에서 퍼셉트론 수식에 대해 설명드렸을 때 θ(세타)를 b(편향)으로 치환하여 수식을 나타냈었는데요.
이를 노드로 표현하면 다음과 같습니다.
가중치가 b이고 입력이 1인 뉴런(노드)이 추가됐습니다. 편향의 입력 신호는 항상 1입니다.
편향이 있는 수식인데 조건 분기 동작을 하나의 함수로 만들어 더 간단히 나타내 보도록 하겠습니다.
입력 신호의 총합이 h(x)라는 함수를 거쳐 반환되고, 그 변환된 값이 y의 출력이 됨을 보여줍니다. h(x) 함수는 입력이 0을 넘으면 1을 돌려주고 그렇지 않다면 0을 돌려줍니다.
이 h(x)라는 함수를 활성화 함수(activation function)로 부릅니다. h(x)를 보시면 임계값을 기준으로 출력이 바뀌는데 이를 계단 함수(step function)이라고 합니다.
위 그림에선 0을 기준으로 출력이 0과 1이 나눠지듯 활성화 함수도 계단 함수를 이용한다고 볼 수 있습니다.
위 그림은 방금 설명드린 활성화 함수의 처리 과정을 담은 그림입니다. h(x)의 x를 a로 바꾼 것뿐입니다.
시그모이드 함수
시그모이드 함수(Sigmoid function)는 신경망에서 자주 이용되는 함수입니다.
시그모이드 함수의 수식은 다음과 같습니다.
신경망에서는 활성화 함수로 시그모이드 함수를 이용해 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달합니다.
퍼셉트론과 신경망의 차이는 이 활성화 함수라고 할 수 있습니다.
계단 함수와 활성화 함수의 공통점
계단 함수와 활성화 함수는 비선형 함수라는 공통점이 있습니다. 비선형 함수는 직선 1개로는 그릴 수 없는 함수를 뜻합니다.
ReLU 함수
최근에는 신경망 분야에서 ReLU라는 함수를 사용한다고 합니다. ReLU는 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하면 0을 출력하는 함수입니다.
이를 수식으로 나타내면 다음과 같습니다.
3층 신경망
다음은 3층 신경망 구현에 대해 설명해드리겠습니다.
입력층은 2개, 은닉층 첫 번째는 3개, 은닉층 두 번째는 2개, 출력층은 2개로 이루어진 3층 신경망 구조 그림입니다.
이제 뉴런(노드)과 뉴런(노드) 사이 선에 가중치값을 표시합니다.
저 표시의 뜻은 다음과 같습니다.
다음은 입력층에서 출력층까지의 과정을 보여드리겠습니다.
1. 입력층->1층 첫 번째 뉴런(노드)
편향은 아래 인덱스가 1밖에 없습니다. 앞 층의 편향 뉴런이 하나뿐이기 때문입니다.
첫 번째 은닉층의 계산식은 이러합니다.
여기에 행렬 곱을 이용하면 1층의 가중치 부분을 다음과 같이 간소화시킬 수 있습니다.
각각의 행렬은 다음과 같습니다.
이어서 1층의 활성화 함수에서의 처리를 다음 그림과 같이 나타낼 수 있습니다.
은닉층에서의 가중치 합(가중 신호와 편향의 총합)을 a로 표시하고, 활성화 함수 h()로 변환된 신호를 z로 표기합니다.
여기에서 활성화 함수는 시그모이드 함수를 예시로 사용해보겠습니다.
import numpy as np
def sigmoid(x):
return 1 / (1+ np.exp(-x))
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)
print(Z1)
출력:
2. 1층->2층 첫 번째 뉴런(노드)
import numpy as np
def sigmoid(x):
return 1 / (1+ np.exp(-x))
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])
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
print(A2)
print(Z2)
3. 2층->출력층
출력층의 구현도 그동안의 구현과 거의 같지만, 활성화 함수가 지금까지의 은닉층과 다릅니다.
지금까지의 은닉층은 시그모이드 함수로 구현했지만 출력층은 항등 함수로 구현 합니다.
출력층의 활성화 함수는 풀고자 하는 문제의 성질에 따라 정합니다.
회귀에는 항등 함수, 2클래스 분류에는 시그모이드 함수, 다중 클래스 분류에는 소프트맥스 함수를 사용하는 것이 일반적입니다.
기계 학습 문제는 분류와 회귀로 나뉘는데요.
분류는 데이터가 어느 클래스에 속하느냐는 문제입니다. 예를 들면 사진 속 인물이 여자인지 남자인지 예측하는 경우이며,
회귀는 입력 데이터에서 연속적인 수치를 예측하는 문제입니다. 예를 들면 사진 속 인물의 몸무게를 예측하는 경우 입니다.
항등 함수
항등 함수(indentity function)는 입력을 그대로 출력합니다.
입력과 출력이 항상 같다는 뜻으로 항등이 붙여졌습니다. 그래서 반대로 출력층에서 항등 함수를 사용하면 입력 신호가 그대로 출력 신호가 됩니다.
소프트맥스 함수
분류에서 사용하는 소프트맥스 함수의 식은 다음과 같습니다.
n은 출력층의 뉴런(노드) 수, y_k는 그중 k번째 출력임을 뜻합니다.
소프트맥스 함수를 그림으로 나타내면 이렇습니다.
파이썬 함수로는 이렇게 나타냅니다.
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
하지만 소프트맥스 함수는 지수 함수를 사용하는데 지수 함수의 값이 엄청나게 커지므로 큰 값을 대입한다면 inf값이 떠 부정확해질 수 있습니다.
이를 개선하기 위해선 C라는 임의의 정수를 분자와 분모 양쪽에 곱하고 지수 함수 안으로 옮겨 logC로 만듭니다. 마지막으로 logC를 C'로 치환해줍니다.
C에 어떤 값을 대입해도 결과는 바뀌지 않지만 overflow를 막을 목적으로는 입력 신호 중 최댓값을 이용한다고 합니다.
예를 들어, numpy 배열 입력값이 1010, 1000, 990이면 C'값에 -1010 값을 대입합니다.
그럼 overflow가 되지 않고 잘 출력이 됩니다.
위 함수를 다시 정의한다면,
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
가 됩니다.
소프트맥스 함수의 특징
import numpy as np
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)
z=np.sum(y)
print(z)
소프트맥스 함수의 출력값의 원소 합이 0에서 1.0 사이인 것을 알 수 있습니다. (확률로는 0%~100%)
각 원소의 확률은 약 1.8%, 24.5%, 73.7% 입니다. 소프트맥스 함수를 거친 원소의 대수 관계는 a원소들과의 대수 관계와 달라지지 않습니다. 이는 분류에서 소프트맥스 함수를 출력층에 사용하지 않는 이유입니다.
기계학습의 문제 풀이는 학습과 추론의 두 단계를 거쳐 이뤄집니다. 학습 단계에서의 모델을 학습하고, 추론 단계에서 앞서 학습한 모델로 미지의 데이터에 대해서 추론을 수행합니다. 추론 단계에서는 출력층의 소프트맥스 함수를 생략하는 것이 일반적이지만 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용합니다.
출력층의 뉴런(노드) 수는 풀려는 문제에 맞게 적절히 정해야 합니다.
예를 들어 남녀를 구분하는 문제면 출력층의 뉴런 수는 2개만 필요할 것이고, 손글씨 인식으로 0~9의 숫자를 인식하는 거면 10개가 필요할 것 입니다.
MNIST 데이터셋을 이용한 손글씨 숫자 인식
MNIST 데이터셋을 이용하기 때문에 학습 과정은 생략하고, 추론 과정을 구현합니다.
이러한 추론 과정을 순전파(forward propagation)라고도 합니다.
기계학습과 마찬가지로, 신경망도 두 단계를 거쳐 문제를 해결하는데요.
먼저 학습 데이터를 사용해 가중치 매개변수를 학습하고, 추론 단계에서는 앞서 학습한 매개변수를 사용하여 입력 데이터를 분류합니다.
데이터를 특정 범위로 변환하는 처리를 정규화라고 하며, 신경망의 입력 데이터에 특정 변환을 가하는 것을 전처리라고 합니다.
전처리를 통해 식별 능력을 개선하고, 학습 속도를 높이는 등의 사례가 많이 제시되고 있다고 합니다.
neuralnet_mnist.py에서는 전처리 과정을 단순히 255로 나누는 과정을 보여줬지만, 현업에서는 데이터 전체의 분포를 고려해 전처리하는 경우가 많습니다.
(전체 평균과 표준편차를 이용해 데이터들이 0을 중심으로 분포하도록 이동, 전체 데이터를 균일하게 분포시키는 데이터 백색화 등)
위 그림을 보면 다차원 배열의 대응하는 차원의 원소 수가 일치하고 있습니다.
784 숫자의 의미는 MNIST 숫자 데이터 하나의 크기가 28*28인 2차원 크기인 이미지를 1차원으로 바꾼 크기 입니다.
100개의 사진을 처리했을 땐 다음 사진과 같습니다.
100*10은 100장의 분량 입력 데이터의 결과가 한 번에 출력됨을 나타냅니다.
이처럼 하나로 묶은 입력 데이터를 배치라고 합니다.
배치 처리는 큰 배열로 이뤄진 계산을 하게 되는데, 컴퓨터는 큰 배열을 한꺼번에 계산하는 것이 분할 된 작은 배열을 여러 번 계산하는 것보다 빠르기 때문에 이미지 1장당 처리 시간을 대폭 줄여줍니다.
다음은 배치 처리를 구현하는 코드 입니다.
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)))
range의 인수가 3개이므로 batch_size 크기만큼 묶어서 루프를 돌리고
np.argmax의 두 번째 인수가 axis=1라는 뜻은 y배치에서 1차원을 구성하는 원소 중 가장 큰 원소의 인덱스 값을 출력하도록 한 것 입니다.
이처럼 데이터를 배치로 처리함으로써 효율적이고, 빠르게 처리할 수 있습니다.
이상으로 「밑바닥부터 시작하는 딥러닝」 책을 참고하여 신경망에 대해 알아보았습니다. 감사합니다.
그림 출처:https://compmath.korea.ac.kr/appmath2021/NeuralNetworks.html, https://www.crocus.co.kr/1519
'AI > Deep Learning' 카테고리의 다른 글
합성곱신경망, CNN(Convolutional neural network) (0) | 2023.07.12 |
---|---|
신경망 학습 방법 (1) | 2022.09.21 |
퍼셉트론(Perceptron) : 딥러닝의 기원 (0) | 2022.08.31 |