{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "b518b04cbfe0" }, "source": [ "##### Copyright 2020 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2022-12-14T22:23:42.959274Z", "iopub.status.busy": "2022-12-14T22:23:42.958703Z", "iopub.status.idle": "2022-12-14T22:23:42.962666Z", "shell.execute_reply": "2022-12-14T22:23:42.961979Z" }, "id": "906e07f6e562" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "394e705afdd5" }, "source": [ "# Keras 모델 저장 및 로드" ] }, { "cell_type": "markdown", "metadata": { "id": "60de82f6bcea" }, "source": [ "
![]() | \n",
" ![]() | \n",
" ![]() | \n",
" ![]() | \n",
"
tf.saved_model.load
와 관련된 페이지](https://www.tensorflow.org/api_docs/python/tf/saved_model/load)에서 자세한 내용을 확인할 수 있습니다."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "d308bc27a04d"
},
"source": [
"#### 구성 메서드 정의하기\n",
"\n",
"명세:\n",
"\n",
"- `get_config`는 Keras 아키텍처 및 모델 저장 API와 호환되도록 JSON 직렬화 가능 사전을 반환해야 합니다.\n",
"- `from_config(config)`(`classmethod`)는 구성에서 생성된 새 레이어 또는 모델 객체를 반환해야 합니다. 기본 구현은 `cls(**config)`를 반환합니다.\n",
"\n",
"**예제:**"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"execution": {
"iopub.execute_input": "2022-12-14T22:23:52.639842Z",
"iopub.status.busy": "2022-12-14T22:23:52.639603Z",
"iopub.status.idle": "2022-12-14T22:23:52.650715Z",
"shell.execute_reply": "2022-12-14T22:23:52.650122Z"
},
"id": "e18c4668dadc"
},
"outputs": [],
"source": [
"class CustomLayer(keras.layers.Layer):\n",
" def __init__(self, a):\n",
" self.var = tf.Variable(a, name=\"var_a\")\n",
"\n",
" def call(self, inputs, training=False):\n",
" if training:\n",
" return inputs * self.var\n",
" else:\n",
" return inputs\n",
"\n",
" def get_config(self):\n",
" return {\"a\": self.var.numpy()}\n",
"\n",
" # There's actually no need to define `from_config` here, since returning\n",
" # `cls(**config)` is the default behavior.\n",
" @classmethod\n",
" def from_config(cls, config):\n",
" return cls(**config)\n",
"\n",
"\n",
"layer = CustomLayer(5)\n",
"layer.var.assign(2)\n",
"\n",
"serialized_layer = keras.layers.serialize(layer)\n",
"new_layer = keras.layers.deserialize(\n",
" serialized_layer, custom_objects={\"CustomLayer\": CustomLayer}\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "425a9baa574e"
},
"source": [
"#### 사용자 정의 객체 등록하기\n",
"\n",
"Keras는 구성을 생성한 클래스를 기록합니다. 위의 예에서 `tf.keras.layers.serialize`는 사용자 정의 레이어의 직렬화된 형식을 생성합니다.\n",
"\n",
"```\n",
"{'class_name': 'CustomLayer', 'config': {'a': 2}}\n",
"```\n",
"\n",
"Keras는 `from_config`를 호출할 올바른 클래스를 찾는 데 사용되는 모든 내장 레이어, 모델, 옵티마이저 및 메트릭 클래스의 마스터 목록을 유지합니다. 클래스를 찾을 수 없으면 `Value Error: Unknown layer` 오류가 발생합니다. 이 목록에 사용자 정의 클래스를 등록하는 몇 가지 방법이 있습니다.\n",
"\n",
"1. 로딩 함수에서 `custom_objects` 인수 설정(위의 \"구성 메서드 정의하기\" 섹션의 예 참조).\n",
"2. `tf.keras.utils.custom_object_scope` 또는 `tf.keras.utils.CustomObjectScope`\n",
"3. `tf.keras.utils.register_keras_serializable`"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "a047be0ba572"
},
"source": [
"#### 사용자 정의 레이어 및 함수 예제"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"execution": {
"iopub.execute_input": "2022-12-14T22:23:52.654315Z",
"iopub.status.busy": "2022-12-14T22:23:52.653786Z",
"iopub.status.idle": "2022-12-14T22:23:52.713388Z",
"shell.execute_reply": "2022-12-14T22:23:52.712748Z"
},
"id": "04a82ec30b5c"
},
"outputs": [],
"source": [
"class CustomLayer(keras.layers.Layer):\n",
" def __init__(self, units=32, **kwargs):\n",
" super(CustomLayer, self).__init__(**kwargs)\n",
" self.units = units\n",
"\n",
" def build(self, input_shape):\n",
" self.w = self.add_weight(\n",
" shape=(input_shape[-1], self.units),\n",
" initializer=\"random_normal\",\n",
" trainable=True,\n",
" )\n",
" self.b = self.add_weight(\n",
" shape=(self.units,), initializer=\"random_normal\", trainable=True\n",
" )\n",
"\n",
" def call(self, inputs):\n",
" return tf.matmul(inputs, self.w) + self.b\n",
"\n",
" def get_config(self):\n",
" config = super(CustomLayer, self).get_config()\n",
" config.update({\"units\": self.units})\n",
" return config\n",
"\n",
"\n",
"def custom_activation(x):\n",
" return tf.nn.tanh(x) ** 2\n",
"\n",
"\n",
"# Make a model with the CustomLayer and custom_activation\n",
"inputs = keras.Input((32,))\n",
"x = CustomLayer(32)(inputs)\n",
"outputs = keras.layers.Activation(custom_activation)(x)\n",
"model = keras.Model(inputs, outputs)\n",
"\n",
"# Retrieve the config\n",
"config = model.get_config()\n",
"\n",
"# At loading time, register the custom objects with a `custom_object_scope`:\n",
"custom_objects = {\"CustomLayer\": CustomLayer, \"custom_activation\": custom_activation}\n",
"with keras.utils.custom_object_scope(custom_objects):\n",
" new_model = keras.Model.from_config(config)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "13c7f2a1be03"
},
"source": [
"### 인메모리 모델 복제\n",
"\n",
"`tf.keras.models.clone_model()`을 통해 모델의 인메모리 복제를 수행할 수도 있습니다. 이는 구성을 가져온 다음 구성에서 모델을 다시 생성하는 것과 같습니다(따라서 컴파일 정보 또는 레이어 가중치 값을 유지하지 않습니다).\n",
"\n",
"**예제:**"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"execution": {
"iopub.execute_input": "2022-12-14T22:23:52.716722Z",
"iopub.status.busy": "2022-12-14T22:23:52.716464Z",
"iopub.status.idle": "2022-12-14T22:23:52.733226Z",
"shell.execute_reply": "2022-12-14T22:23:52.732629Z"
},
"id": "93056ffe6eb4"
},
"outputs": [],
"source": [
"with keras.utils.custom_object_scope(custom_objects):\n",
" new_model = keras.models.clone_model(model)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "05c91a5a23e3"
},
"source": [
"## 모델의 가중치 값만 저장 및 로딩\n",
"\n",
"모델의 가중치 값만 저장하고 로드하도록 선택할 수 있습니다. 다음과 같은 경우에 유용할 수 있습니다.\n",
"\n",
"- 추론을 위한 모델만 필요합니다. 이 경우 훈련을 다시 시작할 필요가 없으므로 컴파일 정보나 옵티마이저 상태가 필요하지 않습니다.\n",
"- 전이 학습을 수행하고 있습니다. 이 경우 이전 모델의 상태를 재사용하는 새 모델을 훈련하므로 이전 모델의 컴파일 정보가 필요하지 않습니다."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c5229f4014f2"
},
"source": [
"### 인메모리 가중치 전이를 위한 API\n",
"\n",
"`get_weights` 및 `set_weights`를 사용하여 다른 객체 간에 가중치를 복사할 수 있습니다.\n",
"\n",
"- `tf.keras.layers.Layer.get_weights()`: numpy 배열의 리스트를 반환합니다.\n",
"- `tf.keras.layers.Layer.set_weights()`: `weights` 인수 내 값으로 모델의 가중치를 설정합니다.\n",
"\n",
"다음은 예제입니다.\n",
"\n",
"***메모리에서 레이어로 가중치 전이***"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"execution": {
"iopub.execute_input": "2022-12-14T22:23:52.736727Z",
"iopub.status.busy": "2022-12-14T22:23:52.736130Z",
"iopub.status.idle": "2022-12-14T22:23:52.755515Z",
"shell.execute_reply": "2022-12-14T22:23:52.754972Z"
},
"id": "c9124df19cb2"
},
"outputs": [],
"source": [
"def create_layer():\n",
" layer = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")\n",
" layer.build((None, 784))\n",
" return layer\n",
"\n",
"\n",
"layer_1 = create_layer()\n",
"layer_2 = create_layer()\n",
"\n",
"# Copy weights from layer 1 to layer 2\n",
"layer_2.set_weights(layer_1.get_weights())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ff7945516c7d"
},
"source": [
"***메모리에서 호환 가능한 아키텍처를 사용하여 모델 간 가중치 전이하기***"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"execution": {
"iopub.execute_input": "2022-12-14T22:23:52.758866Z",
"iopub.status.busy": "2022-12-14T22:23:52.758412Z",
"iopub.status.idle": "2022-12-14T22:23:52.823728Z",
"shell.execute_reply": "2022-12-14T22:23:52.823081Z"
},
"id": "11005d4023d4"
},
"outputs": [],
"source": [
"# Create a simple functional model\n",
"inputs = keras.Input(shape=(784,), name=\"digits\")\n",
"x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
"x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
"outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
"functional_model = keras.Model(inputs=inputs, outputs=outputs, name=\"3_layer_mlp\")\n",
"\n",
"# Define a subclassed model with the same architecture\n",
"class SubclassedModel(keras.Model):\n",
" def __init__(self, output_dim, name=None):\n",
" super(SubclassedModel, self).__init__(name=name)\n",
" self.output_dim = output_dim\n",
" self.dense_1 = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")\n",
" self.dense_2 = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")\n",
" self.dense_3 = keras.layers.Dense(output_dim, name=\"predictions\")\n",
"\n",
" def call(self, inputs):\n",
" x = self.dense_1(inputs)\n",
" x = self.dense_2(x)\n",
" x = self.dense_3(x)\n",
" return x\n",
"\n",
" def get_config(self):\n",
" return {\"output_dim\": self.output_dim, \"name\": self.name}\n",
"\n",
"\n",
"subclassed_model = SubclassedModel(10)\n",
"# Call the subclassed model once to create the weights.\n",
"subclassed_model(tf.ones((1, 784)))\n",
"\n",
"# Copy weights from functional_model to subclassed_model.\n",
"subclassed_model.set_weights(functional_model.get_weights())\n",
"\n",
"assert len(functional_model.weights) == len(subclassed_model.weights)\n",
"for a, b in zip(functional_model.weights, subclassed_model.weights):\n",
" np.testing.assert_allclose(a.numpy(), b.numpy())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "bd4d08bff725"
},
"source": [
"***상태 비저장 레이어의 경우***\n",
"\n",
"상태 비저장 레이어는 순서 또는 가중치 수를 변경하지 않기 때문에 상태 비저장 레이어가 남거나 없더라도 모델은 호환 가능한 아키텍처를 가질 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"execution": {
"iopub.execute_input": "2022-12-14T22:23:52.827149Z",
"iopub.status.busy": "2022-12-14T22:23:52.826681Z",
"iopub.status.idle": "2022-12-14T22:23:52.887915Z",
"shell.execute_reply": "2022-12-14T22:23:52.887349Z"
},
"id": "927dc7934d44"
},
"outputs": [],
"source": [
"inputs = keras.Input(shape=(784,), name=\"digits\")\n",
"x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
"x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
"outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
"functional_model = keras.Model(inputs=inputs, outputs=outputs, name=\"3_layer_mlp\")\n",
"\n",
"inputs = keras.Input(shape=(784,), name=\"digits\")\n",
"x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
"x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
"\n",
"# Add a dropout layer, which does not contain any weights.\n",
"x = keras.layers.Dropout(0.5)(x)\n",
"outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
"functional_model_with_dropout = keras.Model(\n",
" inputs=inputs, outputs=outputs, name=\"3_layer_mlp\"\n",
")\n",
"\n",
"functional_model_with_dropout.set_weights(functional_model.get_weights())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "199e984872d3"
},
"source": [
"### 디스크에 가중치를 저장하고 다시 로딩하기 위한 API\n",
"\n",
"다음 형식으로 `model.save_weights`를 호출하여 디스크에 가중치를 저장할 수 있습니다.\n",
"\n",
"- TensorFlow Checkpoint\n",
"- HDF5\n",
"\n",
"`model.save_weights`의 기본 형식은 TensorFlow 체크포인트입니다. 저장 형식을 지정하는 두 가지 방법이 있습니다.\n",
"\n",
"1. `save_format` 인수: `save_format=\"tf\"` 또는 `save_format=\"h5\"`에 값을 설정합니다.\n",
"2. `path` 인수: 경로가 `.h5` 또는 `.hdf5`로 끝나면 HDF5 형식이 사용됩니다. `save_format`을 설정하지 않으면 다른 접미어의 경우 TensorFlow 체크포인트로 결과가 발생합니다.\n",
"\n",
"인메모리 numpy 배열로 가중치를 검색하는 옵션도 있습니다. 각 API에는 장단점이 있으며 아래에서 자세히 설명합니다."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3505dc65d6c1"
},
"source": [
"### TF Checkpoint 형식\n",
"\n",
"**예제:**"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"execution": {
"iopub.execute_input": "2022-12-14T22:23:52.891446Z",
"iopub.status.busy": "2022-12-14T22:23:52.890982Z",
"iopub.status.idle": "2022-12-14T22:23:52.943249Z",
"shell.execute_reply": "2022-12-14T22:23:52.942620Z"
},
"id": "f92053377391"
},
"outputs": [
{
"data": {
"text/plain": [
"