# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# # "Proceso de aprendizaje automático supervisado"
# > "Una introducción la automatización del aprendizaje supervisado. Python + Pytorch"
#
# - toc: false
# - branch: master
# - badges: true
# - comments: true
# - categories: [curso_Fast.ai]
# - image: images/red.jpg
# - hide: false
# - search_exclude: true
# - metadata_key1: metadata_value1
# - metadata_key2: metadata_value2

# Fuentes: [Practical Deep Learning for Coders (Fast.ai](https://course.fast.ai/), [<NAME> -  Dot CSV](https://www.youtube.com/channel/UCy5znSnfMsDwaLlROnZ7Qbg)
#
# <hr>

# Partimos de una **realidad** que queremos estudiar. <br>
# Buscando **patrones** construimos **modelos** que simplifiquen esta realidad y nos ayuden a sacar ventajas.<br>
# Los modelos buscan el equilibrio entre representar bien la realidad y la simplicidad de uso.<br>
#
# Elementos:
#
# - datos: valores sacados de la realidad que alimentan el modelo
# - parámetros: valores que se pueden variar para ajustar el modelo
# - error: mide el grado de ajuste de nuestro modelo a los datos
#
# Partiendo de un modelo inicial, el objetivo será **optimizar**lo (entrenarlo, ajustarlo) a través de la variación de los parámetros buscando el mínimo error.

# ## El modelo: Regresión lineal como primer acercamiento 

# Dentro del *aprendizaje automático* (machine learning) hay tres ramas diferenciadas:
#
#    - **aprendizaje supervisado**: se tiene datos etiquetados (clasificados), sabemos el objetivo buscado
#    - **aprendizaje no supervisado**: se busca sacar conocimiento con datos no etiquetados, se desconoce a priori el objetivo. 
#    - **aprendizaje reforzado**: se establece un sistema de refuerzos y penalizaciones para llegar al objetivo
#
# Nosotros comenzaremos por el aprendizaje supervisado escogiendo lo más sencillo, un modelo lineal.

# #### Regresión lineal simple: una variable
#
# Tenemos sólo una variable **x** de entrada (v. independiente), y se busca un resultado **y** que depende de la anterior. Existe una relación lineal entre ambas, por lo que se representa como una línea:
#
# y = b + w1 * x
#
# b y w1 son los parámetros: el sesgo y la pendiente de la línea

# ![](images/linea.png "y = b + w1 * x")

# #### Regresión lineal múltiple
#
# ##### 2 variables
#
# Representan un plano
#
# y = b + w1 * x1 + w2 * x2

# ![](images/plano.png "y = b + w1 * x1 + w2 * x2")

# ##### n variables
#
# Representan un hiperplano
#
# y = b + w1 * x1 + w2 * x2 + w3 * x3 + ... + wn* xn

# <hr>
# Tanto para regresion lineal simple como múltiple tendremos unos datos sacados de la realidad y queremos encontrar un modelo (una línea, plano,...) que se aproxime a ellos:

# ![](images/estimacion01.png)

# Para cada punto podemos calcular el error (distancia) que hay entre el valor real y la predicción:<br>
# <br>
# error = Y real - Y estimado
# <br><br>
# El **error** (coste) **del modelo** será la media de estas distancias:

error_media_abs = ((y_real-y_estimado)).abs().mean()

# ![](images/estimacion02.png)

# Aunque, se suele utilizar la raiz cuadrada del **error medio cuadrático (MSE)** porque penaliza más cuanto mayor sea el error:

error_mse = ((y_real-y_estimado)**2).mean().sqrt()

# En Pytorch tenemos estas funciones ya optimizadas
F.l1_loss(y_real.float(),y_estimado)
F.mse_loss(y_real,y_estimado).sqrt()

# <hr>
#

# ## El aprendizaje: Descenso del gradiente 

# Una vez tenemos el modelo y la función de error, iremos dando valores a los parámetros **w y b** buscando el menor error hasta encontrar el modelo que mejor se ajuste a la realidad.
#
# Queremos que ese ajuste de parámetros se realice de forma autónoma. Para ello tenemos la herramienta **desceso del gradiente**. 
#
# La técnica de *descenso del gradiente* consiste en moverse por la función de error buscando el punto donde se minimiza. Para ello, en cada posición, busca la dirección en la que se reduce más el error. Esto se obtiene con la derivada parcial en el punto respecto de cáda parámetro (el gradiente de la función), que es la pendiente de la curva.
#
# Una vez obtenida la dirección en que hay que moverse se recalculan los parámetros de la siguiente forma:
#
# w := w - gradiente(w) * lr
#
#
# 'lr' es la **tasa de aprendizaje**, que define el tamaño del paso que se realizará. Si es demasiado pequeño se necesitará mucho tiempo para alcanzar el mínimo y si es muy grande seguramente se pasará de largo sin llegar al mínimo.

# En Pytorch la siguiente función calcula el gradiente
funcion_error.backward()

# Se muestra el gradiente calculado
parametros.grad

# <hr>
# Ahora ya podemos preparar el algoritmo de aprendizaje automático. Los pasos serán:
#
#     1- Cargar los datos y escoger el modelo
#     2- Inicializar los parámetros: se suele hacer de forma aleatoria

# Los parámetros (w) pueden almacenar el gradiente con "requires_grad_()" 
parametros = torch.randn(num_parametros).requires_grad_()

#     3- Calcular la predicciones
#     4- Calcular el error (coste)  

error = mse(prediciones, valores_reales)

#     5- Calcular el gradiente
#     6- Realizar el paso, modificación de los parámetros

lr = 1e-5
parametros.data -= lr * parametros.grad.data
parametros.grad = None

#     7- Repetir el proceso (punto 3) las veces necesarias



StarcoderdataJupyterScriptsDedupFiltered