Spaces:
Sleeping
Sleeping
MilesCranmer
commited on
Commit
•
cedbbde
1
Parent(s):
b18ab5a
Optimize hall of fame constants with LBFGS
Browse files- README.md +4 -4
- eureqa.jl +41 -15
- paralleleureqa.jl +12 -5
README.md
CHANGED
@@ -77,10 +77,6 @@ weights = [8, 1, 1, 1, 0.1, 2]
|
|
77 |
|
78 |
# TODO
|
79 |
|
80 |
-
- [ ] Explicit constant optimization on hall-of-fame
|
81 |
-
- Create method to find and return all constants, from left to right
|
82 |
-
- Create method to find and set all constants, in same order
|
83 |
-
- Pull up some optimization algorithm and add it. No need for gradients; that's a headache. Keep the package small!
|
84 |
- [ ] Hyperparameter tune
|
85 |
- [ ] Create a Python interface
|
86 |
- [ ] Create a benchmark for accuracy
|
@@ -94,6 +90,10 @@ weights = [8, 1, 1, 1, 0.1, 2]
|
|
94 |
- Seems like its necessary right now. But still by far the slowest option.
|
95 |
- [ ] Calculating the loss function - there is duplicate calculations happening.
|
96 |
- [ ] Declaration of the weights array every iteration
|
|
|
|
|
|
|
|
|
97 |
- [x] Create a benchmark for speed
|
98 |
- [x] Simplify subtrees with only constants beneath them. Or should I? Maybe randomly simplify sometimes?
|
99 |
- [x] Record hall of fame
|
|
|
77 |
|
78 |
# TODO
|
79 |
|
|
|
|
|
|
|
|
|
80 |
- [ ] Hyperparameter tune
|
81 |
- [ ] Create a Python interface
|
82 |
- [ ] Create a benchmark for accuracy
|
|
|
90 |
- Seems like its necessary right now. But still by far the slowest option.
|
91 |
- [ ] Calculating the loss function - there is duplicate calculations happening.
|
92 |
- [ ] Declaration of the weights array every iteration
|
93 |
+
- [x] Explicit constant optimization on hall-of-fame
|
94 |
+
- Create method to find and return all constants, from left to right
|
95 |
+
- Create method to find and set all constants, in same order
|
96 |
+
- Pull up some optimization algorithm and add it. Keep the package small!
|
97 |
- [x] Create a benchmark for speed
|
98 |
- [x] Simplify subtrees with only constants beneath them. Or should I? Maybe randomly simplify sometimes?
|
99 |
- [x] Record hall of fame
|
eureqa.jl
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
include("hyperparams.jl")
|
2 |
include("dataset.jl")
|
|
|
3 |
|
4 |
const maxdegree = 2
|
5 |
const actualMaxsize = maxsize + maxdegree
|
@@ -14,6 +15,10 @@ function debug(verbosity, string...)
|
|
14 |
verbosity > 0 ? println(string...) : nothing
|
15 |
end
|
16 |
|
|
|
|
|
|
|
|
|
17 |
# Define a serialization format for the symbolic equations:
|
18 |
mutable struct Node
|
19 |
#Holds operators, variables, constants in a tree
|
@@ -222,9 +227,7 @@ function MSE(x::Array{Float32}, y::Array{Float32})::Float32
|
|
222 |
end
|
223 |
|
224 |
# Score an equation
|
225 |
-
function scoreFunc(
|
226 |
-
tree::Node;
|
227 |
-
parsimony::Float32=0.1f0)::Float32
|
228 |
try
|
229 |
return MSE(evalTreeArray(tree), y) + countNodes(tree)*parsimony
|
230 |
catch error
|
@@ -323,8 +326,7 @@ end
|
|
323 |
# exp(-delta/T) defines probability of accepting a change
|
324 |
function iterate(
|
325 |
tree::Node, T::Float32,
|
326 |
-
alpha::Float32=1.0f0
|
327 |
-
mult::Float32=0.1f0;
|
328 |
annealing::Bool=true
|
329 |
)::Node
|
330 |
prev = tree
|
@@ -353,8 +355,8 @@ function iterate(
|
|
353 |
end
|
354 |
|
355 |
if annealing
|
356 |
-
beforeLoss = scoreFunc(prev
|
357 |
-
afterLoss = scoreFunc(tree
|
358 |
delta = afterLoss - beforeLoss
|
359 |
probChange = exp(-delta/(T*alpha))
|
360 |
|
@@ -382,8 +384,8 @@ mutable struct PopMember
|
|
382 |
score::Float32
|
383 |
birth::Int32
|
384 |
|
385 |
-
PopMember(t::Node) = new(t, scoreFunc(t
|
386 |
-
PopMember(t::Node, score::Float32) = new(t, score,
|
387 |
|
388 |
end
|
389 |
|
@@ -413,9 +415,9 @@ function bestOfSample(pop::Population)::PopMember
|
|
413 |
end
|
414 |
|
415 |
# Return best 10 examples
|
416 |
-
function bestSubPop(pop::Population)::Population
|
417 |
best_idx = sortperm([pop.members[member].score for member=1:pop.n])
|
418 |
-
return Population(pop.members[best_idx[1:
|
419 |
end
|
420 |
|
421 |
# Mutate the best sampled member of the population
|
@@ -425,10 +427,10 @@ function iterateSample(
|
|
425 |
allstar = bestOfSample(pop)
|
426 |
new = iterate(
|
427 |
allstar.tree, T,
|
428 |
-
alpha,
|
429 |
allstar.tree = new
|
430 |
-
allstar.score = scoreFunc(new
|
431 |
-
allstar.birth =
|
432 |
return allstar
|
433 |
end
|
434 |
|
@@ -503,9 +505,33 @@ function setConstants(tree::Node, constants::Array{Float32, 1})
|
|
503 |
end
|
504 |
end
|
505 |
|
506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
507 |
function optimizeConstants(member::PopMember)::PopMember
|
|
|
|
|
|
|
|
|
508 |
x0 = getConstants(member.tree)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
509 |
return member
|
510 |
end
|
511 |
|
|
|
1 |
include("hyperparams.jl")
|
2 |
include("dataset.jl")
|
3 |
+
import Optim
|
4 |
|
5 |
const maxdegree = 2
|
6 |
const actualMaxsize = maxsize + maxdegree
|
|
|
15 |
verbosity > 0 ? println(string...) : nothing
|
16 |
end
|
17 |
|
18 |
+
function giveBirth()::Int32
|
19 |
+
return round(Int32, 1e3*(time()-1.6e9))
|
20 |
+
end
|
21 |
+
|
22 |
# Define a serialization format for the symbolic equations:
|
23 |
mutable struct Node
|
24 |
#Holds operators, variables, constants in a tree
|
|
|
227 |
end
|
228 |
|
229 |
# Score an equation
|
230 |
+
function scoreFunc(tree::Node)::Float32
|
|
|
|
|
231 |
try
|
232 |
return MSE(evalTreeArray(tree), y) + countNodes(tree)*parsimony
|
233 |
catch error
|
|
|
326 |
# exp(-delta/T) defines probability of accepting a change
|
327 |
function iterate(
|
328 |
tree::Node, T::Float32,
|
329 |
+
alpha::Float32=1.0f0;
|
|
|
330 |
annealing::Bool=true
|
331 |
)::Node
|
332 |
prev = tree
|
|
|
355 |
end
|
356 |
|
357 |
if annealing
|
358 |
+
beforeLoss = scoreFunc(prev)
|
359 |
+
afterLoss = scoreFunc(tree)
|
360 |
delta = afterLoss - beforeLoss
|
361 |
probChange = exp(-delta/(T*alpha))
|
362 |
|
|
|
384 |
score::Float32
|
385 |
birth::Int32
|
386 |
|
387 |
+
PopMember(t::Node) = new(t, scoreFunc(t), giveBirth())
|
388 |
+
PopMember(t::Node, score::Float32) = new(t, score, giveBirth())
|
389 |
|
390 |
end
|
391 |
|
|
|
415 |
end
|
416 |
|
417 |
# Return best 10 examples
|
418 |
+
function bestSubPop(pop::Population; topn::Int32=10)::Population
|
419 |
best_idx = sortperm([pop.members[member].score for member=1:pop.n])
|
420 |
+
return Population(pop.members[best_idx[1:topn]])
|
421 |
end
|
422 |
|
423 |
# Mutate the best sampled member of the population
|
|
|
427 |
allstar = bestOfSample(pop)
|
428 |
new = iterate(
|
429 |
allstar.tree, T,
|
430 |
+
alpha, annealing=annealing)
|
431 |
allstar.tree = new
|
432 |
+
allstar.score = scoreFunc(new)
|
433 |
+
allstar.birth = giveBirth()
|
434 |
return allstar
|
435 |
end
|
436 |
|
|
|
505 |
end
|
506 |
end
|
507 |
|
508 |
+
|
509 |
+
# Proxy function for optimization
|
510 |
+
function optFunc(x::Array{Float32, 1}, tree::Node)::Float32
|
511 |
+
setConstants(tree, x)
|
512 |
+
return scoreFunc(tree)
|
513 |
+
end
|
514 |
+
|
515 |
+
# Use Nelder-Mead to optimize the constants in an equation
|
516 |
function optimizeConstants(member::PopMember)::PopMember
|
517 |
+
nconst = countConstants(member.tree)
|
518 |
+
if nconst == 0
|
519 |
+
return member
|
520 |
+
end
|
521 |
x0 = getConstants(member.tree)
|
522 |
+
f(x::Array{Float32,1})::Float32 = optFunc(x, member.tree)
|
523 |
+
if size(x0)[1] == 1
|
524 |
+
result = Optim.optimize(f, x0, Optim.Newton(), Optim.Options(iterations=20))
|
525 |
+
else
|
526 |
+
result = Optim.optimize(f, x0, Optim.LBFGS(), Optim.Options(iterations=10))
|
527 |
+
end
|
528 |
+
if Optim.converged(result)
|
529 |
+
setConstants(member.tree, result.minimizer)
|
530 |
+
member.score = convert(Float32, result.minimum)
|
531 |
+
member.birth = giveBirth()
|
532 |
+
else
|
533 |
+
setConstants(member.tree, x0)
|
534 |
+
end
|
535 |
return member
|
536 |
end
|
537 |
|
paralleleureqa.jl
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
include("eureqa.jl")
|
|
|
2 |
|
3 |
const nthreads = Threads.nthreads()
|
4 |
|
@@ -6,10 +7,9 @@ const nthreads = Threads.nthreads()
|
|
6 |
mutable struct HallOfFame
|
7 |
members::Array{PopMember, 1}
|
8 |
exists::Array{Bool, 1} #Whether it has been set
|
9 |
-
optimized::Array{Bool, 1} #Whether the constants have been optimized
|
10 |
|
11 |
# Arranged by complexity - store one at each.
|
12 |
-
HallOfFame() = new([PopMember(Node(1f0), 1f9) for i=1:actualMaxsize], [false for i=1:actualMaxsize]
|
13 |
end
|
14 |
|
15 |
|
@@ -19,11 +19,13 @@ function fullRun(niterations::Integer;
|
|
19 |
ncyclesperiteration::Integer=3000,
|
20 |
fractionReplaced::Float32=0.1f0,
|
21 |
verbosity::Integer=0,
|
|
|
22 |
)
|
23 |
debug(verbosity, "Lets try to learn (x2^2 + cos(x3)) using regularized evolution from scratch")
|
24 |
debug(verbosity, "Running with $nthreads threads")
|
25 |
# Generate random initial populations
|
26 |
allPops = [Population(npop, 3) for j=1:nthreads]
|
|
|
27 |
# Repeat this many evolutions; we collect and migrate the best
|
28 |
# each time.
|
29 |
hallOfFame = HallOfFame()
|
@@ -32,16 +34,22 @@ function fullRun(niterations::Integer;
|
|
32 |
# Spawn threads to run indepdent evolutions, then gather them
|
33 |
@inbounds Threads.@threads for i=1:nthreads
|
34 |
allPops[i] = run(allPops[i], ncyclesperiteration, annealing, verbosity=verbosity)
|
|
|
|
|
|
|
|
|
35 |
end
|
36 |
|
37 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|
38 |
-
bestPops = deepcopy(Population([member for pop in allPops for member in bestSubPop(pop).members]))
|
|
|
39 |
|
40 |
#Update hall of fame
|
41 |
for member in bestPops.members
|
42 |
size = countNodes(member.tree)
|
43 |
if member.score < hallOfFame.members[size].score
|
44 |
hallOfFame.members[size] = deepcopy(member)
|
|
|
45 |
hallOfFame.exists[size] = true
|
46 |
end
|
47 |
end
|
@@ -57,7 +65,6 @@ function fullRun(niterations::Integer;
|
|
57 |
betterThanAllSmaller = (numberSmallerAndBetter == 0)
|
58 |
if betterThanAllSmaller
|
59 |
debug(verbosity, "$size \t $(member.score-parsimony*size) \t $(stringTree(member.tree))")
|
60 |
-
member = optimizeConstants(member)
|
61 |
push!(dominating, member)
|
62 |
end
|
63 |
end
|
@@ -79,7 +86,7 @@ function fullRun(niterations::Integer;
|
|
79 |
for j=1:nthreads
|
80 |
for k in rand(1:npop, Integer(npop*fractionReplacedHof))
|
81 |
# Copy in case one gets used twice
|
82 |
-
allPops[j].members[k] = deepcopy(dominating[rand(
|
83 |
end
|
84 |
end
|
85 |
end
|
|
|
1 |
include("eureqa.jl")
|
2 |
+
import Optim
|
3 |
|
4 |
const nthreads = Threads.nthreads()
|
5 |
|
|
|
7 |
mutable struct HallOfFame
|
8 |
members::Array{PopMember, 1}
|
9 |
exists::Array{Bool, 1} #Whether it has been set
|
|
|
10 |
|
11 |
# Arranged by complexity - store one at each.
|
12 |
+
HallOfFame() = new([PopMember(Node(1f0), 1f9) for i=1:actualMaxsize], [false for i=1:actualMaxsize])
|
13 |
end
|
14 |
|
15 |
|
|
|
19 |
ncyclesperiteration::Integer=3000,
|
20 |
fractionReplaced::Float32=0.1f0,
|
21 |
verbosity::Integer=0,
|
22 |
+
topn::Int32=10
|
23 |
)
|
24 |
debug(verbosity, "Lets try to learn (x2^2 + cos(x3)) using regularized evolution from scratch")
|
25 |
debug(verbosity, "Running with $nthreads threads")
|
26 |
# Generate random initial populations
|
27 |
allPops = [Population(npop, 3) for j=1:nthreads]
|
28 |
+
bestSubPops = [Population(1) for j=1:nthreads]
|
29 |
# Repeat this many evolutions; we collect and migrate the best
|
30 |
# each time.
|
31 |
hallOfFame = HallOfFame()
|
|
|
34 |
# Spawn threads to run indepdent evolutions, then gather them
|
35 |
@inbounds Threads.@threads for i=1:nthreads
|
36 |
allPops[i] = run(allPops[i], ncyclesperiteration, annealing, verbosity=verbosity)
|
37 |
+
bestSubPops[i] = bestSubPop(allPops[i], topn=topn)
|
38 |
+
for j=1:bestSubPops[i].n
|
39 |
+
bestSubPops[i].members[j] = optimizeConstants(bestSubPops[i].members[j])
|
40 |
+
end
|
41 |
end
|
42 |
|
43 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|
44 |
+
# bestPops = deepcopy(Population([member for pop in allPops for member in bestSubPop(pop).members]))
|
45 |
+
bestPops = deepcopy(Population([member for pop in bestSubPops for member in pop.members]))
|
46 |
|
47 |
#Update hall of fame
|
48 |
for member in bestPops.members
|
49 |
size = countNodes(member.tree)
|
50 |
if member.score < hallOfFame.members[size].score
|
51 |
hallOfFame.members[size] = deepcopy(member)
|
52 |
+
#hallOfFame.members[size] = optimizeConstants(hallOfFame.members[size])
|
53 |
hallOfFame.exists[size] = true
|
54 |
end
|
55 |
end
|
|
|
65 |
betterThanAllSmaller = (numberSmallerAndBetter == 0)
|
66 |
if betterThanAllSmaller
|
67 |
debug(verbosity, "$size \t $(member.score-parsimony*size) \t $(stringTree(member.tree))")
|
|
|
68 |
push!(dominating, member)
|
69 |
end
|
70 |
end
|
|
|
86 |
for j=1:nthreads
|
87 |
for k in rand(1:npop, Integer(npop*fractionReplacedHof))
|
88 |
# Copy in case one gets used twice
|
89 |
+
allPops[j].members[k] = deepcopy(dominating[rand(2:size(dominating)[1])])
|
90 |
end
|
91 |
end
|
92 |
end
|