Compare commits
7 Commits
58f1e7b1ad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ef7dce9bee | |||
| 8fd7baaf93 | |||
| 0fd332a6e5 | |||
| eaf2e7e111 | |||
| fa6a721cc8 | |||
| e22a273609 | |||
| 439f995eae |
16
Abschlussprojekt/connection.py
Normal file
16
Abschlussprojekt/connection.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import dataclasses
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass()
|
||||||
|
class ConnectionGene:
|
||||||
|
nodes: tuple[int, int]
|
||||||
|
weight: float
|
||||||
|
innovation_no: int
|
||||||
|
disabled: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
def reset_innovation_numbers():
|
||||||
|
_CONNECTION_GENES.clear()
|
||||||
|
|
||||||
|
|
||||||
|
_CONNECTION_GENES: dict[tuple[int, int], ConnectionGene] = dict()
|
||||||
280
Abschlussprojekt/genome.py
Normal file
280
Abschlussprojekt/genome.py
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
import itertools
|
||||||
|
from enum import Enum
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from graphs import creates_cycle
|
||||||
|
|
||||||
|
rng = np.random.default_rng()
|
||||||
|
|
||||||
|
|
||||||
|
from connection import _CONNECTION_GENES, ConnectionGene
|
||||||
|
from node import NodeGene, NodeType
|
||||||
|
|
||||||
|
|
||||||
|
class MutationType(Enum):
|
||||||
|
ADD_CONNECTION = 1
|
||||||
|
ADD_NODE = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Genome:
|
||||||
|
def __init__(self):
|
||||||
|
# Initialize nodes
|
||||||
|
self.nodes: dict[int, NodeGene] = dict()
|
||||||
|
|
||||||
|
# Initialize connections
|
||||||
|
self.connections: dict[tuple[int, int], ConnectionGene] = dict()
|
||||||
|
|
||||||
|
self.fitness = 0
|
||||||
|
|
||||||
|
def set_node(self, key: int, node: NodeGene) -> None:
|
||||||
|
self.nodes[key] = node
|
||||||
|
|
||||||
|
def set_connection(self, key: tuple[int, int], connection: ConnectionGene) -> None:
|
||||||
|
self.connections[key] = connection
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
if not isinstance(from_node, int) or not isinstance(to_node, int):
|
||||||
|
raise ValueError("Nodes must be integer keys.")
|
||||||
|
|
||||||
|
if from_node not in self.nodes or to_node not in self.nodes:
|
||||||
|
raise ValueError("Nodes do not exist.")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def copy(genome: Genome) -> Genome:
|
||||||
|
clone = Genome()
|
||||||
|
|
||||||
|
# Copy nodes
|
||||||
|
for key, node in genome.nodes.items():
|
||||||
|
clone.set_node(key, dataclasses.replace(node))
|
||||||
|
|
||||||
|
# Copy connections
|
||||||
|
for key, connection in genome.connections.items():
|
||||||
|
clone.set_connection(key, dataclasses.replace(connection))
|
||||||
|
|
||||||
|
# Set fitness
|
||||||
|
clone.fitness = genome.fitness
|
||||||
|
|
||||||
|
return clone
|
||||||
|
|
||||||
|
|
||||||
|
def mutate(genome: Genome) -> None:
|
||||||
|
mutation = choice([MutationType.ADD_NODE, MutationType.ADD_CONNECTION])
|
||||||
|
if mutation is MutationType.ADD_CONNECTION:
|
||||||
|
_mutate_add_connection(genome)
|
||||||
|
elif mutation is MutationType.ADD_NODE:
|
||||||
|
_mutate_add_node(genome)
|
||||||
|
|
||||||
|
|
||||||
|
def crossover(mother: Genome, father: Genome) -> Genome:
|
||||||
|
mother_connections = {conn.innovation_no: conn for conn in mother.connections.values()}
|
||||||
|
father_connections = {conn.innovation_no: conn for conn in father.connections.values()}
|
||||||
|
innovation_numbers = set(mother_connections.keys()) | set(father_connections.keys())
|
||||||
|
|
||||||
|
child_connections: dict[int, ConnectionGene] = {}
|
||||||
|
|
||||||
|
for i in innovation_numbers:
|
||||||
|
# Matching genes
|
||||||
|
if i in mother_connections and i in father_connections:
|
||||||
|
child_connections[i] = choice((mother_connections[i], father_connections[i]))
|
||||||
|
|
||||||
|
# Disjoint or excess
|
||||||
|
else:
|
||||||
|
# Mother has better fitness
|
||||||
|
if mother.fitness > father.fitness and i in mother_connections:
|
||||||
|
child_connections[i] = mother_connections[i]
|
||||||
|
|
||||||
|
# Father has better fitness
|
||||||
|
elif father.fitness > mother.fitness and i in father_connections:
|
||||||
|
child_connections[i] = father_connections[i]
|
||||||
|
|
||||||
|
# Equal fitness
|
||||||
|
else:
|
||||||
|
connection = choice((mother_connections.get(i, None), father_connections.get(i, None)))
|
||||||
|
if connection is not None:
|
||||||
|
child_connections[i] = connection
|
||||||
|
|
||||||
|
# Determine input/output dimensions
|
||||||
|
inputs = sum(node.type == NodeType.INPUT for node in mother.nodes.values())
|
||||||
|
outputs = sum(node.type == NodeType.OUTPUT for node in mother.nodes.values())
|
||||||
|
|
||||||
|
# Create child and set nodes & connections
|
||||||
|
child = Genome.new(inputs, outputs)
|
||||||
|
for connection in child_connections.values():
|
||||||
|
# Set connections
|
||||||
|
child.set_connection(connection.nodes, dataclasses.replace(connection))
|
||||||
|
from_node, to_node = connection.nodes
|
||||||
|
|
||||||
|
# Add nodes if required
|
||||||
|
if from_node not in child.nodes:
|
||||||
|
child.set_node(from_node, NodeGene(from_node, NodeType.HIDDEN))
|
||||||
|
if to_node not in child.nodes:
|
||||||
|
child.set_node(to_node, NodeGene(to_node, NodeType.HIDDEN))
|
||||||
|
|
||||||
|
return child
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from_node = choice([id for id, node in genome.nodes.items() if node.type != NodeType.OUTPUT])
|
||||||
|
try:
|
||||||
|
to_node = choice(
|
||||||
|
[
|
||||||
|
id
|
||||||
|
for id, node in genome.nodes.items()
|
||||||
|
if node.type != NodeType.INPUT and (from_node, id) not in genome.connections
|
||||||
|
]
|
||||||
|
)
|
||||||
|
except IndexError:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Checking for cycles
|
||||||
|
if creates_cycle(genome.connections.keys(), (from_node, to_node)):
|
||||||
|
return
|
||||||
|
|
||||||
|
genome.add_connection(from_node, to_node, weight=rng.uniform(0, 1))
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def _excess(g1: Genome, g2: Genome) -> list[int]:
|
||||||
|
g1_connections = {conn.innovation_no: conn for conn in g1.connections.values()}
|
||||||
|
g2_connections = {conn.innovation_no: conn for conn in g2.connections.values()}
|
||||||
|
|
||||||
|
less_connections, more_connections = sorted((g1_connections, g2_connections), key=lambda c: max(c.keys()))
|
||||||
|
return [k for k in more_connections.keys() if k > max(less_connections.keys())]
|
||||||
|
|
||||||
|
|
||||||
|
def _disjoint(g1: Genome, g2: Genome) -> list[int]:
|
||||||
|
g1_connections = {conn.innovation_no: conn for conn in g1.connections.values()}
|
||||||
|
g2_connections = {conn.innovation_no: conn for conn in g2.connections.values()}
|
||||||
|
|
||||||
|
less_connections, more_connections = sorted((g1_connections, g2_connections), key=lambda c: max(c.keys()))
|
||||||
|
return list(
|
||||||
|
{i for i in less_connections.keys() if i not in more_connections}
|
||||||
|
| {i for i in more_connections.keys() if i not in less_connections and i <= max(less_connections.keys())}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_delta(g1: Genome, g2: Genome, c1: float, c2: float, c3: float) -> float:
|
||||||
|
n = max([len(g1.nodes), len(g2.nodes)])
|
||||||
|
|
||||||
|
g1_connections = {conn.innovation_no: conn for conn in g1.connections.values()}
|
||||||
|
g2_connections = {conn.innovation_no: conn for conn in g2.connections.values()}
|
||||||
|
innovation_numbers = set(g1_connections.keys()) | set(g2_connections.keys())
|
||||||
|
|
||||||
|
# Calculate number of excess genes
|
||||||
|
less_connections, more_connections = sorted((g1_connections, g2_connections), key=lambda c: max(c.keys()))
|
||||||
|
e = len([k for k in more_connections.keys() if k > max(less_connections.keys())])
|
||||||
|
|
||||||
|
# Calculate number of disjoint genes
|
||||||
|
d = len(
|
||||||
|
{i for i in less_connections.keys() if i not in more_connections}
|
||||||
|
| {i for i in more_connections.keys() if i not in less_connections and i <= max(less_connections.keys())}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Average weight difference of matching genes
|
||||||
|
w = 0
|
||||||
|
for i in innovation_numbers:
|
||||||
|
if i in g1_connections and i in g2_connections:
|
||||||
|
w += abs(g1_connections[i].weight - g2_connections[i].weight)
|
||||||
|
|
||||||
|
delta = ((c1 * e) / n) + ((c2 * d) / n) + (c3 * w)
|
||||||
|
return delta
|
||||||
|
|
||||||
|
|
||||||
|
def specify(genomes: list, c1: float, c2: float, c3: float) -> list[list]:
|
||||||
|
THRESHOLD = 1
|
||||||
|
species = []
|
||||||
|
for genom in genomes:
|
||||||
|
done = False
|
||||||
|
if len(species) < 1:
|
||||||
|
species.append([genom])
|
||||||
|
done = True
|
||||||
|
for spicy in species:
|
||||||
|
print("Delta: ", _get_delta(genom, spicy[0], c1, c2, c3))
|
||||||
|
if _get_delta(genom, spicy[0], c1, c2, c3) < THRESHOLD and not done:
|
||||||
|
spicy.append(genom)
|
||||||
|
done = True
|
||||||
|
if not done:
|
||||||
|
species.append([genom])
|
||||||
|
|
||||||
|
return species
|
||||||
28
Abschlussprojekt/graphs.py
Normal file
28
Abschlussprojekt/graphs.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def creates_cycle(connections: list[tuple[int, int]], test: tuple[int, int]) -> bool:
|
||||||
|
"""
|
||||||
|
Returns true if the addition of the 'test' connection would create a cycle,
|
||||||
|
assuming that no cycle already exists in the graph represented by 'connections'.
|
||||||
|
https://github.com/CodeReclaimers/neat-python/blob/4928381317213ee3285204ae1f2a086286aa3a10/neat/graphs.py#L4
|
||||||
|
"""
|
||||||
|
|
||||||
|
i, o = test
|
||||||
|
if i == o:
|
||||||
|
return True
|
||||||
|
|
||||||
|
visited = {o}
|
||||||
|
while True:
|
||||||
|
num_added = 0
|
||||||
|
for a, b in connections:
|
||||||
|
if a in visited and b not in visited:
|
||||||
|
if b == i:
|
||||||
|
return True
|
||||||
|
|
||||||
|
visited.add(b)
|
||||||
|
num_added += 1
|
||||||
|
|
||||||
|
if num_added == 0:
|
||||||
|
return False
|
||||||
518
Abschlussprojekt/neat.ipynb
Normal file
518
Abschlussprojekt/neat.ipynb
Normal file
File diff suppressed because one or more lines are too long
14
Abschlussprojekt/node.py
Normal file
14
Abschlussprojekt/node.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import dataclasses
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class NodeType(Enum):
|
||||||
|
INPUT = 1
|
||||||
|
HIDDEN = 2
|
||||||
|
OUTPUT = 3
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class NodeGene:
|
||||||
|
id: int
|
||||||
|
type: NodeType
|
||||||
1
Abschlussprojekt/requirements.txt
Normal file
1
Abschlussprojekt/requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pygraphviz
|
||||||
83
Abschlussprojekt/visualization.py
Normal file
83
Abschlussprojekt/visualization.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import networkx as nx
|
||||||
|
import numpy as np
|
||||||
|
import tabulate
|
||||||
|
|
||||||
|
from genome import Genome, NodeType, mutate
|
||||||
|
from node import NodeType
|
||||||
|
|
||||||
|
|
||||||
|
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 2 if len(path_lengths) == 0 else max(path_lengths)
|
||||||
|
|
||||||
|
|
||||||
|
def genome_graph(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, node_color="#ff0000")
|
||||||
|
nx.draw_networkx_nodes(graph, pos, nodelist=hidden, node_color="#00ff00")
|
||||||
|
nx.draw_networkx_nodes(graph, pos, nodelist=outputs, node_color="#0000ff")
|
||||||
|
nx.draw_networkx_labels(graph, pos)
|
||||||
|
nx.draw_networkx_edges(graph, pos)
|
||||||
|
|
||||||
|
|
||||||
|
def genome_table(genome: Genome):
|
||||||
|
table = [
|
||||||
|
(conn.innovation_no, "->".join([str(n) for n in conn.nodes]), "DIS" if conn.disabled else "")
|
||||||
|
for conn in genome.connections.values()
|
||||||
|
]
|
||||||
|
table.sort(key=lambda c: c[0])
|
||||||
|
table = zip(*table)
|
||||||
|
|
||||||
|
print(tabulate.tabulate(table, tablefmt="psql"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
g1 = Genome.new(3, 2)
|
||||||
|
g1.add_connection(0, 4, 0.5)
|
||||||
|
mutate(g1)
|
||||||
|
mutate(g1)
|
||||||
|
mutate(g1)
|
||||||
|
mutate(g1)
|
||||||
|
mutate(g1)
|
||||||
|
mutate(g1)
|
||||||
|
|
||||||
|
# mutate(g1)
|
||||||
|
# genome_graph(g1)
|
||||||
|
# plt.show()
|
||||||
|
genome_table(g1)
|
||||||
317
Aufgabe 6/aufgabe06.ipynb
Normal file
317
Aufgabe 6/aufgabe06.ipynb
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 205,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import tensorflow as tf\n",
|
||||||
|
"from tensorflow import keras\n",
|
||||||
|
"import numpy as np\n",
|
||||||
|
"from tqdm import tqdm, trange\n",
|
||||||
|
"from random import sample\n",
|
||||||
|
"from typing import Literal\n",
|
||||||
|
"import statistics\n",
|
||||||
|
"import gymnasium \n",
|
||||||
|
"\n",
|
||||||
|
"rng = np.random.default_rng()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 206,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def build_dqn(n_actions, input_dims, fc1_dims, fc2_dims):\n",
|
||||||
|
" model = tf.keras.Sequential([\n",
|
||||||
|
" tf.keras.layers.Dense(fc1_dims, activation=tf.keras.activations.relu, input_shape=(input_dims,)),\n",
|
||||||
|
" tf.keras.layers.Dense(fc2_dims, activation=tf.keras.activations.relu),\n",
|
||||||
|
" tf.keras.layers.Dense(n_actions)\n",
|
||||||
|
" ])\n",
|
||||||
|
" model.compile()\n",
|
||||||
|
"\n",
|
||||||
|
" return model"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 207,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"mutate_weights = np.vectorize(lambda w : w if rng.uniform() > 0.7 else w + rng.normal(scale=0.3))\n",
|
||||||
|
"\n",
|
||||||
|
"def mutate(agent: tf.keras.Sequential) -> None:\n",
|
||||||
|
" for layer in agent.layers:\n",
|
||||||
|
" w, b = layer.get_weights()\n",
|
||||||
|
" layer.set_weights([mutate_weights(w), b]) # don't touch biases\n",
|
||||||
|
"\n",
|
||||||
|
"def recombine(mother: tf.keras.Sequential, father: tf.keras.Sequential) -> tf.keras.Sequential:\n",
|
||||||
|
" parent = rng.choice((mother, father))\n",
|
||||||
|
" child = tf.keras.models.clone_model(parent)\n",
|
||||||
|
" child.build(4)\n",
|
||||||
|
" child.compile()\n",
|
||||||
|
" child.set_weights(parent.get_weights())\n",
|
||||||
|
" return child"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attachments": {},
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Warum ist es eventuell nicht die beste Lösung, jedes Individuum nur einmal zu testen?\n",
|
||||||
|
"Um die Fitness zu ermitteln, sollte jedes Individuum mehrmals getestet werden, da der Agent nicht immer gleich gut abschneidet. Hier gestaltet es sich sinnvoll, jedes Individuum mindestens 3 Mal zu testen und den Median-Score zu wählen um Ausreißer nach unten und oben aus der Wertung zu nehmen. Alternativ könnte auch der durchschnittliche Score sinnvoll sein.\n",
|
||||||
|
"\n",
|
||||||
|
"### Fällt Ihnen eine gute Methode für ein Crossover ein?\n",
|
||||||
|
"Ja! Tests haben ergeben, dass das Wählen eines zufälligen Elternteils sinnvoll ist und schnell berechnet werden kann. Danke an Melissa für den Tipp.\n",
|
||||||
|
"\n",
|
||||||
|
"### Welche Beobachtung machen Sie mit den unterschiedlichen Selektionsarten?\n",
|
||||||
|
"Hier die Avg-Scores des besten Individuums von jeweils 5 Durchgängen mit 20 Generationen, 30 Individuen und einer Selection von 15: \\\n",
|
||||||
|
"Avg-Score mit Elitist: 384, 500, 500, 500, 500 \\\n",
|
||||||
|
"Avg-Score mit Proportional: 500, 291, 500, 432, 188 \\\n",
|
||||||
|
"500 ist der maximale Score.\n",
|
||||||
|
"Für genauere Aussagen müsste man mehr Durchgänge durchführen. Da ein Durchgang >4min dauert, wurde darauf allerdings verzichtet.\n",
|
||||||
|
"Beide Selektionsarten haben oft das Optimum erreicht. In diesen Durchgängen hat Elitist bessere Ergebnisse erzielen können. Mit Elitist konnte 4 von 5 Mal der Optimum erreicht werden."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 212,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def fitness(agent: tf.keras.Sequential, n: int = 3) -> int:\n",
|
||||||
|
" env = gymnasium.make('CartPole-v1')\n",
|
||||||
|
"\n",
|
||||||
|
" scores = []\n",
|
||||||
|
"\n",
|
||||||
|
" for _ in range(n):\n",
|
||||||
|
" done = False\n",
|
||||||
|
" score = 0\n",
|
||||||
|
" observation, _ = env.reset()\n",
|
||||||
|
" while not done:\n",
|
||||||
|
" actions = agent(observation[np.newaxis, :])\n",
|
||||||
|
" action = np.argmax(actions)\n",
|
||||||
|
" observation, reward, terminated, truncated, _ = env.step(action)\n",
|
||||||
|
" done = terminated or truncated\n",
|
||||||
|
" score += reward\n",
|
||||||
|
"\n",
|
||||||
|
" scores.append(score)\n",
|
||||||
|
"\n",
|
||||||
|
" return statistics.median(scores)\n",
|
||||||
|
" \n",
|
||||||
|
"\n",
|
||||||
|
"def evolution(\n",
|
||||||
|
" generations: int = 50,\n",
|
||||||
|
" population_size: int = 20,\n",
|
||||||
|
" selection_size: int = 5,\n",
|
||||||
|
" selection_type: Literal[\"elitist\"] | Literal[\"proportional\"] = \"elitist\",\n",
|
||||||
|
"):\n",
|
||||||
|
" assert selection_type in (\"elitist\", \"proportional\")\n",
|
||||||
|
" population = [build_dqn(2, 4, 5, 5) for _ in range(population_size)]\n",
|
||||||
|
"\n",
|
||||||
|
" for _ in trange(generations):\n",
|
||||||
|
" # Select individuals with highest fitness for reproduction\n",
|
||||||
|
" population = sorted(population, key=lambda agent: fitness(agent), reverse=True)\n",
|
||||||
|
"\n",
|
||||||
|
" if selection_type == \"elitist\":\n",
|
||||||
|
" selection = population[:selection_size]\n",
|
||||||
|
" elif selection_type == \"proportional\":\n",
|
||||||
|
" population_fitness = [fitness(nn) for nn in population]\n",
|
||||||
|
" selection = np.random.choice(\n",
|
||||||
|
" population,\n",
|
||||||
|
" selection_size,\n",
|
||||||
|
" p=[f / sum(population_fitness) for f in population_fitness],\n",
|
||||||
|
" replace=False\n",
|
||||||
|
" ).tolist()\n",
|
||||||
|
"\n",
|
||||||
|
" # Reproduce\n",
|
||||||
|
" offsprings = []\n",
|
||||||
|
" for _ in range(population_size - selection_size):\n",
|
||||||
|
" mother, father = sample(selection, 2)\n",
|
||||||
|
"\n",
|
||||||
|
" offspring = recombine(mother, father)\n",
|
||||||
|
" mutate(offspring)\n",
|
||||||
|
" \n",
|
||||||
|
" offsprings.append(offspring)\n",
|
||||||
|
"\n",
|
||||||
|
" # Create new population\n",
|
||||||
|
" population = selection + offsprings\n",
|
||||||
|
" \n",
|
||||||
|
" # Return best individual of final population\n",
|
||||||
|
" return max(population, key=lambda agent: fitness(agent))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 222,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"100%|██████████| 20/20 [02:08<00:00, 6.41s/it]\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"episode 0 score 500.00 average score 500.00\n",
|
||||||
|
"episode 1 score 500.00 average score 500.00\n",
|
||||||
|
"episode 2 score 500.00 average score 500.00\n",
|
||||||
|
"episode 3 score 500.00 average score 500.00\n",
|
||||||
|
"episode 4 score 500.00 average score 500.00\n",
|
||||||
|
"episode 5 score 500.00 average score 500.00\n",
|
||||||
|
"episode 6 score 500.00 average score 500.00\n",
|
||||||
|
"episode 7 score 500.00 average score 500.00\n",
|
||||||
|
"episode 8 score 500.00 average score 500.00\n",
|
||||||
|
"episode 9 score 500.00 average score 500.00\n",
|
||||||
|
"episode 10 score 133.00 average score 466.64\n",
|
||||||
|
"episode 11 score 500.00 average score 469.42\n",
|
||||||
|
"episode 12 score 106.00 average score 441.46\n",
|
||||||
|
"episode 13 score 500.00 average score 445.64\n",
|
||||||
|
"episode 14 score 128.00 average score 424.47\n",
|
||||||
|
"episode 15 score 500.00 average score 429.19\n",
|
||||||
|
"episode 16 score 500.00 average score 433.35\n",
|
||||||
|
"episode 17 score 500.00 average score 437.06\n",
|
||||||
|
"episode 18 score 500.00 average score 440.37\n",
|
||||||
|
"episode 19 score 500.00 average score 443.35\n",
|
||||||
|
"episode 20 score 500.00 average score 446.05\n",
|
||||||
|
"episode 21 score 500.00 average score 448.50\n",
|
||||||
|
"episode 22 score 500.00 average score 450.74\n",
|
||||||
|
"episode 23 score 500.00 average score 452.79\n",
|
||||||
|
"episode 24 score 118.00 average score 439.40\n",
|
||||||
|
"episode 25 score 500.00 average score 441.73\n",
|
||||||
|
"episode 26 score 500.00 average score 443.89\n",
|
||||||
|
"episode 27 score 500.00 average score 445.89\n",
|
||||||
|
"episode 28 score 117.00 average score 434.55\n",
|
||||||
|
"episode 29 score 500.00 average score 436.73\n",
|
||||||
|
"episode 30 score 500.00 average score 438.77\n",
|
||||||
|
"episode 31 score 500.00 average score 440.69\n",
|
||||||
|
"episode 32 score 123.00 average score 431.06\n",
|
||||||
|
"episode 33 score 189.00 average score 423.94\n",
|
||||||
|
"episode 34 score 500.00 average score 426.11\n",
|
||||||
|
"episode 35 score 500.00 average score 428.17\n",
|
||||||
|
"episode 36 score 106.00 average score 419.46\n",
|
||||||
|
"episode 37 score 500.00 average score 421.58\n",
|
||||||
|
"episode 38 score 500.00 average score 423.59\n",
|
||||||
|
"episode 39 score 500.00 average score 425.50\n",
|
||||||
|
"episode 40 score 500.00 average score 427.32\n",
|
||||||
|
"episode 41 score 500.00 average score 429.05\n",
|
||||||
|
"episode 42 score 500.00 average score 430.70\n",
|
||||||
|
"episode 43 score 500.00 average score 432.27\n",
|
||||||
|
"episode 44 score 138.00 average score 425.73\n",
|
||||||
|
"episode 45 score 500.00 average score 427.35\n",
|
||||||
|
"episode 46 score 500.00 average score 428.89\n",
|
||||||
|
"episode 47 score 500.00 average score 430.38\n",
|
||||||
|
"episode 48 score 500.00 average score 431.80\n",
|
||||||
|
"episode 49 score 500.00 average score 433.16\n",
|
||||||
|
"episode 50 score 459.00 average score 433.67\n",
|
||||||
|
"episode 51 score 500.00 average score 434.94\n",
|
||||||
|
"episode 52 score 500.00 average score 436.17\n",
|
||||||
|
"episode 53 score 500.00 average score 437.35\n",
|
||||||
|
"episode 54 score 500.00 average score 438.49\n",
|
||||||
|
"episode 55 score 157.00 average score 433.46\n",
|
||||||
|
"episode 56 score 500.00 average score 434.63\n",
|
||||||
|
"episode 57 score 500.00 average score 435.76\n",
|
||||||
|
"episode 58 score 164.00 average score 431.15\n",
|
||||||
|
"episode 59 score 112.00 average score 425.83\n",
|
||||||
|
"episode 60 score 346.00 average score 424.52\n",
|
||||||
|
"episode 61 score 500.00 average score 425.74\n",
|
||||||
|
"episode 62 score 500.00 average score 426.92\n",
|
||||||
|
"episode 63 score 500.00 average score 428.06\n",
|
||||||
|
"episode 64 score 500.00 average score 429.17\n",
|
||||||
|
"episode 65 score 500.00 average score 430.24\n",
|
||||||
|
"episode 66 score 500.00 average score 431.28\n",
|
||||||
|
"episode 67 score 140.00 average score 427.00\n",
|
||||||
|
"episode 68 score 500.00 average score 428.06\n",
|
||||||
|
"episode 69 score 500.00 average score 429.09\n",
|
||||||
|
"episode 70 score 500.00 average score 430.08\n",
|
||||||
|
"episode 71 score 500.00 average score 431.06\n",
|
||||||
|
"episode 72 score 500.00 average score 432.00\n",
|
||||||
|
"episode 73 score 500.00 average score 432.92\n",
|
||||||
|
"episode 74 score 500.00 average score 433.81\n",
|
||||||
|
"episode 75 score 500.00 average score 434.68\n",
|
||||||
|
"episode 76 score 500.00 average score 435.53\n",
|
||||||
|
"episode 77 score 500.00 average score 436.36\n",
|
||||||
|
"episode 78 score 477.00 average score 436.87\n",
|
||||||
|
"episode 79 score 500.00 average score 437.66\n",
|
||||||
|
"episode 80 score 130.00 average score 433.86\n",
|
||||||
|
"episode 81 score 500.00 average score 434.67\n",
|
||||||
|
"episode 82 score 394.00 average score 434.18\n",
|
||||||
|
"episode 83 score 500.00 average score 434.96\n",
|
||||||
|
"episode 84 score 500.00 average score 435.73\n",
|
||||||
|
"episode 85 score 500.00 average score 436.48\n",
|
||||||
|
"episode 86 score 500.00 average score 437.21\n",
|
||||||
|
"episode 87 score 500.00 average score 437.92\n",
|
||||||
|
"episode 88 score 500.00 average score 438.62\n",
|
||||||
|
"episode 89 score 500.00 average score 439.30\n",
|
||||||
|
"episode 90 score 500.00 average score 439.97\n",
|
||||||
|
"episode 91 score 500.00 average score 440.62\n",
|
||||||
|
"episode 92 score 140.00 average score 437.39\n",
|
||||||
|
"episode 93 score 500.00 average score 438.05\n",
|
||||||
|
"episode 94 score 500.00 average score 438.71\n",
|
||||||
|
"episode 95 score 500.00 average score 439.34\n",
|
||||||
|
"episode 96 score 179.00 average score 436.66\n",
|
||||||
|
"episode 97 score 282.00 average score 435.08\n",
|
||||||
|
"episode 98 score 500.00 average score 435.74\n",
|
||||||
|
"episode 99 score 108.00 average score 432.46\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"env = gymnasium.make('CartPole-v1')\n",
|
||||||
|
"agent = evolution(generations=20, population_size=30, selection_size=15, selection_type='elitist')\n",
|
||||||
|
"scores = []\n",
|
||||||
|
"\n",
|
||||||
|
"for i in range(100):\n",
|
||||||
|
" done = False\n",
|
||||||
|
" score = 0\n",
|
||||||
|
" observation, info = env.reset()\n",
|
||||||
|
" while not done:\n",
|
||||||
|
" action = np.argmax(agent(observation[np.newaxis, :]))\n",
|
||||||
|
" observation, reward, terminated, truncated, info = env.step(action)\n",
|
||||||
|
" done = terminated or truncated\n",
|
||||||
|
" score += reward\n",
|
||||||
|
"\n",
|
||||||
|
" scores.append(score)\n",
|
||||||
|
" \n",
|
||||||
|
"\n",
|
||||||
|
" avg_score = np.mean(scores[max(0, i-100):(i+1)])\n",
|
||||||
|
" print('episode ', i, 'score %.2f' % score, 'average score %.2f' % avg_score)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.10.12"
|
||||||
|
},
|
||||||
|
"orig_nbformat": 4,
|
||||||
|
"vscode": {
|
||||||
|
"interpreter": {
|
||||||
|
"hash": "bd385fe162c5ca0c84973b7dd5c518456272446b2b64e67c2a69f949ca7a1754"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 2
|
||||||
|
}
|
||||||
BIN
Aufgabe 6/dqn_model.keras
Normal file
BIN
Aufgabe 6/dqn_model.keras
Normal file
Binary file not shown.
Reference in New Issue
Block a user