Implementación de Gradiente Descendente en Julia-0.4.5


In [405]:
# Carga de librerías
using Gadfly;
using DataFrames;

In [449]:
# Lectura de set demo (2 variables (x1 e Y), 50 datos en total)
df = readtable("data_regresion_lineal.txt", header=true);

In [463]:
# Estadísticas básicas del set de datos
describe(df)


x1
Min      0.7
1st Qu.  13.275
Median   25.4
Mean     25.511999999999997
3rd Qu.  37.699999999999996
Max      50.1
NAs      0
NA%      0.0%

y
Min      71.4
1st Qu.  447.45
Median   784.25
Mean     751.8560000000001
3rd Qu.  1048.4
Max      1420.8
NAs      0
NA%      0.0%


In [451]:
# Una muestra de los primeros 10 registros
df[1:10,:]


Out[451]:
x1y
10.7330.8
22.1246.3
33.0356.5
44.0353.7
55.171.4
66.1446.7
76.9449.7
87.9466.5
99.0341.1
1010.0230.0

In [459]:
# Tamaño del set de datos
@printf("filas: %d, columnas: %d\n",size(df,1),size(df,2))


filas: 50, columnas: 2

In [462]:
# Examen gráfico de datos
plot(x=df[:x1],y=df[:y],Geom.point, Guide.title("Nube de puntos"))


Out[462]:
x -70 -60 -50 -40 -30 -20 -10 0 10 20 30 40 50 60 70 80 90 100 110 120 130 -60 -58 -56 -54 -52 -50 -48 -46 -44 -42 -40 -38 -36 -34 -32 -30 -28 -26 -24 -22 -20 -18 -16 -14 -12 -10 -8 -6 -4 -2 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 104 106 108 110 112 114 116 118 120 -100 0 100 200 -60 -55 -50 -45 -40 -35 -30 -25 -20 -15 -10 -5 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 105 110 115 120 -2000 -1500 -1000 -500 0 500 1000 1500 2000 2500 3000 3500 -1500 -1450 -1400 -1350 -1300 -1250 -1200 -1150 -1100 -1050 -1000 -950 -900 -850 -800 -750 -700 -650 -600 -550 -500 -450 -400 -350 -300 -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1050 1100 1150 1200 1250 1300 1350 1400 1450 1500 1550 1600 1650 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250 2300 2350 2400 2450 2500 2550 2600 2650 2700 2750 2800 2850 2900 2950 3000 -2000 0 2000 4000 -1500 -1400 -1300 -1200 -1100 -1000 -900 -800 -700 -600 -500 -400 -300 -200 -100 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000 y Nube de puntos

Implementación de las funciones de costo $J(\Theta)$ y gradiente $\nabla J(\Theta)$ para el algoritmo

Para la construcción de las funciones, se usaron las versiones vectorizadas y matriciales de costo y gradiente:


In [409]:
# Función de costo
function J(Theta,X,Y)
    m = length(Y);
    (X*Theta-Y)'*(X*Theta-Y)/m;
end


Out[409]:
J (generic function with 1 method)

In [410]:
# Función Nabla de J
function nabla(Theta,X,Y)
    ((X'*X)*Theta - X'*Y);
end


Out[410]:
nabla (generic function with 1 method)

In [464]:
# Gradiente
function gradiente(X,Y,Theta,alpha,epsilon)
    m = length(Y);
    Jhist = [];
    n = 1
    while alpha*norm(nabla(Theta,X,Y)) > epsilon
        temp = Theta - alpha * nabla(Theta,X,Y)/m;
        Theta = temp;
        push!(Jhist,J(Theta,X,Y)[1]) # El subíndice 1 es porque J es matriz de 1x1
        n += 1;
    end
    return (Jhist,Theta,n)
end


Out[464]:
gradiente (generic function with 1 method)

Inicialización de variables e iteración para $\alpha = 0.001$ y $\epsilon > 0.001$


In [465]:
# Inicialización
X = [ones(50) df[:x1]];
Y = df[:y];

In [466]:
# Iteraciones para a = 0.001 y epsilon = 0.001
@time (Jhist,Theta,n) = gradiente(X,Y,[0,0],0.001,0.001);


  0.901631 seconds (3.88 M allocations: 266.547 MB, 6.98% gc time)

In [467]:
#Número de iteraciones
n


Out[467]:
31788

In [468]:
# Parámetro Theta
Theta


Out[468]:
2-element DataArrays.DataArray{Float64,1}:
 186.246 
  22.1696

In [469]:
# Curva de costo
plot(x=1:length(Jhist),y=Jhist,Geom.line)


Out[469]:
x -5×10⁴ -4×10⁴ -3×10⁴ -2×10⁴ -1×10⁴ 0 1×10⁴ 2×10⁴ 3×10⁴ 4×10⁴ 5×10⁴ 6×10⁴ 7×10⁴ 8×10⁴ 9×10⁴ -4.0×10⁴ -3.8×10⁴ -3.6×10⁴ -3.4×10⁴ -3.2×10⁴ -3.0×10⁴ -2.8×10⁴ -2.6×10⁴ -2.4×10⁴ -2.2×10⁴ -2.0×10⁴ -1.8×10⁴ -1.6×10⁴ -1.4×10⁴ -1.2×10⁴ -1.0×10⁴ -8.0×10³ -6.0×10³ -4.0×10³ -2.0×10³ 0 2.0×10³ 4.0×10³ 6.0×10³ 8.0×10³ 1.0×10⁴ 1.2×10⁴ 1.4×10⁴ 1.6×10⁴ 1.8×10⁴ 2.0×10⁴ 2.2×10⁴ 2.4×10⁴ 2.6×10⁴ 2.8×10⁴ 3.0×10⁴ 3.2×10⁴ 3.4×10⁴ 3.6×10⁴ 3.8×10⁴ 4.0×10⁴ 4.2×10⁴ 4.4×10⁴ 4.6×10⁴ 4.8×10⁴ 5.0×10⁴ 5.2×10⁴ 5.4×10⁴ 5.6×10⁴ 5.8×10⁴ 6.0×10⁴ 6.2×10⁴ 6.4×10⁴ 6.6×10⁴ 6.8×10⁴ 7.0×10⁴ 7.2×10⁴ 7.4×10⁴ 7.6×10⁴ 7.8×10⁴ 8.0×10⁴ -5×10⁴ 0 5×10⁴ 1×10⁵ -4.0×10⁴ -3.5×10⁴ -3.0×10⁴ -2.5×10⁴ -2.0×10⁴ -1.5×10⁴ -1.0×10⁴ -5.0×10³ 0 5.0×10³ 1.0×10⁴ 1.5×10⁴ 2.0×10⁴ 2.5×10⁴ 3.0×10⁴ 3.5×10⁴ 4.0×10⁴ 4.5×10⁴ 5.0×10⁴ 5.5×10⁴ 6.0×10⁴ 6.5×10⁴ 7.0×10⁴ 7.5×10⁴ 8.0×10⁴ -5×10⁴ -4×10⁴ -3×10⁴ -2×10⁴ -1×10⁴ 0 1×10⁴ 2×10⁴ 3×10⁴ 4×10⁴ 5×10⁴ 6×10⁴ 7×10⁴ 8×10⁴ 9×10⁴ -4.0×10⁴ -3.8×10⁴ -3.6×10⁴ -3.4×10⁴ -3.2×10⁴ -3.0×10⁴ -2.8×10⁴ -2.6×10⁴ -2.4×10⁴ -2.2×10⁴ -2.0×10⁴ -1.8×10⁴ -1.6×10⁴ -1.4×10⁴ -1.2×10⁴ -1.0×10⁴ -8.0×10³ -6.0×10³ -4.0×10³ -2.0×10³ 0 2.0×10³ 4.0×10³ 6.0×10³ 8.0×10³ 1.0×10⁴ 1.2×10⁴ 1.4×10⁴ 1.6×10⁴ 1.8×10⁴ 2.0×10⁴ 2.2×10⁴ 2.4×10⁴ 2.6×10⁴ 2.8×10⁴ 3.0×10⁴ 3.2×10⁴ 3.4×10⁴ 3.6×10⁴ 3.8×10⁴ 4.0×10⁴ 4.2×10⁴ 4.4×10⁴ 4.6×10⁴ 4.8×10⁴ 5.0×10⁴ 5.2×10⁴ 5.4×10⁴ 5.6×10⁴ 5.8×10⁴ 6.0×10⁴ 6.2×10⁴ 6.4×10⁴ 6.6×10⁴ 6.8×10⁴ 7.0×10⁴ 7.2×10⁴ 7.4×10⁴ 7.6×10⁴ 7.8×10⁴ 8.0×10⁴ -5×10⁴ 0 5×10⁴ 1×10⁵ -4.0×10⁴ -3.5×10⁴ -3.0×10⁴ -2.5×10⁴ -2.0×10⁴ -1.5×10⁴ -1.0×10⁴ -5.0×10³ 0 5.0×10³ 1.0×10⁴ 1.5×10⁴ 2.0×10⁴ 2.5×10⁴ 3.0×10⁴ 3.5×10⁴ 4.0×10⁴ 4.5×10⁴ 5.0×10⁴ 5.5×10⁴ 6.0×10⁴ 6.5×10⁴ 7.0×10⁴ 7.5×10⁴ 8.0×10⁴ y

Recta ajustada y solución algebraica por Mínimos Cuadrado Ordinarios


In [433]:
# Recta ajustada
plot(layer(x=df[:x1],y=df[:y],Geom.point),layer(x=df[:x1],
y=Theta[1]+Theta[2]*df[:x1],Geom.line,Theme(default_color=color("red"))))


Out[433]:
x -70 -60 -50 -40 -30 -20 -10 0 10 20 30 40 50 60 70 80 90 100 110 120 130 -60 -58 -56 -54 -52 -50 -48 -46 -44 -42 -40 -38 -36 -34 -32 -30 -28 -26 -24 -22 -20 -18 -16 -14 -12 -10 -8 -6 -4 -2 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 104 106 108 110 112 114 116 118 120 -100 0 100 200 -60 -55 -50 -45 -40 -35 -30 -25 -20 -15 -10 -5 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 105 110 115 120 -2000 -1500 -1000 -500 0 500 1000 1500 2000 2500 3000 3500 -1500 -1450 -1400 -1350 -1300 -1250 -1200 -1150 -1100 -1050 -1000 -950 -900 -850 -800 -750 -700 -650 -600 -550 -500 -450 -400 -350 -300 -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1050 1100 1150 1200 1250 1300 1350 1400 1450 1500 1550 1600 1650 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250 2300 2350 2400 2450 2500 2550 2600 2650 2700 2750 2800 2850 2900 2950 3000 -2000 0 2000 4000 -1500 -1400 -1300 -1200 -1100 -1000 -900 -800 -700 -600 -500 -400 -300 -200 -100 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000 y

In [434]:
# Resultado exacto: Mínimos Cuadrados Ordinarios
Beta = pinv(X'*X)*(X'*Y)


Out[434]:
2-element DataArrays.DataArray{Float64,1}:
 186.328 
  22.1671

In [435]:
# Recta ajustada con Beta superpuesta
plot(layer(x=df[:x1],y=df[:y],Geom.point),layer(x=df[:x1],
y=Theta[1]+Theta[2]*df[:x1],Geom.line,Theme(default_color=color("red"))),
layer(x=df[:x1],y=Beta[1]+Beta[2]*df[:x1],Geom.line,Theme(default_color=color("green"))))


Out[435]:
x -70 -60 -50 -40 -30 -20 -10 0 10 20 30 40 50 60 70 80 90 100 110 120 130 -60 -58 -56 -54 -52 -50 -48 -46 -44 -42 -40 -38 -36 -34 -32 -30 -28 -26 -24 -22 -20 -18 -16 -14 -12 -10 -8 -6 -4 -2 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 104 106 108 110 112 114 116 118 120 -100 0 100 200 -60 -55 -50 -45 -40 -35 -30 -25 -20 -15 -10 -5 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 105 110 115 120 -2000 -1500 -1000 -500 0 500 1000 1500 2000 2500 3000 3500 -1500 -1450 -1400 -1350 -1300 -1250 -1200 -1150 -1100 -1050 -1000 -950 -900 -850 -800 -750 -700 -650 -600 -550 -500 -450 -400 -350 -300 -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1050 1100 1150 1200 1250 1300 1350 1400 1450 1500 1550 1600 1650 1700 1750 1800 1850 1900 1950 2000 2050 2100 2150 2200 2250 2300 2350 2400 2450 2500 2550 2600 2650 2700 2750 2800 2850 2900 2950 3000 -2000 0 2000 4000 -1500 -1400 -1300 -1200 -1100 -1000 -900 -800 -700 -600 -500 -400 -300 -200 -100 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2700 2800 2900 3000 y

In [441]:
# Error general
norm(Beta-Theta)^(0.5)


Out[441]:
0.28696690961730725

Se puede observar que el resultado obtenido es lo esperable por el algoritmo. Después de 31.788 iteraciones en 0.9 segundos, se obtuvieron los valores de $\theta_0$ y $\theta_1$, con una muy pequeña diferencia respecto del valor obtenido por la solución algebraica de Mínimos Cuadrados Ordinarios.

En términos de la usabilidad, es muy cómodo poder utilizar las fórmulas vectorizadas y es agradable la velocidad de ejecución. Para una prueba más definitiva, probaré el algoritmo con un set de datos más grande y con más variables, y también haré un paralelo con Octave para probar velocidad.

Pero más allá de la mera eficacia computacional, me agradó mucho como lenguaje, y creo que de comprobarse las aseveraciones de sus creadores, fácilmente podría transformarse en mi herramienta predilecta para análisis de datos y Machine Learning.

Rodrigo Abt B., Ing. Civil Industrial U. de Chile
Consultor y Data Scientist