14. 플러터 : 개와 고양이 사진 분류 (케라스) (5)

이번 프로젝트는 너무 힘들었기에
문제를 해결하는데 사용했던 몇 가지 함수를 정리한다.
훨씬 더 많은 코드를 만들어서 검증했지만
완성하고 나서 보니 대부분은 의미 없었던 함수였다.

첫 번째는 지정한 사진에 대해 정확도를 보여주는 코드다.
에뮬레이터에서 시험해 보면
결과가 너무 말도 안되게 나와서 어떤 문제인지 먼저 찾아야 했다.
데스크탑에서도 똑같이 말도 안되게 나오는지 확인이 필요했다.

나의 실수로 서로 다른 파일을 사용했던 관계로
데스크탑에서는 결과가 너무 잘 나왔다.
개와 고양이 모델 파일을 여러 개 만드는 과정에서 발생한
말도 안되는 실수로 일 주일은 날려 먹었다.

어쨌든 데스크탑과 스마트폰에서
동일한 결과가 나온다는 것을 검증하는 것은 중요하다.
물론 변환을 거쳤기 때문에 미세한 오차가 발생하는 것은 감안해야 한다.

scaling 옵션에 따라 결과가 다르게 나오는 점도 확인해야 한다.
True 옵션을 사용하는 것이 맞다.

import tensorflow as tf
from PIL import Image
import numpy as np


def load_image(img_path, scaling):
img = Image.open(img_path)
img = img.resize([150, 150])
img.load()
data = np.float32(img)

# 스케일링 유무에 따라 결과가 달라진다. 하는 것이 좋은 결과를 만든다.
# 학습할 때 스케일링을 적용했기 때문에 여기서도 적용하는 것이 맞다.
if scaling:
data /= 255
return data


def predict_model(model_path, images, scaling):
model = tf.keras.models.load_model(model_path)

stack = []
for img_path in images:
d = load_image(img_path, scaling=scaling)

d = d[np.newaxis] # (150, 150, 3) ==> (1, 150, 150, 3)
stack.append(d)

# (1, 150, 150, 3)으로 구성된 배열 결합 ==> (4, 150, 150, 3)
data = np.concatenate(stack, axis=0)

preds = model.predict(data)
print(preds.reshape(-1))


images = ['cats_and_dogs/small/test/cats/cat.1500.jpg',
'cats_and_dogs/small/test/cats/cat.1501.jpg',
'cats_and_dogs/small/test/dogs/dog.1500.jpg',
'cats_and_dogs/small/test/dogs/dog.1501.jpg']

predict_model('models/cats_and_dogs_small_4.h5', images, scaling=False)
predict_model('models/cats_and_dogs_small_4.h5', images, scaling=True)
# False : [0. 0. 1. 1.]
# True : [0.00184013 0.00273606 0.94292957 0.9738878 ]


앞의 코드보다 더 중요한 것이 있다.
같은 모델이라도 변환을 거쳤기 때문에
그 과정에서 원하지 않던 결과가 나올 수도 있다.

스마트폰에 사용한 모델을 데스크탑에서 사용할 수 있으면 좋지 않을까?
그래서 준비했다.
텐서플로 라이트 모델을 데스크탑에서 사용하는 코드.


def test_tflite_model(tflite_path, images):
interpreter = tf.lite.Interpreter(model_path=tflite_path)
interpreter.allocate_tensors()

# 입력 텐서 정보 : 인덱스를 알아야 데이터를 전달할 수 있다.
input_details = interpreter.get_input_details()
# [{'name': 'conv2d_60_input', 'index': 3, 'shape': array([ 1, 150, 150, 3], dtype=int32),
# 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0)}]

# 출력 텐서 정보 : 인덱스를 알아야 결과를 받아올 수 있다.
output_details = interpreter.get_output_details()
# [{'name': 'dense_41/Sigmoid', 'index': 18, 'shape': array([1, 1], dtype=int32),
# 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0)}]

result = []
for img_path in images:
input_data = load_image(img_path, scaling=True)
input_data = input_data[np.newaxis]

# 입력 데이터 전달
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()

# 출력 데이터 읽기
output_data = interpreter.get_tensor(output_details[0]['index'])
result.append(output_data)

# 1차원 변환 출력
print(np.reshape(result, -1))


# 텐서플로 라이트 모델 경로와 시험할 사진 경로 전달
test_tflite_model('models/cats_and_dogs.tflite', images)


위의 코드는 내가 만든 코드는 아니고 스택 오버플로우를 참고했다.
여기서는 난수를 사용해서 어떤 데이터에 대해서도 동작하도록 처리하고 있다.
더 범용적인 코드라고 보면 된다.
나도 두고두고 봐야 하니까 코드를 붙여넣어 둔다.

How to import the tensorflow lite interpreter in Python?


def test_tflite_model_by_stack_overflow(tflite_path):
# Load TFLite model and allocate tensors.
interpreter = tf.contrib.lite.Interpreter(model_path=tflite_path)
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

# Test model on random input data.
input_shape = input_details[0]['shape']

# 사진 경로 매개 변수는 없다. 난수로 만들면 되니까. 문법적으로 에러 나지 않는 것만 확인하면 된다.
input_data = np.array(np.random.random_sample(input_shape), dtype=np.float32)
interpreter.set_tensor(input_details[0]['index'], input_data)

interpreter.invoke()
output_data = interpreter.get_tensor(output_details[0]['index'])
print(output_data)