Spaces:
Running
Running
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 = ""
|