CoolFace
Datasetpublic

Fraser/dream-coder

Program Synthesis Data Generated program synthesis datasets used to train dreamcoder. Currently just supports text & list data.

sourceHugging Facemitupdated 4y agoView on Hugging Face
6likes750downloads
fragmentGrammar.py431 linesDownload Raw Back to dreamcoder
1from dreamcoder.fragmentUtilities import *2from dreamcoder.grammar import *3from dreamcoder.program import *4 5from itertools import chain6import time7 8 9class FragmentGrammar(object):10    def __init__(self, logVariable, productions):11        self.logVariable = logVariable12        self.productions = productions13        self.likelihoodCache = {}14 15    def clearCache(self):16        self.likelihoodCache = {}17 18    def __repr__(self):19        return "FragmentGrammar(logVariable={self.logVariable}, productions={self.productions}".format(20            self=self)21 22    def __str__(self):23        def productionKey(xxx_todo_changeme):24            (l, t, p) = xxx_todo_changeme25            return not isinstance(p, Primitive), -l26        return "\n".join(["%f\tt0\t$_" % self.logVariable] + ["%f\t%s\t%s" % (l, t, p)27                                                              for l, t, p in sorted(self.productions, key=productionKey)])28 29    def buildCandidates(self, context, environment, request):30        candidates = []31        variableCandidates = []32        for l, t, p in self.productions:33            try:34                newContext, t = t.instantiate(context)35                newContext = newContext.unify(t.returns(), request)36                candidates.append((l, newContext,37                                   t.apply(newContext),38                                   p))39            except UnificationFailure:40                continue41        for j, t in enumerate(environment):42            try:43                newContext = context.unify(t.returns(), request)44                variableCandidates.append((newContext,45                                           t.apply(newContext),46                                           Index(j)))47            except UnificationFailure:48                continue49        if variableCandidates:50            z = math.log(len(variableCandidates))51            for newContext, newType, index in variableCandidates:52                candidates.append(53                    (self.logVariable - z, newContext, newType, index))54 55        z = lse([candidate[0] for candidate in candidates])56        return [(l - z, c, t, p) for l, c, t, p in candidates]57 58    def logLikelihood(self, request, expression):59        _, l, _ = self._logLikelihood(Context.EMPTY, [], request, expression)60        if invalid(l):61            f = 'failures/likelihoodFailure%s.pickle' % (time() + getPID())62            eprint("PANIC: Invalid log likelihood. expression:",63                   expression, "tp:", request, "Exported to:", f)64            with open(f, 'wb') as handle:65                pickle.dump((self, request, expression), handle)66            assert False67        return l68 69    def closedUses(self, request, expression):70        _, l, u = self._logLikelihood(Context.EMPTY, [], request, expression)71        return l, u72 73    def _logLikelihood(self, context, environment, request, expression):74        '''returns (context, log likelihood, uses)'''75 76        # We can cash likelihood calculations faster whenever they don't involve type inference77        # This is because they are guaranteed to not modify the context,78        polymorphic = request.isPolymorphic or any(79            v.isPolymorphic for v in environment)80        # For some reason polymorphic caching slows it down81        shouldDoCaching = not polymorphic82 83        # Caching84        if shouldDoCaching:85            if polymorphic:86                inTypes = canonicalTypes(87                    [request.apply(context)] + [v.apply(context) for v in environment])88            else:89                inTypes = canonicalTypes([request] + environment)90            cacheKey = (tuple(inTypes), expression)91            if cacheKey in self.likelihoodCache:92                outTypes, l, u = self.likelihoodCache[cacheKey]93                context, instantiatedTypes = instantiateTypes(94                    context, outTypes)95                outRequest = instantiatedTypes[0]96                outEnvironment = instantiatedTypes[1:]97                # eprint("request:", request.apply(context), "environment:",98                #        [ v.apply(context) for v in environment ])99                # eprint("will be unified with: out request:",outRequest,"out environment",outEnvironment)100                if polymorphic:101                    context = context.unify(request, outRequest)102                    for v, vp in zip(environment, outEnvironment):103                        context = context.unify(v, vp)104                return context, l, u105 106        if request.isArrow():107            if not isinstance(expression, Abstraction):108                return (context, NEGATIVEINFINITY, Uses.empty)109            return self._logLikelihood(context,110                                       [request.arguments[0]] + environment,111                                       request.arguments[1],112                                       expression.body)113 114        # Not a function type115 116        # Construct and normalize the candidate productions117        candidates = self.buildCandidates(context, environment, request)118 119        # Consider each way of breaking the expression up into a120        # function and arguments121        totalLikelihood = NEGATIVEINFINITY122        weightedUses = []123 124        possibleVariables = float(int(any(isinstance(candidate, Index)125                                          for _, _, _, candidate in candidates)))126        possibleUses = {candidate: 1. for _, _, _, candidate in candidates127                        if not isinstance(candidate, Index)}128 129        for f, xs in expression.applicationParses():130            for candidateLikelihood, newContext, tp, production in candidates:131                variableBindings = {}132                # This is a variable in the environment133                if production.isIndex:134                    if production != f:135                        continue136                else:137                    try:138                        newContext, fragmentType, variableBindings = \139                            Matcher.match(newContext, production, f, len(xs))140                        # This is necessary because the types of the variable141                        # bindings and holes need to match up w/ request142                        fragmentTypeTemplate = request143                        for _ in xs:144                            newContext, newVariable = newContext.makeVariable()145                            fragmentTypeTemplate = arrow(146                                newVariable, fragmentTypeTemplate)147                        newContext = newContext.unify(148                            fragmentType, fragmentTypeTemplate)149                        # update the unified type150                        tp = fragmentType.apply(newContext)151                    except MatchFailure:152                        continue153 154                argumentTypes = tp.functionArguments()155                if len(xs) != len(argumentTypes):156                    # I think that this is some kind of bug. But I can't figure it out right now.157                    # As a hack, count this as though it were a failure158                    continue159                    #raise GrammarFailure('len(xs) != len(argumentTypes): tp={}, xs={}'.format(tp, xs))160 161                thisLikelihood = candidateLikelihood162                if isinstance(production, Index):163                    theseUses = Uses(possibleVariables=possibleVariables,164                                     actualVariables=1.,165                                     possibleUses=possibleUses.copy(),166                                     actualUses={})167                else:168                    theseUses = Uses(possibleVariables=possibleVariables,169                                     actualVariables=0.,170                                     possibleUses=possibleUses.copy(),171                                     actualUses={production: 1.})172 173                # Accumulate likelihood from free variables and holes and174                # arguments175                for freeType, freeExpression in chain(176                        variableBindings.values(), zip(argumentTypes, xs)):177                    freeType = freeType.apply(newContext)178                    newContext, expressionLikelihood, newUses = self._logLikelihood(179                        newContext, environment, freeType, freeExpression)180                    if expressionLikelihood is NEGATIVEINFINITY:181                        thisLikelihood = NEGATIVEINFINITY182                        break183 184                    thisLikelihood += expressionLikelihood185                    theseUses += newUses186 187                if thisLikelihood is NEGATIVEINFINITY:188                    continue189 190                weightedUses.append((thisLikelihood, theseUses))191                totalLikelihood = lse(totalLikelihood, thisLikelihood)192 193                # Any of these new context objects should be equally good194                context = newContext195 196        if totalLikelihood is NEGATIVEINFINITY:197            return context, totalLikelihood, Uses.empty198        assert weightedUses != []199 200        allUses = Uses.join(totalLikelihood, *weightedUses)201 202        # memoize result203        if shouldDoCaching:204            outTypes = [request.apply(context)] + \205                [v.apply(context) for v in environment]206            outTypes = canonicalTypes(outTypes)207            self.likelihoodCache[cacheKey] = (208                outTypes, totalLikelihood, allUses)209 210        return context, totalLikelihood, allUses211 212    def expectedUses(self, frontiers):213        if len(list(frontiers)) == 0:214            return Uses()215        likelihoods = [[(l + entry.logLikelihood, u)216                        for entry in frontier217                        for l, u in [self.closedUses(frontier.task.request, entry.program)]]218                       for frontier in frontiers]219        zs = (lse([l for l, _ in ls]) for ls in likelihoods)220        return sum(math.exp(l - z) * u221                   for z, frontier in zip(zs, likelihoods)222                   for l, u in frontier)223 224    def insideOutside(self, frontiers, pseudoCounts):225        uses = self.expectedUses(frontiers)226        return FragmentGrammar(log(uses.actualVariables +227                                   pseudoCounts) -228                               log(max(uses.possibleVariables, 1.)), [(log(uses.actualUses.get(p, 0.) +229                                                                           pseudoCounts) -230                                                                       log(uses.possibleUses.get(p, 0.) +231                                                                           pseudoCounts), t, p) for _, t, p in self.productions])232 233    def jointFrontiersLikelihood(self, frontiers):234        return sum(lse([entry.logLikelihood + self.logLikelihood(frontier.task.request, entry.program)235                        for entry in frontier])236                   for frontier in frontiers)237 238    def jointFrontiersMDL(self, frontiers, CPUs=1):239        return sum(240            parallelMap(241                CPUs,242                lambda frontier: max(243                    entry.logLikelihood +244                    self.logLikelihood(245                        frontier.task.request,246                        entry.program) for entry in frontier),247                frontiers))248 249    def __len__(self): return len(self.productions)250 251    @staticmethod252    def fromGrammar(g):253        return FragmentGrammar(g.logVariable, g.productions)254 255    def toGrammar(self):256        return Grammar(self.logVariable, [(l, q.infer(), q)257                                          for l, t, p in self.productions258                                          for q in [defragment(p)]])259 260    @property261    def primitives(self): return [p for _, _, p in self.productions]262 263    @staticmethod264    def uniform(productions):265        return FragmentGrammar(0., [(0., p.infer(), p) for p in productions])266 267    def normalize(self):268        z = lse([l for l, t, p in self.productions] + [self.logVariable])269        return FragmentGrammar(self.logVariable - z,270                               [(l - z, t, p) for l, t, p in self.productions])271 272    def makeUniform(self):273        return FragmentGrammar(0., [(0., p.infer(), p)274                                    for _, _, p in self.productions])275 276    def rescoreFrontier(self, frontier):277        return Frontier([FrontierEntry(e.program,278                                       logPrior=self.logLikelihood(frontier.task.request, e.program),279                                       logLikelihood=e.logLikelihood)280                         for e in frontier],281                        frontier.task)282 283    @staticmethod284    def induceFromFrontiers(285            g0,286            frontiers,287            _=None,288            topK=1,289            topk_use_only_likelihood=False,290            pseudoCounts=1.0,291            aic=1.0,292            structurePenalty=0.001,293            a=0,294            CPUs=1):295        _ = topk_use_only_likelihood # not used in python compressor296        originalFrontiers = frontiers297        frontiers = [frontier for frontier in frontiers if not frontier.empty]298        eprint("Inducing a grammar from", len(frontiers), "frontiers")299 300        bestGrammar = FragmentGrammar.fromGrammar(g0)301        oldJoint = bestGrammar.jointFrontiersMDL(frontiers, CPUs=1)302 303        # "restricted frontiers" only contain the top K according to the best grammar304        def restrictFrontiers():305            return parallelMap(306                CPUs,307                lambda f: bestGrammar.rescoreFrontier(f).topK(topK),308                frontiers)309        restrictedFrontiers = []310 311        def grammarScore(g):312            g = g.makeUniform().insideOutside(restrictedFrontiers, pseudoCounts)313            likelihood = g.jointFrontiersMDL(restrictedFrontiers)314            structure = sum(primitiveSize(p) for p in g.primitives)315            score = likelihood - aic * len(g) - structurePenalty * structure316            g.clearCache()317            if invalid(score):318                # FIXME: This should never occur but it does anyway319                score = float('-inf')320            return score, g321 322        if aic is not POSITIVEINFINITY:323            restrictedFrontiers = restrictFrontiers()324            bestScore, _ = grammarScore(bestGrammar)325            eprint("Starting score", bestScore)326            while True:327                restrictedFrontiers = restrictFrontiers()328                fragments = [f329                             for f in proposeFragmentsFromFrontiers(restrictedFrontiers, a, CPUs=CPUs)330                             if f not in bestGrammar.primitives331                             and defragment(f) not in bestGrammar.primitives]332                eprint("Proposed %d fragments." % len(fragments))333 334                candidateGrammars = [335                    FragmentGrammar.uniform(336                        bestGrammar.primitives +337                        [fragment]) for fragment in fragments]338                if not candidateGrammars:339                    break340 341                scoredFragments = parallelMap(CPUs, grammarScore, candidateGrammars,342                                              # Each process handles up to 100343                                              # grammars at a time, a "job"344                                              chunksize=max(345                                                  1, min(len(candidateGrammars) // CPUs, 100)),346                                              # maxTasks: Maximum number of jobs allocated to a process347                                              # This means that after evaluating this*chunk many grammars,348                                              # we killed the process, freeing up its memory.349                                              # In exchange we pay the cost of spawning a new process.350                                              # We should play with this number,351                                              # figuring out how big we can make it without352                                              # running out of memory.353                                              maxtasksperchild=5)354                newScore, newGrammar = max(scoredFragments, key=lambda sg: sg[0])355 356                if newScore <= bestScore:357                    break358                dS = newScore - bestScore359                bestScore, bestGrammar = newScore, newGrammar360                newPrimitiveLikelihood, newType, newPrimitive = bestGrammar.productions[-1]361                expectedUses = bestGrammar.expectedUses(362                    restrictedFrontiers).actualUses.get(newPrimitive, 0)363                eprint(364                    "New primitive of type %s\t%s\t\n(score = %f; dScore = %f; <uses> = %f)" %365                    (newType, newPrimitive, newScore, dS, expectedUses))366 367                # Rewrite the frontiers in terms of the new fragment368                concretePrimitive = defragment(newPrimitive)369                bestGrammar.productions[-1] = (newPrimitiveLikelihood,370                                               concretePrimitive.tp,371                                               concretePrimitive)372                frontiers = parallelMap(373                    CPUs, lambda frontier: bestGrammar.rescoreFrontier(374                        RewriteFragments.rewriteFrontier(375                            frontier, newPrimitive)), frontiers)376                eprint(377                    "\t(<uses> in rewritten frontiers: %f)" %378                    (bestGrammar.expectedUses(frontiers).actualUses[concretePrimitive]))379        else:380            eprint("Skipping fragment proposals")381 382        if False:383            # Reestimate the parameters using the entire frontiers384            bestGrammar = bestGrammar.makeUniform().insideOutside(frontiers, pseudoCounts)385        elif True:386            # Reestimate the parameters using the best programs387            restrictedFrontiers = restrictFrontiers()388            bestGrammar = bestGrammar.makeUniform().insideOutside(389                restrictedFrontiers, pseudoCounts)390        else:391            # Use parameters that were found during search392            pass393 394        eprint("Old joint = %f\tNew joint = %f\n" %395               (oldJoint, bestGrammar.jointFrontiersMDL(frontiers, CPUs=CPUs)))396        # Return all of the frontiers, which have now been rewritten to use the397        # new fragments398        frontiers = {f.task: f for f in frontiers}399        frontiers = [frontiers.get(f.task, f)400                     for f in originalFrontiers]401 402        productionUses = bestGrammar.expectedUses(403            [f for f in frontiers if not f.empty]).actualUses404        productionUses = {405            p: productionUses.get(406                p, 0.) for p in bestGrammar.primitives}407        possibleUses = bestGrammar.expectedUses(408            [f for f in frontiers if not f.empty]).possibleUses409        possibleUses = {410            p: possibleUses.get(411                p, 0.) for p in bestGrammar.primitives}412 413        for p in bestGrammar.primitives:414            eprint("%f / %f\t%s" % (productionUses[p],415                                    possibleUses[p],416                                    p))417 418        bestGrammar.clearCache()419 420        grammar = bestGrammar.toGrammar()421 422        if False and \423           any(productionUses.get(p, 0) < 0.5 for p in grammar.primitives if p.isInvented):424            uselessProductions = [ p for p in grammar.primitives                 425                                   if p.isInvented and productionUses.get(p, 0) < 0.5]426            eprint("The following invented primitives are no longer needed, removing them...")427            eprint("\t" + "\t\n".join(map(str, uselessProductions)))428            grammar = grammar.removeProductions(uselessProductions)429 430        return grammar, frontiers431