MilesCranmer commited on
Commit
319103f
1 Parent(s): cf89640

Add option to control max depth instead of size

Browse files
Files changed (4) hide show
  1. README.md +14 -4
  2. benchmarks/benchmark.sh +3 -1
  3. julia/sr.jl +15 -3
  4. 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 defining a binary tree as an array, rather than a linked list. See https://stackoverflow.com/a/6384714/2689923
 
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
- - Then, we could even work with trees on the GPU, since they are all pre-allocated arrays.
354
- - A population could be a Tree, but with degree 2 on all the degrees. So a slice of population arrays forms a tree.
 
 
 
 
 
 
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] >= 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 simplify tree
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