eckity-dts 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eckity_dts-0.1.0/LICENSE +29 -0
- eckity_dts-0.1.0/PKG-INFO +167 -0
- eckity_dts-0.1.0/README.md +136 -0
- eckity_dts-0.1.0/deep_tournament_selection/__init__.py +4 -0
- eckity_dts-0.1.0/deep_tournament_selection/caching_evaluator.py +36 -0
- eckity_dts-0.1.0/deep_tournament_selection/config.py +99 -0
- eckity_dts-0.1.0/deep_tournament_selection/elitist_breeder.py +28 -0
- eckity_dts-0.1.0/deep_tournament_selection/logging_utils.py +64 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/__init__.py +4 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/dts_aux.py +96 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/dts_policy.py +322 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/eckity_adapter.py +36 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/population_to_vec_transformer.py +135 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/self_attention_pointer.py +103 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/simple_self_attention.py +37 -0
- eckity_dts-0.1.0/deep_tournament_selection/selection/tournament_utils.py +61 -0
- eckity_dts-0.1.0/eckity_dts/__init__.py +5 -0
- eckity_dts-0.1.0/eckity_dts.egg-info/PKG-INFO +167 -0
- eckity_dts-0.1.0/eckity_dts.egg-info/SOURCES.txt +23 -0
- eckity_dts-0.1.0/eckity_dts.egg-info/dependency_links.txt +1 -0
- eckity_dts-0.1.0/eckity_dts.egg-info/requires.txt +11 -0
- eckity_dts-0.1.0/eckity_dts.egg-info/top_level.txt +2 -0
- eckity_dts-0.1.0/pyproject.toml +63 -0
- eckity_dts-0.1.0/setup.cfg +4 -0
- eckity_dts-0.1.0/tests/test_package.py +117 -0
eckity_dts-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Eliad Shem-Tov, Ron Edri, Achiya Elyasaf and the EC-KitY project
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eckity-dts
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deep Tournament Selection operator for EC-KitY genetic algorithms
|
|
5
|
+
Author: Eliad Shem-Tov, Ron Edri, Achiya Elyasaf
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/EC-KitY/DTS-for-GA
|
|
8
|
+
Project-URL: Repository, https://github.com/EC-KitY/DTS-for-GA
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: eckity~=0.4.1
|
|
21
|
+
Requires-Dist: numpy>=2.0.2
|
|
22
|
+
Requires-Dist: overrides>=7.7.0
|
|
23
|
+
Requires-Dist: scipy>=1.13.0
|
|
24
|
+
Requires-Dist: torch>=2.7.1
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
29
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Deep Tournament Selection for EC-KitY
|
|
33
|
+
|
|
34
|
+
`eckity-dts` provides Deep Tournament Selection (DTS), a learned selection operator for genetic algorithms built on [EC-KitY](https://github.com/EC-KitY/EC-KitY).
|
|
35
|
+
|
|
36
|
+
DTS uses a Transformer encoder and a self-attention pointer network trained online with REINFORCE. It was introduced in **“Deep Tournament Selection for Genetic Algorithms”** by Eliad Shem-Tov, Ron Edri, and Achiya Elyasaf. The paper has not yet been published; a formal citation will be added when available.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install eckity-dts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Public API
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from eckity_dts import CachingEvaluator, DeepTournamentSelection, DTSPolicy
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Constructing DTS
|
|
51
|
+
|
|
52
|
+
The policy combines a population encoder with a pointer network:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from eckity_dts import DeepTournamentSelection, DTSPolicy
|
|
56
|
+
from deep_tournament_selection.selection.population_to_vec_transformer import (
|
|
57
|
+
PopulationToVecTransformer,
|
|
58
|
+
)
|
|
59
|
+
from deep_tournament_selection.selection.self_attention_pointer import (
|
|
60
|
+
SelfAttentionPointer,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
population_size = 100
|
|
64
|
+
vocab_size = 2 # maximum gene value + 1
|
|
65
|
+
|
|
66
|
+
encoder = PopulationToVecTransformer(
|
|
67
|
+
vocab_size=vocab_size,
|
|
68
|
+
emb_dim=32,
|
|
69
|
+
latent_dim=32,
|
|
70
|
+
n_heads=4,
|
|
71
|
+
n_layers=2,
|
|
72
|
+
dim_feedforward=256,
|
|
73
|
+
max_pointers=population_size,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
pointer = SelfAttentionPointer(
|
|
77
|
+
pointer_len=population_size,
|
|
78
|
+
d_model=32,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
policy = DTSPolicy(
|
|
82
|
+
pop_to_vec_transformer=encoder,
|
|
83
|
+
pointer_transformer=pointer,
|
|
84
|
+
device="cpu",
|
|
85
|
+
train_every_n_gens=10,
|
|
86
|
+
learning_rate=2e-3,
|
|
87
|
+
final_lr=1e-3,
|
|
88
|
+
epsilon_greedy=1.0,
|
|
89
|
+
epsilon_greedy_decay=0.999,
|
|
90
|
+
min_epsilon=0.2,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
dts = DeepTournamentSelection(policy, higher_is_better=True)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Use it as the EC-KitY selection method:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
selection_methods=[(dts, 1)]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Important parameters:
|
|
103
|
+
|
|
104
|
+
- `population_size` determines the rank-embedding and pointer-table capacities.
|
|
105
|
+
- `vocab_size` is the maximum integer gene value plus one.
|
|
106
|
+
- `train_every_n_gens` controls how often accumulated trajectories train the policy.
|
|
107
|
+
- `epsilon_greedy` and its decay control teacher-forced tournament selection versus learned selection.
|
|
108
|
+
- `custom_reward_function`, when supplied, receives current fitness, previous fitness, and population arrays.
|
|
109
|
+
- `device` can be `"cpu"` or `"cuda"` when a compatible PyTorch installation is available.
|
|
110
|
+
|
|
111
|
+
## Fitness caching
|
|
112
|
+
|
|
113
|
+
EC-KitY may evaluate unchanged vectors again across generations. `CachingEvaluator` avoids recomputing fitness for vectors it has already seen:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from eckity_dts import CachingEvaluator
|
|
117
|
+
|
|
118
|
+
evaluator = CachingEvaluator(MyEvaluator())
|
|
119
|
+
print(evaluator.cache_stats())
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Compatibility
|
|
123
|
+
|
|
124
|
+
- Python 3.9 or newer
|
|
125
|
+
- EC-KitY 0.4.x
|
|
126
|
+
- NumPy 2.0.2 or newer
|
|
127
|
+
- SciPy 1.13.0 or newer
|
|
128
|
+
- PyTorch 2.7.1 or newer
|
|
129
|
+
- overrides 7.7.0 or newer
|
|
130
|
+
|
|
131
|
+
These bounds are compatible with `eckity-dnc`, `eckity-bert-ga`, and `eckity-bert-gp`. None of the operator packages depends directly on another operator package.
|
|
132
|
+
|
|
133
|
+
## Research repository
|
|
134
|
+
|
|
135
|
+
The repository contains the full paper experiments for Graph Coloring, Set Cover, and TSP, together with benchmark instances, notebook-style runners, and result figures. These research resources are not included in the `eckity-dts` wheel.
|
|
136
|
+
|
|
137
|
+
For repository development:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
uv sync --extra dev --resolution lowest-direct
|
|
141
|
+
uv run pytest
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Experiment entry points remain available from a source checkout:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python -m deep_tournament_selection.experiments.graph_coloring --instance queen8_12.col.txt --generations 200
|
|
148
|
+
python -m deep_tournament_selection.experiments.set_cover --instance scp41.txt --generations 200
|
|
149
|
+
python -m deep_tournament_selection.experiments.tsp --instance att48.tsp --generations 200
|
|
150
|
+
python run_experiments.py --selection both --runs 3 --generations 500
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Results are written under `runs/`. Paper figures are stored under `figures/`, and the architecture diagram is under `images/`.
|
|
154
|
+
|
|
155
|
+
## Development and release
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
uv run pytest
|
|
159
|
+
uv run ruff check .
|
|
160
|
+
uv build
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Release preparation and manual PyPI upload commands are documented in [`RELEASING.md`](RELEASING.md).
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
This project is licensed under the BSD 3-Clause License. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Deep Tournament Selection for EC-KitY
|
|
2
|
+
|
|
3
|
+
`eckity-dts` provides Deep Tournament Selection (DTS), a learned selection operator for genetic algorithms built on [EC-KitY](https://github.com/EC-KitY/EC-KitY).
|
|
4
|
+
|
|
5
|
+
DTS uses a Transformer encoder and a self-attention pointer network trained online with REINFORCE. It was introduced in **“Deep Tournament Selection for Genetic Algorithms”** by Eliad Shem-Tov, Ron Edri, and Achiya Elyasaf. The paper has not yet been published; a formal citation will be added when available.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install eckity-dts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Public API
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from eckity_dts import CachingEvaluator, DeepTournamentSelection, DTSPolicy
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Constructing DTS
|
|
20
|
+
|
|
21
|
+
The policy combines a population encoder with a pointer network:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from eckity_dts import DeepTournamentSelection, DTSPolicy
|
|
25
|
+
from deep_tournament_selection.selection.population_to_vec_transformer import (
|
|
26
|
+
PopulationToVecTransformer,
|
|
27
|
+
)
|
|
28
|
+
from deep_tournament_selection.selection.self_attention_pointer import (
|
|
29
|
+
SelfAttentionPointer,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
population_size = 100
|
|
33
|
+
vocab_size = 2 # maximum gene value + 1
|
|
34
|
+
|
|
35
|
+
encoder = PopulationToVecTransformer(
|
|
36
|
+
vocab_size=vocab_size,
|
|
37
|
+
emb_dim=32,
|
|
38
|
+
latent_dim=32,
|
|
39
|
+
n_heads=4,
|
|
40
|
+
n_layers=2,
|
|
41
|
+
dim_feedforward=256,
|
|
42
|
+
max_pointers=population_size,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
pointer = SelfAttentionPointer(
|
|
46
|
+
pointer_len=population_size,
|
|
47
|
+
d_model=32,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
policy = DTSPolicy(
|
|
51
|
+
pop_to_vec_transformer=encoder,
|
|
52
|
+
pointer_transformer=pointer,
|
|
53
|
+
device="cpu",
|
|
54
|
+
train_every_n_gens=10,
|
|
55
|
+
learning_rate=2e-3,
|
|
56
|
+
final_lr=1e-3,
|
|
57
|
+
epsilon_greedy=1.0,
|
|
58
|
+
epsilon_greedy_decay=0.999,
|
|
59
|
+
min_epsilon=0.2,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
dts = DeepTournamentSelection(policy, higher_is_better=True)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use it as the EC-KitY selection method:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
selection_methods=[(dts, 1)]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Important parameters:
|
|
72
|
+
|
|
73
|
+
- `population_size` determines the rank-embedding and pointer-table capacities.
|
|
74
|
+
- `vocab_size` is the maximum integer gene value plus one.
|
|
75
|
+
- `train_every_n_gens` controls how often accumulated trajectories train the policy.
|
|
76
|
+
- `epsilon_greedy` and its decay control teacher-forced tournament selection versus learned selection.
|
|
77
|
+
- `custom_reward_function`, when supplied, receives current fitness, previous fitness, and population arrays.
|
|
78
|
+
- `device` can be `"cpu"` or `"cuda"` when a compatible PyTorch installation is available.
|
|
79
|
+
|
|
80
|
+
## Fitness caching
|
|
81
|
+
|
|
82
|
+
EC-KitY may evaluate unchanged vectors again across generations. `CachingEvaluator` avoids recomputing fitness for vectors it has already seen:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from eckity_dts import CachingEvaluator
|
|
86
|
+
|
|
87
|
+
evaluator = CachingEvaluator(MyEvaluator())
|
|
88
|
+
print(evaluator.cache_stats())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Compatibility
|
|
92
|
+
|
|
93
|
+
- Python 3.9 or newer
|
|
94
|
+
- EC-KitY 0.4.x
|
|
95
|
+
- NumPy 2.0.2 or newer
|
|
96
|
+
- SciPy 1.13.0 or newer
|
|
97
|
+
- PyTorch 2.7.1 or newer
|
|
98
|
+
- overrides 7.7.0 or newer
|
|
99
|
+
|
|
100
|
+
These bounds are compatible with `eckity-dnc`, `eckity-bert-ga`, and `eckity-bert-gp`. None of the operator packages depends directly on another operator package.
|
|
101
|
+
|
|
102
|
+
## Research repository
|
|
103
|
+
|
|
104
|
+
The repository contains the full paper experiments for Graph Coloring, Set Cover, and TSP, together with benchmark instances, notebook-style runners, and result figures. These research resources are not included in the `eckity-dts` wheel.
|
|
105
|
+
|
|
106
|
+
For repository development:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv sync --extra dev --resolution lowest-direct
|
|
110
|
+
uv run pytest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Experiment entry points remain available from a source checkout:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
python -m deep_tournament_selection.experiments.graph_coloring --instance queen8_12.col.txt --generations 200
|
|
117
|
+
python -m deep_tournament_selection.experiments.set_cover --instance scp41.txt --generations 200
|
|
118
|
+
python -m deep_tournament_selection.experiments.tsp --instance att48.tsp --generations 200
|
|
119
|
+
python run_experiments.py --selection both --runs 3 --generations 500
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Results are written under `runs/`. Paper figures are stored under `figures/`, and the architecture diagram is under `images/`.
|
|
123
|
+
|
|
124
|
+
## Development and release
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv run pytest
|
|
128
|
+
uv run ruff check .
|
|
129
|
+
uv build
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Release preparation and manual PyPI upload commands are documented in [`RELEASING.md`](RELEASING.md).
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
This project is licensed under the BSD 3-Clause License. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from eckity.evaluators.simple_individual_evaluator import SimpleIndividualEvaluator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CachingEvaluator(SimpleIndividualEvaluator):
|
|
6
|
+
def __init__(self, inner: SimpleIndividualEvaluator, max_size=None, events=None):
|
|
7
|
+
super().__init__(events=events)
|
|
8
|
+
self.inner = inner
|
|
9
|
+
self.max_size = max_size
|
|
10
|
+
self.fitness_cache = OrderedDict()
|
|
11
|
+
self.hits = 0
|
|
12
|
+
self.misses = 0
|
|
13
|
+
|
|
14
|
+
def evaluate_individual(self, individual):
|
|
15
|
+
key = tuple(individual.vector)
|
|
16
|
+
if key in self.fitness_cache:
|
|
17
|
+
self.hits += 1
|
|
18
|
+
self.fitness_cache.move_to_end(key)
|
|
19
|
+
return self.fitness_cache[key]
|
|
20
|
+
|
|
21
|
+
self.misses += 1
|
|
22
|
+
value = self.inner.evaluate_individual(individual)
|
|
23
|
+
self.fitness_cache[key] = value
|
|
24
|
+
if self.max_size is not None and len(self.fitness_cache) > self.max_size:
|
|
25
|
+
self.fitness_cache.popitem(last=False)
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
def cache_stats(self):
|
|
29
|
+
total = self.hits + self.misses
|
|
30
|
+
hit_rate = self.hits / total if total else 0.0
|
|
31
|
+
return {
|
|
32
|
+
"hits": self.hits,
|
|
33
|
+
"misses": self.misses,
|
|
34
|
+
"size": len(self.fitness_cache),
|
|
35
|
+
"hit_rate": hit_rate,
|
|
36
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class DTSConfig:
|
|
7
|
+
"""Hyperparameters for the Deep Tournament Selection operator (paper setting)."""
|
|
8
|
+
|
|
9
|
+
tournament_size: int = 5
|
|
10
|
+
latent_dim: int = 32
|
|
11
|
+
dim_feedforward: int = 256
|
|
12
|
+
n_layers: int = 2
|
|
13
|
+
n_heads: int = 4
|
|
14
|
+
learning_rate: float = 2e-3
|
|
15
|
+
final_lr: float = 1e-3
|
|
16
|
+
train_every_n_gens: int = 10
|
|
17
|
+
epsilon_greedy: float = 1.0
|
|
18
|
+
epsilon_greedy_decay: float = 0.999
|
|
19
|
+
min_epsilon: float = 0.2
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
POPULATION_SIZE = 100
|
|
23
|
+
ELITISM = 2
|
|
24
|
+
CROSSOVER_PROB = 0.5
|
|
25
|
+
MUTATION_PROB = 0.5
|
|
26
|
+
FLIP_MUTATION_PROB = 0.1
|
|
27
|
+
RUNS = 15
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class TSPConfig:
|
|
32
|
+
instances: List[str] = field(
|
|
33
|
+
default_factory=lambda: [
|
|
34
|
+
"att48.tsp",
|
|
35
|
+
"berlin52.tsp",
|
|
36
|
+
"st70.tsp",
|
|
37
|
+
"lin105.tsp",
|
|
38
|
+
"pr107.tsp",
|
|
39
|
+
"pcb442.tsp",
|
|
40
|
+
"d1291.tsp",
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
population_size: int = POPULATION_SIZE
|
|
44
|
+
generations: int = 1000
|
|
45
|
+
crossover_prob: float = CROSSOVER_PROB
|
|
46
|
+
mutation_prob: float = MUTATION_PROB
|
|
47
|
+
elitism: int = ELITISM
|
|
48
|
+
runs: int = RUNS
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class GraphColoringConfig:
|
|
53
|
+
instances: List[str] = field(
|
|
54
|
+
default_factory=lambda: [
|
|
55
|
+
"instances_games120.col.txt",
|
|
56
|
+
"instances_myciel7.col.txt",
|
|
57
|
+
"miles1000.col.txt",
|
|
58
|
+
"miles1500.col.txt",
|
|
59
|
+
"mulsol.i.2.col",
|
|
60
|
+
"queen8_12.col.txt",
|
|
61
|
+
"zeroin.i.1.col",
|
|
62
|
+
"zeroin.i.2.col",
|
|
63
|
+
]
|
|
64
|
+
)
|
|
65
|
+
population_size: int = POPULATION_SIZE
|
|
66
|
+
generations: int = 6000
|
|
67
|
+
crossover_prob: float = CROSSOVER_PROB
|
|
68
|
+
mutation_prob: float = MUTATION_PROB
|
|
69
|
+
flip_mutation_prob: float = FLIP_MUTATION_PROB
|
|
70
|
+
penalty: float = 100.0
|
|
71
|
+
colors_margin: int = 10
|
|
72
|
+
elitism: int = ELITISM
|
|
73
|
+
runs: int = RUNS
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class SetCoverConfig:
|
|
78
|
+
instances: List[str] = field(
|
|
79
|
+
default_factory=lambda: [
|
|
80
|
+
"scp41.txt",
|
|
81
|
+
"scp51.txt",
|
|
82
|
+
"scp52.txt",
|
|
83
|
+
"scp53.txt",
|
|
84
|
+
"scp54.txt",
|
|
85
|
+
"scp56.txt",
|
|
86
|
+
"scp57.txt",
|
|
87
|
+
"scp64.txt",
|
|
88
|
+
"scp65.txt",
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
population_size: int = POPULATION_SIZE
|
|
92
|
+
generations: int = 6000
|
|
93
|
+
crossover_prob: float = CROSSOVER_PROB
|
|
94
|
+
mutation_prob: float = MUTATION_PROB
|
|
95
|
+
flip_mutation_prob: float = FLIP_MUTATION_PROB
|
|
96
|
+
penalty: float = 100.0
|
|
97
|
+
weighted: bool = False
|
|
98
|
+
elitism: int = ELITISM
|
|
99
|
+
runs: int = RUNS
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from eckity.breeders.simple_breeder import SimpleBreeder
|
|
2
|
+
from eckity.genetic_operators.selections.elitism_selection import ElitismSelection
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ElitistBreeder(SimpleBreeder):
|
|
6
|
+
def apply_breed(self, population):
|
|
7
|
+
for subpopulation in population.sub_populations:
|
|
8
|
+
nextgen_population = []
|
|
9
|
+
|
|
10
|
+
num_elites = subpopulation.n_elite
|
|
11
|
+
if num_elites > 0:
|
|
12
|
+
ElitismSelection(
|
|
13
|
+
num_elites=num_elites,
|
|
14
|
+
higher_is_better=subpopulation.higher_is_better,
|
|
15
|
+
).apply_operator((subpopulation.individuals, nextgen_population))
|
|
16
|
+
|
|
17
|
+
selection = subpopulation.get_selection_methods()[0][0]
|
|
18
|
+
nextgen_population = selection.select(
|
|
19
|
+
subpopulation.individuals, nextgen_population
|
|
20
|
+
)
|
|
21
|
+
self.selected_individuals = nextgen_population
|
|
22
|
+
|
|
23
|
+
elites = nextgen_population[:num_elites]
|
|
24
|
+
offspring = self._apply_operators(
|
|
25
|
+
subpopulation.get_operators_sequence(),
|
|
26
|
+
nextgen_population[num_elites:],
|
|
27
|
+
)
|
|
28
|
+
subpopulation.individuals = elites + offspring
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from eckity.statistics.statistics import Statistics
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FileLogger(Statistics):
|
|
10
|
+
BASE_KEYS = ("mean", "std", "median", "max", "min", "time")
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
output_path=None,
|
|
15
|
+
save_every_n_generations=50,
|
|
16
|
+
diversity_fn=None,
|
|
17
|
+
format_string=None,
|
|
18
|
+
):
|
|
19
|
+
super().__init__(format_string or "")
|
|
20
|
+
self.output_path = output_path
|
|
21
|
+
self.save_every_n_generations = save_every_n_generations
|
|
22
|
+
self.diversity_fn = diversity_fn
|
|
23
|
+
keys = self.BASE_KEYS + (("population_diversity",) if diversity_fn else ())
|
|
24
|
+
self.generation_metrics = {key: [] for key in keys}
|
|
25
|
+
self._last_time = time.time()
|
|
26
|
+
|
|
27
|
+
def write_statistics(self, sender, data_dict):
|
|
28
|
+
sub_pop = data_dict["population"].sub_populations[0]
|
|
29
|
+
fitness_values = np.array(
|
|
30
|
+
[ind.get_pure_fitness() for ind in sub_pop.individuals], dtype=float
|
|
31
|
+
)
|
|
32
|
+
self.generation_metrics["mean"].append(float(np.mean(fitness_values)))
|
|
33
|
+
self.generation_metrics["std"].append(float(np.std(fitness_values)))
|
|
34
|
+
self.generation_metrics["median"].append(float(np.median(fitness_values)))
|
|
35
|
+
self.generation_metrics["max"].append(float(np.max(fitness_values)))
|
|
36
|
+
self.generation_metrics["min"].append(float(np.min(fitness_values)))
|
|
37
|
+
|
|
38
|
+
if self.diversity_fn is not None:
|
|
39
|
+
population = np.array([ind.vector for ind in sub_pop.individuals])
|
|
40
|
+
self.generation_metrics["population_diversity"].append(
|
|
41
|
+
float(self.diversity_fn(population))
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
now = time.time()
|
|
45
|
+
self.generation_metrics["time"].append(now - self._last_time)
|
|
46
|
+
self._last_time = now
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
self.output_path
|
|
50
|
+
and self.save_every_n_generations
|
|
51
|
+
and data_dict["generation_num"] % self.save_every_n_generations == 0
|
|
52
|
+
):
|
|
53
|
+
self.save()
|
|
54
|
+
|
|
55
|
+
def save(self, extra=None):
|
|
56
|
+
if self.output_path is None:
|
|
57
|
+
return None
|
|
58
|
+
os.makedirs(os.path.dirname(os.path.abspath(self.output_path)), exist_ok=True)
|
|
59
|
+
payload = dict(self.generation_metrics)
|
|
60
|
+
if extra:
|
|
61
|
+
payload.update(extra)
|
|
62
|
+
with open(self.output_path, "w") as f:
|
|
63
|
+
json.dump(payload, f, indent=2)
|
|
64
|
+
return self.output_path
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_ranks_from_fitness_values(fitness_values: np.ndarray):
|
|
8
|
+
order = np.argsort(-fitness_values, axis=1)
|
|
9
|
+
ranks = np.argsort(order, axis=1)
|
|
10
|
+
return ranks
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_ranking_based_rewards(fitness_values: np.ndarray, gamma=1.25):
|
|
14
|
+
fitness_values = fitness_values.reshape(1, -1)
|
|
15
|
+
ranks = get_ranks_from_fitness_values(fitness_values)
|
|
16
|
+
ranks_weights = 1 / (ranks + 1) ** gamma
|
|
17
|
+
rewards = ranks_weights * fitness_values
|
|
18
|
+
rewards = rewards.sum(axis=1)
|
|
19
|
+
return rewards
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_exponential_ranking_based_rewards(fitness_values: np.ndarray, alpha=0.2):
|
|
23
|
+
fitness_values = fitness_values.reshape(1, -1)
|
|
24
|
+
ranks = get_ranks_from_fitness_values(fitness_values)
|
|
25
|
+
ranks_weights = np.exp(-alpha * ranks)
|
|
26
|
+
rewards = ranks_weights * fitness_values
|
|
27
|
+
rewards = rewards.sum(axis=1)
|
|
28
|
+
return rewards
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_average_pairwise_hamming_distance(population: np.ndarray) -> float:
|
|
32
|
+
population = np.asarray(population)
|
|
33
|
+
|
|
34
|
+
if population.ndim != 2 or len(population) < 2:
|
|
35
|
+
return 0.0
|
|
36
|
+
|
|
37
|
+
diffs = population[:, None, :] != population[None, :, :]
|
|
38
|
+
pairwise_dist = diffs.sum(axis=2)
|
|
39
|
+
triu_indices = np.triu_indices(len(population), k=1)
|
|
40
|
+
return float(pairwise_dist[triu_indices].mean() / population.shape[1])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_reward_from_fitness_scores(
|
|
44
|
+
cur_gen_fitness_values: np.ndarray,
|
|
45
|
+
prev_gen_fitness_values: np.ndarray,
|
|
46
|
+
population: np.ndarray | None = None,
|
|
47
|
+
top_k_to_consider: int = 5,
|
|
48
|
+
best_weight: float = 0.25,
|
|
49
|
+
top_k_weight: float = 1.0,
|
|
50
|
+
mean_weight: float = 0.25,
|
|
51
|
+
diversity_weight: float = 0.05,
|
|
52
|
+
eps: float = 1e-8,
|
|
53
|
+
):
|
|
54
|
+
cur_gen_fitness_values = np.asarray(cur_gen_fitness_values, dtype=np.float64)
|
|
55
|
+
prev_gen_fitness_values = np.asarray(prev_gen_fitness_values, dtype=np.float64)
|
|
56
|
+
|
|
57
|
+
k = min(
|
|
58
|
+
top_k_to_consider, len(cur_gen_fitness_values), len(prev_gen_fitness_values)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def normalized_delta(cur_value: float, prev_value: float) -> float:
|
|
62
|
+
scale = max(abs(prev_value), eps)
|
|
63
|
+
return (cur_value - prev_value) / scale
|
|
64
|
+
|
|
65
|
+
cur_top_k_mean = np.partition(cur_gen_fitness_values, -k)[-k:].mean()
|
|
66
|
+
prev_top_k_mean = np.partition(prev_gen_fitness_values, -k)[-k:].mean()
|
|
67
|
+
|
|
68
|
+
cur_best = cur_gen_fitness_values.max()
|
|
69
|
+
prev_best = prev_gen_fitness_values.max()
|
|
70
|
+
|
|
71
|
+
cur_mean = cur_gen_fitness_values.mean()
|
|
72
|
+
prev_mean = prev_gen_fitness_values.mean()
|
|
73
|
+
|
|
74
|
+
diversity_reward = 0.0
|
|
75
|
+
if population is not None:
|
|
76
|
+
diversity_reward = get_average_pairwise_hamming_distance(population)
|
|
77
|
+
|
|
78
|
+
reward = (
|
|
79
|
+
top_k_weight * normalized_delta(cur_top_k_mean, prev_top_k_mean)
|
|
80
|
+
+ best_weight * normalized_delta(cur_best, prev_best)
|
|
81
|
+
+ mean_weight * normalized_delta(cur_mean, prev_mean)
|
|
82
|
+
+ diversity_weight * diversity_reward
|
|
83
|
+
)
|
|
84
|
+
return float(reward)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_trajectory_probability_from_log_probs(log_probs, selected_population_indices):
|
|
88
|
+
"""
|
|
89
|
+
calculate total probability for the trajectory by summing the log probabilities over the selected indices
|
|
90
|
+
"""
|
|
91
|
+
trajectory_prob = (
|
|
92
|
+
torch.gather(log_probs, 2, selected_population_indices.unsqueeze(-1))
|
|
93
|
+
.squeeze(-1)
|
|
94
|
+
.sum(dim=-1)
|
|
95
|
+
)
|
|
96
|
+
return trajectory_prob
|