이번 포스팅에서는 딥러닝(Deep Learning)이라고도 불리는 인공 신경망(ANN)에 대해 알아보고,
패션 MNIST 데이터셋으로 모델을 훈련시켜 패션 아이템을 분류해보는 실습을 해보도록 하자.
인공 신경망(ANN)은 우리가 흔히 이야기하는 딥러닝(Deep Laerning)과 거의 동의어로 사용되며,
위 그림처럼 층(layer)이라는 개념이 여러 개씩 쌓인 구조로 이루어져 있다.
층은 크게 '입력층 - 은닉층 - 출력층'으로 총 3종류로 구성되어 있다.
이 층(layer)에 대해 하나씩 알아보도록 하자.
입력층(Input Layer)은 단순히 값을 입력받는 역할만 수행하며, 특별히 계산을 하지는 않는다.
예를 들어, 지난 글에서 K-평균 모델로 3 종류의 과일을 분류할 때 사용했던 이미지를 떠올려보자.
([인공지능][실습] K-평균(K-Means) 알고리즘으로 과일(fruits) 사진을 분류해보고 엘보우(Elbow) 방법을 통해 최적의 k값을 찾아보자 : https://itstory1592.tistory.com/18)
과일 이미지는 100px * 100px 크기이며, reshape() 메소드를 사용하여 이미지를 펼쳤을 때는 총 10,000개의 픽셀로 이루어져 있었다.
여기서 우리는 픽셀 하나하나를 특징(feature)으로 생각하여 모델에게 픽셀을 특징의 값으로 전달하였다.
인공신경망(ANN)에서도 동일하게 픽셀을 입력값으로 입력층에 전달해주기만 하면 된다.
여기서는 픽셀이 10,000개이므로 특징 또한 10,000개가 될 것이다. 따라서 입력층 Xn의 n값은 10,000이 될 것이다.
즉, 입력층은 픽셀값 그 자체이고, 값을 입력받기만 하며 특별히 계산을 수행하지 않는다.
그다음으로 출력층(Output Layer)을 살펴보자.
위 그림처럼 클래스가 총 3개라면, 각 클래스에 대한 z1 ~ z3의 값을 계산해야 한다.
각 층을 거쳐 연산을 마치고 최종적으로 출력층에 도달하게 되면,
계산된 값을 바탕으로 클래스를 예측하기 때문에, 신경망의 최종 값을 만든다는 의미에서 출력층이라고 부르는 것이다.
인공 신경망에서는 z값을 계산하는 단위를 뉴런이라고 부른다.
이 단위가 뉴런이라고 불리는 이유는, 생물학적 뉴런에서 신호가 전달되는 과정과 구조가 마치 인공 신경망의 출력층에 있는 인공 뉴런과 비슷하기 때문이다.
하지만 뉴런에서 일어나는 일은 선형 계산이 전부이기 때문에 최근에는 뉴런이란 표현 대신에 유닛이라는 표현으로 많이 사용된다.
자, 그럼 마지막으로 은닉층(Hidden Layer)에 대해 알아보자.
은닉층은 입력층과 출력층 사이에 존재하는 층이다.
단순히 모델을 입력층과 출력층으로만 구성하게 되면, 정확도가 그리 높게 평가되지는 않을 것이다.
따라서 인공 신경망에는 몇 가지 층을 추가해줄 필요가 있는데, 이 층이 바로 은닉층이다.
(이렇게 은닉층을 추가하여 인공신경망을 복잡하게 구성한 신경망을 심층신경망이라고 부른다.)
은닉층에는 활성화(Activation) 함수가 함께 딸려오는데,
활성화 함수는 신경망 층의 '선형 방정식 계산 값에 적용하는 함수'이다.
이해를 위해 그림처럼 2개의 선형 방정식이 있다고 생각해보자.
왼쪽의 첫 번째 식에서 계산된 b가 두 번째 식에서 c를 계산하기 위해 사용된다.
하지만 두 번째 식에 첫 번째 식을 대입하면 오른쪽 식처럼 하나로 합쳐질 수 있고, 이에 따라 b는 사라지게 된다.
즉, b가 하는 일이 없는 셈인 것이다.
만약 신경망에서 은닉층이 선형적인 산술 계산만 수행한다면 사실상 은닉층의 역할이 없는 셈이나 다름 없어진다.
따라서 선형 계산을 log와 같이 비선형적으로 적당히 비틀어줄 필요가 있다.
그래야 다음 층의 계산과 단순히 합쳐지지 않고 나름의 역할을 할 수 있게 된다.
이 역할을 수행하는 것이 바로 은닉층의 활성화(Activation) 함수이다.
많이 사용하는 활성화 함수 중 하나는 이전 포스팅에서 알아보았던 시그모이드(Sigmoid) 함수이다.
시그모이드 함수는 출력을 0과 1로 압축하여 두 가지 클래스 중 하나를 선택하도록 도와준다.
이 외에도 많이 사용하는 활성화 함수로는 소프트맥수(Softmax) 함수, 렐루(Lelu) 함수, 하이퍼볼릭 탄젠트(Tanh) 함수가 있다.
추가적으로 은닉층에는 '한 가지 제약 사항'이 있는데,
은닉층의 뉴런 개수를 적어도 출력층의 뉴런보다는 많게 만들어주어야 한다는 점이다.
클래스 10개에 대한 확률을 예측해야 하는데 이전 은닉층의 뉴런이 10개보다 적다면 부족한 정보가 전달될 수 있기 때문이다.
지금까지 인공 신경망의 구조에 대해 모두 알아보았다.
이제 실제로 인공 신경망을 코드를 통해 구현해보고, 패션 MNIST 데이터셋을 사용하여 모델을 훈련시켜보자.
여기서 사용할 패션 MNIST 데이터셋은 클래스가 0~9까지의 숫자로 이루어진 패션 아이템 데이터 집합이다.
그림과 같이 운동화, 셔츠, 샌들 등의 작은 이미지들의 모음이며,
기본 MNIST 데이터셋과 같이 0~9 열 가지로 분류할 수 있는 28x28 픽셀의 이미지 70,000개로 이루어져 있다.
from tensorflow import keras
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
#훈련 데이터 shape
print(train_input.shape, train_target.shape)
#테스트 데이터 shape
print(test_input.shape, test_target.shape)
케라스(keras)에서는 편리하게도 패션MNIST 데이터셋을 불러올 수 있는 메소드를 제공해준다.
fashion_mnist_load_data() 메소드로 각각 훈련용 데이터셋과 테스트 데이터셋을 불러오고,
데이터가 어떤 형태로 이루어져 있는지 살펴보자.
불러온 데이터셋 중 훈련용 데이터셋은 총 60,000개, 28px * 28px 크기의 이미지로 이루어져 있으며,
테스트 데이터셋은 28px * 28px 크기의 이미지가 총 10,000개 포함되어 있다.
그럼 과연 어떤 이미지가 포함되어 있는 것일까?
아래의 코드를 통해 출력해보자.
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 10, figsize=(10,10))
for i in range(10):
axs[i].imshow(train_input[-i], cmap='gray_r')
axs[i].axis('off')
plt.show()
훈련용 데이터의 마지막에서 10개 이미지를 출력해보았다.
신발, 반팔 상의, 가방, 바지 등 여러 패션 아이템으로 구성되어 있음을 확인해볼 수 있다.
이 아이템에 해당하는 레이블(label)은 0~9 사이의 숫자 중 무엇일까?
print([train_target[-i] for i in range(10)])
패션 MNIST 데이터셋은 0~9까지의 숫자로, 총 10개의 클래스로 이루어져 있다고 했다.
출력된 결과를 봤을 때, 부츠는 9로 나타나며 슬리퍼는 5, 바지는 1로 구분되어 있는 것 같다.
이처럼 패션MNIST 데이터셋은 의류 카테고리별로 0~9라는 각기 다른 숫자로 구성되어 있다.
참고 : 각 카테고리별 레이블과 클래스
import numpy as np
print(np.unique(train_target, return_counts=True))
각 카테고리별로 의류가 몇 개씩 포함되어 있는지 확인해보기 위해 넘파이의 unique메소드로 target을 출력해본 결과
훈련용 데이터셋은 의류 카테고리별로 6,000개씩 균등하게 나눠져있음을 알 수 있었다.
import tensorflow as tf
from tensorflow import keras
from sklearn.model_selection import train_test_split
train_scaled = train_input / 255.0
train_scaled = train_scaled.reshape(-1, 28*28)
train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
print(train_scaled.shape, train_target.shape)
print(val_scaled.shape, val_target.shape)
코드를 살펴보면 train_input을 255.0으로 나누어주는 부분이 보일 것이다.
값을 255.0로 나누는 이유는, 만약 특성마다 값의 범위가 많이 다르면 올바르게 손실 함수를 구할 수 없기 때문이다.
패션 MNIST의 경우 각 픽셀은 0~255 사이의 정수값을 가지며,
이런 이미지의 경우 보통 255로 나누어 0~1 사이의 값으로 정규화한다.
표준화는 아니지만 양수 값으로 이루어진 이미지를 전처리(Scaling)할 때 널리 사용하는 방법이다.
그리고 검증(Validation)을 위해, 전처리한 60,000개의 데이터셋을 훈련세트와 검증세트로 다시 나누어준다.
코드를 출력해보면 48,000개와 12,000개의 데이터로 나누어진 모습을 확인할 수 있다.
model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28, 28)))
model.add(keras.layers.Dense(100, activation='relu', name='hidden1'))
model.add(keras.layers.Dense(10, activation='softmax'))
model.summary()
이제 본격적으로 나만의 인공 신경망을 만들어보자!👍
우선, 28px * 28px의 이미지를 펼쳐주기 위해 Flatten층을 추가하여 784개로 넓게 펼쳐준다.
(사실 Flatten은 입력 데이터를 펼치는 역할밖에 하지 않기때문에 층이라고 부르지는 않는다.)
그리고 은닉층을 추가하는데, 해당 예제에서는 100개의 뉴런과 활성화 함수로는 렐루(Relu) 함수로 이루어진 층을 사용하겠다.
렐루 함수는 0보다 작은 값은 모두 0으로 취하는 함수이다.
마지막엔 층엔 10개의 클래스들 중 가장 높은 z값의 아이템 1개를 고를 수 있도록, 소프트맥스(Softmax) 함수를 사용하는 층을 추가해준다.
이 model에는 summary() 라는 메소드가 있는데, 이 메소드를 호출하면 우리가 구성한 모델의 구조를 출력해준다.
첫 번째 flatten_1을 보면, 28px * 28px을 펼쳐주었기 때문에, 784개의 입력 특성이 있는 걸 확인할 수 있으며,
Flatten은 픽셀을 넓게 펼치는 역할밖에 하지 않아 Param이 0으로 출력되었다.
두번째로, name을 'hidden1'로 지정한 은닉층을 보면, 아까 설정한 바와 같이 100개의 뉴런으로 구성되어 있다.
Param이 78500개인 이유는 784개의 특성과 100개의 뉴런이 연결되었기 때문이며 절편 100개가 추가로 있기 때문이다.
이 그림에서 층과 층 사이에 줄처럼 연결된 것이 있는데,
이 줄이 총 78,400개 연결되어 있고, 절편이 100개 있기 때문에 Param이 78,500개인 것이다.
마지막 softmax를 사용한 층에서는 10개의 뉴런을 지정해주었기 때문에,
은닉층의 뉴런 100개와 출력층의 뉴런 10개가 연결되어 1,000개의 Param과 출력층 10개의 절편으로 구성되어
총 1,010개의 파라미터가 생성되었다.
이렇게 summary() 함수를 사용하면 내가 구성한 모델의 구조를 보기 쉽게 파악할 수 있는 장점이 있다.
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
model.evaluate(val_scaled, val_target)
이제 이 모델을 컴파일하기 위해 compile() 메소드를 호출해주는데,
이때 옵티마이저(optimizer)는 'adam'으로 설정해주고, 손실 함수로는 '다중 분류 손실 함수'를 사용하겠다.
옵티마이저는 확률적 경사 하강법에서의 개념을 인공 신경망에 적용할 수 있도록 도와주는 것이라고 이해하면 된다.
에폭스(epochs)는 5로 설정하여, 데이터셋을 총 5번 사용하여 훈련하도록 지정해주었다.
훈련 과정을 마친 후, 아까 따로 준비해두었던 검증 세트로 모델을 평가해보자.
위 코드를 실행하면, 총 5번에 걸쳐 훈련하는 과정과 손실 함수, 정확도를 출력해준다.
결과적으로 손실 함수 0.33과 정확도 0.88의 모델이 만들어졌다.
print(model.predict_classes(test_input[:5]))
fig, axs = plt.subplots(1, 5, figsize=(10,10))
for i in range(5):
axs[i].imshow(test_input[i], cmap='gray_r')
axs[i].axis('off')
plt.show()
이제 마지막으로 모델에게 테스트 데이터셋에서 5개 정도를 골라서 맞춰보도록 시켜보자.
맞춘 아이템은 데이터의 예측 클래스가 출력되도록 코딩하였고, 이미지로도 확인해볼 수 있도록 하였다.
한번 결과를 확인해보자😊
코드를 실행해본 결과 [부츠, 상의, 바지, 바지, 셔츠]이며 레이블은 [9 2 1 1 6]가 출력되었다.
이 레이블을 참고하여 결과를 비교해 보면, 모델이 입력한 옷 이미지의 카테고리를 정확히 맞추었다.
이번 포스팅에서는 간단한 인공 신경망(ANN)을 구축하여 패션 아이템을 분류해보는 시간을 가져보았다.
또한, 층을 여러개 추가하여 인공 신경망을 깊게 만들어 주기 때문에 딥러닝이라고 부른다는 사실도 알 수 있었다.
전체 소스 코드 :
https://colab.research.google.com/drive/1PTUvzwXETXyz01HPuBm5Kc5NDPmzQJhu
👍클릭으로 구독하기👍
(이해가 다소 힘들거나, 틀린 부분이 있다면 댓글 부탁드리겠습니다! 😊)
💖도움이 되셨다면 '구독'과 '공감' 부탁드립니다!💖