[TFLite] 4(1). 모델 직접 개발

6 minute read

모델 직접 개발


모델 선택

모델 직접 개발 방법의 경우 텐서플로를 이용하여 딥러닝 모델을 직접 개발하고 이를 변환하여 안드로이드 앱에 배포합니다.

모델 설계, 모델 훈련, 모델 변환 의 과정을 직접 할 수도 있고, 모델 설계만 텐서플로에서 제공하는 모델 아키텍처를 이용하는 방법으로 대체할 수도 있습니다.

이 방법은 모델 개발 방법 중 가장 많은 시간과 노력이 필요하지만 자유도가 가장 높기 때문에 문제에 최적화된 모델을 만들 수 있습니다.


모델 개발

여기서는 MNIST 데이터셋을 이용한 손글시 분류 모델을 만들어봅니다. 직접 설계한 간단한 다층 퍼셉트론과 함성곱 신경망을 구현하고, 텐서플로에서 제공하는 ResNet 아키텍처를 이용한 모델을 구현합니다.

모델을 개발하기 위해 저수준 API인 텐서플로와 고수준 API인 케라스를 사용합니다.


1. 데이터셋 준비

총 7만 개의 28x28 손글씨 이미지와 레이블이 제공되며, 이 중 6만 개는 학습 데이터로, 1만 개는 검증 데이터로 사용합니다.

import tensorflow as tf

# 데이터 가져오기
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 데이터 정규화 (0~1의 범위로 변환)
x_train, x_test = x_train / 255.0, x_test / 255.0
print(len(x_test), len(y_test))
10000 10000

데이터 정규화 공식은 다음과 같습니다.

  • (대상 값 - 입력 값의 최솟값) / (입력값의 최댓값 - 입력값의 최솟값)

데이터 정규화는 실제로 모델의 정확도를 높이는 데 도움이 됩니다.


2. 모델 설계 및 학습

2개의 완전 연결 계층으로 구성된 신경망을 생성합니다.

완전 연결 신경망의 경우 입력 데이터를 받기 전에 평면화(Flatten)해주어야 하고, 최종 출력은 0~9의 10개 원소로 이루어진 벡터입니다. 이미지 분류이므로 첫번째 계층의 활성화 함수로는 relu를 사용하고, 다중 분류이므로 최종(두번째) 계층의 활성화 함수로는 softmax를 사용합니다.

# 모델 구성: 순차형 API
input_shape = (28, 28)

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=input_shape))
model.add(tf.keras.layers.Dense(128, activation='relu'))
model.add(tf.keras.layers.Dense(10, activation='softmax'))

또는 아래와 같이 할 수도 있습니다.

# 모델 구성: 함수형 API
model_input = tf.keras.layers.Input(shape=input_shape)
output = tf.keras.layers.Flatten()(model_input)
output = tf.keras.layers.Dense(128, activation='relu')(output)
output = tf.keras.layers.Dense(10, activation='softmax')(output)
model = tf.keras.models.Model(model_input, output)

함수형 API를 이용하면 순차형 API보다 자유롭게 모델을 만들 수 있습니다.

모델이나 레이어의 다중 입력 또는 다중 출력을 구현할 수 있고 잔차 연결(residual connection), 다중 분기 (multi-branch) 등 비선형 토폴로지 모델을 구현할 수 있습니다.

또는 아래와 같이 Model 클래스를 상속하여 만들 수도 있습니다.

# 모델 구성: Model 클래스 상속을 통한 모델 생성
class Model(tf.keras.Model):
    def __init__(self):
        super(Model, self).__init__()
        self.flatten = tf.keras.layers.Flatten()
        self.dense = tf.keras.layers.Dense(128, activation='relu')
        self.softmax = tf.keras.layers.Dense(10, activation='softmax')
        
    def call(self, inputs):
        x = self.flatten(inputs)
        x = self.dense(x)
        return self.softmax(x)
    
model = Model()

위 3 가지 방법은 모두 같은 모델을 생성합니다.


모델 구성을 완료하면 모델을 컴파일하기 위해 아래와 같이 optimizer(옵티마이저), loss(손실 함수), metrics(평가 지표) 를 인자로 전달하고 compile() 메서드를 호출합니다.

# 모델 컴파일
model.compile(optimizer='adam', 
              loss = 'sparse_categorical_crossentropy', 
              metrics = ['accuracy'])

손실 함수에는 MSE, Cross Entropy 뿐 아니라 다른 많은 함수들이 존재합니다. 일반적으로 회귀 문제에는 MSE를 사용하고 분류 문제에는 CE를 사용합니다.

교차 엔트로피 오차는 문제 유형 및 데이터에 따라 세 가지 유형이 있는데, 이진 분류에는 binary_crossentropy를, 다중 분류에는 categorical_crossentropy 또는 sparse_categorical_crossentropy를 사용합니다. 입력 데이터의 레이블이 원-핫 인코딩 되어 있을 경우 categorical_crossentropy를 사용하고, 아닌 경우 sparse_categorical_crossentropy를 사용합니다.

원-핫 인코딩은 tf.keras.utils.to_categorical() 함수를 사용할 수 있습니다.

# 모델 구조 확인
model.summary()
Model: "model_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten_7 (Flatten)          multiple                  0         
_________________________________________________________________
dense_8 (Dense)              multiple                  100480    
_________________________________________________________________
dense_9 (Dense)              multiple                  1290      
=================================================================
Total params: 101,770
Trainable params: 101,770
Non-trainable params: 0
_________________________________________________________________


모델 설계 및 생성이 끝났으면 fit() 메서드를 호출하여 학습을 시작합니다.

# 모델 훈련
model.fit(x_train, y_train, epochs=10, verbose=1)
Epoch 1/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.2540 - accuracy: 0.9271
Epoch 2/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.1096 - accuracy: 0.9676
Epoch 3/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0747 - accuracy: 0.9776
Epoch 4/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0566 - accuracy: 0.9828
Epoch 5/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0437 - accuracy: 0.9867
Epoch 6/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0338 - accuracy: 0.9898
Epoch 7/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0269 - accuracy: 0.9916
Epoch 8/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0224 - accuracy: 0.9930
Epoch 9/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0178 - accuracy: 0.9944
Epoch 10/10
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0162 - accuracy: 0.9949





<tensorflow.python.keras.callbacks.History at 0x25a27df6820>


3. 모델 정확도 평가

학습이 완료되면 evaluate() 메서드를 호출하여 테스트 데이터를 가지고 모델의 정확도를 확인합니다.

model.evaluate(x_test, y_test, verbose=2)
313/313 - 1s - loss: 0.0757 - accuracy: 0.9790





[0.07569630444049835, 0.9789999723434448]


4. 합성곱 신경망

다층 퍼셉트론 모델의 평가 결과도 매우 높지만, 합성곱 신경망(CNN)을 이용하면 이미지 분류 모델의 정확도를 더욱 향상시킬 수 있습니다.

합성곱 계층은 완전연결계층에 비해 파라미터가 훨씬 적으며, 이미지의 공간적 특징을 잡아낼 수 있습니다.

합성곱 신경망을 사용하기 위해서는 입력 데이터의 형태를 (높이, 너비, 채널)의 3차원으로 바꿔주어야 합니다.

x_train_4d = x_train.reshape(-1, 28, 28, 1) # (60000,28,28) -> (60000,28,28,1)
x_test_4d = x_test.reshape(-1, 28, 28, 1)

cnn_model = tf.keras.models.Sequential([
    tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)),
    tf.keras.layers.MaxPooling2D((2,2)), 
    tf.keras.layers.Conv2D(64, (3, 3), activation='relu'), 
    tf.keras.layers.MaxPooling2D((2,2)), 
    tf.keras.layers.Conv2D(64, (3, 3), activation='relu'), 
    tf.keras.layers.Flatten(), 
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

cnn_model.compile(optimizer='adam', 
                  loss='sparse_categorical_crossentropy', 
                  metrics=['accuracy'])

cnn_model.summary()
cnn_model.fit(x_train_4d, y_train, epochs=10, verbose=1)
cnn_model.evaluate(x_test_4d, y_test, verbose=2)
Model: "sequential_7"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 26, 26, 32)        320       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 11, 11, 64)        18496     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 3, 3, 64)          36928     
_________________________________________________________________
flatten_8 (Flatten)          (None, 576)               0         
_________________________________________________________________
dense_10 (Dense)             (None, 64)                36928     
_________________________________________________________________
dense_11 (Dense)             (None, 10)                650       
=================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________
Epoch 1/10
1875/1875 [==============================] - 11s 4ms/step - loss: 0.1451 - accuracy: 0.9542 0s - l
Epoch 2/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0466 - accuracy: 0.9858
Epoch 3/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0328 - accuracy: 0.9899
Epoch 4/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0259 - accuracy: 0.9914
Epoch 5/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0205 - accuracy: 0.9935: 
Epoch 6/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0174 - accuracy: 0.9945
Epoch 7/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0129 - accuracy: 0.9962
Epoch 8/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0114 - accuracy: 0.9962
Epoch 9/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0108 - accuracy: 0.9965: 0s - loss: 0.0110 - accu
Epoch 10/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0090 - accuracy: 0.9970: 0s - l
313/313 - 1s - loss: 0.0373 - accuracy: 0.9913





[0.03732339292764664, 0.9912999868392944]

다층 퍼셉트론보다 파라미터의 수도 적고, 정확도는 더 높은 것을 확인할 수 있습니다.


5. 케라스 애플리케이션 모델

앞에서 구현한 다층 퍼셉트론과 합성곱 신경망은 비교적 간단한 모델이므로 구현이 어렵지 않았지만 ResNet, MobileNet, EfficientNet 등 최근 많이 사용되고 있는 모델은 훨씬 깊고 복잡한 구조를 가지고 있습니다.

텐서플로는 이러한 모델을 직접 구현하지 않아도 편리하게 이용할 수 있도록 케라스 애플리케이션 모듈에서 몇 가지 모델들을 제공합니다.

아래 코드는 그 중 ResNet 모델을 이용하여 MNIST 데이터로 훈련시킨 손글씨 분류를 구현합니다.

ResNet 인공 신경망은 네트워크의 깊이가 깊어질수록 더 복잡한 문제를 해결할 수 있지만 깊이가 지나치게 깊으면 기울기 소실 문제가 발생하여 성능이 급격히 떨어집니다.

ResNet은 이러한 문제를 잔차 학습을 이용하여 해결한 모델로, 잔차 블록을 여러 층 쌓은 구조입니다. 잔차 블록은 입력을 그대로 출력으로 연결하는 숏컷 연결을 가지고 있어서 네트워크가 깊어도 신호가 소실되지 않고 네트워크 전체에 영향을 줄 수 있습니다. KakaoTalk_20210807_204926789

잔차 연결(Residual Connection) 잔차 연결은 하위 층의 출력 텐서(x)를 상위 층의 출력 텐서(F(x))에 더해서(F(x) + x) 아래 층의 표현이 네트워크 위쪽으로 흘러갈 수 있도록 합니다. 하위 층에서 학습된 정보가 데이터 처리 과정에서 손실되는 것을 방지합니다. KakaoTalk_20210807_205114612


x_train_4d = x_train.reshape(-1, 28, 28, 1)
x_test_4d = x_test.reshape(-1, 28, 28, 1)

# (28, 28) -> (32, 32) 로 resizing (ResNet이 지원하는 최소 이미지 크기)
resized_x_train = tf.image.resize(x_train_4d, [32, 32])
resized_x_test = tf.image.resize(x_test_4d, [32, 32])

resnet_model = tf.keras.applications.ResNet50V2(input_shape = (32, 32, 1), 
                                                classes = 10, 
                                                weights = None)

# resnet_model.summary()

resnet_model.compile(optimizer='adam', 
                     loss = 'sparse_categorical_crossentropy', 
                     metrics = ['accuracy'])
resnet_model.fit(resized_x_train, y_train, epochs = 5, verbose = 1)
resnet_model.evaluate(resized_x_test, y_test, verbose = 2)
Epoch 1/5
1875/1875 [==============================] - 86s 44ms/step - loss: 0.2068 - accuracy: 0.9427
Epoch 2/5
1875/1875 [==============================] - 84s 45ms/step - loss: 0.1134 - accuracy: 0.9716
Epoch 3/5
1875/1875 [==============================] - 84s 45ms/step - loss: 0.0835 - accuracy: 0.9780
Epoch 4/5
1875/1875 [==============================] - 84s 45ms/step - loss: 0.0887 - accuracy: 0.9788
Epoch 5/5
1875/1875 [==============================] - 85s 45ms/step - loss: 0.0651 - accuracy: 0.9830
313/313 - 6s - loss: 0.0418 - accuracy: 0.9877





[0.04177485778927803, 0.9876999855041504]

ResNet 같은 깊은 신경망은 하나의 에포크를 완료하는 데 시간도 오래 걸리고 무조건 에포크를 늘리면 오히려 성능이 떨어지기 때문에 적절한 양의 학습이 필요합니다.

Categories:

Updated:

Leave a comment