Spaces:
Sleeping
Sleeping
Commit
·
6f3a331
1
Parent(s):
c3d54db
Reduce precision
Browse files- eureqa.jl +66 -62
- paralleleureqa.jl +1 -1
eureqa.jl
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# Define allowed operators
|
| 2 |
-
plus(x::
|
| 3 |
-
mult(x::
|
| 4 |
|
| 5 |
##########################
|
| 6 |
# # Allowed operators
|
|
@@ -17,16 +17,16 @@ const nvar = 5;
|
|
| 17 |
#
|
| 18 |
##########################
|
| 19 |
# # Dataset to learn
|
| 20 |
-
const X = randn(100, nvar)*2
|
| 21 |
-
const y = ((cx,)->cx^2).(X[:, 2]) + cos.(X[:, 3])
|
| 22 |
##########################
|
| 23 |
|
| 24 |
##################
|
| 25 |
# Hyperparameters
|
| 26 |
# How much to punish complexity
|
| 27 |
-
const parsimony =
|
| 28 |
# How much to scale temperature by (T between 0 and 1)
|
| 29 |
-
const alpha =
|
| 30 |
const maxsize = 20
|
| 31 |
##################
|
| 32 |
|
|
@@ -38,27 +38,27 @@ const nops = nuna + nbin
|
|
| 38 |
# Define a serialization format for the symbolic equations:
|
| 39 |
mutable struct Node
|
| 40 |
#Holds operators, variables, constants in a tree
|
| 41 |
-
degree::
|
| 42 |
-
val::Union{
|
| 43 |
constant::Bool #false if variable
|
| 44 |
op::Function #enumerates operator (for degree=1,2)
|
| 45 |
l::Union{Node, Nothing}
|
| 46 |
r::Union{Node, Nothing}
|
| 47 |
|
| 48 |
-
Node(val::
|
| 49 |
-
Node(val::
|
| 50 |
-
Node(op, l::Node) = new(1, 0.
|
| 51 |
-
Node(op, l::Union{
|
| 52 |
-
Node(op, l::Node, r::Node) = new(2, 0.
|
| 53 |
|
| 54 |
#Allow to pass the leaf value without additional node call:
|
| 55 |
-
Node(op, l::Union{
|
| 56 |
-
Node(op, l::Node, r::Union{
|
| 57 |
-
Node(op, l::Union{
|
| 58 |
end
|
| 59 |
|
| 60 |
# Evaluate a symbolic equation:
|
| 61 |
-
function evalTree(tree::Node, x::Array{
|
| 62 |
if tree.degree == 0
|
| 63 |
if tree.constant
|
| 64 |
return tree.val
|
|
@@ -73,7 +73,7 @@ function evalTree(tree::Node, x::Array{Float64, 1}=Float64[])::Float64
|
|
| 73 |
end
|
| 74 |
|
| 75 |
# Count the operators, constants, variables in an equation
|
| 76 |
-
function countNodes(tree::Node)::
|
| 77 |
if tree.degree == 0
|
| 78 |
return 1
|
| 79 |
elseif tree.degree == 1
|
|
@@ -129,7 +129,7 @@ function randomNode(tree::Node)::Node
|
|
| 129 |
end
|
| 130 |
|
| 131 |
# Count the number of unary operators in the equation
|
| 132 |
-
function countUnaryOperators(tree::Node)::
|
| 133 |
if tree.degree == 0
|
| 134 |
return 0
|
| 135 |
elseif tree.degree == 1
|
|
@@ -140,7 +140,7 @@ function countUnaryOperators(tree::Node)::Int
|
|
| 140 |
end
|
| 141 |
|
| 142 |
# Count the number of binary operators in the equation
|
| 143 |
-
function countBinaryOperators(tree::Node)::
|
| 144 |
if tree.degree == 0
|
| 145 |
return 0
|
| 146 |
elseif tree.degree == 1
|
|
@@ -151,7 +151,7 @@ function countBinaryOperators(tree::Node)::Int
|
|
| 151 |
end
|
| 152 |
|
| 153 |
# Count the number of operators in the equation
|
| 154 |
-
function countOperators(tree::Node)::
|
| 155 |
return countUnaryOperators(tree) + countBinaryOperators(tree)
|
| 156 |
end
|
| 157 |
|
|
@@ -174,9 +174,9 @@ function mutateOperator(tree::Node)::Node
|
|
| 174 |
end
|
| 175 |
|
| 176 |
# Count the number of constants in an equation
|
| 177 |
-
function countConstants(tree::Node)::
|
| 178 |
if tree.degree == 0
|
| 179 |
-
return convert(
|
| 180 |
elseif tree.degree == 1
|
| 181 |
return 0 + countConstants(tree.l)
|
| 182 |
else
|
|
@@ -186,8 +186,8 @@ end
|
|
| 186 |
|
| 187 |
# Randomly perturb a constant
|
| 188 |
function mutateConstant(
|
| 189 |
-
tree::Node, T::
|
| 190 |
-
probNegate::
|
| 191 |
# T is between 0 and 1.
|
| 192 |
|
| 193 |
if countConstants(tree) == 0
|
|
@@ -198,9 +198,9 @@ function mutateConstant(
|
|
| 198 |
node = randomNode(tree)
|
| 199 |
end
|
| 200 |
|
| 201 |
-
bottom = 0.
|
| 202 |
-
maxChange = T + 1.
|
| 203 |
-
factor = maxChange^rand()
|
| 204 |
makeConstBigger = rand() > 0.5
|
| 205 |
|
| 206 |
if makeConstBigger
|
|
@@ -219,31 +219,35 @@ end
|
|
| 219 |
# Evaluate an equation over an array of datapoints
|
| 220 |
function evalTreeArray(
|
| 221 |
tree::Node,
|
| 222 |
-
x::Array{
|
| 223 |
return mapslices(
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
end
|
| 229 |
|
| 230 |
# Sum of square error between two arrays
|
| 231 |
-
function SSE(x::Array{
|
| 232 |
return sum(((cx,)->cx^2).(x - y))
|
| 233 |
end
|
| 234 |
|
| 235 |
# Mean of square error between two arrays
|
| 236 |
-
function MSE(x::Array{
|
| 237 |
return SSE(x, y)/size(x)[1]
|
| 238 |
end
|
| 239 |
|
| 240 |
# Score an equation
|
| 241 |
function scoreFunc(
|
| 242 |
tree::Node,
|
| 243 |
-
X::Array{
|
| 244 |
-
y::Array{
|
| 245 |
-
parsimony::
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
end
|
| 248 |
|
| 249 |
# Add a random unary/binary operation to the end of a tree
|
|
@@ -256,12 +260,12 @@ function appendRandomOp(tree::Node)::Node
|
|
| 256 |
choice = rand()
|
| 257 |
makeNewBinOp = choice < nbin/nops
|
| 258 |
if rand() > 0.5
|
| 259 |
-
left = randn()
|
| 260 |
else
|
| 261 |
left = rand(1:nvar)
|
| 262 |
end
|
| 263 |
if rand() > 0.5
|
| 264 |
-
right = randn()
|
| 265 |
else
|
| 266 |
right = rand(1:nvar)
|
| 267 |
end
|
|
@@ -293,7 +297,7 @@ function deleteRandomOp(tree::Node)::Node
|
|
| 293 |
node = randomNode(tree)
|
| 294 |
# Can "delete" variable or constant too
|
| 295 |
if rand() > 0.5
|
| 296 |
-
val = randn()
|
| 297 |
else
|
| 298 |
val = rand(1:nvar)
|
| 299 |
end
|
|
@@ -310,10 +314,10 @@ end
|
|
| 310 |
# Go through one simulated annealing mutation cycle
|
| 311 |
# exp(-delta/T) defines probability of accepting a change
|
| 312 |
function iterate(
|
| 313 |
-
tree::Node, T::
|
| 314 |
-
X::Array{
|
| 315 |
-
alpha::
|
| 316 |
-
mult::
|
| 317 |
)::Node
|
| 318 |
prev = deepcopy(tree)
|
| 319 |
|
|
@@ -357,8 +361,8 @@ function iterate(
|
|
| 357 |
end
|
| 358 |
|
| 359 |
# Create a random equation by appending random operators
|
| 360 |
-
function genRandomTree(length::
|
| 361 |
-
tree = Node(1.
|
| 362 |
for i=1:length
|
| 363 |
tree = appendRandomOp(tree)
|
| 364 |
end
|
|
@@ -369,21 +373,21 @@ end
|
|
| 369 |
# Define a member of population by equation, score, and age
|
| 370 |
mutable struct PopMember
|
| 371 |
tree::Node
|
| 372 |
-
score::
|
| 373 |
-
birth::
|
| 374 |
|
| 375 |
-
PopMember(t) = new(t, scoreFunc(t, X, y, parsimony), time()-1.
|
| 376 |
end
|
| 377 |
|
| 378 |
# A list of members of the population, with easy constructors,
|
| 379 |
# which allow for random generation of new populations
|
| 380 |
mutable struct Population
|
| 381 |
members::Array{PopMember, 1}
|
| 382 |
-
n::
|
| 383 |
|
| 384 |
Population(pop::Array{PopMember, 1}) = new(pop, size(pop)[1])
|
| 385 |
-
Population(npop::
|
| 386 |
-
Population(npop::
|
| 387 |
|
| 388 |
end
|
| 389 |
|
|
@@ -407,19 +411,19 @@ function bestSubPop(pop::Population)::Population
|
|
| 407 |
end
|
| 408 |
|
| 409 |
# Mutate the best sampled member of the population
|
| 410 |
-
function iterateSample(pop::Population, T::
|
| 411 |
allstar = bestOfSample(pop)
|
| 412 |
new = iterate(allstar.tree, T, X, y, alpha, parsimony)
|
| 413 |
allstar.tree = new
|
| 414 |
allstar.score = scoreFunc(new, X, y, parsimony)
|
| 415 |
-
allstar.birth = time() - 1.
|
| 416 |
return allstar
|
| 417 |
end
|
| 418 |
|
| 419 |
# Pass through the population several times, replacing the oldest
|
| 420 |
# with the fittest of a small subsample
|
| 421 |
-
function regEvolCycle(pop::Population, T::
|
| 422 |
-
for i=1:
|
| 423 |
baby = iterateSample(pop, T)
|
| 424 |
#printTree(baby.tree)
|
| 425 |
oldest = argmin([pop.members[member].birth for member=1:pop.n])
|
|
@@ -432,18 +436,18 @@ end
|
|
| 432 |
# printing the fittest equation every 10% through
|
| 433 |
function run(
|
| 434 |
pop::Population,
|
| 435 |
-
ncycles::
|
| 436 |
annealing::Bool=false;
|
| 437 |
-
verbose::
|
| 438 |
)::Population
|
| 439 |
pop = deepcopy(pop)
|
| 440 |
|
| 441 |
-
allT = LinRange(1.
|
| 442 |
for iT in 1:size(allT)[1]
|
| 443 |
if annealing
|
| 444 |
pop = regEvolCycle(pop, allT[iT])
|
| 445 |
else
|
| 446 |
-
pop = regEvolCycle(pop, 1.
|
| 447 |
end
|
| 448 |
if verbose > 0 && (iT % verbose == 0)
|
| 449 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|
|
|
|
| 1 |
# Define allowed operators
|
| 2 |
+
plus(x::Float32, y::Float32) = x+y
|
| 3 |
+
mult(x::Float32, y::Float32) = x*y;
|
| 4 |
|
| 5 |
##########################
|
| 6 |
# # Allowed operators
|
|
|
|
| 17 |
#
|
| 18 |
##########################
|
| 19 |
# # Dataset to learn
|
| 20 |
+
const X = convert(Array{Float32, 2}, randn(100, nvar)*2)
|
| 21 |
+
const y = convert(Array{Float32, 1}, ((cx,)->cx^2).(X[:, 2]) + cos.(X[:, 3]))
|
| 22 |
##########################
|
| 23 |
|
| 24 |
##################
|
| 25 |
# Hyperparameters
|
| 26 |
# How much to punish complexity
|
| 27 |
+
const parsimony = 1f-3
|
| 28 |
# How much to scale temperature by (T between 0 and 1)
|
| 29 |
+
const alpha = 10.0f0
|
| 30 |
const maxsize = 20
|
| 31 |
##################
|
| 32 |
|
|
|
|
| 38 |
# Define a serialization format for the symbolic equations:
|
| 39 |
mutable struct Node
|
| 40 |
#Holds operators, variables, constants in a tree
|
| 41 |
+
degree::Integer #0 for constant/variable, 1 for cos/sin, 2 for +/* etc.
|
| 42 |
+
val::Union{Float32, Integer} #Either const value, or enumerates variable
|
| 43 |
constant::Bool #false if variable
|
| 44 |
op::Function #enumerates operator (for degree=1,2)
|
| 45 |
l::Union{Node, Nothing}
|
| 46 |
r::Union{Node, Nothing}
|
| 47 |
|
| 48 |
+
Node(val::Float32) = new(0, val, true, id, nothing, nothing)
|
| 49 |
+
Node(val::Integer) = new(0, val, false, id, nothing, nothing)
|
| 50 |
+
Node(op, l::Node) = new(1, 0.0f0, false, op, l, nothing)
|
| 51 |
+
Node(op, l::Union{Float32, Integer}) = new(1, 0.0f0, false, op, Node(l), nothing)
|
| 52 |
+
Node(op, l::Node, r::Node) = new(2, 0.0f0, false, op, l, r)
|
| 53 |
|
| 54 |
#Allow to pass the leaf value without additional node call:
|
| 55 |
+
Node(op, l::Union{Float32, Integer}, r::Node) = new(2, 0.0f0, false, op, Node(l), r)
|
| 56 |
+
Node(op, l::Node, r::Union{Float32, Integer}) = new(2, 0.0f0, false, op, l, Node(r))
|
| 57 |
+
Node(op, l::Union{Float32, Integer}, r::Union{Float32, Integer}) = new(2, 0.0f0, false, op, Node(l), Node(r))
|
| 58 |
end
|
| 59 |
|
| 60 |
# Evaluate a symbolic equation:
|
| 61 |
+
function evalTree(tree::Node, x::Array{Float32, 1}=Float32[])::Float32
|
| 62 |
if tree.degree == 0
|
| 63 |
if tree.constant
|
| 64 |
return tree.val
|
|
|
|
| 73 |
end
|
| 74 |
|
| 75 |
# Count the operators, constants, variables in an equation
|
| 76 |
+
function countNodes(tree::Node)::Integer
|
| 77 |
if tree.degree == 0
|
| 78 |
return 1
|
| 79 |
elseif tree.degree == 1
|
|
|
|
| 129 |
end
|
| 130 |
|
| 131 |
# Count the number of unary operators in the equation
|
| 132 |
+
function countUnaryOperators(tree::Node)::Integer
|
| 133 |
if tree.degree == 0
|
| 134 |
return 0
|
| 135 |
elseif tree.degree == 1
|
|
|
|
| 140 |
end
|
| 141 |
|
| 142 |
# Count the number of binary operators in the equation
|
| 143 |
+
function countBinaryOperators(tree::Node)::Integer
|
| 144 |
if tree.degree == 0
|
| 145 |
return 0
|
| 146 |
elseif tree.degree == 1
|
|
|
|
| 151 |
end
|
| 152 |
|
| 153 |
# Count the number of operators in the equation
|
| 154 |
+
function countOperators(tree::Node)::Integer
|
| 155 |
return countUnaryOperators(tree) + countBinaryOperators(tree)
|
| 156 |
end
|
| 157 |
|
|
|
|
| 174 |
end
|
| 175 |
|
| 176 |
# Count the number of constants in an equation
|
| 177 |
+
function countConstants(tree::Node)::Integer
|
| 178 |
if tree.degree == 0
|
| 179 |
+
return convert(Integer, tree.constant)
|
| 180 |
elseif tree.degree == 1
|
| 181 |
return 0 + countConstants(tree.l)
|
| 182 |
else
|
|
|
|
| 186 |
|
| 187 |
# Randomly perturb a constant
|
| 188 |
function mutateConstant(
|
| 189 |
+
tree::Node, T::Float32,
|
| 190 |
+
probNegate::Float32=0.01f0)::Node
|
| 191 |
# T is between 0 and 1.
|
| 192 |
|
| 193 |
if countConstants(tree) == 0
|
|
|
|
| 198 |
node = randomNode(tree)
|
| 199 |
end
|
| 200 |
|
| 201 |
+
bottom = 0.1f0
|
| 202 |
+
maxChange = T + 1.0f0 + bottom
|
| 203 |
+
factor = maxChange^Float32(rand())
|
| 204 |
makeConstBigger = rand() > 0.5
|
| 205 |
|
| 206 |
if makeConstBigger
|
|
|
|
| 219 |
# Evaluate an equation over an array of datapoints
|
| 220 |
function evalTreeArray(
|
| 221 |
tree::Node,
|
| 222 |
+
x::Array{Float32, 2})::Array{Float32, 1}
|
| 223 |
return mapslices(
|
| 224 |
+
(cx,) -> evalTree(tree, cx),
|
| 225 |
+
x,
|
| 226 |
+
dims=[2]
|
| 227 |
+
)[:, 1]
|
| 228 |
end
|
| 229 |
|
| 230 |
# Sum of square error between two arrays
|
| 231 |
+
function SSE(x::Array{Float32}, y::Array{Float32})::Float32
|
| 232 |
return sum(((cx,)->cx^2).(x - y))
|
| 233 |
end
|
| 234 |
|
| 235 |
# Mean of square error between two arrays
|
| 236 |
+
function MSE(x::Array{Float32}, y::Array{Float32})::Float32
|
| 237 |
return SSE(x, y)/size(x)[1]
|
| 238 |
end
|
| 239 |
|
| 240 |
# Score an equation
|
| 241 |
function scoreFunc(
|
| 242 |
tree::Node,
|
| 243 |
+
X::Array{Float32, 2},
|
| 244 |
+
y::Array{Float32, 1},
|
| 245 |
+
parsimony::Float32=0.1f0)::Float32
|
| 246 |
+
try
|
| 247 |
+
return MSE(evalTreeArray(tree, X), y) + countNodes(tree)*parsimony
|
| 248 |
+
catch error
|
| 249 |
+
return 1f9
|
| 250 |
+
end
|
| 251 |
end
|
| 252 |
|
| 253 |
# Add a random unary/binary operation to the end of a tree
|
|
|
|
| 260 |
choice = rand()
|
| 261 |
makeNewBinOp = choice < nbin/nops
|
| 262 |
if rand() > 0.5
|
| 263 |
+
left = Float32(randn())
|
| 264 |
else
|
| 265 |
left = rand(1:nvar)
|
| 266 |
end
|
| 267 |
if rand() > 0.5
|
| 268 |
+
right = Float32(randn())
|
| 269 |
else
|
| 270 |
right = rand(1:nvar)
|
| 271 |
end
|
|
|
|
| 297 |
node = randomNode(tree)
|
| 298 |
# Can "delete" variable or constant too
|
| 299 |
if rand() > 0.5
|
| 300 |
+
val = Float32(randn())
|
| 301 |
else
|
| 302 |
val = rand(1:nvar)
|
| 303 |
end
|
|
|
|
| 314 |
# Go through one simulated annealing mutation cycle
|
| 315 |
# exp(-delta/T) defines probability of accepting a change
|
| 316 |
function iterate(
|
| 317 |
+
tree::Node, T::Float32,
|
| 318 |
+
X::Array{Float32, 2}, y::Array{Float32, 1},
|
| 319 |
+
alpha::Float32=1.0f0,
|
| 320 |
+
mult::Float32=0.1f0
|
| 321 |
)::Node
|
| 322 |
prev = deepcopy(tree)
|
| 323 |
|
|
|
|
| 361 |
end
|
| 362 |
|
| 363 |
# Create a random equation by appending random operators
|
| 364 |
+
function genRandomTree(length::Integer)::Node
|
| 365 |
+
tree = Node(1.0f0)
|
| 366 |
for i=1:length
|
| 367 |
tree = appendRandomOp(tree)
|
| 368 |
end
|
|
|
|
| 373 |
# Define a member of population by equation, score, and age
|
| 374 |
mutable struct PopMember
|
| 375 |
tree::Node
|
| 376 |
+
score::Float32
|
| 377 |
+
birth::Float32
|
| 378 |
|
| 379 |
+
PopMember(t) = new(t, scoreFunc(t, X, y, parsimony), Float32(time())-1.6f9)
|
| 380 |
end
|
| 381 |
|
| 382 |
# A list of members of the population, with easy constructors,
|
| 383 |
# which allow for random generation of new populations
|
| 384 |
mutable struct Population
|
| 385 |
members::Array{PopMember, 1}
|
| 386 |
+
n::Integer
|
| 387 |
|
| 388 |
Population(pop::Array{PopMember, 1}) = new(pop, size(pop)[1])
|
| 389 |
+
Population(npop::Integer) = new([PopMember(genRandomTree(3)) for i=1:npop], npop)
|
| 390 |
+
Population(npop::Integer, nlength::Integer) = new([PopMember(genRandomTree(nlength)) for i=1:npop], npop)
|
| 391 |
|
| 392 |
end
|
| 393 |
|
|
|
|
| 411 |
end
|
| 412 |
|
| 413 |
# Mutate the best sampled member of the population
|
| 414 |
+
function iterateSample(pop::Population, T::Float32)::PopMember
|
| 415 |
allstar = bestOfSample(pop)
|
| 416 |
new = iterate(allstar.tree, T, X, y, alpha, parsimony)
|
| 417 |
allstar.tree = new
|
| 418 |
allstar.score = scoreFunc(new, X, y, parsimony)
|
| 419 |
+
allstar.birth = Float32(time()) - 1.6f9
|
| 420 |
return allstar
|
| 421 |
end
|
| 422 |
|
| 423 |
# Pass through the population several times, replacing the oldest
|
| 424 |
# with the fittest of a small subsample
|
| 425 |
+
function regEvolCycle(pop::Population, T::Float32)::Population
|
| 426 |
+
for i=1:Integer(pop.n/ns)
|
| 427 |
baby = iterateSample(pop, T)
|
| 428 |
#printTree(baby.tree)
|
| 429 |
oldest = argmin([pop.members[member].birth for member=1:pop.n])
|
|
|
|
| 436 |
# printing the fittest equation every 10% through
|
| 437 |
function run(
|
| 438 |
pop::Population,
|
| 439 |
+
ncycles::Integer,
|
| 440 |
annealing::Bool=false;
|
| 441 |
+
verbose::Integer=0
|
| 442 |
)::Population
|
| 443 |
pop = deepcopy(pop)
|
| 444 |
|
| 445 |
+
allT = LinRange(1.0f0, 0.0f0, ncycles)
|
| 446 |
for iT in 1:size(allT)[1]
|
| 447 |
if annealing
|
| 448 |
pop = regEvolCycle(pop, allT[iT])
|
| 449 |
else
|
| 450 |
+
pop = regEvolCycle(pop, 1.0f0)
|
| 451 |
end
|
| 452 |
if verbose > 0 && (iT % verbose == 0)
|
| 453 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|
paralleleureqa.jl
CHANGED
|
@@ -17,7 +17,7 @@ for k=1:niterations
|
|
| 17 |
|
| 18 |
# Spawn threads to run indepdent evolutions, then gather them
|
| 19 |
@inbounds Threads.@threads for i=1:nthreads
|
| 20 |
-
allPops[i] = run(allPops[i], ncyclesperiteration, annealing, verbose=
|
| 21 |
end
|
| 22 |
|
| 23 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|
|
|
|
| 17 |
|
| 18 |
# Spawn threads to run indepdent evolutions, then gather them
|
| 19 |
@inbounds Threads.@threads for i=1:nthreads
|
| 20 |
+
allPops[i] = run(allPops[i], ncyclesperiteration, annealing, verbose=500)
|
| 21 |
end
|
| 22 |
|
| 23 |
# Get best 10 models from each evolution. Copy because we re-assign later.
|