#!/usr/bin/env python
# coding: utf-8

# # Guia inicial de TensorFlow 2.0

# In[1]:


# pip install tensorflow


# In[2]:


import tensorflow as tf


# #### Tensores

# Un tensor es una matriz multidimensional. Al igual que los objetos NumPy ndarray , los objetos **tf.Tensor** tienen un tipo de datos y una forma. Además, **tf.Tensor** puede residir en la memoria del acelerador (como una GPU). TensorFlow ofrece una rica biblioteca de operaciones (**tf.add, tf.matmul, tf.linalg.inv**, etc.) que consumen y producen **tf.Tensor**. Estas operaciones convierten automáticamente los tipos nativos de Python,

# In[31]:


print(tf.add(1, 2))
print(tf.add([1, 2], [3, 4]))
print(tf.square(5))
print(tf.reduce_sum([1, 2, 3]))

print(tf.square(2) + tf.square(3))


# In[32]:


tf.print(tf.add(1, 2))
tf.print(tf.add([1, 2], [3, 4]))
tf.print(tf.square(5))
tf.print(tf.reduce_sum([1, 2, 3]))

tf.print(tf.square(2) + tf.square(3))


# Cada **tf.Tensor** tiene una forma y un tipo de datos:

# In[36]:


x = tf.matmul([[2]], [[2, 3]])  # multiplica matrices
print(x)
print(x.shape)
print(x.dtype)


# Las diferencias principales entre los arrays **NumPy** y los **tf.Tensor** son:
# * Los tensores pueden estar respaldados por memoria aceleradora (como GPU, TPU).
# * Los tensores son **inmutables**.

# #### Compatibilidad NumPy
# Convertir entre un TensorFlow **tf.Tensor** y un NumPy ndarray es fácil:
# 
# Las operaciones de TensorFlow convierten automáticamente NumPy ndarrays en Tensores.
# Las operaciones NumPy convierten automáticamente los tensores en ndarrays NumPy.
# 

# In[41]:


import numpy as np

ndarray = np.ones([3, 3])
print(ndarray)
print("Tipo: ", type(ndarray))


print("Las operaciones TensorFlow convierten los numpy arrays a Tensores automáticamente")
tensor = tf.multiply(ndarray, 42)
print(tensor)
print("Tipo: ", type(tensor))

print("Y al revés (tensores a arrays)")
print(np.add(tensor, 1))
print("Tipo: ", type(np.add(tensor, 1)))

print("El método .numpy() convierte explicitamente un Tensor a numpy array")
print(tensor.numpy())
print("Tipo: ", type(tensor.numpy()))


# #### Aceleración GPU

# TensorFlow decide automáticamente si usar la GPU o la CPU para una operación.

# In[44]:


x = tf.random.uniform([3, 3])

print("GPU disponible: "),
print(tf.config.list_physical_devices("GPU"))

print("Está el Tensor en GPU #0:  "),
print(x.device.endswith('GPU:0'))


# ### Aplicación transformaciones

# A través de las funciones **map, batch, shuffle** podemos aplicar transformaciones a los registros.

# In[7]:


ds_tensors = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4, 5, 6]) # Creamos un conjunto de datos con Dataset


# In[51]:


[tf.print(x) for x in ds_tensors]


# In[52]:


ds_tensors = ds_tensors.map(tf.square).shuffle(2).batch(2)
[tf.print(x) for x in ds_tensors]


# ## Clasificación de imagenes cargando imágenes existentes en una carpeta

# In[53]:


import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential


# In[54]:


import pathlib
dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True)
data_dir = pathlib.Path(data_dir)


# In[56]:


image_count = len(list(data_dir.glob('*/*.jpg')))
print(image_count)


# In[57]:


data_dir


# Tenemos las siguientes carpetas dentro de flower_photos:

# In[59]:


os.listdir(data_dir)


# In[60]:


roses = list(data_dir.glob('roses/*'))
PIL.Image.open(str(roses[0]))


# In[61]:


PIL.Image.open(str(roses[1]))


# In[62]:


tulips = list(data_dir.glob('tulips/*'))
PIL.Image.open(str(tulips[0]))


# In[65]:


PIL.Image.open(str(tulips[10]))


# ### Creamos el conjunto de datos

# In[66]:


batch_size = 32
img_height = 180
img_width = 180

train_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="training",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)

val_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="validation",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)


# In[67]:


class_names = train_ds.class_names
print(class_names)


# ### Visualización de datos

# In[68]:


import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1):
  for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])
    plt.axis("off")


# Comprobamos las características de los tensores que vamos a usar

# In[72]:


for image_batch, labels_batch in train_ds:
    print(image_batch.shape)
    print(labels_batch.shape)
    break  # todos los tensores tienen las mismas dimensiones


# **image_batch** es un tensor de la forma (32, 180, 180, 3). Éste es un lote de 32 imágenes de forma 180x180x3 (la última dimensión se refiere a los canales de color RGB). El **label_batch** es un tensor de la forma (32,), éstas son las etiquetas correspondientes a las 32 imágenes.

# #### Nota: De cara a aumentar el rendimiento con las operaciones E/S de datos en disco, usaremos AUTOTUNE

# In[73]:


AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)


# ### Creación del modelo

# In[75]:


num_classes = len(class_names)

model = Sequential([
  layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)),  # Rescaling nos permite la normalización [0,1]
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(32, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(64, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes)
])


# ### Compilación del modelo

# In[76]:


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


# In[77]:


model.summary()


# Otra manera de ver lo mismo

# In[111]:


tf.keras.utils.plot_model(model = model , dpi=75, show_shapes=True)


# ### Entrenamos el modelo

# In[83]:


epochs=10
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)


# ### Visualización de los resultados de entrenamiento

# In[85]:


acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()


# El modelo ha logrado solo alrededor del 60 % de precisión en el conjunto de validación.  Vemos que hay un claro sobreajuste en el momento que los datos de entrenamiento se despegan de los de validación.
# Tenemos varias estratégias para evitar el sobreajuste:
# * Parar el entrenamiento cuando la precisión de los datos de validación no aumenten respecto de los de train
# * Aumentar el número de datos de entrenamiento (y agregar Dropout) al modelo.

# ### Aumento de los datos

# In[93]:


data_augmentation = keras.Sequential(
  [
    layers.RandomFlip("horizontal",
                      input_shape=(img_height,
                                  img_width,
                                  3)),
    layers.RandomRotation(0.1),
    layers.RandomZoom(0.1),
  ]
)


# Vemos cómo se ven las imágenes tras la transformación

# In[94]:


plt.figure(figsize=(10, 10))
for images, _ in train_ds.take(1):
    for i in range(9):
        augmented_images = data_augmentation(images)
        ax = plt.subplot(3, 3, i + 1)
        plt.imshow(augmented_images[0].numpy().astype("uint8"))
        plt.axis("off")


# ### Nuevo modelo

# In[95]:


model = Sequential([
  data_augmentation,    # aumentamos los datos
  layers.Rescaling(1./255),
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(32, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(64, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Dropout(0.2),  # dropout
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes)
])


# In[96]:


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


# In[97]:


model.summary()


# In[98]:


epochs = 15
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)


# ### Visualización 2

# In[100]:


acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()


# ### Predicción con nuevos datos

# In[101]:


sunflower_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg"
sunflower_path = tf.keras.utils.get_file('Red_sunflower', origin=sunflower_url)


# In[102]:


img = tf.keras.utils.load_img(
    sunflower_path, target_size=(img_height, img_width)
)


# In[103]:


img


# In[104]:


img_array = tf.keras.utils.img_to_array(img)
img_array = tf.expand_dims(img_array, 0) # Crea el lote, de una imagen en este caso


# In[108]:


img_array.shape


# In[105]:


img_array


# In[109]:


predictions = model.predict(img_array)
score = tf.nn.softmax(predictions[0])

print(
    "Esta imagen pertence al grupo de {} con una confianza del {:.2f} por ciento."
    .format(class_names[np.argmax(score)], 100 * np.max(score))
)


# ## Clasificación usando capas personalizadas

# In[2]:


import tensorflow as tf

from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model


# Cargamos y preparamos el conjunto de datos [MNIST](http://yann.lecun.com/exdb/mnist/)

# In[3]:


mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# Agregamos la dimension de canales (de color)
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]


# Utilizamos `tf.data` para separar por lotes y mezclar el conjunto de datos:

# In[4]:


train_ds = tf.data.Dataset.from_tensor_slices(
    (x_train, y_train)).shuffle(10000).batch(32)

test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)


# Construimos el modelo `tf.keras` utilizando la API de Keras [model subclassing API](https://www.tensorflow.org/guide/keras#model_subclassing):

# In[15]:


class MyModel(Model):
    def __init__(self):
        super(MyModel, self).__init__()
        self.conv1 = Conv2D(32, 3, activation='relu')
        self.flatten = Flatten()
        self.d1 = Dense(128, activation='relu')
        self.d2 = Dense(10, activation='softmax')

    def call(self, x):
        x = self.conv1(x)
        x = self.flatten(x)
        x = self.d1(x)
        return self.d2(x)

# Crea una instancia del modelo
model = MyModel()


# Escogemos un optimizador y una funcion de perdida para el entrenamiento del modelo:

# In[16]:


loss_object = tf.keras.losses.SparseCategoricalCrossentropy()

optimizer = tf.keras.optimizers.Adam()


# Escogemos las metricas para medir la perdida y exactitud del modelo.
# Estas metricas acumulan los valores cada epoch y despues imprimen el resultado total.

# In[17]:


train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')


# Utilizamos `tf.GradientTape` para entrenar el modelo.  A través de GradientTape podemos registrar operaciones para diferenciación automática.  Con esta función obtenemos más rendimiento ya que sólo tenemos el cuenta el gradiente en las operaciones que queramos, no sobre todo el proceso de de entrenamiento.

# In[8]:


# Ejemplo
prueba = tf.constant(4.0) 
  
with tf.GradientTape() as gfg: 
    gfg.watch(prueba) 
  
  
with tf.GradientTape() as gg: 
    gg.watch(prueba) 
    y = prueba * prueba * prueba 

    primera_derivada = gg.gradient(y, prueba) 
    
segunda_derivada  = gfg.gradient(primera_derivada, prueba)  
  
print("primera derivada: ",primera_derivada) 
print("segunda derivada: ",segunda_derivada) 


# In[18]:


@tf.function
def train_step(images, labels):
    with tf.GradientTape() as tape:
        predictions = model(images)
        loss = loss_object(labels, predictions)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    train_loss(loss)
    train_accuracy(labels, predictions)


# In[19]:


@tf.function
def test_step(images, labels):
    predictions = model(images)
    t_loss = loss_object(labels, predictions)

    test_loss(t_loss)
    test_accuracy(labels, predictions)


# Probamos el modelo:

# In[20]:


for epoch in range(EPOCHS):
    for images, labels in train_ds:
        train_step(images, labels)

    for test_images, test_labels in test_ds:
        test_step(test_images, test_labels)

    template = 'Epoch {}, Perdida: {}, Exactitud: {}, Perdida de prueba: {}, Exactitud de prueba: {}'
    print(template.format(epoch+1,
                        train_loss.result(),
                        train_accuracy.result()*100,
                        test_loss.result(),
                        test_accuracy.result()*100))

  # Reinicia las metricas para el siguiente epoch.
    train_loss.reset_states()
    train_accuracy.reset_states()
    test_loss.reset_states()
    test_accuracy.reset_states()


# El modelo de clasificacion de images una vez entrenado ha alcanzado una exactitud de ~98% en este conjunto de datos.

# ## Carga de datos (CSV)... del mismo tipo

# In[1]:


import pandas as pd
import numpy as np

# Permite leer los datos numpy más fácil.
np.set_printoptions(precision=3, suppress=True)

import tensorflow as tf
from tensorflow.keras import layers


# In[114]:


df = pd.read_csv(
    "https://storage.googleapis.com/download.tensorflow.org/data/abalone_train.csv",
    names=["Longitud", "Diámetro", "Altura", "Peso Bruto", "Peso vísceras",
           "Peso descascarillado", "Peso Carcasa", "Edad"])

df.head()


# Datos relativos a un tipo de caracól marino.  Usaremos tensorflow para predecir la edad en base al resto de variables.

# In[115]:


df.shape


# In[116]:


X = df.copy()
y = X.pop('Edad')


# In[117]:


X = np.array(X)
X


# #### Definimos un modelo de regresión lineal para predecir la edad.  Como sólo tenemos un tensor de entrada único, el modelo keras.Sequential es suficiente (en este caso).
# De momento no aplicamos normalización

# In[118]:


modelo = tf.keras.Sequential([
    layers.Dense(64),
    layers.Dense(1)
])

modelo.compile(loss = tf.losses.MeanSquaredError(),
               optimizer = tf.optimizers.Adam(),
               metrics = [tf.metrics.RootMeanSquaredError()])


# #### Entrenamos...

# In[119]:


modelo.fit(X, y, epochs=10)


# #### Aplicamos un preprocesamiento

# In[120]:


normalizado = layers.Normalization()


# In[121]:


normalizado.adapt(X)


# In[123]:


modelo_normalizado = tf.keras.Sequential([
  normalizado,
  layers.Dense(64),
  layers.Dense(1)
])

modelo_normalizado.compile(loss = tf.losses.MeanSquaredError(),
                           optimizer = tf.optimizers.Adam(),
                           metrics = [tf.metrics.RootMeanSquaredError()])

modelo_normalizado.fit(X, y, epochs=10)


# ## Carga de datos (CSV) de diferente tipo

# In[2]:


titanic = pd.read_csv("https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic.head()


# In[3]:


X = titanic.copy()
y = X.pop('survived')


# In[4]:


X.info()


# Como tenemos diferentes tipos de variables, debemos manejar cada columna individualmente, antes de 'apilar' las funciones en Keras.  Podemos usar el enfoque 'clásico' (preprocesar la info y luego pasarla al modelo) ó este enfoque...

# #### Aplicación de la etapa de preprocesamiento con la API funcional de Keras.  Enfoque similar a las tuberías de SKLearn

# In[5]:


# Extraemos los tipos de cada columna.
inputs = {}

for name, column in X.items():
    dtype = column.dtype
    if dtype == object:
        dtype = tf.string
    else:
        dtype = tf.float32

    inputs[name] = tf.keras.Input(shape=(1,), name=name, dtype=dtype)

inputs


# In[6]:


# Procesamos las entradas numéricas
numericas = {name:input for name,input in inputs.items()
             if input.dtype==tf.float32}

x = layers.Concatenate()(list(numericas.values()))
norm = layers.Normalization()
norm.adapt(np.array(titanic[numericas.keys()]))
numericas = norm(x)

numericas


# In[17]:


datos_preprocesados = [numericas]


# In[18]:


# Procesamos entradas de texto
for name, input in inputs.items():
    if input.dtype == tf.float32:
        continue

    lookup = layers.StringLookup(vocabulary=np.unique(X[name]))        # Asignamos índices a cadenas
    one_hot = layers.CategoryEncoding(num_tokens=lookup.vocabulary_size())  # Aplicamos OneHot Encoding al paso previo
    
    x = lookup(input)
    x = one_hot(x)
    datos_preprocesados.append(x)


# In[15]:


datos_preprocesados


# Ahora concatenamos los datos preprocesados...

# In[19]:


preprocesado = layers.Concatenate()(datos_preprocesados)

titanic_preprocesado = tf.keras.Model(inputs, preprocesado)

tf.keras.utils.plot_model(model = titanic_preprocesado , rankdir="LR", dpi=72, show_shapes=True)


# De momento sólo tenemos "la tubería".  Hay que "enchufar" los datos...
# Los modelos de Keras no convierten automáticamente Pandas DataFrames porque no saben si deben convertir a un tensor o a un diccionario de tensores. Así que lo convertimos en un diccionario de tensores

# In[20]:


X_dict = {name: np.array(value)
          for name, value in X.items()}


# Podemos ver lo hecho con un registro...

# In[23]:


registro1_dict = {name:values[:1] for name, values in X_dict.items()}
titanic_preprocesado(registro1_dict)


# #### Construimos el modelo

# In[26]:


def titanic_model(preprocessing_head, inputs):
    body = tf.keras.Sequential([
        layers.Dense(64),
        layers.Dense(1)
    ])

    preprocessed_inputs = preprocessing_head(inputs)
    result = body(preprocessed_inputs)
    model = tf.keras.Model(inputs, result)

    model.compile(loss=tf.losses.BinaryCrossentropy(from_logits=True),
                  optimizer=tf.optimizers.Adam(),
                  metrics = ["accuracy"])
    return model

modelo_titanic = titanic_model(titanic_preprocesado, inputs)


# In[27]:


modelo_titanic.fit(x=X_dict, y=y, epochs=10)


# #### Ahora guardamos el modelo, para su uso posterior...

# In[28]:


modelo_titanic.save('modelo-keras-titanic')


# #### Cargamos los datos previamente guardados

# In[29]:


modelo_cargado = tf.keras.models.load_model('modelo-keras-titanic')


# ## Modelo clasificación con datos Fashion_MNIST

# In[32]:


# TensorFlow y tf.keras
import tensorflow as tf
from tensorflow import keras


import numpy as np
import matplotlib.pyplot as plt


# In[40]:


from keras import datasets
fashion_mnist = datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()


# In[41]:


train_images


# No tenemos las clases etiquetadas.

# In[42]:


train_labels


# In[43]:


class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']


# Los datos que tenemos...

# In[44]:


print(train_images.shape)
print(train_labels.shape)
print(test_images.shape)
print(test_labels.shape)


# #### Preprocesado

# In[49]:


plt.figure()
plt.imshow(train_images[0])
plt.colorbar()
plt.grid(False)
plt.show()


# In[46]:


plt.figure(figsize=(10,10))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i])
    plt.xlabel(class_names[train_labels[i]])
plt.show()


# Normalizamos los datos...

# In[38]:


train_images = train_images / 255.0
test_images = test_images / 255.0


# In[48]:


plt.figure(figsize=(10,10))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i], cmap=plt.cm.binary)
    plt.xlabel(class_names[train_labels[i]])
plt.show()


# Las etiquetas de imagen en el notebook con fondo oscuro no se ven.

# #### Construimos el modelo

# In[50]:


model = keras.Sequential([
    keras.layers.Flatten(input_shape=(28, 28)),  # Creamos la primera capa
    keras.layers.Dense(128, activation='relu'),  # Función de activación ReLU en una capa 'DENSAMENTE' conectada con 128 neuronas -> layers.Dense
    keras.layers.Dense(10, activation='softmax') # Función de activación SoftMax en una capa 'DENSAMENTE' conectada con 10 neuronas
                                                 # Esta capa devolverá un array con 10 probabilidades que sumen 1 (tenemos 10 clases)
])


# #### Compilamos el modelo

# In[51]:


model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])


# #### Entrenamos el modelo

# In[53]:


model.fit(train_images, train_labels, epochs=10)


# #### Evaluación de la exactitud

# In[54]:


test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)

print('\nExactitud Test:', test_acc)


# #### Predicciones

# In[55]:


predictions = model.predict(test_images)


# In[84]:


# Primera imagen test
print(predictions[0])
print(np.argmax(predictions[0]))
print(class_names[np.argmax(predictions[0])])


# Construimos una función para automatizar el proceso

# In[62]:


def plot_image(i, predictions_array, true_label, img):
  predictions_array, true_label, img = predictions_array, true_label[i], img[i]
  plt.grid(False)
  plt.xticks([])
  plt.yticks([])

  plt.imshow(img, cmap=plt.cm.binary)

  predicted_label = np.argmax(predictions_array)
  if predicted_label == true_label:
    color = 'blue'
  else:
    color = 'red'

  plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
                                100*np.max(predictions_array),
                                class_names[true_label]),
                                color=color)

def plot_value_array(i, predictions_array, true_label):
  predictions_array, true_label = predictions_array, true_label[i]
  plt.grid(False)
  plt.xticks(range(10))
  plt.yticks([])
  thisplot = plt.bar(range(10), predictions_array, color="#777777")
  plt.ylim([0, 1])
  predicted_label = np.argmax(predictions_array)

  thisplot[predicted_label].set_color('red')
  thisplot[true_label].set_color('blue')


# In[63]:


i = 0
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i],  test_labels)
plt.show()


# In[64]:


i = 1
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i],  test_labels)
plt.show()


# In[66]:


num_rows = 8
num_cols = 3
num_images = num_rows*num_cols
plt.figure(figsize=(2*2*num_cols, 2*num_rows))
for i in range(num_images):
  plt.subplot(num_rows, 2*num_cols, 2*i+1)
  plot_image(i, predictions[i], test_labels, test_images)
  plt.subplot(num_rows, 2*num_cols, 2*i+2)
  plot_value_array(i, predictions[i], test_labels)
plt.tight_layout()
plt.show()


# Las predicciones incorrectas se muestran en rojo.
# Usamos ahora el modelo para hacer una predicción sobre una única imagen.

# In[78]:


img = test_images[80]
print(img.shape)


# Añadimos esta imagen al 'lote'.  Los modelos tf.keras, se optimizan en bloques (o colecciones).  Ello implica que para agregar una sola imagen debemos agregarla a una lista.

# In[79]:


img = (np.expand_dims(img,0)) # El 0 añade la dimensión correspondiente a la posición en la colección.
print(img.shape)


# ¿Qué tenemos en img?

# In[81]:


plt.figure()
plt.imshow(test_images[80])
plt.colorbar()
plt.grid(False)
plt.show()


# In[85]:


prediccion_sencilla = model.predict(img)
print(prediccion_sencilla)
print(class_names[np.argmax(prediccion_sencilla [0])])

