alan-turing-institute / p2lab-pokemon

A Python library for running genetic algorithms to optimize Pokemon teams!
BSD 3-Clause "New" or "Revised" License
8 stars 1 forks source link

Operations #20

Closed philswatton closed 1 year ago

philswatton commented 1 year ago

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:

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 commented 1 year ago

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 commented 1 year ago

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

phinate commented 1 year ago

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 :)