Spaces:
Running
Running
MilesCranmer
commited on
Commit
•
c3d240e
1
Parent(s):
ec48038
Add benchmark file
Browse files- README.md +26 -8
- benchmark.jl +14 -0
- benchmark.sh +1 -0
- eureqa.jl +12 -9
- paralleleureqa.jl +11 -11
README.md
CHANGED
@@ -1,14 +1,36 @@
|
|
1 |
# Running:
|
2 |
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
-
|
|
|
7 |
|
8 |
## Modification
|
9 |
|
10 |
You can change the binary and unary operators in `eureqa.jl` here:
|
11 |
-
```
|
12 |
const binops = [plus, mult]
|
13 |
const unaops = [sin, cos, exp];
|
14 |
```
|
@@ -28,10 +50,6 @@ by either loading in a dataset, or modifying the definition of `y`.
|
|
28 |
|
29 |
### Hyperparameters
|
30 |
|
31 |
-
Turn on annealing by setting the following in `paralleleureqa.jl`:
|
32 |
-
|
33 |
-
`const annealing = true`
|
34 |
-
|
35 |
Annealing allows each evolutionary cycle to turn down the exploration
|
36 |
rate over time: at the end (temperature 0), it will only select solutions
|
37 |
better than existing solutions.
|
|
|
1 |
# Running:
|
2 |
|
3 |
+
You can run the performance benchmark with `./benchmark.sh`.
|
4 |
+
|
5 |
+
Modify the search code in `paralleleureqa.jl` and `eureqa.jl` to your liking
|
6 |
+
(see below for options). Then, in a new Julia file called
|
7 |
+
`myfile.jl`, you can write:
|
8 |
+
|
9 |
+
```julia
|
10 |
+
include("paralleleureqa.jl")
|
11 |
+
fullRun(10,
|
12 |
+
npop=100,
|
13 |
+
annealing=true,
|
14 |
+
ncyclesperiteration=1000,
|
15 |
+
fractionReplaced=0.1f0,
|
16 |
+
verbosity=100)
|
17 |
+
```
|
18 |
+
The first arg is the number of migration periods to run,
|
19 |
+
with `ncyclesperiteration` determining how many generations
|
20 |
+
per migration period. `npop` is the number of population members.
|
21 |
+
`annealing` determines whether to stay in exploration mode,
|
22 |
+
or tune it down with each cycle. `fractionReplaced` is
|
23 |
+
how much of the population is replaced by migrated equations each
|
24 |
+
step.
|
25 |
+
|
26 |
|
27 |
+
Run it with threading turned on using:
|
28 |
+
`julia --threads auto -O3 myfile.jl`
|
29 |
|
30 |
## Modification
|
31 |
|
32 |
You can change the binary and unary operators in `eureqa.jl` here:
|
33 |
+
```julia
|
34 |
const binops = [plus, mult]
|
35 |
const unaops = [sin, cos, exp];
|
36 |
```
|
|
|
50 |
|
51 |
### Hyperparameters
|
52 |
|
|
|
|
|
|
|
|
|
53 |
Annealing allows each evolutionary cycle to turn down the exploration
|
54 |
rate over time: at the end (temperature 0), it will only select solutions
|
55 |
better than existing solutions.
|
benchmark.jl
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
include("paralleleureqa.jl")
|
2 |
+
fullRun(1,
|
3 |
+
npop=100,
|
4 |
+
annealing=true,
|
5 |
+
ncyclesperiteration=1000,
|
6 |
+
fractionReplaced=0.1f0,
|
7 |
+
verbosity=0)
|
8 |
+
@time fullRun(3,
|
9 |
+
npop=100,
|
10 |
+
annealing=true,
|
11 |
+
ncyclesperiteration=1000,
|
12 |
+
fractionReplaced=0.1f0,
|
13 |
+
verbosity=0
|
14 |
+
)
|
benchmark.sh
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
julia --threads 8 -O3 benchmark.jl
|
eureqa.jl
CHANGED
@@ -35,6 +35,10 @@ const nbin = size(binops)[1]
|
|
35 |
const nops = nuna + nbin
|
36 |
const nvar = size(X)[2];
|
37 |
|
|
|
|
|
|
|
|
|
38 |
# Define a serialization format for the symbolic equations:
|
39 |
mutable struct Node
|
40 |
#Holds operators, variables, constants in a tree
|
@@ -241,7 +245,7 @@ end
|
|
241 |
function scoreFunc(
|
242 |
tree::Node,
|
243 |
X::Array{Float32, 2},
|
244 |
-
y::Array{Float32, 1}
|
245 |
parsimony::Float32=0.1f0)::Float32
|
246 |
try
|
247 |
return MSE(evalTreeArray(tree, X), y) + countNodes(tree)*parsimony
|
@@ -341,8 +345,8 @@ function iterate(
|
|
341 |
end
|
342 |
|
343 |
try
|
344 |
-
beforeLoss = scoreFunc(prev, X, y, mult)
|
345 |
-
afterLoss = scoreFunc(tree, X, y, mult)
|
346 |
delta = afterLoss - beforeLoss
|
347 |
probChange = exp(-delta/(T*alpha))
|
348 |
|
@@ -378,7 +382,7 @@ mutable struct PopMember
|
|
378 |
score::Float32
|
379 |
birth::Int32
|
380 |
|
381 |
-
PopMember(t) = new(t, scoreFunc(t, X, y, parsimony), round(Int32, 1e3*(time()-1.6e9))
|
382 |
)
|
383 |
end
|
384 |
|
@@ -418,7 +422,7 @@ function iterateSample(pop::Population, T::Float32)::PopMember
|
|
418 |
allstar = bestOfSample(pop)
|
419 |
new = iterate(allstar.tree, T, X, y, alpha, parsimony)
|
420 |
allstar.tree = new
|
421 |
-
allstar.score = scoreFunc(new, X, y, parsimony)
|
422 |
allstar.birth = round(Int32, 1e3*(time()-1.6e9))
|
423 |
return allstar
|
424 |
end
|
@@ -441,7 +445,7 @@ function run(
|
|
441 |
pop::Population,
|
442 |
ncycles::Integer,
|
443 |
annealing::Bool=false;
|
444 |
-
|
445 |
)::Population
|
446 |
pop = deepcopy(pop)
|
447 |
|
@@ -452,12 +456,11 @@ function run(
|
|
452 |
else
|
453 |
pop = regEvolCycle(pop, 1.0f0)
|
454 |
end
|
455 |
-
if
|
456 |
-
# Get best 10 models from each evolution. Copy because we re-assign later.
|
457 |
bestPops = bestSubPop(pop)
|
458 |
bestCurScoreIdx = argmin([bestPops.members[member].score for member=1:bestPops.n])
|
459 |
bestCurScore = bestPops.members[bestCurScoreIdx].score
|
460 |
-
|
461 |
end
|
462 |
end
|
463 |
return pop
|
|
|
35 |
const nops = nuna + nbin
|
36 |
const nvar = size(X)[2];
|
37 |
|
38 |
+
function debug(verbosity, string...)
|
39 |
+
verbosity > 0 ? println(string...) : nothing
|
40 |
+
end
|
41 |
+
|
42 |
# Define a serialization format for the symbolic equations:
|
43 |
mutable struct Node
|
44 |
#Holds operators, variables, constants in a tree
|
|
|
245 |
function scoreFunc(
|
246 |
tree::Node,
|
247 |
X::Array{Float32, 2},
|
248 |
+
y::Array{Float32, 1};
|
249 |
parsimony::Float32=0.1f0)::Float32
|
250 |
try
|
251 |
return MSE(evalTreeArray(tree, X), y) + countNodes(tree)*parsimony
|
|
|
345 |
end
|
346 |
|
347 |
try
|
348 |
+
beforeLoss = scoreFunc(prev, X, y, parsimony=mult)
|
349 |
+
afterLoss = scoreFunc(tree, X, y, parsimony=mult)
|
350 |
delta = afterLoss - beforeLoss
|
351 |
probChange = exp(-delta/(T*alpha))
|
352 |
|
|
|
382 |
score::Float32
|
383 |
birth::Int32
|
384 |
|
385 |
+
PopMember(t) = new(t, scoreFunc(t, X, y, parsimony=parsimony), round(Int32, 1e3*(time()-1.6e9))
|
386 |
)
|
387 |
end
|
388 |
|
|
|
422 |
allstar = bestOfSample(pop)
|
423 |
new = iterate(allstar.tree, T, X, y, alpha, parsimony)
|
424 |
allstar.tree = new
|
425 |
+
allstar.score = scoreFunc(new, X, y, parsimony=parsimony)
|
426 |
allstar.birth = round(Int32, 1e3*(time()-1.6e9))
|
427 |
return allstar
|
428 |
end
|
|
|
445 |
pop::Population,
|
446 |
ncycles::Integer,
|
447 |
annealing::Bool=false;
|
448 |
+
verbosity::Integer=0
|
449 |
)::Population
|
450 |
pop = deepcopy(pop)
|
451 |
|
|
|
456 |
else
|
457 |
pop = regEvolCycle(pop, 1.0f0)
|
458 |
end
|
459 |
+
if verbosity > 0 && (iT % verbosity == 0)
|
|
|
460 |
bestPops = bestSubPop(pop)
|
461 |
bestCurScoreIdx = argmin([bestPops.members[member].score for member=1:bestPops.n])
|
462 |
bestCurScore = bestPops.members[bestCurScoreIdx].score
|
463 |
+
debug(verbosity, bestCurScore, " is the score for ", stringTree(bestPops.members[bestCurScoreIdx].tree))
|
464 |
end
|
465 |
end
|
466 |
return pop
|
paralleleureqa.jl
CHANGED
@@ -1,30 +1,31 @@
|
|
1 |
include("eureqa.jl")
|
2 |
|
3 |
-
println("Lets try to learn (x2^2 + cos(x3)) using regularized evolution from scratch")
|
4 |
const nthreads = Threads.nthreads()
|
5 |
-
println("Running with $nthreads threads")
|
6 |
-
const npop = 300
|
7 |
-
const annealing = true
|
8 |
-
const ncyclesperiteration = 30000
|
9 |
-
const fractionReplaced = 0.1
|
10 |
|
11 |
-
function fullRun(niterations::Integer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
# Generate random initial populations
|
13 |
allPops = [Population(npop, 3) for j=1:nthreads]
|
14 |
# Repeat this many evolutions; we collect and migrate the best
|
15 |
# each time.
|
16 |
for k=1:niterations
|
17 |
-
|
18 |
# Spawn threads to run indepdent evolutions, then gather them
|
19 |
@inbounds Threads.@threads for i=1:nthreads
|
20 |
-
allPops[i] = run(allPops[i], ncyclesperiteration, annealing,
|
21 |
end
|
22 |
|
23 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|
24 |
bestPops = deepcopy(Population([member for pop in allPops for member in bestSubPop(pop).members]))
|
25 |
bestCurScoreIdx = argmin([bestPops.members[member].score for member=1:bestPops.n])
|
26 |
bestCurScore = bestPops.members[bestCurScoreIdx].score
|
27 |
-
|
28 |
|
29 |
# Migration
|
30 |
for j=1:nthreads
|
@@ -36,4 +37,3 @@ function fullRun(niterations::Integer)
|
|
36 |
end
|
37 |
end
|
38 |
|
39 |
-
fullRun(10)
|
|
|
1 |
include("eureqa.jl")
|
2 |
|
|
|
3 |
const nthreads = Threads.nthreads()
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
+
function fullRun(niterations::Integer;
|
6 |
+
npop::Integer=300,
|
7 |
+
annealing::Bool=true,
|
8 |
+
ncyclesperiteration::Integer=3000,
|
9 |
+
fractionReplaced::Float32=0.1f0,
|
10 |
+
verbosity::Integer=0,
|
11 |
+
)
|
12 |
+
debug(verbosity, "Lets try to learn (x2^2 + cos(x3)) using regularized evolution from scratch")
|
13 |
+
debug(verbosity, "Running with $nthreads threads")
|
14 |
# Generate random initial populations
|
15 |
allPops = [Population(npop, 3) for j=1:nthreads]
|
16 |
# Repeat this many evolutions; we collect and migrate the best
|
17 |
# each time.
|
18 |
for k=1:niterations
|
|
|
19 |
# Spawn threads to run indepdent evolutions, then gather them
|
20 |
@inbounds Threads.@threads for i=1:nthreads
|
21 |
+
allPops[i] = run(allPops[i], ncyclesperiteration, annealing, verbosity=verbosity)
|
22 |
end
|
23 |
|
24 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|
25 |
bestPops = deepcopy(Population([member for pop in allPops for member in bestSubPop(pop).members]))
|
26 |
bestCurScoreIdx = argmin([bestPops.members[member].score for member=1:bestPops.n])
|
27 |
bestCurScore = bestPops.members[bestCurScoreIdx].score
|
28 |
+
debug(verbosity, bestCurScore, " is the score for ", stringTree(bestPops.members[bestCurScoreIdx].tree))
|
29 |
|
30 |
# Migration
|
31 |
for j=1:nthreads
|
|
|
37 |
end
|
38 |
end
|
39 |
|
|