10 Commits
TF20 ... TF113

19 changed files with 954 additions and 367 deletions

View File

@ -2,13 +2,17 @@ import os
import time
import dill
from tqdm import tqdm
import copy
from copy import copy
from tensorflow.python.keras import backend as K
from abc import ABC, abstractmethod
class IllegalArgumentError(ValueError):
pass
class Experiment(ABC):
@staticmethod
@ -20,15 +24,24 @@ class Experiment(ABC):
def reset_model():
K.clear_session()
def __init__(self, name=None, ident=None):
def __init__(self, name=None, ident=None, **kwargs):
self.experiment_id = f'{ident or ""}_{time.time()}'
self.experiment_name = name or 'unnamed_experiment'
self.next_iteration = 0
self.iteration = 0
self.log_messages = list()
self.historical_particles = dict()
self.params = dict(exp_iterations=100, application_steps=100, prints=True, trains_per_application=100)
self.with_params(**kwargs)
def __copy__(self, *args, **kwargs):
params = self.params
params.update(name=self.experiment_name)
params.update(**kwargs)
self_copy = self.__class__(*args, **params)
return self_copy
def __enter__(self):
self.dir = os.path.join('experiments', f'exp-{self.experiment_name}-{self.experiment_id}-{self.next_iteration}')
self.dir = os.path.join('experiments', f'exp-{self.experiment_name}-{self.experiment_id}-{self.iteration}')
os.makedirs(self.dir)
print(f'** created {self.dir} **')
return self
@ -36,7 +49,14 @@ class Experiment(ABC):
def __exit__(self, exc_type, exc_value, traceback):
self.save(experiment=self.without_particles())
self.save_log()
self.next_iteration += 1
# Clean Exit
self.reset_all()
# self.iteration += 1 Taken From here!
def with_params(self, **kwargs):
# Make them your own
self.params.update(kwargs)
return self
def log(self, message, **kwargs):
self.log_messages.append(message)
@ -47,14 +67,13 @@ class Experiment(ABC):
for log_message in self.log_messages:
print(str(log_message), file=log_file)
def __copy__(self):
self_copy = self.__class__(name=self.experiment_name,)
self_copy.__dict__ = {attr: self.__dict__[attr] for attr in self.__dict__ if
attr not in ['particles', 'historical_particles']}
return self_copy
def without_particles(self):
self_copy = copy.copy(self)
self_copy = copy(self)
# Check if attribute exists
if hasattr(self, 'historical_particles'):
# Check if it is empty.
if self.historical_particles:
# Do the Update
# self_copy.particles = [particle.states for particle in self.particles]
self_copy.historical_particles = {key: val.states for key, val in self.historical_particles.items()}
return self_copy
@ -64,28 +83,32 @@ class Experiment(ABC):
with open(os.path.join(self.dir, f"{name}.dill"), "wb") as dill_file:
dill.dump(value, dill_file)
def reset_log(self):
self.log_messages = list()
@abstractmethod
def run_net(self, net, trains_per_application=100, step_limit=100, run_id=0, **kwargs):
def run_net(self, net, **kwargs):
raise NotImplementedError
pass
def run_exp(self, network_generator, exp_iterations, prints=False, **kwargs):
def run_exp(self, network_generator, reset_model=False, **kwargs):
# INFO Run_ID needs to be more than 0, so that exp stores the trajectories!
for run_id in range(exp_iterations):
for run_id in range(self.params.get('exp_iterations')):
network = network_generator()
self.run_net(network, 100, run_id=run_id + 1, **kwargs)
self.run_net(network, **kwargs)
self.historical_particles[run_id] = network
if prints:
if self.params.get('prints'):
print("Fixpoint? " + str(network.is_fixpoint()))
self.iteration += 1
if reset_model:
self.reset_model()
def reset_all(self):
self.reset_log()
self.reset_model()
class FixpointExperiment(Experiment):
if kwargs.get('logging', False):
self.log(self.counters)
def __init__(self, **kwargs):
kwargs['name'] = self.__class__.__name__ if 'name' not in kwargs else kwargs['name']
@ -93,12 +116,22 @@ class FixpointExperiment(Experiment):
self.counters = dict(divergent=0, fix_zero=0, fix_other=0, fix_sec=0, other=0)
self.interesting_fixpoints = []
def run_net(self, net, step_limit=100, run_id=0, **kwargs):
i = 0
while i < step_limit and not net.is_diverged() and not net.is_fixpoint():
net.self_attack()
i += 1
if run_id:
def run_exp(self, network_generator, logging=True, reset_model=False, **kwargs):
kwargs.update(reset_model=False)
super(FixpointExperiment, self).run_exp(network_generator, **kwargs)
if logging:
self.log(self.counters)
if reset_model:
self.reset_model()
def run_net(self, net, **kwargs):
if len(kwargs):
raise IllegalArgumentError
for i in range(self.params.get('application_steps')):
if net.is_diverged() or net.is_fixpoint():
break
net.set_weights(net.apply_to_weights(net.get_weights()))
if self.iteration and hasattr(self, 'save_state'):
net.save_state(time=i)
self.count(net)
@ -128,44 +161,117 @@ class FixpointExperiment(Experiment):
class MixedFixpointExperiment(FixpointExperiment):
def run_net(self, net, trains_per_application=100, step_limit=100, run_id=0, **kwargs):
for i in range(step_limit):
def __init__(self, **kwargs):
kwargs['name'] = self.__class__.__name__ if 'name' not in kwargs else kwargs['name']
super(MixedFixpointExperiment, self).__init__(**kwargs)
def run_net(self, net, **kwargs):
assert hasattr(net, 'train'), 'This Network must be trainable, i.e. use the "TrainingNeuralNetworkDecorator"!'
for application in range(self.params.get('application_steps')):
epoch_num = self.params.get('trains_per_application') * application
net.set_weights(net.apply_to_weights(net.get_weights()))
if net.is_diverged() or net.is_fixpoint():
break
net.self_attack()
with tqdm(postfix=["Loss", dict(value=0)]) as bar:
for _ in range(trains_per_application):
loss = net.compiled().train()
bar.postfix[1]["value"] = loss
barformat = "Experiment Iteration: {postfix[iteration]} | "
barformat += "Evolution Step:{postfix[step]}| "
barformat += "Training Epoch:{postfix[epoch]}| "
barformat += "Loss: {postfix[loss]} | {bar}"
with tqdm(total=self.params.get('trains_per_application'),
postfix={'step': 0, 'loss': 0, 'iteration': self.iteration, 'epoch': 0, None: None},
bar_format=barformat) as bar:
# This iterates for self.trains_per_application times, the addition is just for epoch enumeration
for epoch in range(epoch_num, epoch_num + self.params.get('trains_per_application')):
if net.is_diverged():
print('Network diverged to either inf or nan... breaking')
break
loss = net.train(epoch=epoch)
if epoch % 10 == 0:
bar.postfix.update(step=application, epoch=epoch, loss=loss, iteration=self.iteration)
bar.update()
if run_id:
epoch_num += 1
if self.iteration and hasattr(net, 'save_sate'):
net.save_state()
self.count(net)
class SoupExperiment(Experiment):
class TaskExperiment(MixedFixpointExperiment):
def __init__(self, **kwargs):
super(SoupExperiment, self).__init__(name=kwargs.get('name', self.__class__.__name__))
kwargs['name'] = self.__class__.__name__ if 'name' not in kwargs else kwargs['name']
super(TaskExperiment, self).__init__(**kwargs)
def run_exp(self, network_generator, exp_iterations, soup_generator=None, soup_iterations=0, prints=False):
for i in range(soup_iterations):
soup = soup_generator()
def run_exp(self, network_generator, reset_model=False, logging=True, **kwargs):
kwargs.update(reset_model=False, logging=logging)
super(FixpointExperiment, self).run_exp(network_generator, **kwargs)
if reset_model:
self.reset_model()
pass
def run_net(self, net, **kwargs):
assert hasattr(net, 'evaluate')
super(TaskExperiment, self).run_net(net, **kwargs)
# Get Performance without Training
task_performance = net.evaluate(*net.get_samples(task_samples=True),
batchsize=net.get_amount_of_weights())
self_performance = net.evaluate(*net.get_samples(self_samples=True),
batchsize=net.get_amount_of_weights())
current_performance = dict(task_performance=task_performance,
self_performance=self_performance,
counters=self.counters, id=self.iteration)
self.log(current_performance)
pass
class SoupExperiment(Experiment):
def __init__(self, soup_generator, **kwargs):
kwargs['name'] = self.__class__.__name__ if 'name' not in kwargs else kwargs['name']
self.soup_generator = soup_generator
super(SoupExperiment, self).__init__(**kwargs)
def run_exp(self, network_generator, **kwargs):
for i in range(self.params.get('exp_iterations')):
soup = self.soup_generator()
soup.seed()
for _ in tqdm(exp_iterations):
for _ in tqdm(range(self.params.get('application_steps'))):
soup.evolve()
self.log(soup.count())
self.save(soup=soup.without_particles())
K.clear_session()
def run_net(self, net, trains_per_application=100, step_limit=100, run_id=0, **kwargs):
def run_net(self, net, **kwargs):
raise NotImplementedError
pass
class IdentLearningExperiment(Experiment):
class TaskingSoupExperiment(Experiment):
def __init__(self):
super(IdentLearningExperiment, self).__init__(name=self.__class__.__name__)
def __init__(self, soup_generator, **kwargs):
kwargs['name'] = self.__class__.__name__ if 'name' not in kwargs else kwargs['name']
super(TaskingSoupExperiment, self).__init__(**kwargs)
self.soup_generator = soup_generator
def run_net(self, net, trains_per_application=100, step_limit=100, run_id=0, **kwargs):
def __copy__(self):
super(TaskingSoupExperiment, self).__copy__(self.soup_generator)
def run_exp(self, **kwargs):
for i in range(self.params.get('exp_iterations')):
soup = self.soup_generator()
soup.seed()
for _ in tqdm(range(self.params.get('application_steps'))):
soup.evolve()
self.log(soup.count())
self.save(soup=soup.without_particles())
K.clear_session()
def run_net(self, net, **kwargs):
raise NotImplementedError()
pass
if __name__ == '__main__':
pass

View File

@ -1,14 +1,26 @@
# Librarys
import numpy as np
from abc import abstractmethod, ABC
from typing import List, Union
from typing import List, Tuple
from types import FunctionType
import warnings
import os
# Functions and Operators
from operator import mul
from functools import reduce
from itertools import accumulate
from copy import deepcopy
# Deep learning Framework
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.callbacks import Callback
from tensorflow.python.keras.layers import SimpleRNN, Dense
from tensorflow.python.keras import backend as K
from experiment import *
# Experiment Class
from task import TaskAdditionOfN
from experiment import TaskExperiment
# Supress warnings and info messages
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
@ -28,104 +40,23 @@ class SaveStateCallback(Callback):
return
class Weights:
class EarlyStoppingByInfNanLoss(Callback):
def __init__(self, monitor='loss', verbose=0):
super(Callback, self).__init__()
self.monitor = monitor
self.verbose = verbose
@staticmethod
def __reshape_flat_array__(array, shapes):
sizes: List[int] = [int(np.prod(shape)) for shape in shapes]
# Split the incoming array into slices for layers
slices = [array[x: y] for x, y in zip(np.cumsum([0]+sizes), np.cumsum([0]+sizes)[1:])]
# reshape them in accordance to the given shapes
weights = [np.reshape(weight_slice, shape) for weight_slice, shape in zip(slices, shapes)]
return weights
def on_epoch_end(self, epoch, logs: dict = None):
logs = logs or dict()
current = logs.get(self.monitor)
if current is None:
warnings.warn(f'Early stopping requires {self.monitor} available!', RuntimeWarning)
pass
def __init__(self, weight_vector: Union[List[np.ndarray], np.ndarray], flat_array_shape=None):
"""
Weight class, for easy manipulation of weight vectors from Keras models
:param weight_vector: A numpy array holding weights
:type weight_vector: List[np.ndarray]
"""
self.__iter_idx = [0, 0]
if flat_array_shape:
weight_vector = self.__reshape_flat_array__(weight_vector, flat_array_shape)
self.layers = weight_vector
# TODO: implement a way to access the cells directly
# self.cells = len(self)
# TODO: implement a way to access the weights directly
# self.weights = self.to_flat_array() ?
def __iter__(self):
self.__iter_idx = [0, 0]
return self
def __getitem__(self, item):
return self.layers[item]
def max(self):
np.max(self.layers)
def avg(self):
return np.average(self.layers)
def __len__(self):
return sum([x.size for x in self.layers])
def shapes(self):
return [x.shape for x in self.layers]
def num_layers(self):
return len(self.layers)
def __copy__(self):
return copy.deepcopy(self)
def __next__(self):
# ToDo: Check iteration progress over layers
# ToDo: There is still a problem interation, currently only cell level is the last loop stage.
# Do we need this?
if self.__iter_idx[0] >= len(self.layers):
if self.__iter_idx[1] >= len(self.layers[self.__iter_idx[0]]):
raise StopIteration
result = self.layers[self.__iter_idx[0]][self.__iter_idx[1]]
if self.__iter_idx[1] >= len(self.layers[self.__iter_idx[0]]):
self.__iter_idx[0] += 1
self.__iter_idx[1] = 0
else:
self.__iter_idx[1] += 1
return result
def __repr__(self):
return f'Weights({self.to_flat_array().tolist()})'
def to_flat_array(self) -> np.ndarray:
return np.hstack([weight.flatten() for weight in self.layers])
def from_flat_array(self, array):
new_weights = self.__reshape_flat_array__(array, self.shapes())
return new_weights
def shuffle(self):
flat = self.to_flat_array()
np.random.shuffle(flat)
self.from_flat_array(flat)
return True
def are_diverged(self):
return any([np.isnan(x).any() for x in self.layers]) or any([np.isinf(x).any() for x in self.layers])
def are_within_bounds(self, lower_bound: float, upper_bound: float):
return bool(sum([((lower_bound < x) & (x > upper_bound)).size for x in self.layers]))
def aggregate_by(self, func: FunctionType, num_aggregates):
collection_sizes = len(self) // num_aggregates
weights = self.to_flat_array()[:collection_sizes * num_aggregates].reshape((num_aggregates, -1))
aggregated_weights = func(weights, num_aggregates)
left_overs = self.to_flat_array()[collection_sizes * num_aggregates:]
return aggregated_weights, left_overs
if np.isnan(current) or np.isinf(current):
if self.verbose > 0:
print(f'Epoch {epoch}: early stopping THR')
self.model.stop_training = True
class NeuralNetwork(ABC):
@ -133,10 +64,50 @@ class NeuralNetwork(ABC):
This is the Base Network Class, including abstract functions that must be implemented.
"""
@staticmethod
def are_weights_diverged(weights: List[np.ndarray]) -> bool:
return any([any((np.isnan(x).any(), np.isinf(x).any())) for x in weights])
@staticmethod
def are_weights_within_bounds(weights: List[np.ndarray], lower_bound: float, upper_bound: float) -> bool:
return any([((lower_bound < x) & (x < upper_bound)).any() for x in weights])
@staticmethod
def get_weight_amount(weights: List[np.ndarray]):
return sum([x.size for x in weights])
@staticmethod
def shapes(weights: List[np.ndarray]):
return [x.shape for x in weights]
@staticmethod
def num_layers(weights: List[np.ndarray]):
return len(weights)
def repr(self, weights: List[np.ndarray]):
return f'Weights({self.weights_to_flat_array(weights).tolist()})'
@staticmethod
def weights_to_flat_array(weights: List[np.ndarray]) -> np.ndarray:
return np.concatenate([d.ravel() for d in weights])
@staticmethod
def reshape_flat_array(array, shapes: List[Tuple[int]]) -> List[np.ndarray]:
# Same thing, but with an additional np call
# sizes: List[int] = [int(np.prod(shape)) for shape in shapes]
sizes = [reduce(mul, shape) for shape in shapes]
# Split the incoming array into slices for layers
slices = [array[x: y] for x, y in zip(accumulate([0] + sizes), accumulate(sizes))]
# reshape them in accordance to the given shapes
weights = [np.reshape(weight_slice, shape) for weight_slice, shape in zip(slices, shapes)]
return weights
def __init__(self, **params):
super().__init__()
self.params = dict(epsilon=0.00000000000001)
self.params = dict(epsilon=0.00000000000001, early_nan_stopping=True, store_states=False)
self.params.update(params)
self.name = params.get('name', self.__class__.__name__)
self.keras_params = dict(activation='linear', use_bias=False)
self.states = []
self.model: Sequential
@ -155,76 +126,89 @@ class NeuralNetwork(ABC):
self.keras_params.update(kwargs)
return self
def get_weights(self) -> Weights:
return Weights(self.model.get_weights())
def print_weights(self, weights=None):
print(self.repr(weights or self.get_weights()))
def get_amount_of_weights(self):
return self.get_weight_amount(self.get_weights())
def get_model(self):
return self.model
def get_weights(self) -> List[np.ndarray]:
return self.get_model().get_weights()
def get_weights_flat(self) -> np.ndarray:
return self.get_weights().to_flat_array()
return self.weights_to_flat_array(self.get_weights())
def set_weights(self, new_weights: Weights):
return self.model.set_weights(new_weights.layers)
def reshape_flat_array_like(self, array, weights: List[np.ndarray]) -> List[np.ndarray]:
return self.reshape_flat_array(array, self.shapes(weights))
@abstractmethod
def get_samples(self):
# TODO: add a dogstring, telling the user what this does, e.g. what is a sample?
raise NotImplementedError
def set_weights(self, new_weights: List[np.ndarray]):
return self.model.set_weights(new_weights)
@abstractmethod
def apply_to_weights(self, old_weights) -> Weights:
# TODO: add a dogstring, telling the user what this does, e.g. what is applied?
raise NotImplementedError
def apply_to_network(self, other_network) -> Weights:
# TODO: add a dogstring, telling the user what this does, e.g. what is applied?
def apply_to_network(self, other_network) -> List[np.ndarray]:
"""
Take a networks weights and apply _this_ networks function.
:param other_network:
:return:
"""
new_weights = self.apply_to_weights(other_network.get_weights())
return new_weights
def attack(self, other_network):
# TODO: add a dogstring, telling the user what this does, e.g. what is an attack?
other_network.set_weights(self.apply_to_network(other_network))
return self
def fuck(self, other_network):
# TODO: add a dogstring, telling the user what this does, e.g. what is fucking?
self.set_weights(self.apply_to_network(other_network))
return self
def self_attack(self, iterations=1):
# TODO: add a dogstring, telling the user what this does, e.g. what is self attack?
for _ in range(iterations):
self.attack(self)
return self
def meet(self, other_network):
# TODO: add a dogstring, telling the user what this does, e.g. what is meeting?
new_other_network = copy.deepcopy(other_network)
return self.attack(new_other_network)
def is_diverged(self):
return self.get_weights().are_diverged()
return self.are_weights_diverged(self.get_weights())
def is_zero(self, epsilon=None):
epsilon = epsilon or self.get_params().get('epsilon')
return self.get_weights().are_within_bounds(-epsilon, epsilon)
return self.are_weights_within_bounds(self.get_weights(), -epsilon, epsilon)
def is_fixpoint(self, degree: int = 1, epsilon: float = None) -> bool:
assert degree >= 1, "degree must be >= 1"
epsilon = epsilon or self.get_params().get('epsilon')
new_weights = copy.deepcopy(self.get_weights())
new_weights = deepcopy(self.get_weights())
for _ in range(degree):
new_weights = self.apply_to_weights(new_weights)
if new_weights.are_diverged():
if self.are_weights_diverged(new_weights):
return False
biggerEpsilon = (np.abs(new_weights.to_flat_array() - self.get_weights().to_flat_array()) >= epsilon).any()
flat_new = self.weights_to_flat_array(new_weights)
flat_old = self.weights_to_flat_array(self.get_weights())
biggerEpsilon = (np.abs(flat_new - flat_old) >= epsilon).any()
# Boolean Value needs to be flipped to answer "is_fixpoint"
return not biggerEpsilon
def print_weights(self, weights=None):
print(weights or self.get_weights())
def aggregate_weights_by(self, weights: List[np.ndarray], func: FunctionType, num_aggregates: int):
collection_sizes = self.get_weight_amount(weights) // num_aggregates
flat = self.weights_to_flat_array(weights)
array_for_aggregation = flat[:collection_sizes * num_aggregates].reshape((num_aggregates, -1))
left_overs = flat[collection_sizes * num_aggregates:]
aggregated_weights = func(array_for_aggregation, num_aggregates)
return aggregated_weights, left_overs
def shuffle_weights(self, weights: List[np.ndarray]):
flat = self.weights_to_flat_array(weights)
np.random.shuffle(flat)
return self.reshape_flat_array_like(flat, weights)
@abstractmethod
def get_samples(self, **kwargs):
# TODO: add a dogstring, telling the user what this does, e.g. what is a sample?
raise NotImplementedError
@abstractmethod
def apply_to_weights(self, old_weights) -> List[np.ndarray]:
"""
Take weights as inputs; retunr the evaluation of _this_ network.
"Apply this function".
:param old_weights:
:return:
"""
raise NotImplementedError
class ParticleDecorator:
@ -273,6 +257,63 @@ class ParticleDecorator:
def get_states(self):
return self.states
def attack(self, other_network, iterations: int = 1):
"""
Set a networks weights based on the output of the application of my function to its weights.
"Alter a networks weights based on my evaluation"
:param other_network:
:param iterations:
:return:
"""
for _ in range(iterations):
other_network.set_weights(self.apply_to_network(other_network))
return self
def self_attack(self, iterations: int = 1):
"""
Set my weights based on the output of the application of my function to its weights.
"Alter my network weights based on my evaluation"
:param iterations:
:return:
"""
for _ in range(iterations):
self.attack(self)
return self
class TaskDecorator(TaskAdditionOfN):
def __init__(self, network, **kwargs):
super(TaskDecorator, self).__init__(**kwargs)
self.network = network
self.batchsize = self.network.get_amount_of_weights()
def __getattr__(self, name):
return getattr(self.network, name)
def get_samples(self, task_samples=False, self_samples=False, **kwargs):
# XOR, cannot be true at the same time
assert not all([task_samples, self_samples])
if task_samples:
return super(TaskDecorator, self).get_samples()
elif self_samples:
return self.network.get_samples()
else:
self_x, self_y = self.network.get_samples()
# Super class = Task
task_x, task_y = super(TaskDecorator, self).get_samples()
amount_of_weights = self.network.get_amount_of_weights()
random_idx = np.random.choice(np.arange(amount_of_weights), amount_of_weights//2)
x = self_x[random_idx] = task_x[random_idx]
y = self_y[random_idx] = task_y[random_idx]
return x, y
class WeightwiseNeuralNetwork(NeuralNetwork):
@ -291,23 +332,23 @@ class WeightwiseNeuralNetwork(NeuralNetwork):
# TODO: Write about it... What does it do?
return self.model.predict(inputs)
def get_samples(self):
weights = self.get_weights()
def get_samples(self, **kwargs: List[np.ndarray]):
weights = kwargs.get('weights', self.get_weights())
sample = np.asarray([
[weight, idx, *x] for idx, layer in enumerate(weights.layers) for x, weight in np.ndenumerate(layer)
[weight, idx, *x] for idx, layer in enumerate(weights) for x, weight in np.ndenumerate(layer)
])
# normalize [layer, cell, position]
for idx in range(1, sample.shape[1]):
sample[:, idx] = sample[:, idx] / np.max(sample[:, idx])
return sample, sample
return sample, sample[:, 0]
def apply_to_weights(self, weights) -> Weights:
def apply_to_weights(self, weights) -> List[np.ndarray]:
# ToDo: Insert DocString
# Transform the weight matrix in an horizontal stack as: array([[weight, layer, cell, position], ...])
transformed_weights = self.get_samples()[0]
new_weights = self.apply(transformed_weights)
transformed_weights, _ = self.get_samples(weights=weights)
new_flat_weights = self.apply(transformed_weights)
# use the original weight shape to transform the new tensor
return Weights(new_weights, flat_array_shape=weights.shapes())
return self.reshape_flat_array_like(new_flat_weights, weights)
class AggregatingNeuralNetwork(NeuralNetwork):
@ -329,11 +370,10 @@ class AggregatingNeuralNetwork(NeuralNetwork):
@staticmethod
def deaggregate_identically(aggregate, amount):
# ToDo: Find a better way than using the a hardcoded [0]
return np.hstack([aggregate for _ in range(amount)])[0]
return np.repeat(aggregate, amount, axis=0)
@staticmethod
def shuffle_not(weights: Weights):
def shuffle_not(weights: List[np.ndarray]):
"""
Doesn't do a thing. f(x)
@ -344,9 +384,8 @@ class AggregatingNeuralNetwork(NeuralNetwork):
"""
return weights
@staticmethod
def shuffle_random(weights: Weights):
assert weights.shuffle()
def shuffle_random(self, weights: List[np.ndarray]):
weights = self.shuffle_weights(weights)
return weights
def __init__(self, aggregates, width, depth, **kwargs):
@ -369,17 +408,14 @@ class AggregatingNeuralNetwork(NeuralNetwork):
def get_shuffler(self):
return self.params.get('shuffler', self.shuffle_not)
def get_amount_of_weights(self):
return len(self.get_weights())
def apply(self, inputs):
# You need to add an dimension here... "..." copies array values
return self.model.predict(inputs[None, ...])
def get_aggregated_weights(self):
return self.get_weights().aggregate_by(self.get_aggregator(), self.aggregates)
return self.aggregate_weights_by(self.get_weights(), self.get_aggregator(), self.aggregates)
def apply_to_weights(self, old_weights) -> Weights:
def apply_to_weights(self, old_weights) -> List[np.ndarray]:
# build aggregations of old_weights
old_aggregations, leftovers = self.get_aggregated_weights()
@ -390,14 +426,14 @@ class AggregatingNeuralNetwork(NeuralNetwork):
new_aggregations = self.deaggregate_identically(new_aggregations, collection_sizes)
# generate new weights
# only include leftovers if there are some then coonvert them to Weight on base of th old shape
new_weights = Weights(new_aggregations if not leftovers.shape[0] else np.hstack((new_aggregations, leftovers)),
flat_array_shape=old_weights.shapes())
complete_weights = new_aggregations if not leftovers.shape[0] else np.hstack((new_aggregations, leftovers))
new_weights = self.reshape_flat_array_like(complete_weights, old_weights)
# maybe shuffle
new_weights = self.get_shuffler()(new_weights)
return new_weights
def get_samples(self):
def get_samples(self, **kwargs):
aggregations, _ = self.get_aggregated_weights()
# What did that do?
# sample = np.transpose(np.array([[aggregations[i]] for i in range(self.aggregates)]))
@ -408,11 +444,11 @@ class AggregatingNeuralNetwork(NeuralNetwork):
epsilon = epsilon or self.get_params().get('epsilon')
old_aggregations, _ = self.get_aggregated_weights()
new_weights = copy.deepcopy(self.get_weights())
new_weights = deepcopy(self.get_weights())
for _ in range(degree):
new_weights = self.apply_to_weights(new_weights)
if new_weights.are_diverged():
if self.are_weights_diverged(new_weights):
return False
new_aggregations, leftovers = self.get_aggregated_weights()
@ -427,7 +463,8 @@ class AggregatingNeuralNetwork(NeuralNetwork):
class RecurrentNeuralNetwork(NeuralNetwork):
def __init__(self, width, depth, **kwargs):
super().__init__(**kwargs)
raise NotImplementedError
super(RecurrentNeuralNetwork, self).__init__()
self.features = 1
self.width = width
self.depth = depth
@ -443,7 +480,7 @@ class RecurrentNeuralNetwork(NeuralNetwork):
def apply_to_weights(self, old_weights):
# build list from old weights
new_weights = copy.deepcopy(old_weights)
new_weights = deepcopy(old_weights)
old_weights_list = []
for layer_id, layer in enumerate(old_weights):
for cell_id, cell in enumerate(layer):
@ -500,7 +537,7 @@ class TrainingNeuralNetworkDecorator:
return self
def compile_model(self, **kwargs):
compile_params = copy.deepcopy(self.compile_params)
compile_params = deepcopy(self.compile_params)
compile_params.update(kwargs)
return self.network.model.compile(**compile_params)
@ -510,13 +547,30 @@ class TrainingNeuralNetworkDecorator:
self.model_compiled = True
return self
def train(self, batchsize=1, store_states=True, epoch=0):
def train(self, batchsize=1, epoch=0):
self.compiled()
x, y = self.network.get_samples()
savestatecallback = [SaveStateCallback(network=self, epoch=epoch)] if store_states else None
history = self.network.model.fit(x=x, y=y, epochs=epoch+1, verbose=0,
batch_size=batchsize, callbacks=savestatecallback,
initial_epoch=epoch)
callbacks = []
if self.get_params().get('store_states'):
callbacks.append(SaveStateCallback(network=self, epoch=epoch))
if self.get_params().get('early_nan_stopping'):
callbacks.append(EarlyStoppingByInfNanLoss())
# 'or' does not work on empty lists
callbacks = callbacks if callbacks else None
"""
Please Note:
epochs: Integer. Number of epochs to train the model.
An epoch is an iteration over the entire `x` and `y`
data provided.
Note that in conjunction with `initial_epoch`,
`epochs` is to be understood as "final epoch".
The model is not trained for a number of iterations
given by `epochs`, but merely until the epoch
of index `epochs` is reached."""
history = self.network.model.fit(x=x, y=y, initial_epoch=epoch, epochs=epoch+1, verbose=0,
batch_size=batchsize, callbacks=callbacks)
return history.history['loss'][-1]
def learn_from(self, other_network, batchsize=1):
@ -524,68 +578,81 @@ class TrainingNeuralNetworkDecorator:
other_network.compiled()
x, y = other_network.network.get_samples()
history = self.network.model.fit(x=x, y=y, verbose=0, batch_size=batchsize)
return history.history['loss'][-1]
def evaluate(self, x=None, y=None, batchsize=1):
self.compiled()
x, y = x, y if x is not None and y is not None else self.network.get_samples()
"""
Please Note:
epochs: Integer. Number of epochs to train the model.
An epoch is an iteration over the entire `x` and `y`
data provided.
Note that in conjunction with `initial_epoch`,
`epochs` is to be understood as "final epoch".
The model is not trained for a number of iterations
given by `epochs`, but merely until the epoch
of index `epochs` is reached."""
loss = self.network.model.evaluate(x=x, y=y, verbose=0, batch_size=batchsize)
return loss
if __name__ == '__main__':
if True:
# WeightWise Neural Network
net_generator = ParticleDecorator(WeightwiseNeuralNetwork(width=2, depth=2).with_keras_params(activation='linear'))
with FixpointExperiment() as exp:
exp.run_exp(net_generator, 10, logging=True)
exp.reset_all()
with TaskExperiment().with_params(application_steps=10, trains_per_application=1000, exp_iterations=30) as exp:
net_generator = lambda: TrainingNeuralNetworkDecorator(TaskDecorator(
WeightwiseNeuralNetwork(width=2, depth=2))
).with_keras_params(activation='linear')
exp.run_exp(net_generator, reset_model=True)
if False:
# Aggregating Neural Network
net_generator = ParticleDecorator(AggregatingNeuralNetwork(aggregates=4, width=2, depth=2).with_keras_params())
with FixpointExperiment() as exp:
exp.run_exp(net_generator, 10, logging=True)
net_generator = lambda: AggregatingNeuralNetwork(aggregates=4, width=2, depth=2)
with MixedFixpointExperiment() as exp:
exp.run_exp(net_generator, 10)
exp.reset_all()
if False:
# FFT Aggregation
net_generator = lambda: ParticleDecorator(
AggregatingNeuralNetwork(
aggregates=4, width=2, depth=2, aggregator=AggregatingNeuralNetwork.aggregate_fft
).with_keras_params(activation='linear'))
net_generator = lambda: AggregatingNeuralNetwork(
aggregates=4, width=2, depth=2, aggregator=AggregatingNeuralNetwork.aggregate_fft)
with FixpointExperiment() as exp:
for run_id in tqdm(range(10)):
exp.run_exp(net_generator, 1)
exp.run_exp(net_generator, 10)
exp.log(exp.counters)
exp.reset_model()
exp.reset_all()
if True:
if False:
# ok so this works quite realiably
run_count = 10000
net_generator = TrainingNeuralNetworkDecorator(
ParticleDecorator(WeightwiseNeuralNetwork(width=2, depth=2))
).with_params(epsilon=0.0001).with_keras_params(optimizer='sgd')
run_count = 1000
net_generator = lambda: TrainingNeuralNetworkDecorator(WeightwiseNeuralNetwork(
width=2, depth=2).with_params(epsilon=0.0001)).with_keras_params(optimizer='sgd')
with MixedFixpointExperiment() as exp:
for run_id in tqdm(range(run_count+1)):
exp.run_exp(net_generator, 1)
if run_id % 100 == 0:
exp.run_net(net_generator, 1)
exp.run_exp(net_generator, 1)
K.clear_session()
if False:
with FixpointExperiment() as exp:
run_count = 1000
net = TrainingNeuralNetworkDecorator(AggregatingNeuralNetwork(4, width=2, depth=2)).with_params(epsilon=0.1e-6)
run_count = 100
net = TrainingNeuralNetworkDecorator(
AggregatingNeuralNetwork(4, width=2, depth=2).with_params(epsilon=0.1e-6))
for run_id in tqdm(range(run_count+1)):
loss = net.compiled().train()
current_loss = net.compiled().train()
if run_id % 100 == 0:
net.print_weights()
old_aggs, _ = net.net.get_aggregated_weights()
old_aggs, _ = net.get_aggregated_weights()
print("old weights agg: " + str(old_aggs))
fp, new_aggs = net.net.is_fixpoint_after_aggregation(epsilon=0.0001)
fp, new_aggs = net.is_fixpoint_after_aggregation(epsilon=0.0001)
print("new weights agg: " + str(new_aggs))
print("Fixpoint? " + str(net.is_fixpoint()))
print("Fixpoint after Agg? " + str(fp))
print("Loss " + str(loss))
print("Loss " + str(current_loss))
print()
if False:
@ -593,26 +660,13 @@ if __name__ == '__main__':
# TODO: Wtf is happening here?
with FixpointExperiment() as exp:
run_count = 10000
net = TrainingNeuralNetworkDecorator(RecurrentNeuralNetwork(width=2, depth=2))\
.with_params(epsilon=0.1e-2).with_keras_params(optimizer='sgd', activation='linear')
net = TrainingNeuralNetworkDecorator(RecurrentNeuralNetwork(width=2, depth=2)
).with_keras_params(optimizer='sgd', activation='linear')
for run_id in tqdm(range(run_count+1)):
loss = net.compiled().train()
current_loss = net.compiled().train()
if run_id % 500 == 0:
net.print_weights()
# print(net.apply_to_network(net))
print("Fixpoint? " + str(net.is_fixpoint()))
print("Loss " + str(loss))
print("Loss " + str(current_loss))
print()
if False:
# and this gets somewhat interesting... we can still achieve non-trivial fixpoints
# over multiple applications when training enough in-between
with MixedFixpointExperiment() as exp:
for run_id in range(10):
net = TrainingNeuralNetworkDecorator(FFTNeuralNetwork(2, width=2, depth=2))\
.with_params(epsilon=0.0001, activation='sigmoid')
exp.run_net(net, 500, 10)
net.print_weights()
print("Fixpoint? " + str(net.is_fixpoint()))
exp.log(exp.counters)

View File

@ -0,0 +1,66 @@
import os
from experiment import Experiment
# noinspection PyUnresolvedReferences
from soup import Soup
from argparse import ArgumentParser
import numpy as np
import plotly as pl
import plotly.graph_objs as go
import colorlover as cl
import dill
from sklearn.manifold.t_sne import TSNE, PCA
def build_args():
arg_parser = ArgumentParser()
arg_parser.add_argument('-i', '--in_file', nargs=1, type=str)
arg_parser.add_argument('-o', '--out_file', nargs='?', default='out', type=str)
return arg_parser.parse_args()
class DataPlotter:
def __init__(self, path=None):
self.path = path or os.getcwd()
pass
def search_and_apply(self, plotting_function, files_to_look_for=None, absolut_file_or_folder=None):
absolut_file_or_folder, files_to_look_for = self.path or absolut_file_or_folder, list() or files_to_look_for
if os.path.isdir(absolut_file_or_folder):
for sub_file_or_folder in os.scandir(absolut_file_or_folder):
self.search_and_apply(plotting_function, files_to_look_for=files_to_look_for,
absolut_file_or_folder=sub_file_or_folder.path)
elif absolut_file_or_folder.endswith('.dill'):
file_or_folder = os.path.split(absolut_file_or_folder)[-1]
if file_or_folder in files_to_look_for and not os.path.exists(
'{}.html'.format(absolut_file_or_folder[:-5])):
print('Apply Plotting function "{func}" on file "{file}"'.format(func=plotting_function.__name__,
file=absolut_file_or_folder)
)
with open(absolut_file_or_folder, 'rb') as in_f:
exp = dill.load(in_f)
names_dill_location = os.path.join(*os.path.split(absolut_file_or_folder)[:-1], 'all_names.dill')
with open(names_dill_location, 'rb') as in_f:
names = dill.load(in_f)
try:
plotting_function((names, exp), filename='{}.html'.format(absolut_file_or_folder[:-5]))
except ValueError:
pass
except AttributeError:
pass
else:
# This was either another FilyType or Plot.html already exists.
pass
if __name__ == '__main__':
plotter = DataPlotter
pass

View File

@ -0,0 +1,109 @@
import os
from collections import defaultdict
# noinspection PyUnresolvedReferences
from soup import Soup
from experiment import TaskExperiment
from argparse import ArgumentParser
import plotly as pl
import plotly.graph_objs as go
import colorlover as cl
import dill
import numpy as np
def build_args():
arg_parser = ArgumentParser()
arg_parser.add_argument('-i', '--in_file', nargs=1, type=str)
arg_parser.add_argument('-o', '--out_file', nargs='?', default='out', type=str)
return arg_parser.parse_args()
def line_plot(exp: TaskExperiment, filename='lineplot'):
assert isinstance(exp, TaskExperiment), ' This has to be a TaskExperiment!'
traces, data = [], defaultdict(list)
color_scale = cl.scales['3']['div']['RdYlBu']
# Sort data per Key
for message in exp.log_messages:
for key in message.keys():
try:
data[key].append(-0.1 if np.isnan(message[key]) or np.isinf(message[key]) else message[key])
except:
data[key].append(message[key])
for line_id, key in enumerate(data.keys()):
if key not in ['counters', 'id']:
trace = go.Scatter(
x=[x for x in range(len(data[key]))],
y=data[key],
name=key,
line=dict(
color=color_scale[line_id],
width=5
),
)
traces.append(trace)
else:
continue
layout = dict(xaxis=dict(title='Trains per self-application', titlefont=dict(size=20)),
yaxis=dict(title='Average amount of fixpoints found',
titlefont=dict(size=20),
# type='log',
# range=[0, 2]
),
legend=dict(orientation='h', x=0.3, y=-0.3),
# height=800, width=800,
margin=dict(b=0)
)
fig = go.Figure(data=traces, layout=layout)
pl.offline.plot(fig, auto_open=True, filename=filename)
pass
def search_and_apply(absolut_file_or_folder, plotting_function, files_to_look_for=None, override=False):
# ToDo: Clean this Mess
assert os.path.exists(absolut_file_or_folder), f'The given path does not exist! Given: {absolut_file_or_folder}'
files_to_look_for = files_to_look_for or list()
if os.path.isdir(absolut_file_or_folder):
for sub_file_or_folder in os.scandir(absolut_file_or_folder):
search_and_apply(sub_file_or_folder.path, plotting_function,
files_to_look_for=files_to_look_for, override=override)
elif absolut_file_or_folder.endswith('.dill'):
file_or_folder = os.path.split(absolut_file_or_folder)[-1]
if file_or_folder in files_to_look_for or not files_to_look_for:
if not os.path.exists('{}.html'.format(absolut_file_or_folder[:-5])) or override:
print('Apply Plotting function "{func}" on file "{file}"'.format(func=plotting_function.__name__,
file=absolut_file_or_folder)
)
with open(absolut_file_or_folder, 'rb') as in_f:
exp = dill.load(in_f)
try:
plotting_function(exp, filename='{}.html'.format(absolut_file_or_folder[:-5]))
except ValueError:
pass
except AttributeError:
pass
else:
# Plot.html already exists.
pass
else:
# This was a wrong FilyType.
pass
if __name__ == '__main__':
args = build_args()
in_file = args.in_file[0]
out_file = args.out_file
search_and_apply(in_file, line_plot, override=True)

View File

@ -7,12 +7,13 @@ sys.path += os.path.join('..', '.')
from experiment import *
from network import *
import keras.backend as K
def generate_counters():
return {'divergent': 0, 'fix_zero': 0, 'fix_other': 0, 'fix_sec': 0, 'other': 0}
def count(counters, net, notable_nets=[]):
def count(counters, net, notable_nets: list=None):
notable_nets = notable_nets or list()
if net.is_diverged():
counters['divergent'] += 1
elif net.is_fixpoint():
@ -31,7 +32,7 @@ def count(counters, net, notable_nets=[]):
if __name__ == '__main__':
with Experiment('applying_fixpoint') as exp:
with FixpointExperiment(name='applying_fixpoint') as exp:
exp.trials = 50
exp.run_count = 100
exp.epsilon = 1e-4
@ -40,7 +41,7 @@ if __name__ == '__main__':
for use_bias in [False]:
net_generators += [lambda activation=activation, use_bias=use_bias: WeightwiseNeuralNetwork(width=2, depth=2).with_keras_params(activation=activation, use_bias=use_bias)]
net_generators += [lambda activation=activation, use_bias=use_bias: AggregatingNeuralNetwork(aggregates=4, width=2, depth=2).with_keras_params(activation=activation, use_bias=use_bias)]
net_generators += [lambda activation=activation, use_bias=use_bias: RecurrentNeuralNetwork(width=2, depth=2).with_keras_params(activation=activation, use_bias=use_bias)]
# net_generators += [lambda activation=activation, use_bias=use_bias: RecurrentNeuralNetwork(width=2, depth=2).with_keras_params(activation=activation, use_bias=use_bias)]
all_counters = []
all_notable_nets = []
all_names = []
@ -50,14 +51,14 @@ if __name__ == '__main__':
for _ in tqdm(range(exp.trials)):
net = ParticleDecorator(net_generator())
net.with_params(epsilon=exp.epsilon)
name = str(net.net.__class__.__name__) + " activiation='" + str(net.get_keras_params().get('activation')) + "' use_bias=" + str(net.get_keras_params().get('use_bias'))
name = str(net.name) + " activiation='" + str(net.get_keras_params().get('activation')) + "' use_bias=" + str(net.get_keras_params().get('use_bias'))
for run_id in range(exp.run_count):
loss = net.self_attack()
count(counters, net, notable_nets)
all_counters += [counters]
all_notable_nets += [notable_nets]
all_names += [name]
K.clear_session()
exp.reset_model()
exp.save(all_counters=all_counters)
exp.save(trajectorys=exp.without_particles())
# net types reached in the end

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
TrainingNeuralNetworkDecorator activiation='linear' use_bias=False
{'xs': [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], 'ys': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 'zs': [0.0, 1.2, 5.2, 7.4, 8.1, 9.1, 9.6, 9.8, 10.0, 9.9, 9.9]}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

View File

@ -11,7 +11,7 @@ from network import *
from soup import *
import tensorflow.python.keras.backend as K
from tensorflow.python.keras import backend as K
from statistics import mean
avg = mean
@ -59,7 +59,7 @@ def count(counters, soup, notable_nets=None):
if __name__ == '__main__':
with SoupExperiment('learn-from-soup') as exp:
with SoupExperiment(name='learn-from-soup') as exp:
exp.soup_size = 10
exp.soup_life = 100
exp.trials = 10
@ -83,10 +83,10 @@ if __name__ == '__main__':
counters = generate_counters()
results = []
for _ in tqdm(range(exp.trials)):
soup = Soup(exp.soup_size, lambda net_generator=net_generator,exp=exp: TrainingNeuralNetworkDecorator(net_generator()).with_params(epsilon=exp.epsilon))
soup = Soup(exp.soup_size, lambda net_generator=net_generator, exp=exp: TrainingNeuralNetworkDecorator(net_generator()).with_params(epsilon=exp.epsilon))
soup.with_params(attacking_rate=-1, learn_from_rate=0.1, train=0, learn_from_severity=learn_from_severity)
soup.seed()
name = str(soup.particles[0].net.__class__.__name__) + " activiation='" + str(soup.particles[0].get_keras_params().get('activation')) + "' use_bias=" + str(soup.particles[0].get_keras_params().get('use_bias'))
name = str(soup.particles[0].name) + " activiation='" + str(soup.particles[0].get_keras_params().get('activation')) + "' use_bias=" + str(soup.particles[0].get_keras_params().get('use_bias'))
for time in range(exp.soup_life):
soup.evolve()
count(counters, soup, notable_nets)

View File

@ -9,8 +9,6 @@ sys.path += os.path.join('..', '.')
from experiment import *
from network import *
import tensorflow.python.keras.backend as K
def generate_counters():
"""
@ -84,7 +82,7 @@ if __name__ == '__main__':
if net.is_diverged() or net.is_fixpoint():
break
count(counters, net, notable_nets)
keras.backend.clear_session()
exp.reset_model()
xs += [trains_per_selfattack]
ys += [float(counters['fix_zero'] + counters['fix_other']) / float(exp.trials)]
all_names += [name]

View File

@ -104,7 +104,7 @@ if __name__ == '__main__':
for run_id in range(10):
net = TrainingNeuralNetworkDecorator(FFTNeuralNetwork(2, width=2, depth=2))\
.with_params(epsilon=0.0001, activation='sigmoid')
exp.run_net(net, 500, 10)
exp.run_net(net)
net.print_weights()

View File

@ -10,8 +10,7 @@ from experiment import *
if __name__ == '__main__':
if True:
with SoupExperiment("soup") as exp:
for run_id in range(1):
with SoupExperiment(namne="soup") as exp:
net_generator = lambda: TrainingNeuralNetworkDecorator(WeightwiseNeuralNetwork(2, 2)) \
.with_keras_params(activation='linear').with_params(epsilon=0.0001)
# net_generator = lambda: TrainingNeuralNetworkDecorator(AggregatingNeuralNetwork(4, 2, 2))\
@ -30,3 +29,4 @@ if __name__ == '__main__':
# or soup.historical_particles[particle_uid].states[time_step]['weights']
# from soup.dill
exp.save(soup=soup.without_particles())
K.clear_session()

View File

@ -1,12 +1,55 @@
import random
from tensorflow.python.keras.layers import Dense, Dropout, BatchNormalization
from tensorflow.python.keras.layers import Input, Layer, Concatenate, RepeatVector, Reshape
from tensorflow.python.keras.models import Sequential, Model
from tensorflow.python.keras import backend as K
from network import *
from typing import List, Tuple
# Functions and Operators
from operator import mul
from functools import reduce
from itertools import accumulate
import numpy as np
from task import Task, TaskAdditionOfN
from copy import copy, deepcopy
from network import ParticleDecorator, WeightwiseNeuralNetwork, TrainingNeuralNetworkDecorator, \
EarlyStoppingByInfNanLoss
from experiment import TaskingSoupExperiment
from math import sqrt
def prng():
return random.random()
class SlicingLayer(Layer):
def __init__(self):
self.kernel: None
self.inputs: int
super(SlicingLayer, self).__init__()
def build(self, input_shape):
# Create a trainable weight variable for this layer.
self.kernel = None
self.inputs = input_shape[-1]
super(SlicingLayer, self).build(input_shape) # Be sure to call this at the end
def call(self, x, **kwargs):
concats = [Concatenate()([x[:, i][..., None]] * self.inputs) for i in range(x.shape[-1].value)]
return concats
def compute_output_shape(self, input_shape):
return [Concatenate()([(None, 1)] * 4) for _ in range(input_shape[-1])]
class Soup(object):
def __init__(self, size, generator, **kwargs):
@ -14,24 +57,29 @@ class Soup(object):
self.generator = generator
self.particles = []
self.historical_particles = {}
self.params = dict(attacking_rate=0.1, learn_from_rate=0.1, train=0, learn_from_severity=1)
self.params.update(kwargs)
self.soup_params = dict(attacking_rate=0.1, learn_from_rate=0.1, train=0, learn_from_severity=1)
self.soup_params.update(kwargs)
self.time = 0
self.is_seeded = False
self.is_compiled = False
def __len__(self):
return len(self.particles)
def __copy__(self):
copy_ = Soup(self.size, self.generator, **self.params)
copy_ = Soup(self.size, self.generator, **self.soup_params)
copy_.__dict__ = {attr: self.__dict__[attr] for attr in self.__dict__ if
attr not in ['particles', 'historical_particles']}
return copy_
def without_particles(self):
self_copy = copy.copy(self)
self_copy = copy(self)
# self_copy.particles = [particle.states for particle in self.particles]
self_copy.historical_particles = {key: val.states for key, val in self.historical_particles.items()}
return self_copy
def with_params(self, **kwargs):
self.params.update(kwargs)
def with_soup_params(self, **kwargs):
self.soup_params.update(kwargs)
return self
def generate_particle(self):
@ -43,9 +91,13 @@ class Soup(object):
return self.historical_particles.get(uid, otherwise)
def seed(self):
if not self.is_seeded:
self.particles = []
for _ in range(self.size):
self.particles += [self.generate_particle()]
else:
print('already seeded!')
self.is_seeded = True
return self
def evolve(self, iterations=1):
@ -53,33 +105,36 @@ class Soup(object):
self.time += 1
for particle_id, particle in enumerate(self.particles):
description = {'time': self.time}
if prng() < self.params.get('attacking_rate'):
if prng() < self.soup_params.get('attacking_rate'):
other_particle_id = int(prng() * len(self.particles))
other_particle = self.particles[other_particle_id]
particle.attack(other_particle)
description['action'] = 'attacking'
description['counterpart'] = other_particle.get_uid()
if prng() < self.params.get('learn_from_rate'):
if prng() < self.soup_params.get('learn_from_rate'):
other_particle_id = int(prng() * len(self.particles))
other_particle = self.particles[other_particle_id]
for _ in range(self.params.get('learn_from_severity', 1)):
for _ in range(self.soup_params.get('learn_from_severity', 1)):
particle.learn_from(other_particle)
description['action'] = 'learn_from'
description['counterpart'] = other_particle.get_uid()
for _ in range(self.params.get('train', 0)):
particle.compiled()
for _ in range(self.soup_params.get('train', 0)):
# callbacks on save_state are broken for TrainingNeuralNetwork
loss = particle.train(store_states=False)
description['fitted'] = self.params.get('train', 0)
description['fitted'] = self.soup_params.get('train', 0)
description['loss'] = loss
description['action'] = 'train_self'
description['counterpart'] = None
if self.params.get('remove_divergent') and particle.is_diverged():
if self.soup_params.get('remove_divergent') and particle.is_diverged():
new_particle = self.generate_particle()
self.particles[particle_id] = new_particle
description['action'] = 'divergent_dead'
description['counterpart'] = new_particle.get_uid()
if self.params.get('remove_zero') and particle.is_zero():
if self.soup_params.get('remove_zero') and particle.is_zero():
new_particle = self.generate_particle()
self.particles[particle_id] = new_particle
description['action'] = 'zweo_dead'
@ -108,25 +163,208 @@ class Soup(object):
print(particle.is_fixpoint())
class TaskingSoup(Soup):
@staticmethod
def weights_to_flat_array(weights: List[np.ndarray]) -> np.ndarray:
return np.concatenate([d.ravel() for d in weights])
@staticmethod
def reshape_flat_array(array, shapes: List[Tuple[int]]) -> List[np.ndarray]:
# Same thing, but with an additional np call
# sizes: List[int] = [int(np.prod(shape)) for shape in shapes]
sizes = [reduce(mul, shape) for shape in shapes]
# Split the incoming array into slices for layers
slices = [array[x: y] for x, y in zip(accumulate([0] + sizes), accumulate(sizes))]
# reshape them in accordance to the given shapes
weights = [np.reshape(weight_slice, shape) for weight_slice, shape in zip(slices, shapes)]
return weights
def __init__(self, population_size: int, task: Task, particle_generator, sparsity_rate=0.1, use_bias=False,
safe=True, **kwargs):
if safe:
input_shape_error_message = f'The population size must be devideable by {task.input_shape[-1]}'
assert population_size % task.input_shape[-1] == 0, input_shape_error_message
assert population_size % 2 == 0, 'The population size needs to be of even value'
super(TaskingSoup, self).__init__(population_size, particle_generator, **kwargs)
self.task = task
self.model: Sequential
self.network_params = dict(sparsity_rate=sparsity_rate, early_nan_stopping=True, use_bias=use_bias,
depth=population_size // task.input_shape[-1])
self.network_params.update(kwargs.get('network_params', {}))
self.compile_params = dict(loss='mse', optimizer='sgd')
self.compile_params.update(kwargs.get('compile_params', {}))
def with_network_params(self, **params):
self.network_params.update(params)
def _generate_model(self):
particle_idx_list = list(range(len(self)))
particles_per_layer = len(self) // self.network_params.get('depth')
task_input = Input(self.task.input_shape, name='Task_Input')
# First layer, which is conected to the input layer and independently trainable / not trainable at all.
input_neurons = particles_per_layer * self.task.output_shape
x = Dense(input_neurons, use_bias=self.network_params.get('use_bias'))(task_input)
x = SlicingLayer()(x)
for layer_num in range(self.network_params.get('depth')):
# This needs to be tensors, because particles come as keras models that applicable
x = [self.particles[layer_num*particles_per_layer + i].get_model()(x[i]) for
i in range(particles_per_layer)]
x = [RepeatVector(particles_per_layer)(x[i]) for i in range(particles_per_layer)]
x = [Reshape((particles_per_layer,))(x[i]) for i in range(particles_per_layer)]
x = Concatenate()(x)
x = Dense(self.task.output_shape, use_bias=self.network_params.get('use_bias'), activation='linear')(x)
model = Model(inputs=task_input, outputs=x)
return model
def get_weights(self):
return self.model.get_weights()
def set_weights(self, weights: List[np.ndarray]):
self.model.set_weights(weights)
def set_intermediate_weights(self, weights: List[np.ndarray]):
all_weights = self.get_weights()
all_weights[1:-1] = weights
self.set_weights(all_weights)
def get_intermediate_weights(self):
return self.get_weights()[1:-1]
def seed(self):
K.clear_session()
self.is_compiled = False
super(TaskingSoup, self).seed()
self.model = self._generate_model()
pass
def compile_model(self, **kwargs):
if not self.is_compiled:
compile_params = deepcopy(self.compile_params)
compile_params.update(kwargs)
return self.model.compile(**compile_params)
else:
raise BrokenPipeError('This Model is not compiled yet! Something went wrong in the Pipeline!')
def get_total_weight_amount(self):
if self.is_seeded:
return sum([x.get_amount_of_weights() for x in self.particles])
else:
return 0
def get_shapes(self):
return [x.shape for x in self.get_weights()]
def get_intermediate_shapes(self):
weights = [x.shape for x in self.get_weights()]
return weights[2:-2] if self.network_params.get('use_bias') else weights[1:-1]
def predict(self, x):
return self.model.predict(x)
def evolve(self, iterations=1):
for iteration in range(iterations):
super(TaskingSoup, self).evolve(iterations=1)
self.train_particles()
def get_particle_weights(self):
return np.concatenate([x.get_weights_flat() for x in self.particles])
def get_particle_input_shape(self):
if self.is_seeded:
return tuple([x if x else -1 for x in self.particles[0].get_model().input_shape])
else:
return -1
def set_particle_weights(self, weights):
particle_weight_shape = self.particles[0].shapes(self.particles[0].get_weights())
sizes = [x.get_amount_of_weights() for x in self.particles]
flat_weights = self.weights_to_flat_array(weights)
slices = [flat_weights[x: y] for x, y in zip(accumulate([0] + sizes), accumulate(sizes))]
for particle, slice in zip(self.particles, slices):
new_weights = self.reshape_flat_array(slice, particle_weight_shape)
particle.set_weights(new_weights)
return True
def compiled(self, **kwargs):
if not self.is_compiled:
self.compile_model(**kwargs)
self.is_compiled = True
return self
def train(self, batchsize=1, epoch=0):
self.compiled()
x, y = self.task.get_samples()
callbacks = []
if self.network_params.get('early_nan_stopping'):
callbacks.append(EarlyStoppingByInfNanLoss())
# 'or' does not work on empty lists
callbacks = callbacks if callbacks else None
"""
Please Note:
epochs: Integer. Number of epochs to train the model.
An epoch is an iteration over the entire `x` and `y`
data provided.
Note that in conjunction with `initial_epoch`,
`epochs` is to be understood as "final epoch".
The model is not trained for a number of iterations
given by `epochs`, but merely until the epoch
of index `epochs` is reached."""
history = self.model.fit(x=x, y=y, initial_epoch=epoch, epochs=epoch + 1, verbose=0,
batch_size=batchsize, callbacks=callbacks)
return history.history['loss'][-1]
def train_particles(self, **kwargs):
self.compiled()
weights = self.get_particle_weights()
shaped_weights = self.reshape_flat_array(weights, self.get_intermediate_shapes())
self.set_intermediate_weights(shaped_weights)
_ = self.train(**kwargs) # This returns the loss values
new_weights = self.get_intermediate_weights()
self.set_particle_weights(new_weights)
return
if __name__ == '__main__':
if True:
net_generator = lambda: WeightwiseNeuralNetwork(2, 2).with_keras_params(activation='linear').with_params()
soup_generator = Soup(100, net_generator).with_params(remove_divergent=True, remove_zero=True)
exp = SoupExperiment()
exp.run_exp(net_generator, 1000, soup_generator, 1, False)
from task import TaskAdditionOfN
net_generator = lambda: TrainingNeuralNetworkDecorator(
WeightwiseNeuralNetwork(2, 2).with_keras_params(activation='linear').with_params()
)
soup_generator = lambda: TaskingSoup(20, TaskAdditionOfN(4), net_generator)
with TaskingSoupExperiment(soup_generator, name='solving_soup') as exp:
exp.run_exp(reset_model=False)
if False:
soup_generator = lambda: Soup(10, net_generator).with_soup_params(remove_divergent=True, remove_zero=True)
with SoupExperiment(soup_generator, name='soup') as exp:
net_generator = lambda: TrainingNeuralNetworkDecorator(
WeightwiseNeuralNetwork(2, 2).with_keras_params(activation='linear').with_params()
)
exp.run_exp(net_generator)
# net_generator = lambda: FFTNeuralNetwork(2, 2).with_keras_params(activation='linear').with_params()
# net_generator = lambda: AggregatingNeuralNetwork(4, 2, 2).with_keras_params(activation='sigmoid')\
# .with_params(shuffler=AggregatingNeuralNetwork.shuffle_random)
# net_generator = lambda: RecurrentNeuralNetwork(2, 2).with_keras_params(activation='linear').with_params()
if True:
if False:
soup_generator = lambda: Soup(10, net_generator).with_soup_params(remove_divergent=True, remove_zero=True)
with SoupExperiment(soup_generator, name='soup') as exp:
net_generator = lambda: TrainingNeuralNetworkDecorator(WeightwiseNeuralNetwork(2, 2)) \
.with_keras_params(activation='linear').with_params(epsilon=0.0001)
soup_generator = lambda: Soup(100, net_generator).with_params(remove_divergent=True, remove_zero=True, train=20)
exp = SoupExperiment(name="soup")
exp.run_exp(net_generator, 100, soup_generator, 1, False)
exp.run_exp(net_generator)
# net_generator = lambda: TrainingNeuralNetworkDecorator(AggregatingNeuralNetwork(4, 2, 2))
# .with_keras_params(activation='linear')\
@ -135,3 +373,4 @@ if __name__ == '__main__':
# .with_keras_params(activation='linear')\
# .with_params(shuffler=AggregatingNeuralNetwork.shuffle_random)
# net_generator = lambda: RecurrentNeuralNetwork(2, 2).with_keras_params(activation='linear').with_params()

32
code/task.py Normal file
View File

@ -0,0 +1,32 @@
from abc import ABC, abstractmethod
import numpy as np
from typing import Tuple
class Task(ABC):
def __init__(self, input_shape, output_shape, **kwargs):
assert any([x not in kwargs.keys() for x in ["input_shape", "output_shape"]]), 'Dublicated arguments were given'
self.input_shape = input_shape
self.output_shape = output_shape
self.batchsize = kwargs.get('batchsize', 100)
def get_samples(self) -> Tuple[np.ndarray, np.ndarray]:
raise NotImplementedError
class TaskAdditionOfN(Task):
def __init__(self, n: int, input_shape=(4,), output_shape=1, **kwargs):
assert any([x not in kwargs.keys() for x in ["input_shape", "output_shape"]]), 'Dublicated arguments were given'
assert n <= input_shape[0], f'You cannot Add more values (n={n}) than your input is long (in={input_shape}).'
kwargs.update(input_shape=input_shape, output_shape=output_shape)
super(TaskAdditionOfN, self).__init__(**kwargs)
self.n = n
def get_samples(self) -> Tuple[np.ndarray, np.ndarray]:
x = np.zeros((self.batchsize, *self.input_shape))
x[:, :self.n] = np.random.standard_normal((self.batchsize, self.n)) * 0.5
y = np.sum(x, axis=1)
return x, y