Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Operations #20

Merged
merged 5 commits into from
Jun 14, 2023
Merged

Operations #20

merged 5 commits into from
Jun 14, 2023

Conversation

philswatton
Copy link
Collaborator

@philswatton philswatton commented Jun 14, 2023

This PR adds the two main genetic operations: crossover and mutation.

Crossover works to preserve good 'genes' (pokemon team mebers) by selecting two 'chromosomes' (teams) (with replacement) in proportion to their fitness, then swapping their 'genes' (pokemon).

Mutation works to maintain 'genetic' diversity (pokemon diversity) by occasionally randomly adding members to the teams and removing old ones.

There's been some discussion about how best to do this, so I've made this code as modular and as flexible as possible.

For crossover:

  • Three methods exist.
    • Locus swap picks a point (e.g. 3), then splits both lists in half and recombines their separate halves
    • Slot swap randomly picks indices, then swaps those indices between the two lists. I realised it's essentially equivalent to locus swap as the lists are unordered, but we move ¯_(ツ)_/¯
    • Sample swap turns the two lists into a population, and then samples from it. Can be done with or without replacement. With replacement is the interesting option (but is still parameterised, as at that point it's going to produce different results to the other two
  • A crossover function can be constructed as follows (using locus_swap as an example):
from p2lab.genetic.operations import build_crossover_fn, locus_swap

crossover = build_crossover_fn(crossover_method = locus_swap, locus=3)
  • the three different crossover methods (locus_swap, slot_swap, and sample_swap all have their own kwargs.

For mutation:

  • two mutation functions have been implemented in light of some of the discussions in the team re how to best implement and optimise 1v1, 2v2, 6v6, etc
  • the first, mutate, is defined by a global mutation probability
  • the second, fitness_mutate, uses the inverse of the fitness scores. I'm not 100% satisfied by this, as as the number of teams increases, the mutation chance for each decreases (although this may be a good thing thinking about it)
  • I've updated genetic_team in genetic.py to take a crossover function as input, along with some mutation and crossover-related params
  • when using fitness_mutate (defined by the mutate_with_fitness argument in the genetic_team function above), the crossover step will be skipped. This is because post-crossover/mutation, and previous fitness scores will be invalid

I've also updated the win_percentages fitness function to standardise the fitnesses to sum to 1.

Some additional thoughts based on some discussion earlier today and in other PRs:

  • For 2v2, I suspect having a low crossover probability may be preferable to mutating inversely to the fitness function. Crossovers role is to pass on good genes, but since there's an associated probability of simply passing on the full teams, this will better allow good genes to pass on instead of relying on mutation alone (which is random as to the genes it brings into the population)
  • EDIT: the same doesn't hold for 1v1 as obviously crossover stops being meaningful - removed a comment where I thought it did!
    • last edit, but maybe this is worth taking back - 'crossover' here really combines two operators from the literature: selection and crossover. The former means higher fitness increases probability of selection into the next generation. Crossover is the breeding part. We still probably want selection to occur! Will add a selection-only crossover function for this
  • I don't think we need all vs all for 1v1 if the BTmodel is used as the fitness function, as the quality of the opponent is taken into account (so long as enough matches occur and all clusters of matches are sufficently connected to each other). This should help to reduce runtime if needed

@philswatton
Copy link
Collaborator Author

I should add: I've done a little testing with these but not much as it's getting late in the day to be working. Will test some more tomorrow!

@philswatton philswatton requested review from phinate and boykovdn and removed request for phinate June 14, 2023 17:32
@philswatton
Copy link
Collaborator Author

Also, just realised i forgot to add the ability to turn mutation off completely. Will add it tomorrow!

@phinate
Copy link
Collaborator

phinate commented Jun 14, 2023

Thanks a lot for this! I see the general idea -- my plan is to merge this in its current state so I can resolve some conflicts with the other PRs (somewhere along the line, your work wasn't rebased into other branches, so I'm taking this to be the most up-to-date stuff!)

This should be a good skeleton for me to design some functions based on the previous work :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants