Raspberry PI에서 Ubuntu Setting부터 Coral TPU를 사용해 MobileNetV2 Inference 해보기


서론

이 장에서는 Raspberry PI에서 MobileNetV2를 Coral Edge TPU를 사용해 inference하는 방법과 필요한 환경셋팅 대해서 설명한다.

목차

  1. 1. Requirements
  2. 2. Install Ubuntu
    1. 방법1. 라즈베리파이에서 제공하는 GUI툴을 이용해 설치
    2. 방법2. Server이미지를 받아서 직접 설치
  3. 3. Setting Ubuntu
    1. 무선 네트워크 설정
    2. Ram Swap Setting
    3. Ubuntu 한글 설정
    4. Find Architecture
    5. Setting root password
  4. 4. Install Package
    1. Check Python Version
    2. Install Ananconda
    3. 추가 기능 설치
    4. Install Coral
  5. Test MobileNetV2
    1. Coral에서 제공하는 MobileNetV2
    2. Coral MobileNetV2 Inference
  6. tf.keras.application에서 제공하는 MobileNetV2
    1. TPU Compile
    2. Keras MobileNetV2 Inference
  7. ONNX(Model Zoo)에서 제공하는 MobileNetV2
    1. TPU Compile
    2. Compile에 실패하는 이유
  8. TF-Lite Inferece(Without using TPU)
  9. 연구 결론

1. Requirements

2022-11-22일 기준

  • A computer with one of the following operating systems:
    • Linux Debian 10, or a derivative thereof (such as Ubuntu 18.04), and a system architecture of either x86-64, Armv7 (32-bit), or Armv8 (64-bit) (includes support for Raspberry Pi 3 Model B+, Raspberry Pi 4, and Raspberry Pi Zero 2)
    • macOS 10.15 (Catalina) or 11 (Big Sur), with either MacPorts or Homebrew installed
    • Windows 10
  • One available USB port (for the best performance, use a USB 3.0 port)
  • Python 3.6 - 3.9

https://coral.ai/docs/accelerator/get-started/#requirements

2. Install Ubuntu

우분투를 설치하는 방법 2가지를 소개한다. 원하는 방법을 선택해 설치하자

방법1. 라즈베리파이에서 제공하는 GUI툴을 이용해 설치

먼저 첫번째 방법을 통해 설치하는법을 설명한다. 아래 링크에서 라즈베리파이 전용 우분투를 설치할 수 있는 방법을 알려준다.

$ sudo apt install snap
$ sudo snap install rpi-imager

https://ubuntu.com/tutorials/how-to-install-ubuntu-on-your-raspberry-pi#1-overview

방법2. Server이미지를 받아서 직접 설치

아래 링크에서 직접 다운받아 설치도 가능하다. 아래 다운받은것은 Server(CLS)용 64bit Architecture.

Server버전을 설치하고 그 다음 명령어로 GUI를 설치할 것이다.

Download

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/47f279bc-fe08-4af4-bddd-61ae070ce1c9/ubuntu-20.04.5-preinstalled-server-arm64raspi.img?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4663YTDVODY%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053645Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQDGW%2Btgjo8coEXVuA1dvKsEICGtLPeJ75rKxANUZhi%2BKQIhAN%2BqGOrLbjLTxhw559m4DSJyDvrlyrEaj5X17SrYdxaQKv8DCBYQABoMNjM3NDIzMTgzODA1IgzEhXTXix51M8R6%2F%2BAq3AOLC2pJUTHXR97wnfcVf6JLzb%2Bk7NxO2DVO%2FZdkr%2Bb%2BY1V04LrH9g3FN4J3msqWjmZs2hveU%2F1sRGT%2F4zc17ITVaZjn8PBml3avA5aQUo2lSORe%2BwQIbD05TgZ5HorihRHJZNby3JeT25VF5dP7P3%2BpJ63Z7O80upBRBaKbGqqR4vbP5HvAiSg9ijXjUvUW7Pc0bySD4agveOsuqHYALkdhWhmurj1VQmis5Y8SJEJ8jT2TOoywHIUiovOAW%2Bjhv%2FuwlsaZ3XXB0pzGWaUJqt1fh6nwx0LNO1YzkeiZlOWLDsYv%2FJ%2FWU54HWHOO1sXSq5NWAxlZv4rL1%2B%2FWvzhtauQXIEBD4TlRDDRh689RRK69D6PR2UxezI5TsVrWqks7njh14eTZqdsoF4xF4mf2RiNgpqKiYPNG5g7DU8Suda2PUNaczJM1UC4%2BCNsL%2BeR6Toj%2F%2B3caK0gZ714MisgTGmbshMwVm6A5CUrQYwJPKV8qejYKk0Tln4ONK0gcx4Wp6LwxStCDGV4SoHI9B33bJHjg7IhwU4vM3%2B9duB0gJrucAgpTMOxEn%2FQvVYKXJwBgHrI72d0qJymBZ%2FTuu0JClPTrHA3PSMRK6cXsJjGtPHwNn2bFAFo1bVUP%2BRbMuDCGu7%2FQBjqkAStRw0b7drfzJWWYAbOEV3fobNEhUkNd%2BUn15yRsc2wQOfGVxOTb8sLuibyfwFhuLhnZLfdWcaahCky5Ylfzg2%2BS17VyZE5CemVTRa3BWoFP%2BC1PxEBp%2FCxwvaRo61CQBTd%2BjUb%2FT3Pf8AcfVNpt3v3CtfyVrEl8lHiXQMmDEfzP0VMCae9M0Z2MHk7ggtCWNiYZagebZuD4rDBjMvMsMA4i33ys&X-Amz-Signature=27840f063465e80a00be6d24b7233a8d87902357c81c4f8198d836f079542d31&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

https://releases.ubuntu.com/20.04.5/?_ga=2.70319312.1797486237.1670478267-1298739506.1669605918

해당 이미지를 아래 프로그램을 사용해서 USB에 fetch 한다.

https://www.balena.io/etcher/

아래 블로그에서 balenaEtcher로 USB에 설치하는 방법을 상세히 올려놓았다. 참고하자

https://chmodi.tistory.com/115

3. Setting Ubuntu

서버버전의 초기 아이디와 비밀번호는 전부 ubuntu이다. 그 후 자동으로 비밀번호를 변경하라고 나온다.

무선 네트워크 설정

로그인하고 비빌면호 변경까지 완료했다면 아래 설정 파일을 열어주자

$ sudo nano /etc/netplan/50-cloud-init.yaml

들여쓰기 하나당 띄어쓰기 4번이다. {name}에 wifi이름, {password}에 비밀번호를 입력 → Tap을 사용하지 말고 space바를 사용

network:
		ethernets:
				eth0:
						dhcp4: true
						optinal: true
		version: 2
		wifis:
				wlan0:
						optional: true
						access-points:
								"SSID-NAME-HERE":
									  password: "PASSWORD-HERE"
						dhcp4: true

설정 파일을 저장한 후에는 아래 코드를 꼭 입력해서 적용시켜야 한다.

$ sudo netplan apply

에러가 난 경우에는 debug옵션을 붙이면 자세한 이유를 알 수 있다.

$ sudo netplan apply --duebug

https://velog.io/@gilchris/Ubuntu-20.04-terminal%EC%97%90%EC%84%9C-wifi-%EC%9E%A1%EA%B8%B0

https://linuxconfig.org/ubuntu-20-04-connect-to-wifi-from-command-line

Ubuntu Desktop 설치

무선네트워크에 연결되면 아래 명령어를 통해 Desktop을 설치하고 재부팅하면 된다.

$ sudo apt update
$ sudo apt-get install ubuntu-desktop
$ sudo reboot

Ram Swap Setting

Raspi의 Ram이 적어서 속도가 느리므로 메모리 Swap을 사용해 늘려준다.

아래 명령어는 swapfile이라는 이름으로 2GB의 Swapfile을 생성한다.

$ sudo fallocate -l 2G /swapfile

Swapfile의 권한을 수정 해줍니다. 600은 root 계정만 읽기/쓰기가 가능하다는 의미다.

$ sudo chmod 600 /swapfile

이제 생성한 Swapfile로 Swap Memory를 활성화한다.

$ sudo mkswap /swapfile
$ sudo swapon /swapfile

마지막으로 시스템을 재부팅해도 Swap이 적용되도록 한다. 아래 파일을 열어준다.

$ sudo nano /etc/fstab

그리고 아래 내용을 추가

/swapfile swap swap defaults 0 0

잘 되었는지 확인

$ free

https://facerain.club/swap-memory/

Ubuntu 한글 설정

https://shanepark.tistory.com/231

Find Architecture

$ uname -m # aarch64

Setting root password

$ sudo passwd root

4. Install Package

사용 가능한 패키지들을 업데이트 한다. 그리고 업그레이드 해준다.

$ sudo apt-get update
$ sudo apt-get upgrade

Check Python Version

$ python3 --version

Install Ananconda

Download Anaconda3-2022.10-Linux-aarch64

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/8743009f-549e-4a5f-92fd-ddd5c6d2d71d/Miniforge-pypy3-Linux-aarch64.sh?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466RLWGONWZ%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053646Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJHMEUCIQCfXf8h2nQGOQTDGHpGt7AvFjYYAb6UCXJhYSn6UGFtyAIgZh8YttLhN1qpnKHr7f568kDqWklcNZJtvapHOMYc%2BZ8q%2FwMIFhAAGgw2Mzc0MjMxODM4MDUiDHMVA3QyoUUL4HobMCrcAxcigBFducqyphdWUSs7XQOxBlih353wGGdaApIf8J346JUvqL4O1I3Kv48z5KrWNjXoBU0pOcGzoZBo8NsgN%2BeT6u47xnftOpc1CI3z39FviyVDmlxjMw9ZVW%2F2BxKMrV7BtlKY2uD4LgQ3G1ppDk9t%2FxwjvDbhg4rc7ZSMKjZ8TEtQCHqDG1bBgZag302kaaEgzx5gyFuZoSpJXkUrNR84XvuUnzBTzqSo9lGr%2Bu2b6ugosTSse1KmMsfYeBMxC%2B%2BHjyXeXComorX%2BK37V3A%2FIqdD0sIYJQrmIj1ND8fsA33BizFrBRPSEldDQzWobEiYfqDAB%2BCcrZkCdAcKTOqg3xRJx54KYUgYLu9%2Bs9P5XArcVRWPn0tdguTwfNNxctVvS0ZYlEEiogSqnDLmQ%2BlY0LgFB%2BtRo6tpHRmAa%2FL8eZsSr8x519jdFIAQefMEnLEfvEGahcDy2umT9JlicdhoNRO3slOKbm6qVUtGGqnpHxi69ratYpxIf4SOTb8BaGG9LAG7m5BY8qj2%2BzINWAoPlviJkUws3rrkAXS2%2BwZa%2BNifUPzz7KpQ0jubUOM4kRvZT38k4iRyr3nwhJEt%2FIXFmAY4DTR6uW%2BVJwNssOMgPw2n3DJznxb49v6uCMPe8v9AGOqUBp388KofUb%2FIFkOf6dtnn1ZOdfyCX4Xr6aYy5UUJFb4PTwrCo%2Be6Rl2NhqKOiZSvrX334TrimnFS%2BhtUYpHob1FhlNiECuttcoj41UqDFySdBqgxoK%2FRRTWFkqNar94l5C8qhteogPGMy%2FR4MDcuGFOP%2Fso6xE%2Bm7IGTIhGqSmLIVczHsz0SZA5Iy1EJYnysR8hgNegycFoCnV4Rch3SvTPuAUHD8&X-Amz-Signature=1f2e3056ce42e8a02da78eb5d4624a0e83b8c6d12cd3767b1ad2990a924cfce6&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

$ cd Downloads
$ bash Anaconda3-2022.10-Linux-aarch64.sh\
$ source ~/.bashrc

https://github.com/conda-forge/miniforge

추가 기능 설치

$ apt-get install python3-pip
$ sudo apt install curl

Install Coral

아래 공식 홈페이지에 나온 Instruction 따라 설치한다.

https://coral.ai/docs/accelerator/get-started/#1-install-the-edge-tpu-runtime


Test MobileNetV2

3가지 출처(Coral, Keras application,ONNX)에 대한 MobileNetV2에 대해서 TPU를 사용해 추론하는 방법을 소개한다.

Coral에서 제공하는 MobileNetV2

이미 Coral에서 uint8-Quantization과 TPU compiler을 해놔서 추가적인 작업이 필요없다. 토글을 통해 받거나 아래 링크에서 받을 수 있다.

https://github.com/google-coral/pycoral

Download mobilenet_v2_1.0_224_quant.tflite(Not For TPU)

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/80a07073-c080-468c-8ac2-12177b7439ef/mobilenet_v2_1.0_224_quant.tflite?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466VIS3CF4Y%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053646Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQCak1zSjtyz63BNRcvHPHh9qp7iZS%2FsG7rn1%2F%2Bqt4sy0wIhAIGMijysUobmNc7tBJ1xp7Vtj1aAqTQM6nRh073uTnh5Kv8DCBYQABoMNjM3NDIzMTgzODA1Igwb9MHtNhQ8xf%2BgFrUq3AM9h79rC1%2Bg4FTNkyti2g%2B5ONZcGtXZU7z6YLpwbtZRmDaH3GlbU2Y%2FXOGY6fgGhixgslZpKExXn3rGBBKrSRflpv0oGyi5PO1bM8w4rp5tUwPQGNh0SlkEIG3hqdoM70Z%2B09YOfTM1JAihKTpU7Lj%2BJWFPKUzFUACHzDHySm71eftbJEW0WR%2B0MZpwz%2Fhwce6Mv3d6iRskb7QJ8qOWLQqEvLU4VJ%2FrmG3RJk3XJgsnBjL3XiURY%2FYWcInp%2FnrtYWZbb7ss%2FJlRmaE2mDh9yUslbOB7sm44wKdqoInuklMYOp%2FBBwqjYWHujBtcA7m7BBh2FN3OOh6I%2FzvYMKS4n5Hzutqf3t7ABjpn3xfnj50dAqhn1ukiFvlNoGAuyA2K%2FmmGa9x4eDE27jSpzRxW173pH7USFSo5OlbB5CpftSI4ReZOznDq3U3uyiHa43617KGHPAzrZRcmp1hJMSvBtxr45IHvqz5nbzIvr5Ke075NjAWDA1gDHDk6SowEMckaAw1D9zCqL08xYqB8C%2BZdQ5GbbVUvG9zhXbGc6G8sSFWANM4qhP7uLGwb0%2Bn5Z8ehbz4W8b1yOyNUTI0h4Oek24jUsirysAahbdCyXznGPqXrBTKLTU6jaZh6ppHxcDD6ur%2FQBjqkAbj6lCdAUsf1b8rCvV0DH84ZuLvBcqTi%2Bc%2Fpb2GwF67VgG74ZTlKCFhKcE2zUv8V5PiTuoyR%2FZkwdbwngx1GlHniUpOxwxHNEq3oEzGVT7%2F4SazvA6uIwJTV%2FagbqZ1wzcKTNggbzmkHQC3wpsUR%2FWVyVE7mJGbhxb%2F6Rn8QrpTwCQ53ZBhe0yvIhC6R8mA5FRTQJPKrUVBajbxVZmU2%2FG%2B92IFz&X-Amz-Signature=5e19f84e99216346f8c350328e265ceadb96bdb2df86f3e4b342cfff301d9158&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

Download mobilenet_v2_1.0_224_quant_edgetpu.tflite (For TPU)

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/ed39802f-1544-4bb7-afab-2ae061021a83/mobilenet_v2_1.0_224_quant_edgetpu.tflite?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466YR2JG6WM%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053649Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJHMEUCIF4qfhxuaM7%2BLf8Di7vhtAQOZAk%2FxF5Wz1sA2YlYWkBMAiEAlQ22tZu%2F2dXPQlvWq8usmhVfZsUVYcJD%2BexgMAo3tfgq%2FwMIFhAAGgw2Mzc0MjMxODM4MDUiDDfjIbh2PU%2FVUNOufyrcAwIIVH9smYTfBxAOfp%2BVB2UemoiRop7PyXoa5lMDL7dlDURB1Rdo%2BB5TecVarMexVAM4v8MVtNZ3HjYAc6X2z5hiTwS1%2FCquz7hDj5dTMggraDEaHaOROMKrTMalTnISkxhaQMvIK4qN%2FZMAl5xHWTjusaqiZAThJhVSHQIWJfSp6sGfvVQRH8LLOYs%2F%2F5mxWVvNUqXQ6nk4kdOmMVSWM1osKRTyKvrAuHhQ%2FrpvQB%2FWXXANlTHc879m0A8aGwory3zl%2BITXkbvRprtOy4i2L5KxelhOavLFKfU68y2nlv2o04kdXvQm7sb0UJwyfYIwGBMefnUz5QjdSkH60pYW4LnErLZTssfrGJbEsyx5JowzYZin4UKDVU04q1MjD2v%2BPXoZzCS8%2BwMvNImDzsr7iZ0G5zTqhuUE3tD5fUuF%2Fm6UTw4DgPWR40Z5rGNgTqWLWi%2FT18nC29jAexbHA8TRzFH0wECcGobQp0cjV3Mmr%2Fld6rIk5czSMP6v0vWIkSzOFKFggrJHqVmKHrlqDH57cWpn4HzLJJdE%2FAEtzAOvDFNJ9gJuEzaNKoomHhchC68Y%2FfdjVf2e4q1wclgRwfq6BiOrXf7rKIJI1WVsuk91slVfwfmUoL%2FmxqZwDEbsMKjLv9AGOqUBKgpZITv%2BC2vNE%2F%2FWfP8tiR3b2uhpl9OhF7bZbA%2BY3McjobatALQyQBDH3VOQKBAWUIbgvlfrptUiNfPB0B1AJTfblQNIb38TIhvkhgUp%2BXyk90%2BuucivJi8qcU1vrfP9s6hsxMbIfSVR5ZtYJ721frSwgr%2BUZNkExF3HWUNK2Fy%2FPZbiukAK9TP3Jolo%2BixeihxrXdKcuF9KHs54U8SalalBoDye&X-Amz-Signature=f8eb5852d0201ae5df6fe09f3d3e2232b6a289fae646e945b0d31cad9353b138&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

  • mobilenet_v2_1.0_224_quant.tflite Structure
    • Conv2D 와 Relu6가 합쳐져 있는 것을 볼 수 있는데, 이러한 모델의 경우 TPU에서 사용가능하도록 컴파일이 가능하다.

https://netron.app/

Coral MobileNetV2 Inference

아래 classify_image.py는 TF-Lite모델을 TPU를 사용해 classification해주는 코드다.

Code(classify_image.py)
# Lint as: python3
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Example using PyCoral to classify a given image using an Edge TPU.

To run this code, you must attach an Edge TPU attached to the host and
install the Edge TPU runtime (`libedgetpu.so`) and `tflite_runtime`. For
device setup instructions, see coral.ai/docs/setup.

Example usage:
```
bash examples/install_requirements.sh classify_image.py

python3 examples/classify_image.py \
  --model test_data/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite  \
  --labels test_data/inat_bird_labels.txt \
  --input test_data/parrot.jpg
```
"""

import argparse
import time

import numpy as np
from PIL import Image
from pycoral.adapters import classify
from pycoral.adapters import common
from pycoral.utils.dataset import read_label_file
from pycoral.utils.edgetpu import make_interpreter


def main():
  parser = argparse.ArgumentParser(
      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  parser.add_argument(
      '-m', '--model', required=True, help='File path of .tflite file.')
  parser.add_argument(
      '-i', '--input', required=True, help='Image to be classified.')
  parser.add_argument(
      '-l', '--labels', help='File path of labels file.')
  parser.add_argument(
      '-k', '--top_k', type=int, default=1,
      help='Max number of classification results')
  parser.add_argument(
      '-t', '--threshold', type=float, default=0.0,
      help='Classification score threshold')
  parser.add_argument(
      '-c', '--count', type=int, default=5,
      help='Number of times to run inference')
  parser.add_argument(
      '-a', '--input_mean', type=float, default=128.0,
      help='Mean value for input normalization')
  parser.add_argument(
      '-s', '--input_std', type=float, default=128.0,
      help='STD value for input normalization')
  args = parser.parse_args()

  labels = read_label_file(args.labels) if args.labels else {}

  interpreter = make_interpreter(*args.model.split('@'))
  interpreter.allocate_tensors()

  # Model must be uint8 quantized
  if common.input_details(interpreter, 'dtype') != np.uint8:
    raise ValueError('Only support uint8 input type.')

  size = common.input_size(interpreter)
  image = Image.open(args.input).convert('RGB').resize(size, Image.ANTIALIAS)

  # Image data must go through two transforms before running inference:
  # 1. normalization: f = (input - mean) / std
  # 2. quantization: q = f / scale + zero_point
  # The following code combines the two steps as such:
  # q = (input - mean) / (std * scale) + zero_point
  # However, if std * scale equals 1, and mean - zero_point equals 0, the input
  # does not need any preprocessing (but in practice, even if the results are
  # very close to 1 and 0, it is probably okay to skip preprocessing for better
  # efficiency; we use 1e-5 below instead of absolute zero).
  params = common.input_details(interpreter, 'quantization_parameters')
  scale = params['scales']
  zero_point = params['zero_points']
  mean = args.input_mean
  std = args.input_std
  if abs(scale * std - 1) < 1e-5 and abs(mean - zero_point) < 1e-5:
    # Input data does not require preprocessing.
    common.set_input(interpreter, image)
  else:
    # Input data requires preprocessing
    normalized_input = (np.asarray(image) - mean) / (std * scale) + zero_point
    np.clip(normalized_input, 0, 255, out=normalized_input)
    common.set_input(interpreter, normalized_input.astype(np.uint8))

  # Run inference
  print('----INFERENCE TIME----')
  print('Note: The first inference on Edge TPU is slow because it includes',
        'loading the model into Edge TPU memory.')
  for _ in range(args.count):
    start = time.perf_counter()
    interpreter.invoke()
    inference_time = time.perf_counter() - start
    classes = classify.get_classes(interpreter, args.top_k, args.threshold)
    print('%.1fms' % (inference_time * 1000))

  print('-------RESULTS--------')
  for c in classes:
    print('%s: %.5f' % (labels.get(c.id, c.id), c.score))


if __name__ == '__main__':
  main()

https://github.com/google-coral/pycoral/tree/master/examples

classify_image.py에 사용되는 라이브러리들(pycoral.utils, pycoral.adapters)에 대한 코드들은 아래 링크에 잘 설명돼 있다. 코드를 수정할 때 참고하자.

https://coral.ai/docs/reference/py/pycoral.utils/

https://coral.ai/docs/reference/py/pycoral.adapters/#module-pycoral.adapters.common

위 코드는 아래 명령어로 사용 가능하다.

python3 classify_image.py \
  --model mobilenet_v2_1.0_224_quant_edgetpu.tflite \
  --labels coral_imagenet_labels.txt \
  --input parrot.jpg

결과는 다음과 같이 나온다.

classify_image.py를 수정해 classify_custom.py를 만들어113개의 데이터를 출력하고 CSV파일로 저장하게 끔 만들었다.

Terminal 에서 아래 코드로 실행시킬 수 있다.

python3 classify_custom.py \
  --model mobilenet_v2_1.0_224_quant_edgetpu.tflite \
  --labels coral_imagenet_labels.txt \
  --input 113_data

수정한 classify_custom.py 결과다.

Directory 환경
classify_custom.py 코드
import os
import argparse
import time
import numpy as np
from PIL import Image
from pycoral.adapters import classify
from pycoral.adapters import common
from pycoral.utils.dataset import read_label_file
from pycoral.utils.edgetpu import make_interpreter

def main() :
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '-m', '--model', required=True, help='File path of .tflite file.')
    parser.add_argument(
        '-i', '--input',  required=True, help='Image path.') # input test_data/
    parser.add_argument(
        '-l', '--labels', help='File path of labels file.')
    parser.add_argument(
        '-k', '--top_k', type=int, default=1,
        help='Max number of classification results')
    parser.add_argument(
        '-t', '--threshold', type=float, default=0.0,
        help='Classification score threshold')
    parser.add_argument(
        '-c', '--count', type=int, default=1,
        help='Number of times to run inference')
    parser.add_argument(
        '-a', '--input_mean', type=float, default=128.0,
        help='Mean value for input normalization')
    parser.add_argument(
        '-s', '--input_std', type=float, default=128.0,
        help='STD value for input normalization')
    args = parser.parse_args()

    labels = read_label_file(args.labels) if args.labels else {}

    print(args.model)
    interpreter = make_interpreter(args.model)
    interpreter.allocate_tensors()

    # Model must be uint8 quantized
    if common.input_details(interpreter, 'dtype') != np.uint8 :
        raise ValueError('Only support uint8 input type.')

    size = common.input_size(interpreter)
    params = common.input_details(interpreter, 'quantization_parameters')
    scale = params['scales']
    zero_point = params['zero_points']
    mean = args.input_mean
    std = args.input_std

    image_name = os.listdir(args.input)  # image name
    image_filter = list(filter(lambda x: x.find('.JPEG') != -1, image_name))  # image format filtering

    
    print("Note : The first inference on Edge TPU is slow because it includes")
    print("loading the model into Edge TPU memeory.")

    f = open("./mobilenet_data.csv","w")
    f.write("ImageName,class,score,inference_time\n")
    i=1
    for img in image_name:

        image = Image.open(args.input + img).convert('RGB').resize(size, Image.ANTIALIAS)

        if abs(scale * std - 1) < 1e-5 and abs(mean - zero_point) < 1e-5:
            # Input data does not require preprocessing.
            common.set_input(interpreter, image)
        else:
            # Input data requires preprocessing
            normalized_input = (np.asarray(image) - mean) / (std * scale) + zero_point
            np.clip(normalized_input, 0, 255, out=normalized_input)
            common.set_input(interpreter, normalized_input.astype(np.uint8))

        print('----INFERENCE TIME AND RESULTS----')
        start = time.perf_counter()
        interpreter.invoke()
        inference_time = time.perf_counter() - start
        classes = classify.get_classes(interpreter, args.top_k, args.threshold)
        
        for c in classes:
            result = labels.get(c.id, c.id)
            result = result.replace(", ","/")
            print(f"{i} {img} {result}, {c.score:.5f}, {inference_time*1000:.1f}ms")
            f.write(f"{img}, {result}, {c.score:.5f}, {inference_time*1000:.1f}ms\n")
            i += 1

if __name__ == '__main__':
    main()
Download classify_custom.py, coral_imagenet_labels.txt, 113_data.7z, mobilenet_data.csv

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/287790c1-4076-4bed-a83a-f27d9e0da076/classify_custom.py?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4665NIUGIP3%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053650Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJGMEQCICkdKlJ3PleTFNabJdO6MtvSihUKCh3bwPh9Y5nq%2BDJhAiAv3Ld3U9cMe6N9%2BXdwS1fMpekLvgBc7F9VtzoBPS4AFSr%2FAwgWEAAaDDYzNzQyMzE4MzgwNSIMXlWVax9uzfrPzACFKtwDlfQ6iSUMnuRMLrpuNvVfROyWPITjV0O9PvzBHeZ7vRfoo%2FPUhmxXp2USsHw1BWqrhaZ7EzLfnfvqgnCq1kGuiJrQg2aIcfIu14wEPUsA0aER2GoJ1lw9ZYXmnzGOhCIdvrRXqRjC%2FM%2BhMW413IVBB4elkMI7iUCEIMrPZ%2F747cw9qcokQRAXMXKJDq%2BzgwVDjWUbs2BNr5KmT6t2R1SMUfGO2DyYfVdb0O54ap9bVgBRKtSshQTXSZINBq6BJHfHZwG6a3Q%2FPrSOyd9cZE4FPaGOADCVZJkb9Ez3EQydeE%2FiPGbciRmlk79AD4Y%2BMoEW%2BVN5A4XsUGaD1FsvLjtuCEhOZ8odQRequK59gomSYmnI%2FEaETFa5CxI86Te5W2AqFSbUeHvkHOVdDauLbyk%2B9rLD249B1jZzR3JJarj06owbifyPtBA8N6u4a5U7TT0U72T%2BkxeY4dEdnn3FUk7iivKhArqcVqzNUYkz9WjlyzYK1C5k%2FB%2BeRH02IDt6DNE0DoQOzuWon1Cvz01zsvFU9NScaxdJZl%2FoIFP%2Flv8rxjNry6TQgPuWWJV1Bfo%2BQOtu062SoTpq8PJoIS4BzDrP70Kz5qZQkXoOpuqQcgWAr89JhLwO5%2BLOlNxfReUw87y%2F0AY6pgEc43tHuBkT1%2FCcq8Vmi2sHnFJT%2BjrmFaCF81bGvBkrxWXfUilGMwLAu3OHC74UH49n7WUw9m7Qw6OmtrqrFgV2j8kaP%2FDpQLC7ZoDhmIG%2FyU%2B5gLjPn1BuW4jAglA82CVkV9%2FzrJGRIO3jyrVrkiRz3UrmwllH1rPr7u6W1xM6e1HN0mLsgLCKyj0UX0T0LrUiU63ceCjV35VdEkLykCutPCoXQWDq&X-Amz-Signature=b5f48e6b4b53f0109d076a9b3af0e5fd8ce4ab582c368faa3703045432d794ec&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/bc85901e-38c4-4ec5-a548-1770338e2c61/coral_imagenet_labels.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4665NIUGIP3%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053650Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJGMEQCICkdKlJ3PleTFNabJdO6MtvSihUKCh3bwPh9Y5nq%2BDJhAiAv3Ld3U9cMe6N9%2BXdwS1fMpekLvgBc7F9VtzoBPS4AFSr%2FAwgWEAAaDDYzNzQyMzE4MzgwNSIMXlWVax9uzfrPzACFKtwDlfQ6iSUMnuRMLrpuNvVfROyWPITjV0O9PvzBHeZ7vRfoo%2FPUhmxXp2USsHw1BWqrhaZ7EzLfnfvqgnCq1kGuiJrQg2aIcfIu14wEPUsA0aER2GoJ1lw9ZYXmnzGOhCIdvrRXqRjC%2FM%2BhMW413IVBB4elkMI7iUCEIMrPZ%2F747cw9qcokQRAXMXKJDq%2BzgwVDjWUbs2BNr5KmT6t2R1SMUfGO2DyYfVdb0O54ap9bVgBRKtSshQTXSZINBq6BJHfHZwG6a3Q%2FPrSOyd9cZE4FPaGOADCVZJkb9Ez3EQydeE%2FiPGbciRmlk79AD4Y%2BMoEW%2BVN5A4XsUGaD1FsvLjtuCEhOZ8odQRequK59gomSYmnI%2FEaETFa5CxI86Te5W2AqFSbUeHvkHOVdDauLbyk%2B9rLD249B1jZzR3JJarj06owbifyPtBA8N6u4a5U7TT0U72T%2BkxeY4dEdnn3FUk7iivKhArqcVqzNUYkz9WjlyzYK1C5k%2FB%2BeRH02IDt6DNE0DoQOzuWon1Cvz01zsvFU9NScaxdJZl%2FoIFP%2Flv8rxjNry6TQgPuWWJV1Bfo%2BQOtu062SoTpq8PJoIS4BzDrP70Kz5qZQkXoOpuqQcgWAr89JhLwO5%2BLOlNxfReUw87y%2F0AY6pgEc43tHuBkT1%2FCcq8Vmi2sHnFJT%2BjrmFaCF81bGvBkrxWXfUilGMwLAu3OHC74UH49n7WUw9m7Qw6OmtrqrFgV2j8kaP%2FDpQLC7ZoDhmIG%2FyU%2B5gLjPn1BuW4jAglA82CVkV9%2FzrJGRIO3jyrVrkiRz3UrmwllH1rPr7u6W1xM6e1HN0mLsgLCKyj0UX0T0LrUiU63ceCjV35VdEkLykCutPCoXQWDq&X-Amz-Signature=745b051014bab271739d566872f335be01ce143ed53450d98f02b49f689a7b60&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/596aebfc-470b-4775-9c04-adf513470816/113_data.7z?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4665NIUGIP3%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053650Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJGMEQCICkdKlJ3PleTFNabJdO6MtvSihUKCh3bwPh9Y5nq%2BDJhAiAv3Ld3U9cMe6N9%2BXdwS1fMpekLvgBc7F9VtzoBPS4AFSr%2FAwgWEAAaDDYzNzQyMzE4MzgwNSIMXlWVax9uzfrPzACFKtwDlfQ6iSUMnuRMLrpuNvVfROyWPITjV0O9PvzBHeZ7vRfoo%2FPUhmxXp2USsHw1BWqrhaZ7EzLfnfvqgnCq1kGuiJrQg2aIcfIu14wEPUsA0aER2GoJ1lw9ZYXmnzGOhCIdvrRXqRjC%2FM%2BhMW413IVBB4elkMI7iUCEIMrPZ%2F747cw9qcokQRAXMXKJDq%2BzgwVDjWUbs2BNr5KmT6t2R1SMUfGO2DyYfVdb0O54ap9bVgBRKtSshQTXSZINBq6BJHfHZwG6a3Q%2FPrSOyd9cZE4FPaGOADCVZJkb9Ez3EQydeE%2FiPGbciRmlk79AD4Y%2BMoEW%2BVN5A4XsUGaD1FsvLjtuCEhOZ8odQRequK59gomSYmnI%2FEaETFa5CxI86Te5W2AqFSbUeHvkHOVdDauLbyk%2B9rLD249B1jZzR3JJarj06owbifyPtBA8N6u4a5U7TT0U72T%2BkxeY4dEdnn3FUk7iivKhArqcVqzNUYkz9WjlyzYK1C5k%2FB%2BeRH02IDt6DNE0DoQOzuWon1Cvz01zsvFU9NScaxdJZl%2FoIFP%2Flv8rxjNry6TQgPuWWJV1Bfo%2BQOtu062SoTpq8PJoIS4BzDrP70Kz5qZQkXoOpuqQcgWAr89JhLwO5%2BLOlNxfReUw87y%2F0AY6pgEc43tHuBkT1%2FCcq8Vmi2sHnFJT%2BjrmFaCF81bGvBkrxWXfUilGMwLAu3OHC74UH49n7WUw9m7Qw6OmtrqrFgV2j8kaP%2FDpQLC7ZoDhmIG%2FyU%2B5gLjPn1BuW4jAglA82CVkV9%2FzrJGRIO3jyrVrkiRz3UrmwllH1rPr7u6W1xM6e1HN0mLsgLCKyj0UX0T0LrUiU63ceCjV35VdEkLykCutPCoXQWDq&X-Amz-Signature=bb852c3ff136ea9c8203064b4d1380802e57f8f0b80187e28c3d2092fc7810f1&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/0e50eb19-e69e-468c-8e6a-bd44f6b7e123/mobilenet_data.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4665NIUGIP3%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053650Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJGMEQCICkdKlJ3PleTFNabJdO6MtvSihUKCh3bwPh9Y5nq%2BDJhAiAv3Ld3U9cMe6N9%2BXdwS1fMpekLvgBc7F9VtzoBPS4AFSr%2FAwgWEAAaDDYzNzQyMzE4MzgwNSIMXlWVax9uzfrPzACFKtwDlfQ6iSUMnuRMLrpuNvVfROyWPITjV0O9PvzBHeZ7vRfoo%2FPUhmxXp2USsHw1BWqrhaZ7EzLfnfvqgnCq1kGuiJrQg2aIcfIu14wEPUsA0aER2GoJ1lw9ZYXmnzGOhCIdvrRXqRjC%2FM%2BhMW413IVBB4elkMI7iUCEIMrPZ%2F747cw9qcokQRAXMXKJDq%2BzgwVDjWUbs2BNr5KmT6t2R1SMUfGO2DyYfVdb0O54ap9bVgBRKtSshQTXSZINBq6BJHfHZwG6a3Q%2FPrSOyd9cZE4FPaGOADCVZJkb9Ez3EQydeE%2FiPGbciRmlk79AD4Y%2BMoEW%2BVN5A4XsUGaD1FsvLjtuCEhOZ8odQRequK59gomSYmnI%2FEaETFa5CxI86Te5W2AqFSbUeHvkHOVdDauLbyk%2B9rLD249B1jZzR3JJarj06owbifyPtBA8N6u4a5U7TT0U72T%2BkxeY4dEdnn3FUk7iivKhArqcVqzNUYkz9WjlyzYK1C5k%2FB%2BeRH02IDt6DNE0DoQOzuWon1Cvz01zsvFU9NScaxdJZl%2FoIFP%2Flv8rxjNry6TQgPuWWJV1Bfo%2BQOtu062SoTpq8PJoIS4BzDrP70Kz5qZQkXoOpuqQcgWAr89JhLwO5%2BLOlNxfReUw87y%2F0AY6pgEc43tHuBkT1%2FCcq8Vmi2sHnFJT%2BjrmFaCF81bGvBkrxWXfUilGMwLAu3OHC74UH49n7WUw9m7Qw6OmtrqrFgV2j8kaP%2FDpQLC7ZoDhmIG%2FyU%2B5gLjPn1BuW4jAglA82CVkV9%2FzrJGRIO3jyrVrkiRz3UrmwllH1rPr7u6W1xM6e1HN0mLsgLCKyj0UX0T0LrUiU63ceCjV35VdEkLykCutPCoXQWDq&X-Amz-Signature=37c852c594935c25d4b8f6daa1bf1ca71196753e6a0ac929c69274816354bf3b&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject


tf.keras.application에서 제공하는 MobileNetV2

mobileNetV2 — convert > TFLite(uint8)

Keras에서 제공하는 Application을 통해 다운받고 TF-Lite로 변환하자. TPU는 uint8만 호한이 가능함으로 변환 할 때 uint8로 바꿔줘야 하며 이때 Calibration Dataset을 이용한다.

  • Get MoibleNetV2
    model = tf.keras.applications.mobilenet_v2.MobileNetV2(
        input_shape=(224, 224, 3),
        alpha=1.0,
        include_top=True,
        weights='imagenet',
        input_tensor=None,
        pooling=None,
        classes=1000,
        classifier_activation='softmax'
    )
  • TF → TF-Lite(Calibration Dataset → Uint8 Quantization)
    # for keras model
    # A generator that provides a representative dataset
    def representative_data_gen():
        image_path = get_image("ImageNet-1000samples")
            yield [image_data]
    
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    # This enables quantization
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    # This sets the representative dataset for quantization
    converter.representative_dataset = representative_data_gen
    # This ensures that if any ops can't be quantized, the converter throws an error
    converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
    # For full integer quantization, though supported types defaults to int8 only, we explicitly declare it for clarity.
    converter.target_spec.supported_types = [tf.int8]
    # These set the input and output tensors to uint8 (added in r2.3)
    converter.inference_input_type = tf.uint8
    converter.inference_output_type = tf.uint8
    tflite_model = converter.convert()
    
    with open('mobilenet_v2_quant_uint8.tflite', 'wb') as f:
      f.write(tflite_model)
Download keras_mobilenet_v2_quant_uint8.tflite

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/b7881994-2811-4eb5-b895-a6eda647172c/mobilenet_v2_quant_uint8.tflite?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4663QIHWPFE%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053651Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQCWPVDbnJDa6SyM5RgbmMq48g2E6cVRh8%2B9Wla73xbeGAIhAJmpGkOb6nPX2oZNgM6PUtSR07R%2BfCBpJ2mvHMOUh0LfKv8DCBYQABoMNjM3NDIzMTgzODA1IgyTKOUnUxdLeDBLTtEq3AORrBIaNSe1%2FbHfUsmZy4rGUYUPr5%2B7F2l1p1JBswXu8kfpNYp5nHrNCo9VKlx%2BswJggGb7xQmAidc242eO2z7YxHde9cqptCBjQDGae4B1VJMjPavcfsDpNNL8vVbzh5yIO%2B4%2FiEXtlrWQI9Ju03BM1Dkr2XqpzH801nCJUKGDhHCqwOOt1J2hSybX8kMePlfkRe10p36%2BOOm0od4G9EfAGbd%2F2FE6QkZxR2bzA090S%2BDKJw%2Ba407pmm4Rl6y2oM2%2BqfeDWc%2B8vlsC6UtbqNQcYkTgCH5qxHq4LAc9Mvj07vPTe0PKYaaKZaqcnYRVFuW%2BShY%2F3HHfKIi0gLYaRGKP0CVkgOMd8pstUZspWs3NmArLadGUcdDlECFBMcXGAlHQhylDK6y2B852a9ehD%2BtpRwJW0TOBkayMVHC8iWJXG4lRjOSJ%2BZNnyrhPYRIuktsZYzPp2pRo8GE0wtESM2JpzK0bV0QSEI5aZYq4623zLoB5G1Ca%2FbH9Qk0bbsn1LNIThOMPlFd90hFTJX7oGL52mGagPq417WW66nBLjdMs85ON5tSGKarFlPd486ZSvIcVGrgSfRim91LMsc%2Bbs6K%2FjE00TwI1alc9mDP1pXr7MtK8NF5hsTuOyKGUtTCLvb%2FQBjqkAQpHgmcomjVcgDH3b2YF1q6CsuGws6c7Ap8ufoLwL9Z%2FVBtogOPxpEosTzAONTZa6ojdoGos2TbUguVvNH%2FNqjerctEI3s4%2BbXycO8i48oyA4VJMEVH%2Bm4BvxBazWtHg122ezPDtPgDu74jHz2lDp4FyND%2F64j0aiUvKk%2FtBEeO23cHfWsNSRFlQrx4hyNhuPhNzIB2whNidInsP2st7K505MS34&X-Amz-Signature=073e1d5d1cd9ccdf2c92e11eaff7ebd908ecc524277bdbfb7a4e413fa5b7e8a4&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

Download ImageNet-1000samples

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/32d2454c-0e44-4dde-b1db-802b04a60a25/ImageNet-1000samples.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466SKXI6WT6%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053651Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQC4e6A%2F0kpiYdqyGht7iH%2FaWC1Q5SHBK4L2AJBk7KssuAIhANZSwwp2f5EfVCJE2iP9we3UioCkSbaG69A7da%2B4OhomKv8DCBYQABoMNjM3NDIzMTgzODA1Igz5G6oVtupn5RHCfyAq3ANlQMFxqEOh37id8EzzOXuDQ%2BywOI0aWbCBeciJY0hQ8BiqwaLixduoT%2B%2FFarmYrkCgPjUJZiA7sbO1PBx11RAqZiY6BiL87t3dGCq3cEl1sR%2FG%2BA%2F%2Fp39TE04rhHEV%2BL4kT4iDXzk8L49ThBF56GItC6E938Akxlle%2B42dIl4xqT%2BH2AF6M0t1JqlY8tGA380cD6gYOzy8Tuij%2BRvy1QiT7Y0fwEm%2BZgYyOJ%2FUDnNoa6Xgsx%2Bbyd8I3mJ7bMjkUcZgEU4JfBi1BqmazzbDsmTVOVaNzRlpdQX%2FUt%2Fh9ZEO41dN4mycBpt1%2Fct8TOuPXBR%2FuiTya6R%2BbRgsA%2BSApaLOTCz9B8DBLrojFz5vvyJVSN7OQZ2YcB4wfZTlnjX1aoBI0SNVXyi%2B5cUqjgqFfpmgds%2B2Cu%2B7GmfJgc0o5aIXpREf8K8T6QAxa2q99suWWFK7qohIAz4Y%2F%2B910L%2B%2F%2BJwGUiq15oJLSATqaE3cVvi5ULNaV0eTlf9wFybRwpazsP%2BzORNop%2FBfavylO0dExnHDT9g85%2F9kKCWJiFLGAwRmypAxx68Df5AlLgDzsnef%2FuTaVOrYnbXaO1iUX6HjqoR17ztIk%2BKCDZuOIrldhP9P7zDN6WAOXeFE2IGXpjDtur%2FQBjqkAbCVJx6QDTwbIKzc6fdOQfWm2D4tntrBSvULwLQGSxy%2BMAaE2PZUm8%2Bi%2BICh85eRLA6Hebcvc62SzOHAtfPuuOIaYJk73UulwccczOiPBbXocsPhJQC5HbQO9W8N0Z887juUhXHaMNk4Q5wXIwDpVLjvA%2Bi%2FcdG3a5JMx%2BsG%2FyMYjw2ZjTtRIUrfmGoRNKTkIky%2Bqkd9Cbehvbf%2F7JsIIw5fbQ73&X-Amz-Signature=14084b97f20bd51a2ed1d69dce8eaf3a21497c6bfbcbd2dd0110299dc7b72e8e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

전체 코드

Code
import tensorflow as tf # 2.10.0
import numpy as np # 1.23.2
from PIL import Image # 9.3.0
import os

def get_image(image_dirs) :
    image_list = []
    for roots, dirs, files in os.walk(image_dirs) :
        for file in files :
            image_dir = os.path.join(roots, file)
            if image_dir.endswith("png") or image_dir.endswith("jpg") or image_dir.endswith("JPEG") :
                image_list.append(image_dir)

    return image_list

# for keras model
# A generator that provides a representative dataset
def representative_data_gen():
    image_path = get_image("ImageNet-1000samples")
   
    for image in image_path :
        image = Image.open(image).convert('RGB').resize((256, 256), Image.ANTIALIAS)
        image = np.array(image).astype(np.float32) # H, W, C
       
        image_data = image[16:240, 16:240, :].copy() #center crop

        mean = np.array([0.079, 0.05, 0]) + 0.406 # 0.485, 0.456, 0.406
        std = np.array([0.005, 0, 0.001]) + 0.224 # 0.229, 0.224, 0.225

        for channel in range(image_data.shape[2]):
            image_data[:, :, channel] = (image_data[:, :, channel] / 255 - mean[channel]) / std[channel] #RGB
       
        image_data = np.expand_dims(image_data, axis=0) # (1, 224, 224, 3)

        yield [image_data]

# Download Model
model = tf.keras.applications.mobilenet_v2.MobileNetV2(
    input_shape=(224, 224, 3),
    alpha=1.0,
    include_top=True,
    weights='imagenet',
    input_tensor=None,
    pooling=None,
    classes=1000,
    classifier_activation='softmax'
)

# convert
converter = tf.lite.TFLiteConverter.from_keras_model(model)
# This enables quantization
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# This sets the representative dataset for quantization
converter.representative_dataset = representative_data_gen
# This ensures that if any ops can't be quantized, the converter throws an error
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
# For full integer quantization, though supported types defaults to int8 only, we explicitly declare it for clarity.
converter.target_spec.supported_types = [tf.int8]
# These set the input and output tensors to uint8 (added in r2.3)
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
tflite_model = converter.convert()

with open('mobilenet_v2_quant_uint8.tflite', 'wb') as f:
  f.write(tflite_model)

이 경우 역시 Coral에서 제공된 TF-Lite모델의 구조와 유사한 것을 볼 수 있다.

< mobilenet_v2_quant_uint8.tflite Structure >
< mobilenet_v2_quant_uint8.tflite Structure >

https://netron.app/

TPU Compile

TFlite(uint8 quantization)이 끝났다면 이제 TPU에서 사용할 수 있도록 컴파일해야 한다.

Requirements (22.11.23 기준)

  • The Edge TPU Compiler can be run on any modern Debian-based Linux system. Specifically, you must have the following:
    • 64-bit version of Debian 6.0 or higher, or any derivative thereof (such as Ubuntu 10.0+)
    • x86-64 system architecture
  • If your system does not meet these requirements, try our web-based compiler using Google Colab.

https://coral.ai/docs/edgetpu/compiler/#system-requirements

요구사항이 충족되면 아래 링크의 명령어를 이용해 컴파일 할 수 있다. 나는 코랩을 추천한다. (코랩에서 사용하기)

https://coral.ai/docs/edgetpu/compiler/#download

또는 Pretrained된 모델을 Custom하고 싶다면 아래 코드를 참고하자

https://colab.research.google.com/github/google-coral/tutorials/blob/master/retrain_classification_ptq_tf1.ipynb?hl=ko

컴파일에 성공하면 아래처럼 뜨며 컴파일된 모델 구조는 다음과 같이 확인된다.

           < mobilenet_v2_quant_uint8_edgetpu.tflite Structure >
< mobilenet_v2_quant_uint8_edgetpu.tflite Structure >

https://coral.ai/docs/edgetpu/models-intro/#supported-operations

Download keras_mobilenet_v2_quant_uint8_edgetpu.tflite

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/6004cf0e-fdc3-481a-ad9e-f9da1f20eecc/mobilenet_v2_quant_uint8_edgetpu.tflite?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466WZNATPIW%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053653Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJHMEUCIQDm5wUmKs%2Bk3UWcUcSRy2NTRkviUl%2FNR%2BeST%2Bz62Dv39gIgWI6dlVDINySbkzXjg%2FPQM5%2FwKQDaczM94zASKwbpPo8q%2FwMIFhAAGgw2Mzc0MjMxODM4MDUiDD2YW6KZk7AUfHT%2FmSrcA864ttMnUHvOQbmkIqr%2BTM9TxHL9nHovt2WrIGEUn%2BbleZAfHuVLiW%2FJ5wg28%2FIHqOUYeLwM6NrKg%2FBqVhye7LVD9nHV%2FnB%2FRj7r1pCdmOMh0JEaRrp9LGqts%2BCMP6q9UH7bFgOQ5wOh48MtlyuEXiwIszADrKOVMRWp8sdtDjuIZJr75ZNvOp8eorIWV1nyBlyJ98ig2IkSJlM3v1G0ExucskTriTPKxQUa%2FScoCOxfF8C32O%2FyPq%2FzR0m8xr5pPlHanyErZLl3n0NYT8rsDZQDDLHGH6o9y0P8E4YZtz%2F6H6bo%2F2xQNLbnu3BSzHyZARqC3zM%2B8yyXrzJS6mZdWDWqrEkUpHHKhS98UEbrxz5GiaZu0suzGDqhxRSlBV%2FSOIUC4mbmWSWzGSljxY6%2B1YuxH3vN2WkZVyz33Ih5RzBKB7xD3o1d47NoMCiNVgW5yDPTEDfDyuo%2FDHwufED00Bix%2BVCtBK9hhUcJNK5XYDgI7kmUU0wOes%2FUAv%2FA3T1y8YAiqiBixH5Cqh%2FJJLh0AMHaJjLc0znN6q7oTcJzGu5vQiMCUeHE%2Fd4Ue%2Ftbb%2FHQ5cdmYAmoDk7qugZ4UXtMbCZsvlDNqzYre21Y2qJpN%2FVGjOK3CH5y0skuNrbEMNy8v9AGOqUBECsDxw6XN1HEh5By28T%2FffR1C%2BmopDgqhhHaqa0e0DSLmqzv%2Bt1zuvCti84cM0%2FJmzGzir5rPTGuNeXXCGOse6YbJLyKilbyT%2F8xehL3ZLlVs8CtWRjEi7%2BJEzgRDCsHwEu%2FhjF3Qfu%2BIy2jITBDNKUiFLqlD51evFN6v1KLLfsgtL38RH8dZ1LnX1AKpPCm4jRmX7wcqQHqKmE0hfsLmngkxT13&X-Amz-Signature=fa67642c73635057353ef84c952d36a8ed7a47c7eb81582ad9c978df5fa017ac&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

Keras MobileNetV2 Inference

python3 classify_custom.py \
  --model keras_mobilenet_v2_quant_uint8_edgetpu.tflite \
  --labels keras_imagenet_classes.txt \
  --input 113_data
Directory 환경
classify_custom.py 코드
import os
import argparse
import time
import numpy as np
from PIL import Image
from pycoral.adapters import classify
from pycoral.adapters import common
from pycoral.utils.dataset import read_label_file
from pycoral.utils.edgetpu import make_interpreter

def main() :
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '-m', '--model', required=True, help='File path of .tflite file.')
    parser.add_argument(
        '-i', '--input',  required=True, help='Image path.') # input test_data/
    parser.add_argument(
        '-l', '--labels', help='File path of labels file.')
    parser.add_argument(
        '-k', '--top_k', type=int, default=1,
        help='Max number of classification results')
    parser.add_argument(
        '-t', '--threshold', type=float, default=0.0,
        help='Classification score threshold')
    parser.add_argument(
        '-c', '--count', type=int, default=1,
        help='Number of times to run inference')
    parser.add_argument(
        '-a', '--input_mean', type=float, default=128.0,
        help='Mean value for input normalization')
    parser.add_argument(
        '-s', '--input_std', type=float, default=128.0,
        help='STD value for input normalization')
    args = parser.parse_args()

    labels = read_label_file(args.labels) if args.labels else {}

    print(args.model)
    interpreter = make_interpreter(args.model)
    interpreter.allocate_tensors()

    # Model must be uint8 quantized
    if common.input_details(interpreter, 'dtype') != np.uint8 :
        raise ValueError('Only support uint8 input type.')

    size = common.input_size(interpreter)
    params = common.input_details(interpreter, 'quantization_parameters')
    scale = params['scales']
    zero_point = params['zero_points']
    mean = args.input_mean
    std = args.input_std

    image_name = os.listdir(args.input)  # image name
    image_filter = list(filter(lambda x: x.find('.JPEG') != -1, image_name))  # image format filtering

    
    print("Note : The first inference on Edge TPU is slow because it includes")
    print("loading the model into Edge TPU memeory.")

    f = open("./keras_mobilenet_data.csv","w")
    f.write("ImageName,class,score,inference_time\n")
    i=1
    for img in image_name:

        image = Image.open(args.input + img).convert('RGB').resize(size, Image.ANTIALIAS)

        if abs(scale * std - 1) < 1e-5 and abs(mean - zero_point) < 1e-5:
            # Input data does not require preprocessing.
            common.set_input(interpreter, image)
        else:
            # Input data requires preprocessing
            normalized_input = (np.asarray(image) - mean) / (std * scale) + zero_point
            np.clip(normalized_input, 0, 255, out=normalized_input)
            common.set_input(interpreter, normalized_input.astype(np.uint8))

        print('----INFERENCE TIME AND RESULTS----')
        start = time.perf_counter()
        interpreter.invoke()
        inference_time = time.perf_counter() - start
        classes = classify.get_classes(interpreter, args.top_k, args.threshold)
        
        for c in classes:
            result = labels.get(c.id, c.id)
            result = result.replace(", ","/")
            print(f"{i} {img} {result}, {c.score:.5f}, {inference_time*1000:.1f}ms")
            f.write(f"{img}, {result}, {c.score:.5f}, {inference_time*1000:.1f}ms\n")
            i += 1

if __name__ == '__main__':
    main()
Download classify_custom.py, keras_imagenet_classes.txt, 113_data.7z, keras_mobilenet_data.csv

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/287790c1-4076-4bed-a83a-f27d9e0da076/classify_custom.py?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466WF4SLKF2%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053654Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQCVGHTFw6J9AE4AuZOFbwsGkBv48EifsrWvVvVaDpRdIQIhAPjlpugyWBE9%2BulkipzkvL5zRuUp4DajxXJmkt6Oav1JKv8DCBYQABoMNjM3NDIzMTgzODA1IgwXc2deYdMxjmhxZasq3ANz5tL5zw1mLy5pgtORTU9olnCx7HVshhmqU4wYT7Xe1KrRXymfad8mYUP7k9MEXHlXuYDLpQrBhVJyM1HHQFJjWZmn0tvXvOOEZpMIR%2B7JF2OK5kWeSkBGTgncHqywfHOOAwTA4euwX9quW%2F6j4FtCjkOCTp89OHOcFzGg6k1fRMlDm73ezgv7fB0YAJZmQXbqDz948yDTpyt6Wi9MLRfEDNnKgcPBTcsCI8YXy2e6tZWwgeNRUmYCi1CFRNF8hodswUWaleZVefzJ2dn70zVgpXDEAodKAvIargc1ZyYDleiyQknCRgV8CggX2oUoCdVaCFHyCDXZCFI9ym8pI0CKwQBOtVWA%2FOasSdtlU1DobMStP%2BL9ZhgPYtN9sDKZNLhdCJpbF%2FHj1PmQ5n3w%2FhgHPYXE3OqUb6R7hhaf1WYgxRspqkxbYB0Ts4O4dZ9kTIZRJVK7Fbw6MTEItgk5e3zW3FHnv3i64Ikc9D5Yg%2Bh3DJsmSFshYfR8QSwj2j0fO5LKr6SygF%2FENiDnUFKUsSJV7kPvsiYynlOArGXK%2BcVgcKBlnewVNVy56uijxP8EYdeyUIFGD3MdsryOcV41Pr6KDeuVGjDITbet%2FNyGi4Cd66qIojOEgsOo86A4YjCUvL%2FQBjqkAWJolKXmPbbYrL%2FvXU5BSm36oPW3HdkEIkQahyo2s37vpCoOJgQ6UAVj3FrRCSHRxv2gbGGBttf%2Bs5GjrbA9EuhZSjgC7b5JlU0RARtDpKFd06rLGUemtySL2Q0uza13y%2BNZm9HlndfAob30VlUFFoBBac%2FVSr31%2BQa9URpuvXpTt6ULbEVnN7XuX105SQp0UzAoUj1V%2BtYxy6bE0YoJ2%2BixBsBa&X-Amz-Signature=6dc06bc2f0774a82f5b366abe678f6e8cafdf1bb9280288633049c924f5dcb88&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/79a37565-c413-4569-8ff4-e6594e475fdf/keras_imagenet_classes.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466WF4SLKF2%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053654Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQCVGHTFw6J9AE4AuZOFbwsGkBv48EifsrWvVvVaDpRdIQIhAPjlpugyWBE9%2BulkipzkvL5zRuUp4DajxXJmkt6Oav1JKv8DCBYQABoMNjM3NDIzMTgzODA1IgwXc2deYdMxjmhxZasq3ANz5tL5zw1mLy5pgtORTU9olnCx7HVshhmqU4wYT7Xe1KrRXymfad8mYUP7k9MEXHlXuYDLpQrBhVJyM1HHQFJjWZmn0tvXvOOEZpMIR%2B7JF2OK5kWeSkBGTgncHqywfHOOAwTA4euwX9quW%2F6j4FtCjkOCTp89OHOcFzGg6k1fRMlDm73ezgv7fB0YAJZmQXbqDz948yDTpyt6Wi9MLRfEDNnKgcPBTcsCI8YXy2e6tZWwgeNRUmYCi1CFRNF8hodswUWaleZVefzJ2dn70zVgpXDEAodKAvIargc1ZyYDleiyQknCRgV8CggX2oUoCdVaCFHyCDXZCFI9ym8pI0CKwQBOtVWA%2FOasSdtlU1DobMStP%2BL9ZhgPYtN9sDKZNLhdCJpbF%2FHj1PmQ5n3w%2FhgHPYXE3OqUb6R7hhaf1WYgxRspqkxbYB0Ts4O4dZ9kTIZRJVK7Fbw6MTEItgk5e3zW3FHnv3i64Ikc9D5Yg%2Bh3DJsmSFshYfR8QSwj2j0fO5LKr6SygF%2FENiDnUFKUsSJV7kPvsiYynlOArGXK%2BcVgcKBlnewVNVy56uijxP8EYdeyUIFGD3MdsryOcV41Pr6KDeuVGjDITbet%2FNyGi4Cd66qIojOEgsOo86A4YjCUvL%2FQBjqkAWJolKXmPbbYrL%2FvXU5BSm36oPW3HdkEIkQahyo2s37vpCoOJgQ6UAVj3FrRCSHRxv2gbGGBttf%2Bs5GjrbA9EuhZSjgC7b5JlU0RARtDpKFd06rLGUemtySL2Q0uza13y%2BNZm9HlndfAob30VlUFFoBBac%2FVSr31%2BQa9URpuvXpTt6ULbEVnN7XuX105SQp0UzAoUj1V%2BtYxy6bE0YoJ2%2BixBsBa&X-Amz-Signature=f2a3b057d24dc9245e85110bfd2b99158c04b63ffd9bc0ea8a5c962381a643aa&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/bcacec85-d4bb-4a36-96f5-2dc5da594bb7/113_data.7z?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466WF4SLKF2%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053654Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQCVGHTFw6J9AE4AuZOFbwsGkBv48EifsrWvVvVaDpRdIQIhAPjlpugyWBE9%2BulkipzkvL5zRuUp4DajxXJmkt6Oav1JKv8DCBYQABoMNjM3NDIzMTgzODA1IgwXc2deYdMxjmhxZasq3ANz5tL5zw1mLy5pgtORTU9olnCx7HVshhmqU4wYT7Xe1KrRXymfad8mYUP7k9MEXHlXuYDLpQrBhVJyM1HHQFJjWZmn0tvXvOOEZpMIR%2B7JF2OK5kWeSkBGTgncHqywfHOOAwTA4euwX9quW%2F6j4FtCjkOCTp89OHOcFzGg6k1fRMlDm73ezgv7fB0YAJZmQXbqDz948yDTpyt6Wi9MLRfEDNnKgcPBTcsCI8YXy2e6tZWwgeNRUmYCi1CFRNF8hodswUWaleZVefzJ2dn70zVgpXDEAodKAvIargc1ZyYDleiyQknCRgV8CggX2oUoCdVaCFHyCDXZCFI9ym8pI0CKwQBOtVWA%2FOasSdtlU1DobMStP%2BL9ZhgPYtN9sDKZNLhdCJpbF%2FHj1PmQ5n3w%2FhgHPYXE3OqUb6R7hhaf1WYgxRspqkxbYB0Ts4O4dZ9kTIZRJVK7Fbw6MTEItgk5e3zW3FHnv3i64Ikc9D5Yg%2Bh3DJsmSFshYfR8QSwj2j0fO5LKr6SygF%2FENiDnUFKUsSJV7kPvsiYynlOArGXK%2BcVgcKBlnewVNVy56uijxP8EYdeyUIFGD3MdsryOcV41Pr6KDeuVGjDITbet%2FNyGi4Cd66qIojOEgsOo86A4YjCUvL%2FQBjqkAWJolKXmPbbYrL%2FvXU5BSm36oPW3HdkEIkQahyo2s37vpCoOJgQ6UAVj3FrRCSHRxv2gbGGBttf%2Bs5GjrbA9EuhZSjgC7b5JlU0RARtDpKFd06rLGUemtySL2Q0uza13y%2BNZm9HlndfAob30VlUFFoBBac%2FVSr31%2BQa9URpuvXpTt6ULbEVnN7XuX105SQp0UzAoUj1V%2BtYxy6bE0YoJ2%2BixBsBa&X-Amz-Signature=30c71ebd39a47915854d1fbf40d82327e8be45966aa718ae43d64af3bb809fee&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/be9bf6b4-24e1-493e-b7eb-a40716a16d9f/keras_mobilenet_data.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466WF4SLKF2%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053654Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQCVGHTFw6J9AE4AuZOFbwsGkBv48EifsrWvVvVaDpRdIQIhAPjlpugyWBE9%2BulkipzkvL5zRuUp4DajxXJmkt6Oav1JKv8DCBYQABoMNjM3NDIzMTgzODA1IgwXc2deYdMxjmhxZasq3ANz5tL5zw1mLy5pgtORTU9olnCx7HVshhmqU4wYT7Xe1KrRXymfad8mYUP7k9MEXHlXuYDLpQrBhVJyM1HHQFJjWZmn0tvXvOOEZpMIR%2B7JF2OK5kWeSkBGTgncHqywfHOOAwTA4euwX9quW%2F6j4FtCjkOCTp89OHOcFzGg6k1fRMlDm73ezgv7fB0YAJZmQXbqDz948yDTpyt6Wi9MLRfEDNnKgcPBTcsCI8YXy2e6tZWwgeNRUmYCi1CFRNF8hodswUWaleZVefzJ2dn70zVgpXDEAodKAvIargc1ZyYDleiyQknCRgV8CggX2oUoCdVaCFHyCDXZCFI9ym8pI0CKwQBOtVWA%2FOasSdtlU1DobMStP%2BL9ZhgPYtN9sDKZNLhdCJpbF%2FHj1PmQ5n3w%2FhgHPYXE3OqUb6R7hhaf1WYgxRspqkxbYB0Ts4O4dZ9kTIZRJVK7Fbw6MTEItgk5e3zW3FHnv3i64Ikc9D5Yg%2Bh3DJsmSFshYfR8QSwj2j0fO5LKr6SygF%2FENiDnUFKUsSJV7kPvsiYynlOArGXK%2BcVgcKBlnewVNVy56uijxP8EYdeyUIFGD3MdsryOcV41Pr6KDeuVGjDITbet%2FNyGi4Cd66qIojOEgsOo86A4YjCUvL%2FQBjqkAWJolKXmPbbYrL%2FvXU5BSm36oPW3HdkEIkQahyo2s37vpCoOJgQ6UAVj3FrRCSHRxv2gbGGBttf%2Bs5GjrbA9EuhZSjgC7b5JlU0RARtDpKFd06rLGUemtySL2Q0uza13y%2BNZm9HlndfAob30VlUFFoBBac%2FVSr31%2BQa9URpuvXpTt6ULbEVnN7XuX105SQp0UzAoUj1V%2BtYxy6bE0YoJ2%2BixBsBa&X-Amz-Signature=a48203b95927a009726ad65c28e0a8ded78802d9ea38a4f6a0fdeff37e4f0c09&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject


ONNX(Model Zoo)에서 제공하는 MobileNetV2

mobileNetV2-12.onnx — convert > TF(saved_model) —convert—> TFLite(uint8)

mobileNetV2-12.onnx는 아래 링크에서 받을 수 있다.

https://github.com/onnx/models/tree/main/vision/classification/mobilenet/model

Download mobileNetV2-12.onnx

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/36c5c598-3bae-4763-8969-b7d16fe8c5e4/mobilenetv2-12.onnx?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466THMVVT3G%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053654Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJHMEUCID%2F%2BJuDRRp8q96YY4rn7OL2EE0Lc5mjHTR9LbrZEbdrKAiEA41o5mMs1NeyTWzFr2LVbQho%2B94R%2FhoZFQD%2FFaMwzLwsq%2FwMIFhAAGgw2Mzc0MjMxODM4MDUiDAGk%2FkXB0kP%2BYttEwSrcAxsajU0Ri38beEtjFCGI%2Bt2eUhHt7%2FqzVjE6PygXH1%2Bxc8BDVXACmDcevvqVsvnucosMxLt153kt3y%2FQo5LCVHSRlIqqsCfzucOIQP4QASy%2FOJ9AS0ZLguQa9TCk0NlWfnwdCXZIlzIAPllg6wjXgiKw9RaM2v233aeaMFqcoNLYmBoUxCxuFQrH1JbxsQvp8FPTsNB8onTQLu%2B7ZZQ0Lay%2FldNgxSbdSZ4bEWqWEx9%2B7fFQmojmMOvxV8dYu6Ol6QQZfm9OYCLr4e4VJvjZTJwHIeBQ6S39%2Bkzi0nnK55r7X9AFnW32tBicK1pnS%2BagxBq%2FtZ9TXRN42xuyOdaA2%2B7pVd0dWGBrXRccy5DN0GGN9P0tjKBqGKbs05K9C%2FD%2Fdl6rnxIT%2Bv8m6iAE8tJ3NuWTmlNE%2BJ4OcsZ7z4V9vxQ1yfu%2FsniQFjdMmq%2F3MKE2472KveOLN8pRCidKYP1AxDT%2FNizNPJ0LrFKwwcpd3SeNR6UJT7ualU%2FAqaWZBv7XDFqaYaU0hUWGgSh24AG9PMCBj7DFnpxUoY1jPtH9ckydp8oSCIUffNEFIwy9507YPXRN0d%2BqkiBAnnTEaFU4HaoHlXavzk2iHTAG8fUwVWbn14dWLjorebWhOOMhMOS9v9AGOqUBaQEk%2Buvg1cklgU7VFQhBnoiS%2Byz1snKeTNhJaVbGj5Gn5dtJb92w56Khb7FrqQ0lbwPa5tlsB0ydrak5D3z1XTPh3%2BJXTJ2QRbmazM%2BPogTLmST1ZmaCCavcDZkcqqfECb%2BMHLfWc%2BeK%2FIf5Z0eEw5tmzeU55zuunyIaWUi3HnL2FHCZC6QRfKajcQJUAFdmKDIxc11JPjMBMUfumXsoXFKrNUPN&X-Amz-Signature=04e05e4047343fe5e17383bcf380642bb75a92920b394a094f9c1b704ac77365&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

mobileNetV2-12.onnx 구조를 살펴보면 이전의 모델과는 좀 달리 Relu대신 Clip이라는 layer가 들어와 있는 것을 볼 수 있다.

  • mobileNetV2-12.onnx → TF(saved_model.pb) 변환
    import onnx # 1.12.0
    from onnx_tf.backend import prepare # onnx-tf 1.12.0
    # tensorflow-probability 0.18.0
    
    onnx_model_path = "./model.onnx" # your onnx model
    
    onnx_model = onnx.load(onnx_model_path)
    tf_rep = prepare(onnx_model)
    tf_rep.export_graph("./onnx2tf") # get saved_model.pb
  • TF(saved_model.pb) → TF-Lite(Calibration Dataset → Uint8 Quantization)
    # for saved model
    def representative_data_gen():
        image_path = get_image("ImageNet-1000samples")
       
        for image in image_path :
            image = Image.open(image).convert('RGB').resize((256, 256), Image.ANTIALIAS)
            image = np.array(image).astype(np.float32)
            image_data = image[16:240, 16:240, :].copy() # center crop
           
            image_data = image_data.transpose([2, 0, 1]) # C, H, W
            mean = np.array([0.079, 0.05, 0]) + 0.406 # 0.485, 0.456, 0.406
            std = np.array([0.005, 0, 0.001]) + 0.224 # 0.229, 0.224, 0.225
    
            for channel in range(image_data.shape[0]):
                image_data[channel, :, :] = (image_data[channel, :, :] / 255 - mean[channel]) / std[channel] #RGB
           
            image_data = np.expand_dims(image_data, axis=0) # 1, 3, 224, 224 
            print(image_data.shape)
           
            yield [image_data]
    
    
    converter = tf.lite.TFLiteConverter.from_saved_model(model)
    # This enables quantization
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    # This sets the representative dataset for quantization
    converter.representative_dataset = representative_data_gen
    # This ensures that if any ops can't be quantized, the converter throws an error
    converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
    # For full integer quantization, though supported types defaults to int8 only, we explicitly declare it for clarity.
    converter.target_spec.supported_types = [tf.int8]
    # These set the input and output tensors to uint8 (added in r2.3)
    converter.inference_input_type = tf.uint8
    converter.inference_output_type = tf.uint8
    converter.allow_custom_op = True
    tflite_model = converter.convert()
    
    with open('from_onnx_mobilenet_v2_quant_uint8.tflite', 'wb') as f:
      f.write(tflite_model)
Download from_onnx_mobilenet_v2_quant_uint8.tflite

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/c9c60a2d-1676-4c5f-8049-cad5037063bc/from_mobilenet_v2_quant_uint8.tflite?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466YBP4GCIF%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053655Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJHMEUCIQCBkagL%2FMo96DkfcDEl4878xASvqFsgAH0fxDM7k04ZTgIgXeenEM%2FGI0uWWokzLVynZwidLV3bRwe83gXkbCeW64Iq%2FwMIFhAAGgw2Mzc0MjMxODM4MDUiDGvp7yeNsFYL18XM3SrcA6t4Gi7YXv0Bj6iU89m8cM5ETe%2B8w5fm%2FwassZvN6Nmug5uplgzGYOzkDbAoUVWWRuhyGmRB8qqZeU4r4PJ1ygj8JoMZ1%2BSa1Gjz7BVNlflGFnSR2dZohd7uM%2BUiRHrGTOm1Wc8MPaiDzKlciXf1hQUwTsEyxNAUpcCdxpDm%2FRKxyEJTa2OvgmVm4o4p01GoZa2Bx6a4jLX%2BLREhnkuiOmile34y9UOsM1m46KivAbjWZtpnuh2MDo34EiO2q%2Fo7Hnk%2FKyUclaptniEFzOFTQ%2Fbbc51r1nTDHJj0etnB8hK9DF8PQwNX5jYthCStDfIkQNny%2FdQc%2BK60Ggbo05SMH4D70pXJfIHTSr6Bhe%2FFFNA%2Bh944VypRM3kaALOhKzz4zDk0mwVuMOPnTPzezaCUmxjAHSMT2NaTt%2Bof6gnzfEAjAOL9uqqOJV%2Bq1sO4pJ7qYa%2FKnb83VJ%2BJGcJiBdzJogcPUhcXbAQ0adwb%2B4UF05ODZ1vprmpqNbJx9h5SkmxRoVE06Ke4bcnuEKKYuQ04PKGK2Xogon%2BGynYeE5Pm88AfNhdzhrdk7jcJuwGRxJBVXbpEZOrxdQkFnn%2F0OJ8dregm6njidUWW5jESQl%2FhH9eFJDxfX0ZpurEO7OuaMP26v9AGOqUBgeBhQkYH5P7DfY7iKa1XVA48PXs6Yfyoi%2B33VIkfHdr%2F9qlz7koxAxoXoWTP6I5cT76%2B%2BEnfupUhAEzfLB9eaZE3ZDD2E8RfNwWMYL0nk5quwBLPpy8rtyv6l94lF%2FXLDVZ9LrWT%2FuZkSHkZFICk%2Bj3p9q25palxPVV5nfWANlUuw623d1IFfvj9uH0eoq9k2L3UD28KqOqLCK4Vf1DXouYMsYFP&X-Amz-Signature=b51d59849b289fdd2ac2a542127a41d56a95fd8e9afb26b87c768f4892199e87&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

전체 코드

Code
import tensorflow as tf # 2.10.0
import numpy as np # 1.23.2
from PIL import Image # 9.3.0
import os

def get_image(image_dirs) :
    image_list = []
    for roots, dirs, files in os.walk(image_dirs) :
        for file in files :
            image_dir = os.path.join(roots, file)
            if image_dir.endswith("png") or image_dir.endswith("jpg") or image_dir.endswith("JPEG") :
                image_list.append(image_dir)

    return image_list

# for saved model
def representative_data_gen():
    image_path = get_image("ImageNet-1000samples")
   
    for image in image_path :
        image = Image.open(image).convert('RGB').resize((256, 256), Image.ANTIALIAS)
        image = np.array(image).astype(np.float32)
        image_data = image[16:240, 16:240, :].copy() # center crop
       
        image_data = image_data.transpose([2, 0, 1]) # C, H, W
        mean = np.array([0.079, 0.05, 0]) + 0.406 # 0.485, 0.456, 0.406
        std = np.array([0.005, 0, 0.001]) + 0.224 # 0.229, 0.224, 0.225

        for channel in range(image_data.shape[0]):
            image_data[channel, :, :] = (image_data[channel, :, :] / 255 - mean[channel]) / std[channel] #RGB
       
        image_data = np.expand_dims(image_data, axis=0) # 1, 3, 224, 224 
        print(image_data.shape)
       
        yield [image_data]

model = "./onnx2tf"

converter = tf.lite.TFLiteConverter.from_saved_model(model)
# This enables quantization
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# This sets the representative dataset for quantization
converter.representative_dataset = representative_data_gen
# This ensures that if any ops can't be quantized, the converter throws an error
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
# For full integer quantization, though supported types defaults to int8 only, we explicitly declare it for clarity.
converter.target_spec.supported_types = [tf.int8]
# These set the input and output tensors to uint8 (added in r2.3)
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
converter.allow_custom_op = True
tflite_model = converter.convert()

with open('from_onnx_mobilenet_v2_quant_uint8.tflite', 'wb') as f:
  f.write(tflite_model)

위에 Coral과 Keras에서 제공했던 TF-Lite구조가 많이 다른데, 개인적인 생각으로는 Clip layer때문에 ONNX → TF로 Convert된 Saved_model.pb이 Clip 기능을 아래와 같은 구조를 갖는 layer로 만들기 때문이 아닐까 생각이 든다.

< from_onnx_mobilenet_v2_quant_uint8.tflite Structure >
< from_onnx_mobilenet_v2_quant_uint8.tflite Structure >

https://netron.app/

TPU Compile

TFlite(uint8 quantization)이 끝났다면 이제 TPU에서 사용할 수 있도록 컴파일해야 한다.

Requirements (22.11.23 기준)

  • The Edge TPU Compiler can be run on any modern Debian-based Linux system. Specifically, you must have the following:
    • 64-bit version of Debian 6.0 or higher, or any derivative thereof (such as Ubuntu 10.0+)
    • x86-64 system architecture
  • If your system does not meet these requirements, try our web-based compiler using Google Colab.

https://coral.ai/docs/edgetpu/compiler/#system-requirements

요구사항이 충족되면 아래 링크의 명령어를 이용해 컴파일 할 수 있다. 나는 코랩을 추천한다. (코랩에서 사용하기)

컴파일을 시도하면 실패하는 것을 볼 수 있다.

ERROR: Attempting to use a delegate that only supports static-sized tensors with a graph that has dynamic-sized tensors.

Compile에 실패하는 이유

예상되는 실패의 이유는 컴파일 되려는 모델의

  • Operation이 TPU에서 지원하지 않는다.
  • Layer가 dynamic shape을 가지고 있다.

나의 경우는 전자이다, ONNX 모델의 layer중 Coral TPU에서 지원하지 않는 Operator가 있기 때문이다.

https://coral.ai/docs/edgetpu/models-intro/#supported-operations

지원 Operator은 위에 링크에서 볼 수 있다.

Model.ONNX에서 출력부분에서 Coral에서 TPU사용에 지원되지 않는 Operator(Shape, Gather, Unsqueeze, Gemm)가 존재 했기 때문에 당연히 변환된 TF-Lite파일도 지원되지 않는 Operator가 있기 마련이다.

아래 파일은 Coral(TF-Lite), ONNX(onnx), Keras(TF-Lite)에서 지원하는 MobileNet의 모델을 분석한 결과이다.

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/060cca45-561d-4d14-849c-3e192e217ae5/model_%EB%B6%84%EC%84%9D.pptx?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466UZ4IFKXZ%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053639Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJIMEYCIQCrCduAYYRS0JouiJH%2BQKMDYuEOeQUsWdfHPtJbSIQOxQIhAJAsS64briOgEObpfpJk%2B8r43k%2FWzkAkYSY32dgxQhudKv8DCBYQABoMNjM3NDIzMTgzODA1Igyf6m0JKg31SKnCX%2F0q3APplxYgTcWXw%2FReNkJt5xexwVHha%2F0a4mMQkuXugzQsaf79bs9Y05MkstXWrJAo89gySdKRUFO07iEt4KPxEVIddDq9d47ERuO3IRpsVsf4FpIuLlTW11kSzhMpxPabEztfnJhE7FeBTZ38rDPZHZl1M4yXjY%2BIwguWKDsoER53ls1GRTAB%2FswayEBYHsn8HnfqMcc49EsMrnfLoBy8gChuCS6gOoOzKgPtQGRLbyenwLbxi1YDc71ow1YK8QTp3r3JC7qBrUWGrlEmkvh1U0c13YUbeqScEer3dnrGzPqjEwl%2Bt8d3bwXSPczd98nQpxmm9V0sPyBWkZV1%2FaBQENtfVt64PgbYZfVDCLNBmxijO1IG1PMK6lHlWD1aKgWx7XzwCgv2k6YzIPxf1BWmFrKp5WH6EOs2lzY8kFoJMHfvBYBiz8RF%2Bt0OJtwpDwfxcyWwppccpP6TQnqnmqktplTucsH3pqQAEjaGxkEljMyxp6EgNR6qSZXcpGCa8AwVMFaLMGWidEI6Szsu6ORzgnJs3EY0wraagGIbCxuG8v3%2F1gBv9O9CGvdC%2B4J71Wdeyz012IJC%2B9s5buh6lYOPCvXS9zSr%2B9XCH0fIqcFNV5ODhR%2BiTrrPRyLbFw5nyTCZu7%2FQBjqkAVoI8v9hID4m9tRPSIZIY0lscZnZUy%2BxFSpUh42SqdALn9y2y4B9QOemmXS%2FQSzNd3RL%2BXMYPYxZbiABNlbdJvvBAtFxhEUfvkcqKOmTcMn5PP1as2EKBZBPXGFur2WIc2osJIC9ezOrifSXyrOho8FYKFLC56OM7i5fI4PFx6c0Dw4JTNzqd4JTR%2FGT%2Fc%2Bt%2FsboTcJEbaJTEt24XuT7hMWC0s6H&X-Amz-Signature=787e2052e0d5282e1bb834d5540a65a225d5d9ee03c1ab5c6341dfbcb517cc58&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

간략히 설명하면 각 모델의 출력부분인데 Coral, Keras는 해당 Operator를 Coral에서 전부 지원하는 반면, Model.ONNX는 지원되지 않는 Operator가 있다.

(2022년 11월 23일 기준으로는 지원 안 함 → 추후엔 지원해줄 수도 있음)

혹시나 하여 모델이 잘못 변형될 수도 있겠다는 생각이 들어 TPU Compile하지 않고 TF-Lite로만 Raspi에서 돌렸을 때 잘 작동한다.

아래 코드는 Coral에서 제공한 classify_image.py를 참고해 수정했다. (TF 버전이 맞지 않으면 에러가 날 수도 있다.)

  • PC에서 시도해보니(tf 2.5.0) 다음과 같은 에러가 난다.(Raspi에서는 잘 작동하니 TF버전에 유의하자)
    ValueError: Didn't find op for builtin opcode 'ADD' version '4'. An older version of this builtin might be supported. Are you using an old TFLite binary with a newer model?
    Registration failed.

TF-Lite Inferece(Without using TPU)

Code
# has been run by Raspi - python 3.7.12
import os
import numpy as np
from PIL import Image
import tensorflow as tf # 2.10.0
import time


def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

def preprocess_image(image_path) :
    image_path = image_path
    image = Image.open(image_path).convert('RGB').resize((256, 256), Image.ANTIALIAS)
    image_data = np.array(image)
    image_data = image_data[16:240, 16:240, :].copy()
    image_data = image_data.transpose([2, 0, 1]) # C, H, W
    
    return image_data


with open("imagenet_classes.txt", "r") as f:
    categories = [s.strip() for s in f.readlines()]


interpreter = tf.lite.Interpreter('from_onnx_mobilenet_v2_quant_uint8.tflite')

interpreter.allocate_tensors()

img_path = "parrot.jpg"
data = preprocess_image(img_path)

input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

params = input_details[0]['quantization_parameters']
scale = params['scales']
zero_point = params['zero_points']

mean = 128.0
std = 128.0
data = np.expand_dims(data, axis=0)

if abs(scale * std - 1) < 1e-5 and abs(mean - zero_point) < 1e-5:
    interpreter.set_tensor(input_details[0]['index'], data)
    print("No_Scaling")
else:
    normalized_input = (np.asarray(data) - mean) / (std * scale) + zero_point
    np.clip(normalized_input, 0, 255, out=normalized_input)
    interpreter.set_tensor(input_details[0]['index'], normalized_input.astype(np.uint8))
    print("Scaling")

start = time.perf_counter()
interpreter.invoke()
end = time.perf_counter()

inference_time = (end-start)*1000

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

scale, zero_point = output_details[0]['quantization']
output_data = scale * (output_data.astype(np.int64) - zero_point)
max_idx = np.argmax(output_data[0])
confidence = softmax(output_data[0])

print(f"{img_path}, {categories[max_idx]}, {confidence[max_idx]*100:.2f}, {inference_time:.4f}ms")
Download uint8_tflite_inference (For mobilenetV2)

https://prod-files-secure.s3.us-west-2.amazonaws.com/2861f846-8c98-4301-9ec7-27b23866c687/94c81484-3cb2-43eb-820e-45b694019a1e/uint8_tflite_inference.py?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466XH5VRULG%2F20260522%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260522T053657Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEE0aCXVzLXdlc3QtMiJHMEUCIQCQY0nEkL7bb9ZW7B9AqSorzfO5k9VaoDd9dJgpLHLR%2BQIgHRMzwE3%2Fk1YFTxxv2mzcIrY4S6wz7nmFTbbI1EJnJKQq%2FwMIFhAAGgw2Mzc0MjMxODM4MDUiDOpzxacJXDylGjNNvircAyYpVfC8Zr%2FXh5HuagyNNZEHOC0tA26V5tVtLqbj5OfEVm%2Fd9ob9kC7CVh%2BZXakNxub%2Fe0sXeJh62dA6dK3oI62BYWbz7mb%2B%2B6FVGzCL28s2Hiq84HyyAwSStk1vPNiEFv68LVzARgkBER2W1RzFllLh1J%2FHAAf%2Frw0GiqnE%2Fgfb2zitkh0FTkWGfR%2BleMNr9QDcVjufzSLignZAirHgjZcppiAciucUHEqDlej9to8Y82%2BGpsxkcRqJ1P3ZDxvOeVt6OtPdM5eo89b%2BhckAtkBrchZgWiRlcdHPR%2BAguYlyGz3o0X9LpoMRkvggTuPahAC1PXpYhoPQtJSr0Eowqi5%2B2RQbx5JKMXLbXdmTr6nbemRen49NwZu0LQibOwcKnCk3g3UUabq%2BR9M642UQb9SS2bqyEKQ%2B8daU66hsdFiiyaku%2Bz2fDjcOA%2B2l1yAwFX6DrX9iELUKYF9eTNG%2BKkWvhQ1Y3%2BRzg3A34XKEvh6M0NKufWdMxwHp%2B8OxhiMLOW5%2Bx7dbseq88QcC7lSp%2BzKfm9M0i0%2FS0j7zKRwmom%2B02adAg%2FNEWR2x9Po2Mw%2BEgsLVG71Y5d6VSayNTV%2FTEPyEidm%2BgIBKQaR8YX60Q4EhUQOZ9HvdC0jxDB8QMJO8v9AGOqUBcniBDSl0Pt2D8FnHN5cBVP8eDVoP3d5Jw8xR%2FqVSPyu%2FnUXiQGsk5S2OmWqOHjsAyCJoRIC2qh9JHaFhHhw4Tgg6V6eTNMqVAJJWF0SuMXYiuRzpRlMdbqPtBxkA3HLHNil2F6dBNnAlaV71itU%2B7bkYuQScmmfoZrYL39gzJ7A03aHvPA%2Fd2ZOXFxXhhjwz8YoGCKdfY2TLcADxlwZ5dVbUAHE8&X-Amz-Signature=071b4708f99bfacfab7de1c0602a25c00b136a254c546bb5b640b93c54b2137e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject

아래는 Raspiberry에서 구동한 결과이다.

연구 결론

Coral-TPU는 모델의 Operator를 전부 TPU에서 작업을 처리하려 하기 때문에 지원하지 않는 Operator가 있으면 해당 모델은 TPU를 사용할 수조차 없다.

다양한 모델을 사용해야 한다면 지원하지 않는 Operator들은 CPU가 처리하고 지원하는 Operator는 TPU가 처리하면 좋을 것 같다.