#@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.

#@title MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

"""
Explanation: Copyright 2019 The TensorFlow Authors.
End of explanation
"""


!pip install pyyaml h5py  # HDF5 포맷으로 모델을 저장하기 위해서 필요합니다

import os

import tensorflow as tf
from tensorflow import keras

print(tf.version.VERSION)

"""
Explanation: 모델 저장과 복원
<table class="tfo-notebook-buttons" align="left">
  <td>     <a target="_blank" href="https://www.tensorflow.org/tutorials/keras/save_and_load"><img src="https://www.tensorflow.org/images/tf_logo_32px.png">TensorFlow.org에서 보기</a>   </td>
  <td><a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/tutorials/keras/save_and_load.ipynb"><img src="https://www.tensorflow.org/images/colab_logo_32px.png">Google Colab에서 실행</a></td>
  <td><a target="_blank" href="https://github.com/tensorflow/docs-l10n/blob/master/site/ko/tutorials/keras/save_and_load.ipynb"><img src="https://www.tensorflow.org/images/GitHub-Mark-32px.png">GitHub에서 소스 보기</a></td>
  <td><a href="https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/tutorials/keras/save_and_load.ipynb"><img src="https://www.tensorflow.org/images/download_logo_32px.png">노트북 다운로드</a></td>
</table>

모델 진행 상황은 훈련 중 및 훈련 후에 저장할 수 있습니다. 즉, 모델이 중단된 위치에서 다시 시작하고 긴 훈련 시간을 피할 수 있습니다. 저장은 또한 모델을 공유할 수 있고 다른 사람들이 작업을 다시 만들 수 있음을 의미합니다. 연구 모델 및 기술을 게시할 때 대부분의 머신러닝 실무자는 다음을 공유합니다.

모델을 만드는 코드
모델의 훈련된 가중치 또는 파라미터

이런 데이터를 공유하면 다른 사람들이 모델의 작동 방식을 이해하고 새로운 데이터로 모델을 실험하는데 도움이 됩니다.
주의: TensorFlow 모델은 코드이며 신뢰할 수 없는 코드에 주의하는 것이 중요합니다. 자세한 내용은 TensorFlow 안전하게 사용하기를 참조하세요.
저장 방식
사용 중인 API에 따라 TensorFlow 모델을 저장하는 다양한 방법이 있습니다. 이 가이드에서는 TensorFlow에서 모델을 빌드하고 훈련하기 위해 고수준 API인 tf.keras를 사용합니다. 다른 접근 방식에 대해서는 TensorFlow 저장 및 복원 가이드 또는 즉시 실행 저장을 참조하세요.
설정
설치와 임포트
필요한 라이브러리를 설치하고 텐서플로를 임포트(import)합니다:
End of explanation
"""


(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()

train_labels = train_labels[:1000]
test_labels = test_labels[:1000]

train_images = train_images[:1000].reshape(-1, 28 * 28) / 255.0
test_images = test_images[:1000].reshape(-1, 28 * 28) / 255.0

"""
Explanation: 예제 데이터셋 받기
MNIST 데이터셋으로 모델을 훈련하여 가중치를 저장하는 예제를 만들어 보겠습니다. 모델 실행 속도를 빠르게 하기 위해 샘플에서 처음 1,000개만 사용겠습니다:
End of explanation
"""


# 간단한 Sequential 모델을 정의합니다
def create_model():
  model = tf.keras.models.Sequential([
    keras.layers.Dense(512, activation='relu', input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10)
  ])

  model.compile(optimizer='adam',
                loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),
                metrics=['accuracy'])

  return model

# 모델 객체를 만듭니다
model = create_model()

# 모델 구조를 출력합니다
model.summary()

"""
Explanation: 모델 정의
먼저 간단한 모델을 하나 만들어 보죠.
End of explanation
"""


checkpoint_path = "training_1/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# 모델의 가중치를 저장하는 콜백 만들기
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
                                                 save_weights_only=True,
                                                 verbose=1)

# 새로운 콜백으로 모델 훈련하기
model.fit(train_images, 
          train_labels,  
          epochs=10,
          validation_data=(test_images,test_labels),
          callbacks=[cp_callback])  # 콜백을 훈련에 전달합니다

# 옵티마이저의 상태를 저장하는 것과 관련되어 경고가 발생할 수 있습니다.
# 이 경고는 (그리고 이 노트북의 다른 비슷한 경고는) 이전 사용 방식을 권장하지 않기 위함이며 무시해도 좋습니다.

"""
Explanation: 훈련하는 동안 체크포인트 저장하기
훈련된 모델을 다시 훈련할 필요 없이 사용하거나 훈련 과정이 중단된 경우 중단한 부분에서 훈련을 다시 시작할 수 있습니다. tf.keras.callbacks.ModelCheckpoint 콜백을 사용하면 훈련 도중 또는 훈련 종료 시 모델을 지속적으로 저장할 수 있습니다.
체크포인트 콜백 사용하기
훈련하는 동안 가중치를 저장하기 위해 ModelCheckpoint 콜백을 만들어 보죠:
End of explanation
"""


os.listdir(checkpoint_dir)

"""
Explanation: 이 코드는 텐서플로 체크포인트 파일을 만들고 에포크가 종료될 때마다 업데이트합니다:
End of explanation
"""


# 기본 모델 객체를 만듭니다
model = create_model()

# 모델을 평가합니다
loss, acc = model.evaluate(test_images,  test_labels, verbose=2)
print("훈련되지 않은 모델의 정확도: {:5.2f}%".format(100*acc))

"""
Explanation: 두 모델이 동일한 아키텍처를 공유하기만 한다면 두 모델 간에 가중치를 공유할 수 있습니다. 따라서 가중치 전용에서 모델을 복원할 때 원래 모델과 동일한 아키텍처로 모델을 만든 다음 가중치를 설정합니다.
이제 훈련되지 않은 새로운 모델을 다시 빌드하고 테스트 세트에서 평가합니다. 훈련되지 않은 모델은 확률 수준(~10% 정확도)에서 수행됩니다.
End of explanation
"""


# 가중치 로드
model.load_weights(checkpoint_path)

# 모델 재평가
loss,acc = model.evaluate(test_images,  test_labels, verbose=2)
print("복원된 모델의 정확도: {:5.2f}%".format(100*acc))

"""
Explanation: 체크포인트에서 가중치를 로드하고 다시 평가해 보죠:
End of explanation
"""


# 파일 이름에 에포크 번호를 포함시킵니다(`str.format` 포맷)
checkpoint_path = "training_2/cp-{epoch:04d}.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# 다섯 번째 에포크마다 가중치를 저장하기 위한 콜백을 만듭니다
cp_callback = tf.keras.callbacks.ModelCheckpoint(
    filepath=checkpoint_path, 
    verbose=1, 
    save_weights_only=True,
    period=5)

# 새로운 모델 객체를 만듭니다
model = create_model()

# `checkpoint_path` 포맷을 사용하는 가중치를 저장합니다
model.save_weights(checkpoint_path.format(epoch=0))

# 새로운 콜백을 사용하여 모델을 훈련합니다
model.fit(train_images, 
          train_labels,
          epochs=50, 
          callbacks=[cp_callback],
          validation_data=(test_images,test_labels),
          verbose=0)

"""
Explanation: 체크포인트 콜백 매개변수
이 콜백 함수는 몇 가지 매개변수를 제공합니다. 체크포인트 이름을 고유하게 만들거나 체크포인트 주기를 조정할 수 있습니다.
새로운 모델을 훈련하고 다섯 번의 에포크마다 고유한 이름으로 체크포인트를 저장해 보겠습니다:
End of explanation
"""


os.listdir(checkpoint_dir)

latest = tf.train.latest_checkpoint(checkpoint_dir)
latest

"""
Explanation: 만들어진 체크포인트를 확인해 보고 마지막 체크포인트를 선택해 보겠습니다:
End of explanation
"""


# 새로운 모델 객체를 만듭니다
model = create_model()

# 이전에 저장한 가중치를 로드합니다
model.load_weights(latest)

# 모델을 재평가합니다
loss, acc = model.evaluate(test_images,  test_labels, verbose=2)
print("복원된 모델의 정확도: {:5.2f}%".format(100*acc))

"""
Explanation: 참고: 기본 TensorFlow 형식은 가장 최근의 체크포인트 5개만 저장합니다.
모델을 초기화하고 최근 체크포인트를 로드하여 테스트해 보겠습니다:
End of explanation
"""


# 가중치를 저장합니다
model.save_weights('./checkpoints/my_checkpoint')

# 새로운 모델 객체를 만듭니다
model = create_model()

# 가중치를 복원합니다
model.load_weights('./checkpoints/my_checkpoint')

# 모델을 평가합니다
loss,acc = model.evaluate(test_images,  test_labels, verbose=2)
print("복원된 모델의 정확도: {:5.2f}%".format(100*acc))

"""
Explanation: 이 파일들은 무엇인가요?
위의 코드는 이진 형식의 훈련된 가중치만 포함하는 체크포인트 형식의 파일 모음에 가중치를 저장합니다. 체크포인트에는 다음이 포함됩니다.

모델의 가중치를 포함하는 하나 이상의 샤드
어떤 가중치가 어떤 샤드에 저장되어 있는지 나타내는 인덱스 파일

단일 머신에서 모델을 훈련하는 경우 접미사가 .data-00000-of-00001인 하나의 샤드를 갖게 됩니다.
수동으로 가중치 저장하기
Model.save_weights 메서드를 사용하여 수동으로 가중치를 저장합니다. 기본적으로 tf.keras, 특히  save_weights는 .ckpt 확장자가 있는 TensorFlow 체크포인트 형식을 사용합니다(.h5 확장자를 사용하여 HDF5에 저장하는 내용은 모델 저장 및 직렬화 가이드에서 다룸).
End of explanation
"""


# 새로운 모델 객체를 만들고 훈련합니다
model = create_model()
model.fit(train_images, train_labels, epochs=5)

# SavedModel로 전체 모델을 저장합니다
!mkdir -p saved_model
model.save('saved_model/my_model') 

"""
Explanation: 전체 모델 저장하기
model.save 메서드를 호출하여 모델의 구조, 가중치, 훈련 설정을 하나의 파일/폴더에 저장합니다. 모델을 저장하기 때문에 원본 파이썬 코드*가 없어도 사용할 수 있습니다. 옵티마이저 상태가 복원되므로 정확히 중지한 시점에서 다시 훈련을 시작할 수 있습니다.
전체 모델은 두 가지 다른 파일 형식(SavedModel 및 HDF5)으로 저장할 수 있습니다. TensorFlow SavedModel 형식은 TF2.x의 기본 파일 형식입니다. 그러나 모델을 HDF5 형식으로 저장할 수 있습니다. 전체 모델을 두 가지 파일 형식으로 저장하는 방법에 대한 자세한 내용은 아래에 설명되어 있습니다.
전체 모델을 저장하는 기능은 매우 유용합니다. TensorFlow.js로 모델을 로드한 다음 웹 브라우저에서 모델을 훈련하고 실행할 수 있습니다(Saved Model, HDF5). 또는 모바일 장치에 맞도록 변환한 다음 TensorFlow Lite를 사용하여 실행할 수 있습니다(Saved Model, HDF5).

사용자 정의 객체(예를 들면 상속으로 만든 클래스나 층)는 저장하고 로드하는데 특별한 주의가 필요합니다. 아래 사용자 정의 객체 저장하기 섹션을 참고하세요. 

SavedModel 포맷
SavedModel 형식은 모델을 직렬화하는 또 다른 방법입니다. 이 형식으로 저장된 모델은 tf.keras.models.load_model을 사용하여 복원할 수 있으며 TensorFlow Serving과 호환됩니다. SavedModel 가이드에 SavedModel을 제공/검사하는 방법이 자세히 설명되어 있습니다. 아래 섹션은 모델을 저장하고 복원하는 단계를 보여줍니다.
End of explanation
"""


# my_model 디렉토리
!ls saved_model

# assests 폴더, saved_model.pb, variables 폴더
!ls saved_model/my_model

"""
Explanation: SavedModel 형식은 protobuf 바이너리와 TensorFlow 체크포인트를 포함하는 디렉토리입니다. 저장된 모델 디렉토리를 검사합니다.
End of explanation
"""


new_model = tf.keras.models.load_model('saved_model/my_model')

# 모델 구조를 확인합니다
new_model.summary()

"""
Explanation: 저장된 모델로부터 새로운 케라스 모델을 로드합니다:
End of explanation
"""


# 복원된 모델을 평가합니다
loss, acc = new_model.evaluate(test_images,  test_labels, verbose=2)
print('복원된 모델의 정확도: {:5.2f}%'.format(100*acc))

print(new_model.predict(test_images).shape)

"""
Explanation: 복원된 모델은 원본 모델과 동일한 매개변수로 컴파일되어 있습니다. 이 모델을 평가하고 예측에 사용해 보죠:
End of explanation
"""


# 새로운 모델 객체를 만들고 훈련합니다
model = create_model()
model.fit(train_images, train_labels, epochs=5)

# 전체 모델을 HDF5 파일로 저장합니다
# '.h5' 확장자는 이 모델이 HDF5로 저장되었다는 것을 나타냅니다
model.save('my_model.h5') 

"""
Explanation: HDF5 파일로 저장하기
케라스는 HDF5 표준을 따르는 기본 저장 포맷을 제공합니다.
End of explanation
"""


# 가중치와 옵티마이저를 포함하여 정확히 동일한 모델을 다시 생성합니다
new_model = tf.keras.models.load_model('my_model.h5')

# 모델 구조를 출력합니다
new_model.summary()

"""
Explanation: 이제 이 파일로부터 모델을 다시 만들어 보죠:
End of explanation
"""


loss, acc = new_model.evaluate(test_images,  test_labels, verbose=2)
print('복원된 모델의 정확도: {:5.2f}%'.format(100*acc))

"""
Explanation: 정확도를 확인해 보겠습니다:
End of explanation
"""


