3장: TensorFlow SavedModel

TensorFlow 생태계를 위한 프로덕션 준비 모델 형식

弘益人間 · 널리 인간을 이롭게 하라

3.1 SavedModel 소개

TensorFlow SavedModel은 TensorFlow 모델을 위한 범용 직렬화 형식입니다. TensorFlow 1.0에서 도입되고 TensorFlow 2.0에서 크게 개선된 SavedModel은 계산 그래프, 변수, 에셋 및 시그니처를 포함하는 완전하고 독립적인 패키지로 프로덕션 배포를 위해 설계되었습니다.

체크포인트 파일이나 동결된 그래프와 같은 이전 형식과 달리, SavedModel은 모델을 서빙하거나 미세 조정하거나 다른 형식으로 변환하는 데 필요한 모든 것을 포함합니다. TensorFlow Serving, TensorFlow Lite, TensorFlow.js 및 클라우드 ML 플랫폼에 권장되는 형식입니다.

SavedModel의 장점

3.2 SavedModel 디렉토리 구조

SavedModel은 다음 구조를 가진 디렉토리로 저장됩니다:

saved_model/
├── assets/                     # 추가 파일 (어휘, 등)
│   └── vocab.txt
├── variables/                  # 모델 가중치 및 변수
│   ├── variables.data-00000-of-00001
│   └── variables.index
└── saved_model.pb             # 직렬화된 계산 그래프 및 메타데이터

# 여러 버전이 있는 경우:
model_repository/
├── my_model/
│   ├── 1/                     # 버전 1
│   │   ├── assets/
│   │   ├── variables/
│   │   └── saved_model.pb
│   └── 2/                     # 버전 2
│       ├── assets/
│       ├── variables/
│       └── saved_model.pb

파일 설명

3.3 SavedModel 생성하기

Keras 모델 저장

import tensorflow as tf
from tensorflow import keras

# 모델 생성 및 훈련
model = keras.Sequential([
    keras.layers.Dense(128, activation='relu', input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10, activation='softmax')
])

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

# 모델 훈련
model.fit(train_data, train_labels, epochs=5)

# SavedModel로 저장
model.save('my_model')  # SavedModel 디렉토리 생성

# 또는 명시적으로 SavedModel 형식 사용
model.save('my_model', save_format='tf')

사용자 정의 모델 저장

class CustomModel(tf.Module):
    def __init__(self):
        super().__init__()
        self.v = tf.Variable(1.0)
        self.w = tf.Variable(2.0)

    @tf.function(input_signature=[
        tf.TensorSpec(shape=[None], dtype=tf.float32)
    ])
    def __call__(self, x):
        return self.v * x + self.w

# 생성 및 저장
model = CustomModel()
tf.saved_model.save(model, 'custom_model')

# 명시적 시그니처와 함께
@tf.function(input_signature=[
    tf.TensorSpec(shape=[None, 224, 224, 3], dtype=tf.float32)
])
def serving_fn(images):
    return model(images)

tf.saved_model.save(
    model,
    'custom_model',
    signatures={'serving_default': serving_fn}
)

3.4 시그니처 정의

시그니처는 서빙 및 추론을 위한 입출력 인터페이스를 정의합니다:

여러 시그니처

class MultiSignatureModel(tf.Module):
    def __init__(self):
        super().__init__()
        self.model = create_model()

    @tf.function
    def predict(self, images):
        return self.model(images)

    @tf.function
    def preprocess_and_predict(self, raw_images):
        processed = tf.image.resize(raw_images, [224, 224])
        processed = processed / 255.0
        return self.model(processed)

# 여러 시그니처로 저장
model = MultiSignatureModel()

signatures = {
    'serving_default': model.predict.get_concrete_function(
        tf.TensorSpec([None, 224, 224, 3], tf.float32)
    ),
    'preprocess': model.preprocess_and_predict.get_concrete_function(
        tf.TensorSpec([None, None, None, 3], tf.float32)
    )
}

tf.saved_model.save(
    model,
    'multi_signature_model',
    signatures=signatures
)

3.5 SavedModel 로드 및 사용

Python에서 로드

# 방법 1: tf.saved_model.load (더 많은 제어)
loaded = tf.saved_model.load('my_model')
predictions = loaded(test_data)

# 방법 2: keras.models.load_model (Keras 모델용)
model = keras.models.load_model('my_model')
predictions = model.predict(test_data)

# 방법 3: 시그니처 사용
loaded = tf.saved_model.load('my_model')
infer = loaded.signatures['serving_default']
output = infer(input_tensor=test_data)['output_0']

C++에서 로드

// TensorFlow Serving용 C++ API
#include "tensorflow/cc/saved_model/loader.h"

tensorflow::SavedModelBundle bundle;
tensorflow::SessionOptions session_options;
tensorflow::RunOptions run_options;

// 모델 로드
tensorflow::Status status = tensorflow::LoadSavedModel(
    session_options,
    run_options,
    "/path/to/saved_model",
    {"serve"},  // 태그
    &bundle
);

// 추론 실행
std::vector outputs;
status = bundle.session->Run(
    {{"input", input_tensor}},
    {"output"},
    {},
    &outputs
);

3.6 TensorFlow Serving

서빙 구성

# 모델 구성 파일 (models.config)
model_config_list {
  config {
    name: 'my_model'
    base_path: '/models/my_model'
    model_platform: 'tensorflow'
    model_version_policy {
      specific {
        versions: 1
        versions: 2
      }
    }
  }
}

# Docker로 TensorFlow Serving 시작
docker run -p 8501:8501 \
  --mount type=bind,source=/path/to/models,target=/models \
  -e MODEL_NAME=my_model \
  -t tensorflow/serving

REST API 클라이언트

import requests
import json
import numpy as np

# 입력 데이터 준비
data = {
    "signature_name": "serving_default",
    "instances": test_images.tolist()
}

# 요청 전송
response = requests.post(
    'http://localhost:8501/v1/models/my_model:predict',
    data=json.dumps(data)
)

# 응답 파싱
predictions = response.json()['predictions']
print(f"예측 결과: {predictions}")

3.7 SavedModel을 다른 형식으로 변환

SavedModel에서 TFLite로

import tensorflow as tf

# SavedModel 로드
converter = tf.lite.TFLiteConverter.from_saved_model('my_model')

# 최적화 구성
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]

# 변환
tflite_model = converter.convert()

# 저장
with open('model.tflite', 'wb') as f:
    f.write(tflite_model)

print(f"TFLite 모델 크기: {len(tflite_model) / 1024:.2f} KB")

SavedModel에서 TensorFlow.js로

# tensorflowjs 변환기 설치
pip install tensorflowjs

# SavedModel을 TF.js 형식으로 변환
tensorflowjs_converter \
    --input_format=tf_saved_model \
    --output_format=tfjs_graph_model \
    --signature_name=serving_default \
    --saved_model_tags=serve \
    my_model \
    tfjs_model/

# JavaScript에서 로드할 수 있음:
# const model = await tf.loadGraphModel('model.json');

3.8 고급 SavedModel 기능

모델 에셋

import tensorflow as tf
import shutil

class ModelWithAssets(tf.Module):
    def __init__(self):
        super().__init__()
        # 에셋에서 어휘 로드
        self.vocab_file = tf.saved_model.Asset('vocab.txt')

    @tf.function
    def tokenize(self, text):
        # 어휘 파일 사용
        vocab_table = tf.lookup.StaticVocabularyTable(
            tf.lookup.TextFileInitializer(
                self.vocab_file.asset_path,
                key_dtype=tf.string,
                key_index=tf.lookup.TextFileIndex.WHOLE_LINE,
                value_dtype=tf.int64,
                value_index=tf.lookup.TextFileIndex.LINE_NUMBER
            ),
            num_oov_buckets=1
        )
        return vocab_table.lookup(text)

# 모델 저장 (vocab.txt가 assets/로 복사됨)
model = ModelWithAssets()
tf.saved_model.save(model, 'model_with_assets')

3.9 SavedModel 최적화

양자화

# 후처리 양자화
converter = tf.lite.TFLiteConverter.from_saved_model('my_model')
converter.optimizations = [tf.lite.Optimize.DEFAULT]

# 완전 정수 양자화
def representative_dataset():
    for _ in range(100):
        yield [np.random.randn(1, 224, 224, 3).astype(np.float32)]

converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8

quantized_model = converter.convert()

3.10 모범 사례

구체적 함수 사용

# 입력 사양을 명시적으로 정의
@tf.function(input_signature=[
    tf.TensorSpec(shape=[None, 224, 224, 3], dtype=tf.float32, name='images')
])
def inference_fn(images):
    # 전처리
    images = tf.image.resize(images, [224, 224])
    images = (images - 127.5) / 127.5

    # 모델 추론
    return model(images, training=False)

# 구체적 함수로 저장
tf.saved_model.save(
    model,
    'my_model',
    signatures={'serving_default': inference_fn}
)

배포 전 테스트

# 검증 스크립트
def validate_savedmodel(model_path, test_data):
    # 모델 로드
    model = tf.saved_model.load(model_path)
    infer = model.signatures['serving_default']

    # 입력 테스트
    for i, (input_data, expected_output) in enumerate(test_data):
        output = infer(**input_data)

        # 출력 형상 확인
        assert output.shape == expected_output.shape

        # 정확도 확인
        diff = tf.abs(output - expected_output)
        max_diff = tf.reduce_max(diff)
        assert max_diff < 0.001, f"테스트 {i} 실패: max_diff={max_diff}"

    print("✓ 모든 검증 테스트 통과")

# 검증 실행
validate_savedmodel('my_model', test_dataset)

요약

이 장에서는 TensorFlow SavedModel 형식을 상세히 다루었습니다. 주요 내용:

복습 문제

  1. SavedModel 디렉토리에 포함된 파일들과 각각의 내용은 무엇인가요?
  2. SavedModel의 시그니처란 무엇이며 왜 중요한가요?
  3. Python에서 SavedModel을 로드하는 세 가지 방법을 비교하세요.
  4. Docker로 TensorFlow Serving을 사용하여 SavedModel을 서빙하는 방법은?
  5. SavedModel을 TensorFlow Lite 형식으로 변환하는 코드를 작성하세요.
  6. 모델 에셋이란 무엇이며 언제 사용하나요?
  7. TensorFlow Serving에서 웜업 요청의 목적을 설명하세요.
  8. SavedModel로 모델 버전 관리를 구현하는 방법은?
  9. SavedModel의 양자화 프로세스를 설명하세요.
  10. SavedModel을 배포하기 전에 수행해야 할 검증 단계는 무엇인가요?
弘益人間 (홍익인간) · 널리 인간을 이롭게 하라