Quick Tutorial
Here is a very basic example just to get a feeling for the package. We set up a simple search for number of fully connected layers and their widths.
using NaiveGAflux, Random
import Flux, Optimisers
import Flux: relu, elu, selu
import Optimisers: Adam
Random.seed!(NaiveGAflux.rng_default, 0)
nlabels = 3
ninputs = 5
Step 1: Create initial models.
Search space: 2-4 dense layers of width 3-10.
layerspace = VertexSpace(DenseSpace(3:10, [identity, relu, elu, selu]))
initial_hidden = RepeatArchSpace(layerspace, 1:3)
Output layer has fixed size and is shielded from mutation.
outlayer = VertexSpace(Shielded(), DenseSpace(nlabels, identity))
initial_searchspace = ArchSpaceChain(initial_hidden, outlayer)
Sample 5 models from the initial search space and make an initial population.
samplemodel(invertex) = CompGraph(invertex, initial_searchspace(invertex))
initial_models = [samplemodel(denseinputvertex("input", ninputs)) for _ in 1:5]
@test nvertices.(initial_models) == [4, 3, 4, 5, 3]
population = Population(CandidateModel.(initial_models))
@test generation(population) == 1
Step 2: Set up fitness function:
Train model for one epoch using datasettrain
, then measure accuracy on datasetvalidate
. We use dummy data here just to make stuff run.
onehot(y) = Flux.onehotbatch(y, 1:nlabels)
batchsize = 4
datasettrain = [(randn(Float32, ninputs, batchsize), onehot(rand(1:nlabels, batchsize)))]
datasetvalidate = [(randn(Float32, ninputs, batchsize), onehot(rand(1:nlabels, batchsize)))]
fitnessfunction = TrainThenFitness(;
dataiter = datasettrain,
defaultloss = Flux.logitcrossentropy, # Will be used if not provided by the candidate
defaultopt = Adam(), # Same as above.
fitstrat = AccuracyFitness(datasetvalidate) # This is what creates our fitness value after training
)
Step 3: Define how to search for new candidates
We choose to evolve the existing ones through mutation.
VertexMutation
selects valid vertices from the graph to mutate. MutationProbability
applies mutation m
with a probability of p
. Lets create a short helper for brevity:
mp(m, p) = VertexMutation(MutationProbability(m, p))
Change size (60% chance) and/or add a layer (40% chance) and/or remove a layer (40% chance). You might want to use lower probabilities than this.
changesize = mp(NoutMutation(-0.2, 0.2), 0.6)
addlayer = mp(AddVertexMutation(layerspace), 0.4)
remlayer = mp(RemoveVertexMutation(), 0.4)
mutation = MutationChain(changesize, remlayer, addlayer)
Selection: The two best models are not changed, then create three new models by applying the mutations above to three of the five models with higher fitness giving higher probability of being selected.
MapCandidate
helps with the plumbing of creating new CandidateModel
s where mutation
is applied to create a new model.
elites = EliteSelection(2)
mutate = SusSelection(3, EvolveCandidates(MapCandidate(mutation)))
selection = CombinedEvolution(elites, mutate)
Step 4: Run evolution
newpopulation = evolve(selection, fitnessfunction, population)
@test newpopulation != population
@test generation(newpopulation) == 2
Repeat until a model with the desired fitness is found.
newnewpopulation = evolve(selection, fitnessfunction, newpopulation)
@test newnewpopulation != newpopulation
@test generation(newnewpopulation) == 3
bestfitness, bestcandnr = findmax(fitness, newnewpopulation)
@test model(newnewpopulation[bestcandnr]) isa CompGraph
Maybe in a loop :)
This page was generated using Literate.jl.