MilesCranmer commited on
Commit
15fbc5f
1 Parent(s): c6b4cc5

Track frequency of complexities, and try to invert

Browse files
Files changed (3) hide show
  1. TODO.md +1 -1
  2. julia/sr.jl +18 -9
  3. pysr/sr.py +5 -0
TODO.md CHANGED
@@ -57,6 +57,7 @@
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
@@ -78,7 +79,6 @@
78
 
79
  ## Algorithmic performance ideas:
80
 
81
- - [ ] **Record density over complexity. Favor equations that have a density we have not explored yet. Want the final density to be evenly distributed.**
82
 
83
  - [ ] Use package compiler and compile sr.jl into a standalone binary that can be used by pysr.
84
  - [ ] When doing equation warmup, only migrate those equations with almost the same complexity. Rather than having to consider simple equations later in the game.
 
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
+ - [x] Record density over complexity. Favor equations that have a density we have not explored yet. Want the final density to be evenly distributed.
61
  - [ ] Sort these todo lists by priority
62
 
63
  ## Feature ideas
 
79
 
80
  ## Algorithmic performance ideas:
81
 
 
82
 
83
  - [ ] Use package compiler and compile sr.jl into a standalone binary that can be used by pysr.
84
  - [ ] When doing equation warmup, only migrate those equations with almost the same complexity. Rather than having to consider simple equations later in the game.
julia/sr.jl CHANGED
@@ -691,7 +691,7 @@ end
691
 
692
  # Go through one simulated annealing mutation cycle
693
  # exp(-delta/T) defines probability of accepting a change
694
- function iterate(member::PopMember, T::Float32, curmaxsize::Integer)::PopMember
695
  prev = member.tree
696
  tree = prev
697
  #TODO - reconsider this
@@ -801,6 +801,11 @@ function iterate(member::PopMember, T::Float32, curmaxsize::Integer)::PopMember
801
  if annealing
802
  delta = afterLoss - beforeLoss
803
  probChange = exp(-delta/(T*alpha))
 
 
 
 
 
804
 
805
  return_unaltered = (isnan(afterLoss) || probChange < rand())
806
  if return_unaltered
@@ -863,7 +868,8 @@ end
863
 
864
  # Pass through the population several times, replacing the oldest
865
  # with the fittest of a small subsample
866
- function regEvolCycle(pop::Population, T::Float32, curmaxsize::Integer)::Population
 
867
  # Batch over each subsample. Can give 15% improvement in speed; probably moreso for large pops.
868
  # but is ultimately a different algorithm than regularized evolution, and might not be
869
  # as good.
@@ -884,7 +890,7 @@ function regEvolCycle(pop::Population, T::Float32, curmaxsize::Integer)::Populat
884
  end
885
  end
886
  allstar = pop.members[best_idx]
887
- babies[i] = iterate(allstar, T, curmaxsize)
888
  end
889
 
890
  # Replace the n_evol_cycles-oldest members of each population
@@ -895,7 +901,7 @@ function regEvolCycle(pop::Population, T::Float32, curmaxsize::Integer)::Populat
895
  else
896
  for i=1:round(Integer, pop.n/ns)
897
  allstar = bestOfSample(pop)
898
- baby = iterate(allstar, T, curmaxsize)
899
  #printTree(baby.tree)
900
  oldest = argmin([pop.members[member].birth for member=1:pop.n])
901
  pop.members[oldest] = baby
@@ -910,16 +916,17 @@ end
910
  function run(
911
  pop::Population,
912
  ncycles::Integer,
913
- curmaxsize::Integer;
 
914
  verbosity::Integer=0
915
  )::Population
916
 
917
  allT = LinRange(1.0f0, 0.0f0, ncycles)
918
  for iT in 1:size(allT)[1]
919
  if annealing
920
- pop = regEvolCycle(pop, allT[iT], curmaxsize)
921
  else
922
- pop = regEvolCycle(pop, 1.0f0, curmaxsize)
923
  end
924
 
925
  if verbosity > 0 && (iT % verbosity == 0)
@@ -1062,6 +1069,7 @@ function fullRun(niterations::Integer;
1062
  channels = [RemoteChannel(1) for j=1:npopulations]
1063
  bestSubPops = [Population(1) for j=1:npopulations]
1064
  hallOfFame = HallOfFame()
 
1065
  curmaxsize = 3
1066
  if warmupMaxsize == 0
1067
  curmaxsize = maxsize
@@ -1074,7 +1082,7 @@ function fullRun(niterations::Integer;
1074
 
1075
  # # 2. Start the cycle on every process:
1076
  @sync for i=1:npopulations
1077
- @async allPops[i] = @spawnat :any run(fetch(allPops[i]), ncyclesperiteration, curmaxsize, verbosity=verbosity)
1078
  end
1079
  println("Started!")
1080
  cycles_complete = npopulations * niterations
@@ -1103,6 +1111,7 @@ function fullRun(niterations::Integer;
1103
 
1104
  for member in cur_pop.members
1105
  size = countNodes(member.tree)
 
1106
  if member.score < hallOfFame.members[size].score
1107
  hallOfFame.members[size] = deepcopy(member)
1108
  hallOfFame.exists[size] = true
@@ -1164,7 +1173,7 @@ function fullRun(niterations::Integer;
1164
 
1165
  @async begin
1166
  allPops[i] = @spawnat :any let
1167
- tmp_pop = run(cur_pop, ncyclesperiteration, curmaxsize, verbosity=verbosity)
1168
  @inbounds @simd for j=1:tmp_pop.n
1169
  if rand() < 0.1
1170
  tmp_pop.members[j].tree = simplifyTree(tmp_pop.members[j].tree)
 
691
 
692
  # Go through one simulated annealing mutation cycle
693
  # exp(-delta/T) defines probability of accepting a change
694
+ function iterate(member::PopMember, T::Float32, curmaxsize::Integer, frequencyComplexity::Array{Float32, 1})::PopMember
695
  prev = member.tree
696
  tree = prev
697
  #TODO - reconsider this
 
801
  if annealing
802
  delta = afterLoss - beforeLoss
803
  probChange = exp(-delta/(T*alpha))
804
+ if useFrequency
805
+ oldSize = countNodes(prev)
806
+ newSize = countNodes(tree)
807
+ probChange *= frequencyComplexity[oldSize] / frequencyComplexity[newSize]
808
+ end
809
 
810
  return_unaltered = (isnan(afterLoss) || probChange < rand())
811
  if return_unaltered
 
868
 
869
  # Pass through the population several times, replacing the oldest
870
  # with the fittest of a small subsample
871
+ function regEvolCycle(pop::Population, T::Float32, curmaxsize::Integer,
872
+ frequencyComplexity::Array{Float32, 1})::Population
873
  # Batch over each subsample. Can give 15% improvement in speed; probably moreso for large pops.
874
  # but is ultimately a different algorithm than regularized evolution, and might not be
875
  # as good.
 
890
  end
891
  end
892
  allstar = pop.members[best_idx]
893
+ babies[i] = iterate(allstar, T, curmaxsize, frequencyComplexity)
894
  end
895
 
896
  # Replace the n_evol_cycles-oldest members of each population
 
901
  else
902
  for i=1:round(Integer, pop.n/ns)
903
  allstar = bestOfSample(pop)
904
+ baby = iterate(allstar, T, curmaxsize, frequencyComplexity)
905
  #printTree(baby.tree)
906
  oldest = argmin([pop.members[member].birth for member=1:pop.n])
907
  pop.members[oldest] = baby
 
916
  function run(
917
  pop::Population,
918
  ncycles::Integer,
919
+ curmaxsize::Integer,
920
+ frequencyComplexity::Array{Float32, 1};
921
  verbosity::Integer=0
922
  )::Population
923
 
924
  allT = LinRange(1.0f0, 0.0f0, ncycles)
925
  for iT in 1:size(allT)[1]
926
  if annealing
927
+ pop = regEvolCycle(pop, allT[iT], curmaxsize, frequencyComplexity)
928
  else
929
+ pop = regEvolCycle(pop, 1.0f0, curmaxsize, frequencyComplexity)
930
  end
931
 
932
  if verbosity > 0 && (iT % verbosity == 0)
 
1069
  channels = [RemoteChannel(1) for j=1:npopulations]
1070
  bestSubPops = [Population(1) for j=1:npopulations]
1071
  hallOfFame = HallOfFame()
1072
+ frequencyComplexity = ones(Float32, actualMaxsize)
1073
  curmaxsize = 3
1074
  if warmupMaxsize == 0
1075
  curmaxsize = maxsize
 
1082
 
1083
  # # 2. Start the cycle on every process:
1084
  @sync for i=1:npopulations
1085
+ @async allPops[i] = @spawnat :any run(fetch(allPops[i]), ncyclesperiteration, curmaxsize, copy(frequencyComplexity)/sum(frequencyComplexity), verbosity=verbosity)
1086
  end
1087
  println("Started!")
1088
  cycles_complete = npopulations * niterations
 
1111
 
1112
  for member in cur_pop.members
1113
  size = countNodes(member.tree)
1114
+ frequencyComplexity[size] += 1
1115
  if member.score < hallOfFame.members[size].score
1116
  hallOfFame.members[size] = deepcopy(member)
1117
  hallOfFame.exists[size] = true
 
1173
 
1174
  @async begin
1175
  allPops[i] = @spawnat :any let
1176
+ tmp_pop = run(cur_pop, ncyclesperiteration, curmaxsize, copy(frequencyComplexity)/sum(frequencyComplexity), verbosity=verbosity)
1177
  @inbounds @simd for j=1:tmp_pop.n
1178
  if rand() < 0.1
1179
  tmp_pop.members[j].tree = simplifyTree(tmp_pop.members[j].tree)
pysr/sr.py CHANGED
@@ -90,6 +90,7 @@ def pysr(X=None, y=None, weights=None,
90
  select_k_features=None,
91
  warmupMaxsize=0,
92
  constraints={},
 
93
  limitPowComplexity=False, #deprecated
94
  threads=None, #deprecated
95
  julia_optimization=3,
@@ -172,6 +173,9 @@ def pysr(X=None, y=None, weights=None,
172
  arguments of operators. E.g., `'pow': (-1, 1)`
173
  says that power laws can have any complexity left argument, but only
174
  1 complexity exponent. Use this to force more interpretable solutions.
 
 
 
175
  :param julia_optimization: int, Optimization level (0, 1, 2, 3)
176
  :returns: pd.DataFrame, Results dataframe, giving complexity, MSE, and equations
177
  (as strings).
@@ -327,6 +331,7 @@ const mutationWeights = [
327
  ]
328
  const warmupMaxsize = {warmupMaxsize:d}
329
  const limitPowComplexity = {"true" if limitPowComplexity else "false"}
 
330
  """
331
 
332
  op_runner = ""
 
90
  select_k_features=None,
91
  warmupMaxsize=0,
92
  constraints={},
93
+ useFrequency=False,
94
  limitPowComplexity=False, #deprecated
95
  threads=None, #deprecated
96
  julia_optimization=3,
 
173
  arguments of operators. E.g., `'pow': (-1, 1)`
174
  says that power laws can have any complexity left argument, but only
175
  1 complexity exponent. Use this to force more interpretable solutions.
176
+ :param useFrequency: bool, whether to measure the frequency of complexities,
177
+ and use that instead of parsimony to explore equation space. Will
178
+ naturally find equations of all complexities.
179
  :param julia_optimization: int, Optimization level (0, 1, 2, 3)
180
  :returns: pd.DataFrame, Results dataframe, giving complexity, MSE, and equations
181
  (as strings).
 
331
  ]
332
  const warmupMaxsize = {warmupMaxsize:d}
333
  const limitPowComplexity = {"true" if limitPowComplexity else "false"}
334
+ const useFrequency = {"true" if useFrequency else "false"}
335
  """
336
 
337
  op_runner = ""