Artículo
· 27 jun, 2023 Lectura de 12 min

Predicciones con IntegratedML e IRIS

Como sabréis, si leeis habitualmente los artículos que se publican en la Comunidad, el pasado mes de mayo InterSystems organizó el Hackaton del JOnTheBeach2023 celebrado en Málaga. El tema que se propuso fue el del uso de las herramientas de análisis predictivo que InterSystems IRIS pone a disposición de todos los desarrolladores con IntegratedML. Debemos agradecer tanto a @Thomas Dyar como a @Dmitry Maslennikov todo el trabajo y el empeño que pusieron para que fuese un rotundo éxito.

Introduzcamos brevemente IntegratedML:

IntegratedML

IntegratedML es una herramienta de análisis predictivo que permite a cualquier desarrollador simplificar las tareas necesarias para el diseño, elaboración y prueba de modelos predictivos.

Nos permite pasar de un modelo de diseño así:

A uno mucho más rapido y sencillo como este:

Y lo hace utilizando comandos SQL, de tal forma que todo sea mucho más sencillo y cómodo de utilizar. IntegratedML también nos permite elegir qué motor vamos a usar en la creación de nuestro modelo, pudiendo así elegir el que más adecuado nos resulte.

¿Cómo verlo en acción?

Siempre que he visto presentaciones de IntegratedML me ha encantado su sencillez, pero me quedaba la duda de cómo traspasar esa sencillez de su uso a un caso real. Pensando un poco en nuestros clientes habituales recordé lo común que es el uso de IRIS para integrar los datos de las aplicaciones departamentales de los hospitales con un HIS y la gran cantidad de información de episodios clínicos disponible en todos ellos, así que me puse manos a la obra para montar un ejemplo completo.

Tenéis el código fuente en Open Exchange. El proyecto se arranca con Docker y sólo deberéis alimentar la producción desplegada con los archivos adjuntos que iremos mostrando.

Como podéis ver el proyecto contiene clases de ObjectScript que se van a cargar automáticamente en el momento de construir la imagen. Para ello sólo necesitáis abrir el terminal de VS Code y ejecutar los siguientes comandos (con Docker arrancado).

docker-compose build
docker-compose up -d

Al arrancar el contenedor se va a crear un namespace llamado MLTEST y se arrancará una producción en el que encontraremos todos los componentes del negocio necesarios para la ingesta de los datos en crudo, la creación del modelo, su entrenamiento y su posterior puesta en práctica mediante la recepción de mensajería HL7.

Pero no nos adelantemos aún y sigamos el gráfico del análisis predictivo.

Adquisición de los datos

Muy bien, acotemos el objetivo de nuestra predicción. Rebuscando por páginas de la Administración Pública de España encontré unos cuantos CSV que encajaban perfectamente con el universo de integraciones con origen y destino en un HIS. En este caso el fichero que elegí fue el relativo a los datos de ingresos y altas hospitalarias por rotura de cadera en Castilla y León entre los años 2020 y 2022.

Como véis tenemos datos como la edad y sexo del paciente, fechas de ingreso y alta y centro hospitalario. Perfecto, con estos datos podríamos intentar predecir la estancia hospitalaria de cada paciente, es decir, el número de días entre ingreso y alta.

Tenemos un CSV pero necesitamos almacenarlo en nuestro IRIS y nada más sencillo que usar el Record Mapper de IRIS. El resultado del uso de Record Mapper lo podéis ver en la columna de Business Services de la producción de MLTEST:

 

  • CSVToEpisodeTrain es el BS encargado de leer el CSV y enviar los datos al BP MLTEST.BP.RecordToEpisodeTrain que explicaremos más adelante. Los datos obtenidos por este BS serán los usados para entrenar nuestro modelo.
  • CSVToEpisode es el BS que leera los datos del CSV que usaremos posteriormente para lanzar predicciones de prueba antes de poner en marcha nuestras predicciones obtenidas a partir de mensajes HL7.

Ambos BS van a crear por cada línea del CSV un objeto de la clase User.IctusMap.Record.cls que se lo enviarán a sus respectivos BP donde se realizarán las transformaciones necesarias para finalmente obtener registros de nuestras tablas MLTEST_Data.Episode y MLTEST_Data.EpisodeTrain, esta última será la tabla que usaremos para generar el modelo de predicción, mientras que la anterior es donde almacenaremos nuestros episodios.

Preparación de los datos

Antes de crear nuestro modelo deberemos transformar la lectura del CSV en objetos que sean fácilmente utilizables por el motor de predicciones y para ello usaremos los siguientes BP:

  • MLTEST.BP.RecordToEpisode: que nos realizará la transformación del registro de CSV a nuestra tabla de episodios MLTEST_Data.Episode
  • MLTEST.BP.RecordToEpisodeTrain: que realiza la misma transformación que en el caso anterior pero almacenando el episodio en MLTEST_Data.EpisodeTrain.

Podríamos haber usado un sólo BP para el registro en ambas tablas, pero para que sea más claro el proceso lo dejaremos así. En la transformación realizada por los BP hemos reemplazado todos los campos de texto por valores numéricos para agilizar el entrenamiento del modelo.

Muy bien, tenemos nuestros BS y nuestros BP funcionando, alimentemoslos copiando en el proyecto el archivo /shared/train-data.csv a la ruta /shared/csv/trainIn:

 Aquí tenemos todos los registros de nuestro archivo consumidos, transformados y registrados en nuestra tabla de entrenamiento. Repitamos la operación con los registros que vamos a usar para una primera prueba de predicciones. Copiando /shared/test-data.csv a la ruta /shared/csv/newIn ya tendríamos todo preparado para crear nuestro modelo.

En este proyecto no sería necesario que ejecutáseis las instrucciones de creación y entrenamiento, ya que se encuentran incluidas en el BO que gestiona el registro de los datos recibidos por mensajería HL7, pero para que lo podáis ver con más detalle vamos a hacerlo antes de probar la integración con los mensajes HL7.

AutoML

Tenemos nuestros datos de entrenamiento y nuestros datos de prueba, creemos nuestro modelo. Para ello accederemos desde el namespace MLTEST a la pantalla SQL de nuestro IRIS (System Explorer --> SQL) y ejecutaremos los siguientes comandos:

CREATE MODEL StayModel PREDICTING (Stay) FROM MLTEST_Data.EpisodeTrain

En esta query estamos creando un modelo de predicción llamado StayModel que va a predecir el valor de la columna Stay (estancia) de nuestra tabla con episodios de entrenamiento. La columna de estancia no venía en nuestro CSV pero la hemos calculado en el BP encargado de la transformación del registro de CSV.

A continuación procedemos a entrenar el modelo:

TRAIN MODEL StayModel

Esta instrucción como el lógico le llevará un tiempo pero una vez que concluya el entrenamiento podremos validar el modelo con nuestros datos de prueba ejecutando la siguiente instrucción:

VALIDATE MODEL StayModel FROM MLTEST_Data.Episode

Esta instrucción nos calculará como de aproximadas son nuestras estimaciones. Como podréis imaginar con los datos que tenemos, estas no serán precisamente para tirar cohetes. Podéis visualizar el resultado de la validación con la siguiente consulta:

SELECT * FROM INFORMATION_SCHEMA.ML_VALIDATION_METRICS

A partir de las metricas que obtenemos con esa consulta podemos inferir que el el modelo elegido automáticamente por AutoML es de clasificación en lugar de un modelo de regresión. Expliquemos que significan los resultados obtenidos (¡gracias @Yuri Marx por tu artículo!):

  • Precision: se calcula dividiendo la cantidad de verdaderos positivos por la cantidad de positivos previstos (suma de verdaderos positivos y falsos positivos).
  • Recall: se calcula dividiendo el número de verdaderos positivos por el número de positivos reales (suma de verdaderos positivos y falsos negativos).
  • F-Measure: calculado por la siguiente expresión: F = 2 * (Precision * Recall) / (Precision + Recall)
  • Accuracy: calculado por la división del número de positivos verdaderos y negativos verdaderos por el numero total de filas (suma de positivos verdaderos, falsos positivos, negativos verdaderos y falsos negativos) de todo el conjunto de datos.

Con esta explicación ya podemos entender como de bueno es el modelo generado:

Como véis, en números generales nuestro modelo es bastante malo, apenas alcanzamos un 35% de aciertos, si entramos en más detalle vemos que para estancias cortas la precisión anda entre el 35% y el 50%, por lo que seguramente necesitaríamos ampliar los datos que tenemos con información a cerca de posibles patologías que pueda tener el paciente y el triaje respecto a la fractura.

Como no disponemos de esos datos que afinarían mucho más nuestro modelo vamos a imaginar que con lo que tenemos es más que suficiente para nuestro objetivo, así que ya podemos empezar a alimentar nuestra producción con mensajes ADT_A01 de admisión de pacientes  y veremos las predicciones que obtenemos.

Puesta en producción

Con el modelo ya entrenado sólo nos resta preparar la producción para crear un registro en nuestra tabla MLTEST_Data.Episode  por cada mensaje recibido. Veamos los componentes de nuestra producción:

  • HL7ToEpisode: es el BS que capturará el archivo con mensajes HL7. Este BS redirigirá los mensajes al BP MLTEST.BP.RecordToEpisodeBPL
  • MLTEST.BP.RecordToEpisodeBPL: este BPL tendrá los siguientes pasos.
    • Transformación del HL7 en un objeto MLTEST.Data.Episode
    • Registro en la base de datos del objeto Episodio.
    • Consulta al BO MLTEST.BO.PredictStayEpisode para obtener la predicción de días de hospitalización.
    • Escritura de traza con la predicción obtenida.
  • MLTEST.BO.PredictStayEpisode: BO encargado de lanzar de forma automática las consultas necesarias al modelo de predicción. Si este no existe se encargará de crearlo y entrenarlo automáticamente, de tal forma que no será necesario ejecutar los comandos de sql. Echemos un vistazo al código.
    Class MLTEST.BO.PredictStayEpisode Extends Ens.BusinessOperation
    {
    
    Property ModelName As %String(MAXLEN = 100);
    /// Description
    Parameter SETTINGS = "ModelName";
    Parameter INVOCATION = "Queue";
    /// Description
    Method PredictStay(pRequest As MLTEST.Data.PredictionRequest, pResponse As MLTEST.Data.PredictionResponse) As %Status
    {
        set predictionRequest = pRequest
        set pResponse = ##class("MLTEST.Data.PredictionResponse").%New()
        set pResponse.EpisodeId = predictionRequest.EpisodeId
        set tSC = $$$OK
        // CHECK IF MODEL EXISTS 
        set sql = "SELECT MODEL_NAME FROM INFORMATION_SCHEMA.ML_MODELS WHERE MODEL_NAME = '"_..ModelName_"'"
        set statement = ##class(%SQL.Statement).%New()
        set status = statement.%Prepare(sql)
        if ($$$ISOK(status)) {
            set resultSet = statement.%Execute()
            if (resultSet.%SQLCODE = 0) {
                set modelExists = 0
                while (resultSet.%Next() '= 0) {
                    if (resultSet.%GetData(1) '= "") {
                        set modelExists = 1
                        // GET STAY PREDICTION WITH THE LAST EPISODE PERSISTED
                        set sqlPredict = "SELECT PREDICT("_..ModelName_") AS PredictedStay FROM MLTEST_Data.Episode WHERE %ID = ?"
                        set statementPredict = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1
                        set statusPredict = statementPredict.%Prepare(sqlPredict)
                        if ($$$ISOK(statusPredict)) {
                            set resultSetPredict = statementPredict.%Execute(predictionRequest.EpisodeId)
                            if (resultSetPredict.%SQLCODE = 0) {
                                    while (resultSetPredict.%Next() '= 0) {
                                        set pResponse.PredictedStay = resultSetPredict.%GetData(1)
                                    }
                            }
                        }
                        else {
                            set tSC = statusPredict
                        }
                    }
                }
                if (modelExists = 0) {
                    // CREATION OF THE PREDICTION MODEL
                    set sqlCreate = "CREATE MODEL "_..ModelName_" PREDICTING (Stay) FROM MLTEST_Data.EpisodeTrain"
                    set statementCreate = ##class(%SQL.Statement).%New()
                    set statusCreate = statementCreate.%Prepare(sqlCreate)
                    if ($$$ISOK(status)) {
                        set resultSetCreate = statementCreate.%Execute()
                        if (resultSetCreate.%SQLCODE = 0) {
                            // MODEL IS TRAINED WITH THE CSV DATA PRE-LOADED
                            set sqlTrain = "TRAIN MODEL "_..ModelName
                            set statementTrain = ##class(%SQL.Statement).%New()
                            set statusTrain = statementTrain.%Prepare(sqlTrain)
                            if ($$$ISOK(statusTrain)) {
                                set resultSetTrain = statementTrain.%Execute()
                                if (resultSetTrain.%SQLCODE = 0) {
                                    // VALIDATION OF THE MODEL WITH THE PRE-LOADED EPISODES
                                    set sqlValidate = "VALIDATE MODEL "_..ModelName_" FROM MLTEST_Data.Episode"
                                    set statementValidate = ##class(%SQL.Statement).%New()
                                    set statusValidate = statementValidate.%Prepare(sqlValidate)
                                    if ($$$ISOK(statusValidate)) {
                                        set resultSetValidate = statementValidate.%Execute()
                                        if (resultSetValidate.%SQLCODE = 0) {
                                            // GET STAY PREDICTION WITH THE LAST EPISODE PERSISTED
                                            set sqlPredict = "SELECT PREDICT("_..ModelName_") AS PredictedStay FROM MLTEST_Data.Episode WHERE %ID = ?"
                                            set statementPredict = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1
                                            set statusPredict = statementPredict.%Prepare(sqlPredict)
                                            if ($$$ISOK(statusPredict)) {
                                                set resultSetPredict = statementPredict.%Execute(predictionRequest.EpisodeId)
                                                if (resultSetPredict.%SQLCODE = 0) {
                                                    while (resultSetPredict.%Next() '= 0) {
                                                        set pResponse.PredictedStay = resultSetPredict.%GetData(1)
                                                    }
                                                }
                                            }
                                            else {
                                                set tSC = statusPredict
                                            }
                                        }
                                    }
                                    else {
                                        set tSC = statusValidate
                                    }
                                }
                            }
                            else {
                                set tSC = statusTrain
                            }
                        }
                    }
                    else {
                        set tSC = status
                    }
                }
            }
        }
        else {
            set tSC = status
        }
        quit tSC
    }
    
    XData MessageMap
    {
    <MapItems>
      <MapItem MessageType="MLTEST.Data.PredictionRequest">
        <Method>PredictStay</Method>
      </MapItem>
    </MapItems>
    }
    
    }
    
    Como podéis observar, tenemos una propiedad que nos servirá para definir el nombre que queremos para nuestro modelo de predicción e inicialmente lanzaremos una consulta a la tabla ML_MODELS para asegurarnos de que el modelo exista.

Pues bien, ya estamos listos para lanza nuestros mensajes, para ello copiaremos el fichero del proyecto /shared/messagesa01.hl7 a la carpeta /shared/hl7/in esta acción nos enviará 50 mensajes de datos generados a nuestra producción. Veamos algunas de las predicciones.

Para nuestra paciente Sonia Martínez con 2 meses de edad tendremos una estancia de...

¡8 días! Le deseamos una pronta recuperación.

Veamos a otro paciente:

Ana Torres Fernández, de 50 años de edad...

9 días de estancia para ella.

Pues por hoy es todo. Lo menos importante de este ejemplo es el valor numérico de la predicción, ya véis que es bastante pobre por las estadísticas que hemos obtenido, pero os podrá resultar muy útil para casos en los que tengáis un buen conjunto de datos sobre el que aplicar esta funcionalidad tan genial de IntegratedML.

Si tenéis ganas de trastear con ella podéis descargaros la versión Community o usar la configurada en el proyecto de OpenExchange asociada a este artículo.

Si tenéis cualquier duda o necesitais alguna aclaración no dudéis en preguntar en los comentarios.

Comentarios (0)1
Inicie sesión o regístrese para continuar