본 글은 "바닥부터 시작하는 딥러닝" 책을 바탕으로 작성되었습니다.
3.1 퍼셉트론에서 신경망으로
3.1.1 신경망의 예
- 은닉층은 사람의 눈에 보이지 않는다.
- 본 책에서는 입력층은 0층, 은닉층은 1층, 출력층은 2층이라고 부른다.
3.1.2 퍼셉트론 복습
- 기존 퍼셉트론에서 편향을 그림으로 명시한다면 그림2와 같다.
- 그림2에서는 가중치가 b이고, 입력이 1인 뉴런이 추가되었다.
- 편향의 입력 신호는 항상 1이다.
- 그림 2를 좀 더 간결한 형식의 수식으로 작성하면 그림3, 4와 같다.
이는 0을 넘으면 1을 출력하고 그렇지 않으면 0을 출력하는 조건 분기의 동작을 하나의 함수로 나타내고,
이를 h(x)라고 표현한다.
- 입력 신호의 총합을 h(x)함수를 거쳐 y의 출력이 되고, h(x)함수는 입력이 0을 넘으면 1 아니면 0을 반환한다.
3.1.3 활성화 함수의 등장
- 위 h(x) 함수와 같이, 입력 신호의 총합을 출력 신호로 변환해주는 함수를 "활성화 함수"라고 한다.
- 활성화 함수는 입력 신호의 총합이 활성화를 일으키는지 정하는 역할을 한다.
- 그림 3의 수식은 2단계로 처리하는데, 이는 그림5의 수식과 같다.
- 그림 5의 식을 그림으로 표현하면 그림 6과 같다.
- 그림 5에서 a는 입력 신호의 총합, h()는 활성화 함수, y는 출력이다.
3.2 활성화 함수
3.2.1 시그모이드 함수
- 신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에게 전달한다.
3.2.2 계단 함수 구현하기
- '계단 함수'는 입력이 0을 넘으면 1을 출력하고, 그 외에는 0을 출력하는 함수이다.
- 아래 코드는 계단함수를 단순히 구현한 것이며, 인수 x에는 실수(부동소수점)만 들어갈 수 있다.
따라서, 인수 x에는 넘파이 배열이 들어갈 수 없는 코드이다.
def step_function(x):
if x>0:
return 1
else:
return 0
- 넘파이 배열도 인수x에 들어가기 위해, 아래와 같이 numpy를 이용하여 코드를 구현할 수 있다.
def step_function(x):
y = x > 0
return y.astype(np.int)
- 위에서 사용한 numpy와 같이, numpy배열에 부등호 연산을 수행하면 각각 부등호 연산이 수행되어 bool 배열이 생성된다
>>> 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])
- 계단 함수는 0이나 1의 int형을 출력하는 함수이기 때문에, 배열 y의 원소를 bool에서 int형으로 변환시켜준다.
( True는 1, False는 0으로 변환된다. )
- 참고로 책에서는 np.int로 진행하는데, 실제 코드 실행 시 int64나 int32를 지정해야 형변환이 가능하였다.
>>> y = y.astype(np.int64)
>>> y
array([0, 1, 1])
3.2.3 계단 함수의 그래프
- 계단 함수는 0을 경계로 출력이 0에서 1로 바뀐다.
import numpy as np
import matplotlib.pylab as plt
def step_func(x):
return np.array(x > 0, dtype=np.int64)
x = np.arange(-5.0, 5.0, 0.1)
y = step_func(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) #y축 범위 지정
plt.show()
3.3 다차원 배열의 계산 + 3.4 3층 신경망 구현하기
해당 내용은 정리한 것이 날라가서, 패쓰 ㅎㅎ
3.5 출력층 설계하기
- 신경망은 분류와 회귀 모두에 이용될 수 있는데, 어떤 문제냐에 따라 출력층에서 사용하는 활성화 함수는 달라진다.
- 보통 회귀는 항등 함수를, 분류는 소프트 맥스 함수를 사용한다.
(분류: 데이터가 어느 클래스에 속하는지 분류 / 회귀 : 입력 데이터에서 수치를 예측)
3.5.1 항등 함수와 소프트맥스 함수 구현하기
- '항등함수'는 입력 그대로 출력한다.
- 소프트맥스 함수를 간단히 구현해보면 아래와 같다.
- 소프트맥스 함수를 파이썬으로 코드를 구현하면 아래와 같다.
import numpy as np
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 소프트맥스 함수 구현 시 주의점
- 3.5.1에서 구현한 소프트맥스 파이썬 코드는 오버플로 문제가 발생할 위험이 있다.
- 소프트맥스 코드에서 사용한 지수함수는 큰 수를 반환할 수도 있기 때문에, 오버플로 문제가 발생할 수 있다.
- 그림 10은 위에서 설명한 소프트맥스의 오버플로 예시로, nan은 not a number라는 의미이다.
- 그림 11과 같이, 입력값에서 최대값을 빼주면 오버플로 문제 없이 계산이 성공적으로 가능하다.
- 위 해결점을 보완한 소프트맥스 코드는 아래와 같다.
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
3.5.3 소프트맥스 함수의 특징
- softmax 함수를 이용하여, 신경망 출력은 아래와 같이 할 수 있다.
- 그림 12와 같이, 소프트맥스 함수의 출력은 0 ~ 1.0 사이의 실수이며, 출력의 총합은 1이다. (중요한 성질)
- 위 성질을 통해 소프트맥스 함수의 출력을 확률로 해석할 수 있으며, 신경망을 이용한 분류에서는 가장 큰 확률의 값만 인식한다.
3.6. 손글씨 숫자 인식
- 이번 절에서는 이미 학습된 매개변수를 사용하여 학습 과정을 생략하고 추론 과정만 구현한다.
- 위 추론 과정을 "신경망의 순전파"라고 한다.
3.6.1 MNIST 데이터셋
- MNIST는 손글씨 숫자 이미지 집합으로, 이번 예제에서 데이터셋으로 사용한다.
- MNIST는 0~9까지 숫자 이미지로 구성되어 있고, 훈련 이미지 60000장, 시험 이미지 10000장 준비되어 있다.
- 이미지 데이터는 28x28 크기의 회색조 이미지(채널1)이며, 각 픽셀은 0~255까지 값을 취한다.
- 각 이미지에는 실제 의미하는 숫자가 레이블로 붙어 있다.
- 데이터셋 다운로드: https://github.com/WegraLee/deep-learning-from-scratch.git
(dataset 디렉터리를 작업 디렉터리의 부모 디렉터리에 clone 해주면 된다.)
- 아래 코드는 dataset의 mnist.py에서 load_mnist함수를 임포트하여, MNIST 데이터셋을 읽어온다.
- load_mnist함수는 3가지 인수를 받아오는데,
1) normalize
: 입력 이미지 픽셀 값 정규화 (True는 0.0 ~ 1.0 사이의 값 / False는 원래 값인 0 ~ 255 값)
2) flatten
: 입력 이미지를 1차원 배열로 생성 (True는 784개 원소로 이뤄진 1차원 배열 / False는 1x28x28의 3차원 배열)
3) one_hot_label
: one-hot encoding 형태로 저장할지에 대한 여부 (True는 one-hot encoding / False는 숫자 형태로 저장)
- load_mnist함수는 (훈련 이미지, 훈련 레이블), (시험 이미지, 시험 레이블) 형식으로 반환한다.
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)
print(t_train.shape)
print(x_test.shape)
print(t_test.shape)
- load_mnist 함수의 인자값으로 flatten을 True로 했기 때문에, 이미지가 1차원 넘파일 배열로 저장되어 있다.
따라서, 이미지 표시할 때는 reshape()를 이용하여 원래 크기인 28x28로 다시 변형해준다.
(flatten을 False로 할 경우, 이미지의 채널까지 같이 출력되므로 계산하기 편하게 1차원 넘파이 배열로 받는 것이다.)
- 넘파이로 저장된 이미지 데이터를 PIL용 데이터 객체로 반환해야 하는데, 이는 Image.fromarray()로 바꿔준다.
import numpy as np
import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
from PIL import Image
def img_show(img):
pil_img = Image.fromarray(np.uint8(img))
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)
img = img.reshape(28, 28) #원래 이미지이 모양으로 변형
print(img.shape)
img_show(img)
3.6.2 신경망의 추론 처리
- 아래는 MNIST 데이터셋을 가지고 추론을 수행하는 신경망을 구현하는 코드이다.
- 구현하는 신경망은 입력층 뉴런 784개, 출력층 뉴런 10개로 구성된다.
- 입력층 뉴런이 784인 것은 이미지 크기가 28x28=784이기 때문이고, 출력층은 0~9까지의 숫자를 구분하기 위해서이다.
- 은닉층은 2개로, 첫번째 은닉층은 50개 뉴런을 배치하고 두번재 은닉층에는 100개의 뉴런을 배치한다.
(여기서 50, 100은 임의로 정한 값이다.)
- sample_weight.pkl에는 학습된 가중치와 편향 매개변수가 딕셔너리 변수로 저장되어 있다.
(다운로드: https://github.com/WegraLee/deep-learning-from-scratch/tree/master/ch03)
import numpy as np
import pickle
import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
from PIL import Image
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
- 위 코드를 통해, 신경망에 의한 추론을 수행하고 정확도(분류가 얼마나 올바른가) 확인을 진행한다.
- predict() 함수는 각 레이블의 활률을 넘파이 배열로 반환하고, np.argmax()함수에서 넘파이 배열에서 가장 큰 확률의 원소 인덱스를 구한다. 해당 인덱스는 예측 결과로, 정답 레이블과 비교하고 정확도를 체크한다.
- 결과는 0.9352로 93.52%의 정확도인 것을 알 수 있다.
- load_mnist 함수의 인수인 normalize를 True를 설정함으로써, 0~255 픽셀 값을 0.0 ~ 1.0 범위로 변환한다.
입력 이미지 데이터에 대한 전처리 작업으로 정규화를 수행 예시이다.
- 데이터를 특정 범위로 변환하는 처리를 "정규화"라고 하며, 신경망의 입력 데이터에 특정 변환을 가하는 것을 "전처리"라고 한다.
import sys, os
sys.path.append(os.pardir)
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)))
3.6.3 배치 처리
- 위 코드에서 가중치에 대해 확인해보면, 다차원 배열의 대응하는 차원의 원소 수가 일치하는 것을 알 수 있다.
- 원소 784개로 구성된 1차원 배열이 마지막에는 원소 10개인 1차원 배열을 출력한다.
- 하나로 묶은 입력 데이터를 "배치"라고 한다.
- 아래는 이미지 100개 묶어, predict()함수에 한번에 넘기는 코드이다. 이는 x의 형상이 100x784로 바뀌어 100장 분량 데이터를 하나의 입력 데이터로 표현된다.
x, y = 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)))
'AI' 카테고리의 다른 글
Building Systems with the ChatGPT API (0) | 2024.08.03 |
---|---|
Chapter 6. 학습 관련 기술들 (0) | 2024.07.13 |
Chapter 5. 오차역전파법 (0) | 2024.06.30 |
Chapter 4. 신경망 학습 (0) | 2024.06.23 |
Chapter2. 퍼셉트론 (1) | 2024.06.17 |