Laboratorio 1 - Parte 1 Regresión polinomial múltiple#

!wget -nc --no-cache -O init.py -q https://raw.githubusercontent.com/jdariasl/Intro_ML_2025/master/init.py
import init; init.init(force_download=False); init.get_weblink()
from local.lib.rlxmoocapi import submit, session
import inspect
session.LoginSequence(endpoint=init.endpoint, course_id=init.course_id, lab_id="L01.01", varname="student");
#configuración del laboratorio
# Ejecuta esta celda!
from Labs.commons.utils.lab1 import *
_, db, x, y = part_1()
y = y.reshape(np.size(y), 1)

Ejercicio 1: Contextualización del problema#

El problema de regresión que abordaremos consiste en predecir el valor de la humedad absoluta en el aire, a partir de varias variables sensadas en el aire (Para más información sobre la base de datos y la contextualización del problema, consulte: http://archive.ics.uci.edu/ml/datasets/air+quality).

# tienes ya cargadas las siguientes variables:
print("conjunto de datos", x)
print("variable a predecir", y)
#Ejercicio de Codigo
def num_muestras_carac(X):
    """Esta función es encargada retornar el número de muestras
        y características del conjunto de datos X

        X: matriz numpy
        retorna:
            número de caracteristicas (int/float)
            número de muestras (int/float)
    """

    return

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T1');

Ejercicio 2#

Analice los siguientes métodos de la teoría de modelos de regresión polinomial múltiple:

  1. error cuadrático medio (ECM)

  2. modelo de regresión múltiple (regression)

  3. calculo del costo de la regresión (cost)

  4. extension de matriz (extension_matriz)

La siguiente celda contiene la implementación de estas 4 funciones. Analizar y entender su funcionamiento

def ECM(Y_est,Y):
    """función para calcular el error cuadratico medio
    Y_est: debe contener los valores predichos por el modelo
    Y: debe contener los valores reales
    retorna: error cuadratico medio
    """
    N = np.size(Y)
    ecm = np.sum((Y_est.reshape(N,1) - Y.reshape(N,1))**2)/(N)
    return ecm

def regression(X, W):
    """calcula la regresión multiple
    X: los valores que corresponden a las características
    W: son los pesos usadados para realizar la regresión
    retorna: valor estimado
    """
    Yest = np.dot(X,W)    #con np.dot se realiza el producto matricial. Aquí X es dim [Nxd] y W es dim [dx1]
    return Yest           #Esta variable contiene la salida de f(X,W)


def cost_f(W,X,Y):
    """calcula la función de costo de la regresión para un vector W dado
    W: son los pesos usadados para realizar la regresión
    X: los valores que corresponden a las caractersiticas
    Y: el valor de salida esperado

    retorna: valor de costo
    """

    m = len(Y)
    y_est = regression(X,W)
    cost = (1/2*m) * np.sum(np.square(y_est-Y))
    return cost

def extension_matriz(X):
    """función que realiza la extensión de la matriz X
    X: los valores que corresponden a las características sin extender
    Y: el valor de salida esperado

    retorna: X_ext: matriz con unos extendidos, Y: matriz con dimensiones ajustadas
    """
    #Obtenemos las dimensiones antes de exteneder la matriz
    caracterisitcas, muestras = num_muestras_carac(X)
    #Extendemos la matriz X
    unos = np.ones(muestras).reshape(-1,1)
    X_ext = np.concatenate((unos, X), axis=1)
    X_ext = X_ext.reshape(muestras, caracterisitcas+1)
    return X_ext
#Pregunta Abierta
#¿cual es el objetivo de la extension_matriz? recordar que estamos "ajustando" en una regresión

respuesta = ''

Ahora vamos a completar el código de la regla de actualización de los parámetros del algoritmo de gradiente_descedente:

\[w_j(iter) = w_j(iter-1) - \eta \frac{\partial E(w)}{\partial w_j}\]

recordar que

\[ \frac{\partial E(w)}{\partial w_j} = \frac{\partial E({\bf{w}})}{\partial w_j} = \frac{1}{N}\sum_{i=1}^{N}\left( f({\bf{x}}_i,{\bf{w}}) - y_i\right) \frac{\partial }{\partial w_j} f({\bf{x}}_i, {\bf{w}})\]

Recuerde que debe usar las funciones ya implementadas y no usar ninguna otra libreria, adicional a las librerias ya pre-cargadas como numpy (se puede llamar con np.)

## Ejercicio de codigo
def gradiente_descendente(X, Y, eta, iteraciones, w_ini = None):
    """Gradiente descendente para regresión lineal múltiple
    X: Matriz de datos
    Y: vector con los valores a predecir
    W: Vector de parámetros del modelo
    eta: Taza de aprendizaje

    retorna: W el valor de los parámetros de la regresión polinómica
             costos: array con el costo por iteración
    """
    # nuevamente usamos la función
    # para saber el número de muestras y características
    X_ext = extension_matriz(X)
    caracterisitcas, N = num_muestras_carac(X_ext)
    #Inicializamos el vector de parámetros con ceros
    if w_ini is not None:
        W = w_ini
    else:
        W = np.zeros((1,caracterisitcas))
    W = W.reshape(np.size(W), 1)
    # incializamos vector para almacenar costos
    costos = np.zeros(iteraciones)

    for i in range(iteraciones):
        ## Aca debes completar la función! recuerda que solo debes usar numpy (np.funcion_a_usar)
        # o las funciones definidas anteriormente
        # Puedes usar las funciones definidas antes
        y_est =
        f_xw_min_yi =

        # acutaliza
        W =

        #Estima el valor de la función de costo para el W actual
        costos[i] = cost(W,X_ext,Y)

    return W, costos

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T2');

Ejercicio 3: Entrenamiento#

Con la función implementada vamos a entrenar un modelo y calcular su error de entrenamiento. Antes de realizar esto, debemos separar nuestro conjunto de datos.

# esto para lograr reproductibilidad
# de nuestro modelo
random.seed(1)
# usamos nuestra funcion para obtener el numero de muestras
_, N = num_muestras_carac(x)
ind=np.random.permutation(N)
Xtrain = x[ind[0:int(math.ceil(0.7*N))],:]
Xtest = x[ind[int(math.ceil(0.7*N)):N],:]
Ytrain = y[ind[0:int(math.ceil(0.7*N))]]
Ytest = y[ind[int(math.ceil(0.7*N)):N]]
#Pregunta Abierta
# ¿Porqué debemos hacer partición de datos? ¿Qué representa Xtrain, qué diferencia tiene con Xtest?
respuesta = ''

Ahora entrena ejecutando la siguiente linea de codigo y verifiquemos el comportamiento del costo

W, costo = gradiente_descendente(Xtrain, Ytrain, eta = 0.0001, iteraciones=5)
# graficar iteraciones y el costo
plt.plot(range(5), costo)
plt.show()

El costo es la medida que el algoritmo de optimización intenta mejorar. Sin embargo, para este tipo de problemas al final debemos evaluar que tan bien estamos modelando nuestra salida. Vamos a evaluar nuestro modelo calculando el error cuadrático medio. Para ello vamos crear a una función. Recuerda usar las funciones definidas anteriormente.

## Ejercicio de Código
def evaluar_modelo (W, X_to_test, Y_True):
    """ función que evalúa un modelo de regresión usando el error cuadrático medio

    W: es un matriz con los parámetros del modelo entrenados
    X_to_test: conjunto de datos para usar en la evaluación del modelo
    Y_True: valores reales para usar en la evaluación del modelo

    retorna: el error cuadrático medio
    """
    ## Comienza a completar tu codigo. recuerda usar la funciones ya definidas
    X_to_test_ext = extension_matriz(X_to_test)
    y_est =
    error =

    return(error)

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T3');
# y ahora usala para calcular el error, para evaluar el modelo
error_train = evaluar_modelo(W, X_to_test = Xtrain,  Y_True = Ytrain)
print("error en entrenamiento del modelo", error_train)
error_test = evaluar_modelo(W, X_to_test = Xtest,  Y_True = Ytest)
print("error en la evaluación del modelo", error_test)
# ¿Que tan bueno es tu modelo?
respuesta = ''

Ejercicio 4: Polinomio de orden superior#

Hasta ahora lo que hemos realizado es un regresión lineal de orden 1. Nuestro siguiente objetivo es tomar esta regresión y transformarla en un polinomio de mayor orden. Comprenda el funcionamiento de la función propuesta:

#Potencia de polinomio
def potenciaPolinomio(X,grado):
    """calcula la potencia del polinomio
    X: los valores que corresponden a las caractersiticas
    grado: es el grado para realizar la potencia al polinomio
    retorna: el valor de X despues de elevarlo al grado del polinimoo indicado
    """
    X2 = X.copy()

    if grado != 1:
        for i in range(2,grado+1):
            Xadd = X**i
            X2 = np.concatenate((X2, Xadd), axis=1)

    return X2

ahora debemos usar esta función para completar la siguiente. PISTAS

  • Usa las funciones previamente construidas

  • Para completar gradiente_descendente_poly Tener presente que buscamos realizar este proceso: aplicar la potenciaPolinomio -> aplicar gradiente descendente

  • Para completar evaluar_modelo_poly Tener presente que buscamos realizar este proceso: aplicar la potenciaPolinomio -> evaluar el modelo

## Ejercicio de codigo
def gradiente_descendente_poly (X, Y, eta, iteraciones, grado):
    """Gradiente descendente para regresión lineal múltiple
    X: Matriz de datos extendida
    Y: vector con los valores a predecir
    W: Vector de parámetros del modelo
    eta: Taza de aprendizaje
    iteraciones: numero de iteraciones maximo para el gradiente
    grado: el valor del polinomio a usar

    retorna: W el valor de de los parametros de regresión polinomica
             costo: array con el valor del costo por cada iteracion

    """
    ## completa el codigo
    X2 =
    W, costo =
    return (W, costo)

def evaluar_modelo_poly (W, X_to_test, Y_True, grado):
    """ funcion que evalua un modelo de regresión usando el error cuadratico medio

    W: es un matriz con los parametros del modelo entrenados
    X_to_test: conjunto de datos para usar en el evaluamiento del modelo
    Y_True: valores reales para usar en el evaluamiento del modelo
    grado: grado del polinimio a usar

    retorna: el error cuadratico medio
    """
    ## Comienza a completar tu codigo. recuerda usar la funciones ya definidas
    X2 =
    error =

    return(error)

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T4');

Entrenemos y evaluemos el comportamiento del costo con la regresion polinómica ¿Vemos algún cambio positivo?

# entrenamos
W, costo_poly = gradiente_descendente_poly(Xtrain, Ytrain, eta = 0.0001, iteraciones=5, grado = 2)
plt.plot(range(5), costo_poly)
# completa los parametros para evaluar el modelo
error_test = evaluar_modelo_poly(W, X_to_test = Xtest,  Y_True = Ytest, grado = 2)
print("error en la evaluación del modelo", error_test)

Ejercicio 5: Normalización#

En nuestro primer experimento vamos a evaluar el rendimiento del modelo usando varias tasas de aprendizaje y grados de polinomios. Vamos a dejar por ahora un número de iteraciones fijas = 5. Para ello completa la siguiente función.

## Ejercicio de codigo
def experimentar (Xtrain, Xtest, Ytrain, Ytest, tasas, grados):
    """ función para realizar experimentos.
    Xtrain: conjunto de datos de entrenamiento
    Xtest: conjunto de datos para evaluación
    Ytrain: salidas reales del conjunto de entrenamiento
    Ytest: salidas reales del conjunto de evaluación
    tasas: Es una lista con los valores númericos de tasas de aprendizaje
        para realizar los experimentos
    grados: Es una lista con los valores númericos de grados
        para realizar los experimentos
    retorna: un dataframe con el resultados de los experimentos
    """
    numero_iter = 5

    resultados = pd.DataFrame()
    idx = 0 # indice
    for eta in tasas:
        for grado in grados:

            # ignorar el costo
            W, _ = ...
            error = ...

            resultados.loc[idx,'grado'] = grado
            resultados.loc[idx,'tasa de aprendizaje'] = eta
            resultados.loc[idx,'ecm'] = error
            idx = idx+1

    return (resultados)
## Ahora ejecutamos la función desarrollada para ver los resultados
tasas_aprendizaje = [1e-6, 1e-5, 1e-3, 1e-2]
grados_polinomio = [1,2]
resultados_ex1 = experimentar(Xtrain, Xtest, Ytrain, Ytest, tasas_aprendizaje, grados_polinomio)
#Imprimimos los resultados
resultados_ex1

Si has implementado todo correctamente, parecieria que nuestros entrenamientos no esta logrando buenos resultados (hasta parece haber errores infinitos! o no determinados!).

Crea una función que implemente la normalización z-score: \(\bar{x} = \frac{x-\mu}{\sigma}\). Debe estimar los parámetros de la normalización con los datos de entrenamiento y aplicar la normalización tanto a los datos de entrenamiento como a los de test.

## Ejercicio de codigo
def normalizar(Xtrain, Xtest):
    """ función para normalizar los datos
    Xtrain: matriz de datos de entrenamiento a normalizar
    Xtest: matriz de datos de evaluación a normalizar
    retorna: matrices normalizadas
    """

    Xtrain_n = ...
    Xtest_n = ...
    # si hay una desviacion por cero, reemplazamos los nan
    Xtrain_n = np.nan_to_num(Xtrain_n)
    Xtest_n = np.nan_to_num(Xtest_n)
    return (Xtrain_n, Xtest_n)

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T5');

Ahora vuelve a realizar los mismos experimentos, pero esta vez usa los valores de salida de la función anterior.

Xtrain_n, Xtest_n = normalizar(Xtrain, Xtest)
resultados_ex2 = experimentar(Xtrain_n, Xtest_n, Ytrain, Ytest, tasas_aprendizaje, grados_polinomio)
#para ver los resultados
resultados_ex2
# ejecuta esta linea de codigo para graficar tus resultados
import matplotlib.pyplot as plt

# Grouping the data for plotting
grouped = resultados_ex2.groupby(['tasa de aprendizaje', 'grado'])['ecm'].mean().unstack()

# Creating the bar plot
fig, ax = plt.subplots()
grouped.plot(kind='bar', ax=ax)

# Labels and title
ax.set_xlabel('Tasa de aprendizaje')
ax.set_ylabel('ECM')
ax.set_title('ECM vs Tasa de Aprendizaje por Grado')

# Adding legend
ax.legend(title='Grado')

plt.xticks(rotation=45)  # Rotate x-axis labels if necessary
plt.show()

Ten en cuenta el resutaldo de los dos experimentos y responde la siguiente pregunta abierta

# Pregunta abierta
#¿Qué proceso hace la normalización sobre los datos? Consulte qué es el número de condición de una matriz y analice qué relación tiene ese concepto con el proceso de normalización
respuesta = ""
# Pregunta abierta
#¿cuáles son los tipos de normalización más comunes. ¿Piensa en cuándo es más apropiado usar uno u otro?
respuesta = ""

Finalmente, en nuestro tercer experimento, vamos a ver el efecto de las iteraciones sobre el error. Completa la siguiente función. Esta vez la tasa de aprendizaje es constante.

## ejercicio de codigo
def experimentar_2 (Xtrain, Xtest, Ytrain, Ytest, iteraciones, grados):
    """ función para realizar experimentos.
    Xtrain: conjunto de datos de entrenamiento
    Xtest: conjunto de datos para evaluación
    Ytrain: salida real para el conjunto de entrenamiento
    Ytest: salida real para el conjunto de evaluación
    tasas: Es una lista con los valores númericos de tasas de aprendizaje
        para realizar los experimentos
    rangos: Es una lista con los valores númericos de grados
        para realizar los experimentos
    retorna: un dataframe con el resultados de los experimentos
    """
    eta = 1e-2
    resultados = pd.DataFrame()
    idx = 0 # indice
    for itera in iteraciones:
        for grado in grados:
            # ignora el costo
            W , _= ...
            error = ...

            resultados.loc[idx,'iteraciones'] = itera
            resultados.loc[idx,'grado'] = grado
            resultados.loc[idx,'ecm'] = error
            idx = idx+1
    return (resultados)
num_iters = [1,5,10,50, 100,200, 1000, 2000]
grados_polinomio = [1,2]
# usamos la función para evaliar los resultados
resultados_ex3 = experimentar_2(Xtrain_n, Xtest_n, Ytrain, Ytest, num_iters, grados_polinomio )
# ejecuta esta linea de código para ver gráficamente tus resultados
import matplotlib.pyplot as plt

grados = resultados_ex3['grado'].unique()
fig, axes = plt.subplots(1, len(grados), figsize=(5 * len(grados), 5), sharey=True)

if len(grados) == 1:
    axes = [axes]

for ax, grado in zip(axes, grados):
    subset = resultados_ex3[resultados_ex3['grado'] == grado]
    ax.plot(subset['iteraciones'], subset['ecm'], marker='o', linestyle='-')
    ax.set_title(f'Grado {grado}')
    ax.set_xlabel('Iteraciones')
    ax.set_ylabel('ECM')

plt.tight_layout()
plt.show()