6  Redes de neuronas artificiales recurrentes

Las redes de neuronas artificiales recurrentes (RNN por sus siglas en inglés) son un tipo de redes neuronales diseñadas para trabajar con datos secuenciales. A diferencia de las redes neuronales tradicionales tienen una memoria interna capaz de recordar información de una secuencia temporal de entradas, y utilizar esa información para predecir nuevos valores de la serie o clasificarla. Esto las hace ideales para tareas de predicción de series temporales, traducción de textos o audios, y procesamiento del lenguaje natural entre otras.

En esta práctica veremos cómo funcionan estas redes neuronales y aprenderemos a implementarlas con el paquete Lux de Julia.

6.1 Ejercicios Resueltos

Para la realización de esta práctica se requieren los siguientes paquetes:

using CSV  # Para la lectura de archivos CSV.
using DataFrames  # Para el manejo de datos tabulares.
using GLMakie  # Para el dibujo de gráficas.
using Random  # Para la generación de números aleatorios.
using Lux  # Para la implementación de redes neuronales.
using Zygote  # Cálculo automático de derivadas y gradientes.
using Optimisers # Para la optimización de funciones.
using Statistics # Para las funciones de coste.

Ejercicio 6.1 La sucesión de Fibonacci es una sucesión recurrente que se define como

\[a_1 = 1,\quad a_2 =1, \quad a_n = a_{n-1} + a_{n-2}\quad \forall n\geq 3.\]

  1. Construir una función para generar la sucesión de Fibonacci.

    function fibonacci(n::Int)
        a = zeros(Float32, n)
        a[1] = 1
        a[2] = 1
        for i in 3:n
            a[i] = a[i-1] + a[i-2]
        end
        return a
    end
    fibonacci (generic function with 1 method)
  2. Dividir la secuencia de los 30 primeros términos de la sucesión de Fibonacci en subsecuencias de longitud 5. Para cada una de estas subsecuencias, guardar los vectores de los cuatro primeros términos en una matriz de entrada y el último valor en una matriz de salida.

    fib = fibonacci(25) 
    tamaño = 4
    n = 25 - tamaño
    # La red esperar una entrada con dimensiones (características, tamaño, lote).
    X =  Array{Float32}(undef, 1, tamaño, n)
    Y = Array{Float32}(undef, 1, n)  
    for i = 1:n
        ventana = fib[i:i+tamaño-1]
        etiqueta = fib[i+tamaño]
        X[1, : , i] .= ventana
        Y[1, i] = etiqueta
    end
  3. Definir una red neuronal con una neurona recurrente con un estado oculto de tamaño 1, sin término independiente (bias). Inicializar la red con pesos aleatorios.

    using Lux, Random
    
    modelo = Chain(
        Recurrence(RNNCell(1 => 1, identity; use_bias = false); return_sequence = false)
    )
    
    rng = Random.default_rng()
    # Semilla aleatoria para reproducibilidad
    Random.seed!(rng, 1234)
    ps, st = Lux.setup(rng, modelo)
    ((layer_1 = (weight_ih = Float32[-1.7194579;;], weight_hh = Float32[1.174417;;]),), (layer_1 = (rng = TaskLocalRNG(),),))
  4. Definir como función de coste el error cuadrático medio para todas las secuencias del conjunto de entrenamiento.

    using Statistics
    function coste(ps, st, X, Y)
        ŷ, estado = modelo(X, ps, st)          # ŷ: (1, batch)
        return mean((ŷ .- Y).^2)
    end
    
    coste(ps, st, X, Y)
    6.2632223f9
  5. Entrenar la red neuronal con el algoritmo de optimización Adam tomando una tasa de aprendizaje 0.1. Realizar 200 épocas.

    using Optimisers, Zygote
    opt = Optimisers.setup(Optimisers.Adam(0.1f0), ps)
    
    nepocas = 200
    costes = []
    for epoca in 1:nepocas
        # gradiente de la red con respecto a los parámetros
        gs = first(Zygote.gradient(p -> coste(p, st, X, Y), ps))
        opt, ps = Optimisers.update(opt, ps, gs) 
        push!(costes, coste(ps, st, X, Y))
        println("Época $epoca | coste = ", costes[end])
    end
    Época 1 | coste = 5.0130447e9
    Época 2 | coste = 4.046983e9
    Época 3 | coste = 3.3034327e9
    Época 4 | coste = 2.7312077e9
    Época 5 | coste = 2.2893059e9
    Época 6 | coste = 1.94581e9
    Época 7 | coste = 1.6764061e9
    Época 8 | coste = 1.4628404e9
    Época 9 | coste = 1.2915259e9
    Época 10 | coste = 1.1523862e9
    Época 11 | coste = 1.0379493e9
    Época 12 | coste = 9.4265696e8
    Época 13 | coste = 8.623539e8
    Época 14 | coste = 7.9391226e8
    Época 15 | coste = 7.349589e8
    Época 16 | coste = 6.836777e8
    Época 17 | coste = 6.386652e8
    Época 18 | coste = 5.9882643e8
    Época 19 | coste = 5.632986e8
    Época 20 | coste = 5.313949e8
    Época 21 | coste = 5.0256346e8
    Época 22 | coste = 4.7635606e8
    Época 23 | coste = 4.5240582e8
    Época 24 | coste = 4.3040934e8
    Época 25 | coste = 4.101142e8
    Época 26 | coste = 3.913082e8
    Época 27 | coste = 3.7381197e8
    Época 28 | coste = 3.5747312e8
    Época 29 | coste = 3.4216106e8
    Época 30 | coste = 3.277637e8
    Época 31 | coste = 3.141841e8
    Época 32 | coste = 3.0133814e8
    Época 33 | coste = 2.891527e8
    Época 34 | coste = 2.77564e8
    Época 35 | coste = 2.665161e8
    Época 36 | coste = 2.5595997e8
    Época 37 | coste = 2.458527e8
    Época 38 | coste = 2.3615629e8
    Época 39 | coste = 2.268375e8
    Época 40 | coste = 2.1786688e8
    Época 41 | coste = 2.0921851e8
    Época 42 | coste = 2.0086955e8
    Época 43 | coste = 1.927998e8
    Época 44 | coste = 1.8499157e8
    Época 45 | coste = 1.774291e8
    Época 46 | coste = 1.7009869e8
    Época 47 | coste = 1.6298826e8
    Época 48 | coste = 1.5608707e8
    Época 49 | coste = 1.493859e8
    Época 50 | coste = 1.4287659e8
    Época 51 | coste = 1.3655203e8
    Época 52 | coste = 1.3040608e8
    Época 53 | coste = 1.24433336e8
    Época 54 | coste = 1.1862924e8
    Época 55 | coste = 1.1298979e8
    Época 56 | coste = 1.0751154e8
    Época 57 | coste = 1.02191656e8
    Época 58 | coste = 9.70276e7
    Época 59 | coste = 9.201727e7
    Época 60 | coste = 8.715889e7
    Época 61 | coste = 8.245096e7
    Época 62 | coste = 7.789219e7
    Época 63 | coste = 7.348143e7
    Época 64 | coste = 6.9217816e7
    Época 65 | coste = 6.510044e7
    Época 66 | coste = 6.1128532e7
    Época 67 | coste = 5.7301384e7
    Época 68 | coste = 5.361825e7
    Época 69 | coste = 5.007843e7
    Época 70 | coste = 4.6681136e7
    Época 71 | coste = 4.3425516e7
    Época 72 | coste = 4.0310664e7
    Época 73 | coste = 3.7335516e7
    Época 74 | coste = 3.449891e7
    Época 75 | coste = 3.1799504e7
    Época 76 | coste = 2.9235844e7
    Época 77 | coste = 2.6806242e7
    Época 78 | coste = 2.450881e7
    Época 79 | coste = 2.2341502e7
    Época 80 | coste = 2.030202e7
    Época 81 | coste = 1.838785e7
    Época 82 | coste = 1.6596277e7
    Época 83 | coste = 1.4924337e7
    Época 84 | coste = 1.3368829e7
    Época 85 | coste = 1.1926354e7
    Época 86 | coste = 1.0593308e7
    Época 87 | coste = 9.36583e6
    Época 88 | coste = 8.2399175e6
    Época 89 | coste = 7.2113615e6
    Época 90 | coste = 6.275804e6
    Época 91 | coste = 5.428737e6
    Época 92 | coste = 4.6655335e6
    Época 93 | coste = 3.9814918e6
    Época 94 | coste = 3.3718168e6
    Época 95 | coste = 2.8316872e6
    Época 96 | coste = 2.3562815e6
    Época 97 | coste = 1.9407861e6
    Época 98 | coste = 1.5804446e6
    Época 99 | coste = 1.270575e6
    Época 100 | coste = 1.0065965e6
    Época 101 | coste = 784080.8
    Época 102 | coste = 598748.06
    Época 103 | coste = 446494.9
    Época 104 | coste = 323433.88
    Época 105 | coste = 225888.9
    Época 106 | coste = 150422.62
    Época 107 | coste = 93842.3
    Época 108 | coste = 53206.5
    Época 109 | coste = 25830.83
    Época 110 | coste = 9288.164
    Época 111 | coste = 1405.4087
    Época 112 | coste = 258.9841
    Época 113 | coste = 4165.221
    Época 114 | coste = 11670.322
    Época 115 | coste = 21538.016
    Época 116 | coste = 32733.744
    Época 117 | coste = 44411.508
    Época 118 | coste = 55893.332
    Época 119 | coste = 66655.56
    Época 120 | coste = 76311.76
    Época 121 | coste = 84591.56
    Época 122 | coste = 91327.59
    Época 123 | coste = 96442.8
    Época 124 | coste = 99924.69
    Época 125 | coste = 101825.52
    Época 126 | coste = 102235.44
    Época 127 | coste = 101282.234
    Época 128 | coste = 99114.875
    Época 129 | coste = 95898.414
    Época 130 | coste = 91803.37
    Época 131 | coste = 86998.22
    Época 132 | coste = 81652.52
    Época 133 | coste = 75922.914
    Época 134 | coste = 69954.91
    Época 135 | coste = 63880.133
    Época 136 | coste = 57814.195
    Época 137 | coste = 51860.23
    Época 138 | coste = 46101.43
    Época 139 | coste = 40606.44
    Época 140 | coste = 35429.72
    Época 141 | coste = 30609.598
    Época 142 | coste = 26174.066
    Época 143 | coste = 22137.898
    Época 144 | coste = 18506.016
    Época 145 | coste = 15274.37
    Época 146 | coste = 12433.482
    Época 147 | coste = 9965.116
    Época 148 | coste = 7848.189
    Época 149 | coste = 6057.5005
    Época 150 | coste = 4566.634
    Época 151 | coste = 3346.4177
    Época 152 | coste = 2367.8516
    Época 153 | coste = 1601.9951
    Época 154 | coste = 1020.641
    Época 155 | coste = 596.68134
    Época 156 | coste = 305.06555
    Época 157 | coste = 122.30558
    Época 158 | coste = 26.97289
    Época 159 | coste = 0.021119582
    Época 160 | coste = 24.514503
    Época 161 | coste = 85.74834
    Época 162 | coste = 171.26875
    Época 163 | coste = 270.41803
    Época 164 | coste = 374.75803
    Época 165 | coste = 477.35016
    Época 166 | coste = 573.0674
    Época 167 | coste = 657.9105
    Época 168 | coste = 729.38
    Época 169 | coste = 785.9467
    Época 170 | coste = 826.7763
    Época 171 | coste = 852.2167
    Época 172 | coste = 862.56854
    Época 173 | coste = 859.2576
    Época 174 | coste = 843.40356
    Época 175 | coste = 816.80646
    Época 176 | coste = 781.19714
    Época 177 | coste = 738.0868
    Época 178 | coste = 689.4481
    Época 179 | coste = 636.91144
    Época 180 | coste = 581.95496
    Época 181 | coste = 525.9896
    Época 182 | coste = 470.28207
    Época 183 | coste = 415.8274
    Época 184 | coste = 363.58008
    Época 185 | coste = 314.0716
    Época 186 | coste = 268.0682
    Época 187 | coste = 225.7752
    Época 188 | coste = 187.4945
    Época 189 | coste = 153.36887
    Época 190 | coste = 123.32797
    Época 191 | coste = 97.2789
    Época 192 | coste = 75.056725
    Época 193 | coste = 56.43496
    Época 194 | coste = 41.149002
    Época 195 | coste = 28.87258
    Época 196 | coste = 19.250816
    Época 197 | coste = 12.009598
    Época 198 | coste = 6.7831855
    Época 199 | coste = 3.2605946
    Época 200 | coste = 1.1491588
  6. Dibujar la evolución de los costes durante el proceso de entrenamiento.

    using GLMakie
    fig = Figure()
    ax = Axis(fig[1, 1], xlabel = "Época", ylabel = "Error cuadrático medio", title = "Evolución del coste")
    lines!(ax, costes)
    fig
  7. Usar la red neuronal entrenada para predecir el siguiente término de la serie de Fibonacci a partir de los 4 últimos términos de la serie.

    fib = fibonacci(30)
    X_test = reshape(fib[end-tamaño : end-1], 1, tamaño, 1) 
    
    y_test, _ = modelo(X_test, ps, st)
    println("Predicción del término 30: ",  y_test[1, 1])
    println("Término 30 de la sucesión de Fibonacci: ", fib[end])
    Predicción del término 30: 831997.2
    Término 30 de la sucesión de Fibonacci: 832040.0
  8. Mostrar los pesos de la red neuronal entrenada.

    println("Pesos de la entrada de la red neuronal:", ps.layer_1.weight_ih)
    println("Pesos del estado de la red neuronal:", ps.layer_1.weight_hh)
    Pesos de la entrada de la red neuronal:Float32[1.662782;;]
    Pesos del estado de la red neuronal:Float32[-0.04483252;;]

Ejercicio 6.2 El fichero stock.csv contiene los precios al cierre de las acciones de una empresas en bolsa de valores de los 300 primeros días del año 2020.

  1. Cargar el conjunto de datos y dibujar la serie temporal.

    using CSV, DataFrames, GLMakie
    
    # Cargamos el conjunto de datos en un data frame
    df = CSV.read("datos/stock.csv", DataFrame)
    
    # Creamos el gráfico de la evolución
    fig = Figure()
    ax = Axis(fig[1, 1], xlabel = "Día", ylabel = "Precio de la acción (€)", title = "Evolución del precio de la acción")
    
    lines!(ax, df.dia, df.precio)
    fig
  2. Normalizar el conjunto de datos.

    using Statistics
    
    # Simple normalization (z-score)
    μ = mean(df.precio)
    σ = std(df.precio)
    serie = (df.precio .- μ) ./ σ     
    300-element Vector{Float64}:
     -1.2770239698472932
     -1.4749432268197025
     -1.2481144154580652
     -0.8745140202741842
     -0.8789616440263692
     -1.4460336724304748
     -0.9679141190701513
     -1.1814000591752272
     -1.1591619404142832
     -1.054642782237841
     -1.1391476335294317
     -0.8923045152829369
     -1.1035666435119202
      ⋮
      1.4249074596075517
      1.5049646871469575
      1.031292757538819
      1.0379641931671029
      1.6472886472170039
      0.9112069162297167
      1.1469309750957366
      1.4782789446338223
      1.3137168658028255
      1.5027408752708586
      1.3759835983334725
      1.6450648353409114
  3. Dividir la serie de los precios en secuencias de 50 términos. Para cada una de estas secuencias, guardar los vectores de los 40 primeros términos en una matriz de entrada y el último valor en una matriz de salida.

    function crear_secuencias(serie, tamaño)
        # Número de ventanas
        n = length(serie) - tamaño  
    
        # X: (características=1, tamaño=seq_len, lotes=n)
        X = Array{Float32}(undef, 1, tamaño, n)
        # Y: (etiquetas=1, lotes=n)
        Y = Array{Float32}(undef, 1, n)
    
        for i in 1:n
            ventana = serie[i : i + tamaño - 1]
            etiqueta = serie[i + tamaño]
            X[1, :, i] .= ventana
            Y[1, i] = etiqueta
        end
    
        return X, Y
    end
    
    X, Y = crear_secuencias(serie, 50)
    (Float32[-1.2770239 -1.4749433 … -1.7551435 -1.501629;;; -1.4749433 -1.2481145 … -1.501629 -1.6973244;;; -1.2481145 -0.87451404 … -1.6973244 -1.4393623;;; … ;;; 1.7607031 1.3515216 … 1.478279 1.3137169;;; 1.3515216 1.1980786 … 1.3137169 1.5027409;;; 1.1980786 1.6117077 … 1.5027409 1.3759836], Float32[-1.6973244 -1.4393623 … 1.3759836 1.6450648])
  4. Dividir el conjunto de secuencias en un conjunto de entrenamiento con las 200 primeras secuencias y otro de prueba con las 50 restantes.

    Xentrenamiento, Yentrenamiento = X[:, :, 1:200], Y[:, 1:200]
    Xtest, Ytest = X[:, :, 201:end], Y[:, 201:end]
    (Float32[0.08839652 0.09062033 … 1.3515216 1.1980786;;; 0.09062033 0.3797159 … 1.1980786 1.6117077;;; 0.3797159 0.29298723 … 1.6117077 1.3248359;;; … ;;; 1.7607031 1.3515216 … 1.478279 1.3137169;;; 1.3515216 1.1980786 … 1.3137169 1.5027409;;; 1.1980786 1.6117077 … 1.5027409 1.3759836], Float32[1.6117077 1.3248359 … 1.3759836 1.6450648])
  5. Definir una red neuronal con una neurona recurrente con un estado oculto de tamaño 64 y una neurona densa de 1 salida. Inicializar la red con pesos aleatorios.

    using Lux, Random
    modelo = Chain(
        Recurrence(RNNCell(1 => 64); return_sequence = false),
        Dense(64 => 1)
    )
    
    rng = Random.default_rng()
    # Semilla aletoria para reproducibilidad
    Random.seed!(rng, 1234)
    ps, st = Lux.setup(rng, modelo)
    ((layer_1 = (weight_ih = Float32[-0.21493223; 0.14680213; … ; 0.09006676; -0.29187012;;], weight_hh = Float32[0.16710633 -0.10049977 … -0.7339213 -0.44402117; -0.12605162 -0.42469883 … -0.2643636 -0.33319688; … ; -0.2543804 0.052617714 … -0.60686016 -0.4568765; 0.24136081 -0.34361988 … 0.12011717 0.118953556], bias_ih = Float32[-0.0951326, 0.08999576, 0.10426867, 0.11379315, 0.11712587, -0.3674832, -0.018230781, -0.12023171, -0.00048576295, 0.7689639  …  -0.06666367, 0.52615803, -0.09323904, -0.015267894, -0.29630774, -0.31387812, -0.2896284, -0.171922, 0.173794, -0.13455431], bias_hh = Float32[0.094174266, -0.32182646, -0.4835172, -0.1707672, -0.11865046, -0.6600209, -0.3815048, -0.3407301, 0.014974058, -0.2173861  …  0.036009803, 0.32550737, -0.016171448, -0.1333059, 0.18780592, -0.085141905, 0.14552, -0.021185711, -0.15243843, -0.029349051]), layer_2 = (weight = Float32[-0.04013527 -0.08779158 … -0.21502617 -0.12662868], bias = Float32[-0.08802833])), (layer_1 = (rng = TaskLocalRNG(),), layer_2 = NamedTuple()))
  6. Definir como función de coste el error cuadrático medio para todas las secuencias del conjunto de entrenamiento.

    using Statistics
    function coste(ps, st, X, Y)
        ŷ, estado = modelo(X, ps, st)
        return mean((ŷ .- Y).^2)
    end
    
    coste(ps, st, X, Y)
    2.4999213f0
  7. Entrenar la red neuronal con el algoritmo de optimización Adam tomando una tasa de aprendizaje 0.01. Realizar 300 épocas.

    using Optimisers, Zygote
    opt = Optimisers.setup(Optimisers.Adam(0.01f0), ps)
    
    nepocas = 100
    costes = []
    for epoca in 1:nepocas
        # gradiente de la red con respecto a los parámetros
        gs = first(Zygote.gradient(p -> coste(p, st, Xentrenamiento, Yentrenamiento), ps))
        opt, ps = Optimisers.update(opt, ps, gs) 
        push!(costes, coste(ps, st, Xentrenamiento, Yentrenamiento))
        println("Época $epoca | coste = ", costes[end])
    end
    Época 1 | coste = 0.7120536
    Época 2 | coste = 0.58144593
    Época 3 | coste = 0.86751
    Época 4 | coste = 0.9924159
    Época 5 | coste = 0.8545057
    Época 6 | coste = 0.6759848
    Época 7 | coste = 0.5618811
    Época 8 | coste = 0.56183267
    Época 9 | coste = 0.6390394
    Época 10 | coste = 0.7087791
    Época 11 | coste = 0.7118296
    Época 12 | coste = 0.6544135
    Época 13 | coste = 0.5607798
    Época 14 | coste = 0.4915699
    Época 15 | coste = 0.49867082
    Época 16 | coste = 0.5568062
    Época 17 | coste = 0.5875028
    Época 18 | coste = 0.566299
    Época 19 | coste = 0.51768893
    Época 20 | coste = 0.4785608
    Época 21 | coste = 0.47465625
    Época 22 | coste = 0.49831668
    Época 23 | coste = 0.517101
    Época 24 | coste = 0.5072868
    Época 25 | coste = 0.4794915
    Época 26 | coste = 0.44851258
    Época 27 | coste = 0.4381218
    Época 28 | coste = 0.4442783
    Época 29 | coste = 0.4462868
    Época 30 | coste = 0.4242044
    Época 31 | coste = 0.38773945
    Época 32 | coste = 0.35950392
    Época 33 | coste = 0.34827664
    Época 34 | coste = 0.3276915
    Época 35 | coste = 0.28721783
    Época 36 | coste = 0.25264022
    Época 37 | coste = 0.2554782
    Época 38 | coste = 0.22638088
    Época 39 | coste = 0.20317441
    Época 40 | coste = 0.19642892
    Época 41 | coste = 0.18331131
    Época 42 | coste = 0.16217327
    Época 43 | coste = 0.14643814
    Época 44 | coste = 0.13728029
    Época 45 | coste = 0.13000613
    Época 46 | coste = 0.11908289
    Época 47 | coste = 0.11113613
    Época 48 | coste = 0.11166415
    Época 49 | coste = 0.10654645
    Época 50 | coste = 0.09456818
    Época 51 | coste = 0.08943724
    Época 52 | coste = 0.08898305
    Época 53 | coste = 0.09065049
    Época 54 | coste = 0.089022696
    Época 55 | coste = 0.08459871
    Época 56 | coste = 0.08418649
    Época 57 | coste = 0.08065779
    Época 58 | coste = 0.078941554
    Época 59 | coste = 0.07779598
    Época 60 | coste = 0.0763044
    Época 61 | coste = 0.07736759
    Época 62 | coste = 0.07613584
    Época 63 | coste = 0.074771434
    Época 64 | coste = 0.07497347
    Época 65 | coste = 0.07299402
    Época 66 | coste = 0.07364231
    Época 67 | coste = 0.07250167
    Época 68 | coste = 0.072929665
    Época 69 | coste = 0.07158016
    Época 70 | coste = 0.07258179
    Época 71 | coste = 0.071268335
    Época 72 | coste = 0.071854874
    Época 73 | coste = 0.07159285
    Época 74 | coste = 0.07038627
    Época 75 | coste = 0.07098476
    Época 76 | coste = 0.07006505
    Época 77 | coste = 0.07028242
    Época 78 | coste = 0.07019345
    Época 79 | coste = 0.06929663
    Época 80 | coste = 0.06949752
    Época 81 | coste = 0.06908774
    Época 82 | coste = 0.068893895
    Época 83 | coste = 0.069023445
    Época 84 | coste = 0.0686395
    Época 85 | coste = 0.06830147
    Época 86 | coste = 0.068120345
    Época 87 | coste = 0.06778442
    Época 88 | coste = 0.06775368
    Época 89 | coste = 0.06764672
    Época 90 | coste = 0.06730844
    Época 91 | coste = 0.067337014
    Época 92 | coste = 0.06719287
    Época 93 | coste = 0.06740736
    Época 94 | coste = 0.06722504
    Época 95 | coste = 0.06719639
    Época 96 | coste = 0.06701797
    Época 97 | coste = 0.0668914
    Época 98 | coste = 0.066815436
    Época 99 | coste = 0.06673246
    Época 100 | coste = 0.06663198
  8. Dibujar la evolución de los costes durante el proceso de entrenamiento.

    using GLMakie
    fig = Figure()
    ax = Axis(fig[1, 1], xlabel = "Época", ylabel = "Error cuadrático medio", title = "Evolución del coste")
    lines!(ax, costes)
    fig
  9. Predecir el precio de las acciones de los próximos 50 días usando el conjunto de prueba.

    y_test, _ = modelo(Xtest, ps, st)
    predicciones = y_test .* σ .+ μ
    
    fig = Figure()
    ax = Axis(fig[1,1], xlabel = "Día", ylabel = "Precio de acción", title = "Predicciones del precio de la acción")
    lines!(ax, df.dia, df.precio, label = "Precio real")
    lines!(ax, df.dia[251:end], vec(predicciones), label = "Predicción")
    axislegend(ax, position = :rb)
    fig