« ^ »

NEAT-Pythonのゲノムの距離を測る

所要時間: 約 4分

NEAT-Pythonのゲノム同士の距離の測り方が気になって夜も眠れないので、手で測ってみる事にした。このライブラリと上手く付き合うコツの一つは、まず何はともあれ設定ファイルを作る所にある。だから、ここでも設定ファイルから作成し example.conf として保存した。このファイルは後で掲載する。

設定ファイルを読み込み、ゲノムを2つ(g1とg2)作成する。この2つのゲノムはノードのコネクションも空のゲノムであり、何も遺伝子を持っていないゲノムだ。この2つの距離を測る。

from neat.config import Config
from neat.genes import DefaultNodeGene
from neat.genome import DefaultGenome
from neat.reproduction import DefaultReproduction
from neat.species import DefaultSpeciesSet
from neat.stagnation import DefaultStagnation

c = Config(
    DefaultGenome,
    DefaultReproduction,
    DefaultSpeciesSet,
    DefaultStagnation,
    "example.conf",
)

g1 = DefaultGenome(1)
g2 = DefaultGenome(2)

return g1.distance(g2, c.genome_config)
0.0

当然だが距離は0になる。違いはないのだから、それはそうだ。次に、g1のゲノムを強制的に遺伝子操作して、ノード遺伝子を追加1し距離を計測する。

from neat.config import Config
from neat.genes import DefaultNodeGene
from neat.genome import DefaultGenome
from neat.reproduction import DefaultReproduction
from neat.species import DefaultSpeciesSet
from neat.stagnation import DefaultStagnation

c = Config(
    DefaultGenome,
    DefaultReproduction,
    DefaultSpeciesSet,
    DefaultStagnation,
    "example.conf",
)

g1 = DefaultGenome(1)
g2 = DefaultGenome(2)

ng1 = DefaultNodeGene(1) # ノード遺伝子
g1.nodes[ng1.key] = ng1  # 強制的に遺伝子を操作する

return g1.distance(g2, c.genome_config)
1.0

ノード遺伝子に違いが出たため、距離は1.0と計算された。

距離の計算

2つのゲノムの距離の計算は neat.genome.DefaultGenome.distance() によって次のように定義されている。ノード遺伝子と、コネクション遺伝子の距離を計算し、その値を使用して、ゲノム自体の距離を算出する。

まずはノード遺伝子の距離の計算から見ていく。

    def distance(self, other, config):
        d = abs(self.bias - other.bias) + abs(self.response - other.response)
        if self.activation != other.activation:
            d += 1.0
        if self.aggregation != other.aggregation:
            d += 1.0
ノード遺伝子(DefaultNodeGene)の距離の計算(neat/genes.py抜粋)

ノード遺伝子では、バイアスの差と、レスポンスの差の絶対値の和を計算する。更にアクティベーションとアクリゲーションが異なれば、それぞれ1.0ずつ加算している。

次にコネクション遺伝子の距離の計算を見る。

    def distance(self, other, config):
        d = abs(self.weight - other.weight)
        if self.enabled != other.enabled:
            d += 1.0
        return d * config.compatibility_weight_coefficient
コネクション遺伝子(DefaultConnectionGene)の距離の計算(neat/genes.py抜粋)

重みの差の絶対値を計算する。そして遺伝子の有効無効が異なれば1.0を加算する。そして最後に重みの互換性のための係数を乗算する。この値がコネクション遺伝子の距離となる。

ゲノムの距離の計算は、各遺伝子の距離を計算した値を取りまとめる実装になっている。

    def distance(self, other, config):
        """
        Returns the genetic distance between this genome and the other. This distance value
        is used to compute genome compatibility for speciation.
        """

        # Compute node gene distance component.
        node_distance = 0.0
        if self.nodes or other.nodes:
            disjoint_nodes = 0
            for k2 in other.nodes:
                if k2 not in self.nodes:
                    disjoint_nodes += 1

            for k1, n1 in self.nodes.items():
                n2 = other.nodes.get(k1)
                if n2 is None:
                    disjoint_nodes += 1
                else:
                    # Homologous genes compute their own distance value.
                    node_distance += n1.distance(n2, config)

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

        # Compute connection gene differences.
        connection_distance = 0.0
        if self.connections or other.connections:
            disjoint_connections = 0
            for k2 in other.connections:
                if k2 not in self.connections:
                    disjoint_connections += 1

            for k1, c1 in self.connections.items():
                c2 = other.connections.get(k1)
                if c2 is None:
                    disjoint_connections += 1
                else:
                    # Homologous genes compute their own distance value.
                    connection_distance += c1.distance(c2, config)

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

        distance = node_distance + connection_distance
        return distance

基本的には同じ位置の遺伝子に対し、距離の計算が行なわれる。ただし2つのゲノムの間で遺伝子の個数が合わない場合もある。その場合は、欠損した遺伝子のための係数( compatibility_disjoint_coefficient )を合わない数だけ乗算して処理している。 遺伝子ごとに算出した距離と欠損した遺伝子分の値を足し、遺伝子の個数の商を取っている。これをノード遺伝子と、コネクション遺伝子の両方に行い、その値を足す事でゲノムの距離としている。

この計算の妥当性はよくわからないが、現状どのように実装されているかという事はわかった。

設定ファイル

公式のドキュメント2からコピペしたものであって特別な設定は何もしていない。

[NEAT]
fitness_criterion     = max
fitness_threshold     = 3.9
pop_size              = 150
reset_on_extinction   = False

[DefaultGenome]
# node activation options
activation_default      = sigmoid
activation_mutate_rate  = 0.0
activation_options      = sigmoid

# node aggregation options
aggregation_default     = sum
aggregation_mutate_rate = 0.0
aggregation_options     = sum

# node bias options
bias_init_mean          = 0.0
bias_init_stdev         = 1.0
bias_max_value          = 30.0
bias_min_value          = -30.0
bias_mutate_power       = 0.5
bias_mutate_rate        = 0.7
bias_replace_rate       = 0.1

# genome compatibility options
compatibility_disjoint_coefficient = 1.0
compatibility_weight_coefficient   = 0.5

# connection add/remove rates
conn_add_prob           = 0.5
conn_delete_prob        = 0.5

# connection enable options
enabled_default         = True
enabled_mutate_rate     = 0.01

feed_forward            = True
initial_connection      = full

# node add/remove rates
node_add_prob           = 0.2
node_delete_prob        = 0.2

# network parameters
num_hidden              = 0
num_inputs              = 2
num_outputs             = 1

# node response options
response_init_mean      = 1.0
response_init_stdev     = 0.0
response_max_value      = 30.0
response_min_value      = -30.0
response_mutate_power   = 0.0
response_mutate_rate    = 0.0
response_replace_rate   = 0.0

# connection weight options
weight_init_mean        = 0.0
weight_init_stdev       = 1.0
weight_max_value        = 30
weight_min_value        = -30
weight_mutate_power     = 0.5
weight_mutate_rate      = 0.8
weight_replace_rate     = 0.1

[DefaultSpeciesSet]
compatibility_threshold = 3.0

[DefaultStagnation]
species_fitness_func = max
max_stagnation       = 20
species_elitism      = 2

[DefaultReproduction]
elitism            = 2
survival_threshold = 0.2

1

本来、遺伝子の追加はmutate_add_nodeで行う。