Sandwood es un lenguaje, compilador y tiempo de ejecución para modelos probabilísticos basados en JVM. Está diseñado para permitir que los modelos se escriban en un lenguaje familiar para los desarrolladores de Java. Los modelos resultantes toman la forma de objetos Java, lo que les permite ser componentes bien abstraídos de un sistema integral.
Con un modelo bayesiano tradicional, el usuario debe diseñar el modelo y luego implementar el código de inferencia para cualquier operación que desee realizar en el modelo. Esto crea una serie de problemas:
Construir código de inferencia es técnicamente difícil y requiere mucho tiempo. Este paso presenta una oportunidad para que se introduzcan errores sutiles.
Si se modifica el modelo, entonces será necesario actualizar el código de inferencia. Esto también requiere mucho tiempo y es un desafío técnico, lo que genera los siguientes problemas:
Actúa como un elemento disuasorio para la modificación de modelos.
Es posible que diferentes operaciones de inferencia se desfasen, por lo que algunas funcionan en el modelo antiguo y otras en el nuevo.
Presenta otra oportunidad para que se introduzcan errores en el algoritmo de inferencia cuando los usuarios intentan realizar ajustes sutiles al código existente.
La programación probabilística supera estos problemas al permitir que los modelos se describan utilizando una API o un lenguaje específico de dominio (DSL), como es el caso de Sandwood. Sandwood DSL se compila para producir clases Java que representan el modelo e implementan todas las operaciones de inferencia requeridas. Esto tiene una serie de ventajas:
Sandwood consta de 3 componentes cada uno en su correspondiente directorio:
Cada pieza depende de las piezas anteriores. Cada directorio de componentes contiene un archivo Maven POM para construir el componente. Para el compilador y el complemento, será necesario llamarlos con install
para que estén disponibles para etapas posteriores, es decir, mvn clean install
. Los ejemplos sólo deben compilarse como mvn clean package
.
Una vez instalado Sandwood, actualmente existen 3 formas de compilar un modelo:
Para usar Sandwood desde la línea de comandos una vez que se hayan creado el compilador y el tiempo de ejecución, se pueden encontrar scripts de línea de comandos que tienen una funcionalidad similar a javac
en commandline/SandwoodC/bin
. Para usar esto, el usuario normalmente agregaría el directorio bin a la ruta y luego llamaría a sandwoodc.sh HMM.sandwood para compilar el modelo HMM. sandwoodc.sh -h
o sandwoodc.bat -h
dará como resultado que se imprima una descripción del uso y las opciones disponibles.
Se puede acceder a toda la funcionalidad de SandwoodC llamando al método compile
en org.sandwood.compilation.SandwoodC
y pasando una matriz que contiene los argumentos que se habrían pasado a la línea de comando.
El complemento Maven se puede utilizar para activar automáticamente la compilación de archivos Sandwood cuando se crea el proyecto dependiente. Para usar el complemento, debe agregar el tiempo de ejecución de Sandwood como una dependencia y agregar el complemento a la compilación. Esto se logra con las siguientes adiciones al archivo POM:
<dependencies>
<dependency>
<groupId>org.sandwood</groupId>
<artifactId>sandwood-runtime</artifactId>
<version>0.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.sandwood</groupId>
<artifactId>sandwoodc-maven-plugin</artifactId>
<version>0.3-SNAPSHOT</version>
<executions>
<execution>
<configuration>
<partialInferenceWarning>true</partialInferenceWarning>
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
</configuration>
<goals>
<goal>sandwoodc</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>`
La inclusión del elemento <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
indica al complemento en qué directorio buscar modelos. Otras banderas útiles incluyen:
debug
Esta opción se utiliza para obtener información de depuración de SandwoodC. Establecer esta opción en true
hace que Sandwood genere un rastro de sus acciones. El valor predeterminado es false
. Tenga en cuenta que esta bandera es para depurar errores con la configuración del compilador/compilador, no con el modelo que se está compilando. El compilador siempre devolverá los errores y advertencias en los archivos del modelo Sandwood.
partialInferenceWarning
Esta opción se utiliza para evitar que SandwoodC falle cuando no se pueden construir algunos pasos de inferencia. Establecer esta opción en true
hace que Sandwood solo genere advertencias sobre los pasos que faltan. El valor predeterminado es false
.
sourceDirectory
Este parámetro establece en qué directorio buscar archivos de modelo. Dentro de este directorio los modelos se pueden ubicar en diferentes paquetes.
outputDirectory
Este parámetro establece en qué directorio se debe colocar el código fuente Java para los modelos. El valor predeterminado es ${project.build.directory}/generated-sources/sandwood
.
calculateIndividualProbabilities
Este parámetro especifica si las probabilidades de cada variable aleatoria construida en un bucle deben calcularse en lugar de un valor único para todas las instancias. El valor predeterminado es false
.
javadoc
Este parámetro indica al compilador que genere JavaDoc para complementar el modelo. El valor predeterminado es false
.
javadocDirectory
Este parámetro especifica la ubicación en la que se debe colocar el archivo generado.
executable
Este parámetro permite especificar una JVM alternativa para ejecutar el compilador Sandwood.
Lo que sigue es una introducción a cómo escribir modelos Sandwood y cómo utilizar las clases resultantes que implementan los modelos.
En este diagrama se puede ver un resumen de los pasos por los que pasa un modelo. Los modelos comienzan como un archivo .sandwood
que se compila en un conjunto de archivos de clase. Se pueden crear instancias de estos varias veces para generar múltiples instancias del modelo con diferentes configuraciones.
Como ejemplo en ejecución usaremos un modelo oculto de Markov (HMM). Este modelo está escrito aquí en Sandwood. Este modelo debe guardarse en un archivo llamado HMM.sandwood
en un directorio de paquetes org/sandwood/examples/hmm
. Puede encontrar una descripción más completa del idioma aquí.
package org . sandwood . examples . hmm ;
model HMM ( int [] eventsMeasured , int numStates , int numEvents ) {
//Construct a transition matrix m.
double [] v = new double [ numStates ] <~ 0.1 ;
double [][] m = dirichlet ( v ). sample ( numStates );
//Construct weighting for which state to start in.
double [] initialState = new Dirichlet ( v ). sample ();
//Construct weighting for each event in each state.
double [] w = new double [ numEvents ] <~ 0.1 ;
double [][] bias = dirichlet ( w ). sample ( numStates );
//Allocate space to record the sequence of states.
int sequenceLength = eventsMeasured . length ;
int [] st = new int [ sequenceLength ];
//Calculate the movements between states.
st [ 0 ] = categorical ( initialState ). sampleDistribution ();
for ( int i : [ 1. . sequenceLength ) )
st [ i ] = categorical ( m [ st [ i - 1 ]]). sampleDistribution ();
//Emit the events for each state.
int [] events = new int [ sequenceLength ];
for ( int j = 0 ; j < sequenceLength ; j ++)
events [ j ] = new Categorical ( bias [ st [ j ]]). sample ();
//Assert that the events match the eventsMeasured data.
events . observe ( eventsMeasured );
}
Además de la documentación del lenguaje Sandwood y el comentario JavaDoc que se puede generar para un modelo, hay varios ejemplos en el directorio de ejemplos de Sandwood y sugerimos a los nuevos usuarios que comiencen por examinarlos y modificarlos.
Puede encontrar una descripción del lenguaje utilizado para describir los modelos Sandwood aquí. El lenguaje se construye con la intención de que resulte familiar para los desarrolladores de Java, pero no contiene la capacidad de construir objetos. Planeamos agregar soporte para tipos de registros en el futuro para simplificar la importación y exportación de datos hacia y desde los modelos.
Cuando se compila un modelo, se generan varios archivos de clases en el mismo paquete en el que se define el modelo. Una de estas clases tendrá el mismo nombre que el nombre proporcionado al modelo, por lo que en este caso HMM.class y esto es la clase que el usuario debe instanciar para tener una instancia del modelo. Cada variable públicamente visible en el modelo corresponde a un campo en la clase generada. El ejemplo de HMM se puede ver a continuación.
Al ejecutar el compilador con el indicador javadoc
establecido, se creará JavaDoc para cada método público y clase en el archivo de modelo generado.
Una vez que se ha compilado el modelo, necesitamos crear instancias del mismo. Estas instancias son independientes y el usuario puede crear tantas copias diferentes del modelo como desee.
Las instancias del objeto modelo se construyen mediante el constructor de clases. Como se describió anteriormente, normalmente hay 3 constructores para el modelo. El único caso en el que habrá menos es cuando las diferentes variantes del constructor se asignan a la misma firma, en cuyo caso un constructor se aplicará a más de uno de estos escenarios.
Constructor completo: este constructor toma todos los argumentos que aparecen en la firma del modelo y los establece. Este constructor se utiliza para las operaciones de inferir valores e inferir probabilidades.
Constructor vacío: este constructor no acepta argumentos y deja los parámetros para que el usuario los establezca más tarde.
Constructor de ejecución: este constructor elimina los argumentos que solo se observan y, para los argumentos observados cuyas dimensiones se utilizan como entradas para el código, toma esas dimensiones en lugar de los parámetros completos. Entonces, en el ejemplo de HMM, el parámetro eventsMeasured se convertirá en un número entero que describe la longitud de la secuencia.
Estos ejemplos de código demuestran cómo realizar llamadas a los modelos compilados.
Las interacciones con un modelo a través del objeto del modelo toman dos formas:
Llamadas a métodos de objetos de modelo para operaciones globales, como establecer políticas de retención predeterminadas, verificar si el modelo está listo para la inferencia e iniciar pasos de inferencia, etc.
Llamadas a objetos de parámetros de modelo. Cada variable pública nombrada en el modelo está representada por un campo correspondiente en el objeto del modelo. Las variables son públicas si se declaran en el ámbito más externo del modelo y no se etiquetan como private
, o se declaran en un ámbito interno y no se etiquetan como public
. Si un campo se declara público en un ámbito iterativo interno, por ejemplo, el cuerpo de un bucle for, se almacenará el valor de cada iteración.
El tipo de objeto dependerá de la variable. Estos se pueden dividir en 3 categorías:
Cada uno de estos campos hace referencia a un objeto con un conjunto de métodos que permiten al usuario establecer y leer valores y propiedades del parámetro. Las propiedades que se pueden configurar y leer incluyen la probabilidad del parámetro, la política de retención del parámetro y si el parámetro debe fijarse en su valor actual.
Algunos de los métodos más importantes del objeto de parámetro al realizar la inferencia del modelo son:
getSamples para devolver valores de muestra.
getMAP para devolver el valor máximo A posteriori.
setValue para permitir que un valor se establezca en un valor específico.
setFixed que toma un boolean
para marcar el valor como fijo y, por lo tanto, no debe actualizarse durante la inferencia. Es importante establecer el valor del parámetro antes de fijarlo.
getLogProbability que obtiene la probabilidad logarítmica de la variable después de inferir probabilidades.
Hay más métodos y recomendamos consultar el JavaDoc para familiarizarse con ellos.
Hay 3 tipos básicos de operaciones que se pueden realizar en un modelo:
setRentionPolicy
en la clase del modelo. Opcionalmente, las variables individuales pueden tener su política de retención establecida mediante llamadas al método setRetentionPolicy
correspondiente en cada objeto variable.Hay 3 políticas de muestreo:
NONE no registra ningún valor. Esto es particularmente útil si una de las variables es grande, por lo que tomarse el tiempo y el espacio para almacenarla sería un desperdicio.
SAMPLE registra el valor de cada iteración del algoritmo de inferencia, por lo que si se realizan 1000 iteraciones, se muestrearán 1000 valores de cada variable establecida en esta política de retención. Esto es útil para calcular la varianza y el valor promedio. Sin embargo, esto tiene una debilidad: si las posiciones de los valores dentro del modelo pueden moverse durante la inferencia, entonces los valores no se pueden promediar. Por ejemplo, con un modelo de tema, los temas 2 y 3 pueden intercambiar lugares durante la inferencia, por lo que promediar todos los valores para el tema 2 produce una mezcla del tema 2 y el tema 3. Para superar este Máximo A Posteriori (MAP) también se proporciona como un política de retención.
MAP o Máximo A Posteriori (MAP) registra los valores de las variables cuando el modelo se encuentra en su estado más probable. Esto soluciona el problema de las posiciones de valores transitorias, lo que significa que los valores no se pueden promediar, pero a expensas de poder calcular los límites. Esta opción también tiene ventajas de espacio si algunas de las variables son grandes.
Configuración: las llamadas a métodos adicionales en el objeto modelo permiten al usuario establecer propiedades como el quemado y el adelgazamiento al realizar este paso de inferencia. Burnin ignora los valores de las primeras n iteraciones, lo que permite que el modelo se aleje de un punto de partida de baja probabilidad antes de comenzar a tomar muestras. La adelgazamiento reduce la autocorrelación inducida por el procedimiento MCMC al considerar solo los valores de cada n -ésima iteración.
Inferir probabilidades Habiendo establecido los valores de algunos o todos los parámetros en el modelo, calcule la probabilidad de generar esos valores. Esto se puede calcular para cada variable del modelo y para el modelo en su conjunto.
Ejecutar modelo Ejecute el modelo como si fuera un código normal generando nuevos valores para cualquier parámetro que no haya sido corregido por el usuario. Un ejemplo de cuándo se utilizaría este comportamiento es para un modelo de regresión lineal. En este caso, los coeficientes del modelo se inferirían primero utilizando datos de entrenamiento. Una vez que se hayan inferido, se arreglarán y se establecerán nuevos datos de entrada. Luego, el modelo se ejecutaría para generar las predicciones correspondientes para estos nuevos datos de entrada. Esta forma de ejecución también podría usarse para generar datos sintéticos representativos a partir de un modelo entrenado.
Construir y entrenar un modelo.
//Load inputs
int nStates = 25 ;
int [] actions = loadActions (....);
int nActions = maxActions (....);
//Construct the model
HMM model = new HMM ( actions , nActions , nStates );
//Set the retention policies
model . setDefaultRetentionPolicy ( RetentionPolicy . MAP );
model . st . setRetentionPolicy ( RetentionPolicy . NONE );
//Pick a random number generator. The ones introduced in Java 17 are faster and better quality.
model . setRNGType ( RandomType . L64X1024MixRandom );
//Instruct the model to use the ForkJoin framework for parallel execution.
model . setExecutionTarget ( ExecutionTarget . forkJoin );
//Run 2000 inference steps to infer model values
model . inferValues ( 2000 );
//Gather the results.
double [] initialState = model . initialState . getMAP ();
double [][] bias = model . bias . getMAP ();
double [][] transitions = model . m . getMAP ();
Construir modelo e inferir probabilidades.
//Load inputs
int nStates = 25 ;
int [] actions = loadActions (....);
int nActions = maxActions (....);
//Load model parameters
double [][] bias = model . bias . getMAP ();
double [][] transitions = model . m . getMAP ();
//Construct the model
HMM model = new HMM ( actions , nActions , nStates );
//Set and fix trained values
model . bias . setValue ( bias );
Model . m . setValue ( transitions );
//Run 2000 inference steps to infer probabilities
model . inferProbabilities ( 2000 );
//Recover the probabilities of the model parameter actions.
double actionsProbability = model . actions . getProbability ();
//Recover the probability of the model as a whole
double modelProbability = model . getProbability ()
Para obtener ayuda con Sandwood, inicie o únase a una discusión en la página de discusiones.
Este proyecto agradece las contribuciones de la comunidad. Antes de enviar una solicitud de extracción, revise nuestra guía de contribución.
Consulte la guía de seguridad para conocer nuestro proceso de divulgación responsable de vulnerabilidades de seguridad.
Copyright (c) 2019-2024 Oracle y/o sus afiliados.
Publicado bajo la Licencia permisiva universal v1.0 como se muestra en https://oss.oracle.com/licenses/upl/.