#@title Licensed under the Apache License, Version 2.0
# 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.

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


!pip install -U tensorflow-addons

import io
import numpy as np

import tensorflow as tf
import tensorflow_addons as tfa
import tensorflow_datasets as tfds

"""
Explanation: TensorFlow 애드온 손실: TripletSemiHardLoss
<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https://www.tensorflow.org/addons/tutorials/losses_triplet"><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/addons/tutorials/losses_triplet.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/addons/tutorials/losses_triplet.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/addons/tutorials/losses_triplet.ipynb"><img src="https://www.tensorflow.org/images/download_logo_32px.png">노트북 다운로드하기</a></td>
</table>

개요
이 노트북은 TensorFlow 애드온에서 TripletSemiHardLoss 함수를 사용하는 방법을 보여줍니다.
리소스:

FaceNet : 얼굴 인식 및 클러스터링을위한 통합 임베딩
Oliver Moindrot의 블로그는 알고리즘을 자세히 설명하는 훌륭한 작업을 수행합니다.

TripletLoss
FaceNet 논문에 처음 소개된 TripletLoss는 신경망을 훈련하여 같은 클래스의 특성을 밀접하게 포함하면서 서로 다른 클래스의 임베딩 간의 거리를 최대화합니다. 이를 위해 하나의 음수 샘플과 하나의 양수 샘플과 함께 앵커가 선택됩니다. 
손실 함수는 Euclidean 거리 함수로 설명됩니다.

A는 앵커 입력이고, P는 양의 샘플 입력이고, N은 음의 샘플 입력이며, 알파는 트리플릿이 너무 "쉬워지는" 시점을 지정하기 위해 사용하는 한계입니다. .
SemiHard 온라인 학습
이 논문에서 볼 수 있듯이 가장 좋은 결과는 "Semi-Hard"로 알려진 트리플릿에서 얻습니다. 트리플릿은 음이 양보다 앵커에서 더 멀리 있는 트리플릿으로 정의되지만, 여전히 양의 손실을 생성합니다. 이러한 트리플릿을 효율적으로 찾기 위해 온라인 학습을 활용하고 각 배치에서 Semi-Hard 예제를 통해서만 훈련합니다.
설정
End of explanation
"""


def _normalize_img(img, label):
    img = tf.cast(img, tf.float32) / 255.
    return (img, label)

train_dataset, test_dataset = tfds.load(name="mnist", split=['train', 'test'], as_supervised=True)

# Build your input pipelines
train_dataset = train_dataset.shuffle(1024).batch(32)
train_dataset = train_dataset.map(_normalize_img)

test_dataset = test_dataset.batch(32)
test_dataset = test_dataset.map(_normalize_img)

"""
Explanation: 데이터 준비하기
End of explanation
"""


model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(filters=64, kernel_size=2, padding='same', activation='relu', input_shape=(28,28,1)),
    tf.keras.layers.MaxPooling2D(pool_size=2),
    tf.keras.layers.Dropout(0.3),
    tf.keras.layers.Conv2D(filters=32, kernel_size=2, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=2),
    tf.keras.layers.Dropout(0.3),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(256, activation=None), # No activation on final dense layer
    tf.keras.layers.Lambda(lambda x: tf.math.l2_normalize(x, axis=1)) # L2 normalize embeddings

])

"""
Explanation: 모델 빌드하기
End of explanation
"""


# Compile the model
model.compile(
    optimizer=tf.keras.optimizers.Adam(0.001),
    loss=tfa.losses.TripletSemiHardLoss())


# Train the network
history = model.fit(
    train_dataset,
    epochs=5)

# Evaluate the network
results = model.predict(test_dataset)

# Save test embeddings for visualization in projector
np.savetxt("vecs.tsv", results, delimiter='\t')

out_m = io.open('meta.tsv', 'w', encoding='utf-8')
for img, labels in tfds.as_numpy(test_dataset):
    [out_m.write(str(x) + "\n") for x in labels]
out_m.close()


try:
  from google.colab import files
  files.download('vecs.tsv')
  files.download('meta.tsv')
except:
  pass

"""
Explanation: 훈련 및 평가하기
End of explanation
"""


