forked from MaxHalford/eaopt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
speciation.go
138 lines (130 loc) · 4.07 KB
/
speciation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package gago
import (
"errors"
"fmt"
"math"
"math/rand"
)
// A Speciator partitions a population into n smaller subpopulations. Each
// subpopulation shares the same random number generator inherited from the
// initial population.
type Speciator interface {
Apply(indis Individuals, rng *rand.Rand) ([]Individuals, error)
Validate() error
}
// SpecKMedoids (k-medoid clustering).
type SpecKMedoids struct {
K int // Number of medoids
MinPerCluster int
Metric Metric // Dissimimilarity measure
MaxIterations int
}
// Apply SpecKMedoids.
func (spec SpecKMedoids) Apply(indis Individuals, rng *rand.Rand) ([]Individuals, error) {
// Check there are at least K Individuals
if len(indis) < spec.K {
return nil, fmt.Errorf("SpecKMedoids: have %d individuals and need at least %d",
len(indis), spec.K)
}
var (
species = make([]Individuals, spec.K)
medoids = make(Individuals, spec.K)
dm = newDistanceMemoizer(spec.Metric)
)
// Initialize the clusters with the individuals having the lowest average
// distances with the other individuals
indis.SortByDistanceToMedoid(dm)
copy(medoids, indis[:spec.K])
// Add each medoid to a cluster
for i, medoid := range medoids {
species[i] = append(species[i], medoid)
}
// Keep track of the total distance from the medoid to each of the cluster's
// members, this total will then be used to compare with different cluster
// dispositions
var total float64
// Assign each individual that is not a medoid to the closest initial medoid
for _, indi := range indis[spec.K:] {
var i = indi.IdxOfClosest(medoids, dm)
species[i] = append(species[i], indi)
total += dm.GetDistance(medoids[i], indi)
}
var nIterations int
for nIterations < spec.MaxIterations {
nIterations++
var (
newSpecies = make([]Individuals, len(species))
newTotal float64
)
// Recompute the new medoid inside each specie
for i, specie := range species {
specie.SortByDistanceToMedoid(dm)
medoids[i] = specie[0]
newSpecies[i] = append(newSpecies[i], specie[0])
}
// Reassign each individual to the closest new medoid
for _, specie := range species {
for _, indi := range specie[1:] {
var i = indi.IdxOfClosest(medoids, dm)
newSpecies[i] = append(newSpecies[i], indi)
newTotal += dm.GetDistance(medoids[i], indi)
}
}
// No more iterations are needed if the new total is worse
if newTotal >= total {
break
}
copy(species, newSpecies)
total = newTotal
}
// Rebalance the species so that their are at least
rebalanceClusters(species, dm, spec.MinPerCluster)
return species, nil
}
// Validate SpecKMedoids fields.
func (spec SpecKMedoids) Validate() error {
if spec.K < 2 {
return errors.New("K should be higher than 1")
}
if spec.Metric == nil {
return errors.New("Metric field has to be provided")
}
if spec.MaxIterations < 1 {
return errors.New("K should be higher than 0")
}
return nil
}
// SpecFitnessInterval speciates a population based on the fitness of each
// individual where each species contains m = n/k (rounded to the closest upper
// integer) individuals with similar fitnesses. For example, with 4 species, 30
// individuals would be split into 3 groups of 8 individuals and 1 group of 6
// individuals (3*8 + 1*6 = 30). More generally each group is of size
// min(n-i, m) where i is a multiple of m.
type SpecFitnessInterval struct {
K int // Number of intervals
}
// Apply SpecFitnessInterval.
func (spec SpecFitnessInterval) Apply(indis Individuals, rng *rand.Rand) ([]Individuals, error) {
// Check there are at least K Individuals
if len(indis) < spec.K {
return nil, fmt.Errorf("SpecFitnessInterval: have %d individuals and need at least %d",
len(indis), spec.K)
}
var (
species = make([]Individuals, spec.K)
n = len(indis)
m = min(int(math.Ceil(float64(n/spec.K))), n)
)
for i := range species {
var a, b = i * m, min((i+1)*m, n)
species[i] = indis[a:b]
}
return species, nil
}
// Validate SpecFitnessInterval fields.
func (spec SpecFitnessInterval) Validate() error {
if spec.K < 2 {
return errors.New("K should be higher than 1")
}
return nil
}