본문 바로가기

Back/Deep Learning

[Python][Keras ][Tensorflow] Keras 모델 TFlite 변환과 최적화

 

이전글

더보기

2020/09/23 - [Python/Deep Learning] - [Keras] 모델 저장하기

 

[Keras] 모델 저장하기

딥러닝은 모델을 학습시기며 학습된 모델을 이용하여 결과를 예측하거나 결과물을 생성해냅니다. 이러한 모델들은 학습이 완료된 뒤(혹은 학습중) 저장하여 사용할 수 있습니다. 모델을 저장하

hidden-loca.tistory.com

2020/09/24 - [Python/Deep Learning] - [Keras] 모델 불러오기

 

[Keras] 모델 불러오기

2020/09/23 - [Python/Deep Learning] - [Keras] 모델 저장하기 [Keras] 모델 저장하기 딥러닝은 모델을 학습시기며 학습된 모델을 이용하여 결과를 예측하거나 결과물을 생성해냅니다. 이러한 모델들은 학습

hidden-loca.tistory.com

2020/10/08 - [Python/Deep Learning] - [Tensorflow] Tensorflowlite를 이용한 Image classification model maker

 

[Tensorflow] Tensorflowlite를 이용한 Image classification model maker

다른 게시물들을 통해 딥러닝 모델들을 저장하는 방법에대해 알아보았습니다. 2020/09/23 - [Python/Deep Learning] - [Keras] 모델 저장하기 2020/09/24 - [Python/Deep Learning] - [Keras] 모델 불러오기 저장..

hidden-loca.tistory.com

2020/10/20 - [Python/Deep Learning] - [Tensorflow] Image classification model maker예측하기.

 

[Tensorflow] Image classification model maker예측하기.

tflite-model-make 설치와 모델생성. 더보기 2020/10/08 - [Python/Deep Learning] - [Tensorflow] Tensorflowlite를 이용한 Image classification model maker 이전 글을 통해 아주 쉽게 예측 모델을 생성해 내었..

hidden-loca.tistory.com

2020/11/02 - [Python] - [Python] Flask를 이용한 Tflite Imageclassfier REST API 구성

 

[Python] Flask를 이용한 Tflite Imageclassfier REST API 구성

이전글 더보기 2020/10/21 - [Python] - [Python] Flask 살펴보기 [Python] Flask 살펴보기 Flask? Flask는 파이썬에서 사용가능한 웹프레임워크 중 하나입니다. 개발사에 따르면 '마이크로'프레임 워크이며 이 마.

hidden-loca.tistory.com

 


1. Keras 모델 생성,

예시로 들기 위한 모델을 생성해 보겠습니다.

from tensorflow.keras import backend as K
from tensorflow.keras import layers as L
from tensorflow.keras.models import Model
from tensorflow.keras.regularizers import l2
from tensorflow.keras.callbacks import ModelCheckpoint,ReduceLROnPlateau,EarlyStopping

def Make_model(train,val):
    K.clear_session()

    model_ckpt = ModelCheckpoint('test.h5',save_best_only=True)
    reduce_lr = ReduceLROnPlateau(patience=30,verbose=1)
    early_stop = EarlyStopping(patience=5,verbose=2,monitor='accuracy')

    entry = L.Input(shape=(225,225, 3))
    x = L.SeparableConv2D(256, (10,10), activation='relu')(entry)
    x = L.MaxPooling2D((2, 2))(x)
    x = L.SeparableConv2D(512, (3, 3), activation='relu')(x)
    x = L.MaxPooling2D((2, 2))(x)
    x = L.SeparableConv2D(1024, (3, 3), activation='relu')(x)
    x = L.GlobalMaxPooling2D()(x)
    x = L.Dense(256)(x)
    x = L.ReLU()(x)
    x = L.Dense(64, kernel_regularizer=l2(2e-4))(x)
    x = L.ReLU()(x)
    x = L.Dense(2, activation='softmax')(x)

    model = Model(entry,x)
    model.summary()
    model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])

    history = model.fit_generator(train,validation_data=val,epochs=600,
                                  callbacks=[model_ckpt,reduce_lr,early_stop],verbose=1)
    return history

이전 점자판별을 위한 모델과 동일한 구조의 모델을 구성합니다. 달라진점은 마지막 소프트 맥스에서 값이 2개로 분류되며 학습에 사용되는 컴퓨터의 성능이 증가하여 이미지의 크기가 증가하였습니다. 

 

2. datagenerator

from tensorflow.keras.preprocessing.image import ImageDataGenerator

def data_ready():
    images_dir = '../python/apple'

    datagen = ImageDataGenerator(rotation_range=5,
                                 shear_range=5,
                                 validation_split=0.2
                                 ) #20%를 검증모델로 사용.

    train_generator = datagen.flow_from_directory(images_dir,
                                                  target_size=(225, 225),
                                                  subset="training")

    val_generator = datagen.flow_from_directory(images_dir,
                                                target_size=(225, 225),
                                                subset="validation")

    return train_generator, val_generator

def load_image(img_path):
    images_dir = img_path
    datagen = ImageDataGenerator()
    real_generator = datagen.flow_from_directory(images_dir,
                                                 target_size=(50, 50))

    return real_generator

 

마찬가지로 이미지제네레이터도 준비합니다. target_size의 값만 바뀌고 나머지는 동일합니다.

두 코드를 이용하여 과실과 잎을 구분하는 Keras model을 생성합니다.

import DATAgen
import model_make

train, vald =DATAgen.data_ready()

history = model_make.Make_model(train,vald)

model_make.print_acc_loss(history)

 

모델은 Model_Checkpoint call back에 의해서 test.h5로 저장 됩니다.

 

3. 모델 불러오기, 변환

Keras 모델을 TFlite 모델로 바꾸는 방법은 2가지가 있습니다.

  • .h5 >> .pb >>.tflite (구버전)
  • .h5 >>>> tflite (신버전)

 

첫번째 과정입니다.

from tensorflow.keras.models import load_model 
import tensorflow as tf
model = load_model('test.h5',compile=False)#

model.summary()

model.save('test', save_format='tf')#.pb모델로 저장(폴더)

##pb.형식에서 tflite
converter = tf.lite.TFLiteConverter.from_saved_model('test')
tflite_quant_model = converter.convert()

특이점이 있다면 from_saved_model 에서 .pb파일이 저장되어 있는 폴더를 지정하여 줍니다.

 

두번째 과정입니다.

#keras 에서 TFlite 형식 생성
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

최신버전에선 from_keras_model()을 통해 쉽게 변환할 수 있습니다.

 

저장

#저장
open('converted_model.tflite', 'wb').write(tflite_quant_model)

 

4. 양자화

최적화 방법엔 크게 3가지 방법이 있습니다. 양자화(quantization), 가지치기(pruning), 군집화(clustering)

이중 양자화를 적용시켜 봅시다.

www.tensorflow.org/lite/performance/model_optimization?hl=ko

 

모델 최적화  |  TensorFlow Lite

에지 장치는 종종 제한된 메모리 또는 계산 능력을 가지고 있습니다. 이러한 제약 조건 내에서 실행될 수 있도록 다양한 최적화를 모델에 적용 할 수 있습니다. 또한 일부 최적화를 통해 추론 가

www.tensorflow.org

 

4-1 동적범위 양자화

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

 

optimizations을 통해 동적범위 양자화를 실행할 수 있습니다. DEFAULT란 속성이 말해주듯, 다른 양자화와 함께 사용됩니다.

 

4-2 float 16양자화

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_quant_model = converter.convert()

converter.target_spec.supported_types = [tf.float16]을 통해 양자화 할수 있습니다. 보통 GPU를 사용할때 float 16이 추천됩니다.

 

생성된 모델의 크기를 보면 아무것도 하지 않은 모델이 약 3.7mb, float 16 양자화를  한 모델이 1.8mb, 동적범위 양자화를 한 모델이 1mb가 살짝 안됩니다.

 

5. 테스트

모델응답시간을 확인하기 위해 기존 과실분류의 API를 다음과 같이 수정하였습니다.

기본적으로 def 바깥쪽에 model load부분을 꺼내어 계속해서 모델을 load하는것을 방지해야 하지만 시간 측정을 위해 안쪽에 넣어주었습니다

import tensorflow as tf
from flask import Flask, request,jsonify
from werkzeug.utils import secure_filename
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import numpy as np
from operator import itemgetter
import os


def seperate():
    ## Seperate model load##
    sep_root_path = "C:\\flask\\models\\models\\sep\\"
    sep_label_path = sep_root_path + 'labels.txt'

    files = open(sep_label_path, "r", encoding="UTF-8")
    labels = files.readlines()
    sep_labels = []

    for label in labels:
        sep_labels.append(label.strip('\n'))
    files.close

    sep_interpreter = tf.lite.Interpreter(model_path="C:\\flask\\converted_model\\nop_converted_model.tflite")
    sep_interpreter.allocate_tensors()

    # Get input and output tensors.
    input_details = sep_interpreter.get_input_details()
    output_details = sep_interpreter.get_output_details()
    datagen = ImageDataGenerator(rescale=1. / 255)
    test_dir = 'uploaded'
    test_generator = datagen.flow_from_directory(
        test_dir,
        target_size=(225, 225),
        shuffle=False,
        class_mode='categorical',
        batch_size=1)

    input_data = np.array(test_generator[0][0], dtype=np.float32)

    sep_interpreter.set_tensor(input_details[0]['index'], input_data)

    sep_interpreter.invoke()

    output_data = sep_interpreter.get_tensor(output_details[0]['index'])
    # print(*output_data)

    print_data = []
    list_print_data = []

    for index, value in enumerate(*output_data):
        list_print_data.append([index, value])

    print_data = sorted(list_print_data, key=itemgetter(1), reverse=True)

    # print(print_data)

    result = []
    for i in range(len(*output_data)):
        result.append(sep_labels[print_data[i][0]])

    return str(result[0])


def remo_credir():
    try:
        import shutil
        shutil.rmtree('uploaded/image')
        print()
    except:
        pass

    try:
        os.mkdir('uploaded/image')
    except:
        pass

app = Flask(__name__)

app.config['JSON_AS_ASCII'] = False # jsonify에서 한글사용
app.config['UPLOAD_FOLDER'] = 'uploaded\\image' #경로설정

@app.route('/API', methods=['POST','GET'])
def pred():
    if request.method == 'POST':
        remo_credir()
        f = request.files['file']
        f.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(f.filename)))
        val1 = seperate()
        return jsonify({"1st":val1})
    else:
        return "get!"


if __name__ == '__main__':
    app.run()

 

flask를 실행하고 request를 보내 모델 불러와 예측하고 답하는 시간까지 측정해 보도록 하겠습니다.

1. 양자화 x

 

2. 동적범위 양자화

 

3.float 16 양자화

 

최적화를 안 한경우 0.35s로 상당히 준수한 속도를 보였습니다. float16 양자화를 한 모델역시 준수하게 나왔습니다. 모델의 크기가 절반인점을 생각하면 상당히 훌륭합니다.

 

다만 동적범위 양자화를 한경우 속도가 22s 가량으로 매우많이 느려집니다. 

이는 GPU 지원이 안되어 생기는 것으로 예상됩니다.