Open cmd-ntrf opened 10 years ago
The current documentation clearly suggests using weights to vary the importance of each objective one against another. This is very misleading with selection operators that treat the fitness vector in a lexographic fashion.
Just stopping by to say I agree with @kanosek and the original filer of this ticket.
Take for example selBest
vs. selTournament
.
selBest
uses sorted
with the reverse=True
argument. However, selTournament
uses max
.
This can lead to tricky behavior (note I'm using deap v1.3.0):
I would like to use deap to create a GA in which my weights represent, well, the weights of my objectives. All my weights will be negative, indicating I want the smallest possible value for each objective. So, I went ahead and subclassed base.Fitness like so to get around the lexicographical sorting (which doesn't meet my use case):
my_module.py
:
from deap import base
class MultiMinFitness(base.Fitness):
"""Compare with sum of wvalues. Smaller (more negative) is better."""
def __le__(self, other):
return sum(self.wvalues) <= sum(other.values)
def __lt__(self, other):
return sum(self.wvalues) < sum(other.values)
Now, if I create some unittests to ensure my subclass is behaving correctly, we get inconsistent results between selBest
and selTournament
:
test_my_module.py
:
import unittest
from unittest.mock import patch
from .my_module import MultiMinFitness
from deap import creator, tools
class MultiFitnessTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Use the deap creator to set things up."""
creator.create('FitnessMin', MultiMinFitness, weights=(-2, -4, -8))
def test_selection(self):
class MyClass:
"""Stub in a class to assign fitnesses to."""
def __init__(self, f):
self.fitness = f
def __repr__(self):
return str('MyClass: ' + str(self.fitness))
# Initialize some fitness objects. Note that f2 is "more fit"
# since the weighted sum is "more negative"
f1 = creator.FitnessMin((1, 2, 3))
f2 = creator.FitnessMin((2, 4, 6))
# Create a couple class members.
c1 = MyClass(f1)
c2 = MyClass(f2)
# Use selBest to ensure c2 (and thus f2) is best.
best = tools.selBest([c1, c2], k=2)
# c2 is the 'most negative' and therefore the best.
self.assertIs(best[0], c2) # Succeeds
# Now perform a tournament selection. Patch selRandom so it just
# returns our list of MyClass objects.
with patch('deap.tools.selection.selRandom', return_value=[c1, c2]):
best_tourn = tools.selTournament([c1, c2], k=1, tournsize=2)
self.assertIs(best_tourn[0], c2) # Fails.
Results of running test_my_module.py
:
Failure
Traceback (most recent call last):
File "/usr/local/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
yield
File "/usr/local/lib/python3.7/unittest/case.py", line 615, in run
testMethod()
File "/opt/project/tmp/test_my_module.py", line 39, in test_selection
self.assertIs(best_tourn[0], c2) # Fails.
File "/usr/local/lib/python3.7/unittest/case.py", line 1120, in assertIs
self.fail(self._formatMessage(msg, standardMsg))
File "/usr/local/lib/python3.7/unittest/case.py", line 680, in fail
raise self.failureException(msg)
AssertionError: MyClass: (1.0, 2.0, 3.0) is not MyClass: (2.0, 4.0, 6.0)
Excellent point, thanks for your contribution. We'll consider this as part of the 2.0 refactoring that is underway.
Reported by cgranade, Jun 4 (6 days ago) In the documentation posted at [0], it is not made clear which selection operators are single-objective, which ones treat the fitness vector in a lexographic fashion, and which are useful in MOEA contexts. For instance,
selTournament
seems to act lexographically, as it uses thesorted
built-in with a tuple-valued fitness as the key, but this is not made clear in the documentation at [1].Links: [0] http://deap.gel.ulaval.ca/doc/default/api/tools.html [1] http://deap.gel.ulaval.ca/doc/default/api/tools.html#deap.tools.selTournament