Sandwood est un langage, un compilateur et un environnement d'exécution pour les modèles probabilistes basés sur JVM. Il est conçu pour permettre d'écrire des modèles dans un langage familier aux développeurs Java. Les modèles résultants prennent la forme d'objets Java, ce qui leur permet d'être des composants bien abstraits d'un système englobant.
Avec un modèle bayésien traditionnel, l'utilisateur doit concevoir le modèle, puis implémenter un code d'inférence pour toute opération qu'il souhaite effectuer sur le modèle. Cela crée un certain nombre de problèmes :
Construire un code d’inférence est techniquement difficile à réaliser et prend du temps. Cette étape présente l’opportunité d’introduire des bugs subtils.
Si le modèle est modifié, alors le code d'inférence devra être mis à jour. Cela prend également du temps et représente un défi technique, entraînant les problèmes suivants :
Cela a un effet dissuasif sur la modification des modèles.
Il est possible que différentes opérations d'inférence ne soient pas synchronisées, donc certaines travaillent sur l'ancien modèle et d'autres sur le nouveau modèle.
Cela présente une autre opportunité pour les bogues d'entrer dans l'algorithme d'inférence lorsque les utilisateurs tentent d'apporter des ajustements subtils au code existant.
La programmation probabiliste surmonte ces problèmes en permettant de décrire les modèles à l'aide soit d'une API, soit d'un langage spécifique à un domaine (DSL), comme c'est le cas avec Sandwood. Le Sandwood DSL est compilé pour produire des classes Java qui représentent le modèle et implémentent toutes les opérations d'inférence requises. Cela présente de nombreux avantages :
Sandwood se compose de 3 composants chacun dans leur répertoire correspondant :
Chaque pièce dépend des pièces précédentes. Chaque répertoire de composant contient un fichier Maven POM pour construire le composant. Pour le compilateur et le plugin, ceux-ci devront être appelés avec install
pour les rendre disponibles pour les étapes ultérieures, c'est-à-dire mvn clean install
. Les exemples ne doivent être construits qu'en tant que mvn clean package
.
Après avoir installé Sandwood, il existe actuellement 3 façons de compiler un modèle :
Pour utiliser Sandwood à partir de la ligne de commande une fois que le compilateur et le runtime ont été créés, des scripts de ligne de commande ayant des fonctionnalités similaires à javac
peuvent être trouvés dans commandline/SandwoodC/bin
. Pour l'utiliser, l'utilisateur ajoute généralement le répertoire bin au chemin, puis appelle sandwoodc.sh HMM.sandwood pour compiler le modèle HMM. sandwoodc.sh -h
ou sandwoodc.bat -h
entraînera l'impression d'une description de l'utilisation et des options disponibles.
Toutes les fonctionnalités de SandwoodC sont accessibles en appelant la méthode compile
dans org.sandwood.compilation.SandwoodC
et en passant un tableau contenant les arguments qui auraient été passés à la ligne de commande.
Le plugin Maven peut être utilisé pour déclencher automatiquement la compilation des fichiers sandwood lors de la construction du projet dépendant. Pour utiliser le plugin, vous devez ajouter le runtime sandwood en tant que dépendance et ajouter le plugin à la build. Ceci est réalisé grâce aux ajouts suivants au fichier 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>`
L'inclusion de l'élément <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
indique au plugin dans quel répertoire rechercher les modèles. D'autres indicateurs utiles incluent :
debug
Cette option est utilisée pour obtenir des informations de débogage de SandwoodC. En définissant cette option sur true
, Sandwood génère une trace de ses actions. La valeur par défaut est false
. Notez que cet indicateur concerne les erreurs de débogage avec la configuration/le compilateur du compilateur, et non avec le modèle en cours de compilation. Les erreurs et les avertissements dans les fichiers de modèle Sandwood seront toujours renvoyés par le compilateur.
partialInferenceWarning
Cette option est utilisée pour empêcher SandwoodC d'échouer lorsque certaines étapes d'inférence ne peuvent pas être construites. Si cette option est définie sur true
, Sandwood génère simplement des avertissements sur les étapes manquantes. La valeur par défaut est false
.
sourceDirectory
Ce paramètre définit le répertoire dans lequel rechercher les fichiers de modèle. Dans ce répertoire, les modèles peuvent être situés dans différents packages.
outputDirectory
Ce paramètre définit le répertoire dans lequel le code source Java des modèles doit être placé. La valeur par défaut est ${project.build.directory}/generated-sources/sandwood
.
calculateIndividualProbabilities
Ce paramètre spécifie si les probabilités de chaque variable aléatoire construite dans une boucle doivent être calculées au lieu d'une valeur unique pour toutes les instances. La valeur par défaut est false
.
javadoc
Ce paramètre demande au compilateur de générer JavaDoc pour compléter le modèle. La valeur par défaut est false
.
javadocDirectory
Ce paramètre spécifie l'emplacement où le généré doit être placé.
executable
Ce paramètre permet de spécifier une JVM alternative pour exécuter le compilateur Sandwood.
Ce qui suit est une introduction à la façon d'écrire des modèles Sandwood et à la façon d'utiliser les classes résultantes qui implémentent les modèles.
Un aperçu des étapes par lesquelles passe un modèle peut être vu dans ce diagramme. Les modèles commencent par un fichier .sandwood
compilé en un ensemble de fichiers de classe. Ceux-ci peuvent être instanciés plusieurs fois pour générer plusieurs instances du modèle avec différentes configurations.
À titre d'exemple concret, nous utiliserons un modèle de Markov caché (HMM). Ce modèle est écrit ici en Sandwood. Ce modèle doit être enregistré dans un fichier appelé HMM.sandwood
dans un répertoire de package org/sandwood/examples/hmm
. Une description plus complète de la langue peut être trouvée ici.
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 );
}
En plus de la documentation du langage Sandwood et du commentaire JavaDoc qui peut être généré pour un modèle, il existe un certain nombre d'exemples dans le répertoire Sandwood Exemples et nous suggérons aux nouveaux utilisateurs de commencer par les examiner et les modifier.
Une description du langage utilisé pour décrire les modèles Sandwood peut être trouvée ici. Le langage est construit dans le but d'être familier aux développeurs Java, mais ne contient pas la possibilité de construire des objets. Nous prévoyons d'ajouter la prise en charge des types d'enregistrement à l'avenir afin de simplifier l'importation et l'exportation de données vers et depuis des modèles.
Lorsqu'un modèle est compilé, un certain nombre de fichiers de classe sont générés dans le même package dans lequel le modèle est défini. L'une de ces classes aura le même nom que celui fourni au modèle, donc dans ce cas HMM.class , et ceci est la classe que l'utilisateur doit instancier pour avoir une instance du modèle. Chaque variable visible publiquement dans le modèle correspond à un champ de la classe générée. L'exemple HMM peut être vu ci-dessous.
En exécutant le compilateur avec l'ensemble d'indicateurs javadoc
JavaDoc sera créé pour chaque méthode et classe publiques dans le fichier de modèle généré.
Une fois le modèle compilé, nous devons en instancier des instances. Ces instances sont indépendantes et l'utilisateur peut créer autant de copies différentes du modèle qu'il le souhaite.
Les instances de l'objet modèle sont construites via le constructeur de classe. Comme décrit précédemment, il existe généralement 3 constructeurs pour le modèle. Le seul cas où il y en aura moins est lorsque les différentes variantes du constructeur correspondent à la même signature, auquel cas un constructeur s'appliquera à plusieurs de ces scénarios.
Constructeur complet - Ce constructeur prend tous les arguments qui apparaissent dans la signature du modèle et les définit. Ce constructeur est utilisé pour les opérations de déduction de valeurs et de probabilités.
Constructeur vide - Ce constructeur ne prend aucun argument, laissant les paramètres à définir ultérieurement par l'utilisateur.
Constructeur d'exécution - Ce constructeur supprime les arguments qui sont uniquement observés et, pour les arguments observés dont les dimensions sont utilisées comme entrées dans le code, prend ces dimensions au lieu des paramètres complets. Ainsi, dans l'exemple HMM, le paramètre eventsMeasured deviendra un entier décrivant la longueur de la séquence.
Ces exemples de code montrent comment effectuer des appels aux modèles compilés.
Les interactions avec un modèle via l'objet modèle prennent deux formes :
Appels aux méthodes d'objet de modèle pour les opérations globales telles que la définition de politiques de rétention par défaut, la vérification si le modèle est prêt pour l'inférence et le démarrage des étapes d'inférence, etc.
Appels aux objets paramètres de modèle. Chaque variable publique nommée dans le modèle est représentée par un champ correspondant dans l'objet du modèle. Les variables sont publiques si elles sont déclarées dans la portée la plus externe du modèle et non étiquetées private
, ou si elles sont déclarées dans une portée interne et ne sont pas étiquetées public
. Si un champ est déclaré public dans une portée itérative interne, par exemple le corps d'une boucle for, la valeur de chaque itération sera stockée.
Le type de l'objet dépendra de la variable. Ceux-ci peuvent être divisés en 3 catégories :
Chacun de ces champs fait référence à un objet avec un ensemble de méthodes qui permettent à l'utilisateur de définir et de lire les valeurs et les propriétés du paramètre. Les propriétés qui peuvent être définies et lues incluent la probabilité du paramètre, la politique de rétention du paramètre et si le paramètre doit être fixé à sa valeur actuelle.
Certaines des méthodes les plus importantes de l'objet paramètre lors de l'exécution de l'inférence de modèle sont :
getSamples pour renvoyer des valeurs échantillonnées.
getMAP pour renvoyer la valeur maximale a posteriori.
setValue pour permettre de définir une valeur sur une valeur spécifique.
setFixed qui prend un boolean
pour marquer la valeur comme fixe, et donc ne pas être mise à jour lors de l'inférence. Il est important de définir la valeur du paramètre avant de le corriger.
getLogProbability qui obtient la probabilité logarithmique de la variable après en avoir déduit les probabilités.
Il existe d'autres méthodes, et nous vous recommandons de consulter le JavaDoc pour vous familiariser avec elles.
Il existe 3 types d'opérations de base qui peuvent être effectuées sur un modèle :
setRentionPolicy
dans la classe du modèle. Facultativement, les variables individuelles peuvent ensuite voir leur politique de rétention définie par des appels à la méthode setRetentionPolicy
correspondante dans chaque objet variable.Il existe 3 politiques d'échantillonnage :
NONE n'enregistre aucune valeur. Ceci est particulièrement utile si l'une des variables est volumineuse, donc prendre le temps et l'espace pour la stocker serait un gaspillage.
SAMPLE enregistre la valeur de chaque itération de l'algorithme d'inférence, donc si 1 000 itérations sont effectuées, 1 000 valeurs seront échantillonnées à partir de chaque variable définie dans cette politique de rétention. Ceci est utile pour calculer la variance ainsi que la valeur moyenne. Il y a cependant une faiblesse à cela : si les positions des valeurs dans le modèle peuvent bouger pendant l'inférence, alors les valeurs ne peuvent pas faire l'objet d'une moyenne. Par exemple, avec un modèle de sujet, les sujets 2 et 3 peuvent échanger leurs places pendant l'inférence, de sorte que la moyenne de toutes les valeurs du sujet 2 produit un mélange du sujet 2 et du sujet 3. Pour surmonter ce maximum A Posteriori (MAP) est également fourni sous forme de modèle. politique de rétention.
MAP ou Maximum A Posteriori (MAP) enregistre les valeurs des variables lorsque le modèle est dans son état le plus probable. Cela résout le problème des positions de valeurs transitoires, ce qui signifie que les valeurs ne peuvent pas être moyennées, mais au détriment de la possibilité de calculer des limites. Cette option présente également des avantages en termes d'espace si certaines variables sont volumineuses.
Configuration : des appels de méthode supplémentaires sur l'objet modèle permettent à l'utilisateur de définir des propriétés telles que le burnin et l'amincissement lors de l'exécution de cette étape d'inférence. Burnin ignore les valeurs des n premières itérations, permettant au modèle de s'éloigner d'un point de départ à faible probabilité avant de commencer l'échantillonnage. L'amincissement réduit l'autocorrélation induite par la procédure MCMC en ne considérant que les valeurs de chaque n ième itération.
Déduire des probabilités Après avoir défini les valeurs de certains ou de tous les paramètres du modèle, calculez la probabilité de générer ces valeurs. Ceci peut être calculé pour chaque variable du modèle et pour le modèle dans son ensemble.
Exécuter le modèle Exécutez le modèle comme s'il s'agissait d'un code normal générant de nouvelles valeurs pour tous les paramètres qui ne sont pas fixés par l'utilisateur. Un exemple d’utilisation de ce comportement est celui d’un modèle de régression linéaire. Dans ce cas, les coefficients du modèle seraient d'abord déduits à l'aide des données d'entraînement. Une fois qu'ils auront été déduits, ils seront corrigés et un nouvel ensemble de données d'entrée. Le modèle serait ensuite exécuté pour générer les prédictions correspondantes pour ces nouvelles données d'entrée. Cette forme d'exécution pourrait également être utilisée pour générer des données synthétiques représentatives à partir d'un modèle entraîné.
Construire et entraîner un modèle
//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 ();
Construire un modèle et en déduire des probabilités
//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 ()
Pour obtenir de l'aide concernant Sandwood, veuillez démarrer ou rejoindre une discussion sur la page de discussions.
Ce projet accueille les contributions de la communauté. Avant de soumettre une pull request, veuillez consulter notre guide de contribution.
Veuillez consulter le guide de sécurité pour connaître notre processus responsable de divulgation des vulnérabilités de sécurité.
Copyright (c) 2019-2024 Oracle et/ou ses sociétés affiliées.
Publié sous la licence permissive universelle v1.0, comme indiqué sur https://oss.oracle.com/licenses/upl/.