Implementar Gradient Descent usando NumPy y Python

Vaibhav Vaibhav 30 enero 2023
  1. Descenso de gradiente
  2. Implementación de Gradient Descent usando Python
Implementar Gradient Descent usando NumPy y Python

El aprendizaje automático es una tendencia en estos días. Cada empresa o startup está tratando de encontrar soluciones que utilicen el aprendizaje automático para resolver problemas del mundo real. Para resolver estos problemas, los programadores crean modelos de aprendizaje automático entrenados sobre algunos datos esenciales y valiosos. Al entrenar modelos, hay muchas tácticas, algoritmos y métodos para elegir. Algunos podrían funcionar y otros no.

Generalmente, Python se usa para entrenar estos modelos. Python tiene soporte para numerosas bibliotecas que facilitan la implementación de conceptos de aprendizaje automático. Uno de esos conceptos es el descenso de gradientes. En este artículo, aprenderemos cómo implementar el descenso de gradientes usando Python.

Descenso de gradiente

Gradient Descent es un algoritmo de optimización convexo basado en funciones que se utiliza al entrenar el modelo de aprendizaje automático. Este algoritmo nos ayuda a encontrar los mejores parámetros del modelo para resolver el problema de manera más eficiente. Mientras se entrena un modelo de aprendizaje automático sobre algunos datos, este algoritmo ajusta los parámetros del modelo para cada iteración, lo que finalmente produce un mínimo global, a veces incluso un mínimo local, para la función diferenciable.

Al ajustar los parámetros del modelo, un valor conocido como tasa de aprendizaje decide la cantidad en la que se deben ajustar los valores. Si este valor es demasiado grande, el aprendizaje será rápido y podríamos terminar desajustando el modelo. Y, si este valor es demasiado pequeño, el aprendizaje será lento y podríamos terminar sobreajustando el modelo a los datos de entrenamiento. Por lo tanto, tenemos que encontrar un valor que mantenga un equilibrio y finalmente produzca un buen modelo de aprendizaje automático con buena precisión.

Implementación de Gradient Descent usando Python

Ahora que hemos terminado con la breve teoría del descenso de gradientes, entendamos cómo podemos implementarla con la ayuda del módulo NumPy y el lenguaje de programación Python con la ayuda de un ejemplo.

Entrenaremos un modelo de aprendizaje automático para la ecuación y = 0.5x + 2, que tiene la forma y = mx + c o y = ax + b. Esencialmente, entrenará un modelo de aprendizaje automático sobre los datos generados mediante esta ecuación. El modelo adivinará los valores de m y c o a y b, es decir, la pendiente y la intersección, respectivamente. Dado que los modelos de aprendizaje automático necesitan algunos datos de los que aprender y algunos datos de prueba para probar su precisión, generaremos los mismos utilizando un script de Python. Realizaremos regresión lineal para realizar esta tarea.

Los insumos de capacitación y los insumos de prueba estarán en la siguiente forma; un array NumPy bidimensional. En este ejemplo, la entrada es un valor entero único y la salida es un valor entero único. Dado que una sola entrada puede ser un array de valores enteros y flotantes, se utilizará el siguiente formato para promover la reutilización del código o la naturaleza dinámica.

[[1], [2], [3], [4], [5], [6], [7], ...]

Y las etiquetas de entrenamiento y las etiquetas de prueba tendrán el siguiente formato; un array NumPy unidimensional.

[1, 4, 9, 16, 25, 36, 49, ...]

Código Python

A continuación se muestra la implementación del ejemplo anterior.

import random
import numpy as np
import matplotlib.pyplot as plt


def linear_regression(inputs, targets, epochs, learning_rate):
    """
    A utility function to run linear regression and get weights and bias
    """
    costs = []  # A list to store losses at each epoch
    values_count = inputs.shape[1]  # Number of values within a single input
    size = inputs.shape[0]  # Total number of inputs
    weights = np.zeros((values_count, 1))  # Weights
    bias = 0  # Bias

    for epoch in range(epochs):
        # Calculating the predicted values
        predicted = np.dot(inputs, weights) + bias
        loss = predicted - targets  # Calculating the individual loss for all the inputs
        d_weights = np.dot(inputs.T, loss) / (2 * size)  # Calculating gradient
        d_bias = np.sum(loss) / (2 * size)  # Calculating gradient
        weights = weights - (learning_rate * d_weights)  # Updating the weights
        bias = bias - (learning_rate * d_bias)  # Updating the bias
        cost = np.sqrt(
            np.sum(loss ** 2) / (2 * size)
        )  # Root Mean Squared Error Loss or RMSE Loss
        costs.append(cost)  # Storing the cost
        print(
            f"Iteration: {epoch + 1} | Cost/Loss: {cost} | Weight: {weights} | Bias: {bias}"
        )

    return weights, bias, costs


def plot_test(inputs, targets, weights, bias):
    """
    A utility function to test the weights
    """
    predicted = np.dot(inputs, weights) + bias
    predicted = predicted.astype(int)
    plt.plot(
        predicted,
        [i for i in range(len(predicted))],
        color=np.random.random(3),
        label="Predictions",
        linestyle="None",
        marker="x",
    )
    plt.plot(
        targets,
        [i for i in range(len(targets))],
        color=np.random.random(3),
        label="Targets",
        linestyle="None",
        marker="o",
    )
    plt.xlabel("Indexes")
    plt.ylabel("Values")
    plt.title("Predictions VS Targets")
    plt.legend()
    plt.show()


def rmse(inputs, targets, weights, bias):
    """
    A utility function to calculate RMSE or Root Mean Squared Error
    """
    predicted = np.dot(inputs, weights) + bias
    mse = np.sum((predicted - targets) ** 2) / (2 * inputs.shape[0])
    return np.sqrt(mse)


def generate_data(m, n, a, b):
    """
    A function to generate training data, training labels, testing data, and testing inputs
    """
    x, y, tx, ty = [], [], [], []

    for i in range(1, m + 1):
        x.append([float(i)])
        y.append([float(i) * a + b])

    for i in range(n):
        tx.append([float(random.randint(1000, 100000))])
        ty.append([tx[-1][0] * a + b])

    return np.array(x), np.array(y), np.array(tx), np.array(ty)


learning_rate = 0.0001  # Learning rate
epochs = 200000  # Number of epochs
a = 0.5  # y = ax + b
b = 2.0  # y = ax + b
inputs, targets, train_inputs, train_targets = generate_data(300, 50, a, b)
weights, bias, costs = linear_regression(
    inputs, targets, epochs, learning_rate
)  # Linear Regression
indexes = [i for i in range(1, epochs + 1)]
plot_test(train_inputs, train_targets, weights, bias)  # Testing
print(f"Weights: {[x[0] for x in weights]}")
print(f"Bias: {bias}")
print(
    f"RMSE on training data: {rmse(inputs, targets, weights, bias)}"
)  # RMSE on training data
print(
    f"RMSE on testing data: {rmse(train_inputs, train_targets, weights, bias)}"
)  # RMSE on testing data
plt.plot(indexes, costs)
plt.xlabel("Epochs")
plt.ylabel("Overall Cost/Loss")
plt.title(f"Calculated loss over {epochs} epochs")
plt.show()

una breve explicación del código Python

El código tiene implementados los siguientes métodos.

  • linear_regression(inputs, targets, epochs, learning_rate): esta función realiza la regresión lineal sobre los datos y devuelve los pesos del modelo, el sesgo del modelo y los costos o pérdidas intermedios para cada época
  • plot_test(inputs, targets, weights, bias): esta función acepta entradas, objetivos, pesos y sesgos y predice la salida de las entradas. Luego trazará un gráfico para mostrar qué tan cerca estaban las predicciones del modelo de los valores reales.
  • rmse(inputs, targets, weights, bias): esta función calcula y devuelve el error cuadrático medio de algunas entradas, pesos, sesgos y objetivos o etiquetas.
  • generate_data(m, n, a, b): esta función genera datos de muestra para que el modelo de aprendizaje automático se entrene mediante la ecuación y = ax + b. Genera los datos de entrenamiento y prueba. m y n se refieren al número de muestras de entrenamiento y prueba generadas, respectivamente.

A continuación se muestra el flujo de ejecución del código anterior.

  • Se llama al método generate_data() para generar algunas muestras de entrada de entrenamiento, etiquetas de entrenamiento, entradas de prueba y etiquetas de prueba.
  • Algunas constantes, como la tasa de aprendizaje y el número de épocas, se inicializan.
  • Se llama al método linear_regression() para realizar una regresión lineal sobre los datos de entrenamiento generados, y se almacenan los pesos, el sesgo y los costes encontrados en cada época.
  • Los pesos y el sesgo del modelo se prueban utilizando los datos de prueba generados, y se dibuja un gráfico que muestra lo cerca que están las predicciones de los valores reales.
  • Se calcula y se imprime la pérdida de RMSE para los datos de entrenamiento y de prueba.
  • Los costes encontrados para cada época se representan con el módulo Matplotlib (una biblioteca de gráficos para Python).

Producción

El código Python generará el estado de entrenamiento del modelo en la consola para cada época o iteración. Será como sigue.

...
Iteration: 199987 | Cost/Loss: 0.05856315870190882 | Weight: [[0.5008289]] | Bias: 1.8339454694938624
Iteration: 199988 | Cost/Loss: 0.05856243033468181 | Weight: [[0.50082889]] | Bias: 1.8339475347628937
Iteration: 199989 | Cost/Loss: 0.05856170197651294 | Weight: [[0.50082888]] | Bias: 1.8339496000062387
Iteration: 199990 | Cost/Loss: 0.058560973627402625 | Weight: [[0.50082887]] | Bias: 1.8339516652238976
Iteration: 199991 | Cost/Loss: 0.05856024528735169 | Weight: [[0.50082886]] | Bias: 1.8339537304158708
Iteration: 199992 | Cost/Loss: 0.05855951695635694 | Weight: [[0.50082885]] | Bias: 1.8339557955821586
Iteration: 199993 | Cost/Loss: 0.05855878863442534 | Weight: [[0.50082884]] | Bias: 1.8339578607227613
Iteration: 199994 | Cost/Loss: 0.05855806032154768 | Weight: [[0.50082883]] | Bias: 1.8339599258376793
...

Una vez que se entrena el modelo, el programa probará el modelo y dibujará una gráfica con las predicciones del modelo y los valores reales. La trama entrenada será similar a la que se muestra a continuación. Tenga en cuenta que, dado que los datos de prueba se generan utilizando el módulo random, los valores aleatorios se generarán sobre la marcha y, por lo tanto, es muy probable que el gráfico que se muestra a continuación sea diferente al suyo.

Gráfica para predicciones VS valores verdaderos

Como podemos ver, las predicciones casi se superponen a todos los valores verdaderos (las predicciones se representan mediante x y los objetivos se representan mediante o). Esto significa que el modelo ha predicho casi con éxito los valores para a y b o m y c.

A continuación, el programa imprime todas las pérdidas encontradas durante el entrenamiento del modelo.

Costos encontrados para cada época durante la fase de entrenamiento

Como podemos ver, la pérdida cayó inmediatamente desde alrededor de 60 cerca de 0 y continuó manteniéndose en torno a él durante el resto de las épocas.

Por último, se imprimieron las pérdidas de RMSE para los datos de entrenamiento y prueba, y los valores predichos para a y b o los parámetros del modelo.

Weights: [0.5008287639956263]
Bias: 1.8339723159878247
RMSE on training data: 0.05855296238504223
RMSE on testing data: 30.609530314187527

La ecuación que usamos para este ejemplo fue y = 0.5x + 2, donde a = 0.5 y b = 2. Y, el modelo predijo a = 0.50082 y b = 1.83397, que están muy cerca de los valores reales. Es por eso que nuestras predicciones se superponen con los verdaderos objetivos.

Para este ejemplo, establecemos el número de épocas en 200000 y la tasa de aprendizaje en 0.0001. Afortunadamente, este es solo un conjunto de configuraciones que nos dio resultados extremadamente buenos, casi perfectos. Recomiendo encarecidamente a los lectores de este artículo que jueguen con estos valores y vean si pueden encontrar algunos conjuntos de valores que produzcan resultados aún mejores.

Vaibhav Vaibhav avatar Vaibhav Vaibhav avatar

Vaibhav is an artificial intelligence and cloud computing stan. He likes to build end-to-end full-stack web and mobile applications. Besides computer science and technology, he loves playing cricket and badminton, going on bike rides, and doodling.

Artículo relacionado - Python Math