|
from dreamcoder.program import * |
|
from dreamcoder.differentiation import * |
|
|
|
import signal |
|
|
|
|
|
class EvaluationTimeout(Exception): |
|
pass |
|
|
|
|
|
EVALUATIONTABLE = {} |
|
|
|
|
|
class Task(object): |
|
def __init__(self, name, request, examples, features=None, cache=False): |
|
'''request: the type of this task |
|
examples: list of tuples of (input, output). input should be a tuple, with one entry for each argument |
|
cache: should program evaluations be cached? |
|
features: list of floats.''' |
|
self.cache = cache |
|
self.features = features |
|
self.request = request |
|
self.name = name |
|
self.examples = examples |
|
if len(self.examples) > 0: |
|
assert all(len(xs) == len(examples[0][0]) |
|
for xs, _ in examples), \ |
|
"(for task %s) FATAL: Number of arguments varies." % name |
|
|
|
def __str__(self): |
|
if self.supervision is None: |
|
return self.name |
|
else: |
|
return self.name + " (%s)"%self.supervision |
|
|
|
def __repr__(self): |
|
return "Task(name={self.name}, request={self.request}, examples={self.examples}"\ |
|
.format(self=self) |
|
|
|
def __eq__(self, o): return self.name == o.name |
|
|
|
def __ne__(self, o): return not (self == o) |
|
|
|
def __hash__(self): return hash(self.name) |
|
|
|
def describe(self): |
|
description = ["%s : %s" % (self.name, self.request)] |
|
for xs, y in self.examples: |
|
if len(xs) == 1: |
|
description.append("f(%s) = %s" % (xs[0], y)) |
|
else: |
|
description.append("f%s = %s" % (xs, y)) |
|
return "\n".join(description) |
|
|
|
def predict(self, f, x): |
|
for a in x: |
|
f = f(a) |
|
return f |
|
|
|
@property |
|
def supervision(self): |
|
if not hasattr(self, 'supervisedSolution'): return None |
|
return self.supervisedSolution |
|
|
|
def check(self, e, timeout=None): |
|
if timeout is not None: |
|
def timeoutCallBack(_1, _2): raise EvaluationTimeout() |
|
try: |
|
signal.signal(signal.SIGVTALRM, timeoutCallBack) |
|
signal.setitimer(signal.ITIMER_VIRTUAL, timeout) |
|
|
|
try: |
|
f = e.evaluate([]) |
|
except IndexError: |
|
|
|
return False |
|
except Exception as e: |
|
eprint("Exception during evaluation:", e) |
|
return False |
|
|
|
for x, y in self.examples: |
|
if self.cache and (x, e) in EVALUATIONTABLE: |
|
p = EVALUATIONTABLE[(x, e)] |
|
else: |
|
try: |
|
p = self.predict(f, x) |
|
except BaseException: |
|
p = None |
|
if self.cache: |
|
EVALUATIONTABLE[(x, e)] = p |
|
if p != y: |
|
if timeout is not None: |
|
signal.signal(signal.SIGVTALRM, lambda *_: None) |
|
signal.setitimer(signal.ITIMER_VIRTUAL, 0) |
|
return False |
|
|
|
return True |
|
|
|
|
|
|
|
except EvaluationTimeout: |
|
eprint("Timed out while evaluating", e) |
|
return False |
|
finally: |
|
if timeout is not None: |
|
signal.signal(signal.SIGVTALRM, lambda *_: None) |
|
signal.setitimer(signal.ITIMER_VIRTUAL, 0) |
|
|
|
def logLikelihood(self, e, timeout=None): |
|
if self.check(e, timeout): |
|
return 0.0 |
|
else: |
|
return NEGATIVEINFINITY |
|
|
|
@staticmethod |
|
def featureMeanAndStandardDeviation(tasks): |
|
dimension = len(tasks[0].features) |
|
averages = [sum(t.features[j] for t in tasks) / float(len(tasks)) |
|
for j in range(dimension)] |
|
variances = [sum((t.features[j] - |
|
averages[j])**2 for t in tasks) / |
|
float(len(tasks)) for j in range(dimension)] |
|
standardDeviations = [v**0.5 for v in variances] |
|
for j, s in enumerate(standardDeviations): |
|
if s == 0.: |
|
eprint( |
|
"WARNING: Feature %d is always %f" % |
|
(j + 1, averages[j])) |
|
return averages, standardDeviations |
|
|
|
def as_json_dict(self): |
|
return { |
|
"name": self.name, |
|
"request": str(self.request), |
|
"examples": [{"inputs": x, "output": y} for x, y in self.examples] |
|
} |
|
|
|
|
|
class DifferentiableTask(Task): |
|
|
|
def __init__(self, name, request, examples, _=None, |
|
features=None, BIC=1., loss=None, likelihoodThreshold=None, |
|
steps=50, restarts=300, lr=0.5, decay=0.5, grow=1.2, actualParameters=None, |
|
temperature=1., maxParameters=None, clipLoss=None, clipOutput=None): |
|
assert loss is not None |
|
self.temperature = temperature |
|
self.actualParameters = actualParameters |
|
self.maxParameters = maxParameters |
|
self.loss = loss |
|
self.BIC = BIC |
|
self.likelihoodThreshold = likelihoodThreshold |
|
|
|
arguments = {"parameterPenalty": BIC * math.log(len(examples)), |
|
"temperature": temperature, |
|
"steps": steps, "restarts": restarts, "lr": lr, "decay": decay, "grow": grow, |
|
"maxParameters": maxParameters, |
|
"lossThreshold": -likelihoodThreshold} |
|
if clipLoss is not None: arguments['clipLoss'] = float(clipLoss) |
|
if clipOutput is not None: arguments['clipOutput'] = float(clipOutput) |
|
if actualParameters is not None: arguments['actualParameters'] = int(actualParameters) |
|
|
|
self.specialTask = ("differentiable", |
|
arguments) |
|
|
|
super( |
|
DifferentiableTask, |
|
self).__init__( |
|
name, |
|
request, |
|
examples, |
|
features, |
|
cache=False) |
|
|
|
def logLikelihood(self, e, timeout=None): |
|
assert timeout is None, "timeout not implemented for differentiable tasks, but not for any good reason." |
|
e, parameters = PlaceholderVisitor.execute(e) |
|
if self.maxParameters is not None and len( |
|
parameters) > self.maxParameters: |
|
return NEGATIVEINFINITY |
|
if self.actualParameters is not None and len( |
|
parameters) > self.actualParameters: |
|
return NEGATIVEINFINITY |
|
f = e.evaluate([]) |
|
|
|
loss = sum(self.loss(self.predict(f, xs), y) |
|
for xs, y in self.examples) / float(len(self.examples)) |
|
if isinstance(loss, DN): |
|
try: |
|
loss = loss.restartingOptimize( |
|
parameters, |
|
lr=self.specialTask[1]["lr"], |
|
steps=self.specialTask[1]["steps"], |
|
decay=self.specialTask[1]["decay"], |
|
grow=self.specialTask[1]["grow"], |
|
attempts=self.specialTask[1]["restarts"], |
|
update=None) |
|
except InvalidLoss: |
|
loss = POSITIVEINFINITY |
|
|
|
|
|
penalty = self.BIC * len(parameters) * math.log(len(self.examples)) |
|
|
|
if self.likelihoodThreshold is not None: |
|
if loss > -self.likelihoodThreshold: |
|
return NEGATIVEINFINITY |
|
else: |
|
return -penalty |
|
else: |
|
return -loss / self.temperature - penalty |
|
|
|
|
|
def squaredErrorLoss(prediction, target): |
|
d = prediction - target |
|
return d * d |
|
|
|
|
|
def l1loss(prediction, target): |
|
return abs(prediction - target) |
|
|
|
|
|
class PlaceholderVisitor(object): |
|
def __init__(self): self.parameters = [] |
|
|
|
def primitive(self, e): |
|
if e.name == 'REAL': |
|
placeholder = Placeholder.named("REAL_", random.random()) |
|
self.parameters.append(placeholder) |
|
return Primitive(e.name, e.tp, placeholder) |
|
return e |
|
|
|
def invented(self, e): return e.body.visit(self) |
|
|
|
def abstraction(self, e): return Abstraction(e.body.visit(self)) |
|
|
|
def application(self, e): |
|
return Application(e.f.visit(self), e.x.visit(self)) |
|
|
|
def index(self, e): return e |
|
|
|
@staticmethod |
|
def execute(e): |
|
v = PlaceholderVisitor() |
|
e = e.visit(v) |
|
return e, v.parameters |
|
|