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()
によって次のように定義されている。ノード遺伝子と、コネクション遺伝子の距離を計算し、その値を使用して、ゲノム自体の距離を算出する。
まずはノード遺伝子の距離の計算から見ていく。
ノード遺伝子では、バイアスの差と、レスポンスの差の絶対値の和を計算する。更にアクティベーションとアクリゲーションが異なれば、それぞれ1.0ずつ加算している。
次にコネクション遺伝子の距離の計算を見る。
重みの差の絶対値を計算する。そして遺伝子の有効無効が異なれば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
本来、遺伝子の追加はmutate_add_nodeで行う。