{ "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", " \n", "
TensorFlow.org에서 보기 Google Colab에서 실행하기\n", "GitHub에서 소스 보기노트북 다운로드하기
" ] }, { "cell_type": "markdown", "metadata": { "id": "a6e70d8d6fa8" }, "source": [ "## 소개\n", "\n", "Keras 모델은 다중 구성 요소로 이루어집니다.\n", "\n", "- 모델에 포함된 레이어 및 레이어의 연결 방법을 지정하는 아키텍처 또는 구성\n", "- 가중치 값의 집합(\"모델의 상태\")\n", "- 옵티마이저(모델을 컴파일하여 정의)\n", "- from tensorflow import keras model = keras.models.load_model('path/to/location')
\n", "\n", "Keras API를 사용하면 이러한 조각을 한 번에 디스크에 저장하거나 선택적으로 일부만 저장할 수 있습니다.\n", "\n", "- TensorFlow SavedModel 형식(또는 이전 Keras H5 형식)으로 모든 것을 단일 아카이브에 저장합니다. 이것이 표준 관행입니다.\n", "- 일반적으로 JSON 파일로 아키텍처 및 구성만 저장합니다.\n", "- 가중치 값만 저장합니다. 이것은 일반적으로 모델을 훈련할 때 사용됩니다.\n", "\n", "이제 각각의 옵션을 살펴보겠습니다. 언제 사용하고 어떻게 사용해야 할까요?" ] }, { "cell_type": "markdown", "metadata": { "id": "ff15300e41fe" }, "source": [ "## 모델을 저장하고 로드하는 방법\n", "\n", "다음은 이 가이드를 읽는데 10초 밖에 없는 경우 알아야 할 사항입니다.\n", "\n", "**Keras 모델 저장하기**\n", "\n", "```python\n", "model = ... # Get model (Sequential, Functional Model, or Model subclass)\n", "model.save('path/to/location')\n", "```\n", "\n", "**모델을 다시 로딩하기**\n", "\n", "```python\n", "from tensorflow import keras\n", "model = keras.models.load_model('path/to/location')\n", "```\n", "\n", "이제 세부 사항을 확인해봅시다." ] }, { "cell_type": "markdown", "metadata": { "id": "41fbd6a3290a" }, "source": [ "## 설치하기" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:42.966187Z", "iopub.status.busy": "2022-12-14T22:23:42.965722Z", "iopub.status.idle": "2022-12-14T22:23:44.887939Z", "shell.execute_reply": "2022-12-14T22:23:44.887253Z" }, "id": "abff67cc7505" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "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\n", "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\n", "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.\n" ] } ], "source": [ "import numpy as np\n", "import tensorflow as tf\n", "from tensorflow import keras" ] }, { "cell_type": "markdown", "metadata": { "id": "438511b14a90" }, "source": [ "## 전체 모델 저장 및 로딩\n", "\n", "전체 모델을 단일 아티팩트로 저장할 수 있습니다. 다음을 포함합니다.\n", "\n", "- 모델의 아키텍처 및 구성\n", "- 훈련 중에 학습된 모델의 가중치 값\n", "- 모델의 컴파일 정보(`compile()`이 호출된 경우)\n", "- 존재하는 옵티마이저와 그 상태(훈련을 중단한 곳에서 다시 시작할 수 있게 해줌)\n", "\n", "#### API\n", "\n", "- `model.save()` 또는 `tf.keras.models.save_model()`\n", "- `tf.keras.models.load_model()`\n", "\n", "전체 모델을 디스크에 저장하는 데 사용할 수 있는 두 형식은 **TensorFlow SavedModel 형식**과 **이전 Keras H5 형식**입니다. 권장하는 형식은 SavedModel입니다. 이는 `model.save()`를 사용할 때의 기본값입니다.\n", "\n", "다음을 통해 H5 형식으로 전환할 수 있습니다.\n", "\n", "- `save_format='h5'`를 `save()`로 전달합니다.\n", "- `.h5` 또는 `.keras`로 끝나는 파일명을 `save()`로 전달합니다." ] }, { "cell_type": "markdown", "metadata": { "id": "812f19d9dc7c" }, "source": [ "### SavedModel 형식\n", "\n", "SavedModel은 모델 아키텍처, 가중치 및 호출 함수에서 추적된 Tensorflow 하위 그래프를 저장하는 보다 포괄적인 저장 형식입니다. Keras는 이를 통해 내장 레이어와 사용자 정의 객체를 모두 복원할 수 있습니다.\n", "\n", "**예제:**" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:44.892265Z", "iopub.status.busy": "2022-12-14T22:23:44.891875Z", "iopub.status.idle": "2022-12-14T22:23:50.449766Z", "shell.execute_reply": "2022-12-14T22:23:50.448897Z" }, "id": "4d910eb33378" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 2s - loss: 0.3190" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 1s 4ms/step - loss: 0.2751\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: my_model/assets\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 0s" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 0s 2ms/step\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 0s" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 0s 2ms/step\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 1s - loss: 0.3790" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 0s 3ms/step - loss: 0.2736\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def get_model():\n", " # Create a simple model.\n", " inputs = keras.Input(shape=(32,))\n", " outputs = keras.layers.Dense(1)(inputs)\n", " model = keras.Model(inputs, outputs)\n", " model.compile(optimizer=\"adam\", loss=\"mean_squared_error\")\n", " return model\n", "\n", "\n", "model = get_model()\n", "\n", "# Train the model.\n", "test_input = np.random.random((128, 32))\n", "test_target = np.random.random((128, 1))\n", "model.fit(test_input, test_target)\n", "\n", "# Calling `save('my_model')` creates a SavedModel folder `my_model`.\n", "model.save(\"my_model\")\n", "\n", "# It can be used to reconstruct the model identically.\n", "reconstructed_model = keras.models.load_model(\"my_model\")\n", "\n", "# Let's check:\n", "np.testing.assert_allclose(\n", " model.predict(test_input), reconstructed_model.predict(test_input)\n", ")\n", "\n", "# The reconstructed model is already compiled and has retained the optimizer\n", "# state, so training can resume:\n", "reconstructed_model.fit(test_input, test_target)" ] }, { "cell_type": "markdown", "metadata": { "id": "3f8e96c8a949" }, "source": [ "#### SavedModel이 포함하는 것\n", "\n", "`model.save('my_model')`을 호출하면 다음을 포함하는 `my_model` 폴더를 생성합니다." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:50.453742Z", "iopub.status.busy": "2022-12-14T22:23:50.452992Z", "iopub.status.idle": "2022-12-14T22:23:50.628475Z", "shell.execute_reply": "2022-12-14T22:23:50.627440Z" }, "id": "47be41998ac5" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "assets\tfingerprint.pb\tkeras_metadata.pb saved_model.pb variables\r\n" ] } ], "source": [ "!ls my_model" ] }, { "cell_type": "markdown", "metadata": { "id": "ead32a4345d1" }, "source": [ "모델 아키텍처 및 훈련 구성(옵티마이저, 손실, 메트릭 등)은 `saved_model.pb`에 저장됩니다. 가중치는 `variables/` 디렉터리에 저장됩니다.\n", "\n", "SavedModel 형식에 대한 자세한 내용은 [SavedModel 가이드(*디스크의 SavedModel 형식*](https://www.tensorflow.org/guide/saved_model#the_savedmodel_format_on_disk))를 참조하세요.\n", "\n", "#### SavedModel이 사용자 정의 객체를 처리하는 방법\n", "\n", "모델과 모델의 레이어를 저장할 때 SavedModel 형식은 클래스명, **호출 함수**, 손실 및 가중치(구현된 경우에는 구성도 포함)를 저장합니다. 호출 함수는 모델/레이어의 계산 그래프를 정의합니다.\n", "\n", "모델/레이어 구성이 없는 경우 호출 함수는 훈련, 평가 및 추론에 사용될 수 있는 기존 모델과 같은 모델을 만드는 데 사용됩니다.\n", "\n", "그럼에도 불구하고 사용자 정의 모델 또는 레이어 클래스를 작성할 때 항상 `get_config` 및 `from_config` 메서드를 정의하는 것이 좋습니다. 이를 통해 필요한 경우 나중에 계산을 쉽게 업데이트할 수 있습니다. 자세한 내용은 [사용자 정의 객체](#custom-objects)에 대한 섹션을 참조하세요.\n", "\n", "예제:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:50.632800Z", "iopub.status.busy": "2022-12-14T22:23:50.632085Z", "iopub.status.idle": "2022-12-14T22:23:51.237390Z", "shell.execute_reply": "2022-12-14T22:23:51.236559Z" }, "id": "28bbf9f611d6" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: my_model/assets\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Original model: <__main__.CustomModel object at 0x7f55682e3820>\n", "Model Loaded with custom objects: <__main__.CustomModel object at 0x7f56e0e9a820>\n", "Model loaded without the custom object class: \n" ] } ], "source": [ "class CustomModel(keras.Model):\n", " def __init__(self, hidden_units):\n", " super(CustomModel, self).__init__()\n", " self.hidden_units = hidden_units\n", " self.dense_layers = [keras.layers.Dense(u) for u in hidden_units]\n", "\n", " def call(self, inputs):\n", " x = inputs\n", " for layer in self.dense_layers:\n", " x = layer(x)\n", " return x\n", "\n", " def get_config(self):\n", " return {\"hidden_units\": self.hidden_units}\n", "\n", " @classmethod\n", " def from_config(cls, config):\n", " return cls(**config)\n", "\n", "\n", "model = CustomModel([16, 16, 10])\n", "# Build the model by calling it\n", "input_arr = tf.random.uniform((1, 5))\n", "outputs = model(input_arr)\n", "model.save(\"my_model\")\n", "\n", "# Option 1: Load with the custom_object argument.\n", "loaded_1 = keras.models.load_model(\n", " \"my_model\", custom_objects={\"CustomModel\": CustomModel}\n", ")\n", "\n", "# Option 2: Load without the CustomModel class.\n", "\n", "# Delete the custom-defined model class to ensure that the loader does not have\n", "# access to it.\n", "del CustomModel\n", "\n", "loaded_2 = keras.models.load_model(\"my_model\")\n", "np.testing.assert_allclose(loaded_1(input_arr), outputs)\n", "np.testing.assert_allclose(loaded_2(input_arr), outputs)\n", "\n", "print(\"Original model:\", model)\n", "print(\"Model Loaded with custom objects:\", loaded_1)\n", "print(\"Model loaded without the custom object class:\", loaded_2)\n" ] }, { "cell_type": "markdown", "metadata": { "id": "ba7b964eb364" }, "source": [ "첫 번째로 로드한 모델은 config 및 `CustomModel` 클래스를 사용하여 로드됩니다. 두 번째 모델은 원래 모델처럼 작동하는 모델 클래스를 동적으로 생성하여 로드됩니다." ] }, { "cell_type": "markdown", "metadata": { "id": "516e30bfbe10" }, "source": [ "#### SavedModel 구성하기\n", "\n", "*TensoFlow 2.4의 새로운 기능* `save_traces` 인수가 `model.save`에 추가되어 SavedModel 함수 추적을 토글할 수 있습니다. 원래 클래스의 정의가 없어도 Keras가 사용자 정의 객체를 다시 로드할 수 있도록 함수가 저장되므로 `save_traces=False`일 때 모든 사용자 정의 객체는 정의된 `get_config`/`from_config` 메서드를 가져야 합니다. 로드할 때 사용자 정의 객체를 `custom_objects` 인수에 전달해야 합니다. `save_traces=False`는 SavedModel 이 사용하는 디스크 공간을 줄이고 시간을 절약합니다." ] }, { "cell_type": "markdown", "metadata": { "id": "71d9e6b3d6af" }, "source": [ "### Keras H5 형식\n", "\n", "또한 Keras는 모델의 아키텍처, 가중치 값, `compile()` 정보를 포함하는 단일 HDF5 파일 저장을 지원합니다. 이는 SavedModel의 경량 대안입니다.\n", "\n", "**예제:**" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:51.241539Z", "iopub.status.busy": "2022-12-14T22:23:51.240877Z", "iopub.status.idle": "2022-12-14T22:23:52.250991Z", "shell.execute_reply": "2022-12-14T22:23:52.250235Z" }, "id": "1ae0912f6f9b" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 1s - loss: 0.5948" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 0s 3ms/step - loss: 0.5795\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 0s" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 0s 2ms/step\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 0s" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 0s 1ms/step\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/4 [======>.......................] - ETA: 0s - loss: 0.6691" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "4/4 [==============================] - 0s 3ms/step - loss: 0.5160\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "model = get_model()\n", "\n", "# Train the model.\n", "test_input = np.random.random((128, 32))\n", "test_target = np.random.random((128, 1))\n", "model.fit(test_input, test_target)\n", "\n", "# Calling `save('my_model.h5')` creates a h5 file `my_model.h5`.\n", "model.save(\"my_h5_model.h5\")\n", "\n", "# It can be used to reconstruct the model identically.\n", "reconstructed_model = keras.models.load_model(\"my_h5_model.h5\")\n", "\n", "# Let's check:\n", "np.testing.assert_allclose(\n", " model.predict(test_input), reconstructed_model.predict(test_input)\n", ")\n", "\n", "# The reconstructed model is already compiled and has retained the optimizer\n", "# state, so training can resume:\n", "reconstructed_model.fit(test_input, test_target)" ] }, { "cell_type": "markdown", "metadata": { "id": "116bd1b1b215" }, "source": [ "#### 한계\n", "\n", "SavedModel 형식과 비교하여 H5 파일에 포함되지 않은 두 가지가 있습니다.\n", "\n", "- `model.add_loss()` 및 `model.add_metric()`을 통해 추가된 **외부 손실 및 메트릭**은 저장되지 않습니다(SavedModel과 다름). 모델에 이러한 솔실 및 메트릭이 있고 훈련을 다시 시작하려면 모델을 로드한 후 이러한 손실을 다시 추가해야 합니다. 이는 `self.add_loss()` 및 `self.add_metric()`을 통해 레이어 *내부*에서 생성한 손실/메트릭에는 적용되지 않습니다. 이러한 손실 및 메트릭은 레이어가 로드되는 한 레이어의 `call` 메서드의 일부이기 때문에 계속 유지됩니다.\n", "- 사용자 정의 레이어와 같은 **사용자 정의 객체의 계산 그래프**는 저장 파일에 포함되지 않습니다. 로드 시 Keras는 모델을 다시 구성하기 위해 이러한 객체의 Python 클래스/함수에 액세스해야 합니다. [사용자 정의 객체](#custom-objects)를 참고하세요.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "bf78706009bf" }, "source": [ "## 아키텍처 저장\n", "\n", "모델의 구성(또는 아키텍처)은 모델에 포함된 레이어와 이러한 레이어의 연결 방법*을 지정합니다. 모델 구성이 있는 경우 가중치에 대해 새로 초기화된 상태로 컴파일 정보 없이 모델을 작성할 수 있습니다.\n", "\n", "*이 기능은 서브 클래스 모델이 아닌 함수형 또는 Sequential API를 사용하여 정의된 모델에만 적용됩니다." ] }, { "cell_type": "markdown", "metadata": { "id": "58a708dbb5da" }, "source": [ "### 순차형 모델 또는 함수형 API 모델의 구성\n", "\n", "이러한 유형의 모델은 레이어의 명시적 그래프입니다. 구성은 항상 구조화된 형식으로 제공됩니다.\n", "\n", "#### API\n", "\n", "- `get_config()` 및 `from_config()`\n", "- `tf.keras.models.model_to_json()` 및 `tf.keras.models.model_from_json()`" ] }, { "cell_type": "markdown", "metadata": { "id": "3d8b20812b50" }, "source": [ "#### `get_config()` 및 `from_config()`\n", "\n", "`config = model.get_config()`를 호출하면 모델의 구성을 포함하는 Python 사전을 반환합니다. 그런 다음 `Sequential.from_config(config)`(`Sequential` 모델의 경우) 또는 `Model.from_config(config)`(함수형 API 모델의 경우)를 통해 동일한 모델을 다시 구성할 수 있습니다.\n", "\n", "동일한 워크플로가 직렬화할 수 있는 모든 레이어에서도 작동합니다.\n", "\n", "**레이어 예제:**" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:52.254891Z", "iopub.status.busy": "2022-12-14T22:23:52.254239Z", "iopub.status.idle": "2022-12-14T22:23:52.259809Z", "shell.execute_reply": "2022-12-14T22:23:52.259155Z" }, "id": "4f26b94e879a" }, "outputs": [], "source": [ "layer = keras.layers.Dense(3, activation=\"relu\")\n", "layer_config = layer.get_config()\n", "new_layer = keras.layers.Dense.from_config(layer_config)" ] }, { "cell_type": "markdown", "metadata": { "id": "a7e5dd2a439c" }, "source": [ "**Sequential 모델 예제:**" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:52.263011Z", "iopub.status.busy": "2022-12-14T22:23:52.262511Z", "iopub.status.idle": "2022-12-14T22:23:52.288910Z", "shell.execute_reply": "2022-12-14T22:23:52.288194Z" }, "id": "ae0842be8a2a" }, "outputs": [], "source": [ "model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])\n", "config = model.get_config()\n", "new_model = keras.Sequential.from_config(config)" ] }, { "cell_type": "markdown", "metadata": { "id": "1e97ca5f73d7" }, "source": [ "**Functional 모델 예제:**" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:52.292807Z", "iopub.status.busy": "2022-12-14T22:23:52.292210Z", "iopub.status.idle": "2022-12-14T22:23:52.318084Z", "shell.execute_reply": "2022-12-14T22:23:52.317426Z" }, "id": "da001f34e412" }, "outputs": [], "source": [ "inputs = keras.Input((32,))\n", "outputs = keras.layers.Dense(1)(inputs)\n", "model = keras.Model(inputs, outputs)\n", "config = model.get_config()\n", "new_model = keras.Model.from_config(config)" ] }, { "cell_type": "markdown", "metadata": { "id": "d7c08fae3eef" }, "source": [ "#### `to_json()` 및 `tf.keras.models.model_from_json()`\n", "\n", "이것은 `get_config` / `from_config`와 비슷하지만, 모델을 JSON 문자열로 변환한 다음 기존 모델 클래스 없이 로드할 수 있습니다. 또한, 모델에만 해당하며 레이어용이 아닙니다.\n", "\n", "**예제:**" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:52.321944Z", "iopub.status.busy": "2022-12-14T22:23:52.321336Z", "iopub.status.idle": "2022-12-14T22:23:52.347354Z", "shell.execute_reply": "2022-12-14T22:23:52.346733Z" }, "id": "12885447bd35" }, "outputs": [], "source": [ "model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])\n", "json_config = model.to_json()\n", "new_model = keras.models.model_from_json(json_config)" ] }, { "cell_type": "markdown", "metadata": { "id": "edcae4bf461c" }, "source": [ "### 사용자 정의 객체\n", "\n", "**모델 및 레이어**\n", "\n", "서브 클래스 모델과 레이어의 아키텍처는 `__init__` 및 `call` 메서드에 정의되어 있습니다. 그것들은 Python 바이트 코드로 간주하며 JSON 호환 구성으로 직렬화할 수 없습니다 -- 바이트 코드 직렬화를 시도할 수는 있지만(예: `pickle`을 통해) 완전히 불안전하므로 모델을 다른 시스템에 로드할 수 없습니다.\n", "\n", "사용자 정의 레이어를 사용하는 모델 또는 서브 클래스 모델을 저장/로드하려면 `get_config` 및 선택적으로 `from_config` 메서드를 덮어써야 합니다. 또한 Keras가 인식할 수 있도록 사용자 정의 객체를 등록해야 합니다.\n", "\n", "**사용자 정의 함수**\n", "\n", "사용자 정의 함수(예: 활성화 손실 또는 초기화)에는 `get_config` 메서드가 필요하지 않습니다. 함수명은 사용자 정의 객체로 등록되어 있는 한 로드하기에 충분합니다.\n", "\n", "**TensorFlow 그래프만 로딩하기**\n", "\n", "Keras가 생성한 TensorFlow 그래프를 로드할 수 있습니다. 그렇게 하면 `custom_objects`를 제공할 필요가 없습니다. 다음과 같이 해볼 수 있습니다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:52.351026Z", "iopub.status.busy": "2022-12-14T22:23:52.350530Z", "iopub.status.idle": "2022-12-14T22:23:52.636042Z", "shell.execute_reply": "2022-12-14T22:23:52.635389Z" }, "id": "1651c6825106" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "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.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: my_model/assets\n" ] } ], "source": [ "model.save(\"my_model\")\n", "tensorflow_graph = tf.saved_model.load(\"my_model\")\n", "x = np.random.uniform(size=(4, 32)).astype(np.float32)\n", "predicted = tensorflow_graph(x).numpy()" ] }, { "cell_type": "markdown", "metadata": { "id": "b15faa16734b" }, "source": [ "이 메서드에는 몇 가지 단점이 있습니다.\n", "\n", "- 추적 가능성을 위해 사용된 사용자 정의 객체에 항상 접근할 수 있어야 합니다. 다시 만들 수 없는 모델을 제품에 넣고 싶지 않을 것입니다.\n", "- `tf.saved_model.load`에 의해 반환된 객체는 Keras 모델이 아닙니다. 따라서 사용하기가 쉽지 않습니다. 예를 들면, `.predict()` 또는 `.fit()`에 접근할 수 없습니다.\n", "\n", "사용을 권장하지는 않지만, 사용자 정의 객체의 코드를 잃어버렸거나 `tf.keras.models.load_model()` 모델을 로드하는 데 문제가 있는 경우와 같이 곤란한 상황에서는 도움이 될 수 있습니다.\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": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Runnable example\n", "sequential_model = keras.Sequential(\n", " [\n", " keras.Input(shape=(784,), name=\"digits\"),\n", " keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\"),\n", " keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\"),\n", " keras.layers.Dense(10, name=\"predictions\"),\n", " ]\n", ")\n", "sequential_model.save_weights(\"ckpt\")\n", "load_status = sequential_model.load_weights(\"ckpt\")\n", "\n", "# `assert_consumed` can be used as validation that all variable values have been\n", "# restored from the checkpoint. See `tf.train.Checkpoint.restore` for other\n", "# methods in the Status object.\n", "load_status.assert_consumed()" ] }, { "cell_type": "markdown", "metadata": { "id": "87f1145ac846" }, "source": [ "#### 형식 세부 사항\n", "\n", "TensorFlow Checkpoint 형식은 객체 속성명을 사용하여 가중치를 저장하고 복원합니다. 예를 들어, `tf.keras.layers.Dense` 레이어를 고려해 봅시다. 레이어에는 `dense.kernel`과 `dense.bias` 두 가지 가중치가 있습니다. 레이어가 `tf` 형식으로 저장되면 결과 체크포인트에는 `\"kernel\"` 및 `\"bias\"`와 해당 가중치 값이 포함됩니다. 자세한 정보는 [TF Checkpoint 가이드의 \"로딩 메커니즘\"](https://www.tensorflow.org/guide/checkpoint#loading_mechanics)을 참조하세요.\n", "\n", "속성/그래프 에지는 **변수명이 아니라 부모 객체에서 사용된 이름**에 따라 이름이 지정됩니다. 아래 예제의 `CustomLayer`를 고려해 봅시다. 변수 `CustomLayer.var`는 `\"var_a\"`가 아니라, 키의 일부로서 `\"var\"`로 저장됩니다." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:52.946723Z", "iopub.status.busy": "2022-12-14T22:23:52.946126Z", "iopub.status.idle": "2022-12-14T22:23:52.964382Z", "shell.execute_reply": "2022-12-14T22:23:52.963772Z" }, "id": "c919189b3697" }, "outputs": [ { "data": { "text/plain": [ "{'save_counter/.ATTRIBUTES/VARIABLE_VALUE': tf.int64,\n", " 'layer/var/.ATTRIBUTES/VARIABLE_VALUE': tf.int32,\n", " '_CHECKPOINTABLE_OBJECT_GRAPH': tf.string}" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class CustomLayer(keras.layers.Layer):\n", " def __init__(self, a):\n", " self.var = tf.Variable(a, name=\"var_a\")\n", "\n", "\n", "layer = CustomLayer(5)\n", "layer_ckpt = tf.train.Checkpoint(layer=layer).save(\"custom_layer\")\n", "\n", "ckpt_reader = tf.train.load_checkpoint(layer_ckpt)\n", "\n", "ckpt_reader.get_variable_to_dtype_map()" ] }, { "cell_type": "markdown", "metadata": { "id": "c4e5a7162b13" }, "source": [ "#### 전송 훈련 예제\n", "\n", "기본적으로 두 모델이 동일한 아키텍처를 갖는 한 동일한 검사점을 공유할 수 있습니다.\n", "\n", "**예제:**" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:52.967668Z", "iopub.status.busy": "2022-12-14T22:23:52.967146Z", "iopub.status.idle": "2022-12-14T22:23:53.120041Z", "shell.execute_reply": "2022-12-14T22:23:53.119478Z" }, "id": "78d08199d27f" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model: \"pretrained_model\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Layer (type) Output Shape Param # \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " digits (InputLayer) [(None, 784)] 0 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_1 (Dense) (None, 64) 50240 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_2 (Dense) (None, 64) 4160 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Total params: 54,400\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Trainable params: 54,400\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Non-trainable params: 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", " --------------------------------------------------\n", "Model: \"new_model\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Layer (type) Output Shape Param # \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " digits (InputLayer) [(None, 784)] 0 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_1 (Dense) (None, 64) 50240 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_2 (Dense) (None, 64) 4160 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " predictions (Dense) (None, 5) 325 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Total params: 54,725\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Trainable params: 54,725\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Non-trainable params: 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Model: \"sequential_3\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Layer (type) Output Shape Param # \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " pretrained (Functional) (None, 64) 54400 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " predictions (Dense) (None, 5) 325 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Total params: 54,725\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Trainable params: 54,725\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Non-trainable params: 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "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", "# Extract a portion of the functional model defined in the Setup section.\n", "# The following lines produce a new model that excludes the final output\n", "# layer of the functional model.\n", "pretrained = keras.Model(\n", " functional_model.inputs, functional_model.layers[-1].input, name=\"pretrained_model\"\n", ")\n", "# Randomly assign \"trained\" weights.\n", "for w in pretrained.weights:\n", " w.assign(tf.random.normal(w.shape))\n", "pretrained.save_weights(\"pretrained_ckpt\")\n", "pretrained.summary()\n", "\n", "# Assume this is a separate program where only 'pretrained_ckpt' exists.\n", "# Create a new functional model with a different output dimension.\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(5, name=\"predictions\")(x)\n", "model = keras.Model(inputs=inputs, outputs=outputs, name=\"new_model\")\n", "\n", "# Load the weights from pretrained_ckpt into model.\n", "model.load_weights(\"pretrained_ckpt\")\n", "\n", "# Check that all of the pretrained weights have been loaded.\n", "for a, b in zip(pretrained.weights, model.weights):\n", " np.testing.assert_allclose(a.numpy(), b.numpy())\n", "\n", "print(\"\\n\", \"-\" * 50)\n", "model.summary()\n", "\n", "# Example 2: Sequential model\n", "# Recreate the pretrained model, and load the saved weights.\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", "pretrained_model = keras.Model(inputs=inputs, outputs=x, name=\"pretrained\")\n", "\n", "# Sequential example:\n", "model = keras.Sequential([pretrained_model, keras.layers.Dense(5, name=\"predictions\")])\n", "model.summary()\n", "\n", "pretrained_model.load_weights(\"pretrained_ckpt\")\n", "\n", "# Warning! Calling `model.load_weights('pretrained_ckpt')` won't throw an error,\n", "# but will *not* work as expected. If you inspect the weights, you'll see that\n", "# none of the weights will have loaded. `pretrained_model.load_weights()` is the\n", "# correct method to call." ] }, { "cell_type": "markdown", "metadata": { "id": "7b07ad5fe5b0" }, "source": [ "일반적으로 모델을 빌드할 때 동일한 API를 사용하는 것이 좋습니다. Sequential 및 Functional 또는 Functional 및 서브 클래스 등 간에 전환하는 경우, 항상 사전 훈련된 모델을 다시 빌드하고 사전 훈련된 가중치를 해당 모델에 로드합니다." ] }, { "cell_type": "markdown", "metadata": { "id": "2ab83c542e2d" }, "source": [ "다음 질문은 모델 아키텍처가 상당히 다른 경우 어떻게 다른 모델에 가중치를 저장하고 로드하는가입니다. 해결책은 `tf.train.Checkpoint`를 사용하여 정확한 레이어/변수를 저장하고 복원하는 것입니다.\n", "\n", "**예제:**" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:53.123563Z", "iopub.status.busy": "2022-12-14T22:23:53.123003Z", "iopub.status.idle": "2022-12-14T22:23:53.157465Z", "shell.execute_reply": "2022-12-14T22:23:53.156854Z" }, "id": "97037b9ea265" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/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.\n", " self.kernel = self.add_variable(\"kernel\", shape=(64, 10))\n", "/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.\n", " self.bias = self.add_variable(\"bias\", shape=(10,))\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create a subclassed model that essentially uses functional_model's first\n", "# and last layers.\n", "# First, save the weights of functional_model's first and last dense layers.\n", "first_dense = functional_model.layers[1]\n", "last_dense = functional_model.layers[-1]\n", "ckpt_path = tf.train.Checkpoint(\n", " dense=first_dense, kernel=last_dense.kernel, bias=last_dense.bias\n", ").save(\"ckpt\")\n", "\n", "# Define the subclassed model.\n", "class ContrivedModel(keras.Model):\n", " def __init__(self):\n", " super(ContrivedModel, self).__init__()\n", " self.first_dense = keras.layers.Dense(64)\n", " self.kernel = self.add_variable(\"kernel\", shape=(64, 10))\n", " self.bias = self.add_variable(\"bias\", shape=(10,))\n", "\n", " def call(self, inputs):\n", " x = self.first_dense(inputs)\n", " return tf.matmul(x, self.kernel) + self.bias\n", "\n", "\n", "model = ContrivedModel()\n", "# Call model on inputs to create the variables of the dense layer.\n", "_ = model(tf.ones((1, 784)))\n", "\n", "# Create a Checkpoint with the same structure as before, and load the weights.\n", "tf.train.Checkpoint(\n", " dense=model.first_dense, kernel=model.kernel, bias=model.bias\n", ").restore(ckpt_path).assert_consumed()" ] }, { "cell_type": "markdown", "metadata": { "id": "18356461e7dd" }, "source": [ "### HDF5 format\n", "\n", "HDF5 형식에는 레이어 이름별로 그룹화된 가중치가 포함됩니다. 가중치는 훈련 가능한 가중치 목록을 훈련 불가능한 가중치 목록(`layer.weights`와 동일)에 연결하여 정렬된 목록입니다. 따라서 모델이 체크포인트에 저장된 것과 동일한 레이어 및 훈련 가능한 상태를 갖는 경우 hdf5 체크포인트을 사용할 수 있습니다.\n", "\n", "**예제:**" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:53.160794Z", "iopub.status.busy": "2022-12-14T22:23:53.160201Z", "iopub.status.idle": "2022-12-14T22:23:53.203747Z", "shell.execute_reply": "2022-12-14T22:23:53.203084Z" }, "id": "43aec1e07913" }, "outputs": [], "source": [ "# Runnable example\n", "sequential_model = keras.Sequential(\n", " [\n", " keras.Input(shape=(784,), name=\"digits\"),\n", " keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\"),\n", " keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\"),\n", " keras.layers.Dense(10, name=\"predictions\"),\n", " ]\n", ")\n", "sequential_model.save_weights(\"weights.h5\")\n", "sequential_model.load_weights(\"weights.h5\")" ] }, { "cell_type": "markdown", "metadata": { "id": "dc63aef6e0d3" }, "source": [ "모델에 중첩된 레이어가 포함된 경우 `layer.trainable`을 변경하면 `layer.weights`의 순서가 다르게 나타날 수 있습니다." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:53.207130Z", "iopub.status.busy": "2022-12-14T22:23:53.206655Z", "iopub.status.idle": "2022-12-14T22:23:53.254331Z", "shell.execute_reply": "2022-12-14T22:23:53.253597Z" }, "id": "83b70826944a" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "variables: ['nested/dense_1/kernel:0', 'nested/dense_1/bias:0', 'nested/dense_2/kernel:0', 'nested/dense_2/bias:0']\n", "\n", "Changing trainable status of one of the nested layers...\n", "\n", "variables: ['nested/dense_2/kernel:0', 'nested/dense_2/bias:0', 'nested/dense_1/kernel:0', 'nested/dense_1/bias:0']\n", "variable ordering changed: True\n" ] } ], "source": [ "class NestedDenseLayer(keras.layers.Layer):\n", " def __init__(self, units, name=None):\n", " super(NestedDenseLayer, self).__init__(name=name)\n", " self.dense_1 = keras.layers.Dense(units, name=\"dense_1\")\n", " self.dense_2 = keras.layers.Dense(units, name=\"dense_2\")\n", "\n", " def call(self, inputs):\n", " return self.dense_2(self.dense_1(inputs))\n", "\n", "\n", "nested_model = keras.Sequential([keras.Input((784,)), NestedDenseLayer(10, \"nested\")])\n", "variable_names = [v.name for v in nested_model.weights]\n", "print(\"variables: {}\".format(variable_names))\n", "\n", "print(\"\\nChanging trainable status of one of the nested layers...\")\n", "nested_model.get_layer(\"nested\").dense_1.trainable = False\n", "\n", "variable_names_2 = [v.name for v in nested_model.weights]\n", "print(\"\\nvariables: {}\".format(variable_names_2))\n", "print(\"variable ordering changed:\", variable_names != variable_names_2)" ] }, { "cell_type": "markdown", "metadata": { "id": "cc261c1a31ee" }, "source": [ "#### 전이 학습 예제\n", "\n", "HDF5에서 사전 훈련된 가중치를 로딩할 때는 가중치를 기존 체크포인트 모델에 로드한 다음 원하는 가중치/레이어를 새 모델로 추출하는 것이 좋습니다.\n", "\n", "**예제:**" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:23:53.257338Z", "iopub.status.busy": "2022-12-14T22:23:53.256845Z", "iopub.status.idle": "2022-12-14T22:23:53.347251Z", "shell.execute_reply": "2022-12-14T22:23:53.346686Z" }, "id": "06cabc31494a" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model: \"sequential_6\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Layer (type) Output Shape Param # \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_1 (Dense) (None, 64) 50240 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_2 (Dense) (None, 64) 4160 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_3 (Dense) (None, 5) 325 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Total params: 54,725\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Trainable params: 54,725\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Non-trainable params: 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] } ], "source": [ "def create_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", " return keras.Model(inputs=inputs, outputs=outputs, name=\"3_layer_mlp\")\n", "\n", "\n", "functional_model = create_functional_model()\n", "functional_model.save_weights(\"pretrained_weights.h5\")\n", "\n", "# In a separate program:\n", "pretrained_model = create_functional_model()\n", "pretrained_model.load_weights(\"pretrained_weights.h5\")\n", "\n", "# Create a new model by extracting layers from the original model:\n", "extracted_layers = pretrained_model.layers[:-1]\n", "extracted_layers.append(keras.layers.Dense(5, name=\"dense_3\"))\n", "model = keras.Sequential(extracted_layers)\n", "model.summary()" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "save_and_serialize.ipynb", "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" } }, "nbformat": 4, "nbformat_minor": 0 }