Comparing Search Strategies¶
This example compares the performance of RandomSearch and PESearch on a toy problem. The full source code can be found here.
from autogoal.search import RandomSearch, PESearch, RichLogger
The problem to solve consists of a simple puzzle: Combining the digits 1 through 9 in different operations to obtain the largest possible value. We can apply addition, substraction, multiplication, division and exponentiation.
To model all the possible operations and operators we will design a simple grammar using the class API
from autogoal.grammar import generate_cfg, Union
from autogoal.utils import nice_repr
@nice_repr
class Number:
def __call__(self, stream):
return next(stream)
class Operator:
def __init__(self, left: "Expr", right: "Expr"):
self.left = left
self.right = right
def __call__(self, stream):
return self.operate(self.left(stream), self.right(stream))
@nice_repr
class Add(Operator):
def operate(self, left, right):
return left + right
@nice_repr
class Mult(Operator):
def operate(self, left, right):
return left * right
@nice_repr
class Concat(Operator):
def operate(self, left, right):
return int(str(left) + str(right))
Expr = Union("Expr", Number, Add, Mult, Concat)
grammar = generate_cfg(Expr)
print(grammar)
Our grammar is composed of addition, multiplication and concatenation operators. Here are some possible examples:
for i in range(100):
try:
solution = grammar.sample()
print(solution)
except ValueError:
continue
To evaluate how good a formula is, we simply feed the expression instance
with a sequence of numbers from 1 to 9. If the expression requires more
than 9 digits, it results in an error. The actual value of performing
corresponding operations is done in the __call__
method of the expression classes.
def evaluate(expr):
def stream():
for i in range(1, 10):
yield i
raise ValueError("Too many values asked")
return expr(stream())
We will run 1000 iterations of each search strategy to compare their long-term performance.
search_rand = RandomSearch(grammar, evaluate, errors="ignore")
best_rand, best_fn_rand = search_rand.run(1000, logger=RichLogger())
search_pe = PESearch(grammar, evaluate, pop_size=10, errors="ignore")
best_pe, best_fn_pe = search_pe.run(1000, logger=RichLogger())
And here are the results.
print(best_rand, best_fn_rand)
print(best_pe, best_fn_pe)