Sandwood é uma linguagem, compilador e tempo de execução para modelos probabilísticos baseados em JVM. Ele foi projetado para permitir que modelos sejam escritos em uma linguagem familiar aos desenvolvedores Java. Os modelos resultantes assumem a forma de objetos Java, permitindo que sejam componentes bem abstraídos de um sistema abrangente.
Com um modelo bayesiano tradicional, o usuário deve projetar o modelo e, em seguida, implementar o código de inferência para qualquer operação que deseje realizar no modelo. Isso cria uma série de problemas:
Construir código de inferência é tecnicamente desafiador e demorado. Esta etapa apresenta uma oportunidade para a introdução de bugs sutis.
Se o modelo for modificado, o código de inferência deverá ser atualizado. Isso também é demorado e tecnicamente desafiador, levando aos seguintes problemas:
Atua como um impedimento à modificação de modelos.
É possível que diferentes operações de inferência fiquem fora de sintonia, então alguns trabalhos no modelo antigo e outros no novo modelo.
Isso apresenta outra oportunidade para erros entrarem no algoritmo de inferência à medida que os usuários tentam fazer ajustes sutis no código existente.
A programação probabilística supera esses problemas, permitindo que os modelos sejam descritos usando uma API ou uma linguagem específica de domínio (DSL), como é o caso do Sandwood. A DSL Sandwood é compilada para produzir classes Java que representam o modelo e implementam todas as operações de inferência necessárias. Isto tem uma série de vantagens:
Sandwood consiste em 3 componentes, cada um em seu diretório correspondente:
Cada peça depende das peças anteriores. Cada diretório de componente contém um arquivo Maven POM para construir o componente. Para o compilador e o plugin, eles precisarão ser chamados com install
para torná-los disponíveis para estágios posteriores, ou seja, mvn clean install
. Os exemplos só devem ser construídos como mvn clean package
.
Depois de instalado o Sandwood existem atualmente 3 maneiras de compilar um modelo:
Para usar o Sandwood a partir da linha de comando, uma vez que o compilador e o tempo de execução tenham sido construídos, scripts de linha de comando que possuem funcionalidade semelhante ao javac
podem ser encontrados em commandline/SandwoodC/bin
. Para usar isso, o usuário normalmente adicionaria o diretório bin ao caminho e, em seguida, chamaria sandwoodc.sh HMM.sandwood para compilar o modelo HMM. sandwoodc.sh -h
ou sandwoodc.bat -h
resultará na impressão de uma descrição do uso e das opções disponíveis.
Todas as funcionalidades do SandwoodC podem ser alcançadas chamando o método compile
em org.sandwood.compilation.SandwoodC
e passando um array contendo os argumentos que teriam sido passados para a linha de comando.
O plugin Maven pode ser usado para acionar automaticamente a compilação de arquivos sandwood quando o projeto dependente é construído. Para usar o plugin você precisa adicionar o tempo de execução sandwood como uma dependência e adicionar o plugin ao build. Isso é conseguido com as seguintes adições ao arquivo 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>`
A inclusão do elemento <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
instrui o plugin em qual diretório procurar por modelos. Outros sinalizadores úteis incluem:
debug
Esta opção é usada para obter informações de depuração do SandwoodC. Definir esta opção como true
faz com que o Sandwood gere um rastreio de suas ações. O valor padrão é false
. Observe que este sinalizador é para depurar erros com a configuração/compilador do compilador, não com o modelo que está sendo compilado. Erros e avisos nos arquivos do modelo sandwood sempre serão retornados pelo compilador.
partialInferenceWarning
Esta opção é usada para impedir que o SandwoodC falhe quando algumas etapas de inferência não puderem ser construídas. Definir esta opção como true
faz com que o Sandwood gere apenas avisos sobre etapas perdidas. O valor padrão é false
.
sourceDirectory
Este parâmetro define em qual diretório procurar arquivos de modelo. Dentro deste diretório os modelos podem estar localizados em diferentes pacotes.
outputDirectory
Este parâmetro define em qual diretório o código-fonte Java dos modelos deve ser colocado. O valor padrão é ${project.build.directory}/generated-sources/sandwood
.
calculateIndividualProbabilities
Este parâmetro especifica se as probabilidades para cada variável aleatória construída em um loop devem ser calculadas em vez de um único valor para todas as instâncias. O valor padrão é false
.
javadoc
Este parâmetro instrui o compilador a gerar JavaDoc para complementar o modelo. O valor padrão é false
.
javadocDirectory
Este parâmetro especifica o local onde o gerado deve ser colocado.
executable
Este parâmetro permite que uma JVM alternativa seja especificada para executar o compilador Sandwood.
O que se segue é uma introdução sobre como escrever modelos Sandwood e como usar as classes resultantes que implementam os modelos.
Um esboço das etapas pelas quais um modelo passa pode ser visto neste diagrama. Os modelos começam como um arquivo .sandwood
que é compilado em um conjunto de arquivos de classe. Eles podem ser instanciados diversas vezes para gerar diversas instâncias do modelo com configurações diferentes.
Como exemplo prático, usaremos um modelo oculto de Markov (HMM). Este modelo está escrito aqui em Sandwood. Este modelo deve ser salvo em um arquivo chamado HMM.sandwood
em um diretório de pacotes org/sandwood/examples/hmm
. Uma descrição mais completa do idioma pode ser encontrada aqui.
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 );
}
Além da documentação da linguagem Sandwood e do comentário JavaDoc que pode ser gerado para um modelo, há vários exemplos no diretório Exemplos Sandwood e sugerimos que novos usuários comecem examinando e modificando-os.
Uma descrição da linguagem usada para descrever os modelos Sandwood pode ser encontrada aqui. A linguagem é construída com o intuito de ser familiar aos desenvolvedores Java, mas não contém a capacidade de construir objetos. Planejamos adicionar suporte para tipos de registro no futuro para simplificar a importação e exportação de dados de e para modelos.
Quando um modelo é compilado, vários arquivos de classe são gerados no mesmo pacote em que o modelo é definido. Uma dessas classes terá o mesmo nome fornecido ao modelo, portanto, neste caso, HMM.class , e este é a classe que o usuário deve instanciar para ter uma instância do modelo. Cada variável publicamente visível no modelo corresponde a um campo na classe gerada. O exemplo HMM pode ser visto abaixo.
Ao executar o compilador com o sinalizador javadoc
definido, o JavaDoc será criado para cada método público e classe no arquivo de modelo gerado.
Depois que o modelo for compilado, precisamos instanciar instâncias dele. Essas instâncias são independentes e o usuário pode criar quantas cópias diferentes do modelo desejar.
As instâncias do objeto modelo são construídas por meio do construtor de classe. Conforme descrito anteriormente, normalmente existem 3 construtores para o modelo. O único caso em que haverá menos é quando as diferentes variantes do construtor são mapeadas para a mesma assinatura, caso em que um construtor se aplicará a mais de um desses cenários.
Construtor completo – Este construtor pega todos os argumentos que aparecem na assinatura do modelo e os define. Este construtor é usado para inferir valores e inferir operações de probabilidades.
Construtor vazio - Este construtor não aceita argumentos, deixando os parâmetros para o usuário definir posteriormente.
Construtor de Execução - Este construtor remove argumentos que são apenas observados e, para argumentos observados cujas dimensões são usadas como entradas para o código, utiliza essas dimensões em vez dos parâmetros completos. Portanto, no exemplo do HMM, o parâmetro eventsMeasured se tornará um número inteiro que descreve o comprimento da sequência.
Esses exemplos de código demonstram como fazer chamadas para os modelos compilados.
As interações com um modelo por meio do objeto modelo assumem duas formas:
Chamadas para modelar métodos de objeto para operações globais, como definir políticas de retenção padrão, verificar se o modelo está pronto para inferência e iniciar etapas de inferência, etc.
Chamadas para modelar objetos de parâmetro. Cada variável pública nomeada no modelo é representada por um campo correspondente no objeto do modelo. As variáveis são públicas se forem declaradas no escopo mais externo do modelo e não forem rotuladas como private
, ou se forem declaradas em um escopo interno e não forem rotuladas como public
. Se um campo for declarado público em um escopo iterativo interno, por exemplo, o corpo de um loop for, o valor de cada iteração será armazenado.
O tipo do objeto dependerá da variável. Eles podem ser divididos em 3 categorias:
Cada um desses campos faz referência a um objeto com um conjunto de métodos que permitem ao usuário definir e ler valores e propriedades do parâmetro. As propriedades que podem ser definidas e lidas incluem a probabilidade do parâmetro, a política de retenção do parâmetro e se o parâmetro deve ser fixado em seu valor atual.
Alguns dos métodos mais importantes do objeto de parâmetro ao realizar a inferência do modelo são:
getSamples para retornar valores amostrados.
getMAP para retornar o valor Máximo A Posteriori.
setValue para permitir que um valor seja definido como um valor específico.
setFixed que utiliza um boolean
para marcar o valor como fixo e, portanto, não deve ser atualizado durante a inferência. É importante definir o valor do parâmetro antes de corrigi-lo.
getLogProbability que obtém o log de probabilidade da variável após inferir probabilidades.
Existem mais métodos e recomendamos consultar o JavaDoc para se familiarizar com eles.
Existem 3 tipos básicos de operação que podem ser realizadas em um modelo:
setRentionPolicy
na classe do modelo. Opcionalmente, variáveis individuais podem então ter sua política de retenção definida por chamadas ao método setRetentionPolicy
correspondente em cada objeto variável.Existem 3 políticas de amostragem:
NONE não registra nenhum valor. Isto é particularmente útil se uma das variáveis for grande, portanto, gastar tempo e espaço para armazená-la seria um desperdício.
SAMPLE registra o valor de cada iteração do algoritmo de inferência, portanto, se 1.000 iterações forem realizadas, 1.000 valores serão amostrados de cada variável definida para esta política de retenção. Isso é útil para calcular a variação e também o valor médio. Porém, há um ponto fraco nisso: se as posições dos valores dentro do modelo puderem se mover durante a inferência, então a média dos valores não poderá ser calculada. Por exemplo, com um modelo de tópico, os tópicos 2 e 3 podem trocar de lugar durante a inferência, portanto, a média de todos os valores do tópico 2 produz uma mistura do tópico 2 e do tópico 3. Para superar esse Máximo A Posteriori (MAP) também é fornecido como um política de retenção.
MAP ou Máximo A Posterior (MAP) registra os valores das variáveis quando o modelo está em seu estado mais provável. Isso supera o problema com posições de valores transitórios, o que significa que os valores não podem ser calculados em média, mas às custas da capacidade de calcular limites. Esta opção também tem vantagens de espaço se algumas das variáveis forem grandes.
Configuração: chamadas de método adicionais no objeto do modelo permitem que o usuário defina propriedades como burnin e thinning ao executar esta etapa de inferência. Burnin ignora os valores das primeiras n iterações, permitindo que o modelo se afaste de um ponto inicial de baixa probabilidade antes de iniciar a amostragem. O desbaste reduz a autocorrelação induzida pelo procedimento MCMC considerando apenas os valores de cada enésima iteração.
Inferir probabilidades Depois de definir os valores de alguns ou de todos os parâmetros do modelo, calcule a probabilidade de gerar esses valores. Isso pode ser calculado para cada variável do modelo e para o modelo como um todo.
Executar Modelo Executa o modelo como se fosse um código normal, gerando novos valores para quaisquer parâmetros que não sejam corrigidos pelo usuário. Um exemplo de quando esse comportamento seria usado é para um modelo de regressão linear. Neste caso, os coeficientes do modelo seriam primeiro inferidos utilizando dados de treinamento. Uma vez inferidos, eles seriam corrigidos e um novo conjunto de dados de entrada. O modelo seria então executado para gerar as previsões correspondentes para esses novos dados de entrada. Esta forma de execução também poderia ser usada para gerar dados sintéticos representativos de um modelo treinado.
Construir e treinar um 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 obter ajuda com Sandwood, inicie ou participe de uma discussão na página de discussões.
Este projeto aceita contribuições da comunidade. Antes de enviar uma solicitação pull, revise nosso guia de contribuição.
Consulte o guia de segurança para conhecer nosso processo responsável de divulgação de vulnerabilidades de segurança.
Copyright (c) 2019-2024 Oracle e/ou suas afiliadas.
Lançado sob a Licença Permissiva Universal v1.0 conforme mostrado em https://oss.oracle.com/licenses/upl/.