Spaces:
Running
Running
Commit
·
319103f
1
Parent(s):
cf89640
Add option to control max depth instead of size
Browse files- README.md +14 -4
- benchmarks/benchmark.sh +3 -1
- julia/sr.jl +15 -3
- pysr/sr.py +4 -0
README.md
CHANGED
|
@@ -320,6 +320,7 @@ pd.DataFrame, Results dataframe, giving complexity, MSE, and equations
|
|
| 320 |
- [ ] Create flexible way of providing "simplification recipes." I.e., plus(plus(T, C), C) => plus(T, +(C, C)). The user could pass these.
|
| 321 |
- [ ] Consider allowing multi-threading turned off, for faster testing (cache issue on travis). Or could simply fix the caching issue there.
|
| 322 |
- [ ] Consider returning only the equation of interest; rather than all equations.
|
|
|
|
| 323 |
|
| 324 |
## Algorithmic performance ideas:
|
| 325 |
|
|
@@ -332,15 +333,18 @@ pd.DataFrame, Results dataframe, giving complexity, MSE, and equations
|
|
| 332 |
- [ ] Calculate feature importances based on features we've already seen, then weight those features up in all random generations.
|
| 333 |
- [ ] Calculate feature importances of future mutations, by looking at correlation between residual of model, and the features.
|
| 334 |
- Store feature importances of future, and periodically update it.
|
|
|
|
| 335 |
|
| 336 |
|
| 337 |
## Code performance ideas:
|
| 338 |
|
|
|
|
|
|
|
| 339 |
- [ ] Add true multi-node processing, with MPI, or just file sharing. Multiple populations per core.
|
| 340 |
- Ongoing in cluster branch
|
| 341 |
-
- [ ] Try @spawn over each sub-population. Do random sort, compute mutation for each, then replace 10% oldest.
|
| 342 |
- [ ] Performance: try inling things?
|
| 343 |
-
- [ ] Try
|
|
|
|
| 344 |
```julia
|
| 345 |
mutable struct Tree
|
| 346 |
degree::Array{Integer, 1}
|
|
@@ -350,8 +354,14 @@ mutable struct Tree
|
|
| 350 |
Tree(s::Integer) = new(zeros(Integer, s), zeros(Float32, s), zeros(Bool, s), zeros(Integer, s))
|
| 351 |
end
|
| 352 |
```
|
| 353 |
-
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
- [ ] Can we cache calculations, or does the compiler do that? E.g., I should only have to run exp(x0) once; after that it should be read from memory.
|
| 357 |
- Done on caching branch. Currently am finding that this is quiet slow (presumably because memory allocation is the main issue).
|
|
|
|
| 320 |
- [ ] Create flexible way of providing "simplification recipes." I.e., plus(plus(T, C), C) => plus(T, +(C, C)). The user could pass these.
|
| 321 |
- [ ] Consider allowing multi-threading turned off, for faster testing (cache issue on travis). Or could simply fix the caching issue there.
|
| 322 |
- [ ] Consider returning only the equation of interest; rather than all equations.
|
| 323 |
+
- [x] Control max depth, rather than max number of nodes?
|
| 324 |
|
| 325 |
## Algorithmic performance ideas:
|
| 326 |
|
|
|
|
| 333 |
- [ ] Calculate feature importances based on features we've already seen, then weight those features up in all random generations.
|
| 334 |
- [ ] Calculate feature importances of future mutations, by looking at correlation between residual of model, and the features.
|
| 335 |
- Store feature importances of future, and periodically update it.
|
| 336 |
+
- [ ] Punish depth rather than size, as depth really hurts during optimization.
|
| 337 |
|
| 338 |
|
| 339 |
## Code performance ideas:
|
| 340 |
|
| 341 |
+
- [ ] **Try @spawn over each sub-population. Do random sort, compute mutation for each, then replace 10% oldest.**
|
| 342 |
+
- [ ] **Try defining a binary tree as an array, rather than a linked list. See https://stackoverflow.com/a/6384714/2689923**
|
| 343 |
- [ ] Add true multi-node processing, with MPI, or just file sharing. Multiple populations per core.
|
| 344 |
- Ongoing in cluster branch
|
|
|
|
| 345 |
- [ ] Performance: try inling things?
|
| 346 |
+
- [ ] Try storing things like number nodes in a tree; then can iterate instead of counting
|
| 347 |
+
|
| 348 |
```julia
|
| 349 |
mutable struct Tree
|
| 350 |
degree::Array{Integer, 1}
|
|
|
|
| 354 |
Tree(s::Integer) = new(zeros(Integer, s), zeros(Float32, s), zeros(Bool, s), zeros(Integer, s))
|
| 355 |
end
|
| 356 |
```
|
| 357 |
+
|
| 358 |
+
- Then, we could even work with trees on the GPU, since they are all pre-allocated arrays.
|
| 359 |
+
- A population could be a Tree, but with degree 2 on all the degrees. So a slice of population arrays forms a tree.
|
| 360 |
+
- How many operations can we do via matrix ops? Mutate node=>easy.
|
| 361 |
+
- Can probably batch and do many operations at once across a population.
|
| 362 |
+
- Or, across all populations! Mutate operator: index 2D array and set it to random vector? But the indexing might hurt.
|
| 363 |
+
- The big advantage: can evaluate all new mutated trees at once; as massive matrix operation.
|
| 364 |
+
- Can control depth, rather than maxsize. Then just pretend all trees are full and same depth. Then we really don't need to care about depth.
|
| 365 |
|
| 366 |
- [ ] Can we cache calculations, or does the compiler do that? E.g., I should only have to run exp(x0) once; after that it should be read from memory.
|
| 367 |
- Done on caching branch. Currently am finding that this is quiet slow (presumably because memory allocation is the main issue).
|
benchmarks/benchmark.sh
CHANGED
|
@@ -6,7 +6,9 @@ import numpy as np
|
|
| 6 |
from pysr import pysr
|
| 7 |
X=np.random.randn(100, 2)*5
|
| 8 |
y=2*np.sin((X[:, 0]+X[:, 1]))*np.exp(X[:, 1]/3)
|
| 9 |
-
if version[1] >= 3 and version[2] >=
|
|
|
|
|
|
|
| 10 |
eq = pysr(X, y, binary_operators=["plus", "mult", "div", "pow"], unary_operators=["sin"], niterations=20, procs=4, parsimony=1e-10, npop=1000, ncyclesperiteration=1000)
|
| 11 |
else:
|
| 12 |
eq = pysr(X, y, binary_operators=["plus", "mult", "div", "pow"], unary_operators=["sin"], niterations=20, threads=4, parsimony=1e-10, npop=1000, ncyclesperiteration=1000)
|
|
|
|
| 6 |
from pysr import pysr
|
| 7 |
X=np.random.randn(100, 2)*5
|
| 8 |
y=2*np.sin((X[:, 0]+X[:, 1]))*np.exp(X[:, 1]/3)
|
| 9 |
+
if version[1] >= 3 and version[2] >= 16:
|
| 10 |
+
eq = pysr(X, y, binary_operators=["plus", "mult", "div", "pow"], unary_operators=["sin"], niterations=20, procs=4, parsimony=1e-10, npop=1000, ncyclesperiteration=1000, maxdepth=6)
|
| 11 |
+
elif version[1] >= 3 and version[2] >= 2:
|
| 12 |
eq = pysr(X, y, binary_operators=["plus", "mult", "div", "pow"], unary_operators=["sin"], niterations=20, procs=4, parsimony=1e-10, npop=1000, ncyclesperiteration=1000)
|
| 13 |
else:
|
| 14 |
eq = pysr(X, y, binary_operators=["plus", "mult", "div", "pow"], unary_operators=["sin"], niterations=20, threads=4, parsimony=1e-10, npop=1000, ncyclesperiteration=1000)
|
julia/sr.jl
CHANGED
|
@@ -103,6 +103,17 @@ function countNodes(tree::Node)::Integer
|
|
| 103 |
end
|
| 104 |
end
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
# Convert an equation to a string
|
| 107 |
function stringTree(tree::Node)::String
|
| 108 |
if tree.degree == 0
|
|
@@ -535,14 +546,15 @@ function iterate(member::PopMember, T::Float32)::PopMember
|
|
| 535 |
cur_weights /= sum(cur_weights)
|
| 536 |
cweights = cumsum(cur_weights)
|
| 537 |
n = countNodes(tree)
|
|
|
|
| 538 |
|
| 539 |
if mutationChoice < cweights[1]
|
| 540 |
tree = mutateConstant(tree, T)
|
| 541 |
elseif mutationChoice < cweights[2]
|
| 542 |
tree = mutateOperator(tree)
|
| 543 |
-
elseif mutationChoice < cweights[3] && n < maxsize
|
| 544 |
tree = appendRandomOp(tree)
|
| 545 |
-
elseif mutationChoice < cweights[4] && n < maxsize
|
| 546 |
tree = insertRandomOp(tree)
|
| 547 |
elseif mutationChoice < cweights[5]
|
| 548 |
tree = deleteRandomOp(tree)
|
|
@@ -551,7 +563,7 @@ function iterate(member::PopMember, T::Float32)::PopMember
|
|
| 551 |
tree = combineOperators(tree) # See if repeated constants at outer levels
|
| 552 |
return PopMember(tree, beforeLoss)
|
| 553 |
elseif mutationChoice < cweights[7]
|
| 554 |
-
tree = genRandomTree(5) # Sometimes we
|
| 555 |
else
|
| 556 |
return PopMember(tree, beforeLoss)
|
| 557 |
end
|
|
|
|
| 103 |
end
|
| 104 |
end
|
| 105 |
|
| 106 |
+
# Count the max depth of a tree
|
| 107 |
+
function countDepth(tree::Node)::Integer
|
| 108 |
+
if tree.degree == 0
|
| 109 |
+
return 1
|
| 110 |
+
elseif tree.degree == 1
|
| 111 |
+
return 1 + countDepth(tree.l)
|
| 112 |
+
else
|
| 113 |
+
return 1 + max(countDepth(tree.l), countDepth(tree.r))
|
| 114 |
+
end
|
| 115 |
+
end
|
| 116 |
+
|
| 117 |
# Convert an equation to a string
|
| 118 |
function stringTree(tree::Node)::String
|
| 119 |
if tree.degree == 0
|
|
|
|
| 546 |
cur_weights /= sum(cur_weights)
|
| 547 |
cweights = cumsum(cur_weights)
|
| 548 |
n = countNodes(tree)
|
| 549 |
+
depth = countDepth(tree)
|
| 550 |
|
| 551 |
if mutationChoice < cweights[1]
|
| 552 |
tree = mutateConstant(tree, T)
|
| 553 |
elseif mutationChoice < cweights[2]
|
| 554 |
tree = mutateOperator(tree)
|
| 555 |
+
elseif mutationChoice < cweights[3] && n < maxsize && depth < maxdepth
|
| 556 |
tree = appendRandomOp(tree)
|
| 557 |
+
elseif mutationChoice < cweights[4] && n < maxsize && depth < maxdepth
|
| 558 |
tree = insertRandomOp(tree)
|
| 559 |
elseif mutationChoice < cweights[5]
|
| 560 |
tree = deleteRandomOp(tree)
|
|
|
|
| 563 |
tree = combineOperators(tree) # See if repeated constants at outer levels
|
| 564 |
return PopMember(tree, beforeLoss)
|
| 565 |
elseif mutationChoice < cweights[7]
|
| 566 |
+
tree = genRandomTree(5) # Sometimes we generate a new tree completely tree
|
| 567 |
else
|
| 568 |
return PopMember(tree, beforeLoss)
|
| 569 |
end
|
pysr/sr.py
CHANGED
|
@@ -73,6 +73,7 @@ def pysr(X=None, y=None, weights=None,
|
|
| 73 |
test='simple1',
|
| 74 |
verbosity=1e9,
|
| 75 |
maxsize=20,
|
|
|
|
| 76 |
threads=None, #deprecated
|
| 77 |
julia_optimization=3,
|
| 78 |
):
|
|
@@ -135,6 +136,8 @@ def pysr(X=None, y=None, weights=None,
|
|
| 135 |
"""
|
| 136 |
if threads is not None:
|
| 137 |
raise ValueError("The threads kwarg is deprecated. Use procs.")
|
|
|
|
|
|
|
| 138 |
|
| 139 |
# Check for potential errors before they happen
|
| 140 |
assert len(unary_operators) + len(binary_operators) > 0
|
|
@@ -200,6 +203,7 @@ const ns=10;
|
|
| 200 |
const parsimony = {parsimony:f}f0
|
| 201 |
const alpha = {alpha:f}f0
|
| 202 |
const maxsize = {maxsize:d}
|
|
|
|
| 203 |
const migration = {'true' if migration else 'false'}
|
| 204 |
const hofMigration = {'true' if hofMigration else 'false'}
|
| 205 |
const fractionReplacedHof = {fractionReplacedHof}f0
|
|
|
|
| 73 |
test='simple1',
|
| 74 |
verbosity=1e9,
|
| 75 |
maxsize=20,
|
| 76 |
+
maxdepth=None,
|
| 77 |
threads=None, #deprecated
|
| 78 |
julia_optimization=3,
|
| 79 |
):
|
|
|
|
| 136 |
"""
|
| 137 |
if threads is not None:
|
| 138 |
raise ValueError("The threads kwarg is deprecated. Use procs.")
|
| 139 |
+
if maxdepth is None:
|
| 140 |
+
maxdepth = maxsize
|
| 141 |
|
| 142 |
# Check for potential errors before they happen
|
| 143 |
assert len(unary_operators) + len(binary_operators) > 0
|
|
|
|
| 203 |
const parsimony = {parsimony:f}f0
|
| 204 |
const alpha = {alpha:f}f0
|
| 205 |
const maxsize = {maxsize:d}
|
| 206 |
+
const maxdepth = {maxdepth:d}
|
| 207 |
const migration = {'true' if migration else 'false'}
|
| 208 |
const hofMigration = {'true' if hofMigration else 'false'}
|
| 209 |
const fractionReplacedHof = {fractionReplacedHof}f0
|