【NEAT】python3(anaconda3)を使ってNEATを実装してみた(2/5)

前回はNEATの概念をご紹介し、populationを用意する段階まで進みました。

今回は用意したpopulationを種に分ける操作を行います。

###種に分ける(②)

次のコードを用意します。

```python:
compatibility_threshold            = 3.0
initial_connection                 = 'full'
compatibility_disjoint_coefficient = 1.0

class Species(object):
    def __init__(self, key, generation):
        self.key = key
        self.created = generation
        self.last_improved = generation
        self.representative = None
        self.members = {}
        self.fitness = None
        self.adjusted_fitness = None
        self.fitness_history = []

    def update(self, representative, members):
        self.representative = representative
        self.members = members

    def get_fitnesses(self):
        return [m.fitness for m in self.members.values()]

class DefaultSpeciesSet(object):

    def __init__(self):
        self.indexer = count(1)
        self.species = {}
        self.genome_to_species = {}

    @staticmethod
    def distance(me, other):

        node_distance = 0.0
        if me.nodes or other.nodes:
            disjoint_nodes = 0
            for k2 in other.nodes:
                if k2 not in me.nodes:
                    disjoint_nodes += 1

            for k1, n1 in me.nodes.items():
                n2 = other.nodes.get(k1)
                if n2 is None:
                    disjoint_nodes += 1
                else:
                    node_distance += n1.distance(n2)

            max_nodes = max(len(me.nodes), len(other.nodes))
            node_distance = (node_distance +
                             (compatibility_disjoint_coefficient *
                              disjoint_nodes)) / max_nodes

        connection_distance = 0.0
        if me.connections or other.connections:
            disjoint_connections = 0
            for k2 in other.connections:
                if k2 not in me.connections:
                    disjoint_connections += 1

            for k1, c1 in me.connections.items():
                c2 = other.connections.get(k1)
                if c2 is None:
                    disjoint_connections += 1
                else:
                    connection_distance += c1.distance(c2)

            max_conn = max(len(me.connections), len(other.connections))
            connection_distance = (connection_distance +
                                   (compatibility_disjoint_coefficient *
                                    disjoint_connections)) / max_conn

        distance = node_distance + connection_distance
        return distance

    def speciate(self, population, generation):

        unspeciated = set(population)
        new_representatives = {}
        new_members = {}
        for sid, s in self.species.items():
            candidates = []
            for gid in unspeciated:
                g = population[gid]
                d = self.distance(s.representative, g)
                candidates.append((d, g))

            ignored_rdist, new_rep = min(candidates, key=lambda x: x[0])
            new_rid = new_rep.key
            new_representatives[sid] = new_rid
            new_members[sid] = [new_rid]
            unspeciated.remove(new_rid)

        while unspeciated:
            gid = unspeciated.pop()
            g = population[gid]

            candidates = []
            for sid, rid in new_representatives.items():
                rep = population[rid]
                d = self.distance(rep, g)
                if d < compatibility_threshold:
                    candidates.append((d, sid))

            if candidates:
                ignored_sdist, sid = min(candidates, key=lambda x: x[0])
                new_members[sid].append(gid)
            else:
                sid = next(self.indexer)
                new_representatives[sid] = gid
                new_members[sid] = [gid]

        self.genome_to_species = {}
        for sid, rid in new_representatives.items():
            s = self.species.get(sid)
            if s is None:
                s = Species(sid, generation)
                self.species[sid] = s

            members = new_members[sid]
            for gid in members:
                self.genome_to_species[gid] = sid

            member_dict = dict((gid, population[gid]) for gid in members)
            s.update(population[rid], member_dict)

    def get_species_id(self, individual_id):
        return self.genome_to_species[individual_id]
```


これで種の管理をするSpeciesクラスと種に分けるメソッドspeciateを含むDefaultSpeciesSetを定義することができました。

そして次のコードで種に分ける操作をします。



```python:
species = DefaultSpeciesSet()
generation = 0
species.speciate(population, generation)
print(species.species)
print(species.genome_to_species)
```

```python:
print(species.species[1].representative)
print(species.species[1].representative.key)
```
![スクリーンショット 2020-02-16 22.51.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582794/56e158d1-e17b-37fa-467f-37540f8b1de5.png)


print(species.species)をみると、辞書型が表示されており、そのkeyが一つしかないことから、一種類の種ができたことがわかります。また、print(species.genome_to_species)より、150体あるpopulationのどのネットワークも1という種に分類されている様子がわかります。

さらに、print(species.species[1].representative)より種の代表(representative)がpopulationのKey=1のネットワークであることもわかります。

representativeは各世代の中のそれぞれの種に一つあり、その種を代表するネットワークとして種を分ける際に基準のネットワークとなります。

ここで、種を分ける方法について説明します。


__speciate__
種に分ける際は、ネットワーク同士の距離(distance)がある水準より大きいか小さいかを計算します。

例として次の簡易的な二つのネットワークのdistanceを測ります。

![image (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582794/0a9d3951-bd2f-6336-3683-f6d76e8a6192.png)

左がrepresentativeで右が比較する対象となるotherです。それぞれNodesとConnectionsがあり、weightやBiasも値が与えられています。

コードに従って説明すると、distanceはnode_distanceとconnection_distanceの和で表すことができます。

__node_distance__

まず、Inputを除くNodesの差を見ます、この例ではどちらも0Nodesしかなく、違いはありません。

disjoint_nodes=0

次に共通のNodesに関してそのBiasの差を計算します。

node_distance=(Bias(rep)-Bias(other))×compatibility_weight_coefficient
=(2-1)×0.5=0.5

また、max_nodesを計算します。

max_nodesはrepresentative、またはotherのうちより多い方のNodes数です。今回はどちらも1なので、

max_nodes=1

となります(BiasはNodesとして含めません)。

最終的に求めるnode_distanceは

node_distance=(node_distance+compatibility_disjoint_coefficient×disjoint_nodes)/max_nodes

より、
node_distance=(0.5+1.0×0)/1=0.5


__connection_distance__

まず、Connectionsの差を見ます、この例ではrepresentativeが2本あり、otherは1本なので、違いは+1として考えます。

disjoint_connections=1

次に共通のConnectionsに関してそのweightの差を計算します。

connection_distance=(weight(rep)-weight(other))×compatibility_weight_coefficient
=(2-1)×0.5=0.5

また、max_connを計算します。

max_connはrepresentative、またはotherのうちより多い方のConnections数です。今回はrepresentativeの2本を採用して、

max_nodes=2

となります。

最終的に求めるconnection_distanceは

connection_distance=(connection_distance+compatibility_disjoint_coefficient×disjoint_connections)/max_conn

より、
connection_distance=(0.5+1.0×1)/2=0.75


つまりdistanceは
distance=node_distance+connection_distance=0.5+0.75=1.25

となります。このような計算を行った上でdistanceがcompatibility_threshold(今回は3.0)を超えれば新たな種が生まれます。

generation=0のときは、populationの最初のネットワーク(Key=1)をrepresentativeとして他のネットワークとの距離(distance)を測ります。generation=1以降は一つ前のrepresentativeと最も近いネットワークをその世代のrepresentativeとし、そのネットワークをそれ以外のネットワークと比べてdistanceを測ります。

次回はネットワークの成績を計算します。

* [NEAT実装してみた（1. NEATの概要、populationの用意）](https://qiita.com/suna__ck/items/6229fd0f05c05f398af2)
* [NEAT実装してみた（2. 種に分ける）](https://qiita.com/suna__ck/items/52582925f41415d7308d)
* [NEAT実装してみた（3. 成績の計算）](https://qiita.com/suna__ck/items/31824504cae8caacaec8)