DEV Community

Cover image for Entrenando a MAJN: Prediciendo fallas de Turbinas con Deep Learning y Bare-Metal C++
galp76
galp76

Posted on

Entrenando a MAJN: Prediciendo fallas de Turbinas con Deep Learning y Bare-Metal C++

El contexto

Soy Ingeniero Industrial desde el año 2002 y, desde el 2020, también soy un apasionado autodidacta de la programación. Hace algunas semanas le pregunté a Google Gemini si podía ayudarme a encontrar un proyecto donde pudiera aplicar ambas áreas. ¿Su respuesta? Una genialidad: usar el famoso Turbofan Engine Degradation Simulation Dataset de la NASA para entrenar redes neuronales y hacer mantenimiento predictivo. Así nació MAJN (el nombre lo escogió mi hijo), y así comenzó el proyecto que quiero documentar a través de este post.

Buckle your seatbelt, Dorothy, 'cause Kansas is going bye-bye.

Hasta ahora había jugado con redes neuronales simples y un clasificador que alcanzaba fácilmente el 99,9 % de accuracy.

Pero este dataset de la NASA es otro nivel.

Aquí ya no basta con un perceptrón multicapa tradicional. Vamos a tener que trabajar con series temporales, memoria a largo plazo y redes LSTM de verdad.

Primer intento

Hace algunos meses usé el código del excelente libro Neural Networks and Deep Learning de Michael Nielsen para entrenar un clasificador sencillo de tan solo 3 clases, el cual obtuvo 99,9% de precisión en los tests, así que decidí hacer pruebas con este nuevo Dataset.
Comenzamos a trabajar con el archivo train0001.txt del dataset. La primera prueba dió apenas un 50% de precisión en los tests, y después de nuevas iteraciones (más capas, más neuronas por capa, mas épocas) solo pude obtener un 59% en los tests, así que tocó hacerle caso a Gemini y empezar a hacer los preparativos en el dataset para usar Redes Neuronales Recurrentes, redes LSTM para ser más exactos, que están diseñadas para trabajar con series temporales, en las cuales es importante tomar en consideración como evolucionan los valores de la variable dependiente en función del tiempo.

Preparando los datos

Al igual que en el caso del código del Perceptrón Multicapa de Nielsen, procedimos a limpiar el dataset. Como ya se sabe, los datos de train_FD001.txt fueron simulados usando una sola condición de vuelo, así que lo primero que tuvimos que hacer fue buscar sensores que no cambiaran sus valores a lo largo de la vida de los motores, ya que los mismos representan ruido en el dataset a la hora de entrenar la red neuronal. Para ello, convertimos train_FD001.txt en un Dataframe de Pandas y usamos la función describe() para ubicar desviaciones estándar iguales o cercanas a cero.

import pandas as pd

# Definimos los nombres de las 26 columnas
nombres_columnas =[
    'id_motor', 'ciclo', 
    'config_1', 'config_2', 'config_3', 
    'sensor_1', 'sensor_2', 'sensor_3', 'sensor_4', 'sensor_5', 
    'sensor_6', 'sensor_7', 'sensor_8', 'sensor_9', 'sensor_10', 
    'sensor_11', 'sensor_12', 'sensor_13', 'sensor_14', 'sensor_15', 
    'sensor_16', 'sensor_17', 'sensor_18', 'sensor_19', 'sensor_20', 
    'sensor_21'
]

# TIP: Usamos sep=r'\s+' porque los datos están separados por uno o más espacios, no por comas.
df_train = pd.read_csv('train_FD001.txt', sep=r'\s+', header=None, names=nombres_columnas)

# Ver las primeras 5 filas para comprobar que cargó bien
print(df_train.head())
# Buscamos valores de std iguales o cercanos a cero
df_train.describe()
Enter fullscreen mode Exit fullscreen mode

Despues del análisis se decidió eliminar los sensores 1, 5, 6, 10, 16, 18 y 19, junto con las 3 configuraciones operativas, ya que no aportan ninguna información útil. Estas eliminaciones son la regla estándar para el estudio del dataset FD001.

# Lista de columnas inútiles para FD001
columnas_a_eliminar =['config_1', 'config_2', 'config_3', 
                       'sensor_1', 'sensor_5', 'sensor_6', 'sensor_10', 
                       'sensor_16', 'sensor_18', 'sensor_19']

# Eliminamos esas columnas de nuestro DataFrame de entrenamiento
df_train.drop(columns=columnas_a_eliminar, inplace=True)
Enter fullscreen mode Exit fullscreen mode

El siguiente paso es crear la columna de Vida Útil Restante (RUL), que será la meta (Y) que nuestra red neuronal aprenderá a predecir.

# Creamos una columna temporal con la vida máxima de CADA motor
df_train['ciclo_max'] = df_train.groupby('id_motor')['ciclo'].transform('max')

# Calculamos el RUL restando el ciclo máximo menos el ciclo actual
df_train['RUL'] = df_train['ciclo_max'] - df_train['ciclo']

# Ya no necesitamos la columna temporal, la borramos
df_train.drop(columns=['ciclo_max'], inplace=True)
Enter fullscreen mode Exit fullscreen mode

El siguiente paso es aplicar el tope al RUL: añadimos esta línea al código para topar el RUL máximo a 125 (el estándar en la NASA para este problema):

# Todo RUL mayor a 125, se convierte en 125.
df_train['RUL'] = df_train['RUL'].clip(upper=125)
Enter fullscreen mode Exit fullscreen mode

Normalización de datos de sensores

¿Qué hace la Normalización?
Toma todas las columnas, sin importar si son RPM, grados Celsius o PSI, y las exprime para meterlas en una misma escala estándar (usualmente entre 0 y 1 o entre -1 y 1).
Así, el valor máximo de RPM (15,000) se convierte en "1.0", y el valor máximo de vibración (0.05) también se convierte en "1.0". Ahora, la red neuronal los trata con el mismo nivel de respeto y puede descubrir cuál importa realmente para predecir la falla basándose en su comportamiento, no en su tamaño. OJO AQUÍ: No podemos normalizar a lo loco. El id_motor, el ciclo y nuestra meta RUL NO se normalizan. Esos son nuestros identificadores y nuestra respuesta en ciclos reales. Solo vamos a normalizar las columnas de los sensores (nuestra "X").

from sklearn.preprocessing import MinMaxScaler

# 1. Identificamos cuáles son las columnas de los sensores que nos quedaron
# (Es decir, todas menos id_motor, ciclo y RUL)
columnas_sensores = df_train.columns.drop(['id_motor', 'ciclo', 'RUL'])

# 2. Inicializamos el escalador
scaler = MinMaxScaler()

# 3. Entrenamos el escalador y transformamos los datos (solo los sensores)
df_train[columnas_sensores] = scaler.fit_transform(df_train[columnas_sensores])
Enter fullscreen mode Exit fullscreen mode



En este primer post nos enfocamos en entender el dataset y preparar los datos. En la próxima entrega entraremos de lleno en ventanas deslizantes (sliding windows), secuencias temporales y el corazón del modelo: las redes LSTM.

Top comments (0)