08. Edge AI - TensorFlow Lite

08. Edge AI - TensorFlow Lite

ํ•™์Šต ๋ชฉํ‘œ

  • Edge AI ๊ฐœ๋…๊ณผ ์žฅ์  ์ดํ•ด
  • TensorFlow Lite ๊ฐœ์š” ํŒŒ์•…
  • ๋ชจ๋ธ ๋ณ€ํ™˜ (.tflite) ๋ฐฉ๋ฒ• ํ•™์Šต
  • ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์—์„œ ์ถ”๋ก  ์ˆ˜ํ–‰
  • ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์˜ˆ์ œ ๊ตฌํ˜„

1. Edge AI ๊ฐœ๋…

1.1 Edge AI๋ž€?

Edge AI๋Š” ํด๋ผ์šฐ๋“œ๊ฐ€ ์•„๋‹Œ ์—ฃ์ง€ ๋””๋ฐ”์ด์Šค(๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด, ์Šค๋งˆํŠธํฐ ๋“ฑ)์—์„œ ์ง์ ‘ AI ์ถ”๋ก ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              ํด๋ผ์šฐ๋“œ AI vs Edge AI                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                              โ”‚
โ”‚   ํด๋ผ์šฐ๋“œ AI                        Edge AI                 โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”             โ”‚
โ”‚   โ”‚  ์„ผ์„œ   โ”‚                       โ”‚  ์„ผ์„œ   โ”‚             โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜                       โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜             โ”‚
โ”‚        โ”‚ ๋ฐ์ดํ„ฐ                          โ”‚ ๋ฐ์ดํ„ฐ            โ”‚
โ”‚        โ–ผ                                 โ–ผ                  โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”             โ”‚
โ”‚   โ”‚ ๋„คํŠธ์›Œํฌโ”‚                       โ”‚   Edge  โ”‚             โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜                       โ”‚   AI    โ”‚             โ”‚
โ”‚        โ”‚                            โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜             โ”‚
โ”‚        โ–ผ                                 โ”‚ ๊ฒฐ๊ณผ              โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                            โ–ผ                  โ”‚
โ”‚   โ”‚ ํด๋ผ์šฐ๋“œโ”‚                       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”             โ”‚
โ”‚   โ”‚   AI    โ”‚                       โ”‚  Action โ”‚             โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜             โ”‚
โ”‚        โ”‚                                                    โ”‚
โ”‚        โ–ผ                            ์žฅ์ :                    โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                       โ€ข ์ €์ง€์—ฐ (< 50ms)       โ”‚
โ”‚   โ”‚  Action โ”‚                       โ€ข ์˜คํ”„๋ผ์ธ ๋™์ž‘         โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                       โ€ข ํ”„๋ผ์ด๋ฒ„์‹œ            โ”‚
โ”‚                                     โ€ข ๋น„์šฉ ์ ˆ๊ฐ             โ”‚
โ”‚   ๋‹จ์ :                                                     โ”‚
โ”‚   โ€ข ์ง€์—ฐ (100ms+)                                           โ”‚
โ”‚   โ€ข ๋„คํŠธ์›Œํฌ ์˜์กด                                           โ”‚
โ”‚   โ€ข ๋ฐ์ดํ„ฐ ์ „์†ก ๋น„์šฉ                                        โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

1.2 Edge AI ํ™œ์šฉ ์‚ฌ๋ก€

๋ถ„์•ผ ํ™œ์šฉ ์˜ˆ์‹œ
์Šค๋งˆํŠธํ™ˆ ์–ผ๊ตด ์ธ์‹ ๋„์–ด๋ฝ, ์Œ์„ฑ ์ธ์‹
์‚ฐ์—… ๋ถˆ๋Ÿ‰ํ’ˆ ๊ฒ€์ถœ, ์˜ˆ์ธก ์ •๋น„
ํ—ฌ์Šค์ผ€์–ด ์›จ์–ด๋Ÿฌ๋ธ” ๊ฑด๊ฐ• ๋ชจ๋‹ˆํ„ฐ๋ง
๋†์—… ์ž‘๋ฌผ ์งˆ๋ณ‘ ๊ฐ์ง€, ํ•ด์ถฉ ์‹๋ณ„
์ž๋™์ฐจ ADAS, ๋ณดํ–‰์ž ๊ฐ์ง€

1.3 Edge AI ํ”„๋ ˆ์ž„์›Œํฌ ๋น„๊ต

ํ”„๋ ˆ์ž„์›Œํฌ ๊ฐœ๋ฐœ์‚ฌ ํŠน์ง• ํ•˜๋“œ์›จ์–ด ์ง€์›
TensorFlow Lite Google ๋ฒ”์šฉ, ์ƒํƒœ๊ณ„ ํ’๋ถ€ CPU, GPU, Edge TPU
ONNX Runtime Microsoft ๋‹ค์–‘ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ ํ˜ธํ™˜ CPU, GPU
OpenVINO Intel Intel ์ตœ์ ํ™” Intel CPU/GPU
TensorRT NVIDIA NVIDIA GPU ์ตœ์ ํ™” NVIDIA GPU

2. TensorFlow Lite ๊ฐœ์š”

2.1 TFLite ํŠน์ง•

# TensorFlow Lite ํŠน์ง•
tflite_features = {
    "๊ฒฝ๋Ÿ‰ํ™”": "๋ชจ๋ธ ํฌ๊ธฐ ๊ฐ์†Œ (์–‘์žํ™”๋กœ 1/4)",
    "์ตœ์ ํ™”": "๋ชจ๋ฐ”์ผ/์ž„๋ฒ ๋””๋“œ ์ถ”๋ก  ์ตœ์ ํ™”",
    "ํ•˜๋“œ์›จ์–ด ๊ฐ€์†": "GPU, Edge TPU, DSP ์ง€์›",
    "ํฌ๋กœ์Šค ํ”Œ๋žซํผ": "Android, iOS, Linux, MCU",
    "์—ฐ์‚ฐ์ž": "TF ์—ฐ์‚ฐ์ž ์„œ๋ธŒ์…‹ ์ง€์›"
}

2.2 ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์—์„œ ์„ค์น˜

# ๋ฐฉ๋ฒ• 1: tflite-runtime (๊ถŒ์žฅ, ๊ฒฝ๋Ÿ‰)
pip install tflite-runtime

# ๋ฐฉ๋ฒ• 2: ์ „์ฒด TensorFlow (๋ฌด๊ฑฐ์›€)
# pip install tensorflow

# ์ถ”๊ฐ€ ํŒจํ‚ค์ง€
pip install numpy pillow

2.3 TFLite ์›Œํฌํ”Œ๋กœ์šฐ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                TensorFlow Lite ์›Œํฌํ”Œ๋กœ์šฐ                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                              โ”‚
โ”‚   1. ๋ชจ๋ธ ํ›ˆ๋ จ (PC/ํด๋ผ์šฐ๋“œ)                                โ”‚
โ”‚      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                 โ”‚
โ”‚      โ”‚  TensorFlow/Keras  โ”‚                                 โ”‚
โ”‚      โ”‚     ๋ชจ๋ธ (.h5)     โ”‚                                 โ”‚
โ”‚      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                 โ”‚
โ”‚                โ”‚                                             โ”‚
โ”‚   2. ๋ชจ๋ธ ๋ณ€ํ™˜                                               โ”‚
โ”‚      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                 โ”‚
โ”‚      โ”‚   TFLite Converter โ”‚                                 โ”‚
โ”‚      โ”‚   (์–‘์žํ™” ์˜ต์…˜)    โ”‚                                 โ”‚
โ”‚      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                 โ”‚
โ”‚                โ”‚                                             โ”‚
โ”‚   3. ์ตœ์ ํ™”๋œ ๋ชจ๋ธ                                          โ”‚
โ”‚      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                 โ”‚
โ”‚      โ”‚   model.tflite     โ”‚                                 โ”‚
โ”‚      โ”‚   (๊ฒฝ๋Ÿ‰ํ™”๋œ ๋ชจ๋ธ)   โ”‚                                 โ”‚
โ”‚      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                 โ”‚
โ”‚                โ”‚                                             โ”‚
โ”‚   4. ์—ฃ์ง€ ๋ฐฐํฌ                                               โ”‚
โ”‚      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                 โ”‚
โ”‚      โ”‚  TFLite Runtime    โ”‚                                 โ”‚
โ”‚      โ”‚  (๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด)    โ”‚                                 โ”‚
โ”‚      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                 โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

3. ๋ชจ๋ธ ๋ณ€ํ™˜ (.tflite)

3.1 ๊ธฐ๋ณธ ๋ณ€ํ™˜

#!/usr/bin/env python3
"""TensorFlow ๋ชจ๋ธ์„ TFLite๋กœ ๋ณ€ํ™˜"""

import tensorflow as tf

# ๊ธฐ์กด Keras ๋ชจ๋ธ ๋กœ๋“œ
model = tf.keras.models.load_model('my_model.h5')

# ๋ณ€ํ™˜๊ธฐ ์ƒ์„ฑ
converter = tf.lite.TFLiteConverter.from_keras_model(model)

# ๋ณ€ํ™˜
tflite_model = converter.convert()

# ์ €์žฅ
with open('model.tflite', 'wb') as f:
    f.write(tflite_model)

print(f"๋ชจ๋ธ ํฌ๊ธฐ: {len(tflite_model) / 1024:.2f} KB")

3.2 ์–‘์žํ™” (Quantization)

#!/usr/bin/env python3
"""์–‘์žํ™”๋ฅผ ํ†ตํ•œ ๋ชจ๋ธ ์ตœ์ ํ™”"""

import tensorflow as tf
import numpy as np

def load_model():
    return tf.keras.models.load_model('my_model.h5')

def convert_to_tflite(model, quantization='none'):
    """
    ์–‘์žํ™” ์˜ต์…˜:
    - 'none': ๊ธฐ๋ณธ (float32)
    - 'dynamic': ๋™์  ๋ฒ”์œ„ ์–‘์žํ™” (๊ฐ€์ค‘์น˜๋งŒ)
    - 'float16': Float16 ์–‘์žํ™”
    - 'int8': ์ „์ฒด ์ •์ˆ˜ ์–‘์žํ™” (๋Œ€ํ‘œ ๋ฐ์ดํ„ฐ์…‹ ํ•„์š”)
    """
    converter = tf.lite.TFLiteConverter.from_keras_model(model)

    if quantization == 'dynamic':
        # ๋™์  ๋ฒ”์œ„ ์–‘์žํ™” (๊ฐ€์žฅ ์‰ฌ์›€)
        converter.optimizations = [tf.lite.Optimize.DEFAULT]

    elif quantization == 'float16':
        # Float16 ์–‘์žํ™”
        converter.optimizations = [tf.lite.Optimize.DEFAULT]
        converter.target_spec.supported_types = [tf.float16]

    elif quantization == 'int8':
        # ์ „์ฒด ์ •์ˆ˜ ์–‘์žํ™” (์ตœ๋Œ€ ์ตœ์ ํ™”)
        converter.optimizations = [tf.lite.Optimize.DEFAULT]
        converter.target_spec.supported_ops = [
            tf.lite.OpsSet.TFLITE_BUILTINS_INT8
        ]
        converter.inference_input_type = tf.int8
        converter.inference_output_type = tf.int8

        # ๋Œ€ํ‘œ ๋ฐ์ดํ„ฐ์…‹ ์ œ๊ณต ํ•„์š”
        def representative_dataset():
            for _ in range(100):
                yield [np.random.rand(1, 224, 224, 3).astype(np.float32)]

        converter.representative_dataset = representative_dataset

    tflite_model = converter.convert()
    return tflite_model

# ๋ณ€ํ™˜ ๋ฐ ํฌ๊ธฐ ๋น„๊ต
model = load_model()

for quant in ['none', 'dynamic', 'float16']:
    tflite_model = convert_to_tflite(model, quant)
    size_kb = len(tflite_model) / 1024

    with open(f'model_{quant}.tflite', 'wb') as f:
        f.write(tflite_model)

    print(f"{quant}: {size_kb:.2f} KB")

3.3 ์‚ฌ์ „ ํ›ˆ๋ จ ๋ชจ๋ธ ๋ณ€ํ™˜

#!/usr/bin/env python3
"""MobileNet์„ TFLite๋กœ ๋ณ€ํ™˜"""

import tensorflow as tf

# MobileNetV2 ๋กœ๋“œ
model = tf.keras.applications.MobileNetV2(
    input_shape=(224, 224, 3),
    weights='imagenet',
    include_top=True
)

# ๋ณ€ํ™˜
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]

tflite_model = converter.convert()

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

print(f"๋ณ€ํ™˜ ์™„๋ฃŒ: {len(tflite_model) / (1024*1024):.2f} MB")

4. ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์—์„œ ์ถ”๋ก 

4.1 TFLite ์ธํ„ฐํ”„๋ฆฌํ„ฐ ๊ธฐ๋ณธ

#!/usr/bin/env python3
"""TFLite ์ถ”๋ก  ๊ธฐ๋ณธ"""

import numpy as np

# tflite-runtime ์‚ฌ์šฉ (๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด)
try:
    from tflite_runtime.interpreter import Interpreter
except ImportError:
    from tensorflow.lite.python.interpreter import Interpreter

class TFLiteModel:
    """TFLite ๋ชจ๋ธ ๋ž˜ํผ"""

    def __init__(self, model_path: str):
        self.interpreter = Interpreter(model_path=model_path)
        self.interpreter.allocate_tensors()

        # ์ž…์ถœ๋ ฅ ์ •๋ณด
        self.input_details = self.interpreter.get_input_details()
        self.output_details = self.interpreter.get_output_details()

        # ์ž…๋ ฅ ํ˜•ํƒœ
        self.input_shape = self.input_details[0]['shape']
        self.input_dtype = self.input_details[0]['dtype']

    def get_input_shape(self):
        """์ž…๋ ฅ ํ˜•ํƒœ ๋ฐ˜ํ™˜"""
        return self.input_shape

    def predict(self, input_data: np.ndarray) -> np.ndarray:
        """์ถ”๋ก  ์ˆ˜ํ–‰"""
        # ์ž…๋ ฅ ์„ค์ •
        self.interpreter.set_tensor(
            self.input_details[0]['index'],
            input_data.astype(self.input_dtype)
        )

        # ์ถ”๋ก 
        self.interpreter.invoke()

        # ์ถœ๋ ฅ ๊ฐ€์ ธ์˜ค๊ธฐ
        output = self.interpreter.get_tensor(
            self.output_details[0]['index']
        )

        return output

# ์‚ฌ์šฉ ์˜ˆ
if __name__ == "__main__":
    model = TFLiteModel("model.tflite")
    print(f"์ž…๋ ฅ ํ˜•ํƒœ: {model.get_input_shape()}")

    # ๋”๋ฏธ ์ž…๋ ฅ
    input_data = np.random.rand(*model.get_input_shape()).astype(np.float32)

    output = model.predict(input_data)
    print(f"์ถœ๋ ฅ ํ˜•ํƒœ: {output.shape}")

4.2 ์„ฑ๋Šฅ ์ธก์ •

#!/usr/bin/env python3
"""TFLite ์ถ”๋ก  ์„ฑ๋Šฅ ์ธก์ •"""

import numpy as np
import time

try:
    from tflite_runtime.interpreter import Interpreter
except ImportError:
    from tensorflow.lite.python.interpreter import Interpreter

def benchmark_model(model_path: str, num_runs: int = 100):
    """๋ชจ๋ธ ์„ฑ๋Šฅ ๋ฒค์น˜๋งˆํฌ"""
    interpreter = Interpreter(model_path=model_path)
    interpreter.allocate_tensors()

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

    input_shape = input_details[0]['shape']
    input_dtype = input_details[0]['dtype']

    # ์›Œ๋ฐ์—…
    dummy_input = np.random.rand(*input_shape).astype(input_dtype)
    interpreter.set_tensor(input_details[0]['index'], dummy_input)
    interpreter.invoke()

    # ๋ฒค์น˜๋งˆํฌ
    times = []
    for _ in range(num_runs):
        start = time.perf_counter()

        interpreter.set_tensor(input_details[0]['index'], dummy_input)
        interpreter.invoke()
        _ = interpreter.get_tensor(output_details[0]['index'])

        end = time.perf_counter()
        times.append((end - start) * 1000)  # ms

    avg_time = np.mean(times)
    std_time = np.std(times)
    fps = 1000 / avg_time

    print(f"=== {model_path} ===")
    print(f"ํ‰๊ท  ์ถ”๋ก  ์‹œ๊ฐ„: {avg_time:.2f} ms (+/- {std_time:.2f})")
    print(f"FPS: {fps:.1f}")
    print(f"์ž…๋ ฅ ํ˜•ํƒœ: {input_shape}")

    return avg_time

# ์—ฌ๋Ÿฌ ๋ชจ๋ธ ๋น„๊ต
if __name__ == "__main__":
    models = [
        "model_none.tflite",
        "model_dynamic.tflite",
        "model_float16.tflite"
    ]

    for model in models:
        try:
            benchmark_model(model)
            print()
        except Exception as e:
            print(f"{model}: ์˜ค๋ฅ˜ - {e}")

5. ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์˜ˆ์ œ

5.1 ImageNet ๋ถ„๋ฅ˜

#!/usr/bin/env python3
"""TFLite ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ (MobileNet)"""

import numpy as np
from PIL import Image
import time

try:
    from tflite_runtime.interpreter import Interpreter
except ImportError:
    from tensorflow.lite.python.interpreter import Interpreter

class ImageClassifier:
    """TFLite ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜๊ธฐ"""

    def __init__(self, model_path: str, labels_path: str = None):
        self.interpreter = Interpreter(model_path=model_path)
        self.interpreter.allocate_tensors()

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

        # ์ž…๋ ฅ ํฌ๊ธฐ ํ™•์ธ
        self.input_height = self.input_details[0]['shape'][1]
        self.input_width = self.input_details[0]['shape'][2]

        # ๋ผ๋ฒจ ๋กœ๋“œ
        self.labels = []
        if labels_path:
            with open(labels_path, 'r') as f:
                self.labels = [line.strip() for line in f.readlines()]

    def preprocess(self, image: Image.Image) -> np.ndarray:
        """์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ"""
        # ๋ฆฌ์‚ฌ์ด์ฆˆ
        image = image.resize((self.input_width, self.input_height))

        # NumPy ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
        input_data = np.array(image, dtype=np.float32)

        # ์ •๊ทœํ™” (-1 ~ 1)
        input_data = (input_data - 127.5) / 127.5

        # ๋ฐฐ์น˜ ์ฐจ์› ์ถ”๊ฐ€
        input_data = np.expand_dims(input_data, axis=0)

        return input_data

    def classify(self, image_path: str, top_k: int = 5) -> list:
        """์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜"""
        # ์ด๋ฏธ์ง€ ๋กœ๋“œ ๋ฐ ์ „์ฒ˜๋ฆฌ
        image = Image.open(image_path).convert('RGB')
        input_data = self.preprocess(image)

        # ์ถ”๋ก 
        start = time.perf_counter()

        self.interpreter.set_tensor(
            self.input_details[0]['index'],
            input_data
        )
        self.interpreter.invoke()

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

        inference_time = (time.perf_counter() - start) * 1000

        # Top-K ๊ฒฐ๊ณผ
        top_indices = output.argsort()[-top_k:][::-1]

        results = []
        for idx in top_indices:
            label = self.labels[idx] if idx < len(self.labels) else f"class_{idx}"
            score = float(output[idx])
            results.append({
                "class_id": int(idx),
                "label": label,
                "score": score
            })

        return {
            "results": results,
            "inference_time_ms": inference_time
        }

# ์‚ฌ์šฉ ์˜ˆ
if __name__ == "__main__":
    classifier = ImageClassifier(
        model_path="mobilenet_v2.tflite",
        labels_path="imagenet_labels.txt"
    )

    result = classifier.classify("test_image.jpg")

    print(f"์ถ”๋ก  ์‹œ๊ฐ„: {result['inference_time_ms']:.2f} ms")
    print("\n๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ:")
    for r in result['results']:
        print(f"  {r['label']}: {r['score']:.4f}")

5.2 ์‹ค์‹œ๊ฐ„ ์นด๋ฉ”๋ผ ๋ถ„๋ฅ˜

#!/usr/bin/env python3
"""Pi Camera๋ฅผ ์ด์šฉํ•œ ์‹ค์‹œ๊ฐ„ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜"""

import numpy as np
from PIL import Image
import time
import io

try:
    from tflite_runtime.interpreter import Interpreter
except ImportError:
    from tensorflow.lite.python.interpreter import Interpreter

try:
    from picamera2 import Picamera2
    HAS_CAMERA = True
except ImportError:
    HAS_CAMERA = False
    print("picamera2 ์—†์Œ: ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ชจ๋“œ")

class RealtimeClassifier:
    """์‹ค์‹œ๊ฐ„ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜๊ธฐ"""

    def __init__(self, model_path: str, labels_path: str):
        # ๋ชจ๋ธ ๋กœ๋“œ
        self.interpreter = Interpreter(model_path=model_path)
        self.interpreter.allocate_tensors()

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

        self.input_height = self.input_details[0]['shape'][1]
        self.input_width = self.input_details[0]['shape'][2]

        # ๋ผ๋ฒจ
        with open(labels_path, 'r') as f:
            self.labels = [line.strip() for line in f.readlines()]

        # ์นด๋ฉ”๋ผ ์ดˆ๊ธฐํ™”
        if HAS_CAMERA:
            self.camera = Picamera2()
            config = self.camera.create_preview_configuration(
                main={"size": (640, 480), "format": "RGB888"}
            )
            self.camera.configure(config)

    def preprocess(self, frame: np.ndarray) -> np.ndarray:
        """ํ”„๋ ˆ์ž„ ์ „์ฒ˜๋ฆฌ"""
        image = Image.fromarray(frame)
        image = image.resize((self.input_width, self.input_height))

        input_data = np.array(image, dtype=np.float32)
        input_data = (input_data - 127.5) / 127.5
        input_data = np.expand_dims(input_data, axis=0)

        return input_data

    def classify_frame(self, frame: np.ndarray) -> dict:
        """๋‹จ์ผ ํ”„๋ ˆ์ž„ ๋ถ„๋ฅ˜"""
        input_data = self.preprocess(frame)

        self.interpreter.set_tensor(
            self.input_details[0]['index'],
            input_data
        )
        self.interpreter.invoke()

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

        top_idx = output.argmax()

        return {
            "label": self.labels[top_idx] if top_idx < len(self.labels) else "unknown",
            "score": float(output[top_idx])
        }

    def run(self, duration: float = 30):
        """์‹ค์‹œ๊ฐ„ ๋ถ„๋ฅ˜ ์‹คํ–‰"""
        if not HAS_CAMERA:
            print("์นด๋ฉ”๋ผ ์—†์Œ")
            return

        self.camera.start()
        print(f"์‹ค์‹œ๊ฐ„ ๋ถ„๋ฅ˜ ์‹œ์ž‘ ({duration}์ดˆ)")

        start_time = time.time()
        frame_count = 0

        try:
            while time.time() - start_time < duration:
                frame = self.camera.capture_array()
                result = self.classify_frame(frame)

                frame_count += 1
                print(f"\r[{frame_count}] {result['label']}: {result['score']:.2f}", end="")

        except KeyboardInterrupt:
            pass
        finally:
            self.camera.stop()
            elapsed = time.time() - start_time
            fps = frame_count / elapsed
            print(f"\n\nFPS: {fps:.1f}")

if __name__ == "__main__":
    classifier = RealtimeClassifier(
        model_path="mobilenet_v2.tflite",
        labels_path="imagenet_labels.txt"
    )
    classifier.run(duration=60)

5.3 IoT ํ†ตํ•ฉ ์˜ˆ์ œ

#!/usr/bin/env python3
"""TFLite + MQTT: ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ ๋ฐœํ–‰"""

import numpy as np
from PIL import Image
import json
import time
import paho.mqtt.client as mqtt

try:
    from tflite_runtime.interpreter import Interpreter
except ImportError:
    from tensorflow.lite.python.interpreter import Interpreter

class AIEdgeNode:
    """AI ์—ฃ์ง€ ๋…ธ๋“œ"""

    def __init__(self, model_path: str, labels_path: str,
                 mqtt_broker: str = "localhost"):
        # TFLite ๋ชจ๋ธ
        self.interpreter = Interpreter(model_path=model_path)
        self.interpreter.allocate_tensors()

        self.input_details = self.interpreter.get_input_details()
        self.output_details = self.interpreter.get_output_details()
        self.input_shape = self.input_details[0]['shape']

        with open(labels_path, 'r') as f:
            self.labels = [line.strip() for line in f.readlines()]

        # MQTT ํด๋ผ์ด์–ธํŠธ
        self.mqtt_client = mqtt.Client()
        self.mqtt_client.connect(mqtt_broker, 1883)
        self.mqtt_client.loop_start()

        self.node_id = "edge_ai_01"

    def classify_and_publish(self, image_path: str):
        """๋ถ„๋ฅ˜ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ MQTT๋กœ ๋ฐœํ–‰"""
        # ์ด๋ฏธ์ง€ ๋กœ๋“œ ๋ฐ ์ „์ฒ˜๋ฆฌ
        image = Image.open(image_path).convert('RGB')
        image = image.resize((self.input_shape[2], self.input_shape[1]))

        input_data = np.array(image, dtype=np.float32)
        input_data = (input_data - 127.5) / 127.5
        input_data = np.expand_dims(input_data, axis=0)

        # ์ถ”๋ก 
        start = time.perf_counter()
        self.interpreter.set_tensor(self.input_details[0]['index'], input_data)
        self.interpreter.invoke()
        output = self.interpreter.get_tensor(self.output_details[0]['index'])[0]
        inference_time = (time.perf_counter() - start) * 1000

        # ๊ฒฐ๊ณผ ์ƒ์„ฑ
        top_idx = output.argsort()[-3:][::-1]
        predictions = [
            {
                "label": self.labels[idx] if idx < len(self.labels) else "unknown",
                "score": float(output[idx])
            }
            for idx in top_idx
        ]

        result = {
            "node_id": self.node_id,
            "image": image_path,
            "predictions": predictions,
            "inference_time_ms": round(inference_time, 2),
            "timestamp": time.time()
        }

        # MQTT ๋ฐœํ–‰
        topic = f"edge/{self.node_id}/classification"
        self.mqtt_client.publish(topic, json.dumps(result))

        print(f"๋ฐœํ–‰: {topic}")
        print(f"  Top-1: {predictions[0]['label']} ({predictions[0]['score']:.2f})")

        return result

    def shutdown(self):
        """์ •๋ฆฌ"""
        self.mqtt_client.loop_stop()
        self.mqtt_client.disconnect()

# ์‚ฌ์šฉ ์˜ˆ
if __name__ == "__main__":
    node = AIEdgeNode(
        model_path="mobilenet_v2.tflite",
        labels_path="imagenet_labels.txt",
        mqtt_broker="localhost"
    )

    try:
        # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜
        node.classify_and_publish("test_image.jpg")
    finally:
        node.shutdown()

์—ฐ์Šต ๋ฌธ์ œ

๋ฌธ์ œ 1: ๋ชจ๋ธ ๋ณ€ํ™˜

  1. Keras ๋ชจ๋ธ์„ TFLite๋กœ ๋ณ€ํ™˜ํ•˜์„ธ์š”.
  2. ๋™์  ์–‘์žํ™”๋ฅผ ์ ์šฉํ•˜๊ณ  ํฌ๊ธฐ๋ฅผ ๋น„๊ตํ•˜์„ธ์š”.

๋ฌธ์ œ 2: ์„ฑ๋Šฅ ์ตœ์ ํ™”

  1. ๋™์ผ ๋ชจ๋ธ์˜ FP32, FP16, INT8 ๋ฒ„์ „ ์„ฑ๋Šฅ์„ ๋น„๊ตํ•˜์„ธ์š”.
  2. ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์—์„œ FPS๋ฅผ ์ธก์ •ํ•˜์„ธ์š”.

๋ฌธ์ œ 3: ์‹ค์‹œ๊ฐ„ ๋ถ„๋ฅ˜

  1. Pi Camera๋กœ ์‹ค์‹œ๊ฐ„ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.
  2. ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ MQTT๋กœ ๋ฐœํ–‰ํ•˜์„ธ์š”.

๋‹ค์Œ ๋‹จ๊ณ„


์ตœ์ข… ์—…๋ฐ์ดํŠธ: 2026-02-01

to navigate between lessons