반응형

[AI] MNIST 숫자 인식 인공지능 만들기

GitHub: https://github.com/PSLeon24/LearnAI/blob/main/%08mnist_deeplearning.ipynb

이번 포스팅에서는 손으로 쓴 숫자를 인식하는 딥러닝을 이용한 인공지능을 만들어 보고자 함

위 그림은 3을 4X5로 나타낸 것인데, 어떤 그림은 한눈에 3으로 알아차리기 쉬우나 몇몇 그림은 무엇인지 한눈에 알아차리기 쉽지 않음

만약 인공지능이 잘 알아맞힌다면 성능이 좋은 인공지능 → 따라서, 이러한 숫자를 알아맞히는 인공지능을 만드는 것이 목표

 

인공지능을 만들기 위해서는 인공지능이 학습할 수 있는 데이터가 필요함 → 이번 시간에 사용할 데이터는 MNIST 데이터셋

이 데이터는 아래와 같은 수많은 숫자가 포함된 총 70,000개의 손글씨 숫자가 있음

숫자는 총 0~9까지 10개의 숫자로 구성되어 있음

 

1.  개발 환경 만들기

본 개발 환경은 구글의 코랩을 사용함(구글 코랩 바로가기: https://colab.research.google.com/)

딥러닝 모델 개발을 위한 라이브러리 불러오기

· from tensorflow.keras.models import Sequential

기본적인 인공 신경망은 레이어(layer)가 순차적으로 구성되어 있음

입력층 → 은닉층 → 출력층

이렇게 순차적인 신경망을 구성할 때 사용할 수 있는 함수가 바로 케라스의 모델 도구(models) 중 시퀀셜 모델(Sequential) 함수

 

· from tensorflow.keras.layers import Dense, Activation

위 명령어는 레이어 도구(layers) 중 Dense와 Activation 도구를 불러오는 명령어

Dense: 전결합층(fully-connected layer)
→ 입력층(input layer)과 출력층(output layer) 사이에 위치하는 층

Dense를 사용하여 각 레이어의 뉴런 개수를 설정할 수 있음

Activation: 활성화 함수

 

· from tensorflow.keras.utils import to_categorical

위 명령어는 유틸 도구(utils) 중 to_categorical 함수를 불러오는 명령어

이번에 만들 인공지능은 0 ~ 9 사이에 있는 숫자 이미지를 구별하는 인공지능임

이때 이미지를 잘 학습시키기 위한 방법 중 하나로 *원-핫 인코딩을 사용하는데, 이를 구현할 수 있는 함수가 바로 to_categorical() 함수

*원-핫 인코딩(one-hot incoding): 하나의 값만 1로 나타내고 나머지 값은 모두 0으로 표시하는 방법

ex) mnist 데이터의 각 이미지는 0~9의 숫자 중 하나. 즉, 숫자 0을 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]로 나타내는 것임

 

· from tensorflow.keras.datasets import mnist

위 명령어는 케라스를 사용해 딥러닝 모델 개발을 연습할 수 있는 여러 데이터셋 도구(datasets)들 중 mnist 데이터셋을 불러오는 명령어

 

· import numpy as np

· import matplotlib.pyplot as plt

수학 계산 라이브러리numpy그래프 라이브러리맷플롯립의 pyplot 라이브러리를 불러오는 명령어

 

2. 데이터셋 불러오기

인공지능 모델을 만들기 위해서 훈련 데이터(train data)검증 데이터(test data)가 필요

학생이 시험을 보기 위해 문제집을 풀어서 공부한 후 시험을 침 → 이와 같이 훈련 데이터로 학습한 후 학습에 사용하지 않은 데이터인 검증 데이터로 인공지능의 성능을 평가해야 함

· (x_train, y_train), (x_test, y_test) = mnist.load_data()

mnist 데이터셋에는 load_data()라는 함수가 포함되어 있음

load_data(): mnist 데이터셋에서 데이터를 불러오라는 명령어

mnist 데이터는 훈련 데이터(x_train, y_train)와 검증 데이터(x_test, y_test)의 네 부분으로 나뉘어 있음

훈련 데이터는 각 손글씨 그림(x_train)그 그림이 어떤 숫자를 의미하는지(y_train)가 있으며 검증 데이터도 마찬가지임

 

· print("x_train shape", x_train.shape)

x_train 데이터의 형태를 출력하는 명령어

x_train.shape에서 shape를 사용하면 데이터 형태를 볼 수 있음

코드 실행 결과: x_train shape (60000, 28, 28)

x_train 데이터에는 총 60,000개의 데이터가 있고, 각 데이터에는 가로 28 x 세로 28의 데이터가 있으므로 x_train 데이터의 모습은 60000, 28, 28임

x_train 데이터 중 숫자 1을 나타냄

위 그림은 숫자 1을 나타냄. 검은색은 0, 흰색은 255, 회색은 1~254 사이의 숫자로 나타냄

 

· print("y_train shape", y_train.shape)

y_train 데이터의 형태를 출력하는 명령어

y_train 데이터는 x_train 데이터의 정답

코드 실행 결과: y_train shape (60000,) # x_train 데이터의 개수와 동일

y_train의 경우 1차원 배열인데, y_train 데이터는 x_train의 몇 번째 데이터의 값이 무엇인지를 나타내고 있기 때문임

 

· print("x_test shape", x_test.shape)

x_test 데이터의 형태를 출력하는 명령어

코드 실행 결과: x_test shape (10000, 28, 28)

x_train 데이터와 비교했을 때 데이터의 총 개수가 다름

 

· print("y_test shape", y_test.shape)

y_test 데이터의 형태를 출력하는 명령어

코드 실행 결과: y_test shape (10000,)

x_test shape와 마찬가지로 y_train과 비교했을 때 데이터의 총 개수가 다름

 

3. mnist 데이터셋에서 X의 형태 변형

인공지능 모델을 학습시키기 위해 28x28 형태의 데이터의 형태를 바꿀 필요가 있음

why? → 인공 신경망의 입력층에 넣을 때는 한 줄로 만들어 넣어야 하기 때문

반드시 한 줄로 넣어야 하나? → 항상 입력 데이터를 한 줄로 만들 필요는 X, 모델을 설계하는 방식에 따라 바뀜

 

먼저, 28x28의 데이터를 1x784 형태처럼 즉 일차원 배열의 형태로 만들고자 함(numpy의 reshape() 함수 사용)

· X_train = x_train.reshape(60000, 784) &X_test = x_test.reshape(10000, 784)

28x28 형태인 x_train 데이터와 x_test 데이터를 1x784로 바꿈

 

· x_train = X_train.astype('float32') & X_test = x_test.astype('float32')

정규화하기 위해 데이터를 0~1 사이의 값으로 바꿔야 함. 따라서 실수형으로 자료형을 변경

 

· X_train /= 255 & X_test /= 255

mnist 데이터는 검은색은 0, 흰색은 255, 회색은 1~254 사이의 값으로 이루어져 있음 이를 정규화하기 위해 데이터를 0~1 사이의 값으로 바꾸는 방법은 바로 255로 나누면 됨

 

4. mnist 데이터셋에서 Y의 형태 변형

y_train 데이터와 y_test 데이터의 형태를 인공지능이 분류를 잘 할 수 있도록 변형이 필요

인공지능은 이미지가 가진 숫자의 특성을 알 필요가 없음(ex: 숫자 간 대소관계)

따라서 이미지의 정답(label)을 인공지능에게 0, 1, 2, 3, 4, 5, 6, 7, 8, 9와 같이 알려주는 것이 아니라 0은 10개의 숫자 중 첫 번째, 4는 10개의 숫자 중 5번째 숫자로 변환해주는 것이 필요(수치형 데이터 ⇒ 범주형 데이터로 변환)

 

따라서, 정답 레이블을 순서로 나타내도록 데이터의 형태를 바꾸기 위해 원-핫 인코딩(one-hot incoding) 사용

· Y_train = to_categorical(y_train, 10) &Y_test = to_categorical(y_test, 10)

Y_train 데이터와 Y-test 데이터를 원-핫 인코딩

to_categorical() 함수는 수치형 데이터를 범주형 데이터로 만들어 주는 함수

이 함수를 사용하기 위해서는 변경 전 데이터와 원-핫 인코딩할 숫자, 즉 몇 개로 구분하고자 하는지가 필요(숫자는 총 10개이므로 10개가 필요. 따라서 10으로 설정)

만약 숫자가 3일 경우 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]과 같이 바뀜

 

5. 인공지능 모델 설계하기

이번에 설계하는 인공지능 모델은 단순히 아래와 같이 층을 설계

입력층(input layer: 784) → 은닉층1(hidden layer 1: 512) → 은닉층2(hidden layer 2: 256) → 출력층(output layer: 10)

우리가 넣는 데이터가 784개이므로 입력층의 뉴런의 수는 784개로 설정

이 데이터를 딥러닝 모델에 넣을 예정이며, 은닉층1의 노드는 512개로 설정, 그리고 은닉층1에서 은닉층2로 갈 때 활성화 함수는 렐루(ReLU) 함수를 사용

은닉층2의 노드는 256개로 설정, 마지막 층으로 갈 때에도 활성화 함수는 렐루 함수를 사용

마지막 노드는 입력된 숫자를 10개로 구분해야 하므로 10개로 설정, 또한 가장 높은 확률의 값으로 분류하기 위해 각 노드의 최종 값을 *소프트맥스(softmax) 함수를 사용

*소프트맥스 함수: 입력된 여러 값을 0~1 사이의 값으로 모두 정규화하며 출력하는 함수 → 그 출력값들의 합은 항상 1이 되는 특성이 있음 (분류 문제에서 어떤 범주를 가장 높은 확률로 예측하는지 살펴보는데 주로 사용함)

· model = Sequential()

이 인공지능 모델을 시퀀셜(Sequential) 방식으로 개발(딥러닝에 사용할 모델을 시퀀셜 모델로 정의)

 

· model.add(Dense(512, input_shape(784,)))

앞서 만든 딥러닝 모델에 512개로 구성되고 입력하는 데이터 형태는 (784,)인 '은닉층1'을 추가하는 명령어 모델에 add() 함수로 층(layer)을 추가

층이 어떤 형태인지를 설정하기 위해 Dense() 함수를 사용

Dense() 함수의 형태: Dense(해당 은닉층의 노드 수, input_shape)

 

· model.add(Activation('relu'))

다음 층으로 값을 전달할 때 렐루(ReLU) 함수를 사용

 

· model.add(Dense(256))

256개의 노드로 구성된 은닉층 2를 추가하는 명령어 두 번째 은닉층부터는 입력받는 노드를 설정해 줄 필요 X

 

· model.add(Dense(10))

10개 노드로 구성된 출력층을 추가하는 명령어(0~9까지의 수 중 하나로 결정되기 때문에)

 

· model.add(Activation('softmax'))

각 노드에서 전달되는 값의 총 합이 1이 되도록 소프트맥스 함수를 사용

 

· model.summary()

모델이 어떻게 구성되었는지 살펴보기 모델 구성 살펴보기

1. 모델은 시퀀셜(sequential) 모델로 구성되어 있음

2. 첫 번째 레이어는 512개의 노드로 이루어져 있으며 총 401,920(784*512+512)개의 파라미터로 이루어져 있음

→ 784개의 입력층에서 512개 은닉층으로 각각 연결되어 있어 784*512개만큼 가중치가 있고, 은닉층 각 노드 수만큼 편향(512)이 있기 때문임

3. 두 번째 레이어는 256개의 노드로 이루어져 있으며 총 131,328(512*256+256)개의 파라미터로 이루어져 있음

4. 마지막 레이어는 0부터 9까지의 수를 구분하기 위해 10개 노드로 이루어져 있으며 총 2,570(256*10+10)개의 파라미터로 이루어져 있음

 

6. 모델 학습시키기

모델을 데이터를 사용하여 심층 신경망을 딥러닝 기법으로 학습시킴

→ 신경망이 예측한 결과와 실제 정답을 비교한 후 오차(loss)가 있다면 다시 신경망을 학습시키는 과정을 거치고, 오차가 없다면 더 학습시킬 필요는 없으나 오차가 0으로 나오는 경우는 거의 없으므로 일반적으로는 학습시키는 횟수를 정하고 그만큼만 학습시킴

신경망을 잘 학습시키려면 학습한 신경망이 분류한 값과 실제 값의 오차부터 계산해야함 본 학습에서는 오차를 줄이기 위해 경사 하강법을 사용

첫 번째 epoch부터 10번째 epoch로 갈수록 오차값(loss)이 줄어드는 모습과 정확도(accuracy) 또한 증가하는 것을 볼 수 있음

· model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

compile() 함수: 심층 신경망의 학습 방법을 정하는 명령어

이 함수를 사용하기 위해서는 3가지 규칙이 있음

1) 오차값을 계산하는 방법을 알려줘야 함(본 인공지능은 다중 분류 문제이므로 'categorical_crossentropy' 방법 사용)

2) 오차를 줄이는 방법을 알려줘야 함(이를 위해 *옵티마이저를 사용)

*옵티마이저(optimizer): 딥러닝을 통해 인공지능 모델을 학습시킬 때 발생하는 오차를 줄이기 위해 경사 하강법이라는 알고리즘을 사용함. 이때 경사 하강법을 어떤 방식으로 사용할지 다양한 알고리즘이 있는데, 이를 모아놓은 것이 옵티마이저 라이브러리임

3) 학습 결과를 어떻게 확인할지 알려줘야 함(본 인공지능은 정확도로 모델의 학습 결과를 확인)

 

· model.fit(X_train, Y_train, batch_size=128, epochs=10, verbose=1)

케라스는 모델을 학습시키기 위해 '맞춘다'는 의미를 가진 fit() 함수를 제공

이 함수도 3가지 규칙이 있음

1) 입력할 데이터를 정함(X_train, Y_train)

2) *배치 사이즈를 정함

*배치 사이즈(batch size): 인공지능 모델이 한번에 학습하는 데이터 수, ex) batch_size=128이면 한 번에 128개 데이터를 학습

3) *에포크를 정함

*에포크(epochs): 모든 데이터를 1번 학습하는 것 ex) epochs=10이면 모든 데이터를 10번 반복해서 학습시킴

*verbose: 케라스 fit 함수의 결괏값을 출력하는 방법(0: 출력 안함, 1: 에포크별 진행 사항 출력, 2: 에포크별 학습 결과 출력 중 하나로 결정)

 

7. 모델의 정확도 살펴보기

· score = model.evaluate(X_test, Y_test)

케라스의 evaluate() 함수는 모델의 정확도를 평가할 수 있는 기능을 제공함

이 함수에는 테스트할 데이터(X_test)와 테스트할 데이터의 정답(Y_test)를 입력해야 함

evaluate() 함수는 결과값으로 오차값(loss)값과 정확도(accuracy)를 반환함

오차값은 0~1 사이의 값으로, 0이면 오차가 없는 것이고 1이면 오차가 아주 큰 것을 의미 정확도는 1에 가까울 수록 정답을 많이 맞춘 것을 의미

 

8. 모델 학습 결과 확인하기

이제 지금까지 만든 심층 신경망 모델이 어떤 그림을 무엇으로 예측했는지 모델 학습 결과를 확인해보고자 함

지금부터 하는 부분은 deeplearning programmin이라기보다 data visualization에 가까움

· predicted_classes = np.argmax(model.predict(X_test), axis=1)

앞서 만든 모델인 model에서 결과를 예측하는 함수인 predict 함수에 X_test 데이터를 입력

인공지능은 결과를 가장 높은 확률이 나온 것을 나타내는데 이때 넘파이의 argmax() 함수를 사용함

NumPy의 argmax() 함수 ← 여러 데이터 중에서 가장 큰 값이 어디에 있는지를 나타냄

행렬 데이터에서 argmax 함수를 사용하기 위해서는 열 또는 행 중에서 가장 큰 것을 고를지 알려줘야하는데 이때 기준을 정해주는 것이 바로 axis(축)

- axis=0: 각 열에서 가장 큰 수를 고른다는 뜻

- axis=1: 각 행에서 가장 큰 수를 고른다는 뜻

→ 본 데이터에서는 각 행에서 가장 큰 값을 찾아야하므로 'axis=1'로 설정

argmax() 함수를 사용해 인공지능 모델이 예측한 모든 값을 predicted_classes 변수에 저장

 

· correct_indices = np.nonzero(predicted_classes == y_test)[0]

다음으로는 인공지능이 잘 예측한 숫자의 모습이 무엇인지 찾아봐야 함

이를 위해 실제 값과 예측 값이 일치하는 값을 찾아내 correct_indices 변수에 저장하는 과정이 필요

논리 연산자 중 '같다'(==)를 사용해 예측 값(predicted_classes) 실제 값(y_test) 비교

두 값이 같으면 1(true), 다르면 0(false)이 반환됨 → 같은 값을 찾기 위해 모든 수를 다 확인하기는 현실적으로 어려움

이를 위해 NumPy의 nonzero() 함수를 사용 → 0이 아닌 값, 여기서는 1(예측 값과 실제 값이 일치)을 찾아냄

 

· incorrect_indicies = np.nonzero(predicted_classes != y_test)[0]

예측하지 못한 값을 찾기 위해서는 위의 과정과 거의 동일하지만 논리 연산자가 '같다'(==)가 아닌 '같지 않다'(!=)를 사용해 값을 찾음

 

9. 잘 예측한 데이터 살펴보기

앞의 8 과정을 통해 정확하게 예측한 데이터의 위치와 그렇지 않은 데이터의 위치를 알게 되었음

이제 이 데이터가 어떻게 생겼는지, matplotlib 라이브러리를 사용해 그래프를 출력해보고자 함

실행 결과, 예측한 값과 실제 값을 총 9개의 이미지로 출력

 

10. 잘 예측하지 못한 데이터 살펴보기

첫 번째 그림을 예로 들면 실제 정답 값은 9이지만, 예측 값은 3으로 잘 예측하지 못한 데이터임을 확인할 수 있음

위 결과를 살펴보면 생각보다 인공지능의 성능이 높지 않음

왜 그럴까? → 인공지능 모델의 학습 횟수 즉, epochs의 횟수가 10회 밖에 되지 않아 학습이 제대로 이루어지지 않음

epochs를 50으로 증가시킨 후 다시 분석해보기

epochs가 10일 때
epochs가 50일 때

epochs를 늘린 후 정확도(accuracy)는 올라가고 오차율(loss)은 떨어진 것을 확인할 수 있음

그렇다면, 모델의 학습 횟수를 늘리면 인공지능의 성능이 계속 높아질까?

하지만 무작정 모델의 학습 횟수만 늘린다면 과적합(overfitting)의 문제가 발생할 수 있음

과적합(overfitting): 훈련 데이터에만 최적화 되어 검증 데이터를 인공지능 모델에 입력했을 때 잘 구별하지 못하는, 즉 성능이 나빠지는 현상

 

따라서, 인공지능을 설계할 때는 단순히 epochs의 수만 아닌 레이어의 수나 각 레이어의 노드 수, 활성화 함수 등 다양한 파라미터를 수정해야 함

 

본 학습을 통해 만든 이 신경망이 여러 신경망 알고리즘의 기초이며, 이를 기초로 하여 순환 신경망, 적대적 생성 신경망 등 다양한 딥러닝 알고리즘이 만들어짐