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

# # Ejercicio TensorFlow 2.0

# Usaremos el mismo caso visto previamente pero ahora utilizando TensorFlow.

# ## Cargamos las librerías

# In[1]:


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


# # Generamos los datos con la misma lógica que en el ejercicio previo.

# Con una diferencia.  En el mundo real, lo habitual es cargar los datos a través de un csv, bbdd, ...
# Vamos a recrear la carga de los datos que hayamos generado de manera aleatoria, guardándolos en formato npz.
# El formato npz es un formato Numpy, que permite guardar arrays en una fichero.

# In[2]:


observations = 1000

np.random.seed(123)
xs = np.random.uniform(low=-10, high=10, size=(observations,1))
zs = np.random.uniform(-10, 10, (observations,1))

inputs = np.column_stack((xs,zs))

noise = np.random.uniform(-1, 1, (observations,1))

targets = 2*xs - 3*zs + 5 + noise

# Hasta aquí hemos generado los mismos datos, ahora los guardamos
np.savez('data', inputs=inputs, targets=targets)


# # Resolviendo con TensorFlow

# In[3]:


# Cargamos el conjunto de datos
training_data = np.load('data.npz')


# In[4]:


# Declaramos una variable donde guardaremos el total de variables del modelo
# En nuestro caso 2 xs y zs
#input_size = 2

# Declaramos el total de variables de salida del modelo
# En nuestro caso 1
output_size = 1

# Definimos nuestro modelo como secuencial.
# En esta etapa no hay datos involucrados, sólo definimos lo que queremos hacer.
model = tf.keras.Sequential([
                            # Cada capa se lista aquí
                            # Definimos las características de la red
                            # Dense, nos permite definir la operación matemática a ejecutar xw + b
                            # Básicamente aplica la operación: output = activation(dot(input, kernel) + bias
                            tf.keras.layers.Dense(output_size,
                                                 kernel_initializer=tf.random_uniform_initializer(minval=-0.1, maxval=0.1),
                                                 bias_initializer=tf.random_uniform_initializer(minval=-0.1, maxval=0.1)
                                                 )
                            ])

# Definimos el ratio de aprendizaje
custom_optimizer = tf.keras.optimizers.SGD(learning_rate=0.02)

# Definimos la función de pérdida a minimizar.  En este caso el error cuadrático medio
model.compile(optimizer=custom_optimizer, loss='mean_squared_error')

# Definimos el modelo con los datos de entrada y targets.
# Ejecutamos 100 ciclos
model.fit(training_data['inputs'], training_data['targets'], epochs=100, verbose=1)


# ## Extraemos los pesos y bias

# La extracción de pesos y bias del modelo no es necesaria en el proceso de machine learning.  De hecho, no se ejecuta habitualmente, pero en este ejemplo simple nos sirve para comprobar que los resultados son correctos.

# In[5]:


# La extracción de pesos y biases es muy sencilla
model.layers[0].get_weights()


# In[6]:


weights = model.layers[0].get_weights()[0]
bias = model.layers[0].get_weights()[1]
print(weights)
print(bias)

# éste es un modelo simple, pero en otros más complicados podemos tener cientos o miles de valores en estas variables


# ## Generar las predicciones

# In[7]:


# Datos predichos
model.predict_on_batch(training_data['inputs']).round(1)


# In[8]:


# Datos reales
training_data['targets'].round(1)


# ## Pintamos los datos

# In[9]:


np.squeeze(model.predict_on_batch(training_data['inputs'])), np.squeeze(training_data['targets'])


# In[10]:


# Usamos np.squeeze para poder ajustar los datos a lo que espera plt.plot
plt.plot(np.squeeze(model.predict_on_batch(training_data['inputs'])), np.squeeze(training_data['targets']))
plt.xlabel('outputs')
plt.ylabel('targets')
plt.show()

# La idea de haber usado TensorFlow para el mismo ejercicio y poder comparar las líneas de código necesarias
# para obtener el mismo resultado.  Muchas menos en TensorFlow

