Regresión lineal con gradiente descendente

Regresión lineal con gradiente descendente en Python

Con el fin de repasar y compartir las cosas que voy aprendiendo en el Máster de Data Science de KSchool, voy a ir publicando una serie de artículos explicativos de algunas de las cosas que considere más interesantes de las clases. ¡Y este es el primero de ellos!

Hace unos meses ya os hablé de regresiones lineales, en aquel caso utilizando incluso una hoja de cálculo, pero esta vez vamos a hacerlo un poquito más avanzado y utilizar Python. Además, explicaré lo que es el gradiente descendiente, la base de muchas técnicas de aprendizaje automático del que espero hablar en el futuro en el blog. Vamos allá.

¿Qué es la regresión lineal?

Aunque ya os hablé de ella en el artículo de predicciones simples en la analítica digital, no está de más recordar el concepto y profundizar un poquito más. Yendo a la base del conocimiento occidental, la Wikipedia define la regresión lineal como:

Pero si sois como yo, es decir, que no sois ningunos ases de las matemáticas y su lenguaje ininteligible, seguramente lo entenderéis mejor si os digo que una regresión lineal nos puede ayudar a predecir determinados valores de una variable dependiente (llamémosla Y o respuesta) en función de una o varias variables independientes (llamémoslas Xi o predictores).

Aunque existen diferentes tipos de regresiones, en este artículo voy a tratar la más sencilla, la regresión lineal simple, que se basa en la función de la recta: y = ax + b.

¿Qué es el gradiente descendente?

Se trata de un algoritmo de optimización que tiene por objetivo minimizar una función a través del ajuste iterativo de sus parámetros. Esto quiere decir, que se buscará siempre los valores que reduzcan el coste de la función a través de un bucle de comprobaciones y asignaciones, como veremos más adelante.

Para entenderlo mejor, debemos tener en la mente que el gradiente es la pendiente o inclinación (derivada) y que el algoritmo se «deslizará» por ésta de manera iterativa hasta encontrar el punto de menor coste para la función.

¿En qué consiste la regresión lineal con gradiente descendente?

La regresión lineal simple se basa en la función de hipótesis ℎ𝜃(𝑥), como veréis, muy similar a la de la recta:

A su vez, esta función tiene un coste, que es exactamente la suma de los cuadrados de los errores frente a los datos reales. Para entendernos, la función de coste nos permite medir la diferencia entre los datos reales y los valores obtenidos de nuestra función hipótesis, y se calcula así:

def cost_function(x, y):
    return lambda theta0, theta1: np.sum((theta0 + theta1 * x - y) ** 2) / len(x)

J = cost_function(x,y)

Así pues, el objetivo es encontrar el mínimo coste de nuestra función, que nos vendrá dado por unos valores concretos 𝜃0 y 𝜃1, que serán calculados a través del gradiente descendente. Como ya he dicho, este algoritmo es iterativo y trata de encontrar estos valores buscando que el gradiente (la inclinación o pendiente) prácticamente desaparezca.

Para ello, utilizaremos la regla de la cadena de las derivadas parciales. Esta regla se utiliza para derivar funciones compuestas y, como vais a ver, no es otra cosa que derivar la función y multiplicar por lo de dentro, en este caso, con la función de coste.

def derivative_theta_0(x, y):
    return lambda theta0, theta1: 2/len(x) * np.sum(theta0 + theta1 * x - y)

def derivative_theta_1(x, y):
    return lambda theta0, theta1: 2/len(x) * np.sum((theta0 + theta1 * x - y) * x)

J0 = derivative_theta_0(x,y)
J1 = derivative_theta_1(x,y)

Una vez tenemos esto, solo quedaría iterar un número de veces que vendrá fijado por el criterio de convergencia 𝜖, el cual marcará la finalización de la iteración, y de un criterio de aprendizaje 𝛼, que determinará la manera de avanzar del algoritmo hacia la reducción de la función y será el número por el que se multiplique la regla de la cadena anterior tal que así:

iterations = 10000 # num. max. de iteraciones
alpha = 0.01 # criterio de aprendizaje
epsilon = 0.01 # criterio de convergencia

for i in range(0,iterations):
        cost = J(theta0,theta1)
        Jp0 = J0(theta0,theta1)
        Jp1 = J1(theta0,theta1)

        theta0 = theta0 - alpha * Jp0
        theta1 = theta1 - alpha * Jp1

        cost_new = J(theta0,theta1) 
        convergence = np.abs(cost_new - cost) < epsilon
        cost = cost_new

        if convergence == True:
            print("Convergence FOUND!")
            print("Theta0: " + str(theta0))
            print("Theta1: " + str(theta1))
            print(str(i) + " iterations")
            print("Cost: " + str(cost))
            break

Así pues, con 𝜖 y 𝛼 deberemos estar atentos, dado que un alpha demasiado elevado para nuestra función puede hacer que nos pasemos del punto de convergencia, y uno muy pequeño que el algoritmo vaya muy lento.

Una vez tenemos la función minimizada, tendremos unos nuevos valores 𝜃0 y 𝜃1 para nuestra función hipótesis inicial y tan solo nos quedará dar valores a la variable independiente x para conocer el valor de ℎ𝜃(𝑥).

¿Cómo se implementa la regresión lineal con gradiente descendente en Python?

Vamos ahora a lo que mola, que es implementar todo esto que he tratado de explicar con Python. Lo primero que tendremos que hacer es contar con un dataset apropiado, para ejemplificar el artículo, he descargado uno de Kaggle sobre la relación entre altura y peso. Así pues, vamos a ver cómo influye la primera sobre la segunda. Puedes descargar el csv aquí.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

def gradient_descent(x, y, theta0 = 0, theta1 = 0, iterations = 10000, alpha = 0.01, epsilon = 0.01):
    
    def cost_function(x, y):
        return lambda theta0, theta1: np.sum((theta0 + theta1 * x - y) ** 2) / len(x)
    
    def derivative_theta_0(x, y):
        return lambda theta0, theta1: 2/len(x) * np.sum(theta0 + theta1 * x - y)

    def derivative_theta_1(x, y):
        return lambda theta0, theta1: 2/len(x) * np.sum((theta0 + theta1 * x - y) * x)
    
    J = cost_function(x,y)
    J0 = derivative_theta_0(x,y)
    J1 = derivative_theta_1(x,y)
    
    convergence = False
    for i in range(0,iterations):
        cost = J(theta0,theta1)
        Jp0 = J0(theta0,theta1)
        Jp1 = J1(theta0,theta1)

        theta0 = theta0 - alpha * Jp0
        theta1 = theta1 - alpha * Jp1

        cost_new = J(theta0,theta1) 
        convergence = np.abs(cost_new - cost) < epsilon
        cost = cost_new

        if convergence == True:
            print("Convergence FOUND!")
            print("Theta0: " + str(theta0))
            print("Theta1: " + str(theta1))
            print(str(i) + " iterations")
            print("Cost: " + str(cost))
            break
    
    if convergence == True:
        return theta0,theta1
    else:
        return 0,0

Si ahora ejecutamos la función con unos valores apropiados:

data = pd.read_csv('./data.csv')

x = data['Height']
y = data['Weight']

th0, th1 = gradient_descent(x,y,theta0 = 0, theta1 = 0,alpha=0.2,epsilon=0.00001)

Finalmente, comprobamos los valores con matplotlib:

plt.plot(x,th1*x+th0,'r-')
plt.title('Height vs Weight\ny = ' + str(round(th1,2)) + ' * x + ' + str(round(th0,2)))
plt.xlabel('Height')
plt.ylabel('Weight')
plt.scatter(x, y)

Por último, quería dejaros un par de enlaces que me han ayudado a entender mejor la regresión lineal con gradiente descendente y que pueden aclararos todavía más las ideas:

https://www.codificandobits.com/blog/el-gradiente-descendente/

https://medium.com/@aprendizaje.maq/regresi%C3%B3n-lineal-con-gradiente-descendente-c3b5ca97e27c

Miguel

Ecommerce Data Analyst
¿Compartes?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *