La dependencia entre los elementos de una secuencia es una característica que podemos encontrar en muchos conjuntos de datos.
Algunos ejemplo son:
En todos estos casos la secuencia de datos es temporal, pero no es la única posibilidad.
Otros ejemplos donde no existe (explicitamente) componente temporal son:
Una serie temporal de datos es una secuencia de datos con un orden temporal.
Muchas veces ocurre que los datos en una serie temporal están auto-correlacionados, existe una correlación entre los datos y los mismos datos retrasados, o adelantados en el tiempo.
Dicho de otro modo, la autocorrelación es la correlación de los datos con ellos mismos.
En Python:
Donde i indica cuanto se desplaza la señal al calcular la correlación.
Los diagramas de autocorrelación es una manera gráfica de mostrar la dependencia temporal de los datos.
Los diagramas de autocorrelación es una manera gráfica de mostrar la dependencia temporal de los datos.
A veces, en las series temporales, se puede observar una componente de tendencia y sobre ella una componente estacional.
A veces, en las series temporales, se puede observar una componente de tendencia y sobre ella una componente estacional.
Calcular la media de una serie de datos con una ventana deslizante es muy sencillo en Python:
Donde 12 indica el número de muestras que se tomarán en la ventana deslizante.
Vamos a ver un nuevo tipo de redes neuronales que pueden hacer predicciones sobre series temporales.
El objetivo de las redes neuronales recurrentes es dar una salida a partir de una entrada, pero, al contrario que en el caso de las redes convolucionales, el tamaño de la entrada puede ser cualquiera, no es fijo.
Rcuerda que al trabajar con redes convolucionales todas las imágenes deben tener el mismo tamaño ancho x alto.
Vamos a definir una secuencia como un conjunto ordenado con un número de elementos no determinado, podemos tener secuencias de un único elemento o de muchos elementos. Por ejemplo una secuencia temporal de datos puede tener cualquier tamaño, una frase puede tener cualquier número de letras.
Los vectores los vamos a definir como un conjunto ordenado con un número de elementos fijo. Por ejemplo cuando clasificamos imágenes con una red convolucional, todas ellas tienen el mismo tamaño.
En este gráfico se muestran todas las combinaciones posible a la entrada y a la salida:
Este caso es una transformación de vector a secuencia. Un ejemplo es pasar a la red una imagen (vector), y que la red nos devuelva una descripción de lo que se ve en la imagen.
En este caso se le pasa una secuencia a la red y esta debe proporcionar un único valor a la salida. Por ejemplo, pasamos un texto a la red, y la red nos debe indicar si el texto contiene sesgos sexistas.
Este caso es una transformación de secuencia a secuencia. Por ejemplo, proporcionamos un texto en una lengua a la red y la red debe traducir el texto a otra lengua.
Es una extensión del caso de vector a secuencia. Por ejemplo, presentamos una secuencia de imágenes a la red, y esta debe generar un texto para cada una de ellas. Imagina, por ejemplo, la interpretación de una película para personas sordas.
La idea básica de las redes recurrentes es que cada neurona almacena un estado \(h_t\) que se calcula a partir de la entrada \(x_t\) y el estado anterior \(h_{t-1}\)
Este es el despliegue de la rede recurrente a lo largo del tiempo.
La salida en \(y_t\) se calcula en función del estado oculto \(h_{t}\):
\[ y_t = f(W_{yh} h_t + b_y) \]
El estado oculto \(h_t\) se calcula en función de la entrada \(x_t\) y del estado oculto anterior \(h_{t-1}\):
\[ h_t = f(W_{hh} h_{t-1} + W_{hx} x_t + b_h) \]
El estado oculto es la memoria de la neurona.
En el proceso de entrenamiento, se ajustan tanto los pesos de la matriz que calcula la salid \(y_t\) como los pesos de las matrices que calculan el estado oculto \(h_t\).
Veamos como definir y entrenar redes recurrentes.
Como ejemplo, vamos a trabajar con la serie de datos temporales de concentración de CO2 para intentar predecir su evolución a un periodo de meses en el futuro.
La primera tarea es decidir qué tipo de red recurrente vamos a implementar:
En nuestro caso, a partir de una serie temporal de datos, queremos predecir cuál es el siguiente dato. Por lo tanto tenemos una estructura many to one.
El objetivo es, a partir de una serie de 10 datos, predecir el siguiente valor en la serie.
Vamos a empezar escalando los datos:
Cada entrada de entrenamiento es una serie de 10 elementos de la serie temporal, la salida es el siguiente dato en la serie:
[[0.03046482 0.05354899 0.05841709 0.06092965 0.07333543 0.09422111
0.1048995 0.09170854 0.06171482 0.01303392]] --> 0.0
[[0.05354899 0.05841709 0.06092965 0.07333543 0.09422111 0.1048995
0.09170854 0.06171482 0.01303392 0. ]] --> 0.018844221105527303
[[0.05841709 0.06092965 0.07333543 0.09422111 0.1048995 0.09170854
0.06171482 0.01303392 0. 0.01884422]] --> 0.03643216080402034
Ahora ya podemos construir la red, vamos a empezar con una red sencilla de una única capa:
modelo = Sequential([
Input(shape=(1, 10)), # Cada entrada es un vector columna de 10 elementos.
SimpleRNN(64), # 64 neuronas recurrentes.
Dense(1) # Un único valor a la salida, la predicción.
])
La entrenamos:
Prácticamente no existe sobreentrenamiento.
Hacemos las predicciones:
y_pred = modelo.predict(X_test)
print(y_pred)
y_pred = scaler.inverse_transform(y_pred)
y_test = y_test.reshape(-1, 1)
y_test = scaler.inverse_transform(y_test)
Fíjate en que debemos deshacer el escalado.
Y este es el resultado que obtenemos:
MSE: 1.34
Para definir una red recurrente profunda, debemos pasar de una capa a otra la secuencia generada:
modelo = Sequential([
Input(shape=(1, steps)),
SimpleRNN(128, return_sequences=True),
SimpleRNN(64, return_sequences=True),
SimpleRNN(32),
Dense(1)
])
Fíjate en el segundo argumento de las primeras capas recurrentes.
MSE: 0.67. El error cuadrático medio ha mejorado.
Si las secuencias que utilizamos en el entrenamiento son largas, la contribución de los primeros términos se desvanece:
\[ y_t = g(\_,h_t,\_) \\ h_t = f(\_,h_{t-1},\_) \rightarrow y_t = g(f(\_,h_{t-1},\_)) \\ h_{t-1} = f(\_,h_{t-2},\_) \rightarrow y_t = g(f(f(\_,h_{t-2},\_))) \\ h_{t-2} = f(\_,h_{t-3},\_) \rightarrow y_t = g(f(f(f(\_,h_{t-3},\_)))) \\ \]
El término \(g(f(f(f(\_,h_{t-3},\_))))\) se va haciendo pequeño. La contribución de los primeros valores de la serie tienen una contribución baja.
La solución de las redes LSTM es añadir, dentro de cada neurona, un conjunto de puertas lógicas que añaden memoria a largo plazo.
Fuente: Hands-on machine learning… Aurélien Géron.
forget gate: controla qué parte de memoria se borra.
input gate: controla qué parte de \(g(x)\) debe añadirse a la memoria.
output gate: controla la parte de la memoria que se utiliza para calcular la salida y el estado oculto.
Las funciones \(f,g,i,o\) dependen tanto del estado oculto \(h_t\) como de la entrada \(x_t\).
\[ i_t = \sigma(W_{ih} h_{t-1} + W_{ix} x_t + b_i) \\ f_t = \sigma(W_{fh} h_{t-1} + W_{fx} x_t + b_f) \\ o_t = \sigma(W_{oh} h_{t-1} + W_{ox} x_t + b_o) \\ g_t = tanh(W_{gh} h_{t-1} + W_{gx} x_t + b_g) \\ \]
Las puertas combinan estos resultados para guardar el estado a largo plazo.
Desde el punto de vista de la programación con Tensorflow sólo tenemos que cambiar el nombre de la capa:
Hemos sustituido la capa recurrente por la LSTM.
El resultado de la predicción:
MSE: 0.52
La historia del entrenamiento:
Si comparamos los resultados de la red recurrente con la red LSTM:
RME: 1.34
RME: 0.52
Con el mismo número de neuronas, la red LSTM funciona mejor en este caso.
Definir una red LSTM profunda es, de nuevo, sustituir el tipo de capa.
modelo = Sequential([
Input(shape=(1, steps)),
LSTM(128, return_sequences=True),
LSTM(64, return_sequences=True),
LSTM(32),
Dense(1)
])
De nuevo, fíjate en el uso del parámetro return_sequences=True en las primeras capas de la red.
El resultado:
RME: 1.28 el resultado ha empeorado, para este caso particular.
El modelo Autoregresive Moving Average (ARMA) predice el próximo valor en una serie temporal a partir de los valores actuales y las diferencias entre los valores reales y los predichos anteriormente:
\[ \hat y_t = \sum_{i=1}^p \alpha_i y_{t-i} + \sum_{i=1}^q \theta_i \epsilon_{t-i} \\ \epsilon_{t-1} = y_{t-1} - \hat y_{t-1} \\ \]
En este caso, el entrenamiento consiste en encontrar los mejores valores para los parámetros \(\alpha_i\) y \(\theta_i\) para una secuencia temporal dada, y unos hiperparámetros \(p\) y \(q\).
Es importante notar que este modelo asume la estacionalidad de la serie. No podríamos aplicar este modelo a la serie de datos de CO2, pero sí a su componente estacional.
El modelo Autoregresive Integrated Moving Averange (ARIMA) permite trabajar con series no estacionarias.
La idea básica de este modelo es que en el cálculo de la predicción también se introduce una media deslizante sobre los datos de la serie para eliminar la posible componente de tendencia de la serie.
Algo parecido a lo que hemos hecho ad-hoc con la serie de datos de concentración de CO2
Existen otras versiones que intentan introducir mejoras sobre los modelos anteriores, como es el caso de SARIMA.
Las cadenas de Markov son un modelo que predice el siguiente valor en una secuencia temporal. No es necesario que los valores sean numéricos, pueden ser de cualquier tipo.
A y E son los dos estados. Las flechas indican la probabilidad de las transiciones entre estados.
Las transiciones se pueden representar como una matriz:
\[ \begin{matrix} & A & E \\ A & 0.6 & 0.7 \\ E & 0.4 & 0.3 \\ \end{matrix} \]
Las probabilidades de los estados finales se calculan multiplicando la matriz de transición por el vector de estado:
\[ \begin{bmatrix} 0.6 & 0.7 \\ 0.4 & 0.3 \\ \end{bmatrix} \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} = \begin{bmatrix} 0.6 \\ 0.4 \\ \end{bmatrix} \]
Es decir, si partimos del estado A, el siguiente estado será A con un 60% de probabilidad y B con un 40% de probabilidad.
Si queremos la probabilidad en el segundo instante, hacemos:
\[ \begin{bmatrix} 0.6 & 0.7 \\ 0.4 & 0.3 \\ \end{bmatrix} \begin{bmatrix} 0.6 & 0.7 \\ 0.4 & 0.3 \\ \end{bmatrix} \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} = \begin{bmatrix} 0.639 \\ 0.361 \\ \end{bmatrix} \]
Este tipo de matrices, cada una de sus columnas suman 1, tienen algunas propiedades interesantes, una de ellas es la de tener un estado estacionario.
Si multiplicamos infinitas veces a matriz por cualquier estado inicial de partida, llegamos a un estado que no varia, el estado estacionario.
\[ \begin{bmatrix} 0.6 & 0.7 \\ 0.4 & 0.3 \\ \end{bmatrix}^\infty \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} = \begin{bmatrix} 0.\widehat{63} \\ 0.\widehat{36} \\ \end{bmatrix} \]
Dicho de otro modo, el vector \(\begin{bmatrix} 0.\widehat{63} \\ 0.\widehat{36} \\ \end{bmatrix}\) es autovector de la matriz con autovalor \(\lambda=1\).
Entrenar un modelo de cadena de Markov implica estimar la matriz de transición dada una cadena.
En los modelo ocultos de Markov, no se conoce el estado en el que se encuentra el sistema, de ahí lo de ocultos, sólo se ven las observaciones que se emiten en cada uno de los estados.
Dentro de cada estado podemos tener un proceso aleatorio que genera el dato que se va a emitir. El proceso aleatorio es una hipótesis, por ejemplo, suponemos que el proceso aleatoria sigue una distribución de Gauss.
Fíjate en que si no existe proceso aleatorio, es decir, si en cada estado se emite siempre el mismo valor, lo que tenemos es una cadena de Markov.
En este caso el entrenamiento consiste en encontrar la matriz de transiciones, y los parámetros que definen el proceso aleatorio de cada estado a partir de una secuencia de observaciones.
Antes de la irrupción de las redes neuronales, los mejores modelos de reconocimiento del lenguaje natural estaban basados en modelos ocultos de Markov.
Aprendizaje Automático (IR2130) - Óscar Belmonte Fernández