
Sea y=mx+b nuestra recta predicción y N el número de puntos, el error de cada punto será (y(i) – ( m*x(i) + b ))2, y por lo tanto el error medio será el sumatorio entre 1 y N de dicha expresión, todo ello dividido entre N.
Su derivada parcial con respecto a m será ((-2*x(i))/N) * ( y(i) – ( m*x(i) + b ) ).
Con respecto a b será (-2/N) * ( y(i) – ( m*x(i) + b )
Si igualamos a cero ambas derivadas para calcular los extremos locales solo vamos a obtener una trivialidad: el error mínimo se dará cuando y(i) = m*x(i) + b.
En vez de eso vamos a inicializar las variables m y b en un valor arbitrario nulo, y vamos a ir avanzando hacia su mínimo local teniendo en cuenta que:
- Si la derivada es negativa, la función está decreciendo, por lo que para llegar a su mínimo local, debemos seguir avanzando -> Si la derivada parcial de la función error con respecto a b es negativa en un punto, la función del error con respecto a b está decreciendo en ese punto.
- Cuanto más lejos estemos en horizontal del extremo local, mayor será la derivada en valor absoluto, hasta ser cero en los extremos locales.
- El gradiente es un vector que contiene las derivadas parciales de una función con respecto a cada una de sus variables, e indica la dirección en la cual ela función varía más rápidamente y su módulo representa el ritmo de variación de dicha función en la dirección de dicho vector gradiente.
De esta forma, en cada iteración lo que debemos hacer es ir avanzando en la dirección que indique el gradiente (en el caso de una función de una variable, la única dirección en la que puede señalar su gradiente es la dirección de su variable dependiente), es decir, restando el gradiente a la posición actual.
Pero hay que tener en cuenta el ritmo al que suceden estas iteraciones.

Por ejemplo si nos encontramos en la función azul en x=0, su gradiente será -4. Si restamos, nos colocamos en x=4. Su gradiente ahora será 4, y, si restamos, nos devolverá al valor inicial, entrando así en un bucle infinito no convergente.
Si hacemos lo mismo pero sumando y restando un cuarto del gradiente:
| Valor de x | 1/4 * Gradiente |
|---|---|
| 0.0 | -1.0 |
| 1.0 | -0.5 |
| 1.5 | -0.25 |
| 1.75 | -0.125 |
| 1.875 | -0.0625 |
| 1.9375 | -0.03125 |
| 1.96875 | -0.015625 |
| 1.984375 | … |
Vemos que obviamente está convergiendo al valor buscado x=2.
Debemos identificar el intervalo útil de valores del learning rate. Si cogemos un valor demasiado grande, nuestra función se saltará el extremo local buscado, pues el orden de magnitud de su «velocidad» será mucho mayor al de las distancias características del «terreno» y no será capaz de introducirse en la zona buscada, quedando atrapado en un bucle infinito o bien requiriendo muchas iteraciones para lograr la convergencia. Si el valor es demasiado pequeño, igualmente requerirá un número de repeticiones demasiado elevado para alcanzar el valor buscado.

En este gráfico se muestran el número de iteraciones requeridas para alcanzar el resultado buscado de x=2 con menos de 0.01 de error:

Y en este se muestran el número de iteraciones requeridas para alcanzar el resultado buscado exacto de x=2:

Se puede apreciar que el learning rate óptimo será 0.5, que resuelve el problema en una única iteración.
Código de Codecademy para hacer una regresión lineal utilizando el descenso del gradiente:
meses = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
ingresos = [52, 74, 79, 95, 115, 110, 129, 126, 147, 146, 156, 184]
def get_gradient_at_b(x, y, b, m):
N = len(x)
diff = 0
for i in range(N):
x_val = x[i]
y_val = y[i]
diff += (y_val - ((m * x_val) + b))
b_gradient = -(2/N) * diff
return b_gradient
def get_gradient_at_m(x, y, b, m):
N = len(x)
diff = 0
for i in range(N):
x_val = x[i]
y_val = y[i]
diff += x_val * (y_val - ((m * x_val) + b))
m_gradient = -(2/N) * diff
return m_gradient
#Your step_gradient function here
def step_gradient(x, y, b_current, m_current):
b_gradient = get_gradient_at_b(x, y, b_current, m_current)
m_gradient = get_gradient_at_m(x, y, b_current, m_current)
b = b_current - (0.01 * b_gradient)
m = m_current - (0.01 * m_gradient)
return [b, m]
# current intercept guess:
b = 0
# current slope guess:
m = 0
b, m = step_gradient(meses, ingresos, b, m)
print(b, m)Códigos para calcular el mínimo de nuestra función azul:
#import matplotlib.pyplot as plt
lr = 0.5 #Cambiar aquí el learning rate para ver cómo afecta al resultado
puntos_x_prov = list(range(-20, 61, 1))
puntos_x = [x/10 for x in puntos_x_prov]
puntos_y = [x**2 - 4*x + 5 for x in puntos_x]
# plt.plot(puntos_x, puntos_y)
# plt.xlabel("x")
# plt.ylabel("y")
#plt.show()
def step_gradient(x,y):
x -= lr*(2*x -4)
return(x)
x = 0
y = x**2 - 4*x + 5
for i in range(10):
x = step_gradient(x,y)
print(x) #Esta línea imprime los sucesivos intentos para que se pueda
# ir apreciando la tendencia convergente.
Código para generar los dos últimos gráficos, así como los dos anteriores si se realizan pequeñas modificaciones:
import matplotlib.pyplot as plt
import numpy as np
puntos_x_prov = list(range(-20, 61, 1))
puntos_x = [x/10 for x in puntos_x_prov]
puntos_y = [x**2 - 4*x + 5 for x in puntos_x]
def step_gradient(x,lr):
x -= lr*(2*x -4)
return(x)
puntos__x = []
puntos__y = []
v = np.linspace(0.1, 0.9, num=1000)
for i in v:
lr = i
x = -1
intentos = 0
while abs(2-x) >= 0.1: #Aqui podemos meter un bucle con un número
# fijo de iteraciones y más tarde guardar, en vez de los intentos, el
# resultado obtenido, para generar los otros dos gráficos
x = step_gradient(x,lr)
intentos +=1
puntos__x.append(lr)
puntos__y.append(intentos)
plt.plot(puntos__x, puntos__y, '-')
ax=plt.gca()
#ax.invert_xaxis()
#ax.invert_yaxis()
plt.xlabel("lr")
plt.ylabel("iteraciones necesarias")
plt.show()