Spaces:
Running
Running
MilesCranmer
commited on
Commit
•
15fbc5f
1
Parent(s):
c6b4cc5
Track frequency of complexities, and try to invert
Browse files- TODO.md +1 -1
- julia/sr.jl +18 -9
- 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
|
|
|
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 = ""
|