지난 블로그, 신경망의 출력층에 대하여 알아보고 설계에 대하여 알아보았습니다.
2023.10.11 - [Programming/Deep Learning] - [Python/DeepLearning] #6. 출력층(output layer) 설계
이번 블로그에서는 출력층의 뉴런 개수를 정하는 방법에 대하여 알아보고, Tensorflow MNIST 데이터를 불러오고 형상 다루는 방법과 추론/분류를 수행하는 신경망을 구현해 보도록 하겠습니다!
출력층의 뉴런 수 정하기
출력층의 뉴런 개수는 적절하게 정해줘야 합니다. 분류문제 에서는 분류하고 싶은 클래스의 개수대로 설정하는 것이 일반적인 상황이라고 볼 수 있습니다. 예를 들어 필기체 숫자 이미지를 입력해 숫자 0부터 9 중 하나로 분류하는 문제라면 출력층의 뉴런을 10개로( 숫자는 0 ~ 9까지 10개이기 때문에 ) 설정합니다.
손글씨 숫자 인식
신경망의 구조를 배웠으니, 실제 손글씨 필기체 숫자 인식을 해보도록 하겠습니다. 이미 학습된 매개변수를 사용하여 학습하는 과정은 생략하며, 추론(분류) 하는 과정만 구현해 볼 것입니다. 앞에서 뒤로 순서대로 이동하며 추론을 하기 때문에 이러한 추론 과정을 신경망의 순전파(forward propagation)라고도 합니다.
먼저 유명한 손글씨 데이터셋인 MNIST 손글씨 숫자 이미지 집합을 가져와 보겠습니다. MNIST 데이터셋은 0부터 9까지의 숫자 이미지로 구성되어 있으며, 훈련이미지가 60,000장, 테스트용 이미지가 10,000장이 준비되어 있습니다.
훈련 이미지를 사용하여 모델을 학습하고, 학습한 모델로 시험 이미지들을 얼마나 정확하게 분류했는지 평가할 수 있습니다.
MNIST 이미지는 28px * 28px의 그레이스케일 이미지이며, 각 픽셀은 0 ~ 255 까지의 값을 취하고, 각 이미지에는 해당 이미지에 맞는 숫자가 레이블로 붙어 있습니다.
필요한 모듈은 다음과 같습니다.
- mnist.py : 이미지를 내려받고 numpy 배열로 변경해 주는 역할 및 실제 MNIST 데이터를 가져올 수 있는 역할을 합니다.
먼저 mnist 파일을 import 해주세요!
# tensorflow의 keras를 이용해서 MNIST 불러오기
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
# mnist 데이터셋 로딩
from tensorflow.keras import datasets
mnist = datasets.mnist
(X_train, y_train), (X_test, y_test) = mnist.load_data()
모아놓은 데이터의 형상(shape) 부터 반드시 확인할 것!
# 이미지 시각화
image = X_train[0] # 첫 번째 이미지 가져오기
image.shape
# matplotlib의 imshow를 사용한 이미지 시각화
plt.imshow(image, 'gray')
plt.title(y_train[0])
plt.show()
plt.imshow(X_train[1], 'gray')
plt.title(y_train[1])
plt.show()
신경망 준비
훈련과 학습 모두다 똑같습니다.
1차원 형태로 데이터를 받는 레이어를 다음과 같이 이야기합니다.
- 신경망 - Dense Layer
- 기하학 - Affine Layer
- 통합적 - Fully Connected Layer (완전 연결 계층)
세 방식의 공통점 : 입력되는 데이터의 차원이 (N, M) 여기서 N은 BATCH_SIZE, M은 데이터의 스칼라 개수
- 배치를 이야기하지 않으면 (M, )
- 배치를 이야기하면(N, M)
CNN은 Fully Connected Layer가 아니고, 2차원 BATCH 데이터를 사용하기 때문에 입력 데이터의 형상이 (N, H, W, C)가 된다.
image = X_train[0].flatten()
image.shape
image = X_train[0].reshape(-1)
image.shape
여기서 의문!
ravel()을 사용해도 되는가?
image = np.ravel(X_train [0])
원본 이미지가 훼손될 염려가 있기 때문에 잘 사용하지 않습니다.
위처럼 2차원 데이터를 1차원으로 펴는 작업을 데이터 평탄화라고 한다.
image.shape
# 평탄화된 이미지를 원래대로 복구
image_bokgu = image.reshape(28, 28) # 원래 이미지의 픽셀인 28 * 28 형태의 배열로 복구
plt.imshow(image_bokgu, 'gray')
plt.show()
신경망의 분류 처리
MNIST 데이터셋을 가지고 추론(분류)을 수행하는 신경망을 구현해볼 차례입니다.
이 신경망은 각 픽셀 하나하나의 입력을 받기 때문에 총입력은 784개가 되며( 28px * 28px ), 출력층은 10개로 설정(숫자 0~9를 분류할 것이기 때문에)할 것입니다.
은닉층은 총두 개로, 첫 번째 은닉층은 50개의 뉴런을, 두 번째 은닉층에는 100개의 뉴런을 배치해 보도록 하겠습니다. 50개, 100개의 뉴런들은 임의로 배치시킨 것입니다.
MNIST 분류 신경망을 함수화 시켜서 만들어 보도록 하겠습니다. 이때 임의의 가중치와 절편이 들어있는 sample_weight.pkl 파일을 읽어와야 하기 때문에 첨부된 파일을 반드시 실행시키고 있는 폴더 안쪽에 같이 넣어주세요!
MNIST 손글씨 데이터셋을 위한 신경망 만들기 Fully Connected Layer ( ANN - Affine Neural Network)
# 활성화 함수 구현( 시그모이드 )
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 테스트용 데이터 가져오기
def get_test_data():
(X_train, y_train), (X_test, y_test) = mnist.load_data()
IMAGE_SIZE = X_test.shape[0]
X_test_reshaped = X_test.reshape(IMAGE_SIZE, -1)
return X_test_reshaped, y_test
# 훈련된 신경망 가져오기
def init_network():
import pickle
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']
# 구현시에 나는 대부분의 오류는 데이터의 shape 때문이다.
# 1. 각 층은 입력되는 값과 해당층의 가중치를 곱하고 편향을 더한다.
# 2. (1)에 의해서 계산된 값에 각 층의 활성화 함수를 씌워주고 다음층으로 넘긴다.
# Layer 1 계산하기 ( 입력 : x )
z1 = np.dot(x, W1) + b1
a1 = sigmoid(z1)
# Layer 2 계산하기 ( 입력 : a1 )
z2 = np.dot(a1, W2) + b2
a2 = sigmoid(z2)
# Layer 3 계산하기 ( 입력 : a2 ) - 출력층이기 때문에 활성화 함수로 softmax를 사용한다.
z3 = np.dot(a2, W3) + b3
y = softmax(z3)
return y
init_network() 함수를 이용해 필요한 가중치와 절편 등 신경망이 직접 습득해야 할 정보들을 미리 저장하고 가져왔습니다.
이 세 가지 함수를 이용해 신경망에 의한 분류를 수행해 보고, 정확도를 평가해 보겠습니다.
X, y = get_test_data()
test_image, test_target = X[0], y[0]
test_image.shape, test_target.shape
network = init_network()
test_result = predict(network, test_image)
test_result, test_target
np.argmax(test_result) == test_target
plt.imshow(test_image.reshape(28, 28), 'gray')
plt.title(test_target)
plt.show()
network의 정확도 accuracy 구하기
X, y = get_test_data()
network = init_network()
accuracy_count = 0 # 정답을 맞춘 개수를 저장 -> 맞췄을 떄 1 증가
# 사진을 한장씩 꺼내서 predict 하기
for i in range(len(X)):
pred = predict(network, X[i]) # (10, )
pred = np.argmax(pred) # 확률이 가장 높은 원소의 인덱스
# 예측한 것과 정답이 같으면
if pred == y[i]:
accuracy_count += 1 # 정답 카운트를 1 증가
print(float(accuracy_count) / len(X)) # 평균 정답 구하기 -> 정확도
위의 코드는 for 문을 돌며 x_test에 들어있는 데이터를 하나씩 predict 함수를 통해 테스트용 데이터인 t와 일치하는지를 묻는 코드입니다.
predict 함수의 결과는 softmax 함수에 의해 [0.1, 0,3, 0.6, 0.9.. ]의 배열 형태로 예측하고, argmax 함수를 이용해 최고로 잘 분류된 확률을 뽑아내게 됩니다.
argmax에 의해 등장한 인덱스가 곧 숫자가 되고, 정답 레이블과 비교하여 분류 정확도를 얻게 됩니다.
가중치와 편향을 미리 가져온 신경망 이기 때문에 추후에 신경망 구조와 학습 방법을 고민하여 정확도를 높여볼 수 있을 것 같습니다!
mnist의 normalize가 True입니다.
이는 픽셀의 범위인 0 ~ 255를 0 ~ 1 사이로 축소시키는 MinMaxScaler와 같은 역할을 합니다.
딥러닝에서 전처리는 항상 자주 사용됩니다.
데이터 전체의 분포를 균일하게 바꿔줄 수 있기 때문이죠!
위의 코드의 단점
- 10,000장의 이미지를 한 장씩 예측을 하고 있다.
- 시간이 오래 걸린다는 이야기이다.
어떻게 해결할 수 있을까?
- BATCH를 사용해서 한 장씩이 아닌 뭉터기로 예측을 하게 하겠다.
- 배치란 데이터의 뭉터기
배치 처리
입력 데이터와 가중치 매개변수의 shape를 확인해 보겠습니다.
network = init_network()
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3'],
W1.shape, W2.shape, W3.shape
b1.shape, b2.shape, b2.shape
이미지 한 장을 예측 (predict)
- 각 은닉층의 활성화 함수는 𝑠𝑖𝑔𝑚𝑜𝑖𝑑로 설정
- 제일 마지막 출력층의 활성화 함수는 𝑠𝑜𝑓𝑡𝑚𝑎𝑥로 설정
전체적으로 봤을 때 원소 784개로 구성된 1차원 배열이 입력되어 마지막에는 원소가 10개인 1차원 배열이 출력되는 구조로 볼 수 있습니다. 이는 이미지 데이터가 1장만 입력했을 때의 결과이며, 100개를 입력했을 경우에는 100장 분량의 데이터를 하나의 입력 데이터로 표현하면 될 것 같습니다.
위의 예에서는 10000장을 입력했기 때문에 결과는 10000장의 결과가 배열의 형태로 한꺼번에 출력될 것입니다. 예를 들어 x [0]의 데이터는 y [0]으로, x [1]의 데이터는 y [1]로 말이죠.
이처럼 하나로 묶은 입력 데이터를 배치라 합니다. 배치란 곧 입력 데이터들의 묶음이라고 생각할 수 있습니다.
배치 처리에 의해 컴퓨터로 계산할 때 큰 이점을 줍니다. 이미지 1장당 처리 시간을 대폭 줄여주게 되는데, 대부분의 수치 계산 라이브러리들이 큰 배열을 효율적으로 처리할 수 있도록 고도의 최적화가 되어 있기 때문입니다.
배치 처리를 함으로 인해서 큰 배열로 이뤄진 계산을 하게 되는데, 컴퓨터에서는 큰 배열을 한꺼번에 계산하는 것이 분할된 작은 배열을 여러 번 계산하는 것보다 빠릅니다.
이전에 구현한 내용은 배치라고 보기엔 약간 애매모호합니다. 데이터를 하나씩 분류(predict)하고 있기 때문이죠, 따라서 배치 처리 개념을 위 코드에 추가해 보도록 하겠습니다.
# 배치를 활용한 예측
X, y = get_test_data()
network = init_network()
# 배치란? 데이터의 묶음이다.
# batch_size : 1 배치당 들어있어야 하는 데이터의 개수
# ex) 60,000개의 데이터를 batch_size 100으로 묶으면 600개의 배치가 생긴다.
batch_size = 100 # 배치 크기 지정
accuracy_count = 0
# batch_size 개씩 건너 뛰면서 예측 수행
for i in range(0, len(X), batch_size):
X_batch = X[i : i + batch_size]
pred_batch = predict(network, X_batch) # 데이터를 100개씩 예측
pred_batch = np.argmax(pred_batch, axis=1) # axis 설정을 어떻게 해야 할까요?
accuracy_count += np.sum( pred_batch == y[i : i + batch_size]) # 예측값 + 정답 (100개씩 한꺼번에 계산)
print(float(accuracy_count) / len(X)) # 전체 평균 구하기
x [i : i+batch_size]가 입력 데이터의 i 번째부터 i + batch_size 번째까지의 데이터를 묶어주는 부분입니다.
batch_size가 100이므로 100개씩 이미지를 묶어서 예측하고 계산처리를 하게 됩니다.
배치를 활용해서 데이터를 모두 소모하면( 활용하면 ) 이것을 1 에폭(epoch)이라고 한다.
np.argmax의 axis가 1인 이유
- 𝑠𝑜𝑓𝑡𝑚𝑎𝑥의 결과는 10개의 원소(결과물)를 가진 "1차원 배열"
- 이 결과물이 100개씩 묶음 지어져 있다. (batch)
- np.argmax의 axis를 1로 줘야지만 각 행별로 최댓값의 인덱스를 구한다.
- axis=0은 1차원 배열이 추가되는 방향이기 때문에 "0"
이상으로 신경망의 순전파에 대해 살펴봤습니다.
신경망의 각 층의 뉴런들이 다음 층의 뉴런으로 신호를 전달한다는 점에서 앞 장의 퍼셉트론과 같습니다.
2023.08.16 - [Programming/Deep Learning] - [Python/DeepLearning] #2. 퍼셉트론 (단층 & 다층)
하지만 다음 뉴런으로 갈 때 신호를 변화시키는 활성화 함수에 큰 차이가 있습니다.
신경망에서는 매끄럽게 변화하는 시그모이드 함수를, 퍼셉트론에서는 갑자기 변화하는 계단 함수를 활성화 함수로 사용했습니다.
이 차이가 신경망 학습에 매우 중요하다는 점입니다.
다음 블로그에서는 손실 함수에 대하여 알아보도록 하겠습니다!
'Programming > Deep Learning' 카테고리의 다른 글
[Python/DeepLearning] #9.1. 수치 미분과 경사하강법(상) (0) | 2024.01.29 |
---|---|
[Python/DeepLearning] #8. 손실 함수의 종류(Loss Function) (1) | 2024.01.27 |
[Python/DeepLearning] #6. 출력층(output layer) 설계 (1) | 2023.10.11 |
[Python/DeepLearning] #5. 행렬과 신경망 (0) | 2023.08.30 |
[Python/DeepLearning] #4. 다차원 배열의 계산 (0) | 2023.08.23 |