MilesCranmer commited on
Commit
fe36e3a
1 Parent(s): 964b669

Add option to slowly increase maxsize

Browse files
Files changed (4) hide show
  1. TODO.md +1 -0
  2. docs/options.md +4 -0
  3. julia/sr.jl +35 -14
  4. pysr/sr.py +6 -0
TODO.md CHANGED
@@ -56,6 +56,7 @@
56
  - [x] Create backup csv file so always something to copy from for `PySR`. Also use random hall of fame file by default. Call function to read from csv after running, so dont need to run again. Dump scores alongside MSE to .csv (and return with Pandas).
57
  - [x] Better cleanup of zombie processes after <ctl-c>
58
  - [x] Consider printing output sorted by score, not by complexity.
 
59
  - [ ] Sort these todo lists by priority
60
 
61
  ## Feature ideas
 
56
  - [x] Create backup csv file so always something to copy from for `PySR`. Also use random hall of fame file by default. Call function to read from csv after running, so dont need to run again. Dump scores alongside MSE to .csv (and return with Pandas).
57
  - [x] Better cleanup of zombie processes after <ctl-c>
58
  - [x] Consider printing output sorted by score, not by complexity.
59
+ - [x] Increase max complexity slowly over time up to the actual max.
60
  - [ ] Sort these todo lists by priority
61
 
62
  ## Feature ideas
docs/options.md CHANGED
@@ -107,6 +107,10 @@ constants, variables). `maxdepth` is by default not used, but can be set
107
  to control the maximum depth of an equation. These will make processing
108
  faster, as longer equations take longer to test.
109
 
 
 
 
 
110
 
111
  ## Batching
112
  One can turn on mini-batching, with the `batching` flag,
 
107
  to control the maximum depth of an equation. These will make processing
108
  faster, as longer equations take longer to test.
109
 
110
+ One can warm up the maxsize from a small number to encourage
111
+ PySR to start simple, by using the `warmupMaxsize` argument.
112
+ This specifies that maxsize increases every `warmupMaxsize`.
113
+
114
 
115
  ## Batching
116
  One can turn on mini-batching, with the `batching` flag,
julia/sr.jl CHANGED
@@ -597,7 +597,7 @@ end
597
 
598
  # Go through one simulated annealing mutation cycle
599
  # exp(-delta/T) defines probability of accepting a change
600
- function iterate(member::PopMember, T::Float32)::PopMember
601
  prev = member.tree
602
  tree = copyNode(prev)
603
  #TODO - reconsider this
@@ -610,23 +610,31 @@ function iterate(member::PopMember, T::Float32)::PopMember
610
  mutationChoice = rand()
611
  weightAdjustmentMutateConstant = min(8, countConstants(tree))/8.0
612
  cur_weights = copy(mutationWeights) .* 1.0
 
613
  cur_weights[1] *= weightAdjustmentMutateConstant
614
- cur_weights /= sum(cur_weights)
615
- cweights = cumsum(cur_weights)
616
  n = countNodes(tree)
617
  depth = countDepth(tree)
618
 
 
 
 
 
 
 
 
 
 
619
  if mutationChoice < cweights[1]
620
  tree = mutateConstant(tree, T)
621
  elseif mutationChoice < cweights[2]
622
  tree = mutateOperator(tree)
623
- elseif mutationChoice < cweights[3] && n < maxsize && depth < maxdepth
624
  if rand() < 0.5
625
  tree = appendRandomOp(tree)
626
  else
627
  tree = prependRandomOp(tree)
628
  end
629
- elseif mutationChoice < cweights[4] && n < maxsize && depth < maxdepth
630
  tree = insertRandomOp(tree)
631
  elseif mutationChoice < cweights[5]
632
  tree = deleteRandomOp(tree)
@@ -711,7 +719,7 @@ end
711
 
712
  # Pass through the population several times, replacing the oldest
713
  # with the fittest of a small subsample
714
- function regEvolCycle(pop::Population, T::Float32)::Population
715
  # Batch over each subsample. Can give 15% improvement in speed; probably moreso for large pops.
716
  # but is ultimately a different algorithm than regularized evolution, and might not be
717
  # as good.
@@ -732,7 +740,7 @@ function regEvolCycle(pop::Population, T::Float32)::Population
732
  end
733
  end
734
  allstar = pop.members[best_idx]
735
- babies[i] = iterate(allstar, T)
736
  end
737
 
738
  # Replace the n_evol_cycles-oldest members of each population
@@ -743,7 +751,7 @@ function regEvolCycle(pop::Population, T::Float32)::Population
743
  else
744
  for i=1:round(Integer, pop.n/ns)
745
  allstar = bestOfSample(pop)
746
- baby = iterate(allstar, T)
747
  #printTree(baby.tree)
748
  oldest = argmin([pop.members[member].birth for member=1:pop.n])
749
  pop.members[oldest] = baby
@@ -757,16 +765,17 @@ end
757
  # printing the fittest equation every 10% through
758
  function run(
759
  pop::Population,
760
- ncycles::Integer;
 
761
  verbosity::Integer=0
762
- )::Population
763
 
764
  allT = LinRange(1.0f0, 0.0f0, ncycles)
765
  for iT in 1:size(allT)[1]
766
  if annealing
767
- pop = regEvolCycle(pop, allT[iT])
768
  else
769
- pop = regEvolCycle(pop, 1.0f0)
770
  end
771
 
772
  if verbosity > 0 && (iT % verbosity == 0)
@@ -909,6 +918,10 @@ function fullRun(niterations::Integer;
909
  channels = [RemoteChannel(1) for j=1:npopulations]
910
  bestSubPops = [Population(1) for j=1:npopulations]
911
  hallOfFame = HallOfFame()
 
 
 
 
912
 
913
  for i=1:npopulations
914
  future = @spawnat :any Population(npop, 3)
@@ -917,10 +930,11 @@ function fullRun(niterations::Integer;
917
 
918
  # # 2. Start the cycle on every process:
919
  @sync for i=1:npopulations
920
- @async allPops[i] = @spawnat :any run(fetch(allPops[i]), ncyclesperiteration, verbosity=verbosity)
921
  end
922
  println("Started!")
923
  cycles_complete = npopulations * niterations
 
924
 
925
  last_print_time = time()
926
  num_equations = 0.0
@@ -1006,7 +1020,7 @@ function fullRun(niterations::Integer;
1006
 
1007
  @async begin
1008
  allPops[i] = @spawnat :any let
1009
- tmp_pop = run(cur_pop, ncyclesperiteration, verbosity=verbosity)
1010
  @inbounds @simd for j=1:tmp_pop.n
1011
  if rand() < 0.1
1012
  tmp_pop.members[j].tree = simplifyTree(tmp_pop.members[j].tree)
@@ -1027,6 +1041,13 @@ function fullRun(niterations::Integer;
1027
  end
1028
 
1029
  cycles_complete -= 1
 
 
 
 
 
 
 
1030
  num_equations += ncyclesperiteration * npop / 10.0
1031
  end
1032
  end
 
597
 
598
  # Go through one simulated annealing mutation cycle
599
  # exp(-delta/T) defines probability of accepting a change
600
+ function iterate(member::PopMember, T::Float32, curmaxsize::Integer)::PopMember
601
  prev = member.tree
602
  tree = copyNode(prev)
603
  #TODO - reconsider this
 
610
  mutationChoice = rand()
611
  weightAdjustmentMutateConstant = min(8, countConstants(tree))/8.0
612
  cur_weights = copy(mutationWeights) .* 1.0
613
+ #More constants => more likely to do constant mutation
614
  cur_weights[1] *= weightAdjustmentMutateConstant
 
 
615
  n = countNodes(tree)
616
  depth = countDepth(tree)
617
 
618
+ # If equation too big, don't add new operators
619
+ if n >= curmaxsize || depth >= maxdepth
620
+ cur_weights[3] = 0.0
621
+ cur_weights[4] = 0.0
622
+ end
623
+
624
+ cur_weights /= sum(cur_weights)
625
+ cweights = cumsum(cur_weights)
626
+
627
  if mutationChoice < cweights[1]
628
  tree = mutateConstant(tree, T)
629
  elseif mutationChoice < cweights[2]
630
  tree = mutateOperator(tree)
631
+ elseif mutationChoice < cweights[3]
632
  if rand() < 0.5
633
  tree = appendRandomOp(tree)
634
  else
635
  tree = prependRandomOp(tree)
636
  end
637
+ elseif mutationChoice < cweights[4]
638
  tree = insertRandomOp(tree)
639
  elseif mutationChoice < cweights[5]
640
  tree = deleteRandomOp(tree)
 
719
 
720
  # Pass through the population several times, replacing the oldest
721
  # with the fittest of a small subsample
722
+ function regEvolCycle(pop::Population, T::Float32, curmaxsize::Integer)::Population
723
  # Batch over each subsample. Can give 15% improvement in speed; probably moreso for large pops.
724
  # but is ultimately a different algorithm than regularized evolution, and might not be
725
  # as good.
 
740
  end
741
  end
742
  allstar = pop.members[best_idx]
743
+ babies[i] = iterate(allstar, T, curmaxsize)
744
  end
745
 
746
  # Replace the n_evol_cycles-oldest members of each population
 
751
  else
752
  for i=1:round(Integer, pop.n/ns)
753
  allstar = bestOfSample(pop)
754
+ baby = iterate(allstar, T, curmaxsize)
755
  #printTree(baby.tree)
756
  oldest = argmin([pop.members[member].birth for member=1:pop.n])
757
  pop.members[oldest] = baby
 
765
  # printing the fittest equation every 10% through
766
  function run(
767
  pop::Population,
768
+ ncycles::Integer,
769
+ curmaxsize::Integer;
770
  verbosity::Integer=0
771
+ )::Population
772
 
773
  allT = LinRange(1.0f0, 0.0f0, ncycles)
774
  for iT in 1:size(allT)[1]
775
  if annealing
776
+ pop = regEvolCycle(pop, allT[iT], curmaxsize)
777
  else
778
+ pop = regEvolCycle(pop, 1.0f0, curmaxsize)
779
  end
780
 
781
  if verbosity > 0 && (iT % verbosity == 0)
 
918
  channels = [RemoteChannel(1) for j=1:npopulations]
919
  bestSubPops = [Population(1) for j=1:npopulations]
920
  hallOfFame = HallOfFame()
921
+ curmaxsize = 3
922
+ if warmupMaxsize == 0
923
+ curmaxsize = maxsize
924
+ end
925
 
926
  for i=1:npopulations
927
  future = @spawnat :any Population(npop, 3)
 
930
 
931
  # # 2. Start the cycle on every process:
932
  @sync for i=1:npopulations
933
+ @async allPops[i] = @spawnat :any run(fetch(allPops[i]), ncyclesperiteration, curmaxsize, verbosity=verbosity)
934
  end
935
  println("Started!")
936
  cycles_complete = npopulations * niterations
937
+ curmaxsize += 1
938
 
939
  last_print_time = time()
940
  num_equations = 0.0
 
1020
 
1021
  @async begin
1022
  allPops[i] = @spawnat :any let
1023
+ tmp_pop = run(cur_pop, ncyclesperiteration, curmaxsize, verbosity=verbosity)
1024
  @inbounds @simd for j=1:tmp_pop.n
1025
  if rand() < 0.1
1026
  tmp_pop.members[j].tree = simplifyTree(tmp_pop.members[j].tree)
 
1041
  end
1042
 
1043
  cycles_complete -= 1
1044
+ cycles_elapsed = npopulations * niterations - cycles_complete
1045
+ if warmupMaxsize != 0 && cycles_elapsed % warmupMaxsize == 0
1046
+ curmaxsize += 1
1047
+ if curmaxsize > maxsize
1048
+ curmaxsize = maxsize
1049
+ end
1050
+ end
1051
  num_equations += ncyclesperiteration * npop / 10.0
1052
  end
1053
  end
pysr/sr.py CHANGED
@@ -85,6 +85,7 @@ def pysr(X=None, y=None, weights=None,
85
  batching=False,
86
  batchSize=50,
87
  select_k_features=None,
 
88
  threads=None, #deprecated
89
  julia_optimization=3,
90
  ):
@@ -157,6 +158,10 @@ def pysr(X=None, y=None, weights=None,
157
  Python using random forests, before passing to the symbolic regression
158
  code. None means no feature selection; an int means select that many
159
  features.
 
 
 
 
160
  :param julia_optimization: int, Optimization level (0, 1, 2, 3)
161
  :returns: pd.DataFrame, Results dataframe, giving complexity, MSE, and equations
162
  (as strings).
@@ -268,6 +273,7 @@ const mutationWeights = [
268
  {weightRandomize:f},
269
  {weightDoNothing:f}
270
  ]
 
271
  """
272
 
273
  if X.shape[1] == 1:
 
85
  batching=False,
86
  batchSize=50,
87
  select_k_features=None,
88
+ warmupMaxsize=0,
89
  threads=None, #deprecated
90
  julia_optimization=3,
91
  ):
 
158
  Python using random forests, before passing to the symbolic regression
159
  code. None means no feature selection; an int means select that many
160
  features.
161
+ :param warmupMaxsize: int, whether to slowly increase max size from
162
+ a small number up to the maxsize (if greater than 0).
163
+ If greater than 0, says how many cycles before the maxsize
164
+ is increased.
165
  :param julia_optimization: int, Optimization level (0, 1, 2, 3)
166
  :returns: pd.DataFrame, Results dataframe, giving complexity, MSE, and equations
167
  (as strings).
 
273
  {weightRandomize:f},
274
  {weightDoNothing:f}
275
  ]
276
+ const warmupMaxsize = {warmupMaxsize:d}
277
  """
278
 
279
  if X.shape[1] == 1: