Fraser/dream-coder
Program Synthesis Data Generated program synthesis datasets used to train dreamcoder. Currently just supports text & list data.
6689
1import datetime2import json3import os4import pickle5import subprocess6import sys7 8from dreamcoder.fragmentGrammar import FragmentGrammar9from dreamcoder.frontier import Frontier, FrontierEntry10from dreamcoder.grammar import Grammar11from dreamcoder.task import Task12from dreamcoder.program import Program, Invented13from dreamcoder.utilities import eprint, timing, callCompiled, get_root_dir14from dreamcoder.vs import induceGrammar_Beta15 16 17def induceGrammar(*args, **kwargs):18 if sum(not f.empty for f in args[1]) == 0:19 eprint("No nonempty frontiers, exiting grammar induction early.")20 return args[0], args[1]21 backend = kwargs.pop("backend", "pypy")22 if 'pypy' in backend:23 # pypy might not like some of the imports needed for the primitives24 # but the primitive values are irrelevant for compression25 # therefore strip them out and then replace them once we are done26 # ditto for task data27 g0,frontiers = args[0].strip_primitive_values(), \28 [front.strip_primitive_values() for front in args[1]]29 original_tasks = {f.task.name: f.task for f in frontiers}30 frontiers = [Frontier(f.entries, Task(f.task.name,f.task.request,[]))31 for f in frontiers ]32 args = [g0,frontiers]33 34 35 with timing("Induced a grammar"):36 if backend == "pypy":37 g, newFrontiers = callCompiled(pypyInduce, *args, **kwargs)38 elif backend == "rust":39 g, newFrontiers = rustInduce(*args, **kwargs)40 elif backend == "vs":41 g, newFrontiers = rustInduce(*args, vs=True, **kwargs)42 elif backend == "pypy_vs":43 kwargs.pop('iteration')44 kwargs.pop('topk_use_only_likelihood')45 fn = '/tmp/vs.pickle'46 with open(fn, 'wb') as handle:47 pickle.dump((args, kwargs), handle)48 eprint("For debugging purposes, the version space compression invocation has been saved to", fn)49 g, newFrontiers = callCompiled(induceGrammar_Beta, *args, **kwargs)50 elif backend == "ocaml":51 kwargs.pop('iteration')52 kwargs.pop('topk_use_only_likelihood')53 kwargs['topI'] = 30054 kwargs['bs'] = 100000055 g, newFrontiers = ocamlInduce(*args, **kwargs)56 elif backend == "memorize":57 g, newFrontiers = memorizeInduce(*args, **kwargs)58 else:59 assert False, "unknown compressor"60 61 if 'pypy' in backend:62 g, newFrontiers = g.unstrip_primitive_values(), \63 [front.unstrip_primitive_values() for front in newFrontiers]64 newFrontiers = [Frontier(f.entries, original_tasks[f.task.name])65 for f in newFrontiers] 66 67 68 return g, newFrontiers69 70def memorizeInduce(g, frontiers, **kwargs):71 existingInventions = {p.uncurry()72 for p in g.primitives }73 programs = {f.bestPosterior.program for f in frontiers if not f.empty}74 newInventions = programs - existingInventions75 newGrammar = Grammar.uniform([p for p in g.primitives] + \76 [Invented(ni) for ni in newInventions])77 78 # rewrite in terms of new primitives79 def substitute(p):80 nonlocal newInventions81 if p in newInventions: return Invented(p).uncurry()82 return p83 newFrontiers = [Frontier([FrontierEntry(program=np,84 logPrior=newGrammar.logLikelihood(f.task.request, np),85 logLikelihood=e.logLikelihood)86 for e in f87 for np in [substitute(e.program)] ],88 task=f.task)89 for f in frontiers ]90 return newGrammar, newFrontiers91 92 93 94 95 96def pypyInduce(*args, **kwargs):97 kwargs.pop('iteration')98 return FragmentGrammar.induceFromFrontiers(*args, **kwargs)99 100 101def ocamlInduce(g, frontiers, _=None,102 topK=1, pseudoCounts=1.0, aic=1.0,103 structurePenalty=0.001, a=0, CPUs=1,104 bs=1000000, topI=300):105 # This is a dirty hack!106 # Memory consumption increases with the number of CPUs107 # And early on we have a lot of stuff to compress108 # If this is the first iteration, only use a fraction of the available CPUs109 if all(not p.isInvented for p in g.primitives):110 if a > 3:111 CPUs = max(1, int(CPUs / 6))112 else:113 CPUs = max(1, int(CPUs / 3))114 else:115 CPUs = max(1, int(CPUs / 2))116 CPUs = 2117 118 # X X X FIXME X X X119 # for unknown reasons doing compression all in one go works correctly and doing it with Python and the outer loop causes problems120 iterations = 99 # maximum number of components to add at once121 122 while True:123 g0 = g124 125 originalFrontiers = frontiers126 t2f = {f.task: f for f in frontiers}127 frontiers = [f for f in frontiers if not f.empty]128 message = {"arity": a,129 "topK": topK,130 "pseudoCounts": float(pseudoCounts),131 "aic": aic,132 "bs": bs,133 "topI": topI,134 "structurePenalty": float(structurePenalty),135 "CPUs": CPUs,136 "DSL": g.json(),137 "iterations": iterations,138 "frontiers": [f.json()139 for f in frontiers]}140 141 message = json.dumps(message)142 if True:143 timestamp = datetime.datetime.now().isoformat()144 os.system("mkdir -p compressionMessages")145 fn = "compressionMessages/%s" % timestamp146 with open(fn, "w") as f:147 f.write(message)148 eprint("Compression message saved to:", fn)149 150 try:151 # Get relative path152 compressor_file = os.path.join(get_root_dir(), 'compression')153 process = subprocess.Popen(compressor_file,154 stdin=subprocess.PIPE,155 stdout=subprocess.PIPE)156 response, error = process.communicate(bytes(message, encoding="utf-8"))157 response = json.loads(response.decode("utf-8"))158 except OSError as exc:159 raise exc160 161 g = response["DSL"]162 g = Grammar(g["logVariable"],163 [(l, p.infer(), p)164 for production in g["productions"]165 for l in [production["logProbability"]]166 for p in [Program.parse(production["expression"])]],167 continuationType=g0.continuationType)168 169 frontiers = {original.task:170 Frontier([FrontierEntry(p,171 logLikelihood=e["logLikelihood"],172 logPrior=g.logLikelihood(original.task.request, p))173 for e in new["programs"]174 for p in [Program.parse(e["program"])]],175 task=original.task)176 for original, new in zip(frontiers, response["frontiers"])}177 frontiers = [frontiers.get(f.task, t2f[f.task])178 for f in originalFrontiers]179 if iterations == 1 and len(g) > len(g0):180 eprint("Grammar changed - running another round of consolidation.")181 continue182 else:183 eprint("Finished consolidation.")184 return g, frontiers185 186 187def rustInduce(g0, frontiers, _=None,188 topK=1, pseudoCounts=1.0, aic=1.0,189 structurePenalty=0.001, a=0, CPUs=1, iteration=-1,190 topk_use_only_likelihood=False,191 vs=False):192 def finite_logp(l):193 return l if l != float("-inf") else -1000194 195 message = {196 "strategy": {"version-spaces": {"top_i": 50}}197 if vs else198 {"fragment-grammars": {}},199 "params": {200 "structure_penalty": structurePenalty,201 "pseudocounts": int(pseudoCounts + 0.5),202 "topk": topK,203 "topk_use_only_likelihood": topk_use_only_likelihood,204 "aic": aic if aic != float("inf") else None,205 "arity": a,206 },207 "primitives": [{"name": p.name, "tp": str(t), "logp": finite_logp(l)}208 for l, t, p in g0.productions if p.isPrimitive],209 "inventions": [{"expression": str(p.body),210 "logp": finite_logp(l)} # -inf=-100211 for l, t, p in g0.productions if p.isInvented],212 "variable_logprob": finite_logp(g0.logVariable),213 "frontiers": [{214 "task_tp": str(f.task.request),215 "solutions": [{216 "expression": str(e.program),217 "logprior": finite_logp(e.logPrior),218 "loglikelihood": e.logLikelihood,219 } for e in f],220 } for f in frontiers],221 }222 223 eprint("running rust compressor")224 225 messageJson = json.dumps(message)226 227 with open("jsonDebug", "w") as f:228 f.write(messageJson)229 230 # check which version of python we are using231 # if >=3.6 do:232 if sys.version_info[1] >= 6:233 p = subprocess.Popen(234 ['./rust_compressor/rust_compressor'],235 encoding='utf-8',236 stdin=subprocess.PIPE,237 stdout=subprocess.PIPE)238 elif sys.version_info[1] == 5:239 p = subprocess.Popen(240 ['./rust_compressor/rust_compressor'],241 stdin=subprocess.PIPE,242 stdout=subprocess.PIPE)243 244 messageJson = bytearray(messageJson, encoding='utf-8')245 # convert messageJson string to bytes246 else:247 eprint("must be python 3.5 or 3.6")248 assert False249 250 p.stdin.write(messageJson)251 p.stdin.flush()252 p.stdin.close()253 254 if p.returncode is not None:255 raise ValueError("rust compressor failed")256 257 if sys.version_info[1] >= 6:258 resp = json.load(p.stdout)259 elif sys.version_info[1] == 5:260 import codecs261 resp = json.load(codecs.getreader('utf-8')(p.stdout))262 263 productions = [(x["logp"], p) for p, x in264 zip((p for (_, _, p) in g0.productions if p.isPrimitive), resp["primitives"])] + \265 [(i["logp"], Invented(Program.parse(i["expression"])))266 for i in resp["inventions"]]267 productions = [(l if l is not None else float("-inf"), p)268 for l, p in productions]269 g = Grammar.fromProductions(productions, resp["variable_logprob"], continuationType=g0.continuationType)270 newFrontiers = [271 Frontier(272 [273 FrontierEntry(274 Program.parse(275 s["expression"]),276 logPrior=s["logprior"],277 logLikelihood=s["loglikelihood"]) for s in r["solutions"]],278 f.task) for f,279 r in zip(280 frontiers,281 resp["frontiers"])]282 return g, newFrontiers283 