In [ ]:
#@title 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.
Keras 모델은 다중 구성 요소로 이루어집니다.
add_loss() 또는 add_metric()을 호출하여 정의된 손실 및 메트릭의 집합Keras API를 사용하면 이러한 조각을 한 번에 디스크에 저장하거나 선택적으로 일부만 저장할 수 있습니다.
언제 사용해야 하는지, 어떻게 동작하는 것인지 각각 살펴봅시다.
In [ ]:
import numpy as np
import tensorflow as tf
from tensorflow import keras
전체 모델을 단일 아티팩트로 저장할 수 있습니다. 다음을 포함합니다.
compile()이 호출된 경우)model.save() 또는 tf.keras.models.save_model()tf.keras.models.load_model()전체 모델을 디스크에 저장하는 데 사용할 수 있는 두 형식은 TensorFlow SavedModel 형식과 이전 Keras H5 형식입니다. 권장하는 형식은 SavedModel입니다. 이는 model.save()를 사용할 때의 기본값입니다.
다음을 통해 H5 형식으로 전환할 수 있습니다.
format='h5'를 save()로 전달합니다..h5 또는 .keras로 끝나는 파일명을 save()로 전달합니다.
In [ ]:
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)
In [ ]:
!ls my_model
모델 아키텍처 및 훈련 구성(옵티마이저, 손실 및 메트릭 포함)은 saved_model.pb에 저장됩니다. 가중치는 variables/ 디렉토리에 저장됩니다.
SavedModel 형식에 대한 자세한 내용은 SavedModel 가이드(디스크의 SavedModel 형식)를 참조하세요.
모델과 모델의 레이어를 저장할 때 SavedModel 형식은 클래스명, 호출 함수, 손실 및 가중치(구현된 경우에는 구성도 포함)를 저장합니다. 호출 함수는 모델/레이어의 계산 그래프를 정의합니다.
모델/레이어 구성이 없는 경우 호출 함수는 훈련, 평가 및 추론에 사용될 수 있는 기존 모델과 같은 모델을 만드는 데 사용됩니다.
그럼에도 불구하고 사용자 정의 모델 또는 레이어 클래스를 작성할 때 항상 get_config 및 from_config 메서드를 정의하는 것이 좋습니다. 이를 통해 필요한 경우 나중에 계산을 쉽게 업데이트할 수 있습니다. 자세한 내용은 사용자 정의 객체에 대한 섹션을 참조하세요.
다음은 구성 메서드를 덮어쓰지않고 SavedModel 형식에서 사용자 정의 레이어를 로딩할 때 발생하는 현상에 대한 예제입니다.
In [ ]:
class CustomModel(keras.Model):
def __init__(self, hidden_units):
super(CustomModel, self).__init__()
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
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")
# Delete the custom-defined model class to ensure that the loader does not have
# access to it.
del CustomModel
loaded = keras.models.load_model("my_model")
np.testing.assert_allclose(loaded(input_arr), outputs)
print("Original model:", model)
print("Loaded model:", loaded)
위 예제에서 볼 수 있듯이 로더는 기존 모델처럼 작동하는 새 모델 클래스를 동적으로 만듭니다.
In [ ]:
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)
SavedModel 형식과 비교하여 H5 파일에 포함되지 않은 두 가지가 있습니다.
model.add_loss() 및 model.add_metric()을 통해 추가된 외부 손실 및 메트릭은 SavedModel과 달리 저장되지 않습니다. 모델에 이러한 손실 및 메트릭이 있고 훈련을 재개하려는 경우 모델을 로드한 후 이러한 손실을 다시 추가해야 합니다. self.add_loss() 및 self.add_metric()을 통해 레이어 내부에 생성된 손실/메트릭에는 적용되지 않습니다. 레이어가 로드되는 한 이러한 손실 및 메트릭은 레이어의 call 메서드의 일부이므로 유지됩니다.
In [ ]:
layer = keras.layers.Dense(3, activation="relu")
layer_config = layer.get_config()
new_layer = keras.layers.Dense.from_config(layer_config)
Sequential 모델 예제:
In [ ]:
model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])
config = model.get_config()
new_model = keras.Sequential.from_config(config)
Functional 모델 예제:
In [ ]:
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)
In [ ]:
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를 제공할 필요가 없습니다. 다음과 같이 해볼 수 있습니다.
In [ ]:
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()
이 메서드에는 몇 가지 단점이 있습니다.
tf.saved_model.load에 의해 반환된 객체는 Keras 모델이 아닙니다. 따라서 사용하기가 쉽지 않습니다. 예를 들면, .predict() 또는 .fit()에 접근할 수 없습니다.사용을 권장하지는 않지만, 사용자 정의 객체의 코드를 잃어버렸거나 tf.keras.models.load_model() 모델을 로드하는 데 문제가 있는 경우와 같이 곤란한 상황에서는 도움이 될 수 있습니다.
tf.saved_model.load와 관련된 페이지에서 자세한 내용을 확인할 수 있습니다.
In [ ]:
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}}
from_config를 호출할 올바른 클래스를 찾는 데 사용되는 모든 내장 레이어, 모델, 옵티마이저 및 메트릭 클래스의 마스터 목록을 유지합니다. 클래스를 찾을 수 없으면 오류가 발생합니다(Value Error: Unknown layer). 다음 목록은 사용자 정의 클래스를 등록하는 몇 가지 방법입니다.
custom_objects 인수 설정(위의 "구성 메서드 정의하기" 섹션의 예 참조).tf.keras.utils.custom_object_scope 또는 tf.keras.utils.CustomObjectScopetf.keras.utils.register_keras_serializable
In [ ]:
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)
In [ ]:
with keras.utils.custom_object_scope(custom_objects):
new_model = keras.models.clone_model(model)
In [ ]:
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 2 to layer 1
layer_2.set_weights(layer_1.get_weights())
메모리에서 호환 가능한 아키텍처를 사용하여 모델 간 가중치 전이하기
In [ ]:
# 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())
상태 비저장 레이어의 경우
상태 비저장 레이어는 순서 또는 가중치 수를 변경하지 않기 때문에 상태 비저장 레이어가 남거나 없더라도 모델은 호환 가능한 아키텍처를 가질 수 있습니다.
In [ ]:
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())
다음 형식으로 model.save_weights를 호출하여 디스크에 가중치를 저장할 수 있습니다.
model.save_weights의 기본 형식은 TensorFlow 체크포인트입니다. 저장 형식을 지정하는 두 가지 방법이 있습니다.
save_format 인수: save_format="tf" 또는 save_format="h5"에 값을 설정합니다.path 인수: 경로가 .h5 또는 .hdf5로 끝나면 HDF5 형식이 사용됩니다. save_format을 설정하지 않으면 다른 접미어의 경우 TensorFlow 체크포인트로 결과가 발생합니다.인메모리 numpy 배열로 가중치를 검색하는 옵션도 있습니다. 각 API에는 장단점이 있으며 아래에서 자세히 설명합니다.
In [ ]:
# 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 Checkpoint 형식은 객체 속성명을 사용하여 가중치를 저장하고 복원합니다. 예를 들어, tf.keras.layers.Dense 레이어를 고려해 봅시다. 레이어에는 dense.kernel과 dense.bias 두 가지 가중치가 있습니다. 레이어가 tf 형식으로 저장되면 결과 체크포인트에는 "kernel" 및 "bias"와 해당 가중치 값이 포함됩니다. 자세한 정보는 TF Checkpoint 가이드의 "로딩 메커니즘"을 참조하세요.
속성/그래프 에지는 변수명이 아니라 부모 객체에서 사용된 이름에 따라 이름이 지정됩니다. 아래 예제의 CustomLayer를 고려해 봅시다. 변수 CustomLayer.var는 "var_a"가 아니라, 키의 일부로서 "var"로 저장됩니다.
In [ ]:
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()
In [ ]:
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.
일반적으로 모델을 빌드할 때 동일한 API를 사용하는 것이 좋습니다. Sequential 및 Functional 또는 Functional 및 서브 클래스 등 간에 전환하는 경우, 항상 사전 훈련된 모델을 다시 빌드하고 사전 훈련된 가중치를 해당 모델에 로드합니다.
다음 질문은 모델 아키텍처가 상당히 다른 경우 어떻게 다른 모델에 가중치를 저장하고 로드하는가입니다. 해결책은 tf.train.Checkpoint를 사용하여 정확한 레이어/변수를 저장하고 복원하는 것입니다.
예제:
In [ ]:
# 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()
In [ ]:
# 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의 순서가 다르게 나타날 수 있습니다.
In [ ]:
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)
In [ ]:
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()