✅ MNIST
NIST는 미국 국립표준기술연구소(National Institute of Standards and Technology)의 약자입니다. 손으로 쓴 숫자들로 이루어진 대형 데이터베이스이며 NIST에서 모은 손글씨 데이터를 가지고 있습니다.
딥러닝 입문을 위한 가장 기본적인 데이터셋인 MNIST를 활용하여 딥러닝에 대해 학습해보겠습니다.
MNIST 데이터셋은 0부터 9까지의 숫자를 예측하는 다중 분류 문제입니다. 손으로 직접 쓴 흑백의 숫자 이미지(28 * 28)와 각 이미지에 해당하는 레이블(0에서 9까지)으로 이루어져 있습니다. 6만개의 학습데이터와 1만개의 테스트 데이터로 나누어집니다.
✅ 데이터 살펴보기
- import
import tensorflow as tf import matplotlib.pyplot as plt import numpy as np from keras.datasets.mnist import load_data from keras.models import Sequential from keras.layers import Dense from keras.utils import to_categorical
필요한 라이브러리를 선언하는 것부터 시작하겠습니다. tensorflow와 keras를 사용합니다. tensorflow의 현재 버전은 2.9.1입니다.
- 데이터셋 다운받기
(x_train, y_train), (x_test, y_test) = load_data(path='mnist.npz')
load_data()
함수는 MNIST 데이터셋을 (x_train, y_train), (x_test, y_test), 학습데이터와 테스트 데이터의 형태로 반환합니다.
- 데이터 형태 확인하기
#학습 데이터 확인 print(x_train.shape, y_train.shape) print(y_train) #데스트 데이터 확인 print(x_test.shape, y_test.shape) print(y_test)
학습데이터는 총 6만개이며, 이미지의 크기는 28 * 28 입니다. 0~9까지의 레이블을 가지고 있습니다.
테스트데이터는 총 1만개이며 이미지 크기와 레이블을 같습니다. 이 데이터를 한번 그림으로 그려보겠습니다.
- 데이터 그려보기
#데이터 그려보기 sample_size = 3 # 6만 개의 데이터중 무작위 3개를 뽑아서 확인해보기 random_idx = np.random.randint(60000, size=sample_size) # 그래프를 그릴 subplots 생성 fig, axes = plt.subplots(1, sample_size) for i, idx in enumerate(random_idx): img = x_train[idx, :] label = y_train[idx] #subplot에 이미지 그리기 axes[i].imshow(img, cmap='gray') axes[i].set_title(f'Lable: {label}') axes[i].axis('off') plt.show()
subplots()
를 사용하여 1행, 3열의 플롯을 만들고 그 위에 시각화하였습니다. 6만 개의 데이터 중에 random 하게 일부의 idx를 가져오고, 해당 idx를 조회하여 각각의 이미지를 각 열의 그렸습니다.-
axes[i].imshow(img, cmap='gray')
:imshow()
함수를 사용하여img
변수에 저장된 이미지를axes[i]
서브 플롯에 그립니다.cmap='gray'
는 이미지를 흑백으로 표시하기 위한 컬러맵을 설정하는 매개변수입니다. 'gray'로 설정하면 흑백 이미지로 표시됩니다.
-
axes[i].set_title(f'Label: {label}')
:set_title()
메서드를 사용하여axes[i]
서브 플롯에 제목을 설정합니다. 제목은 'Label' 문자열과label
변수의 값을 함께 표시합니다. 이는 해당 이미지의 레이블을 나타내는 역할을 합니다.
axes[i].axis('off')
:axis()
메서드를 사용하여axes[i]
서브 플롯의 축을 제거합니다.'off'
를 설정함으로써 x축과 y축을 표시하지 않고 숨깁니다.
-
✅ 데이터 전처리
- 검증 데이터 만들기
#검증 데이터 만들기 x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.3, random_state=777) print(f'훈련 데이터 {x_train.shape} 레이블 {y_train.shape}') print(f'훈련 검증 데이터 {x_val.shape} 레이블 {y_val.shape}')
원래 훈련 데이터를, 훈련 데이터 70%, 훈련 검증 데이터 30%의 비율로 나눕니다. random_state는 책과 동일한 학습을 위해 설정했습니다.
- 데이터 전처리
#데이터 전처리 num_x_train = x_train.shape[0] num_x_val = x_val.shape[0] num_x_test = x_test.shape[0] x_train = (x_train.reshape((num_x_train, 28 * 28))) / 255 x_val = (x_val.reshape((num_x_val, 28 * 28))) / 255 x_test = (x_test.reshape((num_x_test, 28 * 28))) / 255 print(x_train.shape) print(x_val.shape) print(x_test.shape)
.reshape()
메서드의 첫 번째 인자num_x_train
은 원래 배열의 차원 구조를 나타내는 값입니다. 2차원 배열인x_train
을 변환하기 위해 첫 번째 인자로num_x_train
을 전달하면, 해당 값은 원래 배열의 첫 번째 차원의 크기를 나타냅니다.두 번째 인자
28 * 28
은 변경하고자 하는 배열의 크기를 나타냅니다. 여기서28 * 28
은 784와 동일한 값을 가지며, 이는 1차원으로 변환된 배열의 길이를 나타냅니다.따라서,
x_train.reshape((num_x_train, 28 * 28))
코드는x_train
배열을 2차원 배열에서 1차원 배열로 변환하고, 변환된 배열의 크기를(num_x_train, 784)
로 설정하는 작업을 수행합니다.👉신경망은 입력 데이터의 스케일에 매우 민감하므로 적절한 전처리 과정이 필수입니다.
- 레이블 전처리
#레이블 전처리 y_train = to_categorical(y_train) y_val = to_categorical(y_val) y_test = to_categorical(y_test) print(y_train) print(y_val) print(y_test)
kears의
to_categroical()
를 사용했습니다. 해당 레이블을 범주형 레이블로 변환했습니다. 0과 1의 값만으로 해당 레이블을 표시하는, 원-핫 인코딩 방식을 사용합니다. 0~9의 값을 가지고 있는 레이블은 더 높은 값이 더 중요하다고 모델이 인식할 수 있습니다. 따라서 0과 1만 가지는 값으로 변경함으로써 어떤 레이블인지 인식만 할 수 있게 데이터를 전처리하는 것입니다.
✅ 모델 구성, 학습, 평가
3개의 Dense 층을 구성하는 Model을 만들겠습니다. 모델은 784개의 출력을 입력받고, 10개의 출력을 가집니다. Label이 0~9까지 10개이기 때문입니다.
- 모델 구성하기
#모델 구성하기 model = Sequential() #입력 데이터의 형태를 꼭 명시해야함. model.add(Dense(64, activation='relu', input_shape=(784, ))) model.add(Dense(32, activation='relu')) model.add(Dense(10, activation='softmax'))
범주형 데이터를 다루기 때문에 softmax 함수를 사용했습니다. 소프트맥스 함수는 일반적으로 확률을 구하는 방법과 비슷함으로 각 클래스에 해당하는 값들이 서로 영향을 줄 수 있어 비교에 용이합니다.
- 소프트 맥스 vs 시그모이드
#소프트 맥스 vs 시그모이드 def softmax(arr): m = np.max(arr) arr = arr - m arr = np.exp(arr) return arr / np.sum(arr) def sigmoid(x): return 1 / (1 + np.exp(-x)) case_1 = np.array([3.1, 3.0, 2.9]) case_2 = np.array([2.0, 1.0, 0.7]) np.set_printoptions(precision=3) # 소수점 제한 print(f'sigmoid {sigmoid(case_1)}, softmax {softmax(case_1)}') print(f'sigmoid {sigmoid(case_2)}, softmax {softmax(case_2)}') x = np.linspace(-10, 10, 100) y_softmax = softmax(x) y_sigmoid = sigmoid(x) fig, axes = plt.subplots(2, 1) axes[0].plot(x, y_softmax) axes[0].set_title('Softmax') axes[0].set_xlabel('x') axes[0].set_ylabel('y') axes[1].plot(x, y_sigmoid) axes[1].set_title('Sigmoid') axes[1].set_xlabel('x') axes[1].set_ylabel('y') plt.tight_layout() plt.show()
시그모이드 : 이진분류, 실수 값 기준으로 (0.5이상 or not) 0이나 1로 출력
소프트 맥스 : 다분류, 0~1 사이의 확률값(단, 결과 총합이 1이 된다)
- 소프트 맥스 vs 시그모이드
- 모델 학습 과정 설정
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
optimizer='adam'
Adam(Adaptive Moment Estimation)은 경사하강법(gradient descent)의 변종으로, 모멘텀(momentum)과 RMSprop의 아이디어를 결합한 알고리즘입니다. Adam은 학습률을 자동으로 조정하고, 각 가중치의 업데이트 속도를 조절하여 효율적인 모델 학습을 도와줍니다.
loss='categorical_crossentropy'
다중 분류 문제에서는 손실 함수를 “categorical_crossentropy”로 설정합니다. "categorical_crossentropy" 손실 함수는 실제 클래스와 예측 클래스 간의 크로스 엔트로피를 계산하여 모델의 손실을 정의합니다. 손실 함수는 각 클래스의 예측 확률과 실제 클래스의 원-핫 인코딩된 값 사이의 차이를 계산하고, 이를 모든 클래스에 대해 평균을 취하여 최종 손실 값을 계산합니다. 이렇게 계산된 손실 값을 최소화하는 방향으로 모델의 가중치를 조정하면서 훈련이 진행됩니다.
metrics=['accuracy']
모델의 성능을 평가하는 데 사용되는 평가 지표(metrics)로서 정확도(accuracy)를 지정했습니다.
- 모델 학습
history = model.fit(x_train, y_train, epochs=50, batch_size=128, validation_data= (x_val, y_val))
model.fit()
메서드는 주어진 매개변수에 따라 모델을 학습하고, 학습 과정에서 발생한 손실(loss)과 정확도(accuracy) 등의 정보를 반환하는History
객체를 반환합니다. 이History
객체는 모델의 학습 과정에서 발생한 정보를 기록하고 있으며, 이를 활용하여 학습 과정을 시각화하거나 추가 분석에 활용할 수 있습니다.x_train
: 학습에 사용될 입력 데이터입니다. 이는 모델에게 학습할 데이터의 특징(feature)을 제공합니다.
y_train
: 학습에 사용될 목표값(target) 데이터입니다. 이는 모델에게 학습할 데이터의 정답(label)을 제공합니다.
epochs
: 학습의 에포크(epoch) 수를 지정합니다. 에포크는 전체 학습 데이터셋을 한 번 훑는 것을 의미합니다. 주어진 수만큼의 에포크 동안 모델이 학습을 수행합니다.
batch_size
: 한 번의 반복(iteration)에서 처리할 데이터의 개수를 지정합니다. 큰 데이터셋을 작은 배치로 나누어 처리하므로 메모리 효율성을 높일 수 있습니다.
validation_data
: 검증(validation)에 사용될 데이터입니다. 이는 학습 도중 모델의 성능을 평가하는 데 사용됩니다. 주어진 데이터는 모델이 학습하지 않고, 모델의 성능을 평가하기 위해 사용됩니다.
- 학습 결과 그려보기
#학습 결과 그려보기 loss = history.history['loss'] val_loss = history.history['val_loss'] accuracy = history.history['accuracy'] val_accuracy = history.history['val_accuracy'] epochs = range(1, len(loss) + 1) fig, axes = plt.subplots(1, 2) axes[0].plot(epochs, loss, label="val_loss") axes[0].plot(epochs, val_loss, label="val_loss") axes[0].set_title("train and val loss") axes[0].set_xlabel('epochs') axes[0].set_ylabel('loss') axes[0].legend() axes[1].plot(epochs, accuracy, label="accuracy") axes[1].plot(epochs, val_accuracy, label="val_accuracy") axes[1].set_title("train and val accuracy") axes[1].set_xlabel("epochs") axes[1].set_ylabel("accuracy") axes[1].legend() plt.tight_layout() plt.show()
그래프를 살펴보면 10에폭까지는 학습은 잘 되었지만, 학습에 따라서 결과가 함께 상승하는 것이 아닌, 그 값이 오히려 떨어지는 모습을 보입니다. 이때 10에폭 이후부터는 과대적합(Over Fitting)되었다고 할 수 있습니다. 모델이 훈련 세트에 과하게 적합한 상태가 되어 일반성이 떨어지는 현상입니다.
- 모델 향상 시키기
#입력 데이터의 형태를 꼭 명시해야함. model.add(Dense(128, activation='relu', input_shape=(784, ))) model.add(Dense(64, activation='relu')) model.add(Dense(32, activation='relu')) model.add(Dense(10, activation='softmax')) #모델 학습과정 설정 및 학습 model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) history = model.fit(x_train, y_train, epochs=10, batch_size=64, validation_data= (x_val, y_val))
10에폭 이후에는 오버피팅이 발생하므로 10에폭까지만 학습시키겠습니다. 대신 더 상세한 표현을 위해 Desne층을 늘렸습니다.
✅ 모델 평가하기
- evaluate
모델에 테스트 데이터를 입력하여 손실(loss)과 지정한 지표(metrics)를 계산합니다.
model.evaluate(x_test, y_test)
[0.09501107037067413, 0.9779999852180481]
첫 번째 값은 손실값, 두 번째 값은 정확도를 나타냅니다. 97%의 정확도를 얻었습니다. 각 label의 확률을 예측해 보겠습니다.
- 각 Label 별 평균 예측값
#학습된 모델의 평균 예측값 results = model.predict(x_test) print(results.shape) np.set_printoptions(precision=3) # 각 클래스에 대한 확률 평균 계산 mean_results = np.mean(results, axis=0) sum = 0 # 각 클래스에 대한 확률 출력 for i, prob in enumerate(mean_results): print(f"Label {i}: {prob:.3f}") sum += prob rounded_sum = round(sum, 3) print(rounded_sum)
각 Label에 대한 예측값을 표시해보았습니다. 활성화 함수로 softmax를 사용했으므로 각각의 예측 수치를 다 합한 값은 1이 나오는게 맞습니다.
Uploaded by N2T