Skip to content

vektordev/haskell-dppl

Repository files navigation

NeST

This is the code repository for NeST, the Neuro-Symbolic Transpiler.

Instructions

The code herein should compile readily. The intended way of getting your libraries in order is via the haskell tool Stack. You can find it here , or on linux download it like so:

curl -sSL https://get.haskellstack.org/ | sh

From there,

stack run

should run the compiler. Use -- to pass arguments to NeST rather than the build system. You can use

stack run -- -i yourCode.spll compile -o output.py -l python

to generate python inference code from the code in the input file.

You can also run the program directly from the command line using the built-in interpreter:

stack run -- -i yourCode.spll generate
stack run -- -i yourCode.spll probability -x 0.5
stack run -- -i yourCode.spll cumulative -x 0.5

to run the program in the forward direction, the probability (density) at x=0.5, and the cumulative distribution (P(X ≤ 0.5)) of the prior at x=0.5.

Using the haskell interface

The functions in Prelude.hs provide an easy to use interface for probabilistic programming. The following example declares a program, that represents the sum of two dice, generates a random sample from that program and inferes the probability of that value being produced by the program.

showcase :: IO ()
showcase = do
  let twoDice = Program [("main", dice 6 #<+># dice 6)] [] []
  case runGen defaultCompilerConfig twoDice [] of
    Left err -> return $ counterexample err False
    Right gen' -> do
      gen <- evalRandIO gen'
      putStrLn ("Generated value: " ++ show gen)
      case runProb defaultCompilerConfig twoDice [] gen of 
        Left err -> putStrLn err
        Right (VTuple (VFloat prob) (VFloat dim)) -> putStrLn ("Probability of that value occuring: " ++ show prob)

You can also decare continuous distributions using the uniform or normal functions from Prelude.hs. The resulting infered probability is then a density instead of a mass. The following example samples from a normal distribution, with a standart deviation of 2 and a mean of 1. Again the program samples one value from this distribution and outputs the probability density for that value.

showcase2 :: IO ()
showcase2 = do
  let dist = Program [("main", normal #*# constF 2 #+# constF 1)] [] []
  case runGen defaultCompilerConfig dist [] of
    Left err -> putStrLn err
    Right gen' -> do 
      gen <- evalRandIO gen'
      case runProb defaultCompilerConfig dist [] gen of
        Left err -> putStrLn err
        Right (VTuple (VFloat prob) (VFloat dim)) -> putStrLn ("Probability density of that value occuring: " ++ show prob)

Examples

The following sections show simple examples that can be compiled and run using SPPL. To do this follow the instructions in section above

MNist Addition

This example constructs a MNist neural network, evaluates it with two different symbols and adds the results

neural readMNist :: (Symbol -> Int) of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
main a b = readMNist(a) ++ readMNist(b)

The of ... clause enumerates the possible outputs of a neural network. When a network outputs an unbounded recursive ADT, the enumeration has to be bounded to a finite depth. Give the data type a default unroll depth with a trailing depth N, and auto-derivation (of _) uses it:

data FList = FCons hd::Float, tl::FList | FNil depth 2
neural genList :: (Symbol -> FList) of _
main sym = let xs = genList sym in if isFNil xs then 0.0 else hd xs

An explicit of <N> <binder>.{...} overrides the default per declaration (and is required when a field's domain can't be auto-derived, e.g. an Int), where <binder> is the self-reference name used inside the braces:

data Test = A a1::Int | B b1::Test
neural classify :: (Symbol -> Test) of 3x.{A [0, 1, 2] | B x}
main sym = if isA (classify sym) then 0 else 1

Recursive lists

The next example creates a unbound list by rolling a 0.5 probability and prepening a normally distributed value to the list if it suceeded.

main=if Uniform > 0.5 then [] else Normal:main
p([])=(0.5, 0.0)

Half the time the coin comes up empty, so the probability that this program outputs the empty list is exactly 0.5, as the expected-output block above records.

This example shows off some basic features using algebraic data types:

main=(if Uniform>0.5 then left Uniform else right Normal, Uniform)
p((Left 0.3, 0.5))=(0.5, 2.0)

About

Some of the features of NeST we have described are implemented in a Proof of Concept manner and may not work as intended in all cases. If you believe you have encountered such a case, feel free to reach out or create an issue. Contribuations are of course also welcome.

You can cite this work as such: Viktor Pfanschilling, Hikaru Shindo, Devendra Singh Dhami, Kristian Kersting (2022): Sum-Product Loop Programming: From Probabilistic Circuits to Loop Programming. In Proceedings of the 19th International Conference on Principles of Knowledge Representation and Reasoning (KR).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages