Compare commits

..

2 Commits

Author SHA1 Message Date
e22a273609 viz 2023-07-16 00:40:47 +02:00
439f995eae Initial commit 2023-07-15 14:25:10 +02:00
4 changed files with 288 additions and 0 deletions

133
Abschlussprojekt/genome.py Normal file
View File

@ -0,0 +1,133 @@
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from random import choice
class NodeType(Enum):
INPUT = 1
HIDDEN = 2
OUTPUT = 3
class MutationType(Enum):
ADD_CONNECTION = 1
ADD_NODE = 2
@dataclass(frozen=True)
class NodeGene:
id: int
type: NodeType
@dataclass()
class ConnectionGene:
nodes: tuple[int, int]
weight: float
innovation_no: int
disabled: bool = False
_CONNECTION_GENES: dict[tuple[int, int], ConnectionGene] = dict()
class Genome:
def __init__(self):
# Initialize nodes
self.nodes: dict[int, NodeGene] = dict()
# Initialize connections
self.connections: dict[tuple[int, int], ConnectionGene] = dict()
def add_node(self, node_type: NodeType = NodeType.HIDDEN) -> int:
"""
Adds a node of the given type to the genome and returns the identification key.
"""
key = len(self.nodes)
self.nodes[key] = NodeGene(key, node_type)
return key
def add_connection(self, from_node: int, to_node: int, weight: float) -> tuple[int, int]:
"""
Adds a connection of weight between two given nodes to the genome and returns
the identification key.
"""
key = (from_node, to_node)
connection = ConnectionGene(key, weight, -1)
if key in _CONNECTION_GENES:
connection.innovation_no = _CONNECTION_GENES[key].innovation_no
else:
connection.innovation_no = len(_CONNECTION_GENES)
_CONNECTION_GENES[key] = connection
self.connections[key] = connection
return key
@staticmethod
def new(inputs: int, outputs: int) -> Genome:
genome = Genome()
# Add input nodes
for _ in range(inputs):
genome.add_node(node_type=NodeType.INPUT)
# Add output nodes
for _ in range(outputs):
genome.add_node(node_type=NodeType.OUTPUT)
# Fully connect
for i in range(inputs):
for o in range(inputs, inputs + outputs):
genome.add_connection(i, o, weight=1)
return genome
def mutate(genome: Genome) -> None:
mutation = choice([MutationType.ADD_NODE])
if mutation is MutationType.ADD_CONNECTION:
_mutate_add_connection(genome)
elif mutation is MutationType.ADD_NODE:
_mutate_add_node(genome)
def _mutate_add_connection(genome: Genome) -> None:
"""
In the add_connection mutation, a single new connection gene with a random weight
is added connecting two previously unconnected nodes.
"""
...
def _mutate_add_node(genome: Genome) -> None:
"""
In the add_node mutation, an existing connection is split and the new node
placed where the old connection used to be. The old connection is disabled
and two new conections are added to the genome. The new connection leading
into the new node receives a weight of 1, and the new connection leading out
receives the same weight as the old connection.
"""
# Find connection to split
try:
connection = choice([node for node in genome.connections.values() if not node.disabled])
except IndexError:
return
connection.disabled = True
# Create new node
new_node = genome.add_node()
from_node, to_node = connection.nodes
# Connect previous from_node to new_node
genome.add_connection(from_node, new_node, weight=1)
# Connection new_node to previous to_node
genome.add_connection(new_node, to_node, weight=connection.weight)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
pygraphviz

View File

@ -0,0 +1,67 @@
import itertools
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
from genome import Genome, NodeType, mutate
def _find_layer(g: nx.DiGraph, hidden_node: int, inputs: list[int]) -> int:
paths = []
for input_node in inputs:
paths += list(nx.all_simple_paths(g, input_node, hidden_node))
path_lengths = [len(path) for path in paths]
return max(path_lengths)
def genome(genome: Genome):
graph = nx.DiGraph()
# Add nodes
for node in genome.nodes.keys():
graph.add_node(node)
# Add edges
for connection in genome.connections.values():
if connection.disabled:
continue
from_node, to_node = connection.nodes
graph.add_edge(from_node, to_node, weight=connection.weight)
inputs = [node.id for node in genome.nodes.values() if node.type == NodeType.INPUT]
hidden = [node.id for node in genome.nodes.values() if node.type == NodeType.HIDDEN]
outputs = [node.id for node in genome.nodes.values() if node.type == NodeType.OUTPUT]
for input_node in inputs:
graph.nodes[input_node]["layer"] = 0
max_layer = 1
for hidden_node in hidden:
layer = _find_layer(graph, hidden_node, inputs)
max_layer = max(layer, max_layer)
graph.nodes[hidden_node]["layer"] = layer
for output_node in outputs:
graph.nodes[output_node]["layer"] = max_layer + 1
plt.subplot()
pos = nx.multipartite_layout(graph, subset_key="layer")
nx.draw_networkx_nodes(graph, pos, nodelist=inputs, label=inputs, node_color="#ff0000")
nx.draw_networkx_nodes(graph, pos, nodelist=hidden, label=hidden, node_color="#00ff00")
nx.draw_networkx_nodes(graph, pos, nodelist=outputs, label=outputs, node_color="#0000ff")
nx.draw_networkx_edges(graph, pos)
if __name__ == "__main__":
g1 = Genome.new(3, 2)
g1.add_connection(0, 4, 0.5)
mutate(g1)
mutate(g1)
mutate(g1)
# mutate(g1)
genome(g1)
plt.show()