직렬화 & 저장

모델 저장, 직렬화 및 export

저자 : Neel Kovelamudi, Francois Chollet
생성일 : 2023/06/14
최종 편집일 : 2023/06/30
설명 : 모델 저장, 직렬화 및 export에 대한 완벽 가이드.

소개

Keras 모델은 여러 구성 요소로 이루어져 있습니다:

  • 아키텍처 또는 설정: 모델에 포함된 레이어와 이들이 어떻게 연결되는지를 정의합니다.
  • 가중치 값 세트 (“모델의 상태”).
  • 옵티마이저 (모델을 컴파일할 때 정의됨).
  • 손실 함수 및 메트릭 세트 (모델을 컴파일할 때 정의됨).

Keras API는 이러한 모든 요소를 하나의 통합된 형식으로 저장하며, .keras 확장자로 표시됩니다. 이 형식은 다음을 포함하는 zip 아카이브입니다:

  • JSON 기반 설정 파일 (config.json): 모델, 레이어 및 기타 추적 가능한 객체들의 설정을 기록합니다.
  • H5 기반 상태 파일 (예: model.weights.h5): 레이어와 그 가중치에 대한 디렉터리 키를 포함하는 전체 모델 상태 파일.
  • JSON 형식의 메타데이터 파일: 현재 Keras 버전과 같은 정보를 저장합니다.

이제 이것이 어떻게 작동하는지 살펴보겠습니다.

모델 저장 및 로드 방법

이 가이드를 읽을 시간이 10초밖에 없다면, 다음 내용을 숙지하세요.

Keras 모델 저장:

model = ...  # 모델을 가져옵니다 (Sequential, Functional Model, 또는 Model 서브클래스)
model.save('path/to/location.keras')  # 파일은 .keras 확장자로 끝나야 합니다.

모델 다시 로드:

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

이제 세부 사항을 살펴보겠습니다.

셋업

import numpy as np
import keras
from keras import ops

저장

이 섹션은 전체 모델을 하나의 파일로 저장하는 방법에 대한 내용입니다. 이 파일에는 다음이 포함됩니다:

  • 모델의 아키텍처/구성
  • 모델의 가중치 값 (트레이닝 중 학습된 값)
  • 모델의 컴파일 정보 (만약 compile()이 호출되었다면)
  • 옵티마이저 및 그 상태(있을 경우, 트레이닝을 중단했던 지점에서 다시 시작할 수 있도록 해줌)

APIs

model.save() 또는 keras.models.save_model()을 사용해, 모델을 저장할 수 있습니다. (둘은 동일합니다) keras.models.load_model()을 사용해 다시 로드할 수 있습니다.

Keras 3에서 지원되는 유일한 형식은 “.keras” 확장자를 사용하는 “Keras v3” 형식입니다.

예제:

def get_model():
    # 간단한 모델을 만듭니다.
    inputs = keras.Input(shape=(32,))
    outputs = keras.layers.Dense(1)(inputs)
    model = keras.Model(inputs, outputs)
    model.compile(optimizer=keras.optimizers.Adam(), loss="mean_squared_error")
    return model


model = get_model()

# 모델을 트레이닝합니다.
test_input = np.random.random((128, 32))
test_target = np.random.random((128, 1))
model.fit(test_input, test_target)

# `save('my_model.keras')`를 호출하면,
# `my_model.keras`라는 zip 아카이브가 생성됩니다.
model.save("my_model.keras")

# 동일하게 모델을 재구성할 수 있습니다.
reconstructed_model = keras.models.load_model("my_model.keras")

# 확인해봅시다:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
결과
 4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - loss: 0.4232
 4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 281us/step
 4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 373us/step

커스텀 객체

이 섹션에서는 Keras에서 커스텀 레이어, 함수, 모델을 저장하고 다시 불러오는 기본적인 워크플로를 다룹니다.

커스텀 객체(서브클래스된 레이어 등)를 포함하는 모델을 저장할 때, 객체 클래스에 get_config() 메서드를 반드시 정의해야 합니다. 만약 커스텀 객체의 생성자(__init__() 메서드)에 전달된 인자가 기본 타입(정수, 문자열 등)이 아닌 Python 객체라면, from_config() 클래스 메서드에서 이러한 인자들을 명시적으로 반드시 역직렬화해야 합니다.

예시는 다음과 같습니다:

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

    def call(self, x):
        return self.sublayer(x)

    def get_config(self):
        base_config = super().get_config()
        config = {
            "sublayer": keras.saving.serialize_keras_object(self.sublayer),
        }
        return {**base_config, **config}

    @classmethod
    def from_config(cls, config):
        sublayer_config = config.pop("sublayer")
        sublayer = keras.saving.deserialize_keras_object(sublayer_config)
        return cls(sublayer, **config)

자세한 내용과 예시는 config 메서드 정의 섹션을 참조하세요.

저장된 .keras 파일은 경량화되어 있으며, 커스텀 객체에 대한 Python 코드를 저장하지 않습니다. 따라서, 모델을 다시 로드하려면, load_model이 다음 중 하나의 방법을 통해 사용된, 커스텀 객체의 정의에 접근할 수 있어야 합니다:

  1. 커스텀 객체를 등록 (권장 방법),
  2. 로드할 때 커스텀 객체를 직접 전달, 또는
  3. 커스텀 객체 scope를 사용하는 방법

다음은 각각의 워크플로 예시입니다:

커스텀 객체 등록 (권장 방법)

이 방법은 권장되는 방법입니다. 커스텀 객체 등록은 저장 및 로드 코드를 크게 단순화합니다. 커스텀 객체의 클래스 정의에 @keras.saving.register_keras_serializable 데코레이터를 추가하면, 해당 객체가 전역적으로 마스터 리스트에 등록되어, Keras가 모델을 로드할 때 객체를 인식할 수 있습니다.

이를 설명하기 위해 커스텀 레이어와 커스텀 활성화 함수를 포함한, 커스텀 모델을 만들어보겠습니다.

예제:

# 이전에 등록된 모든 커스텀 객체를 지웁니다.
keras.saving.get_custom_objects().clear()


# 등록할 때 패키지나 이름을 선택적으로 지정할 수 있습니다.
# 지정하지 않으면 패키지는 `Custom`, 이름은 클래스 이름으로 설정됩니다.
@keras.saving.register_keras_serializable(package="MyLayers")
class CustomLayer(keras.layers.Layer):
    def __init__(self, factor):
        super().__init__()
        self.factor = factor

    def call(self, x):
        return x * self.factor

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


@keras.saving.register_keras_serializable(package="my_package", name="custom_fn")
def custom_fn(x):
    return x**2


# 모델을 만듭니다.
def get_model():
    inputs = keras.Input(shape=(4,))
    mid = CustomLayer(0.5)(inputs)
    outputs = keras.layers.Dense(1, activation=custom_fn)(mid)
    model = keras.Model(inputs, outputs)
    model.compile(optimizer="rmsprop", loss="mean_squared_error")
    return model


# 모델을 트레이닝합니다.
def train_model(model):
    input = np.random.random((4, 4))
    target = np.random.random((4, 1))
    model.fit(input, target)
    return model


test_input = np.random.random((4, 4))
test_target = np.random.random((4, 1))

model = get_model()
model = train_model(model)
model.save("custom_model.keras")

# 이제, 커스텀 객체에 대해 걱정할 필요 없이 간단히 로드할 수 있습니다.
reconstructed_model = keras.models.load_model("custom_model.keras")

# 확인해봅시다:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
결과
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 46ms/step - loss: 0.2571
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step

load_model()에 커스텀 객체 전달

model = get_model()
model = train_model(model)

# `save('my_model.keras')`를 호출하면, `my_model.keras`라는 zip 아카이브가 생성됩니다.
model.save("custom_model.keras")

# 로드할 때 `keras.models.load_model()`의 `custom_objects` 인자로
# 사용된 커스텀 객체를 포함하는 딕셔너리를 전달합니다.
reconstructed_model = keras.models.load_model(
    "custom_model.keras",
    custom_objects={"CustomLayer": CustomLayer, "custom_fn": custom_fn},
)

# 확인해봅시다:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
결과
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 37ms/step - loss: 0.0535
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step

커스텀 객체 scope를 사용

커스텀 객체 scope 내의 코드는 scope 인자로 전달된 커스텀 객체를 인식할 수 있습니다. 따라서, 모델을 해당 scope 내에서 로드하면 커스텀 객체도 함께 로드할 수 있습니다.

예제:

model = get_model()
model = train_model(model)
model.save("custom_model.keras")

# 커스텀 객체 딕셔너리를 커스텀 객체 scope에 전달하고,
# `keras.models.load_model()` 호출을 해당 scope 내에 배치합니다.
custom_objects = {"CustomLayer": CustomLayer, "custom_fn": custom_fn}

with keras.saving.custom_object_scope(custom_objects):
    reconstructed_model = keras.models.load_model("custom_model.keras")

# 확인해봅시다:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
결과
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 40ms/step - loss: 0.0868
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step

모델 직렬화

이 섹션은 모델의 상태 없이 모델의 구성만 저장하는 방법에 대한 내용입니다. 모델의 구성(또는 아키텍처)은 모델이 어떤 레이어를 포함하고 있으며, 이 레이어들이 어떻게 연결되어 있는지를 명시합니다. 모델의 구성을 가지고 있다면, 가중치나 컴파일 정보 없이 새롭게 초기화된 상태로 모델을 생성할 수 있습니다.

APIs

다음과 같은 직렬화 API를 사용할 수 있습니다:

  • keras.models.clone_model(model): 모델의 (랜덤하게 초기화된) 복사본을 생성합니다.
  • get_config()cls.from_config(): 레이어 또는 모델의 구성을 검색오고, 해당 구성으로 모델 인스턴스를 재생성합니다.
  • keras.models.model_to_json()keras.models.model_from_json(): 유사하지만, JSON 문자열로 처리됩니다.
  • keras.saving.serialize_keras_object(): 임의의 Keras 객체 구성을 검색합니다.
  • keras.saving.deserialize_keras_object(): 객체의 구성으로부터 인스턴스를 재생성합니다.

메모리 내에서 모델 복제

keras.models.clone_model()을 사용하여 메모리 내에서 모델을 복제할 수 있습니다. 이는 config를 가져온 다음 해당 config에서 모델을 재생성하는 것과 동일하며, 컴파일 정보나 레이어 가중치 값은 유지되지 않습니다.

예제:

new_model = keras.models.clone_model(model)

get_config()from_config()

model.get_config() 또는 layer.get_config()을 호출하면, 각각 모델 또는 레이어의 구성을 포함한 Python 딕셔너리가 반환됩니다. 모델이나 레이어의 __init__() 메서드에 필요한 인자를 get_config()에 정의해야 합니다. 로딩 시, from_config(config) 메서드가 이러한 인자들과 함께, __init__()을 호출하여 모델 또는 레이어를 재구성합니다.

레이어 예시:

layer = keras.layers.Dense(3, activation="relu")
layer_config = layer.get_config()
print(layer_config)
결과
{'name': 'dense_4', 'trainable': True, 'dtype': 'float32', 'units': 3, 'activation': 'relu', 'use_bias': True, 'kernel_initializer': {'module': 'keras.src.initializers.random_initializers', 'class_name': 'GlorotUniform', 'config': {'seed': None}, 'registered_name': 'GlorotUniform'}, 'bias_initializer': {'module': 'keras.src.initializers.constant_initializers', 'class_name': 'Zeros', 'config': {}, 'registered_name': 'Zeros'}, 'kernel_regularizer': None, 'bias_regularizer': None, 'kernel_constraint': None, 'bias_constraint': None}

이제 from_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()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)

임의 객체 직렬화 및 역직렬화

keras.saving.serialize_keras_object()keras.saving.deserialize_keras_object() API는 Keras 객체와 커스텀 객체를 직렬화하거나 역직렬화할 수 있는 범용 API입니다. 이는 모델 아키텍처 저장의 기반이 되며, Keras 내의 모든 serialize()/deserialize() 호출의 배경에 있습니다.

예제:

my_reg = keras.regularizers.L1(0.005)
config = keras.saving.serialize_keras_object(my_reg)
print(config)
결과
{'module': 'keras.src.regularizers.regularizers', 'class_name': 'L1', 'config': {'l1': 0.004999999888241291}, 'registered_name': 'L1'}

올바르게 재구성하기 위한, 모든 필수 정보를 포함하는 직렬화 형식을 확인하세요:

  • module: 객체가 속한 Keras 모듈 또는 기타 식별 모듈의 이름을 포함
  • class_name: 객체 클래스의 이름을 포함
  • config: 객체를 재구성하는 데 필요한 모든 정보
  • registered_name: 커스텀 객체의 경우. 여기를 참조하세요.

이제 정규화기(regularizer)를 재구성할 수 있습니다.

new_reg = keras.saving.deserialize_keras_object(config)

모델 가중치 저장

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

  • 모델을 추론에만 사용할 경우: 이 경우 트레이닝을 다시 시작할 필요가 없으므로, 컴파일 정보나 옵티마이저 상태가 필요하지 않습니다.
  • 전이 학습을 할 경우: 이전 모델의 상태를 재사용하여 새 모델을 트레이닝하려는 경우, 이전 모델의 컴파일 정보는 필요하지 않습니다.

메모리 내에서 가중치 전송을 위한 API

get_weights()set_weights()를 사용하여, 다른 객체 간에 가중치를 복사할 수 있습니다:

  • keras.layers.Layer.get_weights(): 가중치 값을 NumPy 배열 목록으로 반환합니다.
  • keras.layers.Layer.set_weights(weights): 제공된 NumPy 배열로 모델의 가중치를 설정합니다.

예제:

메모리 내에서, 한 레이어에서 다른 레이어로 가중치 전송

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()

# 레이어 1에서 레이어 2로 가중치 복사
layer_2.set_weights(layer_1.get_weights())

메모리 내에서, 호환되는 아키텍처를 가진 두 모델 간에 가중치 전송

# 간단한 Functional 모델 생성
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")


# 동일한 아키텍처를 가진 서브클래스된 모델 정의
class SubclassedModel(keras.Model):
    def __init__(self, output_dim, name=None):
        super().__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)
# 서브클래스된 모델을 한 번 호출하여 가중치를 생성합니다.
subclassed_model(np.ones((1, 784)))

# functional_model에서 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())

stateless 레이어의 경우

stateless 레이어는 가중치의 순서나 수를 변경하지 않기 때문에, stateless 레이어가 추가되거나 빠지더라도(extra/missing) 모델은 호환되는 아키텍처를 가질 수 있습니다.

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)

# 가중치를 포함하지 않는, 드롭아웃 레이어를 추가합니다.
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(filepath)을 호출하여, 가중치를 디스크에 저장할 수 있습니다. 파일 이름은 .weights.h5로 끝나야 합니다.

예제:

# 실행 가능한 예시
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("my_model.weights.h5")
sequential_model.load_weights("my_model.weights.h5")

모델에 중첩된 레이어가 있을 경우, layer.trainable을 변경하면 layer.weights의 순서가 달라질 수 있습니다.

class NestedDenseLayer(keras.layers.Layer):
    def __init__(self, units, name=None):
        super().__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: ['kernel', 'bias', 'kernel', 'bias']
Changing trainable status of one of the nested layers...
variables: ['kernel', 'bias', 'kernel', 'bias']
variable ordering changed: False
전이 학습 예시

사전 트레이닝된 가중치를 가중치 파일에서 로드할 때는, 원래 체크포인트된 모델에 가중치를 로드한 후, 원하는 가중치/레이어를 새로운 모델에 추출하는 것이 좋습니다.

예제:

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")

# 별도의 프로그램에서:
pretrained_model = create_functional_model()
pretrained_model.load_weights("pretrained.weights.h5")

# 원본 모델에서 레이어를 추출하여, 새로운 모델을 생성합니다:
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_4"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape              ┃    Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ dense_1 (Dense)                 │ (None, 64)                │     50,240 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense_2 (Dense)                 │ (None, 64)                │      4,160 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense_3 (Dense)                 │ (None, 5)                 │        325 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 Total params: 54,725 (213.77 KB)
 Trainable params: 54,725 (213.77 KB)
 Non-trainable params: 0 (0.00 B)

부록: 커스텀 객체 다루기

config 메서드 정의

명세:

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

참고: 모든 생성자 인자가 이미 직렬화 가능하다면, 예: 문자열, 정수, 또는 커스텀 Keras 객체가 아니라면, from_config를 재정의할 필요가 없습니다. 그러나, __init__에 전달된 레이어 또는 모델과 같은 더 복잡한 객체에 대해서는, __init__ 자체에서 역직렬화를 명시적으로 처리하거나 from_config() 메서드를 재정의해야 합니다.

예제:

@keras.saving.register_keras_serializable(package="MyLayers", name="KernelMult")
class MyDense(keras.layers.Layer):
    def __init__(
        self,
        units,
        *,
        kernel_regularizer=None,
        kernel_initializer=None,
        nested_model=None,
        **kwargs
    ):
        super().__init__(**kwargs)
        self.hidden_units = units
        self.kernel_regularizer = kernel_regularizer
        self.kernel_initializer = kernel_initializer
        self.nested_model = nested_model

    def get_config(self):
        config = super().get_config()
        # 커스텀 레이어의 매개변수로 config를 업데이트합니다.
        config.update(
            {
                "units": self.hidden_units,
                "kernel_regularizer": self.kernel_regularizer,
                "kernel_initializer": self.kernel_initializer,
                "nested_model": self.nested_model,
            }
        )
        return config

    def build(self, input_shape):
        input_units = input_shape[-1]
        self.kernel = self.add_weight(
            name="kernel",
            shape=(input_units, self.hidden_units),
            regularizer=self.kernel_regularizer,
            initializer=self.kernel_initializer,
        )

    def call(self, inputs):
        return ops.matmul(inputs, self.kernel)


layer = MyDense(units=16, kernel_regularizer="l1", kernel_initializer="ones")
layer3 = MyDense(units=64, nested_model=layer)

config = keras.layers.serialize(layer3)

print(config)

new_layer = keras.layers.deserialize(config)

print(new_layer)
결과
{'module': None, 'class_name': 'MyDense', 'config': {'name': 'my_dense_1', 'trainable': True, 'dtype': 'float32', 'units': 64, 'kernel_regularizer': None, 'kernel_initializer': None, 'nested_model': {'module': None, 'class_name': 'MyDense', 'config': {'name': 'my_dense', 'trainable': True, 'dtype': 'float32', 'units': 16, 'kernel_regularizer': 'l1', 'kernel_initializer': 'ones', 'nested_model': None}, 'registered_name': 'MyLayers>KernelMult'}}, 'registered_name': 'MyLayers>KernelMult'}
<MyDense name=my_dense_1, built=False>

위의 MyDense에서는 from_config를 재정의할 필요가 없다는 점에 유의하세요. 왜냐하면 hidden_units, kernel_initializer, 그리고 kernel_regularizer가 각각 정수, 문자열, 그리고 빌트인 Keras 객체이기 때문입니다. 이는 기본 from_config 구현인 cls(**config)가 의도한 대로 작동한다는 것을 의미합니다.

__init__에 전달되는 레이어나 모델과 같은, 더 복잡한 객체에 대해서는 명시적으로 해당 객체를 역직렬화해야 합니다. from_config 재정의가 필요한 모델의 예시를 살펴보겠습니다.

예제:

@keras.saving.register_keras_serializable(package="ComplexModels")
class CustomModel(keras.layers.Layer):
    def __init__(self, first_layer, second_layer=None, **kwargs):
        super().__init__(**kwargs)
        self.first_layer = first_layer
        if second_layer is not None:
            self.second_layer = second_layer
        else:
            self.second_layer = keras.layers.Dense(8)

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

    @classmethod
    def from_config(cls, config):
        # 여기서 [`keras.saving.deserialize_keras_object`](/api/models/model_saving_apis/serialization_utils#deserializekerasobject-function)
        # 를 사용할 수도 있습니다.
        config["first_layer"] = keras.layers.deserialize(config["first_layer"])
        config["second_layer"] = keras.layers.deserialize(config["second_layer"])
        return cls(**config)

    def call(self, inputs):
        return self.first_layer(self.second_layer(inputs))


# 첫 번째 레이어로 이전 예시에서 만든 커스텀 레이어 (MyDense)를 사용합니다.
inputs = keras.Input((32,))
outputs = CustomModel(first_layer=layer)(inputs)
model = keras.Model(inputs, outputs)

config = model.get_config()
new_model = keras.Model.from_config(config)

커스텀 객체가 직렬화되는 방식

직렬화 형식에는 @keras.saving.register_keras_serializable로 등록된 커스텀 객체에 대한 특별한 키가 있습니다. 이 registered_name 키는 로딩/역직렬화 시 Keras 마스터 리스트에서 쉽게 검색할 수 있도록 하며, 사용자들이 커스텀 이름을 추가할 수 있도록 합니다.

위에서 정의한 커스텀 레이어 MyDense를 직렬화한 후의 구성을 살펴보겠습니다.

예제:

layer = MyDense(
    units=16,
    kernel_regularizer=keras.regularizers.L1L2(l1=1e-5, l2=1e-4),
    kernel_initializer="ones",
)
config = keras.layers.serialize(layer)
print(config)
결과
{'module': None, 'class_name': 'MyDense', 'config': {'name': 'my_dense_2', 'trainable': True, 'dtype': 'float32', 'units': 16, 'kernel_regularizer': {'module': 'keras.src.regularizers.regularizers', 'class_name': 'L1L2', 'config': {'l1': 1e-05, 'l2': 0.0001}, 'registered_name': 'L1L2'}, 'kernel_initializer': 'ones', 'nested_model': None}, 'registered_name': 'MyLayers>KernelMult'}

보시다시피, registered_name 키에는 Keras 마스터 리스트의 조회 정보를 포함하고 있으며, MyLayers라는 패키지와 @keras.saving.register_keras_serializable 데코레이터에서 지정한 커스텀 이름 KernelMult가 포함되어 있습니다. 커스텀 클래스 정의/등록에 대해서는 다시 여기를 참조하세요.

class_name 키는 클래스의 원본 이름을 포함하여, from_config에서 적절한 재초기화를 가능하게 합니다.

또한, module 키가 None인 것을 주목하세요. 이는 커스텀 객체이기 때문입니다.