Keras 모델 저장 및 로드

TensorFlow.org에서 보기 Google Colab에서 실행하기 GitHub에서 소스 보기 노트북 다운로드하기

소개

Keras 모델은 다중 구성 요소로 이루어집니다.

  • 모델에 포함된 레이어 및 레이어의 연결 방법을 지정하는 아키텍처 또는 구성
  • 가중치 값의 집합("모델의 상태")
  • 옵티마이저(모델을 컴파일하여 정의)
  • from tensorflow import keras model = keras.models.load_model('path/to/location')

Keras API를 사용하면 이러한 조각을 한 번에 디스크에 저장하거나 선택적으로 일부만 저장할 수 있습니다.

  • TensorFlow SavedModel 형식(또는 이전 Keras H5 형식)으로 모든 것을 단일 아카이브에 저장합니다. 이것이 표준 관행입니다.
  • 일반적으로 JSON 파일로 아키텍처 및 구성만 저장합니다.
  • 가중치 값만 저장합니다. 이것은 일반적으로 모델을 훈련할 때 사용됩니다.

이제 각각의 옵션을 살펴보겠습니다. 언제 사용하고 어떻게 사용해야 할까요?

모델을 저장하고 로드하는 방법

다음은 이 가이드를 읽는데 10초 밖에 없는 경우 알아야 할 사항입니다.

Keras 모델 저장하기

model = ...  # Get model (Sequential, Functional Model, or Model subclass)
model.save('path/to/location')

모델을 다시 로딩하기

from tensorflow import keras
model = keras.models.load_model('path/to/location')

이제 세부 사항을 확인해봅시다.

설치하기

import numpy as np
import tensorflow as tf
from tensorflow import keras
2022-12-14 22:23:43.914585: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory
2022-12-14 22:23:43.914680: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory
2022-12-14 22:23:43.914689: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.

전체 모델 저장 및 로딩

전체 모델을 단일 아티팩트로 저장할 수 있습니다. 다음을 포함합니다.

  • 모델의 아키텍처 및 구성
  • 훈련 중에 학습된 모델의 가중치 값
  • 모델의 컴파일 정보(compile()이 호출된 경우)
  • 존재하는 옵티마이저와 그 상태(훈련을 중단한 곳에서 다시 시작할 수 있게 해줌)

API

전체 모델을 디스크에 저장하는 데 사용할 수 있는 두 형식은 TensorFlow SavedModel 형식이전 Keras H5 형식입니다. 권장하는 형식은 SavedModel입니다. 이는 model.save()를 사용할 때의 기본값입니다.

다음을 통해 H5 형식으로 전환할 수 있습니다.

  • save_format='h5'save()로 전달합니다.
  • .h5 또는 .keras로 끝나는 파일명을 save()로 전달합니다.

SavedModel 형식

SavedModel은 모델 아키텍처, 가중치 및 호출 함수에서 추적된 Tensorflow 하위 그래프를 저장하는 보다 포괄적인 저장 형식입니다. Keras는 이를 통해 내장 레이어와 사용자 정의 객체를 모두 복원할 수 있습니다.

예제:

def get_model():
    # Create a simple model.
    inputs = keras.Input(shape=(32,))
    outputs = keras.layers.Dense(1)(inputs)
    model = keras.Model(inputs, outputs)
    model.compile(optimizer="adam", loss="mean_squared_error")
    return model


model = get_model()

# Train the model.
test_input = np.random.random((128, 32))
test_target = np.random.random((128, 1))
model.fit(test_input, test_target)

# Calling `save('my_model')` creates a SavedModel folder `my_model`.
model.save("my_model")

# It can be used to reconstruct the model identically.
reconstructed_model = keras.models.load_model("my_model")

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)

# The reconstructed model is already compiled and has retained the optimizer
# state, so training can resume:
reconstructed_model.fit(test_input, test_target)
4/4 [==============================] - 1s 4ms/step - loss: 0.2751
INFO:tensorflow:Assets written to: my_model/assets
4/4 [==============================] - 0s 2ms/step
4/4 [==============================] - 0s 2ms/step
4/4 [==============================] - 0s 3ms/step - loss: 0.2736
<keras.callbacks.History at 0x7f55683a0280>

SavedModel이 포함하는 것

model.save('my_model')을 호출하면 다음을 포함하는 my_model 폴더를 생성합니다.

ls my_model
assets  fingerprint.pb  keras_metadata.pb  saved_model.pb  variables

모델 아키텍처 및 훈련 구성(옵티마이저, 손실, 메트릭 등)은 saved_model.pb에 저장됩니다. 가중치는 variables/ 디렉터리에 저장됩니다.

SavedModel 형식에 대한 자세한 내용은 SavedModel 가이드(디스크의 SavedModel 형식)를 참조하세요.

SavedModel이 사용자 정의 객체를 처리하는 방법

모델과 모델의 레이어를 저장할 때 SavedModel 형식은 클래스명, 호출 함수, 손실 및 가중치(구현된 경우에는 구성도 포함)를 저장합니다. 호출 함수는 모델/레이어의 계산 그래프를 정의합니다.

모델/레이어 구성이 없는 경우 호출 함수는 훈련, 평가 및 추론에 사용될 수 있는 기존 모델과 같은 모델을 만드는 데 사용됩니다.

그럼에도 불구하고 사용자 정의 모델 또는 레이어 클래스를 작성할 때 항상 get_configfrom_config 메서드를 정의하는 것이 좋습니다. 이를 통해 필요한 경우 나중에 계산을 쉽게 업데이트할 수 있습니다. 자세한 내용은 사용자 정의 객체에 대한 섹션을 참조하세요.

예제:

class CustomModel(keras.Model):
    def __init__(self, hidden_units):
        super(CustomModel, self).__init__()
        self.hidden_units = hidden_units
        self.dense_layers = [keras.layers.Dense(u) for u in hidden_units]

    def call(self, inputs):
        x = inputs
        for layer in self.dense_layers:
            x = layer(x)
        return x

    def get_config(self):
        return {"hidden_units": self.hidden_units}

    @classmethod
    def from_config(cls, config):
        return cls(**config)


model = CustomModel([16, 16, 10])
# Build the model by calling it
input_arr = tf.random.uniform((1, 5))
outputs = model(input_arr)
model.save("my_model")

# Option 1: Load with the custom_object argument.
loaded_1 = keras.models.load_model(
    "my_model", custom_objects={"CustomModel": CustomModel}
)

# Option 2: Load without the CustomModel class.

# Delete the custom-defined model class to ensure that the loader does not have
# access to it.
del CustomModel

loaded_2 = keras.models.load_model("my_model")
np.testing.assert_allclose(loaded_1(input_arr), outputs)
np.testing.assert_allclose(loaded_2(input_arr), outputs)

print("Original model:", model)
print("Model Loaded with custom objects:", loaded_1)
print("Model loaded without the custom object class:", loaded_2)
INFO:tensorflow:Assets written to: my_model/assets
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
Original model: <__main__.CustomModel object at 0x7f55682e3820>
Model Loaded with custom objects: <__main__.CustomModel object at 0x7f56e0e9a820>
Model loaded without the custom object class: <keras.saving.legacy.saved_model.load.CustomModel object at 0x7f56e0e31b50>

첫 번째로 로드한 모델은 config 및 CustomModel 클래스를 사용하여 로드됩니다. 두 번째 모델은 원래 모델처럼 작동하는 모델 클래스를 동적으로 생성하여 로드됩니다.

SavedModel 구성하기

TensoFlow 2.4의 새로운 기능 save_traces 인수가 model.save에 추가되어 SavedModel 함수 추적을 토글할 수 있습니다. 원래 클래스의 정의가 없어도 Keras가 사용자 정의 객체를 다시 로드할 수 있도록 함수가 저장되므로 save_traces=False일 때 모든 사용자 정의 객체는 정의된 get_config/from_config 메서드를 가져야 합니다. 로드할 때 사용자 정의 객체를 custom_objects 인수에 전달해야 합니다. save_traces=False는 SavedModel 이 사용하는 디스크 공간을 줄이고 시간을 절약합니다.

Keras H5 형식

또한 Keras는 모델의 아키텍처, 가중치 값, compile() 정보를 포함하는 단일 HDF5 파일 저장을 지원합니다. 이는 SavedModel의 경량 대안입니다.

예제:

model = get_model()

# Train the model.
test_input = np.random.random((128, 32))
test_target = np.random.random((128, 1))
model.fit(test_input, test_target)

# Calling `save('my_model.h5')` creates a h5 file `my_model.h5`.
model.save("my_h5_model.h5")

# It can be used to reconstruct the model identically.
reconstructed_model = keras.models.load_model("my_h5_model.h5")

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)

# The reconstructed model is already compiled and has retained the optimizer
# state, so training can resume:
reconstructed_model.fit(test_input, test_target)
4/4 [==============================] - 0s 3ms/step - loss: 0.5795
4/4 [==============================] - 0s 2ms/step
4/4 [==============================] - 0s 1ms/step
4/4 [==============================] - 0s 3ms/step - loss: 0.5160
<keras.callbacks.History at 0x7f56e0becfa0>

한계

SavedModel 형식과 비교하여 H5 파일에 포함되지 않은 두 가지가 있습니다.

  • model.add_loss()model.add_metric()을 통해 추가된 외부 손실 및 메트릭은 저장되지 않습니다(SavedModel과 다름). 모델에 이러한 솔실 및 메트릭이 있고 훈련을 다시 시작하려면 모델을 로드한 후 이러한 손실을 다시 추가해야 합니다. 이는 self.add_loss()self.add_metric()을 통해 레이어 내부에서 생성한 손실/메트릭에는 적용되지 않습니다. 이러한 손실 및 메트릭은 레이어가 로드되는 한 레이어의 call 메서드의 일부이기 때문에 계속 유지됩니다.
  • 사용자 정의 레이어와 같은 사용자 정의 객체의 계산 그래프는 저장 파일에 포함되지 않습니다. 로드 시 Keras는 모델을 다시 구성하기 위해 이러한 객체의 Python 클래스/함수에 액세스해야 합니다. 사용자 정의 객체를 참고하세요.

아키텍처 저장

모델의 구성(또는 아키텍처)은 모델에 포함된 레이어와 이러한 레이어의 연결 방법*을 지정합니다. 모델 구성이 있는 경우 가중치에 대해 새로 초기화된 상태로 컴파일 정보 없이 모델을 작성할 수 있습니다.

*이 기능은 서브 클래스 모델이 아닌 함수형 또는 Sequential API를 사용하여 정의된 모델에만 적용됩니다.

순차형 모델 또는 함수형 API 모델의 구성

이러한 유형의 모델은 레이어의 명시적 그래프입니다. 구성은 항상 구조화된 형식으로 제공됩니다.

API

get_config()from_config()

config = model.get_config()를 호출하면 모델의 구성을 포함하는 Python 사전을 반환합니다. 그런 다음 Sequential.from_config(config)(Sequential 모델의 경우) 또는 Model.from_config(config)(함수형 API 모델의 경우)를 통해 동일한 모델을 다시 구성할 수 있습니다.

동일한 워크플로가 직렬화할 수 있는 모든 레이어에서도 작동합니다.

레이어 예제:

layer = keras.layers.Dense(3, activation="relu")
layer_config = layer.get_config()
new_layer = keras.layers.Dense.from_config(layer_config)

Sequential 모델 예제:

model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])
config = model.get_config()
new_model = keras.Sequential.from_config(config)

Functional 모델 예제:

inputs = keras.Input((32,))
outputs = keras.layers.Dense(1)(inputs)
model = keras.Model(inputs, outputs)
config = model.get_config()
new_model = keras.Model.from_config(config)

to_json()tf.keras.models.model_from_json()

이것은 get_config / from_config와 비슷하지만, 모델을 JSON 문자열로 변환한 다음 기존 모델 클래스 없이 로드할 수 있습니다. 또한, 모델에만 해당하며 레이어용이 아닙니다.

예제:

model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])
json_config = model.to_json()
new_model = keras.models.model_from_json(json_config)

사용자 정의 객체

모델 및 레이어

서브 클래스 모델과 레이어의 아키텍처는 __init__call 메서드에 정의되어 있습니다. 그것들은 Python 바이트 코드로 간주하며 JSON 호환 구성으로 직렬화할 수 없습니다 -- 바이트 코드 직렬화를 시도할 수는 있지만(예: pickle을 통해) 완전히 불안전하므로 모델을 다른 시스템에 로드할 수 없습니다.

사용자 정의 레이어를 사용하는 모델 또는 서브 클래스 모델을 저장/로드하려면 get_config 및 선택적으로 from_config 메서드를 덮어써야 합니다. 또한 Keras가 인식할 수 있도록 사용자 정의 객체를 등록해야 합니다.

사용자 정의 함수

사용자 정의 함수(예: 활성화 손실 또는 초기화)에는 get_config 메서드가 필요하지 않습니다. 함수명은 사용자 정의 객체로 등록되어 있는 한 로드하기에 충분합니다.

TensorFlow 그래프만 로딩하기

Keras가 생성한 TensorFlow 그래프를 로드할 수 있습니다. 그렇게 하면 custom_objects를 제공할 필요가 없습니다. 다음과 같이 해볼 수 있습니다.

model.save("my_model")
tensorflow_graph = tf.saved_model.load("my_model")
x = np.random.uniform(size=(4, 32)).astype(np.float32)
predicted = tensorflow_graph(x).numpy()
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
INFO:tensorflow:Assets written to: my_model/assets

이 메서드에는 몇 가지 단점이 있습니다.

  • 추적 가능성을 위해 사용된 사용자 정의 객체에 항상 접근할 수 있어야 합니다. 다시 만들 수 없는 모델을 제품에 넣고 싶지 않을 것입니다.
  • tf.saved_model.load에 의해 반환된 객체는 Keras 모델이 아닙니다. 따라서 사용하기가 쉽지 않습니다. 예를 들면, .predict() 또는 .fit()에 접근할 수 없습니다.

사용을 권장하지는 않지만, 사용자 정의 객체의 코드를 잃어버렸거나 tf.keras.models.load_model() 모델을 로드하는 데 문제가 있는 경우와 같이 곤란한 상황에서는 도움이 될 수 있습니다.

tf.saved_model.load와 관련된 페이지에서 자세한 내용을 확인할 수 있습니다.

구성 메서드 정의하기

명세:

  • get_config는 Keras 아키텍처 및 모델 저장 API와 호환되도록 JSON 직렬화 가능 사전을 반환해야 합니다.
  • from_config(config)(classmethod)는 구성에서 생성된 새 레이어 또는 모델 객체를 반환해야 합니다. 기본 구현은 cls(**config)를 반환합니다.

예제:

class CustomLayer(keras.layers.Layer):
    def __init__(self, a):
        self.var = tf.Variable(a, name="var_a")

    def call(self, inputs, training=False):
        if training:
            return inputs * self.var
        else:
            return inputs

    def get_config(self):
        return {"a": self.var.numpy()}

    # There's actually no need to define `from_config` here, since returning
    # `cls(**config)` is the default behavior.
    @classmethod
    def from_config(cls, config):
        return cls(**config)


layer = CustomLayer(5)
layer.var.assign(2)

serialized_layer = keras.layers.serialize(layer)
new_layer = keras.layers.deserialize(
    serialized_layer, custom_objects={"CustomLayer": CustomLayer}
)

사용자 정의 객체 등록하기

Keras는 구성을 생성한 클래스를 기록합니다. 위의 예에서 tf.keras.layers.serialize는 사용자 정의 레이어의 직렬화된 형식을 생성합니다.

{'class_name': 'CustomLayer', 'config': {'a': 2} }

Keras는 from_config를 호출할 올바른 클래스를 찾는 데 사용되는 모든 내장 레이어, 모델, 옵티마이저 및 메트릭 클래스의 마스터 목록을 유지합니다. 클래스를 찾을 수 없으면 Value Error: Unknown layer 오류가 발생합니다. 이 목록에 사용자 정의 클래스를 등록하는 몇 가지 방법이 있습니다.

  1. 로딩 함수에서 custom_objects 인수 설정(위의 "구성 메서드 정의하기" 섹션의 예 참조).
  2. tf.keras.utils.custom_object_scope 또는 tf.keras.utils.CustomObjectScope
  3. tf.keras.utils.register_keras_serializable

사용자 정의 레이어 및 함수 예제

class CustomLayer(keras.layers.Layer):
    def __init__(self, units=32, **kwargs):
        super(CustomLayer, self).__init__(**kwargs)
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units,), initializer="random_normal", trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

    def get_config(self):
        config = super(CustomLayer, self).get_config()
        config.update({"units": self.units})
        return config


def custom_activation(x):
    return tf.nn.tanh(x) ** 2


# Make a model with the CustomLayer and custom_activation
inputs = keras.Input((32,))
x = CustomLayer(32)(inputs)
outputs = keras.layers.Activation(custom_activation)(x)
model = keras.Model(inputs, outputs)

# Retrieve the config
config = model.get_config()

# At loading time, register the custom objects with a `custom_object_scope`:
custom_objects = {"CustomLayer": CustomLayer, "custom_activation": custom_activation}
with keras.utils.custom_object_scope(custom_objects):
    new_model = keras.Model.from_config(config)

인메모리 모델 복제

tf.keras.models.clone_model()을 통해 모델의 인메모리 복제를 수행할 수도 있습니다. 이는 구성을 가져온 다음 구성에서 모델을 다시 생성하는 것과 같습니다(따라서 컴파일 정보 또는 레이어 가중치 값을 유지하지 않습니다).

예제:

with keras.utils.custom_object_scope(custom_objects):
    new_model = keras.models.clone_model(model)

모델의 가중치 값만 저장 및 로딩

모델의 가중치 값만 저장하고 로드하도록 선택할 수 있습니다. 다음과 같은 경우에 유용할 수 있습니다.

  • 추론을 위한 모델만 필요합니다. 이 경우 훈련을 다시 시작할 필요가 없으므로 컴파일 정보나 옵티마이저 상태가 필요하지 않습니다.
  • 전이 학습을 수행하고 있습니다. 이 경우 이전 모델의 상태를 재사용하는 새 모델을 훈련하므로 이전 모델의 컴파일 정보가 필요하지 않습니다.

인메모리 가중치 전이를 위한 API

get_weightsset_weights를 사용하여 다른 객체 간에 가중치를 복사할 수 있습니다.

다음은 예제입니다.

메모리에서 레이어로 가중치 전이

def create_layer():
    layer = keras.layers.Dense(64, activation="relu", name="dense_2")
    layer.build((None, 784))
    return layer


layer_1 = create_layer()
layer_2 = create_layer()

# Copy weights from layer 1 to layer 2
layer_2.set_weights(layer_1.get_weights())

메모리에서 호환 가능한 아키텍처를 사용하여 모델 간 가중치 전이하기

# Create a simple functional model
inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")

# Define a subclassed model with the same architecture
class SubclassedModel(keras.Model):
    def __init__(self, output_dim, name=None):
        super(SubclassedModel, self).__init__(name=name)
        self.output_dim = output_dim
        self.dense_1 = keras.layers.Dense(64, activation="relu", name="dense_1")
        self.dense_2 = keras.layers.Dense(64, activation="relu", name="dense_2")
        self.dense_3 = keras.layers.Dense(output_dim, name="predictions")

    def call(self, inputs):
        x = self.dense_1(inputs)
        x = self.dense_2(x)
        x = self.dense_3(x)
        return x

    def get_config(self):
        return {"output_dim": self.output_dim, "name": self.name}


subclassed_model = SubclassedModel(10)
# Call the subclassed model once to create the weights.
subclassed_model(tf.ones((1, 784)))

# Copy weights from functional_model to subclassed_model.
subclassed_model.set_weights(functional_model.get_weights())

assert len(functional_model.weights) == len(subclassed_model.weights)
for a, b in zip(functional_model.weights, subclassed_model.weights):
    np.testing.assert_allclose(a.numpy(), b.numpy())

상태 비저장 레이어의 경우

상태 비저장 레이어는 순서 또는 가중치 수를 변경하지 않기 때문에 상태 비저장 레이어가 남거나 없더라도 모델은 호환 가능한 아키텍처를 가질 수 있습니다.

inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")

inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)

# Add a dropout layer, which does not contain any weights.
x = keras.layers.Dropout(0.5)(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model_with_dropout = keras.Model(
    inputs=inputs, outputs=outputs, name="3_layer_mlp"
)

functional_model_with_dropout.set_weights(functional_model.get_weights())

디스크에 가중치를 저장하고 다시 로딩하기 위한 API

다음 형식으로 model.save_weights를 호출하여 디스크에 가중치를 저장할 수 있습니다.

  • TensorFlow Checkpoint
  • HDF5

model.save_weights의 기본 형식은 TensorFlow 체크포인트입니다. 저장 형식을 지정하는 두 가지 방법이 있습니다.

  1. save_format 인수: save_format="tf" 또는 save_format="h5"에 값을 설정합니다.
  2. path 인수: 경로가 .h5 또는 .hdf5로 끝나면 HDF5 형식이 사용됩니다. save_format을 설정하지 않으면 다른 접미어의 경우 TensorFlow 체크포인트로 결과가 발생합니다.

인메모리 numpy 배열로 가중치를 검색하는 옵션도 있습니다. 각 API에는 장단점이 있으며 아래에서 자세히 설명합니다.

TF Checkpoint 형식

예제:

# Runnable example
sequential_model = keras.Sequential(
    [
        keras.Input(shape=(784,), name="digits"),
        keras.layers.Dense(64, activation="relu", name="dense_1"),
        keras.layers.Dense(64, activation="relu", name="dense_2"),
        keras.layers.Dense(10, name="predictions"),
    ]
)
sequential_model.save_weights("ckpt")
load_status = sequential_model.load_weights("ckpt")

# `assert_consumed` can be used as validation that all variable values have been
# restored from the checkpoint. See `tf.train.Checkpoint.restore` for other
# methods in the Status object.
load_status.assert_consumed()
<tensorflow.python.checkpoint.checkpoint.CheckpointLoadStatus at 0x7f56e0a63cd0>

형식 세부 사항

TensorFlow Checkpoint 형식은 객체 속성명을 사용하여 가중치를 저장하고 복원합니다. 예를 들어, tf.keras.layers.Dense 레이어를 고려해 봅시다. 레이어에는 dense.kerneldense.bias 두 가지 가중치가 있습니다. 레이어가 tf 형식으로 저장되면 결과 체크포인트에는 "kernel""bias"와 해당 가중치 값이 포함됩니다. 자세한 정보는 TF Checkpoint 가이드의 "로딩 메커니즘"을 참조하세요.

속성/그래프 에지는 변수명이 아니라 부모 객체에서 사용된 이름에 따라 이름이 지정됩니다. 아래 예제의 CustomLayer를 고려해 봅시다. 변수 CustomLayer.var"var_a"가 아니라, 키의 일부로서 "var"로 저장됩니다.

class CustomLayer(keras.layers.Layer):
    def __init__(self, a):
        self.var = tf.Variable(a, name="var_a")


layer = CustomLayer(5)
layer_ckpt = tf.train.Checkpoint(layer=layer).save("custom_layer")

ckpt_reader = tf.train.load_checkpoint(layer_ckpt)

ckpt_reader.get_variable_to_dtype_map()
{'save_counter/.ATTRIBUTES/VARIABLE_VALUE': tf.int64,
 'layer/var/.ATTRIBUTES/VARIABLE_VALUE': tf.int32,
 '_CHECKPOINTABLE_OBJECT_GRAPH': tf.string}

전송 훈련 예제

기본적으로 두 모델이 동일한 아키텍처를 갖는 한 동일한 검사점을 공유할 수 있습니다.

예제:

inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")

# Extract a portion of the functional model defined in the Setup section.
# The following lines produce a new model that excludes the final output
# layer of the functional model.
pretrained = keras.Model(
    functional_model.inputs, functional_model.layers[-1].input, name="pretrained_model"
)
# Randomly assign "trained" weights.
for w in pretrained.weights:
    w.assign(tf.random.normal(w.shape))
pretrained.save_weights("pretrained_ckpt")
pretrained.summary()

# Assume this is a separate program where only 'pretrained_ckpt' exists.
# Create a new functional model with a different output dimension.
inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(5, name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="new_model")

# Load the weights from pretrained_ckpt into model.
model.load_weights("pretrained_ckpt")

# Check that all of the pretrained weights have been loaded.
for a, b in zip(pretrained.weights, model.weights):
    np.testing.assert_allclose(a.numpy(), b.numpy())

print("\n", "-" * 50)
model.summary()

# Example 2: Sequential model
# Recreate the pretrained model, and load the saved weights.
inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
pretrained_model = keras.Model(inputs=inputs, outputs=x, name="pretrained")

# Sequential example:
model = keras.Sequential([pretrained_model, keras.layers.Dense(5, name="predictions")])
model.summary()

pretrained_model.load_weights("pretrained_ckpt")

# Warning! Calling `model.load_weights('pretrained_ckpt')` won't throw an error,
# but will *not* work as expected. If you inspect the weights, you'll see that
# none of the weights will have loaded. `pretrained_model.load_weights()` is the
# correct method to call.
Model: "pretrained_model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 digits (InputLayer)         [(None, 784)]             0         
                                                                 
 dense_1 (Dense)             (None, 64)                50240     
                                                                 
 dense_2 (Dense)             (None, 64)                4160      
                                                                 
=================================================================
Total params: 54,400
Trainable params: 54,400
Non-trainable params: 0
_________________________________________________________________

 --------------------------------------------------
Model: "new_model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 digits (InputLayer)         [(None, 784)]             0         
                                                                 
 dense_1 (Dense)             (None, 64)                50240     
                                                                 
 dense_2 (Dense)             (None, 64)                4160      
                                                                 
 predictions (Dense)         (None, 5)                 325       
                                                                 
=================================================================
Total params: 54,725
Trainable params: 54,725
Non-trainable params: 0
_________________________________________________________________
Model: "sequential_3"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 pretrained (Functional)     (None, 64)                54400     
                                                                 
 predictions (Dense)         (None, 5)                 325       
                                                                 
=================================================================
Total params: 54,725
Trainable params: 54,725
Non-trainable params: 0
_________________________________________________________________
<tensorflow.python.checkpoint.checkpoint.CheckpointLoadStatus at 0x7f56e0a38a00>

일반적으로 모델을 빌드할 때 동일한 API를 사용하는 것이 좋습니다. Sequential 및 Functional 또는 Functional 및 서브 클래스 등 간에 전환하는 경우, 항상 사전 훈련된 모델을 다시 빌드하고 사전 훈련된 가중치를 해당 모델에 로드합니다.

다음 질문은 모델 아키텍처가 상당히 다른 경우 어떻게 다른 모델에 가중치를 저장하고 로드하는가입니다. 해결책은 tf.train.Checkpoint를 사용하여 정확한 레이어/변수를 저장하고 복원하는 것입니다.

예제:

# Create a subclassed model that essentially uses functional_model's first
# and last layers.
# First, save the weights of functional_model's first and last dense layers.
first_dense = functional_model.layers[1]
last_dense = functional_model.layers[-1]
ckpt_path = tf.train.Checkpoint(
    dense=first_dense, kernel=last_dense.kernel, bias=last_dense.bias
).save("ckpt")

# Define the subclassed model.
class ContrivedModel(keras.Model):
    def __init__(self):
        super(ContrivedModel, self).__init__()
        self.first_dense = keras.layers.Dense(64)
        self.kernel = self.add_variable("kernel", shape=(64, 10))
        self.bias = self.add_variable("bias", shape=(10,))

    def call(self, inputs):
        x = self.first_dense(inputs)
        return tf.matmul(x, self.kernel) + self.bias


model = ContrivedModel()
# Call model on inputs to create the variables of the dense layer.
_ = model(tf.ones((1, 784)))

# Create a Checkpoint with the same structure as before, and load the weights.
tf.train.Checkpoint(
    dense=model.first_dense, kernel=model.kernel, bias=model.bias
).restore(ckpt_path).assert_consumed()
/tmpfs/tmp/ipykernel_197950/1562824211.py:15: UserWarning: `layer.add_variable` is deprecated and will be removed in a future version. Please use the `layer.add_weight()` method instead.
  self.kernel = self.add_variable("kernel", shape=(64, 10))
/tmpfs/tmp/ipykernel_197950/1562824211.py:16: UserWarning: `layer.add_variable` is deprecated and will be removed in a future version. Please use the `layer.add_weight()` method instead.
  self.bias = self.add_variable("bias", shape=(10,))
<tensorflow.python.checkpoint.checkpoint.CheckpointLoadStatus at 0x7f56e0a3d760>

HDF5 format

HDF5 형식에는 레이어 이름별로 그룹화된 가중치가 포함됩니다. 가중치는 훈련 가능한 가중치 목록을 훈련 불가능한 가중치 목록(layer.weights와 동일)에 연결하여 정렬된 목록입니다. 따라서 모델이 체크포인트에 저장된 것과 동일한 레이어 및 훈련 가능한 상태를 갖는 경우 hdf5 체크포인트을 사용할 수 있습니다.

예제:

# Runnable example
sequential_model = keras.Sequential(
    [
        keras.Input(shape=(784,), name="digits"),
        keras.layers.Dense(64, activation="relu", name="dense_1"),
        keras.layers.Dense(64, activation="relu", name="dense_2"),
        keras.layers.Dense(10, name="predictions"),
    ]
)
sequential_model.save_weights("weights.h5")
sequential_model.load_weights("weights.h5")

모델에 중첩된 레이어가 포함된 경우 layer.trainable을 변경하면 layer.weights의 순서가 다르게 나타날 수 있습니다.

class NestedDenseLayer(keras.layers.Layer):
    def __init__(self, units, name=None):
        super(NestedDenseLayer, self).__init__(name=name)
        self.dense_1 = keras.layers.Dense(units, name="dense_1")
        self.dense_2 = keras.layers.Dense(units, name="dense_2")

    def call(self, inputs):
        return self.dense_2(self.dense_1(inputs))


nested_model = keras.Sequential([keras.Input((784,)), NestedDenseLayer(10, "nested")])
variable_names = [v.name for v in nested_model.weights]
print("variables: {}".format(variable_names))

print("\nChanging trainable status of one of the nested layers...")
nested_model.get_layer("nested").dense_1.trainable = False

variable_names_2 = [v.name for v in nested_model.weights]
print("\nvariables: {}".format(variable_names_2))
print("variable ordering changed:", variable_names != variable_names_2)
variables: ['nested/dense_1/kernel:0', 'nested/dense_1/bias:0', 'nested/dense_2/kernel:0', 'nested/dense_2/bias:0']

Changing trainable status of one of the nested layers...

variables: ['nested/dense_2/kernel:0', 'nested/dense_2/bias:0', 'nested/dense_1/kernel:0', 'nested/dense_1/bias:0']
variable ordering changed: True

전이 학습 예제

HDF5에서 사전 훈련된 가중치를 로딩할 때는 가중치를 기존 체크포인트 모델에 로드한 다음 원하는 가중치/레이어를 새 모델로 추출하는 것이 좋습니다.

예제:

def create_functional_model():
    inputs = keras.Input(shape=(784,), name="digits")
    x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
    x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
    outputs = keras.layers.Dense(10, name="predictions")(x)
    return keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")


functional_model = create_functional_model()
functional_model.save_weights("pretrained_weights.h5")

# In a separate program:
pretrained_model = create_functional_model()
pretrained_model.load_weights("pretrained_weights.h5")

# Create a new model by extracting layers from the original model:
extracted_layers = pretrained_model.layers[:-1]
extracted_layers.append(keras.layers.Dense(5, name="dense_3"))
model = keras.Sequential(extracted_layers)
model.summary()
Model: "sequential_6"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_1 (Dense)             (None, 64)                50240     
                                                                 
 dense_2 (Dense)             (None, 64)                4160      
                                                                 
 dense_3 (Dense)             (None, 5)                 325       
                                                                 
=================================================================
Total params: 54,725
Trainable params: 54,725
Non-trainable params: 0
_________________________________________________________________