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

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


import tensorflow as tf

"""
Explanation: Escribir callbacks de Keras personalizados
<table class="tfo-notebook-buttons" align="left">
  <td>
    <a target="_blank" href="https://www.tensorflow.org/guide/keras/custom_callback"><img src="https://www.tensorflow.org/images/tf_logo_32px.png" />Ver en TensorFlow.org</a>
  </td>
  <td>
    <a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/es-419/guide/keras/custom_callback.ipynb"><img src="https://www.tensorflow.org/images/colab_logo_32px.png" />Ejecutar en Google Colab</a>
  </td>
  <td>
    <a target="_blank" href="https://github.com/tensorflow/docs-l10n/blob/master/site/es-419/guide/keras/custom_callback.ipynb"><img src="https://www.tensorflow.org/images/GitHub-Mark-32px.png" />Ver fuente en GitHub</a>
  </td>
  <td>
    <a href="https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/es-419/guide/keras/custom_callback.ipynb"><img src="https://www.tensorflow.org/images/download_logo_32px.png" />Descargar notebook</a>
  </td>
</table>

Note: Nuestra comunidad de Tensorflow ha traducido estos documentos. Como las traducciones de la comunidad
son basados en el "mejor esfuerzo", no hay ninguna garantia que esta sea un reflejo preciso y actual 
de la Documentacion Oficial en Ingles.
Si tienen sugerencias sobre como mejorar esta traduccion, por favor envian un "Pull request"
al siguiente repositorio tensorflow/docs.
Para ofrecerse como voluntario o hacer revision de las traducciones de la Comunidad
por favor contacten al siguiente grupo docs@tensorflow.org list.
Un callback personalizado es una herramienta poderosa para personalizar el comportamiento de un modelo de Keras durante el entrenamiento, evaluacion o inferencia, incluyendo la lectura/cambio del modelo de Keras. Ejemplos incluyen tf.keras.callbacks.TensorBoard, donde se pueden exportar y visualizar el progreso del entrenamiento y los resultados con TensorBoard, o tf.keras.callbacks.ModelCheckpoint donde el modelo es automaticamente guardado durante el entrenamiento, entre otros. En esta guia aprenderas que es un callback de Keras, cuando se llama, que puede hacer y como puedes construir una propia. Al final de la guia habra demos para la creacion de aplicaciones simples de callback para ayudarte a empezar tu propio callback personalizados.
Setup
End of explanation
"""


# Definir el modelo de Keras model al que se le agregaran los callbacks
def get_model():
  model = tf.keras.Sequential()
  model.add(tf.keras.layers.Dense(1, activation = 'linear', input_dim = 784))
  model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=0.1), loss='mean_squared_error', metrics=['mae'])
  return model

"""
Explanation: Introduccion a los callbacks de Keras
En Keras 'Callback' es una clase de python destinada a ser subclase para proporcionar una funcionalidad específica, con un conjunto de métodos llamados en varias etapas de entrenamiento (incluyendo el inicio y fin de los batch/epoch), pruebas y predicciones. Los Callbacks son útiles para tener visibilidad de los estados internos y las estadísticas del modelo durante el entrenamiento. Puedes pasar una lista de callbacks (como argumento de palabra clave callbacks) a cualquiera de los siguientes metodos tf.keras.Model.fit (),tf.keras.Model.evaluate ()ytf.keras.Model .predict (). Los metodos de los callbacks se llamaran en diferentes etapas del entrenamiento/evaluación/inferencia.
Para comenzar, importemos TensorDlow y definamos un modelo secuencial sencillo en Keras:
End of explanation
"""


# Cargar los datos de ejemplo de MNIST data y preprocesarlos
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') / 255
x_test = x_test.reshape(10000, 784).astype('float32') / 255

"""
Explanation: Luego, cara el dataset de  MNIST para entrenamiento y pruebas de la APLI de datasetws de Keras:
End of explanation
"""


import datetime

class MyCustomCallback(tf.keras.callbacks.Callback):

  def on_train_batch_begin(self, batch, logs=None):
    print('Entrenamiento: batch {} comienza en {}'.format(batch, datetime.datetime.now().time()))

  def on_train_batch_end(self, batch, logs=None):
    print('Entrenamiento: batch {} termina en {}'.format(batch, datetime.datetime.now().time()))

  def on_test_batch_begin(self, batch, logs=None):
    print('Evaluacion: batch {} comienza en {}'.format(batch, datetime.datetime.now().time()))

  def on_test_batch_end(self, batch, logs=None):
    print('Evaluacion: batch {} termina en {}'.format(batch, datetime.datetime.now().time()))

"""
Explanation: Ahora, define un callback simple y personalizado para rastrear el inicio y fin de cada batch de datos. Durante esas llamadas, imprime el indice del batch actual.
End of explanation
"""


model = get_model()
_ = model.fit(x_train, y_train,
          batch_size=64,
          epochs=1,
          steps_per_epoch=5,
          verbose=0,
          callbacks=[MyCustomCallback()])

"""
Explanation: Dar un callback mara los metodos del modelo tales como tf.keras.Model.fit() aseguran que los metodos son llamados en dichas etapas:
End of explanation
"""


_ = model.evaluate(x_test, y_test, batch_size=128, verbose=0, steps=5,
          callbacks=[MyCustomCallback()])

"""
Explanation: Metodos del Modelo que aceptan callbacks
Los usuarios pueden dar una lista de callbacks para los siguientes metodos de tf.keras.Model:
fit(), fit_generator()
Entrena el modelo por una cantidad determinada de epochs  (iteraciones en un dataset, o para los datos determinados por un generador de Python que va batch-por-batch).
evaluate(), evaluate_generator()
Evalua el modelo para determinados datos o generador de datos. Regresa la perdida (loss) y valores metricos para la evaluacion.
predict(), predict_generator()
Genera las predicciones a regresar para los datos ingresados o el generador de datos.
NOTA: Toda la documentacion esta en ingles.
End of explanation
"""


class LossAndErrorPrintingCallback(tf.keras.callbacks.Callback):

  def on_train_batch_end(self, batch, logs=None):
    print('Para el batch {}, la perdida (loss) es {:7.2f}.'.format(batch, logs['loss']))

  def on_test_batch_end(self, batch, logs=None):
    print('Para el  batch {}, la perdida (loss) es {:7.2f}.'.format(batch, logs['loss']))

  def on_epoch_end(self, epoch, logs=None):
    print('La perdida promedio para la epoch {} es {:7.2f} y el MAE es {:7.2f}.'.format(epoch, logs['loss'], logs['mae']))

model = get_model()
_ = model.fit(x_train, y_train,
          batch_size=64,
          steps_per_epoch=5,
          epochs=3,
          verbose=0,
          callbacks=[LossAndErrorPrintingCallback()])

"""
Explanation: Una revision de los metodos de callback
Metodos comunes para entrenamiento/pruebas/prediccion
Para entrenamiento, pruebas y prediccion, los siguientes metodos se han previsto para ser sobreescritos.
on_(train|test|predict)_begin(self, logs=None)
Llamado al inicio de fit/evaluate/predict.
on_(train|test|predict)_end(self, logs=None)
Llamado al fin de fit/evaluate/predict.
on_(train|test|predict)_batch_begin(self, batch, logs=None)
Llamado justo antes de procesar un batch durante entrenamiento/pruebas/prediccion. Dentro de este metodo, logs es un diccionario con las llaves batch y size disponibles, representando el numero de batch actual y las dimensiones del mismo.
on_(train|test|predict)_batch_end(self, batch, logs=None)
Llamado al final del entrenamiento/pruebas/prediccion de un batch. dentro de este metodo, logs es un diccionario que contiene resultados metricos con estado.
Entrenamiento de metodos especificos
Adicionalmente, para el entrenamiento, los siguientes metodos son provistos.
on_epoch_begin(self, epoch, logs=None)
Llamado al inicio de una epoch durante el entrenamiento.
on_epoch_end(self, epoch, logs=None)
Llamado al final de una epoch durante el entrenamiento.
Uso del diccionario logs
El diccionario logs contiene el valor de perdida (loss), y todas las metricas pertinentes al final de un batch o epoch. El ejemplo a continuacion incluye la perdidad (loss) y el MAE (Mean Absolute Error).
End of explanation
"""


_ = model.evaluate(x_test, y_test, batch_size=128, verbose=0, steps=20,
          callbacks=[LossAndErrorPrintingCallback()])

"""
Explanation: De manera similar, uno puede proveer callbacks en las llamadas a evaluate().
End of explanation
"""


import numpy as np

class EarlyStoppingAtMinLoss(tf.keras.callbacks.Callback):
  """Detener el entrenamiento cuando la perdida (loss) esta en su minimo, i.e. la perdida (loss) deja de disminuir.

  Arguments:
      patience: Numero de epochs a esperar despues de que el min ha sido alcanzaado. Despues de este numero
      de no mejoras, el entrenamiento para.
  """

  def __init__(self, patience=0):
    super(EarlyStoppingAtMinLoss, self).__init__()

    self.patience = patience

    # best_weights para almacenar los pesos en los cuales ocurre la perdida minima.
    self.best_weights = None

  def on_train_begin(self, logs=None):
    # El numero de epoch que ha esperado cuando la perdida ya no es minima.
    self.wait = 0
    # El epoch en el que en entrenamiento se detiene.
    self.stopped_epoch = 0
    # Initialize el best como infinito.
    self.best = np.Inf

  def on_epoch_end(self, epoch, logs=None):
    current = logs.get('loss')
    if np.less(current, self.best):
      self.best = current
      self.wait = 0
      # Guardar los mejores pesos si el resultado actual es mejor (menos).
      self.best_weights = self.model.get_weights()
    else:
      self.wait += 1
      if self.wait >= self.patience:
        self.stopped_epoch = epoch
        self.model.stop_training = True
        print('Restaurando los pesos del modelo del final de la mejor epoch.')
        self.model.set_weights(self.best_weights)

  def on_train_end(self, logs=None):
    if self.stopped_epoch > 0:
      print('Epoch %05d: Detencion anticipada' % (self.stopped_epoch + 1))

model = get_model()
_ = model.fit(x_train, y_train,
          batch_size=64,
          steps_per_epoch=5,
          epochs=30,
          verbose=0,
          callbacks=[LossAndErrorPrintingCallback(), EarlyStoppingAtMinLoss()])

"""
Explanation: Ejemplos de aplicaciones de callbacks de Keras
La siguiente seccion te guiara en la creacion de una aplicacion de callback simple.
Detencion anticipada con perdida minima.
El primer ejemplo muestra la creacion de un Callback que detiene el entrenamiento de Keras cuando se alcanza el minimo de perdida mutando el atributomodel.stop_training (boolean). Opcionalmente, el usuario puede proporcionar el argumento patience para especificar cuantas epochs debe esperar el entrenamiento antes de detenerse.
tf.keras.callbacks.EarlyStopping proporciona una implementación mas completa y general.
End of explanation
"""


class LearningRateScheduler(tf.keras.callbacks.Callback):
  """Planificador de Learning rate que define el learning rate deacuerdo a lo programado.

  Arguments:
      schedule: una funcion que toma el indice del epoch
          (entero, indexado desde 0) y el learning rate actual
          como entradas y regresa un nuevo learning rate como salida (float).
  """

  def __init__(self, schedule):
    super(LearningRateScheduler, self).__init__()
    self.schedule = schedule

  def on_epoch_begin(self, epoch, logs=None):
    if not hasattr(self.model.optimizer, 'lr'):
      raise ValueError('Optimizer must have a "lr" attribute.')
    # Obtener el learning rate actua del optimizer del modelo.
    lr = float(tf.keras.backend.get_value(self.model.optimizer.lr))
    # Llamar la funcion schedule para obtener el learning rate programado.
    scheduled_lr = self.schedule(epoch, lr)
    # Definir el valor en el optimized antes de que la epoch comience
    tf.keras.backend.set_value(self.model.optimizer.lr, scheduled_lr)
    print('\nEpoch %05d: Learning rate is %6.4f.' % (epoch, scheduled_lr))

LR_SCHEDULE = [
    # (epoch a comenzar, learning rate) tupla
    (3, 0.05), (6, 0.01), (9, 0.005), (12, 0.001)
]

def lr_schedule(epoch, lr):
  """Funcion de ayuda para recuperar el learning rate programado basado en la epoch."""
  if epoch < LR_SCHEDULE[0][0] or epoch > LR_SCHEDULE[-1][0]:
    return lr
  for i in range(len(LR_SCHEDULE)):
    if epoch == LR_SCHEDULE[i][0]:
      return LR_SCHEDULE[i][1]
  return lr

model = get_model()
_ = model.fit(x_train, y_train,
          batch_size=64,
          steps_per_epoch=5,
          epochs=15,
          verbose=0,
          callbacks=[LossAndErrorPrintingCallback(), LearningRateScheduler(lr_schedule)])

"""
Explanation: Programacion del Learning Rate
Algo que es hecho comunmente en el entrenamiento de un modelo es cambiar el learning rate conforme pasan mas epochs. El backend de Keras expone la API get_value la cual puede ser usada para definir las variables. En este ejemplo estamos mostrando como un Callback personalizado puede ser usado para cambiar dinamicamente el learning rate.
Nota: este es solo una implementacion de ejemplo, callbacks.LearningRateScheduler y keras.optimizers.schedules contienen implementaciones mas generales.
End of explanation
"""


