# Funciones Basicas de TensorFlow

## Introduccion

TensorFlow propone un modelo computacional distinto al normalmente usado: la computacion simbolica. Mas adelante veremos que significa esto, pero por ahora solo veamos un ejemplo de una computacion en python estadar


```python
w = 1.0  #parametro
b = 2.0  #parametro

def f(x):  # x es un input
    return w * x + b

f(1) # w * x + b == 1.0 * 1.0 + 2.0 == 3.0
```

Aqui diremos que `f` es una funcion de `x` que esta parametrizada por las variables `w` y `b`. Esto lo podriamos escribir matematicamente de la siguiente forma:

# $f(x; w,b) = w x + b$

Esto quiere decir que si bien `w` y `b` no son parte de los argumentos de `f`, si determinan su comportamiento. Si cambiamos `w` o `b` tambien cambiara el valor de `f` dado cierto `x`. Modifiquemos `b` a `10.0` a ver que sucede:


```python
b = 10.0

f(1) # w * x + b == 1.0 * 1.0 + 10.0 == 11.0
```

Como es de esperarse, el valor de `f` cambio. Durante el resto del notebook veremos lo necesario para recrear esto en TensorFlow

## Import

Por convension se importa `tensorflow` como `tf`


```python
import tensorflow as tf
```

## Tensores
Un tensor es un arreglo multidimensional. Por ejemplo los vectores son tensores de `1` dimension, las matrices son tensores de dimension `2`, y los escalares son tensores de `0` dimensiones. Asi mismo podemos crear tensores de mayores dimensiones, por ejemplo, las imagenes se pueden representar como un tensor de dimension 

## Constants
El tensor mas simple de TensorFlow es un tensor constante. Para crear uno utilizamos `tf.constant`


```python
c = tf.constant(5.0)

print c
```

Sin embargo, como vemos en la evaluacion de la celda anterior, este tensor constante `c` no parece indicarnos el valor que contiene, para extraer el valor primero tenemos crear crear una session y correr el tensor en ella:

## Session


```python
sess = tf.Session()

sess.run(c)
```

Utilizando `run` pudimos recuperar el valor de `c`! Sin embargo es no parece muy utilil pare ahora. Sin embargo, veamos como crear operaciones un poco mas complejas. Al igual que en numpy, los tensores sobreescriben la mayoria de los operadores de python y nos permiten crear nuevos tensores de esta manera:


```python
k = c * 12.0

print k
```

Aqui creamos `k` a partir de `c` y el numero `12.0`. Lo interesante es que el `print` nos da una pista de que `k` es un tensor de tipo "mul", esto se debe a que representa la operacion de multiplicacion. Para recuperar el valor de `k` debemos ejecutarlo en la session denuevo.


```python
sess.run(k)
```

## Placeholder
Hasta ahora solo hemos creado expresiones constantes y esto no es tan util. Para introducir entradas del exterior a las expresiones debemos crear un tensor tipo `placeholder`


```python
x = tf.placeholder(tf.float32, ())
y = x * 2.0

print y
```

Ok, definimos un tensor sin ningun valor y creamos un operacion a partir de el, ¿pero que pasara si lo evaluamos en la session?


```python
try:
    sess.run(y)
except:
    print "Error!"
```

Como vemos obtenemos un error, esto es porque tensorflow necesita que definamos un valor para `x` dentro del grafo para poder calcular el valor de `y`. Esto lo hacemos pasando un diccionario de tensores a valores al parametro `feed_dict` de `run`


```python
valores = {x:  9.0}

print float(sess.run(y, valores))
```

## Variable
Hemos creados expresiones "puras" en el sentido que dados los mismo valores para los `placeholders` siempre data los mismo valores para el resto de los tensores, en otras palabras, no hay estado. Para tener sistemas que guarden estado utilizamos `Variable`. Creemos una variable sensilla


```python
a = tf.Variable(1.0)
print a
```

Aqui creamos una variable con un valor inicial de `1.0`, sin embargo las variables son un poco diferentes al resto de los tensores en el sentido que deben ser inicializadas, esto posiblemente es para reservar memoria en el dispositivo que vaya a almacenar esta variable, si no lo hacemos e intentamos evaluar una expresion tendremos errores


```python
try:
    print sess.run(a)
except:
    print "Error!"
```

Para iniciar las variables corremos `tf.global_variables_initializer()`


```python
init = tf.global_variables_initializer()
sess.run(init)
```

Esta operacion no retorna nada pero como efecto secundario inicia las variables. Ahora si corremos `a` podemos obtener su valor


```python
sess.run(a)
```

Como dijimos el proposito de tener variables es poder almacener y lo mas importante actualizar cierto estado, de otra manera parecen constantes. Por lo tanto vamos a definir la operacion

$a := a * 2.0$

en TensorFlow


```python
update = a.assign(a * 2.0) #a = a * 2
```

Ahora si corremos `a` veremos que


```python
sess.run(a)
```

...sigue igual. Esto es porque debemos correr el tensor `update` para que `a` se actualice


```python
sess.run(update)

print sess.run(a)
```

Perfecto! Si volvemos a correr la celda anterior varias veces veremos que `a` se va duplicando cada vez. 

## Uniendo Todo
Ahora estamos preparados para recrear nuestra funcion `f` original en tensorflow


```python
w = tf.Variable(1.0) #parametro
b = tf.Variable(2.0) #parametro

x = tf.placeholder(tf.float32, []) #input

f = w * x + b  # f(x; w, b)

#iniciar variables
sess.run(tf.global_variables_initializer())

# f(1.0)
sess.run(f, feed_dict={x: 1.0})
```

Genial! Reproducimos el resultado original de python. Ahora nos falta modificar `b` a `10.0` como lo hicimos en anteriormente y ver como cambia `f`


```python
# b = 10.0
sess.run(tf.assign(b, 10.0))

# f(1.0)
sess.run(f, feed_dict={x: 1.0})
```

## ¿Que sigue?

Todo esto realmente parece una forma muy complicada de realizar oparaciones que serian mas facil con python o numpy. Las ventajas de TensorFlow para el Deep Learning y la computacion cientifica en general son las siguientes:

* Cada tensor puede estar ubicado en un dispositivo diferente como una gpu, la cpu normal, o una maquina remota. Esto es importante para correr grandes modelos utilizando paralelismo y computacion distribuida.
* La computacion simbolica le permite a tensorflow calcular derivadas de una expresion con respecto a una subexpresion de la misma, esto es importante para calcular el gradiente de las redes neuronales.

## Gradients
Gracias a la computacion simbolica, es decir, que no se ejecuta operacion para generar valores de forma inmediata sino que se guarda un grafo de la expresion, muy parecido a una formula matematica, TensorFlow puede calcular la derivada de una expresion con respecto a una subexpresion de la misma. Esto es muy importante para el algoritmo `Gradient Descent` que depende totalmente de poder calcular el gradiente del error con respecto a los pesos:

$
\begin{equation}
    \Delta = \nabla_{\theta} E(x, y; \theta)
\end{equation}
$
<br>
$
\begin{equation}
    \theta := \theta - \alpha \Delta
\end{equation}
$

A continuacion vamos a calcular la derivada de $f$ con respecto a $w$ utilizando la funcion `gradients`



```python
dfdw = tf.gradients(f, w)
```

Analiticamente el valor de esta operacion es

$
\begin{equation}
    \frac{\delta f}{\delta w} = \frac{\delta (x w + b)}{\delta w} = x
\end{equation}
$

y lo podemos comprobar numericamente:


```python
grad = sess.run(dfdw, feed_dict={x: 10.0})
x_val = sess.run(x, feed_dict={x: 10.0})

print "Gradiente: {0}, x: {1}".format(grad[0], x_val)
```

Como vemos, ambdas expresiones tienen el mismo valor!

## Gradient Descent
Ahora vamos aplicar todo el algoritmo de que involucra los siguientes pasos:



Primero vamos a importar unas librarias y generar algunos datos


```python
import plotly.offline as py
from plotly import graph_objs as go
import numpy as np
py.init_notebook_mode()

n = 50

x_data = np.random.uniform(low=0.0, high=100.0, size=(n, 1))
y_data = 3.5 * x_data + 40.3 + np.random.normal(loc=0.0, scale=25.0, size=(n, 1))

#normalizar data
x_data = (x_data - np.mean(x_data)) / np.average(x_data)
y_data = (y_data - np.mean(y_data)) / np.average(y_data)

scatter = go.Scatter(
    x=x_data[:, 0], 
    y=y_data[:, 0], 
    mode = 'markers', 
    marker = dict(size = 10),
    name="data"
)

layout = go.Layout(xaxis=dict(title='x'), yaxis=dict(title='y'))


py.iplot(go.Figure(data=[scatter], layout=layout))
```

Ahora vamos a crear un modelo con parametros aleatorios y visualizarlo


```python
####################
# Tarea: Crear los tensores
####################

x = 
y = 

w = 
b = 

h = 

sess.run(tf.global_variables_initializer())

x_model = [np.min(x_data), np.max(x_data)]
y_model = sess.run(h, feed_dict={x: x_model})


model_line = go.Scatter(
    x=x_model, 
    y=y_model,
    name="model"
)

py.iplot(go.Figure(data=[scatter, model_line], layout=layout))
```


```python
alfa = 0.5

#calcular error
E = 

#calcular gradiente
dEdw, dEdb = 

#actualizar pesos
update_w, update_b = 

#realizar operacion 200 veces
for i in range(200):
    #update

#visualizar modelo aprendido
y_model = sess.run(h, feed_dict={x: x_model})

model_line = go.Scatter(
    x=x_model, 
    y=y_model,
    name="model"
)

py.iplot(go.Figure(data=[scatter, model_line], layout=layout))
```

49571a5bb611f39d831928cfed038d0883d90bbd
ipynb
Jupyter Notebook
notebooks/ep1/tensorflow-basics.ipynb
neura-lab/intro-deeplearning-tensorflow
216f2535c5b10381eb83ce703efcae517d10b3ae
MIT
notebooks/ep1/tensorflow-basics.ipynb
neura-lab/intro-deeplearning-tensorflow
216f2535c5b10381eb83ce703efcae517d10b3ae
MIT
notebooks/ep1/tensorflow-basics.ipynb
neura-lab/intro-deeplearning-tensorflow
216f2535c5b10381eb83ce703efcae517d10b3ae
MIT
Qwen/Qwen-72B
1. YES
2. YES