From 5566c1d8a66c017f53d818ffa6f1b4466e915036 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sun, 15 Sep 2024 11:26:01 -0400 Subject: [PATCH 01/37] Can build and propagate --- aitk/networks/network.py | 125 +++++++++++++++++++++------------------ aitk/networks/utils.py | 90 +++++++--------------------- 2 files changed, 90 insertions(+), 125 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 9bba143..ddfd1fa 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -49,7 +49,7 @@ class Network: """ Wrapper around a keras.Model. """ - def __init__(self, model=None, layers=None, **config): + def __init__(self, model=None, layers=None, connections=None, **config): self._initialized = False self._watchers = [] self._fit_inputs = None @@ -65,7 +65,7 @@ def __init__(self, model=None, layers=None, **config): else: self._pre_layers = {} self._name = None - self._connections = [] + self._connections = [] if connections is None else connections # Place to put models between layers: self._predict_models = {} # Place to map layer to its input layers: @@ -145,13 +145,14 @@ def _show_connection_help(self): print(" ", list(self._pre_layers.keys())) def initialize_model(self): - self._layers = topological_sort(self._model.layers) - # Make a mapping of names to layers: - self._layers_map = {layer.name: layer for layer in self._layers} + # First, make a mapping of names to layers: + self._layers_map = {layer.name: layer for layer in self.model._layers} + # Next, get layers in topological order: + self._layers = topological_sort(self) # Get the input bank names, in order: - self.input_bank_order = self._get_input_layers() + self.input_bank_order = [layer.name for layer in self._get_input_layers()] # Get the output bank names, in order: - self.output_bank_order = self._get_output_layers() + self.output_bank_order = [layer.name for layer in self._get_output_layers()] # Get the best (shortest path) between layers: self._level_ordering = self._get_level_ordering() # Build intermediary models: @@ -785,7 +786,8 @@ def predict_from(self, inputs, from_layer_name, to_layer_name): from_layer = self[from_layer_name] path = find_path(from_layer, to_layer_name) # Input should be what next layer expects: - current = input_layer = make_input_from_shape(self[path[0]].input_shape) + input_shape = self[path[0]]._build_shapes_dict["input_shape"] + current = input_layer = make_input_from_shape(input_shape) for layer_name in path: current = self[layer_name](current) self._predict_models[key] = Model(inputs=input_layer, outputs=current) @@ -1032,7 +1034,7 @@ def _build_predict_models(self): else: self._input_layer_names[layer.name] = tuple([layer.name]) self._predict_models[tuple([layer.name]), layer.name] = Model( - inputs=[layer.input], outputs=[layer.output], + inputs=[layer._input_tensor], outputs=[layer.output], ) def _get_input_tensors(self, layer_name, input_list): @@ -1040,15 +1042,15 @@ def _get_input_tensors(self, layer_name, input_list): Given a layer_name, return {input_layer_name: tensor} """ # Recursive; results in input_list of [(name, tensor), ...] - for layer in self.incoming_layers(layer_name): + for layer in self._get_input_layers(layer_name): if layer.name in self._input_layer_names: for layer_name in self._input_layer_names[layer.name]: if layer_name not in [name for (name, tensor) in input_list]: - input_list.append((layer_name, self[layer_name].input)) + input_list.append((layer_name, self[layer_name]._input_tensor)) else: if self._get_layer_type(layer.name) == "input": if layer.name not in [name for (name, tensor) in input_list]: - input_list.append((layer.name, layer.input)) + input_list.append((layer.name, layer._input_tensor)) else: self._get_input_tensors(layer.name, input_list) return input_list @@ -1104,15 +1106,27 @@ def _make_color(self, item): else: return tuple(item) - def _get_input_layers(self): - return tuple( - [x.name for x in self._layers if self._get_layer_type(x.name) == "input"] - ) + def _get_input_layers(self, layer_name=None): + if layer_name is None: + return tuple( + [x for x in self._layers if self._get_layer_type(x.name) == "input"] + ) + else: + return tuple( + [self._layers_map[layer_from] for layer_from, layer_to in self._connections + if layer_to == layer_name] + ) - def _get_output_layers(self): - return tuple( - [x.name for x in self._layers if self._get_layer_type(x.name) == "output"] - ) + def _get_output_layers(self, layer_name=None): + if layer_name is None: + return tuple( + [x for x in self._layers if self._get_layer_type(x.name) == "output"] + ) + else: + return tuple( + [self._layers_map[layer_to] for layer_from, layer_to in self._connections + if layer_from == layer_name] + ) def vshape(self, layer_name): """ @@ -1128,21 +1142,36 @@ def vshape(self, layer_name): def _get_output_shape(self, layer_name): layer = self[layer_name] - if isinstance(layer.output_shape, list): - return layer.output_shape[0][1:] + if ((layer._build_shapes_dict is not None) and + ("input_shape" in layer._build_shapes_dict)): + output_shape = layer.compute_output_shape( + layer._build_shapes_dict["input_shape"] + ) else: - return layer.output_shape[1:] + output_shape = layer.batch_shape + if isinstance(output_shape, list): + return output_shape[0][1:] + else: + return output_shape[1:] def _get_input_shape(self, layer_name): layer = self[layer_name] - if isinstance(layer.input_shape, list): - return layer.input_shape[0][1:] + input_shape = layer._build_shapes_dict["input_shape"] + if isinstance(input_shape, list): + return input_shape[0][1:] else: - return layer.input_shape[1:] + return input_shape[1:] def _get_raw_output_shape(self, layer_name): layer = self[layer_name] - return layer.output_shape + if ((layer._build_shapes_dict is not None) and + ("input_shape" in layer._build_shapes_dict)): + output_shape = layer.compute_output_shape( + layer._build_shapes_dict["input_shape"] + ) + else: + output_shape = layer.batch_shape + return output_shape def _get_feature(self, layer_name): """ @@ -1236,7 +1265,7 @@ def _get_act_minmax(self, layer_name): """ layer = self[layer_name] if layer.__class__.__name__ == "Flatten": - in_layer = self.incoming_layers(layer_name)[0] + in_layer = self._get_input_layers(layer_name)[0] return self._get_act_minmax(in_layer.name) elif self._get_layer_type(layer_name) == "input": color, mini, maxi = self._get_colormap(layer) @@ -1580,7 +1609,7 @@ def build_struct(self, inputs, targets, mode, colors, sizes): continue elif anchor: continue - for out in self.outgoing_layers(layer_name): + for out in self._get_output_layers(layer_name): if ( out.name not in positioning ): # is it drawn yet? if not, continue, @@ -1761,7 +1790,7 @@ def build_struct(self, inputs, targets, mode, colors, sizes): x1 = cwidth + width / 2 y1 = cheight - 1 # Arrows going up - for out in self.outgoing_layers(layer_name): + for out in self._get_output_layers(layer_name): if out.name not in positioning: continue # draw an arrow between layers: @@ -2009,35 +2038,14 @@ def build_struct(self, inputs, targets, mode, colors, sizes): ) return struct - def incoming_layers(self, layer_name): - layer = self[layer_name] - layers = [] - for node in layer.inbound_nodes: - if hasattr(node.inbound_layers, "__iter__"): - for layer in node.inbound_layers: - if layer not in layers: - layers.append(layer) - else: - if node.inbound_layers not in layers: - layers.append(node.inbound_layers) - return layers - - def outgoing_layers(self, layer_name): - layer = self[layer_name] - layers = [] - for node in layer.outbound_nodes: - if node.outbound_layer not in layers: - layers.append(node.outbound_layer) - return layers - def _get_layer_type(self, layer_name): """ Determines whether a layer is a "input", "hidden", or "output" layer based on its connections. If no connections, then it is "unconnected". """ - incoming_connections = self.incoming_layers(layer_name) - outgoing_connections = self.outgoing_layers(layer_name) + incoming_connections = self._get_input_layers(layer_name) + outgoing_connections = self._get_output_layers(layer_name) if len(incoming_connections) == 0 and len(outgoing_connections) == 0: return "unconnected" elif len(incoming_connections) > 0 and len(outgoing_connections) > 0: @@ -2066,7 +2074,7 @@ def _get_level_ordering(self): levels = {} for layer in self._layers: level = max( - [levels[lay.name] for lay in self.incoming_layers(layer.name)] + [-1] + [levels[lay.name] for lay in self._get_input_layers(layer.name)] + [-1] ) levels[layer.name] = level + 1 max_level = max(levels.values()) @@ -2077,7 +2085,7 @@ def _get_level_ordering(self): ] ordering.append( [ - (name, False, [x.name for x in self.incoming_layers(name)]) + (name, False, [x.name for x in self._get_input_layers(name)]) for name in layer_names ] ) # (going_to/layer_name, anchor, coming_from) @@ -2115,7 +2123,7 @@ def _get_level_ordering(self): else: # if next level doesn't contain an outgoing # connection, add it to next level as anchor point - for layer in self.outgoing_layers(name): + for layer in self._get_output_layers(name): next_level = [ (n, anchor) for (n, anchor, fname) in ordering[level + 1] ] @@ -2634,7 +2642,10 @@ def make_layer(index, layers, activation): super()._init_state() metrics = [self.get_metric(name) for name in metrics] model.compile(optimizer=self._make_optimizer(optimizer), loss=loss, metrics=metrics) - super().__init__(model) + connections = [] + for i in range(0, len(layers) - 1): + connections.append((layers[i].name, layers[i + 1].name)) + super().__init__(model=model, connections=connections) def _make_optimizer(self, optimizer): import tensorflow as tf diff --git a/aitk/networks/utils.py b/aitk/networks/utils.py index 393395e..b755c4c 100644 --- a/aitk/networks/utils.py +++ b/aitk/networks/utils.py @@ -110,7 +110,7 @@ def make_input_from_shape(shape): return Input(input_shape, name="input") -def find_path(from_layer, to_layer_name): +def find_path(network, from_layer, to_layer_name): """ Breadth-first search to find shortest path from from_layer to to_layer_name. @@ -126,78 +126,32 @@ def find_path(from_layer, to_layer_name): return current.path else: # expand: - for node in current.outbound_nodes: - layer = node.outbound_layer + for layer in network._get_output_layers(current.name): layer.path = current.path + [layer.name] queue.append(layer) return None -def gather_nodes(layers): - nodes = [] - for layer in layers: - for node in layer.inbound_nodes: - if node not in nodes: - nodes.append(node) - - for node in layer.outbound_nodes: - if node not in nodes: - nodes.append(node) - return nodes - -#def topological_sort_connections(input_layers, connections): -# layer_list = input_layers[:] -# while not done: -# for connection in connections: - -def topological_sort(layers): - """ - Given a keras model and list of layers, produce a topological - sorted list, from input(s) to output(s). - """ - nodes = topological_sort_nodes(layers) - layer_list = [] - for node in nodes: - if hasattr(node.inbound_layers, "__iter__"): - for layer in node.inbound_layers: - if layer not in layer_list: - layer_list.append(layer) - else: - if node.inbound_layers not in layer_list: - layer_list.append(node.inbound_layers) - - if node.outbound_layer not in layer_list: - layer_list.append(node.outbound_layer) - return layer_list - - -def topological_sort_nodes(layers): - """ - Given a keras model and list of layers, produce a topological - sorted list, from input(s) to output(s). - """ - # Initilize all: - nodes = gather_nodes(layers) - for node in nodes: - node.visited = False - stack = [] - for node in reversed(nodes): - if not node.visited: - visit_node(node, stack) - return reversed(stack) - - -def visit_node(node, stack): - """ - Utility function for topological_sort. - """ - node.visited = True - if node.outbound_layer: - for subnode in node.outbound_layer.outbound_nodes: - if not subnode.visited: - visit_node(subnode, stack) - stack.append(node) - +def topological_sort(network): + # First, marke them all not-visited: + for layer_from_name, layer_to_name in network._connections: + network._layers_map[layer_from_name].visited = False + network._layers_map[layer_to_name].visited = False + # Next gather them: + layers = [] + queue = list(network._get_input_layers()) + while queue: + current = queue.pop(0) + if not current.visited: + layers.append(current) + current.visited = True + queue.extend(list(network._get_output_layers(current.name))) + for layer_from_name, layer_to_name in network._connections: + if network._layers_map[layer_from_name].visited is False: + raise Exception("Layer %r is not part of network graph" % layer_from_name) + elif network._layers_map[layer_to_name].visited is False: + raise Exception("Layer %r is not part of network graph" % layer_to_name) + return layers def scale_output_for_image(vector, minmax, truncate=False): """ From e3d034ad109219fc9a76004d34a41bb63f67e4e9 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sun, 15 Sep 2024 11:42:53 -0400 Subject: [PATCH 02/37] Need to make sure these two tensors are same type --- aitk/networks/network.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index ddfd1fa..26301e4 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -2512,14 +2512,19 @@ def get_learning_rate(self): def get_metric(self, name): import tensorflow.keras.backend as K + import tensorflow as tf if name == "tolerance_accuracy": self._state["tolerance_accuracy_used"] = True def tolerance_accuracy(targets, outputs): return K.mean( K.all( - K.less_equal(K.abs(targets - outputs), - self._tolerance), axis=-1), + K.less_equal( + K.abs(tf.cast(targets, tf.float32) - + tf.cast(outputs, tf.float32)), + self._tolerance + ), axis=-1 + ), axis=-1) return tolerance_accuracy else: From c10cd1f943ecc2ebbf4964b9c57cf882300b818d Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sun, 15 Sep 2024 12:29:31 -0400 Subject: [PATCH 03/37] Change requirements --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b9919c2..d53efd0 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def get_version(file, name="__version__"): "aitk.utils": ["fonts/*.ttf"], "aitk.robots": ["worlds/*.json", "worlds/*.png"], }, - install_requires=["Pillow", "ipywidgets", "tqdm", "numpy<=1.26.4", "matplotlib", "tensorflow<=2.15.1"], + install_requires=["Pillow", "ipywidgets", "tqdm", "numpy", "matplotlib", "tensorflow>=2.17.0"], packages=setuptools.find_packages(), python_requires=">=3.9", license="BSD-3-Clause", From a0d1a634c871667ef74126b1d99728d0fc737485 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sun, 15 Sep 2024 15:32:19 -0400 Subject: [PATCH 04/37] Fixed Network(model) --- aitk/networks/network.py | 27 ++++++++++++++++++--------- aitk/networks/utils.py | 24 +++++++++++++++++------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 26301e4..4ebd79f 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -35,6 +35,8 @@ render_curve, scale_output_for_image, svg_to_image, + get_array_shape, + get_connections, topological_sort, ) @@ -49,13 +51,16 @@ class Network: """ Wrapper around a keras.Model. """ - def __init__(self, model=None, layers=None, connections=None, **config): + def __init__(self, model=None, layers=None, **config): self._initialized = False self._watchers = [] self._fit_inputs = None self._fit_targets = None self._init_state() + self._connections = [] self._model = model + if model: + self._connections = get_connections(model) # {name: (layer, [incoming], [outgoing])...} if layers is not None: self._pre_layers = {get_layer_name(layer): layer @@ -65,7 +70,6 @@ def __init__(self, model=None, layers=None, connections=None, **config): else: self._pre_layers = {} self._name = None - self._connections = [] if connections is None else connections # Place to put models between layers: self._predict_models = {} # Place to map layer to its input layers: @@ -146,7 +150,7 @@ def _show_connection_help(self): def initialize_model(self): # First, make a mapping of names to layers: - self._layers_map = {layer.name: layer for layer in self.model._layers} + self._layers_map = {layer.name: layer for layer in self._model._layers} # Next, get layers in topological order: self._layers = topological_sort(self) # Get the input bank names, in order: @@ -329,8 +333,14 @@ def fit(self, *args, **kwargs): kwargs["verbose"] = 0 kwargs["initial_epoch"] = self._epoch - self._fit_inputs = kwargs.get("x", None) # inputs - self._fit_targets = kwargs.get("y", None) # targets + shape = get_array_shape(kwargs.get("x")) + # TODO: check all types of networks + if shape: + kwargs["x"] = np.array(kwargs["x"]) + kwargs["y"] = np.array(kwargs["y"]) + + self._fit_inputs = kwargs.get("x") # inputs + self._fit_targets = kwargs.get("y") # targets # call underlying model fit: try: @@ -2622,6 +2632,8 @@ def make_layer(index, layers, activation): name = make_name(index, len(layers)) if index == 0: size = layers[index] + if not isinstance(size, (list, tuple)): + size = tuple([size]) return Input(size, name=name) else: size = layers[index] @@ -2647,10 +2659,7 @@ def make_layer(index, layers, activation): super()._init_state() metrics = [self.get_metric(name) for name in metrics] model.compile(optimizer=self._make_optimizer(optimizer), loss=loss, metrics=metrics) - connections = [] - for i in range(0, len(layers) - 1): - connections.append((layers[i].name, layers[i + 1].name)) - super().__init__(model=model, connections=connections) + super().__init__(model=model) def _make_optimizer(self, optimizer): import tensorflow as tf diff --git a/aitk/networks/utils.py b/aitk/networks/utils.py index b755c4c..28f96db 100644 --- a/aitk/networks/utils.py +++ b/aitk/networks/utils.py @@ -36,16 +36,18 @@ def __init__(self, pointA, pointB): self.length = math.sqrt(math.pow(lengthX, 2) + math.pow(lengthY, 2)) self.angle = math.atan2(lengthY, lengthX) +def get_array_shape(array): + if isinstance(array, list): + return [len(array)] + get_array_shape(array[0]) + else: + return [] -def get_layer_name(layer): - from tensorflow.python.framework.ops import Tensor - from tensorflow.keras.models import Model - if isinstance(layer, Tensor): - m = Model(inputs=layer, outputs=layer) - return m.layers[0].name - else: +def get_layer_name(layer): + if hasattr(layer, "name"): return layer.name + else: + return "layer" def get_error_colormap(): @@ -412,3 +414,11 @@ def is_keras_tensor(item): return K.is_keras_tensor(item) except Exception: return False + +def get_connections(model): + connections = [] + for layer in model.layers: + for node in layer._inbound_nodes: + for parent_node in node.parent_nodes: + connections.append((parent_node.operation.name, layer.name)) + return connections From 866f3eec53c33e8fc5c096616dc003481aecf26d Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Mon, 16 Sep 2024 18:43:27 -0400 Subject: [PATCH 05/37] Version 3.0.0b1 --- aitk/_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aitk/_version.py b/aitk/_version.py index b211de5..e1edbb7 100644 --- a/aitk/_version.py +++ b/aitk/_version.py @@ -8,5 +8,5 @@ # # ************************************************************** -version_info = (2, 1, 0) -__version__ = ".".join(map(str, version_info)) +version_info = (3, 0, 0) +__version__ = ".".join(map(str, version_info)) + "b1" From 0d53bb078fa7eb8a3e664ac77eec03fc8206a0ae Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 06:37:31 -0400 Subject: [PATCH 06/37] Major rewrite for new tensorflow --- aitk/networks/network.py | 322 +++++++++++++++++----------- aitk/networks/utils.py | 23 +- tests/test_networks/test_network.py | 102 +++++++++ 3 files changed, 300 insertions(+), 147 deletions(-) create mode 100644 tests/test_networks/test_network.py diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 4ebd79f..3b3e448 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -17,6 +17,7 @@ import operator import random import sys +from types import FunctionType import matplotlib.pyplot as plt import numpy as np @@ -37,7 +38,6 @@ svg_to_image, get_array_shape, get_connections, - topological_sort, ) from aitk.utils import array_to_image @@ -51,25 +51,22 @@ class Network: """ Wrapper around a keras.Model. """ - def __init__(self, model=None, layers=None, **config): + def __init__(self, model=None, layers=None, name="Network", **config): + from tensorflow.keras.models import Model + + if model is not None and layers is not None: + raise Exception("Network() takes model or layers, not both") + + self._state = { + "tolerance_accuracy_used": False, + "pca": {}, + } self._initialized = False self._watchers = [] self._fit_inputs = None self._fit_targets = None - self._init_state() self._connections = [] self._model = model - if model: - self._connections = get_connections(model) - # {name: (layer, [incoming], [outgoing])...} - if layers is not None: - self._pre_layers = {get_layer_name(layer): layer - for layer in layers} - self._name = config.get("name", "Network") - self._show_connection_help() - else: - self._pre_layers = {} - self._name = None # Place to put models between layers: self._predict_models = {} # Place to map layer to its input layers: @@ -79,9 +76,14 @@ def __init__(self, model=None, layers=None, **config): self._history = {"weights": [], "metrics": []} self._epoch = 0 self._tolerance = 0.1 - name = self._model.name if self._model is not None else "Network" + self._name = self._model.name if self._model is not None else name + self._layers = [] + self._layers_map = {} + self.input_bank_order = [] + self.output_bank_order = [] + self._level_ordering = [] self.config = { - "name": name, # for svg title + "name": self._name, # for svg title "class_id": "keras-network", # for svg network classid "id": "keras-network", # for svg id "font_size": 12, # for svg @@ -114,56 +116,64 @@ def __init__(self, model=None, layers=None, **config): # layer_name: {vshape, feature, keep_aspect_ratio, visible # colormap, border_color, border_width} } - # Get all of the layers, even implicit ones, in order: - if self._model is not None: - self.initialize_model() - else: - self._layers = [] - self._layers_map = {} - self.input_bank_order = [] - self.output_bank_order = [] - self._level_ordering = [] - # Override settings: self.set_config(**config) + if model: + self._model = model + for layer in model.layers: + self.add(layer) + self._connections = get_connections(model) + else: + self._model = None + if layers: + for layer in layers: + self.add(layer) + # When we are done here, we are in 1 of 2 states: + # 1. A model, ready to go + # 2. Network, ready for more add(), connect(), compile() def __getattr__(self, attr): + if self._model is None: + raise Exception("Model has not yet been compiled") return getattr(self._model, attr) def __getitem__(self, layer_name): return self._layers_map.get(layer_name, None) - def _init_state(self): - if "_state" not in dir(self): - self._state = { - "tolerance_accuracy_used": False, - "pca": {}, - } + def add(self, layer): + """ + Add a layer to the network. + """ + from tensorflow.keras.layers import InputLayer, Input, Layer + + if isinstance(layer, FunctionType): + raise Exception("Don't use Input; use InputLayer") + + if not isinstance(layer, Layer): + raise Exception("Network.add() requires a Layer") + + # Let's find a good name for the layer: + name = layer.name + if name.startswith("keras_tensor"): + name = "input" + name[12:] + + if name in self._layers_map: + raise Exception("The name %r is already used" % name) + + # Add the layer: + layer.name = name + self._layers.append(layer) + self._layers_map[layer.name] = layer @property def model(self): return self._model - def _show_connection_help(self): - print("Connect layers with Network.connect(NAME, NAME) where NAMEs are in:") - print(" ", list(self._pre_layers.keys())) - def initialize_model(self): - # First, make a mapping of names to layers: - self._layers_map = {layer.name: layer for layer in self._model._layers} - # Next, get layers in topological order: - self._layers = topological_sort(self) - # Get the input bank names, in order: - self.input_bank_order = [layer.name for layer in self._get_input_layers()] - # Get the output bank names, in order: - self.output_bank_order = [layer.name for layer in self._get_output_layers()] - # Get the best (shortest path) between layers: - self._level_ordering = self._get_level_ordering() # Build intermediary models: self._build_predict_models() - # Setup layer config dicts: - self.config["layers"] = {layer.name: {} for layer in self._layers} # Set the colormap, etc for each layer: + self.config["layers"] = {layer.name: {} for layer in self._layers} self.initialize() def initialize(self, inputs=None, reset=True): @@ -185,6 +195,9 @@ def initialize(self, inputs=None, reset=True): If reset is False, consider previous input layer colormap's with new input values. """ + if not self._layers: + raise Exception("Layers must be set before initialization") + if inputs is None: # We don't have direct values, so we base colormap # on activation output ranges @@ -231,34 +244,30 @@ def initialize(self, inputs=None, reset=True): def connect(self, from_layer_name=None, to_layer_name=None): """ """ - if len(self._pre_layers) == 0: + if len(self._layers) == 0: raise Exception("no layers have been added") if from_layer_name is not None and not isinstance(from_layer_name, str): raise Exception("from_layer_name should be a string or None") if to_layer_name is not None and not isinstance(to_layer_name, str): raise Exception("to_layer_name should be a string or None") if from_layer_name is None and to_layer_name is None: - #if (any([layer.outgoing_connections for name, layer in self.layers]) or - # any([layer.incoming_connections for layer in self.layers])): - # raise Exception("layers already have connections") - for i in range(len(self._pre_layers) - 1): - names = list(self._pre_layers) - from_layer = self._pre_layers[names[i]] - to_layer = self._pre_layers[names[i + 1]] + for i in range(len(self._layers) - 1): + from_layer = self._layers[i] + to_layer = self._layers[i + 1] self.connect(from_layer.name, to_layer.name) else: if from_layer_name == to_layer_name: raise Exception("self connections are not allowed") if not isinstance(from_layer_name, str): raise Exception("from_layer_name should be a string") - if from_layer_name not in self._pre_layers: + if from_layer_name not in self._layers_map: raise Exception('unknown layer: %s' % from_layer_name) if not isinstance(to_layer_name, str): raise Exception("to_layer_name should be a string") - if to_layer_name not in self._pre_layers: + if to_layer_name not in self._layers_map: raise Exception('unknown layer: %s' % to_layer_name) - from_layer = self._pre_layers[from_layer_name] - to_layer = self._pre_layers[to_layer_name] + from_layer = self[from_layer_name] + to_layer = self[to_layer_name] # Check for input going to a Dense to warn: #if len(from_layer.shape) > 2 and to_layer.__class__.__name__ == "Dense": # print("WARNING: connected multi-dimensional input layer '%s' to layer '%s'; consider adding a FlattenLayer between them" % ( @@ -500,61 +509,100 @@ def _extract_inputs(self, inputs, input_names): if isinstance(inputs, dict): return [np.array(inputs[name]) for name in input_names] elif len(self.input_bank_order) == 1: - return inputs + return np.array([inputs]) else: return [ - np.array(inputs[index]) + np.array([inputs[index]]) for index in [self.input_bank_order.index(name) for name in input_names] ] def build_model(self): from tensorflow.keras.models import Model + if len(self._connections) == 0: + raise Exception("Need to connect layers before building model") + # Assumes layers either added or passed in via layers # and connected via Network.connect() froms = [connect[0] for connect in self._connections] tos = [connect[1] for connect in self._connections] input_layers = [] output_layers = [] - for layer_name in self._pre_layers: - if layer_name not in tos: - input_layers.append(layer_name) - if layer_name not in froms: - output_layers.append(layer_name) - outputs = [self._get_tensor_to(output_layer) - for output_layer in output_layers] - inputs = [self._pre_layers[layer_name] - for layer_name in input_layers] + for layer in self._layers: + if layer.name not in tos: + input_layers.append(layer.name) + if layer.name not in froms: + output_layers.append(layer.name) + # Now we build the model: + outputs = [ + self._build_graph_to(output_layer) for output_layer in output_layers + ] + inputs = [ + self[layer_name]._input_tensor for layer_name in input_layers + ] self._model = Model(inputs=inputs, outputs=outputs, name=self._name) - self.initialize_model() def _get_layers_to(self, layer_name): - return [connection[0] for connection in self._connections + return [self[connection[0]] for connection in self._connections if connection[1] == layer_name] - def _get_tensor_to(self, layer_name): - from tensorflow.keras.layers import Concatenate - + def _get_layers_from(self, layer_name): + return [self[connection[1]] for connection in self._connections + if connection[0] == layer_name] + + def topological_sort(self, layers, input_layers): + for layer in layers: + layer.visited = False + # Next gather them: + sorted_layers = [] + queue = input_layers + while queue: + current = queue.pop(0) + if not current.visited: + sorted_layers.append(current) + current.visited = True + queue.extend(self._get_layers_from(current.name)) + for layer in layers: + if layer.visited is False: + raise Exception("Layer %r is not part of the network graph" % layer.name) + return sorted_layers + + def _build_graph_to(self, layer_name): # recursive + from tensorflow.keras.layers import Concatenate, InputLayer + layers = self._get_layers_to(layer_name) if len(layers) == 0: # An input layer: - return self._pre_layers[layer_name] + return self[layer_name] - incoming_layers = [self._get_tensor_to(incoming_layer_name) - for incoming_layer_name in layers] + incoming_layers = [self._build_graph_to(incoming_layer.name) + for incoming_layer in layers] if len(incoming_layers) == 1: incoming_layer = incoming_layers[0] else: # more than one - incoming_layer = Concatenate()(incoming_layers) + incoming_layer = Concatenate()([layer._input_tensor + for layer in incoming_layers]) - layer = self._pre_layers[layer_name] - return layer(incoming_layer) + if isinstance(incoming_layer, InputLayer): + incoming_layer = incoming_layer._input_tensor + + layer = self[layer_name] + return layer(inputs=incoming_layer) def compile(self, *args, **kwargs): """ + The last step before you run a network. """ + # _layers, _connections already set + self._layers = self.topological_sort(self._layers, self._get_input_layers()) + # Get the input bank names, in order: + self.input_bank_order = [layer.name for layer in self._get_input_layers()] + # Get the output bank names, in order: + self.output_bank_order = [layer.name for layer in self._get_output_layers()] + # Get the best (shortest path) between layers: + self._level_ordering = self._get_level_ordering() # First, build model if necessary: if self._model is None: self.build_model() @@ -564,13 +612,17 @@ def compile(self, *args, **kwargs): metrics = [self.get_metric(metric) for metric in metrics] kwargs["metrics"] = metrics # Let the standard keras model do the rest: - return self._model.compile(*args, **kwargs) - + results = self._model.compile(*args, **kwargs) + self.initialize_model() + return results def predict(self, inputs): """ Propagate input patterns to a bank in the network. """ + if self._model is None: + raise Exception("Model has not yet been compiled") + input_vectors = self._extract_inputs(inputs, self.input_bank_order) try: outputs = self._model(input_vectors, training=False).numpy() @@ -589,7 +641,10 @@ def predict(self, inputs): % hints ) from None - return outputs + if len(self.output_bank_order) == 1: + return outputs[0] + else: + return [item[0] for item in outputs] def set_pca_spaces(self, inputs): """ @@ -614,6 +669,9 @@ def get_input_length(self, inputs): return len(inputs[0]) def predict_histogram_to(self, inputs, layer_name): + if self._model is None: + raise Exception("Model has not yet been compiled") + hidden_raw = self.predict_to(inputs, layer_name) plt.hist(hidden_raw) @@ -625,6 +683,9 @@ def predict_histogram_to(self, inputs, layer_name): return image def predict_pca_to(self, inputs, layer_name, colors, sizes): + if self._model is None: + raise Exception("Model has not yet been compiled") + if layer_name not in self._state["pca"]: raise Exception("Need to set_pca_spaces first") @@ -664,6 +725,9 @@ def predict_pca( ): """ """ + if self._model is None: + raise Exception("Model has not yet been compiled") + # This are not sticky; need to set each time: config["rotate"] = rotate config["scale"] = scale @@ -717,6 +781,9 @@ def predict_histogram( ): """ """ + if self._model is None: + raise Exception("Model has not yet been compiled") + # This are not sticky; need to set each time: config["rotate"] = rotate config["scale"] = scale @@ -763,6 +830,9 @@ def predict_to(self, inputs, layer_name): Returns: a numpy array """ + if self._model is None: + raise Exception("Model has not yet been compiled") + input_names = self._input_layer_names[layer_name] model = self._predict_models[input_names, layer_name] input_vectors = self._extract_inputs(inputs, input_names) @@ -791,6 +861,9 @@ def predict_from(self, inputs, from_layer_name, to_layer_name): """ from tensorflow.keras.models import Model + if self._model is None: + raise Exception("Model has not yet been compiled") + key = (tuple([from_layer_name]), to_layer_name) if key not in self._predict_models: from_layer = self[from_layer_name] @@ -892,6 +965,9 @@ def display( clear=True, **config, ): + if self._model is None: + raise Exception("Model has not yet been compiled") + if return_type is None: try: get_ipython() # noqa: F821 @@ -1052,7 +1128,7 @@ def _get_input_tensors(self, layer_name, input_list): Given a layer_name, return {input_layer_name: tensor} """ # Recursive; results in input_list of [(name, tensor), ...] - for layer in self._get_input_layers(layer_name): + for layer in self._get_layers_to(layer_name): if layer.name in self._input_layer_names: for layer_name in self._input_layer_names[layer.name]: if layer_name not in [name for (name, tensor) in input_list]: @@ -1116,27 +1192,23 @@ def _make_color(self, item): else: return tuple(item) - def _get_input_layers(self, layer_name=None): - if layer_name is None: - return tuple( - [x for x in self._layers if self._get_layer_type(x.name) == "input"] - ) - else: - return tuple( - [self._layers_map[layer_from] for layer_from, layer_to in self._connections - if layer_to == layer_name] - ) - - def _get_output_layers(self, layer_name=None): - if layer_name is None: - return tuple( - [x for x in self._layers if self._get_layer_type(x.name) == "output"] - ) - else: - return tuple( - [self._layers_map[layer_to] for layer_from, layer_to in self._connections - if layer_from == layer_name] - ) + def _get_input_layers(self): + layers = set() + for layer_from, layer_to in self._connections: + layers.add(layer_from) + for layer_from, layer_to in self._connections: + if layer_to in layers: + layers.remove(layer_to) + return [self._layers_map[name] for name in layers] + + def _get_output_layers(self): + layers = set() + for layer_from, layer_to in self._connections: + layers.add(layer_to) + for layer_from, layer_to in self._connections: + if layer_from in layers: + layers.remove(layer_from) + return [self._layers_map[name] for name in layers] def vshape(self, layer_name): """ @@ -1275,7 +1347,7 @@ def _get_act_minmax(self, layer_name): """ layer = self[layer_name] if layer.__class__.__name__ == "Flatten": - in_layer = self._get_input_layers(layer_name)[0] + in_layer = self._get_layers_to(layer_name)[0] return self._get_act_minmax(in_layer.name) elif self._get_layer_type(layer_name) == "input": color, mini, maxi = self._get_colormap(layer) @@ -1619,7 +1691,7 @@ def build_struct(self, inputs, targets, mode, colors, sizes): continue elif anchor: continue - for out in self._get_output_layers(layer_name): + for out in self._get_layers_from(layer_name): if ( out.name not in positioning ): # is it drawn yet? if not, continue, @@ -1800,7 +1872,7 @@ def build_struct(self, inputs, targets, mode, colors, sizes): x1 = cwidth + width / 2 y1 = cheight - 1 # Arrows going up - for out in self._get_output_layers(layer_name): + for out in self._get_layers_from(layer_name): if out.name not in positioning: continue # draw an arrow between layers: @@ -2054,8 +2126,8 @@ def _get_layer_type(self, layer_name): layer based on its connections. If no connections, then it is "unconnected". """ - incoming_connections = self._get_input_layers(layer_name) - outgoing_connections = self._get_output_layers(layer_name) + incoming_connections = self._get_layers_to(layer_name) + outgoing_connections = self._get_layers_from(layer_name) if len(incoming_connections) == 0 and len(outgoing_connections) == 0: return "unconnected" elif len(incoming_connections) > 0 and len(outgoing_connections) > 0: @@ -2084,7 +2156,7 @@ def _get_level_ordering(self): levels = {} for layer in self._layers: level = max( - [levels[lay.name] for lay in self._get_input_layers(layer.name)] + [-1] + [levels[lay.name] for lay in self._get_layers_to(layer.name)] + [-1] ) levels[layer.name] = level + 1 max_level = max(levels.values()) @@ -2095,7 +2167,7 @@ def _get_level_ordering(self): ] ordering.append( [ - (name, False, [x.name for x in self._get_input_layers(name)]) + (name, False, [x.name for x in self._get_layers_to(name)]) for name in layer_names ] ) # (going_to/layer_name, anchor, coming_from) @@ -2133,7 +2205,7 @@ def _get_level_ordering(self): else: # if next level doesn't contain an outgoing # connection, add it to next level as anchor point - for layer in self._get_output_layers(name): + for layer in self._get_layers_from(name): next_level = [ (n, anchor) for (n, anchor, fname) in ordering[level + 1] ] @@ -2608,7 +2680,7 @@ def __init__( * keras layer instance: an instance of a keras layer, like Flatten() """ from tensorflow.keras.models import Model - from tensorflow.keras.layers import Dense, Input, Layer + from tensorflow.keras.layers import Dense, InputLayer, Layer import tensorflow.keras.layers def make_name(index, total): @@ -2634,7 +2706,7 @@ def make_layer(index, layers, activation): size = layers[index] if not isinstance(size, (list, tuple)): size = tuple([size]) - return Input(size, name=name) + return InputLayer(size, name=name) else: size = layers[index] if isinstance(size, int): @@ -2649,17 +2721,17 @@ def make_layer(index, layers, activation): make_layer(index, layers, activation) for index in range(len(layers)) ] - current_layer = layers[0] - for layer in layers[1:]: - current_layer = layer(current_layer) - model = Model(inputs=layers[0], outputs=current_layer, name=name) + super().__init__(layers=layers) + for i in range(len(layers) - 1): + self.connect(layers[i].name, layers[i + 1].name) if metrics is None: metrics = ["tolerance_accuracy"] - # Replaced special named metrics with ours: - super()._init_state() metrics = [self.get_metric(name) for name in metrics] - model.compile(optimizer=self._make_optimizer(optimizer), loss=loss, metrics=metrics) - super().__init__(model=model) + self.compile( + optimizer=self._make_optimizer(optimizer), + loss=loss, + metrics=metrics + ) def _make_optimizer(self, optimizer): import tensorflow as tf diff --git a/aitk/networks/utils.py b/aitk/networks/utils.py index 28f96db..16dc0a9 100644 --- a/aitk/networks/utils.py +++ b/aitk/networks/utils.py @@ -128,33 +128,12 @@ def find_path(network, from_layer, to_layer_name): return current.path else: # expand: - for layer in network._get_output_layers(current.name): + for layer in network._get_layers_from(current.name): layer.path = current.path + [layer.name] queue.append(layer) return None -def topological_sort(network): - # First, marke them all not-visited: - for layer_from_name, layer_to_name in network._connections: - network._layers_map[layer_from_name].visited = False - network._layers_map[layer_to_name].visited = False - # Next gather them: - layers = [] - queue = list(network._get_input_layers()) - while queue: - current = queue.pop(0) - if not current.visited: - layers.append(current) - current.visited = True - queue.extend(list(network._get_output_layers(current.name))) - for layer_from_name, layer_to_name in network._connections: - if network._layers_map[layer_from_name].visited is False: - raise Exception("Layer %r is not part of network graph" % layer_from_name) - elif network._layers_map[layer_to_name].visited is False: - raise Exception("Layer %r is not part of network graph" % layer_to_name) - return layers - def scale_output_for_image(vector, minmax, truncate=False): """ Given an activation name (or something else) and an output diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py new file mode 100644 index 0000000..b84ce93 --- /dev/null +++ b/tests/test_networks/test_network.py @@ -0,0 +1,102 @@ +from aitk.networks import Network, SimpleNetwork +from tensorflow.keras.layers import InputLayer, Dense + + +def test_network_names(): + network = Network() + network.add(InputLayer([1])) + network.add(InputLayer([2])) + network.add(Dense(5)) + network.add(Dense(6)) + + assert network._layers[0].name.startswith("input") + assert network._layers[1].name.startswith("input_") + assert network._layers[2].name.startswith("dense") + assert network._layers[3].name.startswith("dense_") + + +def test_network_names_again(): + # Should still follow this pattern + network = Network() + network.add(InputLayer([1])) + network.add(InputLayer([2])) + network.add(Dense(5)) + network.add(Dense(6)) + + assert network._layers[0].name.startswith("input") + assert network._layers[1].name.startswith("input_") + assert network._layers[2].name.startswith("dense") + assert network._layers[3].name.startswith("dense_") + + +def test_network_sequential_1(): + network = Network() + network.add(InputLayer([2])) + network.add(Dense(5)) + network.add(Dense(10)) + + network.connect() + network.compile() + + output = network.predict([1, 1]) + + assert len(output) == 10 + +def test_network_sequential_2(): + network = SimpleNetwork( + InputLayer([2]), + Dense(5), + Dense(10), + ) + + network.connect() + network.compile() + + output = network.predict([1, 1]) + + assert len(output) == 10 + +def test_network_sequential_3(): + network = SimpleNetwork( + [2], + 5, + 10, + ) + + network.connect() + network.compile() + + output = network.predict([1, 1]) + + assert len(output) == 10 + +def test_network_sequential_4(): + network = SimpleNetwork( + 2, + 5, + 10, + ) + + network.connect() + network.compile() + + output = network.predict([1, 1]) + + assert len(output) == 10 + +def test_network_multi_inputs(): + network = Network() + network.add(InputLayer([1], name="input-1")) + network.add(InputLayer([2], name="input-2")) + network.add(Dense(5, name="hidden")) + network.add(Dense(6, name="output")) + + network.connect("input-1", "hidden") + network.connect("input-2", "hidden") + network.connect("hidden", "output") + + network.compile() + + output = network.predict([[1], [1, 2]]) + + assert len(output) == 6 From 1ce0fcd4ba2f6f4492b6feac00e632163fe6aa35 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 06:49:52 -0400 Subject: [PATCH 07/37] Working predict, display --- aitk/networks/network.py | 3 +-- tests/test_networks/test_network.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 3b3e448..41549a7 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -211,7 +211,6 @@ def initialize(self, inputs=None, reset=True): self.config["layers"][layer.name]["colormap"] = ("gray", minmax[0], minmax[1]) else: self._initialized = True - input_dataset = self.input_to_dataset(inputs) # If reset is true, we set to extremes so any value will adjust # Only do this on input layers: if reset: @@ -228,7 +227,7 @@ def initialize(self, inputs=None, reset=True): # Now we set the minmax for input layer, based on past values # or extremes: for layer in self._layers: - outputs = self.predict_to(input_dataset, layer.name) + outputs = self.predict_to(inputs, layer.name) color_orig, min_orig, max_orig = self.config["layers"][layer.name]["colormap"] min_new, max_new = math.floor(outputs.min()), math.ceil(outputs.max()) if min_new != max_new: diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py index b84ce93..1fbe0cc 100644 --- a/tests/test_networks/test_network.py +++ b/tests/test_networks/test_network.py @@ -84,6 +84,19 @@ def test_network_sequential_4(): assert len(output) == 10 +def test_network_display(): + network = SimpleNetwork( + 2, + 5, + 10, + ) + + network.connect() + network.compile() + + output = network.display([1, 1], return_type="image") + + def test_network_multi_inputs(): network = Network() network.add(InputLayer([1], name="input-1")) From f582eadd045fa2e9f459f9a197c275e9b007b8de Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 07:53:30 -0400 Subject: [PATCH 08/37] Working display --- aitk/networks/network.py | 43 +++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 41549a7..c2ffd50 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -227,7 +227,8 @@ def initialize(self, inputs=None, reset=True): # Now we set the minmax for input layer, based on past values # or extremes: for layer in self._layers: - outputs = self.predict_to(inputs, layer.name) + outputs = self.predict_to(inputs, layer.name, return_type="numpy") + # FIXME: multiple output banks are lists of numpys color_orig, min_orig, max_orig = self.config["layers"][layer.name]["colormap"] min_new, max_new = math.floor(outputs.min()), math.ceil(outputs.max()) if min_new != max_new: @@ -641,9 +642,9 @@ def predict(self, inputs): ) from None if len(self.output_bank_order) == 1: - return outputs[0] + return outputs[0].tolist() else: - return [item[0] for item in outputs] + return [item[0].tolist() for item in outputs] def set_pca_spaces(self, inputs): """ @@ -818,7 +819,7 @@ def predict_histogram( raise ValueError("unable to convert to return_type %r" % return_type) - def predict_to(self, inputs, layer_name): + def predict_to(self, inputs, layer_name, return_type="list"): """ Propagate input patterns to a bank in the network. @@ -852,7 +853,16 @@ def predict_to(self, inputs, layer_name): % hints ) from None - return outputs + if len(self.output_bank_order) == 1: + if return_type == "list": + return outputs[0].tolist() + elif return_type == "numpy": + return outputs[0] + else: + if return_type == "list": + return [item[0].tolist() for item in outputs] + elif return_type == "numpy": + return [item[0] for item in outputs] def predict_from(self, inputs, from_layer_name, to_layer_name): """ @@ -1064,13 +1074,11 @@ def propagate_to(self, return_type=None, channel=None, ): - dataset = self.input_to_dataset(inputs) # FIXME: rather than just the first, format in case # of multiple output layers - array = self.predict_to(dataset, layer_name) + array = self.predict_to(inputs, layer_name) # FIXME: get output banks # Strip out just the single return row from one bank - array = array[0] if return_type == "image": return self._layer_array_to_image(layer_name, array, channel=channel) else: @@ -1491,13 +1499,16 @@ def target_to_dataset(self, target): def to_svg(self, inputs=None, targets=None, mode="activation", colors=None, sizes=None): """ """ + # FIXME: # First, turn single patterns into a dataset: - if inputs is not None: - if mode == "activation": - inputs = self.input_to_dataset(inputs) - if targets is not None: - if mode == "activation": - targets = self.target_to_dataset(targets) + #if inputs is not None: + # if mode == "activation": + # inputs = self._extract_inputs(inputs, self.input_bank_order) + # + #if targets is not None: + # if mode == "activation": + # # FIXME: + # targets = self.target_to_dataset(targets) # Next, build the structures: struct = self.build_struct(inputs, targets, mode, colors, sizes) templates = get_templates(self.config) @@ -2319,7 +2330,7 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): if inputs is None: inputs = self.make_dummy_dataset() if targets is not None: - outputs = self._model(inputs, training=False).numpy() + outputs = self.predict(inputs) if len(self.output_bank_order) == 1: targets = [targets] errors = (np.array(outputs) - np.array(targets)).tolist() @@ -2368,7 +2379,7 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): else: # activations of a dataset try: image = self.make_image( - layer_name, self.predict_to(inputs, layer_name)[0] + layer_name, self.predict_to(inputs, layer_name) ) except Exception: # Error: make a red image From f8dd8a54a7e898e462f9e8025e062f32daa2ef8e Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 08:05:53 -0400 Subject: [PATCH 09/37] Linting --- aitk/networks/network.py | 384 ++++++++++++++++------------ tests/test_networks/test_network.py | 21 +- 2 files changed, 241 insertions(+), 164 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index c2ffd50..f692c92 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -2,7 +2,7 @@ # ****************************************************** # aitk.networks: Keras model wrapper with visualizations # -# Copyright (c) 2021 Douglas S. Blank +# Copyright (c) 2021-2024 Douglas S. Blank # # https://github.com/ArtificialIntelligenceToolkit/aitk.networks # @@ -16,44 +16,41 @@ import numbers import operator import random -import sys from types import FunctionType import matplotlib.pyplot as plt import numpy as np from matplotlib import cm -from PIL import Image, ImageDraw +from PIL import Image + +from aitk.utils import array_to_image from .utils import ( find_path, get_argument_bindings, + get_array_shape, + get_connections, get_error_colormap, - get_layer_name, get_templates, image_to_uri, is_keras_tensor, make_input_from_shape, render_curve, - scale_output_for_image, svg_to_image, - get_array_shape, - get_connections, ) -from aitk.utils import array_to_image - try: from IPython.display import HTML, clear_output, display except ImportError: HTML = None + class Network: """ Wrapper around a keras.Model. """ - def __init__(self, model=None, layers=None, name="Network", **config): - from tensorflow.keras.models import Model + def __init__(self, model=None, layers=None, name="Network", **config): if model is not None and layers is not None: raise Exception("Network() takes model or layers, not both") @@ -144,7 +141,7 @@ def add(self, layer): """ Add a layer to the network. """ - from tensorflow.keras.layers import InputLayer, Input, Layer + from tensorflow.keras.layers import Layer if isinstance(layer, FunctionType): raise Exception("Don't use Input; use InputLayer") @@ -208,7 +205,11 @@ def initialize(self, inputs=None, reset=True): self.config["layers"][layer.name]["colormap"] = ("gray", -2, 2) else: minmax = self._get_act_minmax(layer.name) - self.config["layers"][layer.name]["colormap"] = ("gray", minmax[0], minmax[1]) + self.config["layers"][layer.name]["colormap"] = ( + "gray", + minmax[0], + minmax[1], + ) else: self._initialized = True # If reset is true, we set to extremes so any value will adjust @@ -221,18 +222,24 @@ def initialize(self, inputs=None, reset=True): # FIXME: set color at some point if image self.config["layers"][layer.name]["colormap"] = ( "gray", - float("+inf"), # extreme too big - float("-inf"), # extreme too small + float("+inf"), # extreme too big + float("-inf"), # extreme too small ) # Now we set the minmax for input layer, based on past values # or extremes: for layer in self._layers: outputs = self.predict_to(inputs, layer.name, return_type="numpy") # FIXME: multiple output banks are lists of numpys - color_orig, min_orig, max_orig = self.config["layers"][layer.name]["colormap"] + color_orig, min_orig, max_orig = self.config["layers"][layer.name][ + "colormap" + ] min_new, max_new = math.floor(outputs.min()), math.ceil(outputs.max()) if min_new != max_new: - self.config["layers"][layer.name]["colormap"] = (color_orig, min_new, max_new) + self.config["layers"][layer.name]["colormap"] = ( + color_orig, + min_new, + max_new, + ) else: # Don't let them be equal: self.config["layers"][layer.name]["colormap"] = ( @@ -242,8 +249,7 @@ def initialize(self, inputs=None, reset=True): ) def connect(self, from_layer_name=None, to_layer_name=None): - """ - """ + """ """ if len(self._layers) == 0: raise Exception("no layers have been added") if from_layer_name is not None and not isinstance(from_layer_name, str): @@ -261,15 +267,15 @@ def connect(self, from_layer_name=None, to_layer_name=None): if not isinstance(from_layer_name, str): raise Exception("from_layer_name should be a string") if from_layer_name not in self._layers_map: - raise Exception('unknown layer: %s' % from_layer_name) + raise Exception("unknown layer: %s" % from_layer_name) if not isinstance(to_layer_name, str): raise Exception("to_layer_name should be a string") if to_layer_name not in self._layers_map: - raise Exception('unknown layer: %s' % to_layer_name) + raise Exception("unknown layer: %s" % to_layer_name) from_layer = self[from_layer_name] to_layer = self[to_layer_name] # Check for input going to a Dense to warn: - #if len(from_layer.shape) > 2 and to_layer.__class__.__name__ == "Dense": + # if len(from_layer.shape) > 2 and to_layer.__class__.__name__ == "Dense": # print("WARNING: connected multi-dimensional input layer '%s' to layer '%s'; consider adding a FlattenLayer between them" % ( # from_layer.name, to_layer.name), file=sys.stderr) self._connections.append((from_layer_name, to_layer_name)) @@ -290,7 +296,7 @@ def fit(self, *args, **kwargs): * monitor: (str) metric to monitor to determine whether to stop * callbacks: (list) list of callbacks """ - from .callbacks import UpdateCallback, make_early_stop, make_stop, make_save + from .callbacks import UpdateCallback, make_early_stop, make_save, make_stop # plot = True # if plot: @@ -298,7 +304,6 @@ def fit(self, *args, **kwargs): # mpl_backend = matplotlib.get_backend() # else: # mpl_backend = None - # Get any kwargs that are not standard: report_rate = kwargs.pop("report_rate", 1) # Early stopping and Stop on Accuracy, Val_accuracy @@ -348,8 +353,8 @@ def fit(self, *args, **kwargs): kwargs["x"] = np.array(kwargs["x"]) kwargs["y"] = np.array(kwargs["y"]) - self._fit_inputs = kwargs.get("x") # inputs - self._fit_targets = kwargs.get("y") # targets + self._fit_inputs = kwargs.get("x") # inputs + self._fit_targets = kwargs.get("y") # targets # call underlying model fit: try: @@ -362,13 +367,23 @@ def fit(self, *args, **kwargs): # FIXME: don't save if didn't go through loop? self._history["weights"].append((self._epoch, self.get_weights())) - - metrics = {key: history.history[key][-1] for key in history.history - if len(history.history[key]) > 0} + metrics = { + key: history.history[key][-1] + for key in history.history + if len(history.history[key]) > 0 + } ## FIXME: getting epochs by keyword: - print("Epoch %d/%d %s" % (self._epoch, kwargs["epochs"], " - ".join( - ["%s: %s" % (key, value) for (key, value) in metrics.items()]))) + print( + "Epoch %d/%d %s" + % ( + self._epoch, + kwargs["epochs"], + " - ".join( + ["%s: %s" % (key, value) for (key, value) in metrics.items()] + ), + ) + ) return history def in_console(self, mpl_backend: str) -> bool: @@ -421,14 +436,13 @@ def on_epoch_end(self, callback, logs, report_rate=None, clear=True): index = random.randint(0, self.get_input_length(self._fit_inputs) - 1) inputs = self.get_input_from_dataset(index, self._fit_inputs) targets = self.get_target_from_dataset(index, self._fit_targets) - self.propagate(inputs, targets) # update watchers + self.propagate(inputs, targets) # update watchers metrics = [list(history[1].keys()) for history in self._history["metrics"]] metrics = set([item for sublist in metrics for item in sublist]) def match_acc(name): - return (name.endswith("acc") or - name.endswith("accuracy")) + return name.endswith("acc") or name.endswith("accuracy") def match_val(name): return name.startswith("val_") @@ -465,7 +479,7 @@ def get_xy(name): loss_ax.plot(x_values, y_values, label=metric, color="orange") elif match_acc(metric) and not match_val(metric) and acc_ax is not None: acc_ax.plot(x_values, y_values, label=metric, color="b") # blue - elif match_acc(metric) and match_val(metric) and acc_ax is not None: + elif match_acc(metric) and match_val(metric) and acc_ax is not None: acc_ax.plot(x_values, y_values, label=metric, color="c") # cyan # FIXME: add a chart for each metric # else: @@ -483,7 +497,6 @@ def get_xy(name): acc_ax.set_ylabel("Accuracy") acc_ax.legend(loc="best") - if True or format == "svg": # FIXME: work in console # if (callback is not None and not callback.in_console) or format == "svg": @@ -534,21 +547,23 @@ def build_model(self): if layer.name not in froms: output_layers.append(layer.name) # Now we build the model: - outputs = [ - self._build_graph_to(output_layer) for output_layer in output_layers - ] - inputs = [ - self[layer_name]._input_tensor for layer_name in input_layers - ] + outputs = [self._build_graph_to(output_layer) for output_layer in output_layers] + inputs = [self[layer_name]._input_tensor for layer_name in input_layers] self._model = Model(inputs=inputs, outputs=outputs, name=self._name) def _get_layers_to(self, layer_name): - return [self[connection[0]] for connection in self._connections - if connection[1] == layer_name] + return [ + self[connection[0]] + for connection in self._connections + if connection[1] == layer_name + ] def _get_layers_from(self, layer_name): - return [self[connection[1]] for connection in self._connections - if connection[0] == layer_name] + return [ + self[connection[1]] + for connection in self._connections + if connection[0] == layer_name + ] def topological_sort(self, layers, input_layers): for layer in layers: @@ -564,7 +579,9 @@ def topological_sort(self, layers, input_layers): queue.extend(self._get_layers_from(current.name)) for layer in layers: if layer.visited is False: - raise Exception("Layer %r is not part of the network graph" % layer.name) + raise Exception( + "Layer %r is not part of the network graph" % layer.name + ) return sorted_layers def _build_graph_to(self, layer_name): @@ -576,14 +593,16 @@ def _build_graph_to(self, layer_name): # An input layer: return self[layer_name] - incoming_layers = [self._build_graph_to(incoming_layer.name) - for incoming_layer in layers] + incoming_layers = [ + self._build_graph_to(incoming_layer.name) for incoming_layer in layers + ] if len(incoming_layers) == 1: incoming_layer = incoming_layers[0] - else: # more than one - incoming_layer = Concatenate()([layer._input_tensor - for layer in incoming_layers]) + else: # more than one + incoming_layer = Concatenate()( + [layer._input_tensor for layer in incoming_layers] + ) if isinstance(incoming_layer, InputLayer): incoming_layer = incoming_layer._input_tensor @@ -626,9 +645,10 @@ def predict(self, inputs): input_vectors = self._extract_inputs(inputs, self.input_bank_order) try: outputs = self._model(input_vectors, training=False).numpy() - except Exception as exc: + except Exception: input_layers_shapes = [ - self._get_raw_output_shape(layer_name) for layer_name in self.input_bank_order + self._get_raw_output_shape(layer_name) + for layer_name in self.input_bank_order ] hints = ", ".join( [ @@ -675,7 +695,7 @@ def predict_histogram_to(self, inputs, layer_name): hidden_raw = self.predict_to(inputs, layer_name) plt.hist(hidden_raw) - plt.axis('off') + plt.axis("off") fp = io.BytesIO() plt.savefig(fp, format="png") plt.close() @@ -693,15 +713,15 @@ def predict_pca_to(self, inputs, layer_name, colors, sizes): pca_space = self._state["pca"][layer_name] if pca_space is not None: hidden_pca = pca_space.transform(hidden_raw) - x = hidden_pca[:,0] - y = hidden_pca[:,1] + x = hidden_pca[:, 0] + y = hidden_pca[:, 1] else: # Only one hidden layer unit; we'll use zeros for Y axis x = hidden_raw y = np.zeros(len(hidden_raw)) plt.scatter(x, y, c=colors, s=sizes) - plt.axis('off') + plt.axis("off") fp = io.BytesIO() plt.savefig(fp, format="png") plt.close() @@ -723,8 +743,7 @@ def predict_pca( sizes=None, **config, ): - """ - """ + """ """ if self._model is None: raise Exception("Model has not yet been compiled") @@ -738,8 +757,9 @@ def predict_pca( self.set_pca_spaces(inputs) try: - svg = self.to_svg(inputs=inputs, targets=targets, mode="pca", - colors=colors, sizes=sizes) + svg = self.to_svg( + inputs=inputs, targets=targets, mode="pca", colors=colors, sizes=sizes + ) except KeyboardInterrupt: raise KeyboardInterrupt() from None @@ -766,7 +786,6 @@ def predict_pca( else: raise ValueError("unable to convert to return_type %r" % return_type) - def predict_histogram( self, inputs=None, @@ -779,8 +798,7 @@ def predict_histogram( clear=True, **config, ): - """ - """ + """ """ if self._model is None: raise Exception("Model has not yet been compiled") @@ -818,7 +836,6 @@ def predict_histogram( else: raise ValueError("unable to convert to return_type %r" % return_type) - def predict_to(self, inputs, layer_name, return_type="list"): """ Propagate input patterns to a bank in the network. @@ -838,7 +855,7 @@ def predict_to(self, inputs, layer_name, return_type="list"): input_vectors = self._extract_inputs(inputs, input_names) try: outputs = model(input_vectors, training=False).numpy() - except Exception as exc: + except Exception: input_layers_shapes = [ self._get_raw_output_shape(layer_name) for layer_name in input_names ] @@ -950,8 +967,13 @@ def get_image( self.initialize(inputs) try: - svg = self.to_svg(inputs=inputs, targets=targets, mode="activation", - colors=None, sizes=None) + svg = self.to_svg( + inputs=inputs, + targets=targets, + mode="activation", + colors=None, + sizes=None, + ) except KeyboardInterrupt: raise KeyboardInterrupt() from None @@ -985,8 +1007,16 @@ def display( return_type = "image" if return_type == "html": - svg = self.get_image(inputs, targets, show_error, show_targets, "svg", - rotate, scale, **config) + svg = self.get_image( + inputs, + targets, + show_error, + show_targets, + "svg", + rotate, + scale, + **config, + ) if HTML is not None: if clear: clear_output(wait=True) @@ -996,13 +1026,20 @@ def display( "need to install `IPython` or use Network.display(return_type='image')" ) else: - image = self.get_image(inputs, targets, show_error, show_targets, return_type, - rotate, scale, **config) + image = self.get_image( + inputs, + targets, + show_error, + show_targets, + return_type, + rotate, + scale, + **config, + ) return image def watch_weights(self, to_name): - """ - """ + """ """ from .watchers import WeightWatcher name = "WeightWatcher: to %s" % (to_name,) @@ -1016,8 +1053,7 @@ def watch_weights(self, to_name): display(watcher._widget) def watch_layer(self, layer_name): - """ - """ + """ """ from .watchers import LayerWatcher name = "LayerWatcher: %s" % (layer_name,) @@ -1030,14 +1066,14 @@ def watch_layer(self, layer_name): display(watcher._widget) - def watch(self, + def watch( + self, show_error=None, show_targets=None, rotate=None, scale=None, ): - """ - """ + """ """ from .watchers import NetworkWatcher name = "NetworkWatcher" @@ -1051,10 +1087,11 @@ def watch(self, widget = watcher.get_widget(show_error, show_targets, rotate, scale) display(widget) - def propagate(self, - inputs, - targets=None, - show=True, + def propagate( + self, + inputs, + targets=None, + show=True, ): """ Update all of the watchers whatever they may be watching, @@ -1068,11 +1105,12 @@ def propagate(self, # of multiple output layers return self._model(dataset, training=False)[0].numpy() - def propagate_to(self, - inputs, - layer_name, - return_type=None, - channel=None, + def propagate_to( + self, + inputs, + layer_name, + return_type=None, + channel=None, ): # FIXME: rather than just the first, format in case # of multiple output layers @@ -1084,9 +1122,10 @@ def propagate_to(self, else: return array - def propagate_each(self, - inputs=None, - targets=None, + def propagate_each( + self, + inputs=None, + targets=None, ): """ Update all of the watchers whatever they may be watching. @@ -1127,7 +1166,8 @@ def _build_predict_models(self): else: self._input_layer_names[layer.name] = tuple([layer.name]) self._predict_models[tuple([layer.name]), layer.name] = Model( - inputs=[layer._input_tensor], outputs=[layer.output], + inputs=[layer._input_tensor], + outputs=[layer.output], ) def _get_input_tensors(self, layer_name, input_list): @@ -1153,8 +1193,6 @@ def make_image(self, layer_name, vector, colormap=None): Given an activation name (or function), and an output vector, display make and return an image widget. """ - import tensorflow.keras.backend as K - image = self._layer_array_to_image(layer_name, vector) # If rotated, and has features, rotate it: if self.config.get("rotate", False): @@ -1167,7 +1205,6 @@ def make_image(self, layer_name, vector, colormap=None): return image def _layer_has_channels(self, layer_name): - layer = self[layer_name] class_name = self[layer_name].__class__.__name__ return class_name in ["Conv2D", "MaxPooling2D"] @@ -1175,10 +1212,13 @@ def _layer_array_to_image(self, layer_name, vector, channel=None): if self._layer_has_channels(layer_name): if channel is None: channel = self._get_feature(layer_name) - select = tuple([slice(None) for i in range(len(vector.shape) - 1)] + [slice(channel, channel+1)]) + select = tuple( + [slice(None) for i in range(len(vector.shape) - 1)] + + [slice(channel, channel + 1)] + ) vector = vector[select] else: - pass # let's try it as is + pass # let's try it as is # If vshape is given, then resize the vector: vshape = self.vshape(layer_name) @@ -1231,8 +1271,9 @@ def vshape(self, layer_name): def _get_output_shape(self, layer_name): layer = self[layer_name] - if ((layer._build_shapes_dict is not None) and - ("input_shape" in layer._build_shapes_dict)): + if (layer._build_shapes_dict is not None) and ( + "input_shape" in layer._build_shapes_dict + ): output_shape = layer.compute_output_shape( layer._build_shapes_dict["input_shape"] ) @@ -1253,8 +1294,9 @@ def _get_input_shape(self, layer_name): def _get_raw_output_shape(self, layer_name): layer = self[layer_name] - if ((layer._build_shapes_dict is not None) and - ("input_shape" in layer._build_shapes_dict)): + if (layer._build_shapes_dict is not None) and ( + "input_shape" in layer._build_shapes_dict + ): output_shape = layer.compute_output_shape( layer._build_shapes_dict["input_shape"] ) @@ -1309,10 +1351,14 @@ def format_range(minmax): if activation: retval += "\nAct function: %s" % activation retval += "\nAct output range: %s" % ( - format_range(self._get_act_minmax(layer_name),) + format_range( + self._get_act_minmax(layer_name), + ) ) retval += "\nActual minmax: %s" % ( - format_range(self._layer_minmax(layer_name),) + format_range( + self._layer_minmax(layer_name), + ) ) retval += "\nShape = %s" % (self._get_raw_output_shape(layer_name),) return retval @@ -1442,7 +1488,7 @@ def get_target_from_dataset(self, index, dataset): return data def enumerate_dataset(self, dataset1, dataset2=None): - """" + """ " Takes a dataset and turns it into individual sets of one pattern each. """ @@ -1496,18 +1542,19 @@ def target_to_dataset(self, target): targets = [np.array([bank]) for bank in target] return targets - def to_svg(self, inputs=None, targets=None, mode="activation", colors=None, sizes=None): - """ - """ + def to_svg( + self, inputs=None, targets=None, mode="activation", colors=None, sizes=None + ): + """ """ # FIXME: # First, turn single patterns into a dataset: - #if inputs is not None: + # if inputs is not None: # if mode == "activation": # inputs = self._extract_inputs(inputs, self.input_bank_order) # - #if targets is not None: + # if targets is not None: # if mode == "activation": - # # FIXME: + # # FIXME: # targets = self.target_to_dataset(targets) # Next, build the structures: struct = self.build_struct(inputs, targets, mode, colors, sizes) @@ -1529,9 +1576,12 @@ def to_svg(self, inputs=None, targets=None, mode="activation", colors=None, size if template_name == "label_svg" and rotate: dict["x"] += 8 dict["text_anchor"] = "middle" - dict["transform"] = ( - """ transform="rotate(-90 %s %s) translate(%s)" """ - % (dict["x"], dict["y"], 2) + dict[ + "transform" + ] = """ transform="rotate(-90 %s %s) translate(%s)" """ % ( + dict["x"], + dict["y"], + 2, ) else: dict["transform"] = "" @@ -1963,7 +2013,7 @@ def build_struct(self, inputs, targets, mode, colors, sizes): ] ) output_shape = self._get_output_shape(layer_name) - if (self._layer_has_channels(layer_name)): + if self._layer_has_channels(layer_name): features = str(output_shape[-1]) # FIXME: feature = str(self._get_feature(layer_name)) @@ -2045,7 +2095,7 @@ def build_struct(self, inputs, targets, mode, colors, sizes): # DONE! # Draw the title: if mode == "activation": - title = "Activations for %s" % self.config["name"] + title = "Activations for %s" % self.config["name"] elif mode == "pca": title = "PCAs for %s" % self.config["name"] elif mode == "histogram": @@ -2148,8 +2198,7 @@ def _get_layer_type(self, layer_name): return "input" def _get_layer_class(self, layer_name): - """ - """ + """ """ layer = self[layer_name] return layer.__class__.__name__ @@ -2227,8 +2276,8 @@ def _get_level_ordering(self): return ordering def _optimize_ordering(self, ordering): - def perms(l): - return list(itertools.permutations(l)) + def perms(items): + return list(itertools.permutations(items)) def distance(xy1, xy2): return math.sqrt((xy1[0] - xy2[0]) ** 2 + (xy1[1] - xy2[1]) ** 2) @@ -2376,30 +2425,38 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): elif mode == "histogram": image = self.predict_histogram_to(inputs, layer_name) keep_aspect_ratio = True - else: # activations of a dataset + else: # activations of a dataset try: image = self.make_image( layer_name, self.predict_to(inputs, layer_name) ) except Exception: # Error: make a red image - image = array_to_image([[ - [255, 0, 0], - [255, 0, 0], - ]]) + image = array_to_image( + [ + [ + [255, 0, 0], + [255, 0, 0], + ] + ] + ) (width, height) = image.size images[layer_name] = image # little image if self._get_layer_type(layer_name) == "output": if targets is not None: # Target image, targets set above: - target_colormap = ("grey", -2, 2) # FIXME: self[layer_name].colormap + target_colormap = ( + "grey", + -2, + 2, + ) # FIXME: self[layer_name].colormap target_bank = targets[self.output_bank_order.index(layer_name)] target_array = np.array(target_bank) target_image = self.make_image( layer_name, target_array, target_colormap ) # Error image, error set above: - error_colormap = (get_error_colormap(), -2, 2) # FIXME + error_colormap = (get_error_colormap(), -2, 2) # FIXME error_bank = errors[self.output_bank_order.index(layer_name)] error_array = np.array(error_bank) error_image = self.make_image( @@ -2435,7 +2492,6 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): max_width = max(max_width, row_width) # of all rows return max_width, max_height, row_heights, images, image_dims - def make_dummy_dataset(self): """ Make a stand-in dataset for this network: @@ -2443,8 +2499,7 @@ def make_dummy_dataset(self): inputs = [] for layer_name in self.input_bank_order: shape = self._get_input_shape(layer_name) - if (shape is None) or (isinstance(shape, (list, tuple)) - and None in shape): + if (shape is None) or (isinstance(shape, (list, tuple)) and None in shape): v = np.random.rand(100) else: v = np.random.rand(*shape) @@ -2515,6 +2570,7 @@ def set_config_layer(self, layer_name, **items): "border_color": "string", "border_width": "integer", } + def validate_type(value, format): if format == "integer": return isinstance(value, int) @@ -2525,7 +2581,7 @@ def validate_type(value, format): elif format == "boolean": return isinstance(value, bool) else: - return all([validate_type(v,f) for v,f in zip(value, format)]) + return all([validate_type(v, f) for v, f in zip(value, format)]) if layer_name in self.config["layers"]: for item in items: @@ -2533,7 +2589,10 @@ def validate_type(value, format): if validate_type(items[item], proper_items[item]): self.config["layers"][layer_name][item] = items[item] else: - raise AttributeError("invalid form for: %r; should be: %s" % (item, proper_items[item])) + raise AttributeError( + "invalid form for: %r; should be: %s" + % (item, proper_items[item]) + ) else: raise AttributeError("no such config layer item: %r" % item) else: @@ -2577,7 +2636,7 @@ def set_weights(self, weights): new_weights = [] for item in orig: total = functools.reduce(operator.mul, item.shape, 1) - w = np.array(weights[current:current + total]) + w = np.array(weights[current : current + total]) new_weights.append(w.reshape(item.shape)) current += total layer.set_weights(new_weights) @@ -2603,28 +2662,33 @@ def get_learning_rate(self): print("WARNING: you need to use an optimizer with lr") def get_metric(self, name): - import tensorflow.keras.backend as K import tensorflow as tf + import tensorflow.keras.backend as K if name == "tolerance_accuracy": self._state["tolerance_accuracy_used"] = True + def tolerance_accuracy(targets, outputs): return K.mean( K.all( K.less_equal( - K.abs(tf.cast(targets, tf.float32) - - tf.cast(outputs, tf.float32)), - self._tolerance - ), axis=-1 + K.abs( + tf.cast(targets, tf.float32) + - tf.cast(outputs, tf.float32) + ), + self._tolerance, + ), + axis=-1, ), - axis=-1) + axis=-1, + ) + return tolerance_accuracy else: return name def get_momentum(self): - """ - """ + """ """ if hasattr(self._model, "optimizer") and hasattr( self._model.optimizer, "momentum" ): @@ -2633,8 +2697,7 @@ def get_momentum(self): print("WARNING: you need to use an optimizer with momentum") def set_momentum(self, momentum): - """ - """ + """ """ if hasattr(self._model, "optimizer") and hasattr( self._model.optimizer, "momentum" ): @@ -2643,17 +2706,19 @@ def set_momentum(self, momentum): print("WARNING: you need to use an optimizer with momentum") def get_tolerance(self): - """ - """ + """ """ if not self._state["tolerance_accuracy_used"]: - print("WARNING: you need Network.compile(metrics=['tolerance_accuracy']) to use tolerance") + print( + "WARNING: you need Network.compile(metrics=['tolerance_accuracy']) to use tolerance" + ) return self._tolerance def set_tolerance(self, tolerance): - """ - """ + """ """ if not self._state["tolerance_accuracy_used"]: - print("WARNING: you need Network.compile(metrics=['tolerance_accuracy']) to use tolerance") + print( + "WARNING: you need Network.compile(metrics=['tolerance_accuracy']) to use tolerance" + ) self._tolerance = tolerance @@ -2689,9 +2754,8 @@ def __init__( * (int, int, ...): (input layers only) the shape of the input patterns * keras layer instance: an instance of a keras layer, like Flatten() """ - from tensorflow.keras.models import Model - from tensorflow.keras.layers import Dense, InputLayer, Layer import tensorflow.keras.layers + from tensorflow.keras.layers import Dense, InputLayer, Layer def make_name(index, total): if index == 0: @@ -2706,8 +2770,9 @@ def make_name(index, total): def make_layer(index, layers, activation): if isinstance(layers[index], Layer) or is_keras_tensor(layers[index]): return layers[index] - elif (isinstance(layers[index], str) and - hasattr(tensorflow.keras.layers, layers[index])): + elif isinstance(layers[index], str) and hasattr( + tensorflow.keras.layers, layers[index] + ): layer_class = getattr(tensorflow.keras.layers, layers[index]) return layer_class() else: @@ -2724,13 +2789,12 @@ def make_layer(index, layers, activation): elif len(size) == 2 and isinstance(size[1], str): size, activation_function = size else: - raise Exception("Invalid SimpleNetwork layer representation: %r" % size) + raise Exception( + "Invalid SimpleNetwork layer representation: %r" % size + ) return Dense(size, activation=activation_function, name=name) - layers = [ - make_layer(index, layers, activation) - for index in range(len(layers)) - ] + layers = [make_layer(index, layers, activation) for index in range(len(layers))] super().__init__(layers=layers) for i in range(len(layers) - 1): self.connect(layers[i].name, layers[i + 1].name) @@ -2738,9 +2802,7 @@ def make_layer(index, layers, activation): metrics = ["tolerance_accuracy"] metrics = [self.get_metric(name) for name in metrics] self.compile( - optimizer=self._make_optimizer(optimizer), - loss=loss, - metrics=metrics + optimizer=self._make_optimizer(optimizer), loss=loss, metrics=metrics ) def _make_optimizer(self, optimizer): diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py index 1fbe0cc..8802006 100644 --- a/tests/test_networks/test_network.py +++ b/tests/test_networks/test_network.py @@ -1,5 +1,16 @@ +# -*- coding: utf-8 -*- +# ****************************************************** +# aitk.networks: Keras model wrapper with visualizations +# +# Copyright (c) 2024 Douglas S. Blank +# +# https://github.com/ArtificialIntelligenceToolkit/aitk.networks +# +# ****************************************************** + +from tensorflow.keras.layers import Dense, InputLayer + from aitk.networks import Network, SimpleNetwork -from tensorflow.keras.layers import InputLayer, Dense def test_network_names(): @@ -13,8 +24,8 @@ def test_network_names(): assert network._layers[1].name.startswith("input_") assert network._layers[2].name.startswith("dense") assert network._layers[3].name.startswith("dense_") - - + + def test_network_names_again(): # Should still follow this pattern network = Network() @@ -42,6 +53,7 @@ def test_network_sequential_1(): assert len(output) == 10 + def test_network_sequential_2(): network = SimpleNetwork( InputLayer([2]), @@ -56,6 +68,7 @@ def test_network_sequential_2(): assert len(output) == 10 + def test_network_sequential_3(): network = SimpleNetwork( [2], @@ -70,6 +83,7 @@ def test_network_sequential_3(): assert len(output) == 10 + def test_network_sequential_4(): network = SimpleNetwork( 2, @@ -84,6 +98,7 @@ def test_network_sequential_4(): assert len(output) == 10 + def test_network_display(): network = SimpleNetwork( 2, From 2ab9576f482de4dcafb10f9ca94ed526d4f623b1 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 11:33:07 -0400 Subject: [PATCH 10/37] Unit test file for Lisa --- tests/test_networks/test_network_methods.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/test_networks/test_network_methods.py diff --git a/tests/test_networks/test_network_methods.py b/tests/test_networks/test_network_methods.py new file mode 100644 index 0000000..d0546f7 --- /dev/null +++ b/tests/test_networks/test_network_methods.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ****************************************************** +# aitk.networks: Keras model wrapper with visualizations +# +# Copyright (c) 2024 Douglas S. Blank +# +# https://github.com/ArtificialIntelligenceToolkit/aitk.networks +# +# ****************************************************** + +from tensorflow.keras.layers import Dense, InputLayer + +from aitk.networks import Network, SimpleNetwork + + +def test_sample(): + network = SimpleNetwork(1, 2, 3, 2, 1) + results = network.predict([1]) + + assert len(results) == 1 From 7ed5bc3af993f1cb462ad16cb21721f643932637 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 15:37:41 -0400 Subject: [PATCH 11/37] Add predict to tests --- tests/test_networks/test_network.py | 57 ++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py index 8802006..cc8515e 100644 --- a/tests/test_networks/test_network.py +++ b/tests/test_networks/test_network.py @@ -49,9 +49,10 @@ def test_network_sequential_1(): network.connect() network.compile() - output = network.predict([1, 1]) + output = network.predict([[1, 1]]) - assert len(output) == 10 + assert len(output) == 1 + assert len(output[0]) == 10 def test_network_sequential_2(): @@ -64,7 +65,7 @@ def test_network_sequential_2(): network.connect() network.compile() - output = network.predict([1, 1]) + output = network.propagate([1, 1]) assert len(output) == 10 @@ -79,7 +80,7 @@ def test_network_sequential_3(): network.connect() network.compile() - output = network.predict([1, 1]) + output = network.propagate([1, 1]) assert len(output) == 10 @@ -94,7 +95,7 @@ def test_network_sequential_4(): network.connect() network.compile() - output = network.predict([1, 1]) + output = network.propagate([1, 1]) assert len(output) == 10 @@ -111,6 +112,8 @@ def test_network_display(): output = network.display([1, 1], return_type="image") + assert output.size == (400, 260) + def test_network_multi_inputs(): network = Network() @@ -125,6 +128,48 @@ def test_network_multi_inputs(): network.compile() - output = network.predict([[1], [1, 2]]) + output = network.propagate([[1], [1, 2]]) assert len(output) == 6 + + +def test_network_multi_outputs(): + network = Network() + network.add(InputLayer([1], name="input-1")) + network.add(Dense(5, name="hidden")) + network.add(Dense(2, name="output-1")) + network.add(Dense(3, name="output-2")) + + network.connect("input-1", "hidden") + network.connect("hidden", "output-1") + network.connect("hidden", "output-2") + + network.compile() + + output = network.propagate([1]) + + assert len(output) == 2 + assert len(output[0]) == 2 + assert len(output[1]) == 3 + + +def test_network_multi_inputs_outputs(): + network = Network() + network.add(InputLayer([1], name="input-1")) + network.add(InputLayer([2], name="input-2")) + network.add(Dense(5, name="hidden")) + network.add(Dense(2, name="output-1")) + network.add(Dense(3, name="output-2")) + + network.connect("input-1", "hidden") + network.connect("input-2", "hidden") + network.connect("hidden", "output-1") + network.connect("hidden", "output-2") + + network.compile() + + output = network.propagate([[1], [0, 0.5]]) + + assert len(output) == 2 + assert len(output[0]) == 2 + assert len(output[1]) == 3 From bbfb45b196d57f09502f71ec6fd87140aca9f1fd Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 17:14:34 -0400 Subject: [PATCH 12/37] WIP Network(model).display() --- aitk/networks/network.py | 126 +++++++++++++++++----------- aitk/networks/utils.py | 13 ++- tests/test_networks/test_network.py | 33 +++++++- 3 files changed, 119 insertions(+), 53 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index f692c92..ca9a35e 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -63,7 +63,7 @@ def __init__(self, model=None, layers=None, name="Network", **config): self._fit_inputs = None self._fit_targets = None self._connections = [] - self._model = model + self._model = None # Place to put models between layers: self._predict_models = {} # Place to map layer to its input layers: @@ -73,7 +73,7 @@ def __init__(self, model=None, layers=None, name="Network", **config): self._history = {"weights": [], "metrics": []} self._epoch = 0 self._tolerance = 0.1 - self._name = self._model.name if self._model is not None else name + self._name = name self._layers = [] self._layers_map = {} self.input_bank_order = [] @@ -117,14 +117,14 @@ def __init__(self, model=None, layers=None, name="Network", **config): self.set_config(**config) if model: self._model = model + self._name = self._model.name for layer in model.layers: self.add(layer) self._connections = get_connections(model) - else: - self._model = None - if layers: - for layer in layers: - self.add(layer) + self.initialize_model() + elif layers: + for layer in layers: + self.add(layer) # When we are done here, we are in 1 of 2 states: # 1. A model, ready to go # 2. Network, ready for more add(), connect(), compile() @@ -195,7 +195,7 @@ def initialize(self, inputs=None, reset=True): if not self._layers: raise Exception("Layers must be set before initialization") - if inputs is None: + if not inputs: # We don't have direct values, so we base colormap # on activation output ranges for layer in self._layers: @@ -228,7 +228,7 @@ def initialize(self, inputs=None, reset=True): # Now we set the minmax for input layer, based on past values # or extremes: for layer in self._layers: - outputs = self.predict_to(inputs, layer.name, return_type="numpy") + outputs = self.propagate_to(inputs, layer.name, return_type="numpy") # FIXME: multiple output banks are lists of numpys color_orig, min_orig, max_orig = self.config["layers"][layer.name][ "colormap" @@ -513,7 +513,7 @@ def get_xy(name): plt.pause(0.01) # plt.show(block=False) - def _extract_inputs(self, inputs, input_names): + def _prepare_input(self, inputs, input_names): """ Get the input_names from the inputs """ @@ -635,16 +635,52 @@ def compile(self, *args, **kwargs): self.initialize_model() return results - def predict(self, inputs): + def _post_process_outputs(self, outputs, return_type): + def numpy(item): + if hasattr(item, "numpy"): + return item.numpy() + else: + return item + + if len(self.output_bank_order) == 1: + if return_type == "list": + return numpy(outputs)[0].tolist() + elif return_type == "numpy": + return numpy(outputs)[0] + else: + if return_type == "list": + return [numpy(item)[0].tolist() for item in outputs] + elif return_type == "numpy": + return [numpy(item)[0] for item in outputs] + + def _post_process_dataset_outputs(self, outputs, return_type): + def numpy(item): + if hasattr(item, "numpy"): + return item.numpy() + else: + return item + + if len(self.output_bank_order) == 1: + if return_type == "list": + return numpy(outputs).tolist() + elif return_type == "numpy": + return numpy(outputs) + else: + if return_type == "list": + return [numpy(item).tolist() for item in outputs] + elif return_type == "numpy": + return [numpy(item) for item in outputs] + + def propagate(self, inputs, return_type="list"): """ Propagate input patterns to a bank in the network. """ if self._model is None: raise Exception("Model has not yet been compiled") - input_vectors = self._extract_inputs(inputs, self.input_bank_order) + input_vectors = self._prepare_input(inputs, self.input_bank_order) try: - outputs = self._model(input_vectors, training=False).numpy() + outputs = self._model(input_vectors, training=False) except Exception: input_layers_shapes = [ self._get_raw_output_shape(layer_name) @@ -661,10 +697,7 @@ def predict(self, inputs): % hints ) from None - if len(self.output_bank_order) == 1: - return outputs[0].tolist() - else: - return [item[0].tolist() for item in outputs] + return self._post_process_outputs(outputs, return_type) def set_pca_spaces(self, inputs): """ @@ -674,7 +707,7 @@ def set_pca_spaces(self, inputs): for layer in self.layers: pca = PCA(2) - hidden_raw = self.predict_to(inputs, layer.name) + hidden_raw = self.predict_to(inputs, layer.name, return_type="numpy") try: pca_space = pca.fit(hidden_raw) except ValueError: @@ -689,10 +722,13 @@ def get_input_length(self, inputs): return len(inputs[0]) def predict_histogram_to(self, inputs, layer_name): + """ + Entire dataset + """ if self._model is None: raise Exception("Model has not yet been compiled") - hidden_raw = self.predict_to(inputs, layer_name) + hidden_raw = self.predict_to(inputs, layer_name, return_type="numpy") plt.hist(hidden_raw) plt.axis("off") @@ -709,7 +745,7 @@ def predict_pca_to(self, inputs, layer_name, colors, sizes): if layer_name not in self._state["pca"]: raise Exception("Need to set_pca_spaces first") - hidden_raw = self.predict_to(inputs, layer_name) + hidden_raw = self.predict_to(inputs, layer_name, return_type="numpy") pca_space = self._state["pca"][layer_name] if pca_space is not None: hidden_pca = pca_space.transform(hidden_raw) @@ -852,9 +888,9 @@ def predict_to(self, inputs, layer_name, return_type="list"): input_names = self._input_layer_names[layer_name] model = self._predict_models[input_names, layer_name] - input_vectors = self._extract_inputs(inputs, input_names) + inputs = self._prepare_dataset_inputs(inputs) try: - outputs = model(input_vectors, training=False).numpy() + outputs = model(inputs, training=False) except Exception: input_layers_shapes = [ self._get_raw_output_shape(layer_name) for layer_name in input_names @@ -870,16 +906,7 @@ def predict_to(self, inputs, layer_name, return_type="list"): % hints ) from None - if len(self.output_bank_order) == 1: - if return_type == "list": - return outputs[0].tolist() - elif return_type == "numpy": - return outputs[0] - else: - if return_type == "list": - return [item[0].tolist() for item in outputs] - elif return_type == "numpy": - return [item[0] for item in outputs] + return self._post_process_dataset_outputs(outputs, return_type) def predict_from(self, inputs, from_layer_name, to_layer_name): """ @@ -1006,6 +1033,8 @@ def display( except Exception: return_type = "image" + inputs = self._prepare_input(inputs, self.input_bank_order) + if return_type == "html": svg = self.get_image( inputs, @@ -1087,7 +1116,7 @@ def watch( widget = watcher.get_widget(show_error, show_targets, rotate, scale) display(widget) - def propagate( + def predict( self, inputs, targets=None, @@ -1100,27 +1129,29 @@ def propagate( if show: for watcher in self._watchers: watcher.update(inputs, targets) - dataset = self.input_to_dataset(inputs) - # FIXME: rather than just the first, format in case - # of multiple output layers - return self._model(dataset, training=False)[0].numpy() + inputs = self._prepare_dataset_inputs(inputs) + outputs = self._model(inputs, training=False) + return outputs def propagate_to( self, inputs, layer_name, - return_type=None, + return_type="numpy", channel=None, ): # FIXME: rather than just the first, format in case # of multiple output layers - array = self.predict_to(inputs, layer_name) - # FIXME: get output banks - # Strip out just the single return row from one bank + input_names = self._input_layer_names[layer_name] + model = self._predict_models[input_names, layer_name] + # FIXME? + # input_vectors = self._prepare_input(inputs, input_names) + array = model(inputs, training=False) + if return_type == "image": return self._layer_array_to_image(layer_name, array, channel=channel) else: - return array + return self._post_process_outputs(array, return_type) def propagate_each( self, @@ -1523,15 +1554,14 @@ def enumerate_dataset(self, dataset1, dataset2=None): count += 1 - def input_to_dataset(self, input): + def _prepare_dataset_inputs(self, inputs): """ - Take input tensor(s) and turn into an appropriate - dataset. + Take input dataset and make sure it is correct format. """ if len(self.input_bank_order) == 1: - inputs = [np.array([input])] + inputs = np.array(inputs) else: - inputs = [np.array([bank]) for bank in input] + inputs = [np.array(bank) for bank in inputs] return inputs def target_to_dataset(self, target): @@ -2379,7 +2409,7 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): if inputs is None: inputs = self.make_dummy_dataset() if targets is not None: - outputs = self.predict(inputs) + outputs = self.propagate(inputs) if len(self.output_bank_order) == 1: targets = [targets] errors = (np.array(outputs) - np.array(targets)).tolist() diff --git a/aitk/networks/utils.py b/aitk/networks/utils.py index 16dc0a9..aae2496 100644 --- a/aitk/networks/utils.py +++ b/aitk/networks/utils.py @@ -36,6 +36,7 @@ def __init__(self, pointA, pointB): self.length = math.sqrt(math.pow(lengthX, 2) + math.pow(lengthY, 2)) self.angle = math.atan2(lengthY, lengthX) + def get_array_shape(array): if isinstance(array, list): return [len(array)] + get_array_shape(array[0]) @@ -139,7 +140,13 @@ def scale_output_for_image(vector, minmax, truncate=False): Given an activation name (or something else) and an output vector, scale the vector. """ - return rescale_numpy_array(vector, minmax, (0, 255), "uint8", truncate=truncate,) + return rescale_numpy_array( + vector, + minmax, + (0, 255), + "uint8", + truncate=truncate, + ) def rescale_numpy_array(a, old_range, new_range, new_dtype, truncate=False): @@ -181,7 +188,8 @@ def svg_to_image(svg, config): else: raise Exception("svg_to_image takes a str, rather than %s" % type(svg)) - image_bytes = cairosvg.svg2png(bytestring=svg) + # FIXME: if not in notebook, need output_height? + image_bytes = cairosvg.svg2png(bytestring=svg) # , output_height=INT) image = Image.open(io.BytesIO(image_bytes)) if "background_color" in config: # create a blank image, with background: @@ -394,6 +402,7 @@ def is_keras_tensor(item): except Exception: return False + def get_connections(model): connections = [] for layer in model.layers: diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py index cc8515e..98b2e02 100644 --- a/tests/test_networks/test_network.py +++ b/tests/test_networks/test_network.py @@ -11,6 +11,7 @@ from tensorflow.keras.layers import Dense, InputLayer from aitk.networks import Network, SimpleNetwork +from aitk.utils import get_dataset def test_network_names(): @@ -49,10 +50,9 @@ def test_network_sequential_1(): network.connect() network.compile() - output = network.predict([[1, 1]]) + output = network.propagate([1, 1]) - assert len(output) == 1 - assert len(output[0]) == 10 + assert len(output) == 10 def test_network_sequential_2(): @@ -173,3 +173,30 @@ def test_network_multi_inputs_outputs(): assert len(output) == 2 assert len(output[0]) == 2 assert len(output[1]) == 3 + + +def test_network_predict(): + network = Network() + network.add(InputLayer([2])) + network.add(Dense(5)) + network.add(Dense(10)) + + network.connect() + network.compile() + + output = network.predict([[1, 1]]) + + assert len(output) == 1 + assert len(output[0]) == 10 + + +def test_network_model(): + from tensorflow.keras.applications import VGG16 + + dataset = get_dataset("dogs-vs-cats-100") + cats = dataset["cats"] + dogs = dataset["dogs"] + + vgg16 = VGG16(weights="imagenet") + vgg16_network = Network(vgg16) + vgg16_network.display(cats[0], rotate=True, scale=1.5, return_type="image") From abcec0212332c689efcbcc0b98b7d7c05c9ab253 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Wed, 18 Sep 2024 21:51:07 -0400 Subject: [PATCH 13/37] Running, but display images are incorrect --- aitk/networks/network.py | 16 +++++++--------- tests/test_networks/test_network.py | 3 ++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index ca9a35e..9fd8977 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -121,7 +121,7 @@ def __init__(self, model=None, layers=None, name="Network", **config): for layer in model.layers: self.add(layer) self._connections = get_connections(model) - self.initialize_model() + self.compile() elif layers: for layer in layers: self.add(layer) @@ -195,7 +195,7 @@ def initialize(self, inputs=None, reset=True): if not self._layers: raise Exception("Layers must be set before initialization") - if not inputs: + if inputs is None or len(inputs) == 0: # We don't have direct values, so we base colormap # on activation output ranges for layer in self._layers: @@ -228,6 +228,7 @@ def initialize(self, inputs=None, reset=True): # Now we set the minmax for input layer, based on past values # or extremes: for layer in self._layers: + # FIXME? outputs = self.propagate_to(inputs, layer.name, return_type="numpy") # FIXME: multiple output banks are lists of numpys color_orig, min_orig, max_orig = self.config["layers"][layer.name][ @@ -1033,11 +1034,11 @@ def display( except Exception: return_type = "image" - inputs = self._prepare_input(inputs, self.input_bank_order) + input_vectors = self._prepare_input(inputs, self.input_bank_order) if return_type == "html": svg = self.get_image( - inputs, + input_vectors, targets, show_error, show_targets, @@ -1140,13 +1141,10 @@ def propagate_to( return_type="numpy", channel=None, ): - # FIXME: rather than just the first, format in case - # of multiple output layers input_names = self._input_layer_names[layer_name] model = self._predict_models[input_names, layer_name] - # FIXME? - # input_vectors = self._prepare_input(inputs, input_names) - array = model(inputs, training=False) + input_vectors = self._prepare_input(inputs, input_names) + array = model(input_vectors, training=False) if return_type == "image": return self._layer_array_to_image(layer_name, array, channel=channel) diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py index 98b2e02..37ca4e7 100644 --- a/tests/test_networks/test_network.py +++ b/tests/test_networks/test_network.py @@ -112,7 +112,8 @@ def test_network_display(): output = network.display([1, 1], return_type="image") - assert output.size == (400, 260) + assert output.size[0] > 300 + assert output.size[1] > 200 def test_network_multi_inputs(): From e59d8e06af9b18a5d61595cd1b2536bdfb76281e Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Wed, 18 Sep 2024 11:43:37 -0400 Subject: [PATCH 14/37] no longer relevant --- Summer24Plans.md | 74 ------------------------------------------------ 1 file changed, 74 deletions(-) delete mode 100644 Summer24Plans.md diff --git a/Summer24Plans.md b/Summer24Plans.md deleted file mode 100644 index 130c204..0000000 --- a/Summer24Plans.md +++ /dev/null @@ -1,74 +0,0 @@ -# Getting started the first week -* [3Blue1Brown channel](https://www.youtube.com/c/3blue1brown), watch videos that introduce neural networks -* The latest version of tensorflow has broken some aspects of aitk -* Have students go through and try every notebook to see what works and what doesn't -* Have them start to look throught he code base for how it is orgainized - -# Managing an open source project - -* Create a README with our developer conventions -* Using issue tracker in github -* Providing type info -* Testing -* Can't push without a review first -* Documentation -* How to know when it is ready to release - -# Reorganization of the GitHub repo - -* Consider ways to best indicate different types of notebooks. -* Some are more introductory, others are more advanced. -* Incorporate latest notebooks used in Ethics class into the repo. -* Should an ordering be suggested? - -# Deliverables - -* All students must write a short summary of their summer project (1 page) -* We will create a poster for the Sigma Xi Fall Poster Session -* We may want to submit our work to SIGCSE or FLAIRS - -# Overall guiding concept - -* We want to build micro versions of imporant AI algorithms to allow non-experts to better understand them and develop the right intuitions about their strenghs and weaknesses. - -# Understanding what an artificial neuron computes - -* An interface for manipulating - - weights - - bias - - inputs - -to see how these all affect the outputs generated - - -# Word Embeddings - -* Create a simple grammar to generate a data set of sentences. -* Use these sententences to create a word embedding. -* Explore how the representations created by the network are related. - - -# Dataset Manipulation - -* Find or create a classification dataset. -* Show how manipulating the class representation within the training set affects the bias within the results. - - -# Transfer Learning - -* Using an existing front end, like image net, to train a new classification task, this already exists in the convolution notebook that Jim created - - -# Generative AI - -* We will need to look into the best ways of accomplishing this -* May need to use pytorch -* Would be nice to be able to visualize the attention head - - -# Reinforcement Learning - -* Perhaps applied to a simple game like Tic-Tac-Toe - - - From 33e4a75540f6e3259311ca9b1384f9092e793b98 Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Thu, 19 Sep 2024 14:35:56 -0400 Subject: [PATCH 15/37] added more explanation about hidden representations, added matplotlib inline command --- .../AnalyzingHiddenRepresentations.ipynb | 4431 +++++++++-------- 1 file changed, 2286 insertions(+), 2145 deletions(-) diff --git a/notebooks/NeuralNetworks/AnalyzingHiddenRepresentations.ipynb b/notebooks/NeuralNetworks/AnalyzingHiddenRepresentations.ipynb index d680e54..3893dde 100644 --- a/notebooks/NeuralNetworks/AnalyzingHiddenRepresentations.ipynb +++ b/notebooks/NeuralNetworks/AnalyzingHiddenRepresentations.ipynb @@ -1,2176 +1,2317 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Open" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Analyzing Hidden Layer Representations\n", - "\n", - "In this notebook we will train a simple network to recognize handwritten digits on a 6x6 grid. After training we will use both Principal Components Analysis (PCA) and Hierarchical Clustering to help visualize the hidden layer representations discovered by the network.\n", - "\n", - "Let's begin by getting all of the necessary libraries." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ + "cells": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[31mERROR: Could not install packages due to an OSError: [Errno 13] Permission denied: 'WHEEL'\n", - "Check the permissions.\n", - "\u001b[0m\u001b[31m\n", - "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" - ] - } - ], - "source": [ - "%pip install aitk --quiet" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from aitk.utils import array_to_image, get_dataset, gallery" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Download the data\n", - "\n", - "This data consists of 24 sets of handwritten digits (0-9) for a total of 240 examples. The inputs are provided on a 6x6 grid and the outputs are one-hot vectors." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "metadata": { + "id": "6AwBk8Msa2wR" + }, + "source": [ + "\"Open" + ] + }, { - "data": { - "text/plain": [ - "(240, 6, 6)" + "cell_type": "markdown", + "metadata": { + "id": "XGT65Voia2wT" + }, + "source": [ + "# Analyzing Hidden Layer Representations\n", + "\n", + "In this notebook we will train a simple network to recognize handwritten digits on a 6x6 grid. After training we will use both Principal Components Analysis (PCA) and Hierarchical Clustering to help visualize the hidden layer representations discovered by the network.\n", + "\n", + "Let's begin by getting all of the necessary libraries." ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "inputs, targets = get_dataset(\"digits6x6\")\n", - "inputs.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ + }, { - "data": { - "text/plain": [ - "(240, 10)" + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "o55wSPBDa2wT" + }, + "outputs": [], + "source": [ + "%pip install aitk --quiet" ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "targets.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's take a look at all of the images." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "IiO2IoR1a2wU" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from aitk.utils import array_to_image, get_dataset, gallery" + ] + }, { - "data": { - "text/html": [ - "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
34
\"34\"
35
\"35\"
36
\"36\"
37
\"37\"
38
\"38\"
39
\"39\"
40
\"40\"
41
\"41\"
42
\"42\"
43
\"43\"
44
\"44\"
45
\"45\"
46
\"46\"
47
\"47\"
48
\"48\"
49
\"49\"
50
\"50\"
51
\"51\"
52
\"52\"
53
\"53\"
54
\"54\"
55
\"55\"
56
\"56\"
57
\"57\"
58
\"58\"
59
\"59\"
60
\"60\"
61
\"61\"
62
\"62\"
63
\"63\"
64
\"64\"
65
\"65\"
66
\"66\"
67
\"67\"
68
\"68\"
69
\"69\"
70
\"70\"
71
\"71\"
72
\"72\"
73
\"73\"
74
\"74\"
75
\"75\"
76
\"76\"
77
\"77\"
78
\"78\"
79
\"79\"
80
\"80\"
81
\"81\"
82
\"82\"
83
\"83\"
84
\"84\"
85
\"85\"
86
\"86\"
87
\"87\"
88
\"88\"
89
\"89\"
90
\"90\"
91
\"91\"
92
\"92\"
93
\"93\"
94
\"94\"
95
\"95\"
96
\"96\"
97
\"97\"
98
\"98\"
99
\"99\"
100
\"100\"
101
\"101\"
102
\"102\"
103
\"103\"
104
\"104\"
105
\"105\"
106
\"106\"
107
\"107\"
108
\"108\"
109
\"109\"
110
\"110\"
111
\"111\"
112
\"112\"
113
\"113\"
114
\"114\"
115
\"115\"
116
\"116\"
117
\"117\"
118
\"118\"
119
\"119\"
120
\"120\"
121
\"121\"
122
\"122\"
123
\"123\"
124
\"124\"
125
\"125\"
126
\"126\"
127
\"127\"
128
\"128\"
129
\"129\"
130
\"130\"
131
\"131\"
132
\"132\"
133
\"133\"
134
\"134\"
135
\"135\"
136
\"136\"
137
\"137\"
138
\"138\"
139
\"139\"
140
\"140\"
141
\"141\"
142
\"142\"
143
\"143\"
144
\"144\"
145
\"145\"
146
\"146\"
147
\"147\"
148
\"148\"
149
\"149\"
150
\"150\"
151
\"151\"
152
\"152\"
153
\"153\"
154
\"154\"
155
\"155\"
156
\"156\"
157
\"157\"
158
\"158\"
159
\"159\"
160
\"160\"
161
\"161\"
162
\"162\"
163
\"163\"
164
\"164\"
165
\"165\"
166
\"166\"
167
\"167\"
168
\"168\"
169
\"169\"
170
\"170\"
171
\"171\"
172
\"172\"
173
\"173\"
174
\"174\"
175
\"175\"
176
\"176\"
177
\"177\"
178
\"178\"
179
\"179\"
180
\"180\"
181
\"181\"
182
\"182\"
183
\"183\"
184
\"184\"
185
\"185\"
186
\"186\"
187
\"187\"
188
\"188\"
189
\"189\"
190
\"190\"
191
\"191\"
192
\"192\"
193
\"193\"
194
\"194\"
195
\"195\"
196
\"196\"
197
\"197\"
198
\"198\"
199
\"199\"
200
\"200\"
201
\"201\"
202
\"202\"
203
\"203\"
204
\"204\"
205
\"205\"
206
\"206\"
207
\"207\"
208
\"208\"
209
\"209\"
210
\"210\"
211
\"211\"
212
\"212\"
213
\"213\"
214
\"214\"
215
\"215\"
216
\"216\"
217
\"217\"
218
\"218\"
219
\"219\"
220
\"220\"
221
\"221\"
222
\"222\"
223
\"223\"
224
\"224\"
225
\"225\"
226
\"226\"
227
\"227\"
228
\"228\"
229
\"229\"
230
\"230\"
231
\"231\"
232
\"232\"
233
\"233\"
234
\"234\"
235
\"235\"
236
\"236\"
237
\"237\"
238
\"238\"
239
\"239\"
" + "cell_type": "markdown", + "metadata": { + "id": "C_1h3q4xa2wU" + }, + "source": [ + "## Download the data\n", + "\n", + "This data consists of 24 sets of handwritten digits (0-9) for a total of 240 examples. The inputs are provided on a 6x6 grid and the outputs are one-hot vectors." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "HP4Ehu7da2wU", + "outputId": "8b705886-4b47-4039-83b5-fc5ea8e8d56b", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(240, 6, 6)" + ] + }, + "metadata": {}, + "execution_count": 3 + } ], - "text/plain": [ - "" + "source": [ + "inputs, targets = get_dataset(\"digits6x6\")\n", + "inputs.shape" ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "images = [array_to_image(inputs[i]) for i in range(len(inputs))]\n", - "bigger = [image.resize((36,36), resample=0) for image in images]\n", - "gallery(bigger)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Build a Neural Network Model\n", - "\n", - "Now we will construct a neural network to learn this data." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "from aitk.networks import SimpleNetwork" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "def build_model():\n", - " return SimpleNetwork(\n", - " (6,6),\n", - " \"Flatten\",\n", - " (5, \"sigmoid\"),\n", - " (10, \"softmax\"),\n", - " loss = \"categorical_crossentropy\",\n", - " metrics = [\"tolerance_accuracy\"] \n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ + }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-06-25 14:49:43.308630: I tensorflow/core/util/util.cc:169] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n", - "2024-06-25 14:49:46.407339: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.422760: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.423014: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.423638: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 AVX_VNNI FMA\n", - "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", - "2024-06-25 14:49:46.424914: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.425102: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.425218: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.740770: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.740884: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.740952: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", - "2024-06-25 14:49:46.741019: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1532] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 9876 MB memory: -> device: 0, name: NVIDIA RTX A2000 12GB, pci bus id: 0000:01:00.0, compute capability: 8.6\n" - ] - } - ], - "source": [ - "net = build_model()\n", - "net.set_tolerance(0.15)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "qMpx40Nva2wU", + "outputId": "1fd5656d-ec62-467b-c808-31ebfe89115a", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(240, 10)" + ] + }, + "metadata": {}, + "execution_count": 4 + } + ], + "source": [ + "targets.shape" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model: \"SimpleNetwork\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input (InputLayer) [(None, 6, 6)] 0 \n", - " \n", - " flatten (Flatten) (None, 36) 0 \n", - " \n", - " hidden_2 (Dense) (None, 5) 185 \n", - " \n", - " output (Dense) (None, 10) 60 \n", - " \n", - "=================================================================\n", - "Total params: 245\n", - "Trainable params: 245\n", - "Non-trainable params: 0\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "net.summary()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Principal Components Analysis\n", - "\n", - "We would like to understand more about how the network learns to categorize the images properly. One way to think about what a neural network is doing, is that each layer of weights is transforming the input into new representations that help the network distinguish between the desired categories. \n", - "\n", - "Let's focus on the hidden layer, which transforms the 36 pixel values into a representation that is 5 long. Unfortunately we can't easily visualize 5 dimensions. However, PCA allows us to focus on just 2 dimensions where the most change is happening. \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze hidden layer prior to training\n", - "\n", - "Remember that each time we build a network model all of its weights will be initialized to small random values. If we test the network on all of the inputs, we can visualize the initial hidden layer representations **prior** to training, and compare them to the hidden layer representations discovered after training.\n", - "\n", - "Let's give each digit a unique color encoding, pink represents 0's, red represents 1's, and so on (see encoding below).\n" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "metadata": { + "id": "opqMFHWma2wV" + }, + "source": [ + "Let's take a look at all of the images." + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(0, 'pink'), (1, 'red'), (2, 'orange'), (3, 'yellow'), (4, 'green'), (5, 'teal'), (6, 'blue'), (7, 'indigo'), (8, 'violet'), (9, 'black')]\n" - ] - } - ], - "source": [ - "# Give each digit it's own color\n", - "visualize = [\"pink\", \"red\", \"orange\", \"yellow\", \"green\", \n", - " \"teal\", \"blue\", \"indigo\", \"violet\", \"black\"]\n", - "encoding = list(zip(range(10),visualize))\n", - "print(encoding)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "g8FeXUKXa2wV", + "outputId": "2d4296b0-6b1e-4e81-b5b9-ad592412f16e", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 945 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
34
\"34\"
35
\"35\"
36
\"36\"
37
\"37\"
38
\"38\"
39
\"39\"
40
\"40\"
41
\"41\"
42
\"42\"
43
\"43\"
44
\"44\"
45
\"45\"
46
\"46\"
47
\"47\"
48
\"48\"
49
\"49\"
50
\"50\"
51
\"51\"
52
\"52\"
53
\"53\"
54
\"54\"
55
\"55\"
56
\"56\"
57
\"57\"
58
\"58\"
59
\"59\"
60
\"60\"
61
\"61\"
62
\"62\"
63
\"63\"
64
\"64\"
65
\"65\"
66
\"66\"
67
\"67\"
68
\"68\"
69
\"69\"
70
\"70\"
71
\"71\"
72
\"72\"
73
\"73\"
74
\"74\"
75
\"75\"
76
\"76\"
77
\"77\"
78
\"78\"
79
\"79\"
80
\"80\"
81
\"81\"
82
\"82\"
83
\"83\"
84
\"84\"
85
\"85\"
86
\"86\"
87
\"87\"
88
\"88\"
89
\"89\"
90
\"90\"
91
\"91\"
92
\"92\"
93
\"93\"
94
\"94\"
95
\"95\"
96
\"96\"
97
\"97\"
98
\"98\"
99
\"99\"
100
\"100\"
101
\"101\"
102
\"102\"
103
\"103\"
104
\"104\"
105
\"105\"
106
\"106\"
107
\"107\"
108
\"108\"
109
\"109\"
110
\"110\"
111
\"111\"
112
\"112\"
113
\"113\"
114
\"114\"
115
\"115\"
116
\"116\"
117
\"117\"
118
\"118\"
119
\"119\"
120
\"120\"
121
\"121\"
122
\"122\"
123
\"123\"
124
\"124\"
125
\"125\"
126
\"126\"
127
\"127\"
128
\"128\"
129
\"129\"
130
\"130\"
131
\"131\"
132
\"132\"
133
\"133\"
134
\"134\"
135
\"135\"
136
\"136\"
137
\"137\"
138
\"138\"
139
\"139\"
140
\"140\"
141
\"141\"
142
\"142\"
143
\"143\"
144
\"144\"
145
\"145\"
146
\"146\"
147
\"147\"
148
\"148\"
149
\"149\"
150
\"150\"
151
\"151\"
152
\"152\"
153
\"153\"
154
\"154\"
155
\"155\"
156
\"156\"
157
\"157\"
158
\"158\"
159
\"159\"
160
\"160\"
161
\"161\"
162
\"162\"
163
\"163\"
164
\"164\"
165
\"165\"
166
\"166\"
167
\"167\"
168
\"168\"
169
\"169\"
170
\"170\"
171
\"171\"
172
\"172\"
173
\"173\"
174
\"174\"
175
\"175\"
176
\"176\"
177
\"177\"
178
\"178\"
179
\"179\"
180
\"180\"
181
\"181\"
182
\"182\"
183
\"183\"
184
\"184\"
185
\"185\"
186
\"186\"
187
\"187\"
188
\"188\"
189
\"189\"
190
\"190\"
191
\"191\"
192
\"192\"
193
\"193\"
194
\"194\"
195
\"195\"
196
\"196\"
197
\"197\"
198
\"198\"
199
\"199\"
200
\"200\"
201
\"201\"
202
\"202\"
203
\"203\"
204
\"204\"
205
\"205\"
206
\"206\"
207
\"207\"
208
\"208\"
209
\"209\"
210
\"210\"
211
\"211\"
212
\"212\"
213
\"213\"
214
\"214\"
215
\"215\"
216
\"216\"
217
\"217\"
218
\"218\"
219
\"219\"
220
\"220\"
221
\"221\"
222
\"222\"
223
\"223\"
224
\"224\"
225
\"225\"
226
\"226\"
227
\"227\"
228
\"228\"
229
\"229\"
230
\"230\"
231
\"231\"
232
\"232\"
233
\"233\"
234
\"234\"
235
\"235\"
236
\"236\"
237
\"237\"
238
\"238\"
239
\"239\"
" + ] + }, + "metadata": {} + } + ], + "source": [ + "images = [array_to_image(inputs[i]) for i in range(len(inputs))]\n", + "bigger = [image.resize((36,36), resample=0) for image in images]\n", + "gallery(bigger)" + ] + }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-06-25 14:49:47.748421: I tensorflow/stream_executor/cuda/cuda_blas.cc:1786] TensorFloat-32 will be used for the matrix multiplication. This will only be logged once.\n" - ] + "cell_type": "markdown", + "metadata": { + "id": "C-CuBP-La2wV" + }, + "source": [ + "## Build a Neural Network Model\n", + "\n", + "Now we will construct a neural network to learn this data." + ] }, { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAB48UlEQVR4nO3dd3hUZd7G8e+ZmVQCAULvUkS6IEVBARUBFUUsgF0UxS72Xdta33V1LahgW8WCYMHeKIKiICi99957SEifmfP+cRJCyLTAlEzm/nhxQc55MvMLksk9TzVM0zQRERERkZhhi3QBIiIiIhJeCoAiIiIiMUYBUERERCTGKACKiIiIxBgFQBEREZEYowAoIiIiEmMUAEVERERijAKgiIiISIxRABQRERGJMQqAIiIiIjFGAVBEREQkxigAioiIiMQYBUARERGRGKMAKCIiIhJjFABFREREYowCoIiIiEiMUQAUERERiTEKgCIiIiIxRgFQREREJMYoAIqIiIjEGAVAERERkRijACgiIiISYxQARURERGKMAqCIiIhIjFEAFBEREYkxCoAiIiIiMUYBUERERCTGKACKiIiIxBgFQBEREZEYowAoIiIiEmMUAEVERERijAKgiIiISIxRABQRERGJMQqAIiIiIjFGAVBEREQkxigAioiIiMQYBUARERGRGKMAKCIiIhJjFABFREREYowCoIiIiEiMUQAUERERiTEKgCIiIiIxRgFQREREJMYoAIqIiIjEGAVAERERkRijACgiIiISYxQARURERGKMAqCIiIhIjFEAFBEREYkxCoAiIiIiMUYBUERERCTGKACKiIiIxBgFQBEREZEYowAoIiIiEmMUAEVERERijAKgiIiISIxRABQRERGJMQqAIiIiIjFGAVBEREQkxigAioiIiMQYBUARERGRGKMAKCIiIhJjFABFREREYowCoIiIiEiMUQAUERERiTEKgCIiIiIxRgFQREREJMYoAIqIiIjEGEekCxCJZS4XTJ0KK1ZASgpcdBHUrRvpqkREpKIzTNM0I12ESCyaMQOuvRa2bgWbDUzT+v3mm2HUKIiPj3SFIiJSUSkAikTAwoVwxhlQUABud8l7Nhtccw18+GFkahMRkYpPAVAkAgYOhB9/tIaAvVmxAlq1Cl9NIiISO7QIRCTMMjPhhx98hz+HAyZMCF9NEjvcbjdj5o7hlu9v4YlfnyA9Nz3SJYlIBGgRiEiYHTpUetj3WIYB+/eHpx6JHS/Pfpl//PIPCtwFR6498/szXNbqMj6//HNsNvUJiMQKfbeLhFmNGpCY6LuNywWNG4en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\n", - "text/plain": [ - "" + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "1G1XuU-Wa2wV" + }, + "outputs": [], + "source": [ + "from aitk.networks import SimpleNetwork" ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "net.set_pca_spaces(inputs)\n", - "net.predict_pca_to(inputs, 'hidden_2', visualize*24, None)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice in the graph above that all of the colors are jumbled and intermixed. The network has not yet been trained so all of its weights are still random. Let's train the network, and redo this analysis and see how the hidden layer representations change." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train the network" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "DWKEQetWa2wV" + }, + "outputs": [], + "source": [ + "def build_model():\n", + " return SimpleNetwork(\n", + " (6,6),\n", + " \"Flatten\",\n", + " (5, \"sigmoid\"),\n", + " (10, \"softmax\"),\n", + " loss = \"categorical_crossentropy\",\n", + " metrics = [\"tolerance_accuracy\"]\n", + " )" + ] + }, { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " 2024-06-25T14:49:52.140742\n", - " image/svg+xml\n", - " \n", - " \n", - " Matplotlib v3.5.3, https://matplotlib.org/\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" + "cell_type": "code", + "execution_count": 8, + "metadata": { + "id": "OMGFtDVNa2wV" + }, + "outputs": [], + "source": [ + "net = build_model()\n", + "net.set_tolerance(0.15)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "id": "8I7AVb3aa2wV", + "outputId": "910f38c7-5a0f-4230-802e-28f4df3f7c1b", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Model: \"SimpleNetwork\"\n", + "_________________________________________________________________\n", + " Layer (type) Output Shape Param # \n", + "=================================================================\n", + " input (InputLayer) [(None, 6, 6)] 0 \n", + " \n", + " flatten (Flatten) (None, 36) 0 \n", + " \n", + " hidden_2 (Dense) (None, 5) 185 \n", + " \n", + " output (Dense) (None, 10) 60 \n", + " \n", + "=================================================================\n", + "Total params: 245 (980.00 Byte)\n", + "Trainable params: 245 (980.00 Byte)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "_________________________________________________________________\n" + ] + } ], - "text/plain": [ - "" + "source": [ + "net.summary()" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stopped because accuracy beat goal of 1.0\n", - "Epoch 174/300 loss: 0.017757482826709747 - tolerance_accuracy: 1.0\n" - ] - } - ], - "source": [ - "history = net.fit(\n", - " inputs, targets, \n", - " batch_size=16, \n", - " shuffle=True,\n", - " epochs=300, \n", - " accuracy=1.0, \n", - " patience=30, \n", - " report_rate=10,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Test the Trained Network\n", - "\n", - "We can test how well the network has learned the data by propagating some of the input patterns through the trained network." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "metadata": { + "id": "n6HOh8qia2wW" + }, + "source": [ + "## Principal Components Analysis\n", + "\n", + "We would like to understand more about how the network learns to categorize the images properly. One way to think about what a neural network is doing, is that each layer of weights is transforming the input into new representations that help the network distinguish between the desired categories.\n", + "\n", + "Let's focus on the hidden layer, which transforms the 36 pixel values into a representation that is 5 long. Unfortunately we can't easily visualize 5 dimensions. However, PCA allows us to focus on just 2 dimensions where the most change is happening.\n" + ] + }, { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " Layer: output 'Dense'\n", - "Act function: softmax\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", - "Act function: sigmoid\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 5)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "cell_type": "markdown", + "metadata": { + "id": "iynyUuiFa2wW" + }, + "source": [ + "## Analyze hidden layer prior to training\n", + "\n", + "Remember that each time we build a network model all of its weights will be initialized to small random values. If we test the network on all of the inputs, we can visualize the initial hidden layer representations **prior** to training, and compare them to the hidden layer representations discovered after training.\n", + "\n", + "Let's give each digit a unique color encoding, pink represents 0's, red represents 1's, and so on (see encoding below).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "id": "xqr1eJtga2wW", + "outputId": "59018718-a077-41c3-e852-d415a66fc9a2", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[(0, 'pink'), (1, 'red'), (2, 'orange'), (3, 'yellow'), (4, 'green'), (5, 'teal'), (6, 'blue'), (7, 'indigo'), (8, 'violet'), (9, 'black')]\n" + ] + } ], - "text/plain": [ - "" + "source": [ + "# Give each digit it's own color\n", + "visualize = [\"pink\", \"red\", \"orange\", \"yellow\", \"green\",\n", + " \"teal\", \"blue\", \"indigo\", \"violet\", \"black\"]\n", + "encoding = list(zip(range(10),visualize))\n", + "print(encoding)" ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from time import sleep\n", - "for pattern in inputs[0:10]:\n", - " net.display(pattern)\n", - " sleep(1.0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze hidden layer after training\n", - "\n", - "Let's look at how the hidden layer representations have changed **after** training. Compare the graph below to the one above. Now clear patterns are emerging. You should see lots of clusters of similar colors indicating that the network has created hidden representations to help it correctly categorize each type of digit." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(0, 'pink'), (1, 'red'), (2, 'orange'), (3, 'yellow'), (4, 'green'), (5, 'teal'), (6, 'blue'), (7, 'indigo'), (8, 'violet'), (9, 'black')]\n" - ] - } - ], - "source": [ - "print(encoding)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 11, + "metadata": { + "id": "VhuSoYJXa2wW", + "outputId": "370051f4-bbe6-47f4-cef1-878549d08dde", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 497 + } + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAACYpElEQVR4Ae2dB3wURfvHf+mEGnrvXZBeRYqCSLGA2EGxY0fUv69dX3svr4oo9oKoqAiKgAiiFKX33nuHAAnp+T/PXS65S3b3Lskl2bv7PXyG252ZnZ357uXud1OeCcsUA40ESIAESIAESIAESCBkCISHTEvZUBIgARIgARIgARIgAQcBCkC+EUiABEiABEiABEggxAhQAIbYA2dzSYAESIAESIAESIACkO8BEiABEiABEiABEggxAhSAIfbA2VwSIAESIAESIAESoADke4AESIAESIAESIAEQowABWCIPXA2lwRIgARIgARIgAQoAPkeIAESIAESIAESIIEQI0ABGGIPnM0lARIgARIgARIgAQpAvgdIgARIgARIgARIIMQIUACG2ANnc0mABEiABEiABEiAApDvARIgARIgARIgARIIMQIUgCH2wNlcEiABEiABEiABEqAA5HuABEiABEiABEiABEKMAAVgiD1wNpcESIAESIAESIAEKAD5HiABEiABEiABEiCBECNAARhiD5zNJQESIAESIAESIAEKQL4HSIAESIAESIAESCDECFAAhtgDZ3NJgARIgARIgARIgAKQ7wESIAESIAESIAESCDECFIAh9sDZXBIgARIgARIgARKgAOR7gARIgARIgARIgARCjAAFYIg9cDaXBEiABEiABEiABCgA+R4gARIgARIgARIggRAjQAEYYg+czSUBEiABEiABEiABCkC+B0iABEiABEiABEggxAhQAIbYA2dzSYAESIAESIAESIACkO8BEiABEiABEiABEggxAhSAIfbA2VwSIAESIAESIAESoADke4AESIAESIAESIAEQowABWCIPXA2lwRIgARIgARIgAQoAPkeIAESIAESIAESIIEQI0ABGGIPnM0lARIgARIgARIgAQpAvgdIgARIgARIgARIIMQIUACG2ANnc0mABEiABEiABEiAApDvARIgARIgARIgARIIMQIUgCH2wNlcEiABEiABEiABEqAA5HuABEiABEiABEiABEKMAAVgiD1wNpcESIAESIAESIAEKAD5HiABEiABEiABEiCBECNAARhiD5zNJQESIAESIAESIAEKQL4HSIAESIAESIAESCDECFAAhtgDZ3NJgARIgARIgARIgAKQ7wESIAESIAESIAESCDECFIAh9sDZXBIgARIgARIgARKgAOR7gARIgARIgARIgARCjAAFYIg9cDaXBEiABEiABEiABCgA+R4gARIgARIgARIggRAjQAEYYg+czSUBEiABEiABEiABCkC+B0iABEiABEiABEggxAhQAIbYA2dzSYAESIAESIAESIACkO8BEiABEiABEiABEggxAhSAIfbA2VwSIAESIAESIAESoADke4AESIAESIAESIAEQowABWCIPXA2lwRIgARIgARIgAQoAPkeIAESIAESIAESIIEQI0ABGGIPnM0lARIgARIgARIgAQpAvgdIgARIgARIgARIIMQIUACG2ANnc0mABEiABEiABEiAApDvARIgARIgARIgARIIMQIUgCH2wNlcEiABEiABEiABEqAA5HuABEiABEiABEiABEKMAAVgiD1wNpcESIAESIAESIAEKAD5HiABEiABEiABEiCBECNAARhiD5zNJQESIAESIAESIAEKQL4HSIAESIAESIAESCDECFAAhtgDZ3NJgARIgARIgARIgAKQ7wESIAESIAESIAESCDECkSHWXjaXBEiABEiABAKKQGZmJv7duxf/7NmDyPBw9G/cGM0qVw6oNrCy9iNAAWi/Z8IakQAJkAAJkICDwKajR3Hl999j5cGDCA8Lg4rBTEkZ1LQpvho6FBVjY0mKBApEIEzeTPpeopEACZAACZAACdiIwIHTp9F23DgcTUxEeq6v6ggRgx1q1sSCm2929AraqNqsSoAQ4BzAAHlQrCYJkAAJkEBoEXh30SJD8acUVBAu3rcPUzZuDC0obK3fCFAA+g0lCyIBEiABEiAB/xH4fOXKPD1/7qVrL+BXq1a5R/GYBHwmQAHoMypmJAESIAESIIHiI3D8zBnLm2kv4OGEBMs8TCQBMwIUgGZkGE8CJEACJEACJUigfoUKCLO4v64IblypkkUOJpGAOQEKQHM2TCEBEiABEiCBEiNwW8eOlvdOy8jAze3bW+ZhIgmYEaAANCPDeBIgARIgARIoQQK3igBsLyt9da5fbtOYEWefjXPr1cudxHMS8IkA3cD4hImZSIAESIAESKD4CZxMTsaDM2fiC1kQkpye7qhAXKlSGNOtGx7r2RMRMgxMI4GCEKAALAg1XkMCJEACJEACxUjgRFIS1hw65PD5175GDcREch+HYsQf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WKXYSnig+wN4+NyHLXsIyY8ESIAESCCwCVAABvbzY+1JoNAEjp85jrWH1yIqPArta7Z37AhS6EJZAAmQAAmQgK0JUADa+vGwciRAAiRAAiRAAiTgfwJcBOJ/piyRBEiABEiABEiABGxNgALQ1o+HlSMBEiABEiABEiAB/xOgAPQ/U5ZIAiRAAiRAAiRAArYmQAFo68fDypEACZAACZAACZCA/wlQAPqfKUskARIgARIgARIgAVsToAC09eNh5UiABEiABEiABEjA/wQoAP3PlCWSAAmQAAmQAAmQgK0JUADa+vGwciRAAiRAAiRAAiTgfwIUgP5nyhJJgARIgARIgARIwNYEKABt/XhYORIgARIgARIgARLwPwEKQP8zZYkkQAIkQAIkQAIkYGsCFIC2fjysHAmQAAmQAAmQAAn4nwAFoP+ZskQSIAESIAESIAESsDUBCkBbPx5WjgRIgARIgARIgAT8T4AC0P9MWSIJkAAJkAAJkAAJ2JoABaCtHw8rRwIkQAIkQAIkQAL+J0AB6H+mLJEESIAESIAESIAEbE2AAtDWj4eVIwESIAESIAESIAH/E6AA9D9TlkgCJEACJEACJEACtiZAAWjrx8PKkQAJkAAJkAAJkID/CVAA+p8pSyQBEiABEiABEiABWxOgALT142HlSIAESIAESIAESMD/BCgA/c+UJZIACZAACZAACZCArQlQANr68bByJEACJEACJEACJOB/AhSA/mfKEkmABEiABEiABEjA1gQoAG39eFg5EiABEiABEiABEvA/AQpA/zNliSRAAiRAAiRAAiRgawIUgLZ+PKwcCZAACZAACZAACfifAAWg/5myRBIgARIgARIgARKwNQEKQFs/HlaOBEiABEiABEiABPxPgALQ/0xZIgmQAAmQAAmQAAnYmgAFoK0fDytHAiRAAiRAAiRAAv4nQAHof6YskQRIgARIgARIgARsTYAC0NaPh5UjARIgARIgARIgAf8ToAD0P1OWSAIkQAIkQAIkQAK2JkABaOvHw8qRAAmQAAmQAAmQgP8JUAD6nylLJAESIAESIAESIAFbE6AAtPXjYeVIgARIgARIgARIwP8EKAD9z5QlkgAJkAAJkAAJkICtCVAA2vrxsHIkQAIkQAIkQAIk4H8CFID+Z8oSSYAESIAESIAESMDWBCgAbf14WDkSIAESIAESIAES8D8BCkD/M2WJJEACJEACJEACJGBrAhSAtn48rBwJkAAJkAAJkAAJ+J8ABaD/mbJEEiABEiABEiABErA1AQpAWz8eVo4ESIAESIAESIAE/E+AAtD/TFkiCZAACZAACZAACdiaAAWgrR8PK0cCJEACJEACJEAC/idAAeh/piyRBEiABEiABEiABGxNgALQ1o+HlSMBEiABEiABEiAB/xOgAPQ/U5ZIAiRAAiRAAiRAArYmQAFo68fDypEACZAACZAACZCA/wlQAPqfKUskARIgARIgARIgAVsToAC09eNh5UiABEiABEiABEjA/wQoAP3PlCWSAAmQAAmQAAmQgK0JUADa+vGwciRAAiRAAiRAAiTgfwIUgP5nyhJJgARIgARIgARIwNYEKABt/XhYORIgARIgARIgARLwPwEKQP8zZYkkQAIkQAIkQAIkYGsCFIC2fjysHAmQAAmQAAmQAAn4nwAFoP+ZskQSIAESIAESIAESsDUBCkBbPx5WjgRIgARIgARIgAT8T4AC0P9MWSIJkAAJkAAJkAAJ2JrA/wMFvSBVZ6ZfVwAAAABJRU5ErkJggg==\n" + }, + "metadata": {}, + "execution_count": 11 + } + ], + "source": [ + "net.set_pca_spaces(inputs)\n", + "net.predict_pca_to(inputs, 'hidden_2', visualize*24, None)" + ] + }, { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAABiF0lEQVR4nO3dd3xUVf7G8c+dmXRI6L333gSkKQgi2BUr2ECxYtfV9efa1oJl7Qqroq6KFVBRQbEgNooC0jtSpJdQElJn5v7+uCQQyJQk05L7vPflC5w5c+cbcJNnzj3newzTNE1ERERExDYc0S5ARERERCJLAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZlzRLkBERCSQbRnb+HL1lxzKP0T7mu05tdmpOB3OaJclUm4pAIqISEzyeD18sPQD7vnuHnYc2gGAgYGJSaO0Rkw8fyInNT4pylWKlE+GaZpmtIsQERE5Wr4nn2EfD+OrtV8V+7yBQYIrgTnXzKFLnS6RLU6kAtAaQBERiTlP/PIE09ZO8/m8iUmeO49Hf3o0glWJVByaARQRkZiS58mj7rN1Sc9ODzjWYTg4+M+DpMSnRKAykYpDM4AiIhJT/tr3V1DhD8BrejmYezDMFYlUPAqAIiISUxxG8D+aklxJVEuqFsZqRComBUAREYkpzas2p17legHHOXFyVeerSHAlRKAqkYpFAVBERGKK0+Hkzl53BhxXM6UmD/R/IAIViVQ8CoAiIhJz7uh9ByO7jASKvyU8pPkQfr/296BmCkXkeNoFLCIiMck0TX7c+COvzX+NFXtWEOeI45Qmp3BTj5toXq15tMsTKdcUAEVERERsRreARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZhQARURERGxGAVBERETEZlzRLkBERCTWuL1uvlj9BZ+u/JTMvEza1mjL6G6jaV6tebRLEwkJwzRNM9pFiI3MmgV33QVLloBpQu3a8I9/wO23R+b9P/kEXngBDhyApk3hqaegffvIvLeIlAvbM7Yz+L3BLN+9HKfhxGN6cBpOvKaXpwc/zd197o52iSJlpgAo4XPwIDz8MKxaBbVqQc2a8J//FD+2b1/49dfw1ZKeDh07wrZtxz93/vnw6afhe28RKTdM06T7G91ZsnMJbq+72DGTL5rMBe0uiHBlIqGlACjhcccd8OKL1ixfsO67D554ouhjW7bAP/9phchKleCWW+CCUnzjbdIENm3y/fzNN8PLL5f8uiJSoczaOItT3jnF5/MOw0GXOl1YcN2CCFYlEnoKgBJ6Dz8MjzxS8telpcH+/Uf+fcwYGDfu+HF16sDixdasYjBmzoRBg/yPiYuDnBxwaF+UiJ3d/e3dvDjvRZ+zfwV23b2Lmik1I1SVSOjpp52EltcLTz5ZutceOGC9HqxrFBf+AHbsgM6dg7/uiy8GHpOfDzNmBH9NEamQ8jx5GBgBx+V6ciNQjUj4KABKaE2fDrkh+MZ47K3gY+3YAZ99Fty1MjKCG7d3b3DjRKTC6la3G/nefL9jaibXpE6lOhGqSCQ8FAAltP7+u/SvrVbNugW7fHlwoS2YmT2AE04IblzfvsGNE5EK6+L2F5OWkIbDx49Hh+FgTI8xuBzqoiblmwKghFb37qV/7R13WL/u3Bnc+MzM4MY99BAYAW7pNGtmtYUREVtLjktm0kWTcDldRUKecfh/JzU6iXv73RvFCkVCQwFQQqtHD6heveSvO/FE+Ne/rN936RLca9q0CW5cpUrw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Hli10ffz3TtAitrAiEjoKQCKRIqv8FfA6YR+XSNXT0Ww/6A1k+r2QKVkqwl0sGsEY8XBTFi+3jrNpEByInRsDYlx0atLRCo0BUCRSPlpfuAx7ZpCzerhr0VERGxN5wyJRELmoeDGrdoYmwHw6JM34l3Qu0tUyxERkbJRABSJhJ1B9nzzxtiE/LpNsHV30cfy3NZsZmol6NomOnWJiEiZaBewSCQkl8OF/On7jg9/RzuYGdxpFiIiEnMUAEUioW7N4MalpoS3jpJYuj7wmC07w1+HiIiEnAKgSKQkxgce07Vt+OsQERHbUwAUiZQTO/lvYNyuaeRqERERW9MmEJFI6tfNOv1j1SYo6MBUOQU6NIf4IGYIRUREQkABUCTSatWw/ol1DiP2diWLiEhI6BawiBSve7vAYzo2D38dIiIScgqAIlK8pCTo4icEtmoE1apGrh4REQkZHQUnIoHtToc1m6zfN6oLDetEtx4RESkTBUARERERm9EtYBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGbUQAUERERsRkFQBERERGb+X/Ztxm2qKUoUQAAAABJRU5ErkJggg==\n", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "o3VL67XGa2wW" + }, + "source": [ + "Notice in the graph above that all of the colors are jumbled and intermixed. The network has not yet been trained so all of its weights are still random. Let's train the network, and redo this analysis and see how the hidden layer representations change." ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "net.set_pca_spaces(inputs)\n", - "net.predict_pca_to(inputs, 'hidden_2', visualize*24, None)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each run of the network will likely be slightly different, but notice that some of the digits are clustered together nicely, while others are more spread out and intermixed. Look at the color coding above and determine which digits are intermixed. Does this make sense given how these digits might have similar sub-structures in them?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Cluster Analysis\n", - "\n", - "Another way to analyze the hidden layer representation is to do a cluster analysis. Here we consider every hidden layer representation and calculate the Euclidean distance between every possible pair. The two closest examples form a cluster and their average is put back into the set of representations. We continue this process until a complete tree has been formed. " - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.cluster import hierarchy" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "labelList = [str(v) for v in range(10)]" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "hidden_raw = net.predict_to(inputs, \"hidden_2\")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1vd_Sn0Va2wW" + }, + "source": [ + "## Train the network" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "id": "zpf1Yqcia2wW", + "outputId": "a32c290b-f99a-4521-bdcb-021fdf4ad519", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 423 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2024-09-19T18:29:19.252285\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.7.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ] + }, + "metadata": {} + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Epoch 300/300 loss: 0.008977795019745827 - tolerance_accuracy: 0.9958333373069763\n" + ] + } + ], + "source": [ + "history = net.fit(\n", + " inputs, targets,\n", + " batch_size=16,\n", + " shuffle=True,\n", + " epochs=300,\n", + " accuracy=1.0,\n", + " patience=30,\n", + " report_rate=10,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AM1Mtzkma2wW" + }, + "source": [ + "## Test the Trained Network\n", + "\n", + "We can test how well the network has learned the data by propagating some of the input patterns through the trained network." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "id": "5q3RioNYa2wW", + "outputId": "c142257f-2810-4fec-c77d-61032bbc67ed", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 421 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Layer: output 'Dense'\n", + "Act function: softmax\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Act function: sigmoid\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 5)hidden_2Layer: flatten 'Flatten'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + ] + }, + "metadata": {} + } + ], + "source": [ + "from time import sleep\n", + "for pattern in inputs[0:10]:\n", + " net.display(pattern)\n", + " sleep(1.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yljy3x96a2wW" + }, + "source": [ + "## Analyze hidden layer after training\n", + "\n", + "Let's look at how the hidden layer representations have changed **after** training. Compare the graph below to the one above. Now clear patterns are emerging. You should see lots of clusters of similar colors indicating that the network has created hidden representations to help it correctly categorize each type of digit." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "id": "1Do7hPkpa2wW", + "outputId": "3a787a02-1f88-47c7-a0f8-c08d8f32fd85", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[(0, 'pink'), (1, 'red'), (2, 'orange'), (3, 'yellow'), (4, 'green'), (5, 'teal'), (6, 'blue'), (7, 'indigo'), (8, 'violet'), (9, 'black')]\n" + ] + } + ], + "source": [ + "print(encoding)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "id": "T3vwDzX4a2wW", + "outputId": "89634f4b-d91b-4316-9ca3-65a8517e19e6", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 497 + } + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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\n" + }, + "metadata": {}, + "execution_count": 15 + } + ], + "source": [ + "net.set_pca_spaces(inputs)\n", + "net.predict_pca_to(inputs, 'hidden_2', visualize*24, None)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gdjqssJ2a2wW" + }, + "source": [ + "Each run of the network will likely be slightly different, but notice that after training, the hidden layer representations for each digit are now more clustered together, rather than being all jumbled as before. Remember that each color represents training examples for one particular digit (e.g. blue is for the digit 6).\n", + "\n", + "Some of the clusters you see are likely more compact than others. Several of the clusters may even be intermingled with one another. Use the color key to try to figure out which digits hidden layer representations are intermingled. Then draw them on top of one another. Do they share some of the same substructure?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ni6gW_pTa2wW" + }, + "source": [ + "## Cluster Analysis\n", + "\n", + "Another way to analyze the hidden layer representation is to do a cluster analysis. Here we consider every hidden layer representation and calculate the Euclidean distance between every possible pair. The two closest examples form a cluster and their average is put back into the set of representations. We continue this process until a complete tree has been formed." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "id": "q4cz0OK4a2wW" + }, + "outputs": [], + "source": [ + "from scipy.cluster import hierarchy\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "id": "Htk-MSeQa2wW" + }, + "outputs": [], + "source": [ + "labelList = [str(v) for v in range(10)]" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "id": "97ILrs1Na2wX" + }, + "outputs": [], + "source": [ + "hidden_raw = net.predict_to(inputs, \"hidden_2\")" + ] + }, { - "data": { - "image/png": "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ZqugWL58uQzDuOxr8ODB0ZobIrBq1SoZhqElS5Z4PZW48uKLLyozM7M5wGfcuHH64IMPvJ5W3Fm5cqXGjBmj1NRU9ezZUzNnztSXX37p9bTiTlFRke655x716dNHhmFo06ZNXk8pLr3wwgu64YYb1LFjR40dO1ZffPGFo/ezvUIxdOhQVVRUNH99+umn0ZgXIrB7927l5+crMzPT66nEnbS0NK1atUp79+7Vnj17dMcdd2jGjBk6dOiQ11OLKzt27NDixYu1a9cuffTRR6qtrdVdd92lCxcueD21uHLhwgUNHz5cL7zwgtdTiVvr16/Xww8/rKeeekr79u3T8OHDNW3aNFVVVTl2T1vbRpcvX65NmzappKQkilNCJM6fP69Ro0bpT3/6k55++mmNGDFCzz77rNfTimtdu3bVH/7wB82bN8/rqcStU6dOqWfPntqxY4cmTpzo9XTikmEY2rhxo2bOnOn1VOLK2LFjNWbMGK1Zs0ZS45lc6enp+s1vfqPHH3/ckXvaXqE4fPiw+vTpo379+mnOnDk6duxYNOYFixYvXqwf//jHuvPOO72eStyrr6/Xm2++qQsXLmjcuHFeTyeunT17VlJjcQfEi0uXLmnv3r2X/T5ISEjQnXfeqZ07dzp2X1OHg13N2LFj9corr2jQoEGqqKjQ7373O02YMEEHDx5UampqtOaIMN58803t27dPu3fv9noqce3AgQMaN26cvv/+e/3gBz/Qxo0bNWTIEK+nFbcaGhq0ZMkS3Xbbbbr55pu9ng7gmm+++Ub19fXq1avXZT/v1auX/vWvfzl2X1sFxfTp05v/OTMzU2PHjlXfvn21YcMGlnldUl5ert/+9rf66KOP1LFjR6+nE9cGDRqkkpISnT17Vm+99ZZycnK0Y8cOigqPLF68WAcPHqSvC3CJrYKitS5dumjgwIEqLS2N5sviGvbu3auqqiqNGjWq+Wf19fUqKirSmjVrdPHiRbVr187DGcaPpKQk9e/fX5J0yy23aPfu3XruueeUn5/v8cziT15ent577z0VFRUpLS3N6+kArurevbvatWunkydPXvbzkydP6vrrr3fsvlHNoTh//ryOHDmi3r17R/NlcQ1TpkzRgQMHVFJS0vw1evRozZkzRyUlJRQTHmpoaNDFixe9nkZcCYVCysvL08aNG/W3v/1NN954o9dTAlyXlJSkW265Rdu3b2/+WUNDg7Zv3+5oX5etFYpHH31U99xzj/r27asTJ07oqaeeUrt27TR79uxozQ9hpKamXvF8+LrrrlO3bt14buyiZcuWafr06crIyNB3332n119/XYWFhdq6davXU4srixcv1uuvv66//vWvSk1NVWVlpSSpc+fOSk5O9nh28eP8+fOXrVR//fXXKikpUdeuXZWRkeHhzOLHww8/rJycHI0ePVq33nqrnn32WV24cEG//OUvHbunrYLi+PHjmj17tk6fPq0ePXro9ttv165du9SjR49ozQ8IhKqqKmVnZ6uiokKdO3dWZmamtm7dqqlTp3o9tbjy4osvSpImTZp02c/Xrl2r3Nxc9ycUp/bs2aPJkyc3f//www9LknJycvTKK694NKv48sADD+jUqVN68sknVVlZqREjRmjLli1XNGpGE8eXAwAA2zjLAwAA2EZBAQAAbKOgAAAAtlFQAAAA2ygoAACAbRQUAADANgoKAABgGwUFAACwjYICAADYRkEBAABso6AAAAC2UVAAAADb/j+uu9pIScu5lgAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" + "cell_type": "code", + "execution_count": 19, + "metadata": { + "id": "0AH7dcBya2wX", + "outputId": "65a970a2-5e77-4062-f14a-145661a3c1e6", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 430 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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APjswIEDpy0ukg3Rcnznoq6uTrt371Z+fr4sy2ry3+sro3S6s8GaWXNUsWbWHFXpuObDhw+rqKhIBw8edPyYwynHdy4yMjJUUFBw2uNatmyZNhesHmtOD6w5PbDm9JCOa87ISH1+JgmdAADAVRQXAADAVa4XF9nZ2br//vuVnZ3t9h8dWKw5PbDm9MCa0wNrTi3HDZ0AAADN4bEIAABwFcUFAABwFcUFAABwFcUFAABwVVLFxezZs2VZlqZNm9bscS+//LJ69uypnJwc9enTR2+//XYyp/XUU089pfPOO68haGXo0KF65513Tnn8ggULZFlWo6+cnBwPZ5w8p2uWwn2NJemhhx7SoEGDlJ+fr7POOktjx47V5s2bmx0T9mudyJqlcF/rVatW6Ze//KU6duwoy7L0+uuvN3v8ihUrmlxjy7K0d+9ebybsAqdrlo6te8CAAcrOzlbXrl21YMGClM8zFZ588kl17txZOTk5uuCCC/TZZ5+d8tiwf57rOVmzlLrPc8LFxdq1azV37lydd955zR73ySef6Prrr9dNN92kkpISjR07VmPHjtWXX36Z6Kk9VVBQoNmzZ2v9+vVat26dLr/8co0ZM0abNm065ZiWLVtqz549DV9lZWUezjh5Ttcc9mssSStXrtRtt92mTz/9VO+//77i8biuvPJKHT16tNlxYb7Wiaw57Nf66NGj6tu3r5588klH4zZv3tzoOp911lkpmqH7nK55+/btuuqqq3TZZZeptLRU06ZN0+TJk7V06dIUz9RdixYt0u9//3vdf//92rBhg/r27atRo0Zp//79pxwT5s+z5HzNKf08mwQcOXLEdOvWzbz//vvm0ksvNXfccccpj7322mvNVVdd1ej3LrjgAnPzzTcncupAOOOMM8xf/vKXk/63+fPnm1atWnk7IQ80t+YoXuP9+/cbSWblypWnPCZq19rOmqN0rSWZJUuWNHvMhx9+aCSZgwcPejKnVLOz5nvuucf07t270e9dd911ZtSoUSmcmfsGDx5sbrvttoZf19bWmo4dO5qHHnropMdH4fPsdM2p/DwndOfitttu01VXXaUrrrjitMeuWbOmyXGjRo3SmjVrEjm1r2pra/XSSy/p6NGjGjp06CmP+/7779WpUycVFhae9i5H0NlZc5Sucb3Dhw9Lktq0adPscVG61nbWHMVrbUe/fv3UoUMHjRw5Uh9//LHf00mpKFzj6upqrV+/vtE6MjIydMUVVzS7jjB/nhNZcyqvtePi4qWXXtKGDRv00EMP2Tp+7969at++faPfa9++faieWW7cuFE//vGPlZ2drVtuuUVLlixRr169Tnpsjx499Nxzz+lvf/ubFi5cqLq6Og0bNky7du3yeNbJcbLmKFzj49XV1WnatGm68MIL9bOf/eyUx0XlWkv21xy1a306HTp00NNPP61XX31Vr776qgoLCzV8+HBt2LDB76mlzKmucXl5uSorK32alTPfffedamtrHX2vhv3znMiaU/l5dvRW1J07d+qOO+7Q+++/H8pGl0T16NFDpaWlOnz4sF555RUVFxdr5cqVJ/1hO3To0Eb/wh82bJjOPfdczZ07Vw888ICX006KkzVHzW233aYvv/xSq1evbva4qFxryf6a002PHj3Uo0ePhl8PGzZM27Zt0yOPPKK//vWvPs4MbovS5zkIHBUX69ev1/79+zVgwICG36utrdWqVav0xBNPqKqqSpmZmY3GnH322dq3b1+j39u3b5/OPvvsJKbtrVgspq5du0qSzj//fK1du1aPPvqo5s6de9qxWVlZ6t+/v7Zu3ZrqabrKyZqjcI3rTZ06VW+++aZWrVqlgoICR2PDeq2drDlK1zpRgwcPjnQRdqpr3LJlS+Xm5vo0K2fatWunzMzMpL5Xw/Z5TmTNqfw8O3osMmLECG3cuFGlpaUNXwMHDtT48eNVWlrapLCQjlWDy5cvb/R777//frM9C0FXV1enqqoqW8fW1tZq48aN6tChQ4pnlVrNrTkK19gYo6lTp2rJkiX64IMPdM455zj+M8J2rRNZcxSudbJKS0tDc40TEYVrHIvFdP755zdaR11dnZYvX257HWH7PCey5pRe62Q7Qk/cLXLDDTeYGTNmNPz6448/Ni1atDBz5swxX331lbn//vtNVlaW2bhxY7Kn9sSMGTPMypUrzfbt280XX3xhZsyYYSzLMu+9954xpul6//jHP5qlS5eabdu2mfXr15vf/OY3Jicnx2zatMmvJTjmdM1hv8bGGHPrrbeaVq1amRUrVpg9e/Y0fFVUVDQcE7Vrnciaw36tjxw5YkpKSkxJSYmRZB5++GFTUlJiysrKjDHHvvdvuOGGhuMfeeQR8/rrr5stW7aYjRs3mjvuuMNkZGSYZcuW+bUEx5yu+ZtvvjF5eXnm7rvvNl999ZV58sknTWZmpnn33Xf9WkJCXnrpJZOdnW0WLFhg/vGPf5gpU6aY1q1bm7179xpjovd5Nsb5mlP5eXa9uLj00ktNcXFxo2MWL15sunfvbmKxmOndu7d56623kj2tZ2688UbTqVMnE4vFzJlnnmlGjBjR8EPWmKbrnTZtmikqKjKxWMy0b9/ejB492mzYsMGHmSfO6ZqNCfc1NubYFr2Tfc2fP7/hmKhd60TWbEy4r3X91tITv+rXWFxcbC699NKG4//0pz+Zn/70pyYnJ8e0adPGDB8+3HzwwQf+TD5BTtdcP6Zfv34mFouZLl26NPqeCJPHH3+84TM6ePBg8+mnnzb8t6h9nus5WbMxqfs888p1AADgKt4tAgAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXEVxAQAAXPX/ARN3PMROYT0NAAAAAElFTkSuQmCC\n" + }, + "metadata": {} + } + ], + "source": [ + "result = hierarchy.dendrogram(\n", + " hierarchy.linkage(hidden_raw[0:40],method='ward'),\n", + " orientation=\"left\",\n", + " labels=labelList*4,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WkcQff0Za2wX" + }, + "source": [ + "Notice that the neural network will typically discover hidden layer representations such that all digits of the same type are closer to one another than to other digits." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "E27gDrtna2wX" + }, + "source": [ + "## Conclusion" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8fU2V2DGa2wX" + }, + "source": [ + "The way a neural network processes data is to transform the data through its hidden layers. Through this notebook, we have visualized how the hidden layer representations changed as a result of training the network." ] - }, - "metadata": {}, - "output_type": "display_data" } - ], - "source": [ - "result = hierarchy.dendrogram(\n", - " hierarchy.linkage(hidden_raw[0:40],method='ward'),\n", - " orientation=\"left\", \n", - " labels=labelList*4,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that the neural network will typically discover hidden layer representations such that all digits of the same type are closer to one another than to other digits. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The way a neural network processes data is to transform the data through its hidden layers. Through this notebook, we have visualized how the hidden layer representations changed as a result of training the network. " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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" + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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" + }, + "colab": { + "provenance": [] + } }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": {}, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file From 7af58db8d8b9d43df60b1727e83b94f254c6a613 Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Thu, 19 Sep 2024 15:09:23 -0400 Subject: [PATCH 16/37] fixed import and added a section about issues with categorization: --- .../NeuralNetworks/CategorizingFaces.ipynb | 8183 ++++++++--------- 1 file changed, 4005 insertions(+), 4178 deletions(-) diff --git a/notebooks/NeuralNetworks/CategorizingFaces.ipynb b/notebooks/NeuralNetworks/CategorizingFaces.ipynb index 8bfabda..52dae96 100644 --- a/notebooks/NeuralNetworks/CategorizingFaces.ipynb +++ b/notebooks/NeuralNetworks/CategorizingFaces.ipynb @@ -1,4267 +1,4094 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Open" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "JGbnXV63jz0L" - }, - "source": [ - "# CMU Faces Dataset\n", - "\n", - "In 1997 Tom Mitchell wrote one of the first textbooks on machine learning appropriately titled **Machine Learning**. Along with the book he provided a dataset of faces to allow readers to experiment with neural networks. This dataset is quite small by today's standards, but it will allow us to explore a variety of classification tasks using relatively small network models.\n", - "\n", - "The dataset contains images depicting 20 different individuals, displaying various emotions (neutral, happy, sad, or angry), facing different directions (forward, right, left, up), and either wearing sunglasses or not. Each image is 120 by 128 for a total of 15,360 pixels.\n", - "\n", - "Let's begin by installing all of the software we will need. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "rdR3olbIk5py" + }, + "source": [ + "\"Open" + ] }, - "id": "YsuI704EIAj2", - "outputId": "08fd9937-da8d-45a3-d4c2-485b5badb293" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m127.6/127.6 kB\u001b[0m \u001b[31m3.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h" - ] - } - ], - "source": [ - "%pip install aitk.networks --quiet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Ok-6KlqnIRzW" - }, - "outputs": [], - "source": [ - "from aitk.networks import SimpleNetwork\n", - "import numpy as np\n", - "from aitk.utils import array_to_image, get_dataset, gallery\n", - "from time import sleep" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "cqACx7jW0kym" - }, - "source": [ - "Next we will download the dataset of faces." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "cell_type": "markdown", + "metadata": { + "id": "JGbnXV63jz0L" + }, + "source": [ + "# CMU Faces Dataset\n", + "\n", + "In 1997 Tom Mitchell wrote one of the first textbooks on machine learning appropriately titled **Machine Learning**. Along with the book he provided a dataset of faces to allow readers to experiment with neural networks. This dataset is quite small by today's standards, but it will allow us to explore a variety of classification tasks using relatively small network models.\n", + "\n", + "The dataset contains images depicting 20 different individuals, displaying various emotions (neutral, happy, sad, or angry), facing different directions (forward, right, left, up), and either wearing sunglasses or not. Each image is 120 by 128 for a total of 15,360 pixels.\n", + "\n", + "Let's begin by installing all of the software we will need." + ] }, - "id": "T9MJWYPKIXp_", - "outputId": "2ab5f221-1e4d-4858-f30c-e091f532bfba" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Downloading data from https://raw.githubusercontent.com/ArtificialIntelligenceToolkit/datasets/master/cmu_faces/cmu_faces_full_size.npz\n" - ] + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "YsuI704EIAj2" + }, + "outputs": [], + "source": [ + "%pip install aitk --quiet" + ] }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "4332797952it [00:00, 14260357634.57it/s]\n" - ] - } - ], - "source": [ - "ds = get_dataset(\"cmu-faces\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PnDupQcFmI-l" - }, - "source": [ - "Let's find out how many images are contained in this dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "Ok-6KlqnIRzW" + }, + "outputs": [], + "source": [ + "from aitk.networks import SimpleNetwork\n", + "import numpy as np\n", + "from aitk.utils import array_to_image, get_dataset, gallery\n", + "from time import sleep" + ] }, - "id": "6LLW_MnEl_d7", - "outputId": "7e312390-9a98-411f-d9c3-44caa3b30f42" - }, - "outputs": [ { - "data": { - "text/plain": [ - "624" + "cell_type": "markdown", + "metadata": { + "id": "cqACx7jW0kym" + }, + "source": [ + "Next we will download the dataset of faces." ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(ds.train_inputs)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "u1YA5b5CmRmT" - }, - "source": [ - "Let's look at how all of the features of the data are encoded. We will just inspect the first 20 images in the dataset.\n", - "\n", - "- The first feature is the unique name of a person.\n", - "- The second feature is the direction they are facing.\n", - "- The third feature is the emotion they are displaying.\n", - "- Finally, the last feature is whether they are wearing sunglasses." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "7vaopLYAmhF2", - "outputId": "c1d0a990-900f-4718-b527-8f660904fb4b" - }, - "outputs": [ { - "data": { - "text/plain": [ - "[['quinn', 'right', 'sad', 'sunglasses'],\n", - " ['quinn', 'up', 'angry', 'eyes'],\n", - " ['quinn', 'up', 'angry', 'sunglasses'],\n", - " ['quinn', 'up', 'happy', 'eyes'],\n", - " ['quinn', 'up', 'happy', 'sunglasses'],\n", - " ['quinn', 'up', 'neutral', 'eyes'],\n", - " ['quinn', 'up', 'neutral', 'sunglasses'],\n", - " ['quinn', 'up', 'sad', 'eyes'],\n", - " ['quinn', 'up', 'sad', 'sunglasses'],\n", - " ['rose', 'forward', 'angry', 'eyes'],\n", - " ['rose', 'forward', 'angry', 'sunglasses'],\n", - " ['rose', 'forward', 'happy', 'eyes'],\n", - " ['rose', 'forward', 'happy', 'sunglasses'],\n", - " ['rose', 'forward', 'neutral', 'eyes'],\n", - " ['rose', 'forward', 'neutral', 'sunglasses'],\n", - " ['rose', 'forward', 'sad', 'eyes'],\n", - " ['rose', 'forward', 'sad', 'sunglasses'],\n", - " ['rose', 'left', 'angry', 'eyes'],\n", - " ['rose', 'left', 'angry', 'sunglasses'],\n", - " ['rose', 'left', 'happy', 'eyes']]" + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "T9MJWYPKIXp_" + }, + "outputs": [], + "source": [ + "ds = get_dataset(\"cmu-faces\")" ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ds.train_features[:20]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "RgXI42iRgLkf" - }, - "source": [ - "# Classification Task: Sunglasses or not?\n", - "\n", - "Let's extract a subset of images from the original dataset. We will focus on only the images showing people facing forward and try to determine whether or not they are wearing sunglasses. We will call this subset *glasses*.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "EGUb7SmBMgZ0" - }, - "outputs": [], - "source": [ - "glasses = ds.query_train(includes=[\"forward\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "ComNULP4M0PM", - "outputId": "16c04de0-4d92-4c5e-b384-cece51ffd62a" - }, - "outputs": [ { - "data": { - "text/plain": [ - "[['rose', 'forward', 'angry', 'eyes'],\n", - " ['rose', 'forward', 'angry', 'sunglasses'],\n", - " ['rose', 'forward', 'happy', 'eyes'],\n", - " ['rose', 'forward', 'happy', 'sunglasses'],\n", - " ['rose', 'forward', 'neutral', 'eyes'],\n", - " ['rose', 'forward', 'neutral', 'sunglasses'],\n", - " ['rose', 'forward', 'sad', 'eyes'],\n", - " ['rose', 'forward', 'sad', 'sunglasses'],\n", - " ['sam', 'forward', 'angry', 'eyes'],\n", - " ['sam', 'forward', 'angry', 'sunglasses']]" + "cell_type": "markdown", + "metadata": { + "id": "PnDupQcFmI-l" + }, + "source": [ + "Let's find out how many images are contained in this dataset." ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "glasses.train_features[:10]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "KCSDHBnZ4XRr", - "outputId": "642e42cf-d353-43ac-f115-172fe8a500a1" - }, - "outputs": [ { - "data": { - "text/plain": [ - "156" + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "6LLW_MnEl_d7", + "outputId": "454b463f-e31e-4238-df86-2394b9268a79" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "624" + ] + }, + "metadata": {}, + "execution_count": 4 + } + ], + "source": [ + "len(ds.train_inputs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "u1YA5b5CmRmT" + }, + "source": [ + "Let's look at how all of the features of the data are encoded. We will just inspect the first 20 images in the dataset.\n", + "\n", + "- The first feature is the unique name of a person.\n", + "- The second feature is the direction they are facing.\n", + "- The third feature is the emotion they are displaying.\n", + "- Finally, the last feature is whether they are wearing sunglasses." ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(glasses.train_inputs)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LwBajXAKn0jg" - }, - "source": [ - "Here are the first 10 images from the glasses dataset. The display will show images of people's faces, alternating with and without sunglasses." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 }, - "id": "6_JPyVX9nqKT", - "outputId": "5fa601b7-153c-4b99-c52c-ccffe4132b3d" - }, - "outputs": [ { - "data": { - "text/html": [ - "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
34
\"34\"
35
\"35\"
36
\"36\"
37
\"37\"
38
\"38\"
39
\"39\"
40
\"40\"
41
\"41\"
42
\"42\"
43
\"43\"
44
\"44\"
45
\"45\"
46
\"46\"
47
\"47\"
48
\"48\"
49
\"49\"
50
\"50\"
51
\"51\"
52
\"52\"
53
\"53\"
54
\"54\"
55
\"55\"
56
\"56\"
57
\"57\"
58
\"58\"
59
\"59\"
60
\"60\"
61
\"61\"
62
\"62\"
63
\"63\"
64
\"64\"
65
\"65\"
66
\"66\"
67
\"67\"
68
\"68\"
69
\"69\"
70
\"70\"
71
\"71\"
72
\"72\"
73
\"73\"
74
\"74\"
75
\"75\"
76
\"76\"
77
\"77\"
78
\"78\"
79
\"79\"
80
\"80\"
81
\"81\"
82
\"82\"
83
\"83\"
84
\"84\"
85
\"85\"
86
\"86\"
87
\"87\"
88
\"88\"
89
\"89\"
90
\"90\"
91
\"91\"
92
\"92\"
93
\"93\"
94
\"94\"
95
\"95\"
96
\"96\"
97
\"97\"
98
\"98\"
99
\"99\"
" + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "7vaopLYAmhF2", + "outputId": "81d6aa77-cfe1-42af-b67f-bb634f7983cf" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[['quinn', 'right', 'sad', 'sunglasses'],\n", + " ['quinn', 'up', 'angry', 'eyes'],\n", + " ['quinn', 'up', 'angry', 'sunglasses'],\n", + " ['quinn', 'up', 'happy', 'eyes'],\n", + " ['quinn', 'up', 'happy', 'sunglasses'],\n", + " ['quinn', 'up', 'neutral', 'eyes'],\n", + " ['quinn', 'up', 'neutral', 'sunglasses'],\n", + " ['quinn', 'up', 'sad', 'eyes'],\n", + " ['quinn', 'up', 'sad', 'sunglasses'],\n", + " ['rose', 'forward', 'angry', 'eyes'],\n", + " ['rose', 'forward', 'angry', 'sunglasses'],\n", + " ['rose', 'forward', 'happy', 'eyes'],\n", + " ['rose', 'forward', 'happy', 'sunglasses'],\n", + " ['rose', 'forward', 'neutral', 'eyes'],\n", + " ['rose', 'forward', 'neutral', 'sunglasses'],\n", + " ['rose', 'forward', 'sad', 'eyes'],\n", + " ['rose', 'forward', 'sad', 'sunglasses'],\n", + " ['rose', 'left', 'angry', 'eyes'],\n", + " ['rose', 'left', 'angry', 'sunglasses'],\n", + " ['rose', 'left', 'happy', 'eyes']]" + ] + }, + "metadata": {}, + "execution_count": 5 + } ], - "text/plain": [ - "" + "source": [ + "ds.train_features[:20]" ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "images = [array_to_image(glasses.train_inputs[i]) for i in range(0,100)]\n", - "gallery(images)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "bTTe6F4Un6yQ" - }, - "source": [ - "Next we need to determine the targets for training this task. Any images with the label \"eyes\" we will train the network to output [1,0], and any images with the label \"sunglasses\" we will train the network to output [0,1]. This is known as a one-hot encoding." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "py4S8ijmNLEP" - }, - "outputs": [], - "source": [ - "glasses.train_targets = []\n", - "for i in range(len(glasses.train_features)):\n", - " if \"eyes\" in glasses.train_features[i]:\n", - " glasses.train_targets.append([1,0])\n", - " elif \"sunglasses\" in glasses.train_features[i]:\n", - " glasses.train_targets.append([0,1])\n", - " else:\n", - " raise ValueError(\"pattern %d should not be in this dataset\" %(i))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "YB797uPwotcD" - }, - "source": [ - "Here are the first 10 targets for training, and we can see that they are correctly encoding the desired output for the first 10 images displayed above." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "nbFm-x7l0WLY", - "outputId": "8edf581b-efbe-4477-e743-1404709d1243" - }, - "outputs": [ { - "data": { - "text/plain": [ - "[[1, 0],\n", - " [0, 1],\n", - " [1, 0],\n", - " [0, 1],\n", - " [1, 0],\n", - " [0, 1],\n", - " [1, 0],\n", - " [0, 1],\n", - " [1, 0],\n", - " [0, 1]]" + "cell_type": "markdown", + "metadata": { + "id": "RgXI42iRgLkf" + }, + "source": [ + "# Classification Task: Sunglasses or not?\n", + "\n", + "Let's extract a subset of images from the original dataset. We will focus on only the images showing people facing forward and try to determine whether or not they are wearing sunglasses. We will call this subset *glasses*.\n", + "\n" ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "glasses.train_targets[:10]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "uQeAEcCEo8tk" - }, - "source": [ - "## Building the neural network\n", - "\n", - "The network must take images that are 120x128 as input and produce 2 outputs. What goes in between the input and output layers is up to the model builder. This is a relatively simple task, so let's start with a very small hidden layer of size 3.\n", - "\n", - "Also, by choosing such a small hidden layer we will be able to easily visualize the weights learned by the network after training is complete.\n", - "\n", - "The network summary shows us the total number of trainable parameters in this neural network, which for this particular network is approximately 46 thousand. For modern networks the number of trainable parameters may be in the billions." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "6DJ3L_q_pC9G", - "outputId": "22462ed8-65e6-43e7-a07f-3942140475ec" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model: \"SimpleNetwork\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input (InputLayer) [(None, 120, 128)] 0 \n", - " \n", - " flatten (Flatten) (None, 15360) 0 \n", - " \n", - " hidden_2 (Dense) (None, 3) 46083 \n", - " \n", - " output (Dense) (None, 2) 8 \n", - " \n", - "=================================================================\n", - "Total params: 46091 (180.04 KB)\n", - "Trainable params: 46091 (180.04 KB)\n", - "Non-trainable params: 0 (0.00 Byte)\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "net = SimpleNetwork(\n", - " (120,128),\n", - " \"Flatten\",\n", - " 3,\n", - " (2, \"softmax\")\n", - " )\n", - "net.summary()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LD-f3Db5g4uC" - }, - "source": [ - "## Training on sunglasses task\n", - "\n", - "Each time you recreate the network (using the cell above) it will start with a different set of random weights. In most cases the network will successfully solve the task. However, because we chose such a small hidden layer, in some cases it will get stuck. Simply rerun the cell above to recreate the network, and try retraining it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 457 + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "EGUb7SmBMgZ0" + }, + "outputs": [], + "source": [ + "glasses = ds.query_train(includes=[\"forward\"])" + ] }, - "id": "AiQpLVLDsa8X", - "outputId": "cf2eb6e1-6e12-473f-d926-aeddd5a73c99" - }, - "outputs": [ { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " 2024-06-13T14:38:49.511161\n", - " image/svg+xml\n", - " \n", - " \n", - " Matplotlib v3.7.1, https://matplotlib.org/\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ComNULP4M0PM", + "outputId": "e01132e9-d0dd-4464-998f-52908f4b721e" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[['rose', 'forward', 'angry', 'eyes'],\n", + " ['rose', 'forward', 'angry', 'sunglasses'],\n", + " ['rose', 'forward', 'happy', 'eyes'],\n", + " ['rose', 'forward', 'happy', 'sunglasses'],\n", + " ['rose', 'forward', 'neutral', 'eyes'],\n", + " ['rose', 'forward', 'neutral', 'sunglasses'],\n", + " ['rose', 'forward', 'sad', 'eyes'],\n", + " ['rose', 'forward', 'sad', 'sunglasses'],\n", + " ['sam', 'forward', 'angry', 'eyes'],\n", + " ['sam', 'forward', 'angry', 'sunglasses']]" + ] + }, + "metadata": {}, + "execution_count": 7 + } ], - "text/plain": [ - "" + "source": [ + "glasses.train_features[:10]" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stopped because accuracy beat goal of 1.0\n", - "Epoch 26/150 loss: 0.001345871016383171 - tolerance_accuracy: 1.0\n" - ] + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "KCSDHBnZ4XRr", + "outputId": "d205e4bc-c56f-448b-a2fc-b92d3386ef8f" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "156" + ] + }, + "metadata": {}, + "execution_count": 8 + } + ], + "source": [ + "len(glasses.train_inputs)" + ] }, { - "data": { - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "LwBajXAKn0jg" + }, + "source": [ + "Here are the first 10 images from the glasses dataset. The display will show images of people's faces, alternating with and without sunglasses." ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "net.fit(\n", - " glasses.train_inputs, glasses.train_targets,\n", - " batch_size=32,\n", - " report_rate=10,\n", - " epochs=150,\n", - " accuracy=1.0\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "2MI89BE9hAxS" - }, - "source": [ - "## Testing on sunglasses task\n", - "\n", - "Let's watch how the trained network classifies the images.Watch the hidden layer activations. Are all of them lighting up white or only some of them? Is there a pattern that they follow based on whether the person is wearing sunglasses or not?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 421 }, - "id": "g4X44v-Bswqh", - "outputId": "1695c7a7-fa33-4a95-acdc-fd2cb79f74fa" - }, - "outputs": [ { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " Layer: output 'Dense'\n", - "Act function: softmax\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 2)outputLayer: hidden_2 'Dense'\n", - "Act function: sigmoid\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 3)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 15360)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 120, 128)]inputActivations for SimpleNetwork" + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 456 + }, + "id": "6_JPyVX9nqKT", + "outputId": "7bc4e946-3b7e-4aed-a3ee-814d603d2183" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
" + ] + }, + "metadata": {} + } ], - "text/plain": [ - "" + "source": [ + "images = [array_to_image(glasses.train_inputs[i]) for i in range(0,10)]\n", + "gallery(images)" ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for i in range(20):\n", - " net.display(glasses.train_inputs[i])\n", - " sleep(1.0)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "YyDP9gaRhMry" - }, - "source": [ - "## Weights learned on sunglasses task\n", - "\n", - "Neural networks are often described as black boxes because it is hard to understand *how* they have learned to solve a particular task.\n", - "\n", - "However, for this simple model applied to an image classification task we can actually visualize how the network has modified its weights in order to solve the problem. To determine whether each person in the image is wearing sunglasses or not, the network must learn to focus on the eye area of the face.\n", - "\n", - "There are 3 hidden layer neurons. Let's see how each one of these hidden neurons has weighted its connections to the input image. Each pixel in the visualizations below represents one weight from the input image to the hidden neuron. The darker the color the lower the weight, the lighter the color the higher the weight.\n", - "\n", - "Each time you retrain the network it may discover a different way to successfully classify the images. In some runs you may find that one or two of the hidden units is largely ignored, and it's weights will look like random noise." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "YUuCsfzLcxuk" - }, - "outputs": [], - "source": [ - "def show_hidden_weights(network, i):\n", - " image = array_to_image(network.get_weights()[0][:,i].reshape((120, 128)))\n", - " return image.resize((240, 256), resample=0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 273 }, - "id": "XZSRiGV5doVB", - "outputId": "3da73877-4027-47f0-9186-2d875a715106" - }, - "outputs": [ { - "data": { - "image/png": "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\n", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "bTTe6F4Un6yQ" + }, + "source": [ + "Next we need to determine the targets for training this task. Any images with the label \"eyes\" we will train the network to output [1,0], and any images with the label \"sunglasses\" we will train the network to output [0,1]. This is known as a one-hot encoding." ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_hidden_weights(net, 0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 273 }, - "id": "rstdP5Yrs9KJ", - "outputId": "fc88713f-1169-4460-897d-810ce8d823fa" - }, - "outputs": [ { - "data": { - "image/png": "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\n", - "text/plain": [ - "" + "cell_type": "code", + "execution_count": 10, + "metadata": { + "id": "py4S8ijmNLEP" + }, + "outputs": [], + "source": [ + "glasses.train_targets = []\n", + "for i in range(len(glasses.train_features)):\n", + " if \"eyes\" in glasses.train_features[i]:\n", + " glasses.train_targets.append([1,0])\n", + " elif \"sunglasses\" in glasses.train_features[i]:\n", + " glasses.train_targets.append([0,1])\n", + " else:\n", + " raise ValueError(\"pattern %d should not be in this dataset\" %(i))" ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_hidden_weights(net, 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 273 }, - "id": "yUKdYxZGfoW-", - "outputId": "47fd27ee-c82a-4859-d9fe-e625fbd93a71" - }, - "outputs": [ { - "data": { - "image/png": "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\n", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "YB797uPwotcD" + }, + "source": [ + "Here are the first 10 targets for training, and we can see that they are correctly encoding the desired output for the first 10 images displayed above." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "nbFm-x7l0WLY", + "outputId": "6dbc3ba7-87a5-451e-833d-043fd3f18d0b" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[[1, 0],\n", + " [0, 1],\n", + " [1, 0],\n", + " [0, 1],\n", + " [1, 0],\n", + " [0, 1],\n", + " [1, 0],\n", + " [0, 1],\n", + " [1, 0],\n", + " [0, 1]]" + ] + }, + "metadata": {}, + "execution_count": 11 + } + ], + "source": [ + "glasses.train_targets[:10]" ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_hidden_weights(net, 2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PFYVURgwgg9Q" - }, - "source": [ - "# Classification task: Direction straight, left, or right?\n", - "\n", - "Now let's try a different classification task to determine which direction a person is facing in each image. Let's focus on images without sunglasses, and let's also exclude the direction up.\n", - "\n", - "We will go through all of the same steps again to extract the desired images into a new dataset, this time called *direction*. Then we will check its size, look at some sample images, verify the features, and create the training targets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0Wo9fuyJfw4l" - }, - "outputs": [], - "source": [ - "direction = ds.query_train(includes = [\"eyes\"], excludes = [\"up\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "m3TC5CYYwnPl", - "outputId": "7c990781-9d68-4afa-c649-ddef49133a6f" - }, - "outputs": [ { - "data": { - "text/plain": [ - "234" + "cell_type": "markdown", + "metadata": { + "id": "uQeAEcCEo8tk" + }, + "source": [ + "## Building the neural network\n", + "\n", + "The network must take images that are 120x128 as input and produce 2 outputs. What goes in between the input and output layers is up to the model builder. This is a relatively simple task, so let's start with a very small hidden layer of size 3.\n", + "\n", + "Also, by choosing such a small hidden layer we will be able to easily visualize the weights learned by the network after training is complete.\n", + "\n", + "The network summary shows us the total number of trainable parameters in this neural network, which for this particular network is approximately 46 thousand. For modern networks the number of trainable parameters may be in the billions." ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(direction.train_inputs)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "-1NOGWPr7JpY" - }, - "source": [ - "Here are the first 10 images from the directions dataset. It should show several images of a feminine presenting person with varying facial expressions. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 456 }, - "id": "7qx4Orifwuxm", - "outputId": "af1f1a84-f53c-4ec8-b36f-737be285887c" - }, - "outputs": [ { - "data": { - "text/html": [ - "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
" + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "6DJ3L_q_pC9G", + "outputId": "01565a9f-3c15-4ac7-d30e-51b8c8e648d9" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Model: \"SimpleNetwork\"\n", + "_________________________________________________________________\n", + " Layer (type) Output Shape Param # \n", + "=================================================================\n", + " input (InputLayer) [(None, 120, 128)] 0 \n", + " \n", + " flatten (Flatten) (None, 15360) 0 \n", + " \n", + " hidden_2 (Dense) (None, 3) 46083 \n", + " \n", + " output (Dense) (None, 2) 8 \n", + " \n", + "=================================================================\n", + "Total params: 46091 (180.04 KB)\n", + "Trainable params: 46091 (180.04 KB)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "_________________________________________________________________\n" + ] + } ], - "text/plain": [ - "" + "source": [ + "net = SimpleNetwork(\n", + " (120,128),\n", + " \"Flatten\",\n", + " 3,\n", + " (2, \"softmax\")\n", + " )\n", + "net.summary()" ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "images = [array_to_image(direction.train_inputs[i]) for i in range(0,10)]\n", - "gallery(images)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "-t1ZT4maxb8k", - "outputId": "27dcde7b-05b0-4645-ece5-201ed52044d2" - }, - "outputs": [ { - "data": { - "text/plain": [ - "[['rose', 'forward', 'angry', 'eyes'],\n", - " ['rose', 'forward', 'happy', 'eyes'],\n", - " ['rose', 'forward', 'neutral', 'eyes'],\n", - " ['rose', 'forward', 'sad', 'eyes'],\n", - " ['rose', 'left', 'angry', 'eyes'],\n", - " ['rose', 'left', 'happy', 'eyes'],\n", - " ['rose', 'left', 'neutral', 'eyes'],\n", - " ['rose', 'left', 'sad', 'eyes'],\n", - " ['rose', 'right', 'angry', 'eyes'],\n", - " ['rose', 'right', 'happy', 'eyes'],\n", - " ['rose', 'right', 'neutral', 'eyes'],\n", - " ['rose', 'right', 'sad', 'eyes'],\n", - " ['sam', 'forward', 'angry', 'eyes'],\n", - " ['sam', 'forward', 'happy', 'eyes'],\n", - " ['sam', 'forward', 'neutral', 'eyes'],\n", - " ['sam', 'forward', 'sad', 'eyes'],\n", - " ['sam', 'left', 'angry', 'eyes'],\n", - " ['sam', 'left', 'happy', 'eyes'],\n", - " ['sam', 'left', 'neutral', 'eyes'],\n", - " ['sam', 'left', 'sad', 'eyes']]" + "cell_type": "markdown", + "metadata": { + "id": "LD-f3Db5g4uC" + }, + "source": [ + "## Training on sunglasses task\n", + "\n", + "Each time you recreate the network (using the cell above) it will start with a different set of random weights. In most cases the network will successfully solve the task. However, because we chose such a small hidden layer, in some cases it will get stuck. Simply rerun the cell above to recreate the network, and then train it by executing the cell below." ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "direction.train_features[:20]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "yJw8mCLC7SAs" - }, - "source": [ - "We want the network to classify each image as either facing foward, left, or right. We will use a one-hot encoding of length three to accomplish this." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "yfwSD4p0xG2Y" - }, - "outputs": [], - "source": [ - "direction.train_targets = []\n", - "for i in range(len(direction.train_features)):\n", - " if \"forward\" in direction.train_features[i]:\n", - " direction.train_targets.append([1,0,0])\n", - " elif \"left\" in direction.train_features[i]:\n", - " direction.train_targets.append([0,1,0])\n", - " elif \"right\" in direction.train_features[i]:\n", - " direction.train_targets.append([0,0,1])\n", - " else:\n", - " raise ValueError(\"pattern %d should not be in this dataset\" %(i))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "xxneKyL5xy3J", - "outputId": "33969ebd-0ab4-4033-e372-be91388f1d16" - }, - "outputs": [ { - "data": { - "text/plain": [ - "[[1, 0, 0],\n", - " [1, 0, 0],\n", - " [1, 0, 0],\n", - " [1, 0, 0],\n", - " [0, 1, 0],\n", - " [0, 1, 0],\n", - " [0, 1, 0],\n", - " [0, 1, 0],\n", - " [0, 0, 1],\n", - " [0, 0, 1],\n", - " [0, 0, 1],\n", - " [0, 0, 1],\n", - " [1, 0, 0],\n", - " [1, 0, 0],\n", - " [1, 0, 0],\n", - " [1, 0, 0],\n", - " [0, 1, 0],\n", - " [0, 1, 0],\n", - " [0, 1, 0],\n", - " [0, 1, 0]]" + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 457 + }, + "id": "AiQpLVLDsa8X", + "outputId": "907dd23b-2f70-4a18-f94a-9c993a02537a" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2024-09-19T19:05:18.677591\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.7.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ] + }, + "metadata": {} + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Stopped because accuracy beat goal of 1.0\n", + "Epoch 21/150 loss: 0.002560738939791918 - tolerance_accuracy: 1.0\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "execution_count": 13 + } + ], + "source": [ + "net.fit(\n", + " glasses.train_inputs, glasses.train_targets,\n", + " batch_size=32,\n", + " report_rate=10,\n", + " epochs=150,\n", + " accuracy=1.0\n", + ")" ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "direction.train_targets[:20]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DQXtFrekuNs5" - }, - "source": [ - "## Building the neural network\n", - "\n", - "This network is very similar to our original network, but we need to make the output layer have 3 outputs for the 3 possible directions." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" }, - "id": "o-BrV7eZx2yh", - "outputId": "424c0174-5c6d-48db-8cd8-e8c3f973f7c0" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model: \"SimpleNetwork\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input (InputLayer) [(None, 120, 128)] 0 \n", - " \n", - " flatten_1 (Flatten) (None, 15360) 0 \n", - " \n", - " hidden_2 (Dense) (None, 3) 46083 \n", - " \n", - " output (Dense) (None, 3) 12 \n", - " \n", - "=================================================================\n", - "Total params: 46095 (180.06 KB)\n", - "Trainable params: 46095 (180.06 KB)\n", - "Non-trainable params: 0 (0.00 Byte)\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "net2 = SimpleNetwork(\n", - " (120,128),\n", - " \"Flatten\",\n", - " 3,\n", - " (3, \"softmax\")\n", - " )\n", - "net2.summary()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ywp4SYTXhjFc" - }, - "source": [ - "## Training on direction task\n", - "\n", - "Remember that you can recreate the network as many times as you'd like by rerunning the cell above. This is a harder task than whether someone is wearing sunglasses, so in some runs the network may not be able to learn the task from the random starting conditions. In those cases, just try again." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 457 + "cell_type": "markdown", + "metadata": { + "id": "2MI89BE9hAxS" + }, + "source": [ + "## Testing on sunglasses task\n", + "\n", + "Let's watch how the trained network classifies the images.Watch the hidden layer activations. Are all of them lighting up white or only some of them? Is there a pattern that they follow based on whether the person is wearing sunglasses or not?" + ] }, - "id": "SggePWZ_yOEK", - "outputId": "df9b47a6-7f7c-4a08-ece1-1a68259f7715" - }, - "outputs": [ { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " 2024-06-13T14:40:42.151742\n", - " image/svg+xml\n", - " \n", - " \n", - " Matplotlib v3.7.1, https://matplotlib.org/\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 421 + }, + "id": "g4X44v-Bswqh", + "outputId": "4e2fffdc-f717-4b74-9b84-d05417df15df" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Layer: output 'Dense'\n", + "Act function: softmax\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 2)outputLayer: hidden_2 'Dense'\n", + "Act function: sigmoid\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 3)hidden_2Layer: flatten 'Flatten'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 15360)flattenLayer: input 'InputLayer'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = [(None, 120, 128)]inputActivations for SimpleNetwork" + ] + }, + "metadata": {} + } ], - "text/plain": [ - "" + "source": [ + "for i in range(10):\n", + " net.display(glasses.train_inputs[i])\n", + " sleep(1.0)" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stopped because accuracy beat goal of 1.0\n", - "Epoch 63/150 loss: 0.0020459750667214394 - tolerance_accuracy: 1.0\n" - ] + "cell_type": "markdown", + "metadata": { + "id": "YyDP9gaRhMry" + }, + "source": [ + "## Weights learned on sunglasses task\n", + "\n", + "Neural networks are often described as black boxes because it is hard to understand *how* they have learned to solve a particular task.\n", + "\n", + "However, for this simple model applied to an image classification task we can actually visualize how the network has modified its weights in order to solve the problem. To determine whether each person in the image is wearing sunglasses or not, the network must learn to focus on the eye area of the face.\n", + "\n", + "There are 3 hidden layer neurons. Let's see how each one of these hidden neurons has weighted its connections to the input image. Each pixel in the visualizations below represents one weight from the input image to the hidden neuron. The darker the color the lower the weight, the lighter the color the higher the weight.\n", + "\n", + "Each time you retrain the network it may discover a different way to successfully classify the images. In some runs you may find that one or two of the hidden units is largely ignored, and it's weights will look like random noise." + ] }, { - "data": { - "text/plain": [ - "" + "cell_type": "code", + "execution_count": 15, + "metadata": { + "id": "YUuCsfzLcxuk" + }, + "outputs": [], + "source": [ + "def show_hidden_weights(network, i):\n", + " image = array_to_image(network.get_weights()[0][:,i].reshape((120, 128)))\n", + " return image.resize((240, 256), resample=0)" ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "net2.fit(\n", - " direction.train_inputs, direction.train_targets,\n", - " batch_size=32,\n", - " report_rate=100,\n", - " epochs=150,\n", - " accuracy=1.0\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "1wr0xbIMhtLT" - }, - "source": [ - "## Testing on direction task\n", - "\n", - "Again, let's watch how the trained network classifies the images. Which hidden layer units light up for the different directions the person is facing?\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 421 }, - "id": "QOmaNAvuyaAb", - "outputId": "783e7480-597b-4fbb-bfe7-7785b987a612" - }, - "outputs": [ { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " Layer: output 'Dense'\n", - "Act function: softmax\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 3)outputLayer: hidden_2 'Dense'\n", - "Act function: sigmoid\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 3)hidden_2Layer: flatten_1 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 15360)flatten_1Layer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 120, 128)]inputActivations for SimpleNetwork" + "cell_type": "code", + "execution_count": 16, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 273 + }, + "id": "XZSRiGV5doVB", + "outputId": "66f44966-d821-4df8-8575-c9ea62c7ddfb" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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\n", + "image/jpeg": "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\n" + }, + "metadata": {}, + "execution_count": 16 + } ], - "text/plain": [ - "" + "source": [ + "show_hidden_weights(net, 0)" ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from time import sleep\n", - "for i in range(20):\n", - " net2.display(direction.train_inputs[i])\n", - " outputs = net2.propagate(direction.train_inputs[i])\n", - " sleep(1.0)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "n_i3mcPmhzpj" - }, - "source": [ - "## Weights learned on direction task\n", - "\n", - "Next let's examine the weights and see if we can understand more about how the network has solved the task given the pattern of hidden unit acitivations that you observed.\n", - "\n", - "In order to solve this task the network has to learn how to recognize the direction a person is facing. Perhaps it focuses on the profile or maybe the neck area or a combination of the two. Let's find out.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 273 }, - "id": "3Z8M_FxxyiUc", - "outputId": "ad26c66b-06c8-4bf9-db83-fed556e141a0" - }, - "outputs": [ { - "data": { - "image/png": "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\n", - "text/plain": [ - "" + "cell_type": "code", + "execution_count": 17, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 273 + }, + "id": "rstdP5Yrs9KJ", + "outputId": "b15cc477-5130-44af-ad2e-e54076e948ca" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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\n", + "image/jpeg": "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\n" + }, + "metadata": {}, + "execution_count": 17 + } + ], + "source": [ + "show_hidden_weights(net, 1)" ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_hidden_weights(net2, 0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 273 }, - "id": "_eIC0O6m21AC", - "outputId": "e1b24091-63fa-444a-9e44-89d7bf194dd2" - }, - "outputs": [ { - "data": { - "image/png": "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\n", - "text/plain": [ - "" + "cell_type": "code", + "execution_count": 18, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 273 + }, + "id": "yUKdYxZGfoW-", + "outputId": "17f431b6-a5ed-47ec-f624-644b680de284" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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\n", + "image/jpeg": "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\n" + }, + "metadata": {}, + "execution_count": 18 + } + ], + "source": [ + "show_hidden_weights(net, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PFYVURgwgg9Q" + }, + "source": [ + "# Classification task: Direction straight, left, or right?\n", + "\n", + "Now let's try a different classification task to determine which direction a person is facing in each image. Let's focus on images without sunglasses, and let's also exclude the direction up.\n", + "\n", + "We will go through all of the same steps again to extract the desired images into a new dataset, this time called *direction*. Then we will check its size, look at some sample images, verify the features, and create the training targets." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "id": "0Wo9fuyJfw4l" + }, + "outputs": [], + "source": [ + "direction = ds.query_train(includes = [\"eyes\"], excludes = [\"up\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "m3TC5CYYwnPl", + "outputId": "f0ebde69-8977-4692-dd95-b57ebfd138ff" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "234" + ] + }, + "metadata": {}, + "execution_count": 20 + } + ], + "source": [ + "len(direction.train_inputs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-1NOGWPr7JpY" + }, + "source": [ + "Here are the first 10 images from the directions dataset. It should show several images of a feminine presenting person with varying facial expressions." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 456 + }, + "id": "7qx4Orifwuxm", + "outputId": "b34d758c-c3c2-44ca-d0d5-39b3576e2b12" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
" + ] + }, + "metadata": {} + } + ], + "source": [ + "images = [array_to_image(direction.train_inputs[i]) for i in range(0,10)]\n", + "gallery(images)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "-t1ZT4maxb8k", + "outputId": "55387cd4-e610-4167-f530-c477047ba00e" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[['rose', 'forward', 'angry', 'eyes'],\n", + " ['rose', 'forward', 'happy', 'eyes'],\n", + " ['rose', 'forward', 'neutral', 'eyes'],\n", + " ['rose', 'forward', 'sad', 'eyes'],\n", + " ['rose', 'left', 'angry', 'eyes'],\n", + " ['rose', 'left', 'happy', 'eyes'],\n", + " ['rose', 'left', 'neutral', 'eyes'],\n", + " ['rose', 'left', 'sad', 'eyes'],\n", + " ['rose', 'right', 'angry', 'eyes'],\n", + " ['rose', 'right', 'happy', 'eyes']]" + ] + }, + "metadata": {}, + "execution_count": 22 + } + ], + "source": [ + "direction.train_features[:10]" ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_hidden_weights(net2, 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 273 }, - "id": "1WgVBkg64E0Z", - "outputId": "6b5ceb75-9dac-404a-c7cc-a15f5c1cfb99" - }, - "outputs": [ { - "data": { - "image/png": "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\n", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "yJw8mCLC7SAs" + }, + "source": [ + "We want the network to classify each image as either facing foward, left, or right. We will use a one-hot encoding of length three to accomplish this." ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "id": "yfwSD4p0xG2Y" + }, + "outputs": [], + "source": [ + "direction.train_targets = []\n", + "for i in range(len(direction.train_features)):\n", + " if \"forward\" in direction.train_features[i]:\n", + " direction.train_targets.append([1,0,0])\n", + " elif \"left\" in direction.train_features[i]:\n", + " direction.train_targets.append([0,1,0])\n", + " elif \"right\" in direction.train_features[i]:\n", + " direction.train_targets.append([0,0,1])\n", + " else:\n", + " raise ValueError(\"pattern %d should not be in this dataset\" %(i))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "xxneKyL5xy3J", + "outputId": "9cd41c5b-67e1-4777-db27-085db223ca21" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[[1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [1, 0, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 1, 0],\n", + " [0, 0, 1],\n", + " [0, 0, 1]]" + ] + }, + "metadata": {}, + "execution_count": 24 + } + ], + "source": [ + "direction.train_targets[:10]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DQXtFrekuNs5" + }, + "source": [ + "## Building the neural network\n", + "\n", + "This network is very similar to our original network, but we need to make the output layer have 3 outputs for the 3 possible directions." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "o-BrV7eZx2yh", + "outputId": "0fb4092d-8e1a-40c5-e73b-c0549e9e318c" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Model: \"SimpleNetwork\"\n", + "_________________________________________________________________\n", + " Layer (type) Output Shape Param # \n", + "=================================================================\n", + " input (InputLayer) [(None, 120, 128)] 0 \n", + " \n", + " flatten_2 (Flatten) (None, 15360) 0 \n", + " \n", + " hidden_2 (Dense) (None, 3) 46083 \n", + " \n", + " output (Dense) (None, 3) 12 \n", + " \n", + "=================================================================\n", + "Total params: 46095 (180.06 KB)\n", + "Trainable params: 46095 (180.06 KB)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "_________________________________________________________________\n" + ] + } + ], + "source": [ + "net2 = SimpleNetwork(\n", + " (120,128),\n", + " \"Flatten\",\n", + " 3,\n", + " (3, \"softmax\")\n", + " )\n", + "net2.summary()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ywp4SYTXhjFc" + }, + "source": [ + "## Training on direction task\n", + "\n", + "Remember that you can recreate the network as many times as you'd like by rerunning the cell above. This is a harder task than whether someone is wearing sunglasses, so in some runs the network may not be able to learn the task from the random starting conditions. In those cases, just try again by re-running the cell above and the cell below." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 457 + }, + "id": "SggePWZ_yOEK", + "outputId": "f8580238-1f89-4d85-ec35-4ac362b1d87e" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2024-09-19T19:06:31.589813\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.7.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ] + }, + "metadata": {} + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Stopped because accuracy beat goal of 1.0\n", + "Epoch 89/150 loss: 0.0013284339802339673 - tolerance_accuracy: 1.0\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "execution_count": 29 + } + ], + "source": [ + "net2.fit(\n", + " direction.train_inputs, direction.train_targets,\n", + " batch_size=32,\n", + " report_rate=100,\n", + " epochs=150,\n", + " accuracy=1.0\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1wr0xbIMhtLT" + }, + "source": [ + "## Testing on direction task\n", + "\n", + "Again, let's watch how the trained network classifies the images. Which hidden layer units light up for the different directions the person is facing?\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 421 + }, + "id": "QOmaNAvuyaAb", + "outputId": "bce3c4ca-b90a-4c96-dd66-fe90993e76f0" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Layer: output 'Dense'\n", + "Act function: softmax\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 3)outputLayer: hidden_2 'Dense'\n", + "Act function: sigmoid\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 3)hidden_2Layer: flatten_2 'Flatten'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 15360)flatten_2Layer: input 'InputLayer'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = [(None, 120, 128)]inputActivations for SimpleNetwork" + ] + }, + "metadata": {} + } + ], + "source": [ + "from time import sleep\n", + "for i in range(20):\n", + " net2.display(direction.train_inputs[i])\n", + " outputs = net2.propagate(direction.train_inputs[i])\n", + " sleep(1.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "n_i3mcPmhzpj" + }, + "source": [ + "## Weights learned on direction task\n", + "\n", + "Next let's examine the weights and see if we can understand more about how the network has solved the task given the pattern of hidden unit acitivations that you observed.\n", + "\n", + "In order to solve this task the network has to learn how to recognize the direction a person is facing. Perhaps it focuses on the profile or maybe the neck area or a combination of the two. Let's find out.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 273 + }, + "id": "3Z8M_FxxyiUc", + "outputId": "fd7393e5-ad53-43f6-8b1e-cdec9af6a693" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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\n", + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAEAAPABAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/AJZHurezE0p/0hPurx8uTg89OlLYTSR6dNDEMKzMZP8AZUgAj3xSRyGYqVufmGIFGzqvYVZju2iC728q4OdzY3Y9OOnSoys097cR3Lb5Y03ngDJAGOnsan8q5ZAY2/emLawwPlj7j3/nSyRyi3Qed5IRQXG0NnPAb8cHj2p01vBLN5lr92X/AJac/Jj2PXNSSi5FtAjvuCtlhgDbyfm9+O1JGxCOxh3IWKsd2Nw9fbNH+iWURkKZJO51yeAT8oz9DSTW0L2olWLzI3+9PuI6H+7+lAmMA+zNb4jRDIqb/vHPrU0CfabQtcpuXzPlTONq44XI6/WmSwgRKEtPMZSQE8zGBnjn6VTtp1dyxXap+8mc7vTntVu/IlaKWeLdE2ECbsfNzzke1V5bi4jYLAMMqYAyP9V6c/8A66kDzpd/Y4F8m3X3Dds9+etNbbdMjb/mnzlcdQvX9KS0hWCaRkjwu4oJd3Q+mP1zRd7I5fOn/fcbRJ93bznGB1/+vTIii2UX7n/Rznzl3fd5+X3PPpTZha3UjtnymTA7t8xHH8qcstvZw5/1kx+71HP8uopYonu5/tKNseNRkYzknOR+H9agMaXUhj2Ydui5+/8Aj2xVomeOFz9+F0LSdBvPTHtwBVaKZfKxMNu47YTnOz+6OOuPekYrDaSK9xgsxyNn+11/Gkt5PPWJPMieOTPMxy/FS28cKtexgkxhXcFCM/Q/4UlnZRtFJLvClQXQA4weoz71HJDPLcpZMsY352ygHJwM9f0qW9FxH5RkjKyMGWWWMEbgcd++B60yISsI4FkuEZnHzsxBKHjAP+RVhleBJUBeQE7d0xzjB7n054o4kkaOHzY1/g3fKD69KdFK9wnlylmJbhoOQPqf7vrT2tzBPHEkzsGwx3Nld2cY+lNjDSXohMfmfMwkDLuVcZx9B6fhU9uk0Nr9jmCsn8Lrkjrk8mqCb5441D7pw+WfOVx6Z646frViK4vBKYMW2E+bjODjjmobpnKGQSzrcyE4CNiMYOPr0okdonaeK3VYJcYEiY2Y457DJqwyKttJJI05YRHKOflVsfeUdsdjVQSOscQTDCQDEknJ3H+DPp7Vaa4t4JozFIZQM73Yhvpk0x3aKSQssKxJj7gwyZ9PTNU55ZFhVbWUNG0wkY7ssD3zjtU0RllcJP5bgLvCDJJHTofrSJI1tZW8bIA8m7Czjjg/x/0p64KSCSNFdxlgFwwA4z9eaz3t2a4GLgBP4WlfgHPANX0jMTKPtSRqVHmESYz6FfqfWoog7iKba0ccec4GJGzxx6/4VbMiNcO8c0TxYw0W/I98D0x+pNMP7xtkSWyqTlfNGDu7AVFIGSVInNoXBO/zPujvzTorG3iS2VvMcJu2PDgg567j/KlaRLe380GA7znah+ZkI/iHc+tJbxLK/mK0saffMbHAx9P7tRhWF1bLFM8uzdh924jI7mpnV5kilkmlYnOUDZXr2Hp600NPtkkkaBUiQtGASHwOlFncpKHeRw0RwWTOW3ew/KnxqhZZJJ8MudyK/T02j+dL9nW3kYQSyeSqYVw33364JHB/+tT5ZW/0fejll2uxjHIXvn2pWMjMPIJSa4JKv0GB03Ec9Kil/dvIrz3TQQ4w6PnOffp1pcAlp4THGChxFnBx7Ad6jPm71ZVbeyY2gHOD3NMiunEzJMIQ4OE8z7ox1zmpryTepWESOZP4G5VMeoHTNNaZpI5pJyVZo2VVHCge4Peq0IDxCM+aqRjzctx8w/u+3pVttLIsRJGygj/W7jw3PGOOfxqOb/RQIGkSQykCR3Oc88YP40JLHFbvG8MQ3sY08tfnORw3096ikdoRHFEref13OOdmDxnrjNSpcyyCMz2yy9cboyxX65qEGXzbhpGwCBtYk88evp6/hU5id0BEduyeV8uFzl/8arJIDfQxzRjcAVZSPkwAcDB9805o2NrIYvt2RjYG+vNEY2yxiOACQptY7PlX5jyfTjFSXOx7tBk4jhBPkdnB+99PekdFkICFJGnA3s/JG0cc+vrUUdxdRTC1V8RL91sDnjJ4qysdg8MUYbdJtCBsMMt9KtNbyxNEPL2oyCKQ7gcr3NQWMMTzNLDL5SnG1NpbHB7mmyn7KzRSy+WApVG25wTzjH+elWJLH93C3+sWWJQx6depquumR2FwjSz4SQsVTZ1GPXPvURhjZXLzeRImN52bt2en0q4EeazjKJ5URnDLzu4AwR+eaFk3bmC78yG264z/ALP/ANepvKnS4hhaLywoOJdwO0YzjHfHTP41Rmt782fkqnP8Zyvzc5H0xUdtG7xm4lfCopO3H8I5LZ/PiiG6Z2kuEm5jzhNvWMc5z/k09beCdWuJn3mTDhcEbc/TrTlDmYqj+XLH/rTjOc/d/T0qjNcBYTEZfPlZ92du3K9P51PNIXhhEfyAxLHnrl/7n/16s2lzckKk5zHz+64+f8R0x1pXEF5L5TWeyUdV80nZ/j0qs88BvUgMGWjwitvPBBwOKs3CRPOjGbypljAZtpb5c/l1pU32skv2oYU42L6+vIqiQbkFjLsUKT5e3O0d+e+aktmuXGI22wwjcOAckfr0qWWKKWWNyNjMMvLknBPt/nrUtnLNdR+RE2cffnwPk7j5e+elVis0k0n2ebEjDBTaOmB3P4U+C1lhkaS4G0NGYgcg7m9OOlRRwtHO7eZ5aRHLDGdm7p9c1Zt7YyeYrTecyY82Pbtznpz/AIVDC6SRqsr5YIAEx/B6ZqxJNK9usUX7ocIE4bIxjOf6VUhiti8bw/6UsefMPKdenX/PFTIGv2jR4/LEYLIu7O45HGe2fWpy8NxKYDFiaOLaDuPGONv596pSQeTGWLchyQmPpkZ9quvJeDUJP3O5eM/MB2pyyTK0rSLvn8oxNHkDAPOc9O9QrdQyRG1ZcsRjysnhumc/pUkdjBHZieRvLdcjfgnbzjGO/pVhXgS2jigbET52DB4weetY8cr3cslxIN7GMhR0we1LcCOVI26uqhSP7pFTWwkaJmA3FMbY+mfU5/WqoeS3ctCPOtH6rnb09zz1qW3tBPFIZX2OVJxjPy0jeVBGLa2t/MUDzGO8j5umeahnjR4jBYR5R/vnP38cjr0xzViKVmiSOGHa4YGYbs4wcjr6+1SCVnmdXPCqWB/ut/d9/rSvJapGB9+5J3BeRkfy9asakFW2uFVcpHtwc/dyR+dQxiKF5VWPCIAzvuPy9wcd8c8Uy2n8yR2++kjmJW6ZB9qJ453iki/uthY+PnXPAz2xjPvmiOLO2Hy90vO+XOM9xx+lTNHPhhcHaHkChOD8+Bjke34VC7IrIjcSeYIWH+z3qw9tHII9o/dqWUDJ/eYOPwxj8apwajvtDIY5Ec/6x41wDzxzmlkmjZesMcq3HGMDdH/n8KWWKOeVPs9xMk2QcSOAhPoMds0lvbTrJJEGh2HG54CcevWrmoR7LiHYTHG8iqDDw34e3r74pj+VZXOF8wsy7hJNjls9CfTilCEspIWdGJd9nzFSe3sOOPxp26Rb2OeQ5Rs7gueeMDFLL5klwZIWhKyYXr8w7ZbHb/61QjyVuNsibJYj5jOAAGUHnB75NWY7mOfTmClOHYkSEcguSKyYLkkfuJFYn7qytnb9AKuxQxvKZJFaFQNmyMbRnr09aZMLd5WjiSUSMhjXcBt3f3j/AI02C4S32W7BzKmVdgOMj19vSs+SKSFURJWklOf3YbK/l9Ksre+Qwj8mUMxy3mL1HcL/AEFWFuPMlC2kMLBhhzIuWVu4OOgHFPMMlvEP3Kuy/cEK5LZ659aauImRU2FWOJWX7yn+H9T3pqCJZX2uSWYxkMR1pLpClxHtiGFX5nK8dx1/KiZJ2hRLtwinO7aSC3+9nrjjFWZIfJSeFMlXXa0kvVsj+E9+pqjEGhuo0UqixqGG/hWIP6mpprua2mGIvMEmXJZSTk9QvtU/2wolzKIY4sbfK81dpPrn/wCtVN7ktEkSvKziYTK7nOQBjk+uauzKZFWRIk3+WGLMvG71+tQPLchI5UCbIs467WY8Nn15zis62WVd0O4Rj+FZyQD35p0rRJfPGqFmkyuZQDsYnt6AVdtzEGJljmaSJdoMSgrx3+tXYlhFrgTL5TfdMLDzGwec+uP5VQkvpJ7OJtocx7mTaMsD6t7UkKyySJNeszK4G1Mk7c8hiD/D15q7aSRrBcgzRoTJtQxtjIHp+f61Erxy8SNMhTo2cIM0yWOS0MaROGVkwzAknJJ7+uMVLHZPhZ5GLE4UhznCdefaoZrMNMY45NsDcmRGwo79enXiidoreNLi1tYnC56Rg7u3GPxqmbySWaFv3oMuCF5xvzgEj8K1YrS4hzOxidtm4jkkd8D3pNu10mjiikd8l49uXJ+lH2VLyRbgKtsjfcyNj+h/l+tVruCGdgjmdCowshwAG9CfX2qssE9sd8TFdpyckgyn2/vA/rV2O4ERTN0ilM582TBOfSpY5V8hnMcJkyCyovL4PG0d8VBdTBvlitxG0hzuZMEE+n+1TJYJGg+z+c6sUDmV3IXOcFSfXvipprTz2CyTSHf1O77uP5Zptwnl3BQTXMhxmMM2VJ4/xqtcJI8iqRubYCxjydh9/TFW4rZCiTvcEtCg4kf5Bnjmo71TKm6bO49PL/1a/wC9npnt70kEKuGUNGy9C6nJQ+oPZcfrmp7uOcxw/Z3LxALExjJPPqcdsVEUHlwmWR1RdwlRGwBzgHHv159arx3Fpcam0cqeZjG1skbvl5+lE6RyXH2pE3sx8zy84wM5zmrEDAwK0cnl77jaw256jlf/AK9V7EOHENsftSx/db7m3OSevXP9KbbgQebvHlqR5YHXO7t+lakXlSxM0o2Ilv5atknpVIRwQ2ryuu5I33KMkdcDd+PHHbFSeTMt2kE0e6ObOUyB0Geop9u0E6+X5+94zvLbCNw+nbrj8Ksxzo+4tPiAfu2XZ19Rnr0qlLMitP8AZJPNt4yM2+NuM/7R565NUEvfOtCls+yNPbPl5Pv1zWkltZxxb2fzJIFLIcEcjkH86t204EHnzS7o3HTbjkjOOP504tCkC3NpHmXJJkyfkyemD17iqd5ciF4UMuAmfk2/dz796ZPIzW8sksfysSwj3dJCOuf6dKqNdubWKZxzGwUL9Bmql9NARHJLBnfnHznnGKvWFwTbK/leZLGfmfdjGT6fSp1nV5yGTzQn71nzt8vB9O+P1pftcNyJUWLcB8wG4jdyOfbr0ps8lxHcxmT9xDJnHRug/PrVqdEluLOUSeSof+7u3HIwPboeagu7e4gS5ljbZuZsnAO5Tz+FNglijt40vosRTqNzbjyAMjge5pbgs0DMItltJ95d2duDx79aWyCRQTDytrEEuN2dqYGT7/zqykgtbHfv8uKRsIMZySOD+Xaqkhjjy11J+4nA2nH3sdenIwazoEiDzHzfsttNtwm0vux79Rz/ADq0Gt3RVkj2vGu8DcTwP4//AK1RTSPFbCS3feJJcGTGMZH3MH+dCmGK3mkUbSu3y5OTjnnirKxTtGy36ZZvuDI49Tx+FWW2RpCwfB2rAeOi+tMmlgRRbr+8WUkF+RyOen+etVUsLvzy0cPluv3V3A4yOeanW1iWJctuZTsHBG49ce3Wka2HnCRuIUXLf7JH8+KphvtF1cXCw+YiELF82M4+U/y71c0S08uaaOMeXjGec7ev51d087LRUQfKSXI9E6Fvz7VdhsIBJLcQp5iGEllyVwepbn+VVbK52tMzP+4jbDLj7nJA9zmmQjzkAI/e/wA6528jtoHlRZ8SqTh9h+96Yqo0sjIS7+bMI9oOAvH/AOui7ut20yNutX/1rYxvx046jB9KjsJo1TzpTufcpHbjPP6V0UUwnhPkt5dtt3DjP738een4VNNcSJDExt/MyoVm343cZx7c802CaO9e5NxB5rfLvbftz6cD6VAvlrF5jS7kdGjI2kYJ6D8cGrSCUWKpPb+bApDK+8LsXHHA5OBTZFt57N55U83yztj5K8ccfrTUsI5Ht8DfFPuwOmNtXrW2SJJJE4mdTtb09OOnUUswAhBmk3ui7xxjLjtx/wDqqoZ7VRD5i7HXczvknyi3PT+LOfwqqbNbR41dWl64GN236+lNQieY74JFkVti7Uwreit6j1FWGjAWOPbDgSh2UD5Q3dR7+1ILaK7aRZFaCHjjAXH17dajvPNDW85nVk8wDKuSpHf+VMaV3mUyBlTfiPAwpPYn2qa+hL2bSBo49gGGU4+YnnB/KnC5e33W4Fw102OX56c8d+lO82OHNuA7qzbUnPK7iOPm9R/SsyS5e3knt0eSeVywxncuTxirWk2Ultva4biTDFCfunk4AP1rbjieVAUhNs3/AC1YpsLem31x3z61Pb6c00BWGOZBGCNzLgEdSvA9+lOugfJWKEyRbF2yY+XdgcjjqKrQW0UADzq5hbkBQPn9d+eDgnioZILiKFpIjE7rjiLJrnNRuVurdZPLt43Mo6Lg9D1rI1BjC6SJMqkxgFS2PX5vp71kDWEnsvJldFT++px3z1zT7O4mN9bM3MLthlTOB26V1UFypSZbcuAiMGRuxHUgCtC0nuDBEzCEKDwtznJ4649PerMflqpkuENvJ/djGwP+B64pP3Rt2tYbdw8fP75Bk5zzx3Gf1p9vdGeNbbypNqfKzBflyODk+lSwskAkVxBJGrE4HK+mD78CoNvnSyPa3IHl48pQ/wAq569On4VVu5Z4Zwlu85mjXcyknaBnPQd+lSxTNc2/2i4MqBDjavG5gM9D1FI0Ed7BuQyLJnheBu57jvjHFSTupBEguCzfdaIdMepqcxTsjSobfAXcypnf6kgf3qi0+RTDK+1MLKW/0oc7uP1/+vVpdlzDGwKAc+d7c/L/AJNY8CvLbLDIuY0VihUcEk/zqxHHHLEMGSWeI4WJTuHHqOvXig21xJalJVbcZWbBB2KDtwD6dOBVhpLeeB/kuQ3GJiBuHPZv0qreG4XzCIP9HB2oEQ5V8Z3eg781DFGZnQSw+ScACRV2sW9Sf61oWMc6faHykzqQqJKSxbBIyB345rVtI57i6jdpXIbPBY7FwO/pmtyytJREssbXAgIJJkJxnPX06ClECTXu5o49jDy+nDHP3h6n3ps1gtys8OwxrGQqkjA6/T2qodPWEwAOI1+bzvMOP93PH864fUNMa0LedExjZdwIX7p545HFcxf2dykMk6xyTF8xrvUsqxnJ49D+lcTfW0kchlSQiJvux5O1cYzuHb2rZsNQeWG2SILGA2fNl4BOcjnNdX/qbBJUnUSMQXKv9/jkL6g9q17Ca3eKMy3cbKVBKvIC6tj7vsB6etaSD7ajSzOoD42c/dxwcZ6dKVUmguRM0iMxQiXexPGR098DvTFjkKyGKUCOSQtiNvm3Hsfao4TO0zq8DBF+UgIcMRn5j6k8c+1WC9lFC95FuQHG1F2hTzjpVVjHbs8nmSuWXyy7Nndn0Pf0/CiG8IdFEauDhVJXMYPYt/tevtVma3KyPKZViBC5aNtqqcc49MmltN13sSM+aYs+ZxtznpVjzI1GIRtwm4v13j0wemaqqsbBhIfLLybhHyd+fftmrQWKCC4kiXfE+35ckdDVK0t5I7OIrN50LttR9u3Bye3+elLBGLJZrmCXfOZWjZtuMdyOeOo61e89JbbAuPmfBlGz7p/rznpVBPMjjjjkb9xzmXH3vw6jnirjOLoXKRNst1O4S4zg7Rxjr3zVONJbk7hNmOI4HyD5yP5Zq5BGYLqKR7rYz5Ij8vO3j179a2dDDbo1lTJlzsXPpnPNdIJYrWw/s6S4+ck4Gw8Kc8fnUaw5eArFtijZcvuzkjvj6VJcTIhmRU8wkgnnHXmsTV7q3Jnia42yfLuOwnb0I+tYOr211cIQ0/7tX2P8g7ZzXP38SQ2htvteRjcD5f3eCMe+K4XUbG1RjaLJ50x7YK+h/lSabp6R2qxTpvjjBw+cbycnoDxg4rZaeCSOCJDtjjC7jydpHH41o6bb2U52xHzJN5Ynkbhj9PWtm2jleMwC42iP/YB680+aYpODLL5kbnl9uMqOpx7U2C6VDIIW2xFyVbGcejYP8qkt9WVJ2jmO4EnMvTI7cAf5zVVy8cEUUtviJ87Y9/TByeaEDNZKjjzWD4U/d2nsPfr+tJH8qeXu8pRJycbv3nr/AJ4qeSV7m3W183l8hX2/6zB9O2MfjWnbukENzCkW8xbc/Njbnn8asyxKUZ4rvbtlwG8vO3/Z9/rVFpfLW4W4PyurDdj72e+B0zUttdyw31usk+2MbvMOwHtxUOnyN5LIs/zQDCzbPvbsnG3t0/SpJJDb2uBNuaabM3y4yCPmH/6qsjykjjjifeGUMqYIwe4z9MVlx6kJblZZPlaDPmR9fvDjnFWJIJH0qOIHf8528Y3dfy9KqG6aCaIyDMkICbvQD+DH9a3rKZDCZP8AVyyfM7dcg8jj6GraeZJeDyY/Mn/5ZfNjZxz14OR61tRhpQzS/wCr8syfQg4z+nSqFxe28Vwlxt85lURhMlcDOd2f6VOdQEsTiWXIYKVh2/cHX72Oc8Gsefyyqon7qFs7k+9nHTn61C/nypJLcQ7YvKIjTcD7hsj+VZUy2pCrBJ5TFcyDaW3t3Xnpn1rjNSsYJZ5rGK4+ebblNh4wAetSXSbSIYR/o4B3j14yOvPWo5niS0gVm8pRIrKcFvmx93/69XIHRdOeXzsfvM7tvXIHFbpnsWWG6Hy7t25fmO7t+FQfZI0k5fcGBycY8sjHPv8ASr1vBKYP3fEcbbg3HzED7/t9KktX2SyxA/PId59zzz+lVPLDQmSyi2f313Z2+nJ655qaOGR/JjJxsHmrHj+IMcHNQyK6TDzP+Ptpt6j0TsfTrU848qAXKyeVIxO9tu7BBx09+fzqxbTgvG6x8PnerL8z46cd8VEt2WkjR3jWNrwHGcMw9fdfetCRrPzZogqyqqNMzABgB/dB9faqpi8q7e5kChHxtEn3UwMc+maghtni2NHIhiZgBhuWPv696tT2scKhpHLTn50jzlc9sjrjNVnvZgjSrFGHhADJtPfj5R+f5VnrHHc+SzfupJM5VPlLY9fWr7MXwRdRBQhKrHJ7noPWs62hMly9w/nPHFISwfncQcmtkahavCyW27z+MI2MfQAHPA/KtXRjIJ0eSZFLZ3sWx5eAcfTP61YuNTB0xI7Vn80v85J6Dnjg/d6E/jWHeau8dr5jW24xybWKJkEAcnr0rPtvFlrJJMg85zkAoMFx7AZ7fyFbtlq1hc6lEpR5F5zEgUt09M/jVbUpLdQBHdXAXft2NIMheecelc1dagLaSaKRkaMhiJIjlgPrn71YaKHjF263JlT7jKOXzwfripUvY5d7PDJHCv3w64cg+lOmuLV7izEMSyxFkDB1DBRnknHQ+9TXFxE8jxNEkdsneJQMkHA9vSthNPjulRlMqE5+Toi/hjjNSRx3M1w5t4m8thjM6nb+GK0pFWG2ZGl8uUW5GwNgE46gVTjtZZHicO4JjX7hO49aubYBK/l5jHG9DgY9OKz3LJqUu5plCQ5AU4yeuPpUkLNJKbqbYQse1fUHqN2f1qR1L/ZyUeVJC5dVG5fbAomlxLs3KjJ1CHDDP93+tQgBJowsBJ2AgzpyPpUqSSSJOI0SNMMHcjBLdxn09qWW9nF2sV7HF5AzkhTtfjtng4OKS3uZbuErDB5ZUhlLIQvfpjvVuSNmaEbmecbXkJOcDuB361DfOiEb08uBxhyBiRiO47Ecj9aoQLJthnEQkKbtrIuV54PNWbyKLz1MSxpsYGNFABYemO5JzxUGotOLNkijaJgplYIpU9Dnd/WsdNRWF4GZQpAPzoMEHHO45/KulSSGbfKLidVkxlY3AHHHNaSJtSSSQorRoRIkfAK9SVHrjisO6nWSOdWZkjYNtwcDb2J96yLbS7SOSGSGQC4kLt94ZbOfQZ6GtOzmtLKeK8Vmd+dxgIPbH9aztS1COLUl3ynzXAXY7cZJPQZ+7WPdyyhnkYRSIXIxHkhT7+i0+w/f2Bm3zRqP4HOCOSOBT7ia4u4mQ2KRxspzIISO3rUunxW5MIcxRqCq84BJ46+1XrkRAyCJbZ8cdAVPP86uW12LaIrGXlnTpzuRs/qeKtI0kJdEafylwp3k5Gec/wA6mkuAbxDJEJFW1Cglc856mned51q3kzQwlTjezbSvTjI6Co5mX7M1xC25nxnec5wcdqrGeK7DyysULKS3lkBgRx/3zjn60unjzZWSJtyAHcshyWXjkDuTU6v5VzMj3EcSLjCb9rKCOgHb3qOWNLqSW5hbBGMyY+726HrmpXmSRo7jzMAAQiLb0frjP6elT2ltK3mrONlu2XPQ5b8OelVNslzfQWV1H5kg3cZx2z2p1mk9pHD50+1S3I2A4GeelXEV1kmk+0YlcExrs6xno2f6daivjFdWPzHdJCAGfkYyR298GnNYyafo6FLv92M5Hl/7X/16yfNjaVZGv98sZBVfJx8w5FbDzWv9mNNK/mTTKYnbBHlgrk/XB/OuOlW3eaSBF8yQsQqZI3gHg57cc/pWjYzI8jRR3+UGOPJ6da65XRrWaSWLaFjYO27O8dcY7cfyrndSSGcxRo/2fzFGyPBfzFOcc9s1lKJ4bl2jTfJFhIuQNuBtb69O9PtTFa2Jlkb5R0XB45qlrn2CeQXP3LlJNgb5jtIyduOh571m3S3FtECH3Qyx7ycAbmPb1GR3pbeQ2ts0El357v8AcXy9vQ5NbV6Zdxsbeflfl2bBzuwepqzBYW7aejvbZuI5Aj/vD2HLdcde1Urhfsc28cxZ3Mn1yOv5VcgUqkUcJ3XHO49N358DitKae4ZZmWL5ZyoQ7hwQCP61NCH2IkkX70IDJ83WPv8Ar+NVGtjN55hOy1LYfvgg9Oee4qYxO0UVpCNsgz5nfb3HXrVdLXZaB5E2b22F85+X1xUwtZtPMVzDNhGwobaOV69/pTpf7PnvIXlbzC+7z3wy4OOOP04qQSxXYkeT9zGmNp5by89frn9KbceW8MfnXv7t5BI37rqx6nip7NUsbKdxF5iyuyg7sZQjr+nSrceqNPqEIhi3Q/NtG7GOOeo9aqW8qQNcLLLvkfb8u3G3g8+/WrkNpLeI0uPMCrsU5A2ns3v16VUna6iuYFaXY6blZtoPygfKfzzVy7fyrWSe4beq4+TGO+OorIVbG2Xas+8o2N2xhuHXb7fWtEyS3aRwum1dgMAyDtPRW9/oawYo3j8QSxXA3RFsO/TJCnPA96l1G3msLWAqv+k/N5ZyPl5GfY8GulG0Wf7tdzsCpkzj17flWPcW00gb7XLttwcbtoO72wOemaz3sjb8Wkm2GXmRtueOq8HnvVZxGypcb/Jit87XwWxu4PH1rIa7M8+1IfO8o4eXdt2kHkY7460lvJJd3U0JfPyNg46LnFZtxL5DR3qzcPnMe37+OOvbHWrujtM02nzPDucsxX5gPMIP6YrotV824MPn3XlEEHZ5YbA54yKy7n57tIfMyix7un1Gf/rVtW6TTQTRzfKZtvHB6H2pZ9UZNNG07J2JDd+OeemOOKuWbuBBc7sl4liPHXPNOl8u9k8po8Eu0Z56bef60QWjtcqQ+PM/jx/rMD07YppYq81vBH5gQeYp3Y2DA+bnrg9qB9suvKGeI8Njj5iO9GbiS9dXbG3Gw4HynHP1pfKWCF4IkeYDG4kbi/fj1xTXNrOpURzM0Z2gKARgfxfT3qyjwwWkkX7yZ3jLKq4YJkYHHbFVLqGWNIXtyYhDu3EZV+enSpLFftNu023c+CxJGTxnrWlaXEphFvGCC0nzFM5Cng/jVG6QRXTl2m2BiD5x+Zuf4fUVoWwDwvFNtuCuNyj589+9ZLSWyPCVhBV8DZIo3lsnnHp2rSVS97bExyxjaoyowoGen1pusaHcIVvrbyzEpY5OdxJIB3YHrnFLcWxubCzuJYXeeLfviC5bk4GR16c1btop30Mqoh85QzEHO7Zzz9c9KyXMpgW3ZHz5wcvKDt24xjP9KrX0phVY8wkvxlPugDGMfh196wTeCT7SDGFhfb+5YYYY9F6deazGtiJZpLdbksu7KqPld/U46g021tzMGlLzRXC58xIjtO3uAOvWs66EqwvAFikdceWACcc5NbOkg2f2ZS6SQo+5CTmTg5IHbk8Y71oSXElxNcSyIwj+bG8fdHoPRqr6bD9o1QIN7Eocebz8ta2nQXBimW4eXc+3ayk5XGc4J6Uxom82ZDEvlsu1PMXk5HJX3qaC3u4HEbOdixb0GTgY6D/eq3DJH9tglZtrBSCinGWwcsR78c+1W4bqR7hkdI0lTHloowRkc8VUSe4kh/1UaPICGkVSMDpgn8jj6VM00sAhQJ8u1V3oD8x9Ae9F04kuIUhysjA+avRwQBjgfj1osnvIHB2RMX7MCfLx/LP6051SBGFoAZCSJA/UDv07dKgEcsMjMJYRI0W45Y7Qp7D/AGvamyQSEGBppjIOhDcN3+b1x2q1FaNFGYrZ5lXG5nY4XA7ZH1/SrMc8n2cpAkWf9WzKP3gbHLZH8Pv61XgjkuXkil3TlTjc3zMDz0P4VoJ9lgVphcKvvG4HtWdNaWsSxTJcJM8Q+Xa4buTk+2auW2pZkTEfmOqhsbcr+HPWtzT7xtSt5bR0ijXOXDjHUkjH5c1Lbwi8TzXikgHZwu0H6n8KjtbIOrsrMvBQYOPfBrHubSSK6lNxJEIFBBG7jr2zxuxWZJZJI7KskLxPzHubLgdeP61xd0Ll5TMPIjT+82QfSp/thgQvIrf6zcpgHyt/iKhe/hV3RFEVxcqcNKAuN3oeuc1Et5bn97JaqWi6tDGMnPrUVrua7ij2yRspOxJBgg47j681upA7Ha+4fLuIPRn9fcU+3ZrW9S4eJUKAqQq4JX1+matQzSvbFLeVGZfuZYl2556dcVM7Bp0kSaE7j8qyNwg4yQOxzinyrP5LvvJZiUU5OD6YplvCUs5A8kJmLZ3s3K9OM9R3q5OrzNFceW8c0uclBgjHHHfpUFrBcQxnzZEdFyzYYn/vr8P0xUrN9ps1MbOCLgADPTjqPb3qUW6pKbnL+egAMjn5CcY69f8A69TxAoqSTzYa4zj5fu7fpVIPC082258q4BaIr5ZbzDnr6DJokwt3bRyQ+akirEx3bcOTSCSXYlyybTHnecg7s8D6Yqza3jsxVW3wEYPGNuf1PenCKRJ5Y7VtkhjMmcZyM9OarRagbRjvb7NMWIIxv83HU9MDqeKc4huWiitzstud7cnPp1561Ey28UMSwSeYHbymOCOpJzzV62iNtEJTHtKybc7s70Hf2z+dalj5pb7RGm3zCT1ByOcfpW0b0wRtbumVbHkx5645bn/GqdlfxqTb7s7wdr46SHgcY9Pwqre2k9zazxSRbiXb97uAwcHt+tc+bWS1WP8Ae73yyxvtA27Tg8d652+s4THCkkuyE7vNO0n0x79fSqeooi2OyI+VHGdqNjdvYA4GO2azLiKF/sjXD7rsbDGmCOO3I461MbXajvG+yGTHyYz09/rV9YxPNa3sK7pNx8/nGBnAPPtnpVlJ7gXkvmP5uATGuAvGeOa0pYlurQXCnDFFhYf3SMMfr6U22jto7Z5reTBjxtl2n5MnHQ9c9KeYLeOEXRjzHn5VyffIz+FSm5jOwef+72B0TZ9xuwz3xVaXdHNt37xKN7cY25z+ef6VpyXUsn2c58phu2Jw2/157YqTY3mSRJHmOVeTu6A8Fvwx0pkcccEDIkm5o2yRjHAFIJ5PnjnG5Dhli4G4HnqOnUGrYnkvo23y7c9Y9udv4984zWbFDE4KqfP3fd6rhuxq+iRwzrBLDkG3yfm/izjFQy2enyyCBXzJ34b5e/40RW6QvGVi8uWMFnl3Z4yD0/z0oLpJcrILnfKsgkLeXjamevvU2rXsrXEH2cZxCG3cc5zzg+uKpvEYjBcA/ZkG7dJ9/Hbp/nrViaWYW4Mdvuh3ctvAxxzxWfFMsqSyY2yhyijruWtvSL6GK4txOuwRg7RknJIOelbMsiSJNOs/lLFtymzdtzx171zaTtD+7MXkJEd6tu3YPXH9c1ojVr2V4bbGTKFO/wCX/Vnjpj/69R38SSqkIbGCwVsdSD836isK8vrbzi1u2zyvuLgnzM9evTFZep3cEtmiL/pDAjavKYGDg5ridRu51vjC8eyVkxGdwPy5IC/n3rTeGTZCJxmBN2U/uZ9xycmtXRphcQGJeI5QVVP+eZ5798n8q0bG2nRikbboFk2yNgDnjK469O9aUcEcE5lji34GCu7HHr/KoBK0tycDZefwP1zxzx06UtuswnaHyf36DLPuHLH7px04FaUyiNYZZ7XfJtVTL5mOfTA/OqdzBG93FI8GUwSw3+vQ/wCfSppJbePUll8rAPUbj8/H6YqxazTm0EO7eNpBiwBtOTznv/8AXqL+z/Lmb5vmMRlYY+6PzpBc/vVR7ryiAAi+XuwMdfxGD7ZqQy/6IscSmOY53kjBHPHv0qO3kh5J+V7dvLCJgF2XvjvmrUNzH9pPnFWuHjyq8EKCeFweQ2e1QiRIrfeoV5D92TqRzzuP8quRFWnaByix+S43ScOckdT+eKrxQRJKX2PzJ5AyBhl9R60zU4/s86wq4+eNSS5+ZRk4C/rUMshMTooLKcbBMMq3rt/rTNXE0caRmYIhHMcLENjnJx/X6VXtpItsSRDO1gzF8bj/AL3t61s2QVb5p3SNhhfJXGVb5cNgd8e1T6hqUG2QB1UjHmrEQA3THGece9ckupTG5j8wOwwC6nJzz6Z9MV1NnI17HHLGIYuBFlvlKr7ejc0l0xFyEAlcwcKqDLSEjk47+tZE0R+zvDbWsTjjy5Xjy3XJ3EfkMVm3mkSJaqZVZiXBQ2oyduOM8f54rmbqw+z3H2idPMkMuyJZBkgZyM5H8qvLDcSW7MwhSRv+Wc2RjnuK1NI0ueFV+WNUnDZfBAixnkHHB/rWkmbeI28DxSlpMMVO5snjecdvenvukDxQsxmWIDg8MwYZ6c+tRRSJulM/ljZjEsXbPqT+VWklikuow29BuDGRMAkDrvPpzVyWS4VWk8lJ4Q+EAUt8vZj26d6zkNy9204QGJScqwOBnOBj2ols4o3HmTzM464cEfhV+OaRPNQQp8sZYFV+Y+/07fhS2wSV908kyy7MgbsAr6NntnqKj3QR3UpkSNpOMswBjUY4we3GPxqNrcLcbElmLfxea3zdOKqK0cl19og3qitlg/BLZyelarwGWS3ulW2UrtcnGCcc4qrMk/MCxDj7zxqdvr1qxJc/Z4LbfErM+5N8i5YEkcsf88U5nMkqM7KFRAf3Z+Xjv/vVDcSNfziWNFbyF2fvBknrzU9wkTs7rtVIseSpwA2fvYH+FZOq2bTRieK6YEjaBNJ84HPX2zUFlAbNNjyxPLOuAS2cbsf4VclW5isZESUeZwI/mOY8HnHpn9RUmrC2ZDGiSoD96ZQAD0x836VxcF26zuZXeSfGMRHK54wfXHStqy1a4FsEQttEnzdch8fXpXX6NMN4nnG/Kqd55OccgE+nSrVxpQXKxrLHE/cjBTHrxxk1TksrkwmBvLGBtRkzkdgD/tVgajojAhyY5MDawblgecsOP1qax0ZjeC4YRyK//LKYZbgEcjH41cu4Da2yxbwivkbs4Uc//XqlAbSASyqkxZlMO/AxnrkH0rU8gNpgkSNRcBV2tCvJHHJPXPXNQQxWk0y7bWRoBncixgl/TI6HBq2NLbdcRlEiY7TvcbU24OQDjvkce1NEXlCSFpZF/clVw2EPYYqqBHKsa+YVKEoyxtjeQByfU1I8duxt2RvnTdu84jC56b/6UsEXmzJKDPGO+/gFM8j65zxVm6iiAUqGEZYZfA3D2J/uVWEEMjNuxIkvD+XglAvCn2z+tZyXkkt7McZlfbs/Ac0n7uPzRjy8oSkPXJ7Nu/TFaNiTJaRbp943BT8mNrYHyf8A16RJ557aRbP96Rjc/C9+OD+NR3RmZ1R7bzY8FlfzAuQMZOPbNPhKIjQMm7dGZd2cbB024749adJEIYYltpNk0wzt253gYI5PA71amWC1tYFuov3vzbRuPPPPT2xWdd200MZ823835xzvC/Ljp/Osl2/0kfL90ZQ5/wBXg8D3x61KL2VyXK8DgtkfNjjPtUt1exSWXlRjznb7sfK7eeea4i3SWNVmaXy5gfn+UH5fX+VallfR4eKCHzjzK8m7bg8ZODXS6TqTyWajd5cMZO+TGcZJxxjPXiuqtNdjvLZhdzbAuPMbbnPPHQewq7BatLLcEncQWkUdMt6UDQGumaaePyYnXywd27Mh5xwf/rVVksYoZor0S+XG+d0e0nZjjr3zWBqQ82ylDyeYkKks+3GzPTjvWLHeI9vFAi4UuFDZ++2Mfhmtu1knPlwfavJkPyhPLDZAHXP4U6zuPs0bpCnmXUeMDOM5+vHSrdxqUk0aiUeSGb2bPbHTv6028t7qVLeWJP3ChS8mRwo5Ix16VThtyXeVU2L5jbDnOT3b8QRxUkFp59zLh/Nt5Mc42+ZgfmMH86alycNbvc7JViLiPZnHPXNTmctaLBLzIVDH/aTGM+1MRokljgtVw8o/ecn+EcDmofKju7raT5kf8MfTPHPNNgg2wz3EcOHXcrS7vujr07+tNiSeaWJ7qfGcbBsHzJ26dM1aSxdb64MHyTJtweu3I9+DkUR2iRWtqs0fmldx2btuxc8nI69uKFjkDh4I9imXCLnO70OT0zUR+0reoZ7fzJBI/lx7wO3PI/zxV9r0XzGKJ9qj/Vy4zn14/DFc1d3jxyQ2DDbKkisr5zknoMfjWbqVwyN89zibzMH5Pvdfk9Bk96pG8uolYwr5Am4bkNu2/XpWxpFtcPpJfHnj+OLhf4jjn9aw7vSVtJ2Mo8yMsI2PTJIz6+lUHtrwl5Ejzb8wRjcPlHb3PFXYLy7tbRCy4a2GNmRznjOf1rrdDurs+Wtw/lyy5+XAO7Ge46V3ekCyM5MaYU/u/Ly3JyOc/wBK6aGCPzh9mm8nEfz/AClvr1rmn82G5kimt/KjXGX3hvKyPQdc/pXMSGaHMckm3yo3aYbQdnykqff14rOQzlPNij3hjuLZA+X+/wD/AFqkuA8ESXu3LgBVbPr2x+dOsdGubj7NJG/ytuzFgfJjPfPOetX4p1tmNn5/mptIU7NvlA/eP+11Bx7VbithdYEVz5vlruPybcAfxf8A1qjSVlvY4GuvMViw3eXjbgdPeqsepGO2eDfkx4/e465OemPwqtPPHHelpbfbIzLJGu8nJAAHP1Bqe4IktkYjaJpQjDrtJHX3qGKJI7lEhXzFGRszjeQOeT05yaU70njjhkti7Z+dW+XgdiKlju2W3m86BUDEsERMbsj0PUVHDdSrGZTBAQDtRXQ8DqKWK5uovOuE8tpZcYifJk446fr9KbbXEjw+WGBnRWLeaeCM9B35449qit7uYo/2orEBOVj2ZUhu3Xt1oa8WRw88yxi3dsuHwWBwBgnqcg5+tZlpq80XmukG4xYzhCVbP93n86hnErEExiUhwTOFLEDH971FUZktnLxyGUuXL+Y+OPfNNuIwEt2gjaVFBDcbs9BkY9etb9pPHFZeQjJEo/1khO0HnIwaoPbvewwM5jy1yuST8rjGOPX0/CpZNJEfmiWWBYsEhFbBB9QMdaw3imEpWKFZYizBd6lmkAP64rY0Q3STwhB5m3ds83JcZBzu/pXZaddqpadZURypVUZsAngggevvXTWqMUSUXNwjNGGkMkmEPqq+/oKgu5ittcvMqCQbcq4+br3Brjry6zphnkiLTsjh9i5z2G7vXJL/AGh50ri5YRpGZAiSNtGD90j09q011GS8soIblokTI5jOOQvU5P8Ak4rS07WobQK0rXCMmfkJAznPQZrTuktpLhSq7WIKMYwATnHA9+OKTT7wWcNxCkcu4yMpkkHReBjP4dKkaVGHyW7Mw58xEzgnrzVSGGOK7lizbzJNjbt+Y8DmoxLb3U6zSKN2MAYHBzx+Hf8AGnpMqEwyQySgSeapjXcAegH1ppkRopP3ckUiMSMLtcbjn+VNkubRZtiCBvM6NFg+Xj+Wf1qOZmu7cfMocf3D/DjqKk80RRRriJkChSW5Ib+8fb3qKzRzPNPA+9xt8vzjkrxg5x0pjacLZIZPtmSCSxEvJAP0qjG0kt5JDuWSJiSBnLjJxke+KLuyu0tpwYFkZApiAQnKk4+f347d6bpdr9ncRJHPLH/y08wbmXrj6VN+6guUjBk+z+Vkq+Mn5jkkdMds1WkSCZJ5kiUklouVG0J13fX3rPvZzbaZ9miA8wgeUydTyCefYelJbxqYPMke5e2/iAOc88Y7da3YGjj09Wt4tyJLjZMuXU4zkAdP/r0TSJJYu4Ql5HK5kHKsR+gqkrOLNgtv+9h4V1Thcnk5963vMt7eINa2/wDpEvTKDK49ccjitXSbZp7pIo4LUBAMGdPQjp710ToFtjDNcwALLlgj/MABjC5/SmeQL4/ZpFkV3/1rSjDnHIwfw71nXsVnBNNb/Z0Y4BICAr0yAPc/zrlpdMVLi4/1SxuGm2Dh8E/cxj9KxptOnml8mFYlD8oACCO+DgelRXNpceU8zqxkfGMg7Vxxx6VpwXdzA4MrROrECMAkuW9R7+laIuPM3yeVNtMZjZVX+Pux9/eomupLezZfOEYzkhmwxHGMfrmpIbiNmje2SOOSPOFlGGOfYUpEKXD26QOJBgZZBgZA/Xn8qjeIwSoFmlaV3AKq2dqnvjsferUQLtlth2kgs33m7fN6mspNMa1gkMh2vxuk67OeOM85p4WZYxNDHhAm1l3D5V6luf5VG8UzIFV/MSZcBsAYJq1ayJaag0Bbluox14zTIB9rjHnJ5USsDvzuyvO7gVCGtUmzp8HmTed5atvI79cHjrirmo3ctpAyv/r5I0J6fKQTkeh71mPqEmnPOxbaJtvz4Bxj2x70lo51TY4t+dhDPv6Ju5GOPXOetWbOzSNblZRmI7goz2rFmVGvI40tfMW3Z8P5mMhugx7dPwq8lvAFS1gPmQnO1MEbu55PPWmWE9xcloiPs0jP93IfPAHWtOKJButLhedxbfnoemcD+VTLpskAkiZ8pPgq2B8wHI7+4p9yjRXL4HlyjG2Prt49e+a6XToCreekuATn7v3V4OferV7BG8YdHwSctJj73tjtV25kMUu6R/JuW+5Jjdj146dKwrh0lmuJkn8xpighbZjaRgH6/jVMWpN4Xlj8zcnlkZxg5+//APWrOv4RbXUU1p0DlCP9rDZ6+1F1DG8bBx5dtFjnrvz+owarG2tnjtQzeXPFIHL4J79MdPT8qekk9s8qAeYkrls8DAP8X/1qbNZjUYhInzLGxjcdMkY/xqpFJGLjzfMwT9xtp54weKvNe5lMnlZnncY+b2AHt2p7+ampo4t97/ZgSu8DDbvWo4pJmllWR/IZ2JjTAbdyS3PbFZz3EV0vmSyeew+6m0rn8afZvtQ7W2Mk2wLjO4j37ZrVTbDFM6DMhRneL1buc/pWbZJKjRlR5Wc7RkNs9frmtN3bKM67RHmR3zndjtjt1p729rPIZpE/1lvuTk9TyKp6lNaLaQw3EO0Ku1V3E78Y5yOnasO5CNGzNJ5u/wC+u3b06c0qn+z2eGNvsyqpU8b+Dzj9c5q3LeW8djGd2ZXwm/B6EelVE8l1cmb5v+Wsm0/IP4eO+RxxW7bP5jSBl/eNj9znp+P61jxtBBfRzm52TuQrL5ZOefXp2FdVHq0YtNsx+Tbjfzzx6AUsV+omEyjYJFUQt13BVwT7fjVaWdJZFG7Yo+/xnyfT/ez+lXYLqX7QBIcGQeUBx8+T+matNDlFJuMJ5/lldn3W+vfFVzrEUm+2cZeXGZORvxz0xxilsYbkIiS/Kc5CcHbg56+9Pu1uUiG47nafCjgfKe3/ANesiQ+feiyH3OXkf+44yCMd/rVG5wL24kNlvPy8+bjbx+tRWcFssRUNujKMsbYIyxPpWlb2sccIuDHgxcbt3QjnNTtBA9rcG9k3JMEdflI4znt+FU7SN0eFVvvKVN3y+Vu6+9WjIl5eyiSPZgYjk3ZwMDjH1JNQTi2GLe4l+YNuB2n7vTtTo7UT2s8QXyYZMBJM7twU9cdR0/Ws6SzAaRxEfs82Nvkr86Y9OwyajXy4oVeNZGd22uSMqrEckf7VTxqY1Gbh5Fk4+V8spPY+g9alvLpWiPlJHEfYbSP93+taCxrdSqEJVAQzl+Bt7j8f6UkkolaSIoSsYKx/ZhzgdM+3/wBas25817qLC2x2JgLdg47/AK+v4VUaKISzTCN/k24RlGw546VDdrEbhpFSaQthMOAQDjv7/wBKZLFmBQ8YJBBUKvfHFNjS3mj8oNJE7EiQsQoyD3rROoRxXu9kYMvZBwePrVq601fJWMQxs6IW85VzhsnALY9MVl2s89tqRimUSxmPOHBZRz15rdtzEXUFZGjkyVwAfL7kL6DP6UhspYjmOWJ0Xu7Elv8Ae9cdq0tP/dBGlEUjpIDzyQox0/2qs3eoQqkyyG3XzNzJsIBUnoW54b1rm7t/tGLq3jZIm6lVwV7fLjp71uQzOly0UcoeGIqplkbLNu9D3xmoJ5JHM2ySWQRyMVOc4YdvpWBN9pE8rxeasmPMYnIJJbBA9uc1pXkkdvHcB48sm3cSBk56VmME82REfbFGVYbDgkY52/564q/byCKERST/AOjT95H+dd3r2AA/WpraCGadopJ7h4U+VCHBGOevbHAx+NSajbef5Vzby29vGM5Vm2Meg5AqsFMl/JOqzxqpBKEYGABwB61O0EM9s0kySKTIVQ4Ac8cDntTYmZb6GBFm2quMOPk+7/P196oSM8IaCaWSNmxtYNhB34zQ8Uc6KiOYkSUKdx278dx6sfWrlvbwwhsozwkHLqAWB9SfpTRaRajHFG37qZc/KuFJz6j6CpJRNb2sPlpKyxElyATvGc/N6gdKrtIxJmtyybxgoDhtx54A7VVvY7h4ItzRK7FupIcDj9euapyyL9uKrLIw/u7sr07ioTulgBE4iZJQ+HfazqB19+ePwqxsZxHP58ZjyEYK/K98+31pNLmtTc3EMhgkbzDhjgjqx5PrWjZWkKi4uBLBKp27RcMGK9QfpVr7W0ikH7sjZ/d9OmMH346UyQJJHKY7bLCMqsmzgH0J/XFUrdLmJo97Td8lCdi/T0q5PqqXLpGgSONs/OvA/E5p8Wvo8+yGKIbY+Sy/eYH2PJqletHPdpP54ycEwo/8ec4K+napMqlqirJJG753pIcRxYPGR/Dn9TUy6nLdpNPBFHGkWDtCkdfUA+2RUcEt0sc0gLkNufame/c/7PvU9sZAEBMReQ7iZuwxn8uKq3wuAJkmUhJNuSwO84x0zVWFdtlbLuTzQ5Vtx+bknGfb/wCtVq6aMTRRuACYgpH8OfUf4025W6b7MtkJFTBRiuRkgDrjtzx+NX4JxuWO4i3uM74yucemAanRlhcQyOJGPykqckE9z+H9Krhj5xjaceYk26NZH4IHT8afMjtcRzLKyjLbwGxg9PyznHtWVlbmPzL653J/yybZjPrwPw61fuoITbxLjyn84Y5LY461JDJKkv2NE8/zE+c5C4U8E1JZmNppJCvlXMWM87s5/TpViaZ2DQxQfuxww39VPU/59azo44j5ht5t0iOTt2EbMdueuKp31yI5YTPFuxuLHdjOenT3pk9pmbfHZ+Sf4z5u7HpUE8MAlZWtt2BtifeRnv0+uetQNC6QiNE8oM+WXO7K96o2UMQ1GZEfy/MYhWwTyM5rcXbCyxbN0cmcnOOlXMQyqRBJ5UYUyqcFt2OO/Tn+VWYTIRFZwjKyETSyZHQ8EYP9KWaOUTyJAd8CYDrgDb6cnk5NZLWsdvaZUeeq/VdvP65qCysxPdGK1k3urbi2MdCB0NWp9LggvYWN5tnZ1ynlE5bPrnHWpo1S9F5uTMkmzdDnpj/a/DNPswklvIbW32QyDEg35z1x196nto5Q0nG1GjMHY7c9/emyp5NwhaPfEkQj3ZxkjvinXkql83J+0gdH+5t/Adf/AK1U5rDyLiQTHG7Hlv8Ahy2B6HHHepZbVLtQFbdNDDlXxjdj26Dmks55liWISYlLlVG0cEAZq2TCzeZC/n3Mn3XwV349jwMCmvGougofEnmqGmx14HGPpj8qbNY7L3zppcIBlZdvTnPQfnUhlt/Jk23O9eN8uwjZzxx3z+lf/9k=\n" + }, + "metadata": {}, + "execution_count": 31 + } + ], + "source": [ + "show_hidden_weights(net2, 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 273 + }, + "id": "_eIC0O6m21AC", + "outputId": "38e4dbc2-6491-4139-d982-7bba57a6b5eb" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "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\n", + "image/jpeg": "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\n" + }, + "metadata": {}, + "execution_count": 32 + } + ], + "source": [ + "show_hidden_weights(net2, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 273 + }, + "id": "1WgVBkg64E0Z", + "outputId": "9d7685f1-10ae-4de9-8de1-21f23fb7a816" + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ], + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPAAAAEACAAAAACo26TjAABMKklEQVR4AeW9x3Oj3ZLembCEIwESIAB6b4qsYhWr6qvPXyfXMhEzm4mJkEKhnZbSTIwmZj2LWUgh9UIR0kILLXujxcTEqFvd0d26fb/7ubrlaYreeweAcIQhgPk9qPkvXlYUSbzmnMN8MvPkyZOZx/XPzGNmdavMWpdt2nN7Zy0LW6nLIua1FXtoV9Z7aHc2ZpP20Ybt3tZmbMue2J51mM+K5j61cX7dM7/12615q1wbtRqtdtolL/LmiQ3ZjZ3Zt/azNVq0eUgzvfScs8GyuSxuZXttIYtaNW7HvOnjfb394pzGyvzqp+cLS76xpiV4q9MaNmH5NcabtYyGb3N2nLYd6+Zu3RbMbVcnNksXVZ6I25I9aNDqDdcd9uVtWNCSEOL0HLIvgkCfpW3DYmGocWM9ANewkZAFrABxfXbOpe59QDmCUAEI90TsUbV9UN7i3okNpKD/kaUANGQx2GZjDcBdluf/ng2Yb8+WbYonOu09PBSN2AFwH/FoBd4qFnikC1Z6SL+dVveC0hkc56arOXtV51qQtwK2ax+sa5i+k9yPMYK6zWwymnObp5lLWOIiwLU3Ns2fMWCDVjJYwedAhOcBrghNWm5ImofbW7YKAPchu0aKe5GJsu2NgWCOXwZB6c66JbmDkO81InRusSDYBnl4BGgWrVQHshmIfg41c4CTyoJdAAns5ULeOqeAxA3DbKMULmwrgYw3ELq3DCRvDwMgl+OFPPcHLXxFmzW47oAXli00ziCy4PeMceVt0I/Q9sMfHqBbtkc9tg6gLsDsoYUJDWSWXgPg3LBnWzxWcCDCRQjgAxwv+hVJ6ON7GhRTO9BoHimrI9rR1wD1FPUZ5N+OpYTPPsLjg5ohO0ry4D5Ck+HdI5s75VoZWg9CzRho9tQBJQatpQdSlpOGPuFRL0g1jIcvwDaHnh+3U6s0ENXveXPPHgHQ8BhvhYBs3x7QxkkZBqzTXYOmJm1juv3uNEOdRqiL68jwCQ1120tG5/bzWILhd6BuvrBTF+NNOA9hJ05LAVR9w3IjcEsE1fGLtoiXj+HsTXhBM9LwNMy+DcudoCncFq7CXRG4MICd0bIHRdRKB2zn5XLULlMw3RaaqATLXtFybw/8V0cUxuHluK3GYa1tJOQQPnxn7h74v4JquqW7KVt5bD/QsyEBb2gjH5UI2RpKTco08zk/uhGTOmrpo4Ub/OinkQnuhewsyNw1w2D+YF/D1Ol+3lxmoBn6fWV9MWRy0nks7fqPIFGDYvU89D1oo9CD+fF11X6PBnMBmtmVl18/QtqHUOuDeTwAHeN/FPLF7WqE+ekxs5Ae37DRLHAVaSjDK35+nlfRFRlMw23I+5md7zBTGI+7mNSu7daFYZAGCxetdNl4C714SNthML6z4Es6mOGJNT6+Ml8/Ix1Hg9VpuWVRN+ruhM5mYaQIv6P1emGJQXjWZ/VzGmih4VZpzmfDPXCc23kIY+rXwNhvk27I0wTjKWg2ZKtDwDKJtKQhrGsCGj+C/sfQ0SMMbyDdIHNQN6Rv+pCkC5DYgfA+858yJRgS+AzYqgA31ODxGA9qHvPYXQwUxAUjSHi3+S80CUL8c+ajuq0FEeke1EEJWBr2Pka7DUY1yuU+G15BWezRXIrGDm13mBZveU/jHjK/wB7jhUs++i1c5o7bfmSgA4zPdUkTsn0c9uWNIEdzaNyNBxA9A+ECUCBp8XdQUbAtQ8TINVAsgUQSdpi27TBw7yBl93xMWE8nTZwC1yya8rd2hz7gl2neLNFk1oL3cEaSVwL8zBmtdsFETRCK2E/2dAwEGjxWRjvM2G2MN69Qvedo+pItbMBiS/S+zr2ylYv8OIXzDmiwYU9lZfTCPeLLA+sfwvooc/eSHg7t4gvmHBcvaKWZs71heGrVeQi7/hNIabLydQGMBxLJEI/Y7QXW2BKEi0OroRpzXRIaTkN0DMAVTLhbHvOC040l+7i7AEbHiN2YZVrcm+WuptY8je50QuVdAImDBeb+HPfeobRdTLwxS6dp833bOOygK0YzSataM7hBeT9NE/co7gqgH1oyAHqPmT8WkORhu+oH7hbtZniiavETRL3A3LHHK+O2XWf459z18MyJxcbQK48ciPB/geiHYFEdhDBjoJgBx3Or3IMDqzVI67JwBTqdogIDyNullbTaz7cFPgIz1CrozyserCL9eZvTdOpBXu4AJcPlSw+Pr2BabTILL1ulDOovsKdeAOyind3QQRXYxFtNCw6gHVwIbR5Z77FijB47kEXxXcWe3MAoQQTaT78dllji+iSPztjv+Dh/wawS4O0Kb3ZZ8oBxFRhGjuYY6CmPXDsPYVaFYXTqpQ03EEnNpItYS0XLyY3ih1b70OrcEIoYlBqS88dKz5lUJyDrZlu9niW4ewill7FqRu3dCwQmCzXTSGoYIuddKHzZSn4gTdu2Fxa6hQNcvLaONCLDk5DeC0Pg8ZNPT1KcB0n8Am507AGwlDHLalZ0tyeKFhOrVv6NBdTMBU1laaZgJQ+NTNJIAtvh0g6T9CcOgBWAPqOO5Cty2JfrX7YV3LBFctDhCioOQSvWRJqSlyFeWmgWIHYUFIeh5ZVNHfExA8IdTJ1fWXUDZE+hm7wBIbttQcVLPjyCJeIYyM0vkM8Qv4qR0LgNOpE3rwIjFWzxIygMIaF3wBqzQ2xv8O+EM8oA9GiXIVzCH9P0gKGUpt8MTRV49868E4DWyV2Z3evcb7/XA+xjDOfjDKzmppsQQ3HZTppOPM5D2Hks/U/hU4+UzyTstsPkgWqAV5tbME4SHZaT+ZCEMZI8eMqFDtuMwEWDvBVAob23cdnkt+35PonymXiD5tpj0jlFSx2jJlJ3aLgz7otTt23ejSAE0WMhZqNV+7IAM8qA0LquaakuxjKEqqzwpmxdOpLXsxPVZZY6x+TZpSEkBznyveeXPGP2YVrsWlj2xRRcnmEeqtjkAaOpIjtatubsLIL+unUeS3sHUeXy5Lo2QBD07HOgPbWwl4+7LACfoLaWtbySZ6WCYru1/n7ASPNR/n02ok5RLEcgpGk+a2dxkPkMDZIGxxz64iLJD3n/q7BE0lZdqBQvsPbxftqKCR6vA2vZvoRdopc8tMnjYoq4ldJovXHsCHrlYkl+oHnUqPysDVt7yoguab0GzgkLXaFId9BgnYx8wY7y7fXPLS1HuAt3vKVf58nwv0Ljr0D85BXU72NOaEL2gJ2fI0ny815AnvoVYHmh5CLCVbLiPXDLDknzMWWxIwRH+4tZyOeyjhpgyHHwDIHNcCE7B+cc01AfdL62QpJrp6AcQdKH7O4OfOQEvOA/01MdMOsIPNa+jA/5o1nK8EKDwWz3IdnrAF5r82UzCJQyV0d54dQSN3DcPa32M74TG/MyP40wMh8tn1lE/gdNmQ77wrTULl/Bzp6gB5cgQhwK3FurDg2H0KcR6NwmTAe4/wwXFA1xjSNLPsja5M3RHa6FoLCPj2jxJzRwDWl30QK3/PasyZujGCxBcP7cli5BoMqjR7BT2jwJep0XiyGN9NuDIIfbFkMBSNfCXD3n/WGktWRJWR9pBtrFJZfV4jSjtcYh7JC0+z2WtHj6GN8SSN5082snj57DgbS8D2c4cfHQQg77kcdpH0TpQWKuocu6jbXa5l8KpJYsXkdp7nL3HmCf2NUtCHsg6hGE37ejp0ynEo4CTDFuqY8o5CSQabMpDNqrSaQ7DtkLPF6zoFb0YbgjAmPgivHyS4n7UiQVC7bo8aINqLjj3hhait61YXxjp6OwkHatS1xq2lIvTHjfbi4EB1XnGVEc7N0M8oNVZZa+ZNGRRK03LbRIBzXuOezLG4X8PSwBznIg0ALbXnBiT3+AHwUESBZXqAxZtbfHGgOku4dAvQy14mD0xMq70PkNEjoP4QN2eIdSrEDcY7RDJ7PigfT/ARPyW7yFZSv0tHnjFPnOAIBviB5dNBTmJVacWQAYBSk/L7C138SOuue+LASvzcsHkYEvygB/YaEi78kvH2NAn9uBD1UurpPx9sxefc3gRxDqeYbaaYkiw+hwIMLHeFd2ADB0ad9AmjrA9AP5JkwPNTQjdhoUOoNaD0BQzrTzHuZuOe9PuMfWSJD72j3ZAKV7i7q4/pTJMQlgf0HbT07hgCrKVy8dWbYTjKZpWY66DhlAdQDTxB5mFyzeA8fdAVuegXhtcBfO2QBh2fEnFuqhAePSYxQNoTtnqIsW8pnh/4H17NLjFe+e077PFm/p18+FTobStIoeceA87DylNQc/KKoH3+MP8ME0GuMMXhrQTlwcppaX4EIeq1nY4hweXDOPQh388GAdFeK3yyYP5uCzEYxHDAHtG4qx8mipB7y/PoPuK6I6FD0xYieTNHMBY3qQg7TtnbZV2XuYWiFK1Szcf4MNOs20iLN7Dv4WYzLLIDjXJdgyyIUDuuyz4hl8n6XPK56oWqPCjyD99qGpsJNatLiJuBxyoWyBdXj7znlKy/W/Isjy9NTl3z9j6/wKqhEsqDVEGFvS0BetK2hUQedr2z9tBW31LKLFsqzo8BHWIKwCWqq8+cJW5f5KtkmeB0sCBrRbVeHBFB2t22QE3fSY68W2kRjUzoP2JcbhsJbNSBkdohkVfDhpx5qyegEswFNF7eJrMfuWgYzSYO4c+6MOq+1xd9RO/eDaC+Au/hHUds/Ml6azHgZzY02tjYrOQ9ibA0Htt13fQYWnUPICgGrQm68tMNbKz78AaeSx3mPiCtttCRYoInQe5nyfJY+4c44dGEdk7y0g464KcUO08Jw2Zt4hufsI5SXAB8zzvu0e+tC+H7e1buRMpqU2sq6suUOrY7S9Z7/GnuwIwkaKQ1tlXfAXtjXB4PZRB1sww7pFWCEwnu32cPbtMZFr4KuwNHk1S9doonvQ1fKyYVMxpkq/AxF+gUTIM+c7gawHiEAYfKftuA6VZIZ3QbPkPmClQVFLR3zHX8MGHUhJBw9jlQxgEoxA6yBY5cw/jpydI4VrCO1vIe92ADi621Keh3N+XOROAjjXwCNvyRKPeMCizLQAq4W4p538GggSvnqH9OuxEcyPZ7auIUxwJw9DDFtqBTBz3N+kuwdS/YpvyyCrHl6LsSnGnxDjXhKG2Qnx0eVAhD+C4SIGW2cvOOxCmKdI456N7kM4+VezyGFG8daKdoojL0H7+/KOXoBwH7TusGGJs9hhH3Dcdr8MM4zSULrNDmlrfIRzirT7gfs5a2ltkkWsPCA5Zdsy+3RhgncPrEOr/R46+4LL+H9vaaqTTao5Hq9Z7JI7xAkzHdzz1KE8ETV+DTJmohhH+XYIfxXoGYZL8WdpspcbOG/NAI9p1nDYl+vfQSntFcWw8iFeFCLI4I7K2NKKrAhdCgHkrMQFP9Tx2oc+nu/i+jIyUbB+afbH3NeWYcm2/ECiQE5t/F3BNpNe5sJ5Ho4AaEZrulseHEAoJ3jK+5YHozCT1uYLdtBsa+ivYYU0Qh1J8eMP4MS+Il2uFTAU3iCLUuiH9iCIBm8C9xRv31nczV8ywLgu4C/cDNMwy45sO9oguiECxyiQ0GFf3jvo3YJ41y7srMu2gMgLTpLHCoRJgfCu+c5l5YKggqjwnaUQmQ0W6LJ3CeqQ+/UK5LX2HrVeRRl/gORPeVT5Gb/XTChXuZ/1fsKCW3hrbpljh3gpYN0jMMkkbefgjmNzy5s/DCgBRHPQ/MvA3MVAfqSxYetTMHsXYvkzJh12/ju40gv++TYHgqwmjDFGNwf+NS+MpASTkbbgD7FTwxCdh3A39IhAmQBLd8T5FFlTQOOJAq8uAKyFvObGoa8C9W8AjF004vgBHKihV8NaspRE0jE4BP9bljtDtLjJM9/SVPyauTCM2g2hH/Zt4hdtpesGlhjdDdwB5iY999Iy0R4rcE6WibPE22wBaMflEWOSgXxneyO8q5jJfvig13bvke8BBtHFYPEcjcNxi+zNjMJBVzZ5BDuMM5VoC+DaXE2iwj5zHsLOY+kA3Cbn/c0oWsLLB5l4mIKrsM0ArLvC9+MCekjhA6wUYKLsE1ROJ3xYRg89sA7t3J7y9iEX4UIFwoTgsh60SQWWLjVQHjkUhzZ+HtmmfAlVmJ5dJ575OUlHGe7LNLm2W20j36EoPfA/3uVP/uxLBlHEFvUdck1RyEqkeWoJLy120EwfLZDdtEKrO5glB1yatfA2WqrBACLYoWZ3Tf64Q+extFdO3WkIMbAKrZooDu0SDtrPPr7Jd6Lkn7mPUEjhfxMgSYiwtpg043+LNsHp2Mcbu0wdvQAbtdICULqZtnrRcQozjnezuFsETMCmj7gCAe65n4TiTVus0sgYrffQGbse/UB5DT4E+KPWfLcYFd/T3BEfG+YZ5fogj3oZyK28RX4YLQqzuGGJMoZqG3IP3wlS/IoGovxvco/d6z04Z8yBCHtB0Au1FkrA0YVwdWMGIgISO8Xma3l47+Z6HtJ3MfmzVbHTnjYi6Hvt1IYvkZ8GdwtYA9dWyaD8L6Glj5ckROUtOOOQdrXpN2k7g9gfdUDBEoDm2yF+ySGjcsikLP49IpenVT8YVexeIqutau1a5azrwH5Fr1oA+pDWUTnGtfzbZRHRafuH/JiEpy54/IM9ZyHDo27+iAgXbz+zv0HynaeltSZfgeJLLkkQZJP6DdmuItQUnJOESA35XBOgoOTee3utRFEFbeRAKGZNacImpPsSu55Itgr4zPKCvPyK/N4LgnAFLvgNBiJZoFkMgxpC+RacSnY/xSPdtKv8UDYOffDGDRyiKAHeVgjBIpJ/Q79Be7tIQ10M8ppxddndBBPFGgOdhr+ObWEdBtTKpxs2zNi6ixFV6SIPsJ029yOi3OlAhLUuU1DmmPZ3DNJmkcW0bf0CkroheRDYb7UtLxOti0szNu6CfoNc16Vti6VEbGhdpRkQGYfkcpd1c/8cPvBraSCfvnz3K59ikVf50GgbiKxDd+GPHnBQIk49woOPaOwNd4ny2YBR5F4KowGG7Znmhn4YcJT/Hltl7cHGp2JcF5j827qjgzEfwQFByf81i3/FLHTTwqUyFMq07rAv178FqSJINBRhY9hTz/C8TRlid47YqhwAGm8cwrxvR4trXtsfhH4K2JNSJzdV8jXCxz5EHifOHFIkX04HsDdQvK0tvhWg8ibCh3twub0aDPOuAtB3FMFToRu1HLLgcPv6NgI9yo29Hq6lYaQCcwdhX/IFzTAy7S9uWH2IRzQxD6IdRq2nwTCVYLIOQ12bX0E9ifbfFOXyfI1uHIiwN4M1rL3g3B+gkySU9RwX/CVdg05zSClA/wgG2rjqQ0AHlyCsB7GRKctWnHY4/tB2ECkIM3cKlKzz4Bzl54BjEshPIe8C0npiuxPgY7QuFw/hPz4k24+ula39wFa1A6YQcfZGYIiY5lEZbm4ULFEcccBWxtYqDY3a2hGtEp/JpCsXVH6Cu/L2a+fnka0okVgbRCpUEbOf+mGVIA057Mtxf7D3HuaJwkadfriUAD1xGRrpUqkgAbglg1VY9MENUikP4FYi1tiogTnKaKoWvFI6gAfJqedCBwy0y4QA047SUA7VwmtnXPAQdKKOlmzhO8wS7RArSarDqlGu3iA7IeabD/ZIS9XHsLv8XexraBOrBX9mebxmgRwdHMPSyotasy6lRQzTL5qTNlxXvBXkccVOrlhKOx7KaNLC5soWsT3b86rDWNr1LyB/FRJQx4LCEAAm0rFyULmCGWaDMCQ+boGA4rr6cRqxY3sMCjIufgO1cVfX0GuK59QyjLhiFcKQLrkHqTUwPrgHB1ZrtHrLz7K+FTEsfXR3o6IYciwsYaF00cal3JDauO+k5w2bJgyKuzIsyly+ysM58mrnUWssS2S9hDBJNCUy5QXp9YyZSPsarBcydDTJqGtAG7N0mUel/xz2RWBaAPJk7cMkRM0jETfQJGYfW1zXLBMBRW8Asl1D41smn7T9Px0AKtFjzQ35fdq7afKW8sOnrVriF9mQPVA7pAWkdhbI3gY/cjytfMl7l0xJWqQ3rHKJpClA/JzLbP2/pasKHZ0yqSXMK/kvcOeYjihg0gVHZWCycYSyJn+5MsDz3A8xsrsaoy1zqQMdQ12i14xGAc33fPTZynOG8NqBCPeDhrJAPVUIl0ZOVN3JbQ86mMDziEcNSlM+oQDZVAPggIspAapwgZeQd9288yhlbUM9REQoUdXJwz28HYGsLiy/8WtAWUFbntIua81ORK4E+nITxS1bRVVc8aDUPpVFVOMqxhDGWNNN21k3qM/SrpJEAhb4CUOoG5E8Z4/p0vZUt0DO8zwKJWXbvTwYprEIAk0ESD+jDTMY5TePmlemZbcDEVbq1jtwTFwjMimI9wCKvLZH22DoRTSeM401FFixyH0t0XZsTomCae6P8yFhQ3uwiI+dhVNQODPXDBp4iiZYcnIjZpcKPOyiqV0YgnieQ+CMYxpO8TZ7TykyQ/2oEXn2WGBoI6ECIC0ulSxY42Ht8P+mzUOvxxDXFlO6whJxD2q3E9ceYCtOKFmGdxSr3ABfEkRiNKGhiNWIVszyQ0teh315j5gNQ5A2uwgtXzEbboHiH1k5yHV53jLg1HWM7+wWOj7n+7hFf4B0Q1ByC4nKWlbFfcbAVkWo/DbwFqV9xaVGe28xbtU9JKkKDiw6Abavijo44sIHKM5sq1XmIFBrl6hizxX9c8ed9zT1yEolRqOM9SwffSpOVqKRAhxZ47Wjz7kuq0H7g2476UTqV2CjbdTJmeVVYy7JiLVN9dZiPj46MMbD9R8QjkHUXCkEAtpBEQBxO4+DbgzpngQJKobVgM0Hgl9AeoJ4zxD3dSRNUeUhRelFIThJWjx1oqp9cqcrBiCKCl6Xi+2arTtt5KzauexhrfV66TVraaL8IX0MfJVf3hPhjX1kbYxHeXEXZgq170Vp8Hyg3bNS0guAmnmEMMtrJzd8zDL3zO6vgFs22kO7uwTsXe5F2h2iHaSnnSfDWim1IN5CPyik+bWIeLLgUOYTFf3aspTYAszf4SPTen7I6jkQWEU/kxsFEh7t3RYgHgUfEKHQPrh+D39MovCXQGnuUsjhpFdwFRmSonIAJtpjgsU/e4FWKMBddTQpm3UE4/CLQnO6+Z7/DW/JhluDIeC9OT7KylYRQKqIVIA5CsaSYJb68wCuWN0urPgrReIpn0Dzf5U5prED1DK9HPbluD/Y22xvCGXtVInwJbSNAghx+irjlLBx2FrFgtqMfgA37fJE5w6uzWvMS/k756yijLMXsGQPl/ASasqI0cQZktGFyvtCJQ9azHFpdMq+JEBxxHHmm2NaKfcxC2npH0TPFMylGKMhZEdu7ns7VdjaLe9PIgFf2Yf3vPUOHu6ku4SVV3lM6fBZJCtqRa0/aozohj8lYyOTfCQCnUEOYnq2fMjTqvNYmvBhufa+tGMKDiHv15BXy8QPEVQO6bzcBcBn3LkFVoUuBexsEtzbOdg8vmKZBETtAa4yM35NBTG0k5Fn97iKLiIaaQWynoGEkAK2AJAMUl4mBaREIyqwcBw9JGswbhtz6Jg4DyoZSgnAqKsB/il+5gf7fIsBLHBpkp0Jv81mYEOx4rcsIah7qaq367BEnq7m7YAZs/24om8oh6sQB2XqOOzL9ZdIwysAOlVxiaO2RHwA8g4X1QBVmfTP0eiufq78DnL3AlmnnV+1JwYX0MwgJOceANPOguI8fmv9Sk5SRZkhvgu0bA5cC+DPHi1PfDfI7DQD31SxcyiacgEKw22R74P8CVkKMld82Jp4v1XHyA1C+zyT0K5WBe6JwllyIlWVIKIglHEYompuF3+Ln0fxSfNuRwPJXgNqWSFEk6sUixzcDvty/QlrPq2ijxVdS7IfClIxXydhSKoonAvk5XAeGT5h1t5oR3ZtKKx4CVr2ooUxMqkgwj+JjgflWAm3t2N74RA51W6s+yPyWW4bLRvAfn0CLGWa0y7xjg2r+on8p2FgrdvQz20v0s80+JDLlXG6UdiqEso3LB6DRbphlSKXeq2oCOQktuR7WDRm2/gt+GsmMTryWKs+bxtQ5X0cMODTKVYQYQci/C9B4TniF5BrRwGG51DsxBpFKCzvtxwmCUWR9/JQk+8N8yPGQCUHjap7lrVzEwCuNP+vbF8VYF9h3M2hobUH6cvZX9GuDMQifQCa0nT2wCkMfwyfshI4RNYSNHdtfSPIXxQJVGXBj1ZVNdtHyL6cwMQsy1DQ0kCJ4wMWOqVH5e2U29NJ4xV88Qz9TfE49EixRRNF7j7gclblR5XN4zgZ9ipwIweZjgJQATc57K/0WyTsBCouQO92JY55CHdkv2xPjxd5xHkfMEGP6ZdNmlkgUWaN4qFOb/gRQZnLD34CQOVemjiEFb5DWRxbSPsqXfSqIDp2CXu4q33cfbpkyX4GEyhWVFw0bTufESn8M/xxxPt5u0vBZoNoF3neKTCrcgAkndCQlpi3qlAkP2KNoaTM+8lrf4C6/wY90Dzh+rwDEY4hESXEdjfBXKidWjnaHtnSGCKnmm+S1SvtDVODG6Gqg8QTN/puEJFVBvmQhWWFecFogO97FgigOBXTP8KHb0H53TAidw8WfTRIvE+ZR4fR8vIZbtrYZXuLJ4R4HcAUrjryd4Z+P4FZbu1pBo/dFo3VMIMnLHnLtQiPDdJuh9VbDEE2WjeDJSHUDQtt82coCoz8ZbHuBjI8C8cS/Ky/SDOxw74c9wd7Fci3iBpJHcNLxIkBv6KSImfokBJKgOAQhYeIcdbQE8oUevsAG0Fl6uTVInqXhIn/XwddwM1Tg1j5ilP5VDOR6MAyojKDjepigXdjD6/5pvrSK6iWF+ZXnZFD7tfgxX9sf/2MzmXlLtAoCRZaYVKTmA9uRKLYgHvvYGr5Nol21a7ROdfPmXo8liqj7AqMd5ynfqUaOZdMTKqSVWb8t6puIfeXw768fgignfgrbfi2APsLyB+2XARKKWSNOhQG/f38Z2HAnX1rvsKoOEZxKEecaanGdCW/8x7ahomnE+2WA+G/R6s1QZChxRItK4rxoeoJzYMxkwqQk4ogG0JxLqpFouBTfjxEQ3m5j8Xg5hdtGGgLmxRYFTHM0/o9GvLcOjfUH3MU6Q8M54ZCFxg8q9xdpo28G3RveVibFcQqLjNabZs57IviQzHoTS621gIl1go+vuOhUbRZAlCqYNylAi9vEJ0g99jtUWiF9hxLEI8goREeOkDsCsxESxaUu5/N+rZhmsQdPayTLLS54QX2EytMMB3Jn/2QiyT4h1mF+oFEsYokCsyxEvDw7xp8CT0M8NBDBlhiJoxav1yQWmW4matYNSoiZQDIg+C7pkpVj7jfy4h9WCpXcpzKwan8drftTDB0EqMcBrC5/gQVqU3U7/shQJJt19+314Vd8pkoDXESwfIcY+VlEYETSE/xUJX46uSDSp5gxH1KRUyDH7u/thWD4Mss8rQlmJe90ECdy+tzDnFZvWn7Qnlv0+CQsBvFMBoIKuF82zxTsIsKH42A4ZztzYD/U/tT2tbyNUKGAcpA0QW7gk+JpDI6jpBwl11piHH+Gh8spSxjeFIeQ1UkypnLS3t4CRyH8D/nb1YC78zfQO6v+H8FPB2WGMPmVl7PLD8fBcE3CsmUHDCiY3yK0F+hhyEIO1EFblV7+pFmiInS3qCCw78EUun/mPwuhbYkyeW2oXpuitB4yH1WbUrrV8KNIqpw6A23FXadj6orGczSFOWO2MoYA9wBFXuIY+FSpQgp/8aLZH+B8+lrQJ+32hbj0f6wCgTuWvYxTDjMuOT+qdpoFAV17jyEvaP83Ur9/+2XzLDuNq0fQhPPFteikGgPznd/QDC06lNqvs8uIkjnCWCpqhe+0OeAcol6nAFO9nKPkNB9xEdhfb0QfiMIKyiFo4PvQ9ajyC9YiA/aZV46xFxT/WAVFSe92yNxo4UQYvveFhRhOg2OinQN2lofw1Auily9PbYxAe9ITyt7Z8Xiqh/dyXtLGAwu+0xmhOYdlfpcsqNOBD3lPIRd/wvk0U5t6QlE0fFCcX5u2N4LpDSBaCqf/u7TsSy/ZhrrBuuDFnB2QmWd+nZrE6uQrgs6X4HzgH24RWzYrIeaMUhctegR6Py+rWengYXArALMoHoNF9A7cQs//ZKpcx1pJcL7DJ7QFsxz4B6zvFaoGRTy93DJlT3Q9swgqqJIy3h8mjBgADYJMmaGpJIeUwxQW9EN+0plfTJ8LPHvuRUVWqwKMQ778qoECwX0bf6sbUX38bEEQG55XuNgqBDJ+hw/VK15qC3jAfa8oPFTLimpBkeLMis3wDEBTjnNrnhjAE2+U68dnmEFXdjfRlUEuXEchjv+K3ZUBay27LKLh95D+BagYyTN018nl1bpnXpligyRkpFrERtAW82aZ7NoCSKJ5A0Y4WOh/X5DIUiqViZfZMS27mCWHCzIXiAc8q18G0vOQ9h5LD0Ep5DUZ5m79rpKxqGL2buumN0KiqeOij+X+1kB4JtMEX7r2YZtVMhuiODDRTuVlSHG0Uk17BIH0SMVLo2gehLMKN11dEUc7h2A7ckupGowffby7huavVRm/Awz3CK8yr8abKdtKB98TDTcBA+fIht+uLjTXAFsjCpaTtsNXfaqj+lIAa0bdNSwPsWC9yMBC6jNHXvQohkPfPwHZs8+exPloRHnsbRXdUAVYjbeDdkuoWIGFcAW3Bxg6Cgo5SbmF6DUAUTXXn7c6i7UzSlqqQrptpWXWoc7VADsIW+vfQnBFVaaRbHpCLE/72H3qA563fBB1vL9tNpn/x03wQZG6EyFbpQbvI9KK9pLlTHUOkRlD2rWfAMsF9zT3pTPMnW4pwVD7NH+jrluUWaKjfyCdqlwrLUk/gm+3wDsR528oDVhmbstq/XRmwO9lt5+wFHV5pNhUBxitp/gJ4nBK0Cm+tGKN+tdQcIUpFkC/ZyFpN6Vnabzprx2IXw4Cwoq6hTSr+Ig+N9BQVGf3VpRvMLbU+CfEgGIGmSXg0ZUdv6E1cofWjwuobzBtDwwNxHkvFdHqJVE1ehpP+xGNNnE+nRmVAnZv0QaAzaD7YleGWcyHGcwBEwlGBfBbty/M3+qLduqphqDhcdes5+55zwZdv17dCnFFey5AkA30acHkJ8F+Qwwqyj0JPdqPpD/imUjmhB67XQh6jppSCl/3yjEW0fA1Hh3C2kKXEJsBZcPIt8KBl85pdrbMdyjmoI39pkWBepMr4Qs9RZxq0P4IxpkKaiSQx1IqErOPrKm6gfAFaxStervVM6ytL8OuqXc7AFahXMH2rrnyjok4VoJqgjNkDX3aPeCu11MFXHLx2CRIech7FXolcL8L/pBQVXfOpHUO7tTCtYh0CxLXpSTUwBdFf/90cbllW+BThMeOLeKzgZQzF++vVhbveOxDSRc+3w6Zurz/5vHpd+3oTbhg0d8VJD+rwD11HI69zQN6N2Irc+GrsFwFDa7g1lclsjzTglo3Pa3mE/P8ixZc+15XGvOrimAztOyyjlmLYS3HVXT5N1ztEwlCU9okyoCq2zKH6hIZOdZWnsQYF4z7kdorKLRGTB+YoUS8+RL0Ndu/HkBf402e2UvfWnLOrGvAfEUrpmWg1cxOiHua4d5+pg3pph6p5HkJM8cfQGrEL3KM5L2gjJuhIU8bwSvbgDVEf87eZtVqnznCvQeAHBcNTKeFKk3xDYAWTqziKUWiB9pjHLU51zrYhBDDIfakyEE+iMTcydj21X1CfmgtJ+g+aXUwyDkg3TYF4laW5CHrZFhfoRQy6rRdKmDHpXEoSxxjnEY4JtKOyjdhvPaQ0ydEiztglH308M1bXkkmDjP7C9VsEWBd/vwgRyBty7E6muKaLSAe14B5Ao0iEpvgNfAMtPjXyKpxlyftR036P4MStSH4bVCDEh0KLy2zIggUaCIqoskuPfACgoywBDg0hQdXaqguGytAaR82kI/oyI8yLGykbpVIWaZETsOYcf9wa5/A5OIZ2r98BnlLeE1Jf6GJrHXFFoqt25cexKffF3dsDV5mtO8dQgX+uH/6AU6IoRG4KQZ2Db1Bv0kz2IKxn4BNy15USobOEjeoKtCtprhUSWczvL9rfXEaIbQdzpT9tGPf5tHLmE9FafwWk17uxVkS1tVBW1i9NKjCpCEYFvXI2SiwRDV3JE9VCSiqlMP01yX6ikqllRRbH30H1WgxJTzWJr0zT6odmEN7Roo4VPFXPw6vPwR84yqDD7Rll6R6eqE+9MQ9ziONhgDIdUpObdL7esog1xZqTXLtkBIWiIKJBe8EvxUyNLF5HCDavt6E6Ar6LA6z+EquAGyDlRLd1sPRm9oYoDrHIILuCm5GmdoNws8+5aVQ2GR4cl47TCvjN1HvKvg9rTt3vFjG1aUI2vbXvQAJ8dyMtg6b/fGANuBeUukBqtS8Jgdy2fob88XylLhKKdryOJG8Ag+3IbkKYBxQ60BG8mD7RkY6EQZjiHq54McOBIelhkqfanUbRcN3oJxmMI/NHOPFH/PCx+6EPk3yNgdr1GiSonimIWg0Im1ci+DNE5HinAK2JGboZ0wC6lOhc/2coxVgf4HtE7CubKHtWHkobmSPT5m3lPSgrLiZuzDAC4Jld5M8+alBXvxVztx9/B/hziqqP5dH8Q7g7hBSIv6/IeQjDQKcGYLPQXh5kBRRfTx+cjg6AZO1QWions/QpPgiVOoTS62TPcIfKHsIVUhX6CoHEJ7w9tkbVi5A/2f4olOQlAJ7JBReszDW+BETSs1FgEJVZ0hDPiQNYSK+8tFzulgihRP8K8HHonYLBEDNFWnVdXbDG8yKpUrVS0rt6WoKMDi0oMVI593bpYPw1x32Jd3DLL7WD1Ri2YP0ilq9yVikFZun6xFlWDo5/A+PihpeJX7rQjirBJgqgt4Ya0wknIKQqocxvb9DpC9g6wnXLhhTvbnsegfQu1x+2uaG7qFzlpC3qH/W3bi4s4Yi845LL+muU+ZgYcZgrYFazaiZOAWDf3MUOq2PgQraP/wW/5VbFOFo6q8r+iBLQsqaUSlyFUMMmupE8a8yNun/HTZNyd8KDkQ4Qs06RSCkFZ2RS+grSHLZGTKAa9NXdVvuxiHQtoX6sBiL9vdBZJtLB9dfB+1V8qP2eOxAFQmUUQLcKKkuKDE7n1ryaEglfsH2o1a9Rb+0N6TFPCBZX1A+hNDaPFCyt4R5op0fs13bRfXVIVsi46GcfFQHZSYEB4lNxgO+GA9Sic9b+vxXswCThivogG0s6TKyc0uBqczUT10yAFjBtBOjMQr8scrEiPARi8/5OU+QdpaCotaRGKyEMkbhpwTYKw6vmzgHnLnBhyUNkMdbx8UfIHIc1wAl8j4KgPKIuRV2McT8yi0ZocJexTz540ltY3iRta2oXjBxuJ0X0U/qNhnw74p8EHWdo1n7lQHcgUZLSB8v9IwK6iLBuKsvc0HlpdDOYCdLCQRbR0HkkDcyVJgIJgSqlycgR3CGvsd17TadNgXcfI6b5t1dAfkuWiLVQRqBxfacMl4IrMjiFBtIS2PUZ14aFXu4SF+t3EQpqh8HQ2r+g+XkDBsiUuuau9LzgMPP0cLdHAPtoO8+42ty+MqN/oL1HKnDVwh6jrUQUG2qNZRUFDNWJ0c220wyD6N9dKQvAVnUeA5ZgZQeDll4UP0p/xjEssY30AHvKXjW9bINlq1iGISaoxLFgCWmB/5Hncews5j6WO4QqK8oROslbmgyKCQDclZpHJc2qfzU6uOWUpbyToZ7/YFj2gWUvmRkE33wmziQ51FsGlzOqdAdqbK2E2iMx6qLp1CiG4Qibp1XdNUDx8UnJS072s0leC9IG1QWsGHbpJPcxoeZ0bJwKUqz3dPV322j2WCHjK+9zCy8hMGKUtSie8ksjXo7w5xCMC8SdvERGIon4JW81ZRRox2rBz25fon0KMJeEGtnp9ArSPoDPGzUEuEbQFK4ggtI6hjUDtlhyoWquqhRJLqFXk7XzNLRVEuP1krBdEvMS7HmPyNFlIvQOcdGMgZ47W7D6DQh2rRWpOKVV1oIYWd1en3l8oQlQO8DDAfQenDKHrmkH9ljFtCEnRabg4bI9Y2IXKawybb/PGY8d0pYD3DW/uMwGPNBD+0caa811EbVc1aBy4PvWlw0CmRjVEIoLhtpYTixhsFPQnPA4BdUbXaGtP4IghVbOzPIOs21FK2PhHlKgTPAV4Q/hQDIOwGnRr4diNtWihW85gGFzQTAK2frEdnJBzDA0QdSHRx+/EhAX8UmZy6XDTRy0BU1ILzNFQoeoRG0kDWqcr108xuF/waZyZMqvhgAwx1DjXhG4NcE/bTvMm4tIHdj4Vzyf50zeo96AYnunhSyMpX0GNc2X2i1QY0KdpzZRXrLGedBNqahY7sGgHdLbC7JwHFi6TJATBpzX5+UQa3KqDEdciTKrZpd1ih+EXzE6mN0Cq4px1IPAwcj1EYcWSZ+4ofCMAb8uh9breybg/ok8QiGq0pb3UVuAlahQc8ygZQJskaA23IxXNAm3hzAfbGKqN0o+XlKZcI69GI5E8a5W/qtXMVGZDmcNiX9zVaTQfJ7PaBbA7m70G3DdqOjgeZhdps91iH3PNy3mrNRtxwEWnoQELlyhu0kzs+aiOYNHNAi2R4aBdpUaV+JYP0lIFa0SFrQBOzzT66kEgXuTttFzpNqoLpWkH49uzis3b3SdyxN4A7v8JDv2A2pVA5rc82uKo0017G3KkDpkoMUcch3zPQnjOGIfdtk6coSi1fH7k+NOOF5fjztFnsPIS1ctZBCxMKrlJJ8G5o8tp6K3xUplOLj4kE82gOb41Og6Zwi5JkngKZdOmu3Q6DjrY6hnkUqVG9TqnGLHL4D0AipDM0FYOlvZUDiyjWQqU1bpEzvH53vKkaVsPcXbCCSkUMo8ETsAy3upkm5KBRCgJlGs5YOFzDV52MjlJxeYC85EFVgum2rjLvhnm0F5xvrMHRBvwYoCMtiuLSSnLuOezLq5jGGDiNDfBLjl/TuNqIbgti3qhUqyI5XFXw6WBVp9hs9tXT/NBhbS4wCJpvg0WfQi9+wlJ6bx8HEMVHaNhZBEw7tr4ZWpQlrRq8bLotQ3j8cYimZuKPi/TowRek+X/ZRlTjJ0u78u1gZu8yVT+gEZ3reWjZW4awz1sKJqDyr6ZqOerrjBgjSlsHQWYHsQxHfKS4WwZWpZrjXvqcBx3oAHAeS9fQBQrmbGqrZx4tpSAxKg9EkHWJ+BhMQpGnbnjkCh718VtIOzhKJvJiHOzZw34cEDpYSFVFvrCfZRT+CMMtoUmYlKz7Ft5P8eYHbnxjx2Pw/jPa34fbDu3v+HjoDtPg13SHPduFGCmQRplJY/ZWZaN+x1CUme+2vTTac4APtzxFZKkcX5v8jyFh7E0+RxxeIwoDDLRqw4Qb89YxwtbJuNZ07EWEhx325b3GFFOm3kkNfb/JbKMcrxXzeIFMeQsdGB61KDRUTS/l8VEHQYUk3/GLjpymEoVMyxEejjLFb1iRTf62YdBg6Rig0cgrGlFm4zhvh6wYQ2ntgbGckcSEKkZRzgXsCNjBXUFHqRKKDsxBz/01nNEB8kOAdq84JZ176RZH8tSknKozjFZr+7LNrOIFMMakAlU+872kZQW2K0D1xAZPQF3s4LAvr+Z1yTC12hXkp8AklZxk1aazfVwEAn9mr1yIrEErHfWV4j7EfgCtJMePzfU9ZI9C0mMIy7bfGbPAeXuW0ckx1LqY4H4Q84BzKkAxvgx+ObzHC7SyZ/OkH2Ir7jJLZeCSUdUwk3tbpqNZ92dAucb7b9u5TRxHekgXWboaZSa60WEoTXqWR/zG3sfhGlXf2OGZa1v4JaxWgOe+xorZ5hYdOXH3UNn2E9DEdY8U6JB1L/bAgMWUo/kWpSr3QFBJHjfQ6gUUaygBRPmd6+Arpb7y6TQZrRR1JmZdalHvpmmsxaWmclJxE2PgPUO8Dh6jSRXLppSyhFVj3OlFIWtFMmbl97QqT7nKCO5ZMY/8aWfqKdDOmOdTMFwA4RzBTvIrb0lZZ5uwKMdXaAMjwhyA85smV7Wf1UVzqmXVb+5zWGXeeTLs+r8QrTyUvjlFLeqo0inkqWmP7sFJWTADkKe2SHSGwhJ1FsQi9OTbBqC725SMjDDDfg4S7MFD4J0jHutGRu95osTloK43wDiBYE2pKLDKFkmlx8G4b6s9HWwQtKrlBzj8wJ0EgMkvxFpCOcAvwT4M8H27aJQy4qjkfzYUVVtCWSpK/ffb9TRzR4Autvh+aV+pvLGLjlRFgsCxHtT5Y+ch7N0Fxz9i46egzT65WaKoTxISteeklOA7qH3b4EMaKqlUo9diC9jiVWZvresD1tmAcCXYROdmHNuN9P4u6P6/iF0SUL9SGTeFB/t5IWtlJYKS+80HuXOOu7gu624TOStbOkif9+B7hqVmdl2h52tQ7oPh8O6MwmraPxwCrS3rEYsp9ll9c7ydwvImYCUmbHAGX9WkVa3dj7x0p0LGbx2IcBV9t4/65LAeCcBDKK2CN+f3AM5Ba2jLPRtSHRx8JujoK3C4l8GkZKkM0OI/+QiVi+jDDdCYsdQRj6movBxEUVhiJQwcyp6SO79qzTqNKDcjzWss8hSfeSYtCg/s2qlWqBF44B3SyvHCv4VV/pzx5Pn3zHIqNq8shVcI9ZF1rPNB2wCq5kDVMCl6P/bdMoxEitlg+29RgdEpuGNIkQlJ5yHs+j8R3AXI+LNBX6UMp1COJ5ZPQ9YLprgfAC2+BcIfEY8MGHKAg3zy7dPBIVzQ+lSuR9G0DxFt6i3MAvcJSGyBogK0qtrfCqPDd3mYpJtNsJdboQfA2bHVmzpwdY1ZngN6VEDQ2qApWad8SNvaUFNJcVzIT+A5xZ544Kw+K4sJdUSkEkQCRot99H0GV1X4P8kuD9OBihO+RY+/JrEL/B1nSzvuDyZe5TlAE3T7Dj4Zhb/9sNgDW5mC/1QTQmmh77EAYRudPqiSgvVllNU97LwM64Wt1QOX5bmu/K81izRRO+ft0MATnvnMXAoPvOTRIvw0pDSnDRTLJG1fYa80VVBU6SbarsL3/VPb5OjG1hhE13VrmyLOzKjwI0IQtaeZoZludNWxtRSBvsl9HVcaVin3OnPULaIiJ8DRFB1F+TNc/HtlkzqUz4kOgD3o+Bv+9mQPuiIPcTahNjTpguAL0FG5nhOau8oohgcgvGvFcaaNDI8noTJ7ANpC6IXsEbRGyS4nAKPMR2WvZIDvhfYFtQPsRVNdWlOHi9Ug/QC6Cq+OioO56EROygV79wVwBjEefsm0hws1Zv8DAylghfQDfcXgmR260QLkGzt7w9zESh8kYzQYOGFcYdqt8W7Lkn449oD/qulQsOMv4Mos43LYl1elLJeRiVst8JsQ3Q9AlzaqzOGtNhJX5lWBMJWj3ERmIlb5CDknmGGSfH9pw6oiFgOWC8TupUU5QwSLTj7r39HgoU130XgUFGZgiIxRK3UKJGSLRpDw6j4SngNqnUUVs19+BzY/AYjM/xsbU6KplML/yP176/KDoGqFXQHcnv1W4apKKVcyEy4GVcHyw7FfwS43djJKi3Xu6lCOsJWUd5pzHsKufw0t8X/qAPYOjDfp0iJ4+lSn/wY6LoHC+gRyojpyOsqLutwrIMsake8Fvm9OQEXt7mvLN2xvB9t3X3OnBazrNl3lXo73lJ1D+M8wHYwSScjxmfRMQekCawkt1M+BPalwn5T9CUyh44ib2B5ILkeawTATOoBQ1q2miijzwMEUV2/hHKUR4HFQlbJ2oAA9kyGu6FbtKmkbGtNHpwU6cfdQHrIWBBhR7LUiKurMt+zGuCGq1gNSch6KYiBwKhSmIL4VLxgNA8dLPC9Ve3SGDOkURcV4BBRasYmoasWgah4cXaXatcoFoDIxr1XyPFhEAwyAMJne82BT494S+oEaAd8DlkrR+9uSfOeDr/ZgE32P2v06E/kQGroToQ/oMOsYHwu8oHOmA8oXOORv0WoSsd7kD1MXTf6upB32MeKS82SYDHGd4UN5hQywDEJ2TVVdNn0N4YALwBt2r3TcBDIYR867zBcFFIJKgV3h9gOqFaTwOonPpL1SYcA8FxRLV0SEltPQ+S0o7GJgvbQnWokMckG+pKgNXzO/KlLnEQzBSUg6H+Bv8NmuoJSXLTqPH0Hx5nLmbFqHm0HoPEofzIIzvkSPSvJhX4VO0/o7ZBvIfiAmtYOmFGZosAOhDLIpuh2IcByK6QTANeIrsVsUD6kwq5AKbcpnow3j/izkDiDOO1Aza0NvQC7HLz3gR/H3PPfiPKZ4iz+ziIQthOgkofMGSPVmeM8L3NpUdttKCPwrXPgRiauic2n7OTLYBfBkBqzR4lMe3ePtOfvxhh6bdKYK1TOW3+XabFvlq1zBJinETLo+mE0FeS90cEcFt4LikX3KPR7hLa1UVdzhxR19OTBD3HmmpXJ5pUQe36FDFLKyzoduu9ZB6kFYYBmuqWfhMtU9U+IomSej8HALDjZ0UNV+qYqX38Jnd7BPj+VUKeQGLn1NY9r8zblQaKplk4I5R23qqj0hRREZ5UQRT698GVWxOYeL6w1kQ5tUyj88tTxZ8bynSJfnSIHn121FqBoMLdYU/Tn0Vw2G1Xr0yuojDK+fMXWjMNctrqIXRd7MI6ZleznBo8rDctgXJ3qOo7I8Ulo9aPWvQCktiiqSJAh5cyiH6lMgUz1EciG4z2rhALW0iGoJQrPXA5D1tr3i1/Y9B0pvQ8cz3tTiomlLCguUw0k24Y25E9ggPWz490J8jEeltqg4zTu46MoyT9pwv2qvRF7ZQ2UWLzNEnX/KqL4AKgJhuaS08id1eFIhiRu8xK7GIbwa41fFqlFZ8TVc1ceji4TafGbRPGOR1nTYl1eJ2/KQVLUNMIo9MA55VqzVD62OEQ25Sw5UzzIL/UPcZW2WgagPmXheMd//bB3XYK9VZhGzg9BUtTYKm+wi1IqEqqnO1D9irpiGwAHll4/SxSUTiDK9U7PIZhUZJw2Ji1E/sq90UwUTNOygl7ss+7kTQmmce3iIE2zASbXfXH4GouPCx/gjuu0nrfcFuI6ynbHSODCr1zeM7squyKBBsp2HsM6gvYIetUv+eFURVtjGlwYGoqIKAOXsibwqcR4bg7yXNpdtI7QM9qv8DyvVTIfLaKMxYssDNHKJrfEEGXXxZCyJtpQtMIEAEg6MU5A7Y5CeCCClE6lybRB20MlgfsEZbavsxwDv/h33FKuoeg5rOkJR4Yqqj3NOZ881UfwpNmqSsXXY/yQL1oca2mXspxaSRqm1mUUHsMbcfHTgzoO3FznUMUDP6ghNAbIpF4b9HqUK36P6tD273cGdDa5nwPmFFQs8sIJQa5PCrONBW+jukEPlmI9tAgbbACwvqcMAawQOEGjl1mhpOWbvTvilg0VHH2q5YrvPkE3xzhKXvTb8hjdLAHPATyoYe/lRhV3Y4KDnygU9KsFNqQVvrO6BTfxoYZX/f2EXewxRG8ZD3G/Zfb6tVWKA3WT0wXseFRs77Mv1f0CcUeiBqa1cfcXUXANAXOVc56DHCRLlb0JhxdjMQOsDaxGbBSVzPK4Kfg1NyNp8VdhW2SaOENZTlmd482EXlP8SovwUFII01bArDnumkWH0/98F4W3ZSh9hli4+rFr0iiZK3I3S5IVVXoCNlvARnjq3ySu47hhlsacBWmCBh1DHSLkOk7tWnTnlBE0yoLj90OSXCkKvAwU4Mf4jyOedh7BXh/RWWIH7qkigMiS0jGd79gkU3AKrfkBnoR3neoHdwV7kMVWEjsNMyQJ+y4o/Q9QFpLcESpxPK9fsHOjvwTnHtLIkgX6Fwsf4BS+3/PJy708CaMZ+IUd8Ce5RcRXkmbz/tq6tMAzc9ArvV6UyVVvhJJAE70WAeo6LqGAlHL0C2yUao4yZJmMp/L1PE8Yj+v2BrnoZBrP0FKxy6kCEx5FSmTqTquIQRlJ+gC6fWVBJtwkW6crAKiq3LoQ4doHkmmXLKMYlSD+Dow0PmcI1FE2lIBoMsOfwRDf3FdNK5o1N+zFwHvFB9nbAJrZBbobl1RmNReywiqiqpqTSfH5r8TBdPUNtq77vgm08Rume0vJPQJe2jzkWTU246xz+urO3V3xQnJ8HZiF5WJmXmhCS3CeJUJtzL5BgHZN8Yg/lz2s5D2HHTUveEvz0ErnfjLSnoysmDqU7RJW2185ChxGCqhTjhuWfoCSi9vkP4hB+xWzkqVIfSqUPLaXsQ5LZVHhMp5dq92iN5vpumYfWEJRHmpTsXnUVurjXCdsd2bxS2nPw8Qxvn1t3N9fkzRxC1xzYLCn2dJFDS+3zTDWB3jR8muPIXod9ISsjxiWFXG2YJ9ruRhau6jes1xDRJXpVGnTF7jVFOjEzrckkMANN5/cRfpUOUAF8koYoQStsUUBF+yMdNpxFs61iqKfsxxGmE6UgPcbMIzZMUcSqcKIopYjtquZ0AFtC3gStGluDvK3SUkdoI5aQCv4eZV3+LdNHvzVrqLI0KKSYZxrmW21/iPBwi0XGgGpeKwPd4J9O65niuiLZVFPObfd91DPdt/+ZqonH9F1XmIIObnRxcdZuPqeBQaatXpYuZis+FN6+85SW618hunL5UgRGeYc9YNwPPL5JpiRVqlDx9m4V9L5ATl4A3oKdKyhIJeP7mIfwlGnCUtkp1Zwg0FjRSapFtgZLKA7qIAeuxtszfMdaPUascuCowy84/luGgaY90hDBebZAU0oimGTaIg1EocchRnYCeE27qIJtH/zRDXBVGx1sT0faKpxkMzqgM899sKcyJomS/YnpapS22ZPkiSUZHgozcdiXt4IpJ+d84QTY6pBmAEJQLOwe4mhj/hfIa1Cn0spw/G+Ajmegihon9J9L8sy6Em3p8wCaTv4rdsETO6jPp/DOsMzER4iyznzSSRZNu0nySxopL6A5CH87gxl6aagOFmM28DPdvMXa38H64YAMZXUU2u+/5fv1c5Sxl250rtiG5fbhuSWeUOrBP7LfnzCiAZxOk6gSl6LbJlATij56hfwvKnoVl4LDADbX/4Z6VFzpRAMB0JFcOhG8aushIKE2JiiHVS+jDtnwyINUwIpXaGDVyuhHJlKW1Ll+cuIfQdOQFcQkmvikWodQzpNZENiHzlq5hXSy2xN6VDUGH+BWXQAmx0IRvlqzZAmN0st4VCGeQG9FoWsJMA1ncRSNh6tJcNyE/yhdpsOpOpBj7V7eWFRnT/TTVYl5mo1OHy1vA3WKp/hz7vgDHbi35K1DxyH0WvACegwAjFbvhHKrOqtspTmApbiGrCFDdD9DuAIq+aB9E4VukYrht78Ao1+i4XdQwOenMMNHmlJYnuoVbSvsnAgcBFp6IjwGOkRg0PopsN/Kk/QUlL5iFp6y7Aac0UTkdBII503q/FkdBKcUwIIltFuwS991hsOBgQr0qYK+pmWP3fvo5JhGfk/brCV1qvkizKJaVDkLaG2qdh325fpjzN8ZCYBKw2ku7uDDpP2EloOamvyIa9QJH6oGd9RWzSFlTmwCWIhVG549L2Q7g7js7nJjssp7qgp6woWnUJUiUJ92+fsl/zamLa8ECB8D9441x+ixSQujIHhrY2cwy1hb2lRW5Fr7I9KzF+iICxs6gA0mEOlf0XbABrRfl+DSCLJKgZ47WHCcHpWR1GdzMvYVnPUAsc5aX46/0+U8hB3H0hgeSjdh41AJgWdwg4oO/5W1FAax2+aYbjtMwBbrMGw33Ji0rnM0gWLTZ3m8rukH1zUsNwM3cXptCC6ropBUuVbOBt8K7Kp40m2+E4Ti4Y1BHtfhN8QTptBu17CrSh+9tWudZL8OY+qo717r0snHc4xsGG8IQUh+ftlABlTQkmOM0rwZRTZu4FWiLHTwPXl0WKi/oDO/1okBtOY6F5kBlfPswJR4rwK5tE8XKkNpVfnUBv2hjnJJEKCriGIczgoofshVFY/g0AsVoY7xuJ/3ilZ4SBPam1AaRAfQoco6+aH4J4UZzWlXWLRWQAxO7u/Arym42ouM4AnvcbAmClFc8n2MfaNNIKEsBe/mnsJEynpQTUZKJHpoW6tYJbJ1WshLE9rgVizaiBUHGJ5UbZFRc2zCC2aoc4aQ4k9go2IOJpB2dNiX6z8D2T3/W6usjz8wNUXsOwSlU8caKCRDBnf5GLIFmcTdrNvDtinv7t/DYOkDgyN7pGggZZBUIfysnSh//yUzSZZmaNfYvjpk6pfH4JDJK5Cj3QVQUiVppHSDHpWiomAD9pDiQKbicWVgTdrYDnkyKkWgKsIxlabSmWG9vK/CRt732ExKhHvAyEvmUk1jhR9/TRteuy19Uk50dcWFB7vwjAMND68s7wKEeifyfIP43EH6TlsPYyuqgJCOAQuoKMMZCM6CLxv0YQC5AQFtQ1ENQvE7HgjugZYZe7BFe53YqAqfqUP8/D6QskoAtses3T5ThLKSgdOYnlc2EuKOz/4YuPOwU0WJH7tt7aAAwqM0barcsFrP2UkcUdVKUxuCt3avaOEL8H8D/1FjXEuDMdjljB6mjOCGHn6dYig666Kh1VGcJxz25e0BjwSraGoCvQUyBUgosftbD4RX8KAPquwpVeK2TU1FVZQ9EEkFbbTuq9mFYgF1AmkvVM7ahYdHkgidimgomY2pdQ2otbx/C8tU2ATkziHcocKxK0rf2QdDN6LXa9f9XE+AvGpdha1/E8AUxK8SUX6b0IpPRYsSjGDW3nXAatvsWl2ipQdt754H/TCSeCivYtQvWVhUUN0LfK/54apXDkRYB49uAWpCdfsUeLPaXrc9L0BSRTzdQK+KyrQNITac4gFSEUURq5LKAD7wx9bNebVAGge/IIRnD2cPlFRgSHFcZRs+hKwPYJYQz4zbvQpPreCxzSB6hP2c89gsT+hMhgvzPaALmXyaMdlljPBBpVmoagwrUl9cGMrk03qQIwR1xOMJ2n2EJn/9HVzZS4/rMMs7e6a5WcEqF7w9ZpshhvAtrTrsy3sAbGHMqB85Chjgfg+oQehxqSzeInR+ijebg1vW+EBuOMxAfb8b6HwMpY+gGd73V2CkcsNy5Dy1iOrwqGTmBp4f7SH6JMp6854ecNYlaWwKPV6CD6g9109/KrshDpu00x0AUCVopZikrZCiiwPspXnwJ3RrnI9uuivznak5Cm+0sA1kw3VaM8rVQ94U6CkdMPg5Uvyc5pQolLhrG+zOQ3gE4HRYjXsMAsil/QQsOuwuCMkyyMkyaOwqjWqE/37IX7FLLWGEwQ6SHOZd3DMK7FC8BUGOnzFVz8IBs1A7TBtDfXSwip7XEe8FO5C53Yn2Hwc6KqsoWryDXq9pkvpfyjboYUxuEB61FZ3iEwFW8gFht7yCUIZB+Ac+jprHw3hPQV8LfSLCdSp1ClZRnSA8d6r/M4lsj9AvWwSKNH9Cuw77ctwfTLyCbK/HllGyoawFVWEiFsGHspJzoIHGGNU51Z0wjk4ZbNodJUPgwi0kYAvF8kAHhGRRPf1c7rJ1H7/ewpYnfLhlGbf8iZXv0Y5N5pvhI9jtCu69ayvFr7VdqLAjZSb8wa5f0JTOM/LDyy8VISWVpPgHoiMssAz/DrJSGITHKZqUpr0xFJLWohPmUsmx75ArDyOP2J3WpwPIlx9hZOU4gQi1nMfSFC4JQgKOoBnFiXKJRlDxX3bcZEl68K7oOMLdUUi2gepZxFZg9aa4NaWbnCL/HFGf4VqojVKcZ5pKfypD+gIYamP5ySls0kszJ+iMJStpxwEXI3cH0GPdC1ii96AnK7RqYVWB1p6UDIyojSkh9FPuYieN9qtwmHpWLCunSb8UkLCZjqbZs9vPabWf/8oDL5tLa1OdcHXJ33NlT7AusY0cJ8PsLQkt/CAezqpXrn0FJP6OVRTge4FG3+B/RvtGTxGsDDTrt7slfnFh+m/DA9QPVo0hQ9Y4EJyfM/L09LUnlgKkDRntyU7UAk/l+qqKYtrnVxkngKbYpioCKUUSte4MnDEPXIpDY2PZDZOtwwg1nhm1Li0pOpDhKqNL2lmdnZALLil6YMkmVKFM596WmBLJqdvnjlx4HXzImL+Pb1IHDvv6/wCnfp2eUBryZQAAAABJRU5ErkJggg==\n", + "image/jpeg": "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\n" + }, + "metadata": {}, + "execution_count": 33 + } + ], + "source": [ + "show_hidden_weights(net2, 2)" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Issues with Categorization\n", + "\n", + "In Kate Crawford's book [1], she argues that every dataset used to train machine learning systems has a worldview:\n", + "\n", + "\"To create a training set is to take an almost infinitely complex and varied world and fix it into taxonomies composed of discrete classifications of individual data points, a process that requires inherently political, cultural, and social choices. By paying attention to these classifications, we can glimpse the various forms of power that are built into the architectures of AI world building\" (pgs. 135-136).\n", + "\n", + "Here are some questions to consider with respect to the faces data set we used in this notebook:\n", + "\n", + "* Do you think it was collected with the participants' consent and did they likely know it would be made publicly available?\n", + "* How diverse is the dataset in terms of gender, race, age, etc.?\n", + "* Is emotion something that is easily recognized in someone's facial expressions?\n", + "* What do you notice about how these images were posed?\n", + "* How well do you expect classifiers trained on this dataset would generalize to novel images that were not posed in the same way?\n" + ], + "metadata": { + "id": "MHbChDZwnYZE" + } + }, + { + "cell_type": "markdown", + "metadata": { + "id": "h9A6a4z3_cIa" + }, + "source": [ + "# Conclusion\n", + "\n", + "Most modern classification problems are much more complex than the ones we've explored here, involving hundreds of thousands of images and using networks with many more layers of neurons.\n", + "\n", + "We hope that by exploring these relatively simple classification tasks that you've gained a better understanding of how the hidden layer of a neural network is able to learn how to interpret the data in order to help it solve a classification task." + ] + }, + { + "cell_type": "markdown", + "source": [ + "# References\n", + "\n", + "[1] Kate Crawford (2021). *Atlas of AI*, Yale University Press." + ], + "metadata": { + "id": "5c9UgZ2PpfcG" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "63mSk6XIAglI" + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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" } - ], - "source": [ - "show_hidden_weights(net2, 2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "h9A6a4z3_cIa" - }, - "source": [ - "# Conclusion\n", - "\n", - "Most modern classification problems are much more complex than the ones we've explored here, involving hundreds of thousands of images and using networks with many more layers of neurons.\n", - "\n", - "We hope that by exploring these relatively simple classification tasks that you've gained a better understanding of how the hidden layer of a neural network is able to learn how to interpret the data in order to help it solve a classification task." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "63mSk6XIAglI" - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file From e1997edf28ee2d3c05c3f91dd0d925659fb916f5 Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Thu, 19 Sep 2024 16:03:05 -0400 Subject: [PATCH 17/37] updated intro with some additional information --- notebooks/IntroToJupyterNotebook.ipynb | 544 +++++++++++++------------ 1 file changed, 280 insertions(+), 264 deletions(-) diff --git a/notebooks/IntroToJupyterNotebook.ipynb b/notebooks/IntroToJupyterNotebook.ipynb index a5a401b..59a0246 100644 --- a/notebooks/IntroToJupyterNotebook.ipynb +++ b/notebooks/IntroToJupyterNotebook.ipynb @@ -1,278 +1,294 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Open" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8011iY_gErOE" - }, - "source": [ - "# Introduction to Jupyter Notebook\n", - "Through this notebook, we hope to provide a general introduction to `Jupyter Notebooks` to familiarize you, the user, with this platform. Here we will overview the basics of using a Jupyter notebook, preparing you for using other notebooks in the `aitk` collection." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kbGz67KtUFb3" - }, - "source": [ - "In our notebooks, you will typically have to run an install and import block at the very top before starting. **Press SHIFT+ENTER** to install the relevant libraries for this notebook to work. Once a cell is done executing, you should see a green check mark to the top left of the cell." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "BH7C-Qi7sZW2" + }, + "source": [ + "\"Open" + ] }, - "id": "KLUTQFkAUGNn", - "outputId": "53155423-46f1-4e1d-89e6-4f4b0204ff32" - }, - "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - ":3: MatplotlibDeprecationWarning: The seaborn styles shipped by Matplotlib are deprecated since 3.6, as they no longer correspond to the styles shipped by seaborn. However, they will remain available as 'seaborn-v0_8-\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QgZnzunCfg2P" + }, + "source": [ + "We also need to extract just the two digits of interest from the **testing** data." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gR8upW9NC4kP", + "outputId": "6619e49c-b3be-4d4a-80ef-24b93a501b5b" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "number of 3's: 750\n", + "number of 5's: 750\n" + ] + } ], - "text/plain": [ - "" + "source": [ + "new_test_x = []\n", + "new_test_y = []\n", + "num_digit1 = 0\n", + "num_digit2 = 0\n", + "\n", + "for i in range(len(test_x)):\n", + " if test_y[i] == digit1 and num_digit1 < 750:\n", + " new_test_x.append(test_x[i])\n", + " new_test_y.append([1,0])\n", + " num_digit1 += 1\n", + " elif test_y[i] == digit2 and num_digit2 < 750:\n", + " new_test_x.append(test_x[i])\n", + " new_test_y.append([0,1])\n", + " num_digit2 += 1\n", + "\n", + "new_test_x = np.array(new_test_x)\n", + "new_test_y = np.array(new_test_y)\n", + "\n", + "new_test_x_normalized = new_test_x/255\n", + "\n", + "print(\"number of %d's: %d\" % (digit1, num_digit1))\n", + "print(\"number of %d's: %d\" % (digit2, num_digit2))" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 5/5 loss: 0.007666457910090685 - tolerance_accuracy: 0.9516321420669556 - val_loss: 0.00848546251654625 - val_tolerance_accuracy: 0.948803186416626\n" - ] - } - ], - "source": [ - "history = net.fit(inputs, # new training examples\n", - " targets, # new training labels\n", - " verbose=1, # verbose output\n", - " validation_data=(new_test_x_normalized, # validation examples\n", - " new_test_y), # validation labels\n", - " epochs=5) # number of times to loop through the training set" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7vrCM2yO6hiH" - }, - "source": [ - "After training the network, **take note of the tolerance and value tolerance accuracy for every time you retrain the network with manipulated percentages**. The tolerance accuracy tells us how accurately the network has learned the data in the dataset it is trained on, where the value tolerance accuracy reports the accuracy of the network on the test dataset." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_-kae00F6mqw" - }, - "source": [ - "**val_tolerance_accuracy**: *enter here*\n", - "\n", - "**tolerance_accuracy**: *enter here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "q1amP4wiszlm" - }, - "source": [ - "###Testing the Network" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "M7d30W-9a3zL" - }, - "source": [ - "Now, we can look at inputs in the network to see if we get the expected output. For 4's, there should be a white block on the left and a black block on the right for the output. For 5's, there should be a black block on the left and a white block on the right for the output. For inputs that the network is having trouble recognizing, their output will not be clearly black or white in either of the two output blocks. Additionally, below the visualization of the network, when the test function is run, you can see percentages of certainty. The first number is the certainty that the digit is a four, and the second number is the certainty that the digit is a five." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": { - "id": "NASP8hqUQv_w" - }, - "outputs": [], - "source": [ - "from time import sleep\n", - "def test(net, n):\n", - " for i in range(n):\n", - " net.display(new_test_x_normalized[i])\n", - " outputs = net.propagate(new_test_x_normalized[i])\n", - " print(\", \".join([str(round(v,2)) for v in outputs]))\n", - " sleep(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TkhGLdInbZ2Z" - }, - "source": [ - "We are looking at the first ten inputs for the test dataset. You may notice some strange looking examples. The third one is a 5, but with a much smaller bottom half. The ninth one appears to be a quickly drawn 4 where the bottom half is missing." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 439 - }, - "id": "bXjwxNHPVsvD", - "outputId": "b0c2b50a-a624-43fa-8424-9b7fdd3fde7a" - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " Layer: output 'Dense'\n", - "Act function: softmax\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 2)outputLayer: hidden_2 'Dense'\n", - "Act function: sigmoid\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 25)hidden_2Layer: flatten_1 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 784)flatten_1Layer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 28, 28)]inputActivations for SimpleNetwork" + "cell_type": "markdown", + "metadata": { + "id": "_R8h4DaG5CZG" + }, + "source": [ + "Now that we have all of the data, we can begin to manipulate the balance within it. The next function allows us to specify how to split up the data." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "id": "BzAeR_EMwl_A" + }, + "outputs": [], + "source": [ + "def split_data(pct_digit1, pct_digit2):\n", + " assert pct_digit1+pct_digit2 == 1, \"percentages must sum to 1\"\n", + " num_digit1 = int(pct_digit1*(len(digit1_train_x)))\n", + " num_digit2 = int(pct_digit2*(len(digit2_train_x)))\n", + " shuffle(digit1_train_x)\n", + " shuffle(digit2_train_x)\n", + " print(\"%d train length: %d\" % (digit1, num_digit1))\n", + " print(\"%d train length: %d\" % (digit2, num_digit2))\n", + " inputs = digit1_train_x[:num_digit1] + digit2_train_x[:num_digit2]\n", + " targets = ([[1,0]]*num_digit1) + ([[0,1]]*num_digit2)\n", + " mix = list(zip(inputs, targets))\n", + " shuffle(mix)\n", + " inputs, targets = zip(*mix)\n", + "\n", + " inputs = np.array(inputs)/255\n", + " targets = np.array(targets)\n", + "\n", + " return inputs, targets\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nSSabDe77w55" + }, + "source": [ + "### Set percentages of each digit in the training set\n", + "\n", + "Now, **enter the percentages for both digits** that you want in the dataset as a decimal. To begin, we will look at a balanced dataset that is split 50/50 (which should be entered as 0.5 for both digits below).\n", + "\n", + "After seeing how that works, change the percentages and rerun all the code blocks below to see how the network changes. For example you might want to try a 70/30 split (which should be entered as .7 and .3 below)." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "id": "h3JoM5BJ4O77" + }, + "outputs": [], + "source": [ + "pct_digit1 = 0.5\n", + "pct_digit2 = 0.5" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GMsOZm_P8V2a" + }, + "source": [ + "Run the next code block to split up the data as you specified. Additionally, the quantities of each digit will be printed; verify that the numbers look correct given the percentages that you entered." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "11CCyt921x9f", + "outputId": "2fb06bbe-188f-4579-b36a-235c54896bbe" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "3 train length: 2500\n", + "5 train length: 2500\n" + ] + } ], - "text/plain": [ - "" + "source": [ + "inputs, targets = split_data(pct_digit1, pct_digit2)" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.0, 0.0\n" - ] - } - ], - "source": [ - "test(net, 10)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "5jVbLWuL9X7r" - }, - "source": [ - "As you can see from these examples, when the network's dataset is split evenly between 4's and 5's, it typically performs very well. It has an accuracy of around 95% on the test dataset (which it was not trained on) which is very significant. This shows that this network is very strong and effective at predicting whether a hand drawn digit is a 4 or a 5." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Pa6PBYpagOAh" - }, - "source": [ - "So far, we have created and shown the effectiveness of the neural network that is trained to assess whether a given digit in the test dataset is a 4 or 5. The network should perform with a high accuracy and confidence in its guesses for any given input digit. For some less clear examples, it may have a hard time distinguishing the digit, however, overall the network should perform with a good deal of accuracy when the dataset it is trained on has an equal number of 4's and 5's." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DJlbuEzjTZ_j" - }, - "source": [ - "Now, we can see how many total errors this network had and which specific digits it classified incorrectly. Run this next code block to have a summary of the errors the network made, as well as the percentage of errors that were 4's and percentage of errors that were 5's. **Note how many errors the network made and which digit it classified incorrectly most often.**" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "3xY3TF98O1re", - "outputId": "c687e623-e6fd-48c2-e687-5f22719764e5" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "number of digits classified incorrectly: 16\n", - "\n", - "\n", - "percentage of errors on 4's: 0.125\n", - "percentage of errors on 5's: 0.875\n" - ] - } - ], - "source": [ - "from numpy import argmax\n", - "outputs = net.predict(new_test_x_normalized)\n", - "answers = [argmax(output) for output in outputs]\n", - "newtargets = [argmax(target) for target in new_test_y]\n", - "incorrect = [i for i in range(len(answers)) if answers[i] != newtargets[i]]\n", - "print(\"number of digits classified incorrectly:\", len(incorrect))\n", - "missed_target = [targets[i] for i in incorrect]\n", - "wrong_answer = [answers[i] for i in incorrect]\n", - "per4 = 0\n", - "per5 = 0\n", - "for i in range(len(incorrect)):\n", - " if wrong_answer[i] == 1:\n", - " per4 += 1\n", - " else:\n", - " per5 += 1\n", - "\n", - "print(\"\\n\")\n", - "print(\"percentage of errors on 4's:\", per4/len(incorrect))\n", - "print(\"percentage of errors on 5's:\", per5/len(incorrect))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8KapHOuj-hNj" - }, - "source": [ - "*write observations here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "prn5CWDRT7Aw" - }, - "source": [ - "Prints gallery of digits that were classified incorrectly." - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 233 + "cell_type": "markdown", + "metadata": { + "id": "MBYsp30e8clA" + }, + "source": [ + "Now, we can see what a sample of 10 images from the new dataset looks like. The number of examples of each digit will vary based on the percentages you inputted." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 232 + }, + "id": "MTUkPmHPTrkc", + "outputId": "d30e6073-4e01-4506-cec4-02396cbe1876" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
" + ] + }, + "metadata": {} + } + ], + "source": [ + "images = [array_to_image(inputs[i]) for i in range(20)]\n", + "gallery(images)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eBcWmdw-ZzHF" + }, + "source": [ + "Here we are creating the neural network. We will utilize a simple network which first flattens the two-dimensional input, passes it through two hidden layers, and then on to the output layer." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "id": "ZYhRl_wAPRQr" + }, + "outputs": [], + "source": [ + "net = SimpleNetwork((28, 28), \"Flatten\", 25, 10, (2, \"softmax\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EhaHl4jNaptW" + }, + "source": [ + "Summarizing the network allows us to make sure it looks as it is expected. The total number of parameters gives you a good sense of the size of the network. Ours is less than 20 thousand, which is quite small by modern standard." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0T6j_nRrP6Xd", + "outputId": "af86be59-2b1e-4527-e117-0e2cfb686548" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Model: \"SimpleNetwork\"\n", + "_________________________________________________________________\n", + " Layer (type) Output Shape Param # \n", + "=================================================================\n", + " input (InputLayer) [(None, 28, 28)] 0 \n", + " \n", + " flatten_1 (Flatten) (None, 784) 0 \n", + " \n", + " hidden_2 (Dense) (None, 25) 19625 \n", + " \n", + " hidden_3 (Dense) (None, 10) 260 \n", + " \n", + " output (Dense) (None, 2) 22 \n", + " \n", + "=================================================================\n", + "Total params: 19907 (77.76 KB)\n", + "Trainable params: 19907 (77.76 KB)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "_________________________________________________________________\n" + ] + } + ], + "source": [ + "net.summary()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PHHfikpM4jAv" + }, + "source": [ + "This is a fairly simple task for the network so it only needs 10 epochs of training to achieve high accuracy (each epoch is one pass through the training data)." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 423 + }, + "id": "djrFqGdI0DG9", + "outputId": "d2ab048f-75a6-450c-d8e6-212f93bcca11" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2024-09-25T19:18:56.011642\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.7.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ] + }, + "metadata": {} + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Epoch 10/10 loss: 0.015097171068191528 - tolerance_accuracy: 0.9150079488754272 - val_loss: 0.01628131978213787 - val_tolerance_accuracy: 0.9147986769676208\n" + ] + } + ], + "source": [ + "history = net.fit(inputs, # new training examples\n", + " targets, # new training labels\n", + " verbose=1, # verbose output\n", + " validation_data=(new_test_x_normalized, # validation examples\n", + " new_test_y), # validation labels\n", + " epochs=10) # number of times to loop through the training set" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7vrCM2yO6hiH" + }, + "source": [ + "After training the network, **take note of the tolerance_accuracy and val_tolerance_accuracy for every time you retrain the network with manipulated percentages**.\n", + "\n", + "The tolerance accuracy reports the accuracy of the network on the **training** data\n", + "\n", + "The validation tolerance accuracy reports the accuracy of the network on the **testing** data." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_-kae00F6mqw" + }, + "source": [ + "**tolerance_accuracy**: *enter here*\n", + "\n", + "**val_tolerance_accuracy**: *enter here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "q1amP4wiszlm" + }, + "source": [ + "###Testing the Network" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "M7d30W-9a3zL" + }, + "source": [ + "Let's create a function to allow us to easily visualize how the trained network is doing on some sample inputs from the testing data." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "id": "NASP8hqUQv_w" + }, + "outputs": [], + "source": [ + "from time import sleep\n", + "def test(net, n):\n", + " for i in range(n):\n", + " net.display(new_test_x_normalized[i])\n", + " outputs = net.propagate(new_test_x_normalized[i])\n", + " print(\", \".join([str(round(v,2)) for v in outputs]))\n", + " sleep(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TkhGLdInbZ2Z" + }, + "source": [ + "In the visualization below, focus your attention on the two boxes in the final output layer.\n", + "\n", + "* When the network recognizes digit1 (which is initially 3), there should be a white block on the left and a black block on the right for the output.\n", + "\n", + "* When the network recognizes digit2 (which is initially 5), there should be a black block on the left and a white block on the right for the output.\n", + "\n", + "* For inputs that the network is having trouble recognizing, their output will not be clearly black or white in either of the two output blocks.\n", + "\n", + "Additionally, below the visualization of the network, when the test function is run, you can see percentages of certainty. The first number is the certainty that the digit is a digit1, and the second number is the certainty that the digit is a digit2." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 439 + }, + "id": "bXjwxNHPVsvD", + "outputId": "84db7c3c-890b-4a4a-f6a0-6a27b69e2f3f" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Layer: output 'Dense'\n", + "Act function: softmax\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 2)outputLayer: hidden_3 'Dense'\n", + "Act function: sigmoid\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 10)hidden_3Layer: hidden_2 'Dense'\n", + "Act function: sigmoid\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 25)hidden_2Layer: flatten_1 'Flatten'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 784)flatten_1Layer: input 'InputLayer'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = [(None, 28, 28)]inputActivations for SimpleNetwork" + ] + }, + "metadata": {} + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "0.01, 0.99\n" + ] + } + ], + "source": [ + "test(net, 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5jVbLWuL9X7r" + }, + "source": [ + "As you can see from these examples, when the network's dataset is split evenly between the two digits, it typically performs very well. It has an accuracy of around 91% on the test dataset (which it was not trained on). This shows that this network is effective at predicting whether a hand drawn digit is a digit1 vs digit2." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DJlbuEzjTZ_j" + }, + "source": [ + "Now, let's see how many total errors this network made and which specific digits it classified incorrectly.\n", + "\n", + "Run this next code block to see a summary of the errors the network made. **Note how many errors the network made and which digit it classified incorrectly most often.**" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3xY3TF98O1re", + "outputId": "9b2ce47e-dbf9-4069-fb8f-9cd4ecb5dc18" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "number of digits classified incorrectly: 34\n", + "percentage of errors on 3's: 0.38\n", + "percentage of errors on 5's: 0.62\n" + ] + } + ], + "source": [ + "from numpy import argmax\n", + "outputs = net.predict(new_test_x_normalized)\n", + "answers = [argmax(output) for output in outputs]\n", + "newtargets = [argmax(target) for target in new_test_y]\n", + "incorrect = [i for i in range(len(answers)) if answers[i] != newtargets[i]]\n", + "print(\"number of digits classified incorrectly:\", len(incorrect))\n", + "missed_target = [targets[i] for i in incorrect]\n", + "wrong_answer = [answers[i] for i in incorrect]\n", + "per_digit1 = 0\n", + "per_digit2 = 0\n", + "for i in range(len(incorrect)):\n", + " if wrong_answer[i] == 1:\n", + " per_digit1 += 1\n", + " else:\n", + " per_digit2 += 1\n", + "print(\"percentage of errors on %d's: %.2f\" % (digit1, per_digit1/len(incorrect)))\n", + "print(\"percentage of errors on %d's: %.2f\" % (digit2, per_digit2/len(incorrect)))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8KapHOuj-hNj" + }, + "source": [ + "*write observations here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "prn5CWDRT7Aw" + }, + "source": [ + "We can see a gallery of all of the digits that were classified incorrectly." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 338 + }, + "id": "5cCoTBS4Qf6a", + "outputId": "8c1a2503-2785-4277-bdff-7f18404f4452" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
" + ] + }, + "metadata": {} + } + ], + "source": [ + "images = [array_to_image(new_test_x[index]) for index in incorrect]\n", + "gallery(images)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Zkv9AeCqUBN8" + }, + "source": [ + "After seeing which digits were classified incorrectly, consider **why** this may have occurred and **how** the percentages that you inputted would have this effect." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "m5yUzT88wvbn" + }, + "source": [ + "*write observations here*" + ] }, - "id": "5cCoTBS4Qf6a", - "outputId": "d7bb537e-9c1c-455c-bbbc-ee229c7e7ff0" - }, - "outputs": [ { - "data": { - "text/html": [ - "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
" + "cell_type": "markdown", + "metadata": { + "id": "VClpaPuxhFCV" + }, + "source": [ + "###Changing Dataset Composition" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TrQSwWuKpJLp" + }, + "source": [ + "Now that we have shown how this network performs with an equal number of each digit in the dataset, we want to show how the performance and accuracy of the network changes when we manipulate the percentages in the dataset.\n", + "\n", + "**Return to where you entered the percentages of each digit in the training dataset and change the percentages from 50/50 to 70/30.**\n", + "\n", + "**Rerun all the code blocks from there (including the testing summary).**\n", + "\n", + "Describe the differences you saw in the results between the 50/50 split and the 70/30 split." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jkOV70V92MG7" + }, + "source": [ + "*write observations here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JdMZcsGhrrR1" + }, + "source": [ + "After you finish training your network with the first manipulated percentages, experiment with the percentages some more.\n", + "\n", + "How imbalanced does the training set have to be before the network is unable to distinguish between the two digits?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vLFyonP0xUwf" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "source": [ + "Also feel free to explore other digits besides 3 vs 5. To do this go back to the section where you set the digits to explore, and then rerun all of the code cells below that." ], - "text/plain": [ - "" + "metadata": { + "id": "2baQZImGkoxc" + } + }, + { + "cell_type": "markdown", + "source": [ + "*write any additional observations here*" + ], + "metadata": { + "id": "_py-OICBkobR" + } + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ud4JdXQg3qTU" + }, + "source": [ + "After finishing testing the different percentages that you can use to show varying levels of efficacy in the network, consider the potential broader implications of dataset composition before moving on to the next section.\n", + "\n", + "* How could having a specific subsection of data that an AI is trained on being underrepresented have very real world consequences?\n", + "* What possible issues and biases might arise with the human decision making that goes into the creation of datasets that are used to train these networks?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4Vy_cB1Z3r1a" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rZLQldMkMpaB" + }, + "source": [ + "## Implications of Dataset Composition\n", + "We have explored how manipulating a dataset can change a network's efficacy in a categorization task. Datasets, and thus their composition, is an essential component of neural networks. Below, we will explore how bias in a dataset's composition can lead to negative impacts on marginalized communities." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "j2dLUABn99b6" + }, + "source": [ + "Within the past couple of years, bias within algorithms and AI has begun to receive attention. Many computer scientists and researchers have begun to recognize inherent bias, known also as \"algorithmic prejudices,\" present in algorithms, software, machine learning, artificial intelligence, and nearly every facet of computer science (see reference [3]). Within the context of datasets that are used for the training of neural networks, bias is pervasive. This bias becomes particularly concerning as algorithms are beginning to take over human responsibilities (see reference [2]). For example, algorithms are now being used by US law enforcement for \"predictive justice\"(see reference [3]). These tools \"calculate the probability that a person will not show up for trial as scheduled or commit future crimes\"(see reference [3]). As these algorithms become increasingly present in our society, we must evaluate and consider their inherent biases." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "j7rFF1WhBCue" + }, + "source": [ + "A major contributor to the current movement exploring and combatting biases in algorithms is Joy Buolamwini. As a graduate student at MIT, Buolamwini co-wrote the paper \"Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification\" along with Timmit Gebru which explores the ways in which machine learning algorithms can discriminate based on classes like race and gender (see reference [1]). As Buolamwini describes in her Ted Talk, she was inspired to address bias in machine learning algorithms when as an undergraduate student at Georgia Tech a robot that was supposed to recognize faces could not detect her's, as a black woman. Among other findings, this paper revealed that while lighter-skinned males had an extremely low error rate of 0.8% while darker-skinned females had a significantly higher error rate of up to 34.7% (see reference [1])." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BFsKqbWBBG5l" + }, + "source": [ + "### Buolamwini's Work" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D8B-Nj2iJeuD" + }, + "source": [ + "In 2017, Buolamwini gave a Ted Talk demonstrating the discriminatory tendencies of widely used and accepted training sets and algorithms (see reference [2]). She refers to the concept of the \"coded gaze\" as algorithmic bias in the field of computer science. Within her talk, she dives deeper into the harms and discriminatory practices perpetrated by these training sets which are often severely lacking diversity. These practices include predictive policing. A study from the Georgetown Law Center showed that these police systems contain 1 in 2 adults in the US in a criminal facial recognition network (see reference [4]). These networks used by law enforcement have not been audited for accuracy and can result in misidentification of criminals, having a potentially serious consequence on the victim of this misidentification. With such serious stakes, it is essential to consider and address the biases of these algorithms and networks." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FS2tQbRTmuDk" + }, + "source": [ + "To see the Georgetown Law Center's full report on law enforcement's use of facial recognition and recommendations, please access this link: [Perpetual Line Up](https://www.perpetuallineup.org/)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "t89MgWox8_Cw" + }, + "source": [ + "Click here to watch Buolamwini's Ted Talk!\n", + "\n", + "[![IMAGE ALT TEXT](http://img.youtube.com/vi/UG_X_7g63rY/0.jpg)](https://www.youtube.com/watch?v=UG_X_7g63rY \"How I'm fighting bias in algorithms\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KE5-cmlYOJax" + }, + "source": [ + "#### Gender Shades" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0KTL9vzSOP3w" + }, + "source": [ + "\"Gender Shades\" tested 3 commercial gender classification systems (Microsoft, IBM, Face++) using a dataset specifically designed to determine the potential biases present in these systems (see reference [1]). The dataset (Pilot Parliaments Benchmark), specifically created for this study, was composed of faces of 1270 individuals from three African countries and three European countries. The individuals were each given skin type labels per the Fitzpatrick six-point labeling system and given gender labels, either female or male given the binary nature of the evaluation systems." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Q2rG1Jkrpkdq" + }, + "source": [ + "In evaluation of these classifiers, there were several main takeaways. Firstly, \"male subjects were more accurately classified than female subjects\"(reference [1] pg. 8). Additionally, lighter-skinned subjects were more accurately classified than those with darker skin. Further, all classifiers performed worst on darker female subjects (reference [1] pg. 8). Here is the complete summarized key findings as outlined in the study:\n", + "\n", + "* All classifiers perform better on male faces\n", + "than female faces (8.1% − 20.6% difference\n", + "in error rate)\n", + "\n", + "* All classifiers perform better on lighter faces\n", + "than darker faces (11.8% − 19.2% difference\n", + "in error rate)\n", + "\n", + "* All classifiers perform worst on darker female\n", + "faces (20.8% − 34.7% error rate)\n", + "\n", + "* Microsoft and IBM classifiers perform best on lighter male faces (error rates of 0.0% and 0.3% respectively)\n", + "\n", + "* Face++ classifiers perform best on darker\n", + "male faces (0.7% error rate)\n", + "\n", + "* The maximum difference in error rate between the best and worst classified groups is 34.4%\n", + "\n", + "(reference [1] pg. 8)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XtKuFsp3Wcz-" + }, + "source": [ + "Further, this paper emphasizes the complete inability of these commercial systems to recognize gender minorities as they are completely excluded from datasets and classification options. Buolamwini notes, \"The companies provide no documentation to clarify if their gender classification systems which provide sex labels are classifying gender identity or biological sex\"(reference [1] pg. 6). As she emphasizes, \"This reductionist view of gender does not adequately capture the complexities of gender or address trangender identities\"(reference [1] pg. 6). When using these systems it is important to consider the erasure they create of people of non binary gender identities." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ynHyOYb3tO2H" + }, + "source": [ + "Buolamwini and Gerbru's study \"Gender Shades\" brought to the forefront the inherent biases present in well-established commercial classifiers and marginalization of those with intersectional identities, particularly darker skinned women, in these algorithms. The consequences of these prejudices have the potential to only further harm people of intersectional minority identities who are already marginalized in our society. As companies continue to develop these tools, Buolamwini calls for \"inclusive benchmark datasets and subgroup accuracy reports\" which will be \"necessary to increase transparency and accountability in artificial intelligence\"(reference [1] pg. 12). Continuing into the development of these tools, there will need to be increased \"demographic and phenotypic transparency and accountability in artificial intelligence\"(reference [1] pg. 12)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UNEIAPryL3Ir" + }, + "source": [ + "To have a more comprehensive understanding of Buolamwini and her co-collabrator Timmit Gebru's research \"Gender Shades,\" you can read the full paper here: [Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification](https://proceedings.mlr.press/v81/buolamwini18a/buolamwini18a.pdf)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XUxhDr-tyhsd" + }, + "source": [ + "###Reflect" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eRu4xvL_ymAO" + }, + "source": [ + "After reading more about the biases present in machine learning algorithms, **how do you see your role as a member of a modern society in which the presence of these algorithms is only increasing? What are ways in which we can combat these biases?**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XmCFNjW111hN" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "upbZADR41UtC" + }, + "source": [ + "Go back to the data manipulation section of this notebook and take note of the accuracy percentage you recorded when either digit1s or digit2s were overrepresented (particularly for when the minority digit represented 7% or less of the dataset). **Why is accuracy alone not always a reliable parameter? What are the fallacies underlying reporting the \"accuracy\" of a system? How could this number impact systems' usage and our trust in them?** Think about Buolamwini's findings. Contextualize your answer accordingly." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4-MDz0om1z8Q" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ons3DJxzM2m6" + }, + "source": [ + "## Navigating Biases\n", + "Considering ways in which we can work towards a more inclusive computing community." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vqlxGkqDNGp_" + }, + "source": [ + "As we work towards a more inclusive and less prejudiced computer science sphere, we must consider these issues, recognize them in our processes, and change our practices. A major component of changing the presence of these biases and their impact is focusing on inclusive coding practices. As Buolamwini outlines in her Ted Talk, we must consider who codes, how we code, and why we code (see reference [2]). Having a more diverse community of coders that consider and prioritize the needs and experiences of marginalized communities is an important step in creating a more inclusive field and algorithms." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sT0MZd41gOUQ" + }, + "source": [ + "Within the Georgetown Law Center report, the writers emphasize a need for significant legislative and regulatory change (see reference [4]). Law enforcement's use of facial recognition has the potential to do real damage, if it has not already impacted countless individuals. The report suggests legislation should be passed to regulate these technologies including requiring reasonable suspicion to use facial recognition, only use mug shot databases, court approval to use ID photos and license photos, requiring probable cause to use surveillance footage, completely ban tracking individuals for free speech issues, and increase accuracy testing. Further, they suggest a complete reform to the FBI facial recognition systems. They argue that these systems must be transparent and held publicly accountable, releasing statistics relating to arrest numbers. Importantly, they call for testing of racial bias within these systems and datasets that reflect the diversity of the American population. All of these reforms are important to implement if we want to mitigate the potential harm that these law enforcement agencies can perpetuate against already vulnerable communities." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zas1mHMJfhkL" + }, + "source": [ + "As Buolamwini emphasizes at the end of her Ted Talk, we must create \"a world where technology works for all of us, not just some of us, a world where we value inclusion and center social change.\" To finish her talk, she poses a question: \"Will you join me in the fight?\" **After reading through this computational essay, consider why it is important to join this \"fight\"? What are your personal motivations behind creating a more inclusive computing space and why is it important?**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XNWsYkB7gJSo" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "k7baTbHH5lIl" + }, + "source": [ + "## References\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vW9gND485pXx" + }, + "source": [ + "[1] J. Buolamwini, “Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification,” MIT Media Lab. Accessed: Jul. 23, 2024. [Online]. Available: https://www.media.mit.edu/publications/gender-shades-intersectional-accuracy-disparities-in-commercial-gender-classification/\n", + "\n", + "[2] J. Buolamwini, How I’m fighting bias in algorithms, (1489075733). Accessed: Jul. 23, 2024. [Online Video]. Available: https://www.ted.com/talks/joy_buolamwini_how_i_m_fighting_bias_in_algorithms\n", + "\n", + "[3] M. S. Cataleta, “Humane Artificial Intelligence: The Fragility of Human Rights Facing AI,” East-West Center, 2020. Accessed: Jul. 23, 2024. [Online]. Available: https://www.jstor.org/stable/resrep25514\n", + "\n", + "[4] “The Perpetual Line-Up,” Perpetual Line Up. Accessed: Jul. 23, 2024. [Online]. Available: https://www.perpetuallineup.org/" ] - }, - "metadata": {}, - "output_type": "display_data" } - ], - "source": [ - "images = [array_to_image(new_test_x[index]) for index in incorrect]\n", - "gallery(images)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Zkv9AeCqUBN8" - }, - "source": [ - "After seeing which digits were classified incorrectly, consider **why** this may have occurred and **how** the percentage of 4's and 5's you inputted would have this effect." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "m5yUzT88wvbn" - }, - "source": [ - "*write observations here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "VClpaPuxhFCV" - }, - "source": [ - "###Changing Dataset Composition" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TrQSwWuKpJLp" - }, - "source": [ - "Now that we have shown how this network performs with an equal number of 4's and 5's in the dataset, we want to show how the performance and accuracy of the network changes when we manipulate the percentage of 4's and 5's in the dataset. **Return to where you entered the percentages of 4's and 5's in the training dataset and change the percentages, rerunning all the code blocks. To start, change the percentage of 4's to 25% and percentage of 5's to 75%.**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "HsXkC5tvwhgO" - }, - "source": [ - "After rebuilding and retraining the network on a new percentage distribution of 4's and 5's, we must retest the network. **Go back to the testing the network section and note how the digits are classified, what percentage of 4's and 5's you used, and any errors you notice.**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jkOV70V92MG7" - }, - "source": [ - "*write observations here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "JdMZcsGhrrR1" - }, - "source": [ - "After you finish training your network with the first manipulated percentage of 4's and 5's, change the values. You can increase or decrease the percentages, retraining the network, and observe how well the network is able to recognize 4's and 5's in the test dataset. First, see if you can find a balance of 4's and 5's where the majority of errors occur for only one of the digits. Then, see if you can find a percentage of 5's where all the 5's start being categorized as 4's or vice versa. **What are the tipping points?**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vLFyonP0xUwf" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "S1bgrSlUgPr1" - }, - "source": [ - "Ensure that you are overrepresenting 4's as well as 5's as you go back to rerun this network on manipulated percentages. **How does the network's accuracy change when you overrepresent one digit versus when you overrepresent the other?** *Note which digit was overrepresented and what percentages you used in your observations.*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Yeep3DTUgmuO" - }, - "source": [ - "*write observations here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ud4JdXQg3qTU" - }, - "source": [ - "After finishing testing the different percentages that you can use to show varying levels of efficacy in the network, consider the potential broader implications of dataset composition before moving on to the next section. **How could having a specific subsection of data that an AI is trained on being underrepresented have very real world consequences? What possible issues and biases can you see with human decision making that goes into creation of datasets that are used to train these networks?**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4Vy_cB1Z3r1a" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rZLQldMkMpaB" - }, - "source": [ - "## Implications of Dataset Composition\n", - "We have explored how manipulating a dataset can change a network's efficacy in recognition of specific numbers when they are under or overrepresented in a dataset. Datasets, and thus their composition, is an essential component of neural networks. Below, we will explore how bias in a dataset's composition can lead to negative impacts on marginalized communities." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "j2dLUABn99b6" - }, - "source": [ - "Within the past couple of years, bias within algorithms and AI has begun to receive attention. Many computer scientists and researchers have begun to recognize inherent bias, known also as \"algorithmic prejudices,\" present in algorithms, software, machine learning, artificial intelligence, and nearly every facet of computer science (see reference [3]). Within the context of datasets that are used for the training of neural networks, bias is pervasive. This bias becomes particularly concerning as algorithms are beginning to take over human responsibilities (see reference [2]). For example, algorithms are now being used by US law enforcement for \"predictive justice\"(see reference [3]). These tools \"calculate the probability that a person will not show up for trial as scheduled or commit future crimes\"(see reference [3]). As these algorithms become increasingly present in our society, we must evaluate and consider their inherent biases." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "j7rFF1WhBCue" - }, - "source": [ - "A major contributor to the current movement exploring and combatting biases in algorithms is Joy Buolamwini. As a graduate student at MIT, Buolamwini co-wrote the paper \"Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification\" along with Timmit Gebru which explores the ways in which machine learning algorithms can discriminate based on classes like race and gender (see reference [1]). As Buolamwini describes in her Ted Talk, she was inspired to address bias in machine learning algorithms when as an undergraduate student at Georgia Tech a robot that was supposed to recognize faces could not detect her's, as a black woman. Among other findings, this paper revealed that while lighter-skinned males had an extremely low error rate of 0.8% while darker-skinned females had a significantly higher error rate of up to 34.7% (see reference [1])." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BFsKqbWBBG5l" - }, - "source": [ - "### Buolamwini's Work" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "D8B-Nj2iJeuD" - }, - "source": [ - "In 2017, Buolamwini gave a Ted Talk demonstrating the discriminatory tendencies of widely used and accepted training sets and algorithms (see reference [2]). She refers to the concept of the \"coded gaze\" as algorithmic bias in the field of computer science. Within her talk, she dives deeper into the harms and discriminatory practices perpetrated by these training sets which are often severely lacking diversity. These practices include predictive policing. A study from the Georgetown Law Center showed that these police systems contain 1 in 2 adults in the US in a criminal facial recognition network (see reference [4]). These networks used by law enforcement have not been audited for accuracy and can result in misidentification of criminals, having a potentially serious consequence on the victim of this misidentification. With such serious stakes, it is essential to consider and address the biases of these algorithms and networks." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "FS2tQbRTmuDk" - }, - "source": [ - "To see the Georgetown Law Center's full report on law enforcement's use of facial recognition and recommendations, please access this link: [Perpetual Line Up](https://www.perpetuallineup.org/)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "t89MgWox8_Cw" - }, - "source": [ - "Click here to watch Buolamwini's Ted Talk!\n", - "\n", - "[![IMAGE ALT TEXT](http://img.youtube.com/vi/UG_X_7g63rY/0.jpg)](https://www.youtube.com/watch?v=UG_X_7g63rY \"How I'm fighting bias in algorithms\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "KE5-cmlYOJax" - }, - "source": [ - "#### Gender Shades" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "0KTL9vzSOP3w" - }, - "source": [ - "\"Gender Shades\" tested 3 commercial gender classification systems (Microsoft, IBM, Face++) using a dataset specifically designed to determine the potential biases present in these systems (see reference [1]). The dataset (Pilot Parliaments Benchmark), specifically created for this study, was composed of faces of 1270 individuals from three African countries and three European countries. The individuals were each given skin type labels per the Fitzpatrick six-point labeling system and given gender labels, either female or male given the binary nature of the evaluation systems." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Q2rG1Jkrpkdq" - }, - "source": [ - "In evaluation of these classifiers, there were several main takeaways. Firstly, \"male subjects were more accurately classified than female subjects\"(reference [1] pg. 8). Additionally, lighter-skinned subjects were more accurately classified than those with darker skin. Further, all classifiers performed worst on darker female subjects (reference [1] pg. 8). Here is the complete summarized key findings as outlined in the study:\n", - "\n", - "\"All classifiers perform better on male faces\n", - "than female faces (8.1% − 20.6% difference\n", - "in error rate)\n", - "\n", - "• All classifiers perform better on lighter faces\n", - "than darker faces (11.8% − 19.2% difference\n", - "in error rate)\n", - "\n", - "• All classifiers perform worst on darker female\n", - "faces (20.8% − 34.7% error rate)\n", - "\n", - "• Microsoft and IBM classifiers perform best\n", - "on lighter male faces (error rates of 0.0% and\n", - "0.3% respectively)\n", - "\n", - "• Face++ classifiers perform best on darker\n", - "male faces (0.7% error rate)\n", - "\n", - "• The maximum difference in error rate between the best and worst classified groups is\n", - "34.4%\" (reference [1] pg. 8)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XtKuFsp3Wcz-" - }, - "source": [ - "Further, this paper emphasizes the complete inability of these commercial systems to recognize gender minorities as they are completely excluded from datasets and classification options. Buolamwini notes, \"The companies provide no documentation to clarify if their gender classification systems which provide sex labels are classifying gender identity or biological sex\"(reference [1] pg. 6). As she emphasizes, \"This reductionist view of gender does not adequately capture the complexities of gender or address trangender identities\"(reference [1] pg. 6). When using these systems it is important to consider the erasure they create of people of non binary gender identities." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ynHyOYb3tO2H" - }, - "source": [ - "Buolamwini and Gerbru's study \"Gender Shades\" brought to the forefront the inherent biases present in well-established commercial classifiers and marginalization of those with intersectional identities, particularly darker skinned women, in these algorithms. The consequences of these prejudices have the potential to only further harm people of intersectional minority identities who are already marginalized in our society. As companies continue to develop these tools, Buolamwini calls for \"inclusive benchmark datasets and subgroup accuracy reports\" which will be \"necessary to increase transparency and accountability in artificial intelligence\"(reference [1] pg. 12). Continuing into the development of these tools, there will need to be increased \"demographic and phenotypic transparency and accountability in artificial intelligence\"(reference [1] pg. 12)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UNEIAPryL3Ir" - }, - "source": [ - "To have a more comprehensive understanding of Buolamwini and her co-collabrator Timmit Gebru's research \"Gender Shades,\" you can read the full paper here: [Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification](https://proceedings.mlr.press/v81/buolamwini18a/buolamwini18a.pdf)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XUxhDr-tyhsd" - }, - "source": [ - "###Reflect" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eRu4xvL_ymAO" - }, - "source": [ - "After reading more about the biases present in machine learning algorithms, **how do you see your role as a member of a modern society in which the presence of these algorithms is only increasing? What are ways in which we can combat these biases?**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XmCFNjW111hN" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "upbZADR41UtC" - }, - "source": [ - "Go back to the data manipulation section of this notebook and take note of the accuracy percentage you recorded when either fours or fives were overrepresented (particularly for when the minority digit represented 7% or less of the dataset). **Why is accuracy alone not always a reliable parameter? What are the fallacies underlying reporting the \"accuracy\" of a system? How could this number impact systems' usage and our trust in them?** Think about Buolamwini's findings. Contextualize your answer accordingly." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4-MDz0om1z8Q" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ons3DJxzM2m6" - }, - "source": [ - "## Navigating Biases\n", - "Considering ways in which we can work towards a more inclusive computing community." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vqlxGkqDNGp_" - }, - "source": [ - "As we work towards a more inclusive and less prejudiced computer science sphere, we must consider these issues, recognize them in our processes, and change our practices. A major component of changing the presence of these biases and their impact is focusing on inclusive coding practices. As Buolamwini outlines in her Ted Talk, we must consider who codes, how we code, and why we code (see reference [2]). Having a more diverse community of coders that consider and prioritize the needs and experiences of marginalized communities is an important step in creating a more inclusive field and algorithms." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sT0MZd41gOUQ" - }, - "source": [ - "Within the Georgetown Law Center report, the writers emphasize a need for significant legislative and regulatory change (see reference [4]). Law enforcement's use of facial recognition has the potential to do real damage, if it has not already impacted countless individuals. The report suggests legislation should be passed to regulate these technologies including requiring reasonable suspicion to use facial recognition, only use mug shot databases, court approval to use ID photos and license photos, requiring probable cause to use surveillance footage, completely ban tracking individuals for free speech issues, and increase accuracy testing. Further, they suggest a complete reform to the FBI facial recognition systems. They argue that these systems must be transparent and held publicly accountable, releasing statistics relating to arrest numbers. Importantly, they call for testing of racial bias within these systems and datasets that reflect the diversity of the American population. All of these reforms are important to implement if we want to mitigate the potential harm that these law enforcement agencies can perpetuate against already vulnerable communities." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zas1mHMJfhkL" - }, - "source": [ - "As Buolamwini emphasizes at the end of her Ted Talk, we must create \"a world where technology works for all of us, not just some of us, a world where we value inclusion and center social change.\" To finish her talk, she poses a question: \"Will you join me in the fight?\" **After reading through this computational essay, consider why it is important to join this \"fight\"? What are your personal motivations behind creating a more inclusive computing space and why is it important?**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNWsYkB7gJSo" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "k7baTbHH5lIl" - }, - "source": [ - "## References\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vW9gND485pXx" - }, - "source": [ - "[1] J. Buolamwini, “Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification,” MIT Media Lab. Accessed: Jul. 23, 2024. [Online]. Available: https://www.media.mit.edu/publications/gender-shades-intersectional-accuracy-disparities-in-commercial-gender-classification/\n", - "\n", - "[2] J. Buolamwini, How I’m fighting bias in algorithms, (1489075733). Accessed: Jul. 23, 2024. [Online Video]. Available: https://www.ted.com/talks/joy_buolamwini_how_i_m_fighting_bias_in_algorithms\n", - "\n", - "[3] M. S. Cataleta, “Humane Artificial Intelligence: The Fragility of Human Rights Facing AI,” East-West Center, 2020. Accessed: Jul. 23, 2024. [Online]. Available: https://www.jstor.org/stable/resrep25514\n", - "\n", - "[4] “The Perpetual Line-Up,” Perpetual Line Up. Accessed: Jul. 23, 2024. [Online]. Available: https://www.perpetuallineup.org/" - ] - } - ], - "metadata": { - "colab": { - "collapsed_sections": [ - "rZLQldMkMpaB", - "ons3DJxzM2m6", - "k7baTbHH5lIl" - ], - "provenance": [], - "toc_visible": true - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" + ], + "metadata": { + "colab": { + "collapsed_sections": [ + "rZLQldMkMpaB", + "ons3DJxzM2m6", + "k7baTbHH5lIl" + ], + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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" + } }, - "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" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file From 8a7c662fa33ba4bbb26c7d20a726651f5f9b4df8 Mon Sep 17 00:00:00 2001 From: Douglas Blank Date: Fri, 27 Sep 2024 12:19:06 -0400 Subject: [PATCH 22/37] Typo --- aitk/networks/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 9fd8977..d48228e 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -921,7 +921,7 @@ def predict_from(self, inputs, from_layer_name, to_layer_name): key = (tuple([from_layer_name]), to_layer_name) if key not in self._predict_models: from_layer = self[from_layer_name] - path = find_path(from_layer, to_layer_name) + path = find_path(self, from_layer, to_layer_name) # Input should be what next layer expects: input_shape = self[path[0]]._build_shapes_dict["input_shape"] current = input_layer = make_input_from_shape(input_shape) From 236f9c29078a65285f77e9bd747daf1f481d7d74 Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Mon, 30 Sep 2024 16:51:45 -0400 Subject: [PATCH 23/37] added tests for get_weights and topological_sort --- tests/test_networks/test_network_methods.py | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/test_networks/test_network_methods.py b/tests/test_networks/test_network_methods.py index bdaf6a2..dbd271e 100644 --- a/tests/test_networks/test_network_methods.py +++ b/tests/test_networks/test_network_methods.py @@ -8,6 +8,21 @@ # # ****************************************************** + +""" +Questions: + +In the file test_network.py, all test cases call connect +and compile before using the networks. However, the +constructors for both the Network and SimpleNetwork classes +call self.compile. And I would argue that the constructors +should also call self.connect by default. When would there ever +be a case when you wouldn't want the layers to be connected? + + +""" + + import numpy as np from tensorflow.keras.layers import Dense, InputLayer @@ -28,6 +43,20 @@ def test_set_weights(): output = network.propagate(inputs[i]) assert np.allclose(output, expected_outputs[i]) +def test_get_weights(): + network = SimpleNetwork(3, 2, 1) + network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) + # weights are returned in this order: + # input->hidden + # hidden->output + # hidden biases + # output biases + weights = network.get_weights() + assert len(weights[0]) == 3 + assert len(weights[1]) == 2 + assert len(weights[2]) == 2 + assert len(weights[3]) == 1 + def test_propagate_to(): network = SimpleNetwork(3, 2, 1) @@ -137,3 +166,23 @@ def test_train_from_set_weights(): for array in weights: actual_weights += list(array.flatten()) assert np.allclose(expected_weights, actual_weights) + + +def test_topological_sort(): + network = Network() + network.add(InputLayer([2], name="inputA")) + network.add(InputLayer([3], name="inputB")) + network.add(Dense(2, name="hiddenA")) + network.add(Dense(3, name="hiddenB")) + network.add(Dense(1, name="output")) + network.connect("inputA", "hiddenA") + network.connect("inputB", "hiddenB") + network.connect("hiddenA","output") + network.connect("hiddenB","output") + result = network.topological_sort(network._layers, + network._get_input_layers()) + names = [layer.name for layer in result] + assert names[0][:-1] == names[1][:-1] == "input" + assert names[2][:-1] == names[3][:-1] == "hidden" + assert names[4] == "output" + From 70ebbac3a6391b7b5cded65350757cf4d2dd8ac2 Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Tue, 1 Oct 2024 16:54:00 -0400 Subject: [PATCH 24/37] continue to add more tests --- tests/test_networks/test_network_methods.py | 111 ++++++++++++++++++-- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/tests/test_networks/test_network_methods.py b/tests/test_networks/test_network_methods.py index dbd271e..b31e0b0 100644 --- a/tests/test_networks/test_network_methods.py +++ b/tests/test_networks/test_network_methods.py @@ -9,24 +9,24 @@ # ****************************************************** -""" -Questions: +"""Questions: In the file test_network.py, all test cases call connect and compile before using the networks. However, the constructors for both the Network and SimpleNetwork classes -call self.compile. And I would argue that the constructors -should also call self.connect by default. When would there ever -be a case when you wouldn't want the layers to be connected? +call self.compile. So is the compile call necessary? +Should the constructors also call self.connect by default. When would +there ever be a case when you wouldn't want the layers to be +connected? """ import numpy as np from tensorflow.keras.layers import Dense, InputLayer - from aitk.networks import Network, SimpleNetwork +from aitk.utils import get_dataset def test_set_weights(): @@ -124,6 +124,21 @@ def test_predict_from(): assert np.allclose(actual_activations, expected_activations) +def test_propagate(): + network = SimpleNetwork(3, 2, 1) + network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) + inputs = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]] + expected_activations = [ + [0.53426534], + [0.5517651], + [0.5280447], + [0.44220227] + ] + for i in range(len(inputs)): + result = network.propagate(np.array(inputs[i])) + assert np.allclose(result, expected_activations[i]) + + def test_train_from_set_weights(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) @@ -169,6 +184,11 @@ def test_train_from_set_weights(): def test_topological_sort(): + # output + # / \ + # hiddenA hiddenB + # | | + # inputA inputB network = Network() network.add(InputLayer([2], name="inputA")) network.add(InputLayer([3], name="inputB")) @@ -179,10 +199,89 @@ def test_topological_sort(): network.connect("inputB", "hiddenB") network.connect("hiddenA","output") network.connect("hiddenB","output") + network.compile() result = network.topological_sort(network._layers, network._get_input_layers()) names = [layer.name for layer in result] assert names[0][:-1] == names[1][:-1] == "input" assert names[2][:-1] == names[3][:-1] == "hidden" assert names[4] == "output" + +def test_get_input_from_dataset(): + network = SimpleNetwork( + (6,6), + "Flatten", + 10, + (10, "softmax")) + test_inputs, test_targets = get_dataset("validate_6x6") + result = network.get_input_from_dataset(0, test_inputs) + diff = result - test_inputs[0] + assert np.count_nonzero(diff) == 0 + + +def test_get_target_from_dataset(): + network = SimpleNetwork( + (6,6), + "Flatten", + 10, + (10, "softmax")) + test_inputs, test_targets = get_dataset("validate_6x6") + result = network.get_target_from_dataset(0, test_targets) + diff = result - test_targets[0] + assert np.count_nonzero(diff) == 0 + +def test_get_input_from_banked_dataset(): + # outputA outputB + # \ / + # hidden + # / \ + # inputA inputB + network = Network() + network.add(InputLayer([2], name="inputA")) + network.add(InputLayer([3], name="inputB")) + network.add(Dense(4, name="hidden")) + network.add(Dense(1, name="outputA")) + network.add(Dense(2, name="outputB")) + network.connect("inputA", "hidden") + network.connect("inputB", "hidden") + network.connect("hidden","outputA") + network.connect("hidden","outputB") + network.compile() + inputs = [np.array([[0,0],[1,0],[1,1]]), + np.array([[0,0,0],[1,0,1],[1,1,1]])] + targets = [np.array([[0],[0],[1]]), + np.array([[0,0],[1,0],[1,1]])] + result = network.get_input_from_dataset(2, inputs) + diff = inputs[0][2] - result[0] + assert np.count_nonzero(diff) == 0 + diff = inputs[1][2] - result[1] + assert np.count_nonzero(diff) == 0 + + +def test_get_target_from_banked_dataset(): + # outputA outputB + # \ / + # hidden + # / \ + # inputA inputB + network = Network() + network.add(InputLayer([2], name="inputA")) + network.add(InputLayer([3], name="inputB")) + network.add(Dense(4, name="hidden")) + network.add(Dense(1, name="outputA")) + network.add(Dense(2, name="outputB")) + network.connect("inputA", "hidden") + network.connect("inputB", "hidden") + network.connect("hidden","outputA") + network.connect("hidden","outputB") + network.compile() + inputs = [np.array([[0,0],[1,0],[1,1]]), + np.array([[0,0,0],[1,0,1],[1,1,1]])] + targets = [np.array([[0],[0],[1]]), + np.array([[0,0],[1,0],[1,1]])] + result = network.get_target_from_dataset(2, inputs) + diff = inputs[0][2] - result[0] + assert np.count_nonzero(diff) == 0 + diff = inputs[1][2] - result[1] + assert np.count_nonzero(diff) == 0 From 4823e777fd5cdb6efa845f3cbd697496e2d59538 Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Wed, 2 Oct 2024 14:21:11 -0400 Subject: [PATCH 25/37] added another test --- tests/test_networks/test_network_methods.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_networks/test_network_methods.py b/tests/test_networks/test_network_methods.py index b31e0b0..774609c 100644 --- a/tests/test_networks/test_network_methods.py +++ b/tests/test_networks/test_network_methods.py @@ -43,6 +43,7 @@ def test_set_weights(): output = network.propagate(inputs[i]) assert np.allclose(output, expected_outputs[i]) + def test_get_weights(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) @@ -56,6 +57,14 @@ def test_get_weights(): assert len(weights[1]) == 2 assert len(weights[2]) == 2 assert len(weights[3]) == 1 + + +def test_get_weights_flat(): + network = SimpleNetwork(3, 2, 1) + original = [1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0] + network.set_weights(original) + weights = network.get_weights(flat=True) + assert np.allclose(weights, original) def test_propagate_to(): From 09bbbe855ed7bdbf338b186cb96f0d22b4b2e3e6 Mon Sep 17 00:00:00 2001 From: Lisa Meeden Date: Fri, 4 Oct 2024 16:23:01 -0400 Subject: [PATCH 26/37] fixed miscalculation of approximate number of mutations to expect per generation printed in summary --- aitk/algorithms/ga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aitk/algorithms/ga.py b/aitk/algorithms/ga.py index 3f2f2cf..57f2643 100644 --- a/aitk/algorithms/ga.py +++ b/aitk/algorithms/ga.py @@ -223,7 +223,7 @@ def evolve(self, generations, crossover_rate=0.7, mutation_rate=0.001, print(f"Maximum number of generations: {self.generations}") print(f" Elite percentage {self.elite_percent} ({elite_count}/{self.popSize} chromosomes per generation)") print(f" Crossover rate: {self.crossover_rate} (~{int((self.popSize - elite_count) * self.crossover_rate)}/{self.popSize - elite_count} crossovers per generation)") - print(f" Mutation rate: {self.mutation_rate} (~{int((self.popSize - elite_count) * self.length * self.mutation_rate * 2)}/{(self.popSize - elite_count) * self.length} genes per generation)") + print(f" Mutation rate: {self.mutation_rate} (~{int((self.popSize - elite_count) * self.length * self.mutation_rate)}/{(self.popSize - elite_count) * self.length} genes per generation)") if self.generation == 0: print("Evaluating initial population...") From 8f550cf6e1d28ed84e3d5e7a5ca5136c4a1766fb Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 09:20:01 -0400 Subject: [PATCH 27/37] Fixed find_path; moved to Network --- aitk/networks/network.py | 29 +++++++++++++++++++++++++++-- aitk/networks/utils.py | 22 ---------------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index d48228e..c2d23e4 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -26,7 +26,6 @@ from aitk.utils import array_to_image from .utils import ( - find_path, get_argument_bindings, get_array_shape, get_connections, @@ -921,7 +920,11 @@ def predict_from(self, inputs, from_layer_name, to_layer_name): key = (tuple([from_layer_name]), to_layer_name) if key not in self._predict_models: from_layer = self[from_layer_name] - path = find_path(self, from_layer, to_layer_name) + path = self.find_path(from_layer, to_layer_name) + if path is None: + raise Exception( + "no path between %r to %r" % (from_layer_name, to_layer_name) + ) # Input should be what next layer expects: input_shape = self[path[0]]._build_shapes_dict["input_shape"] current = input_layer = make_input_from_shape(input_shape) @@ -2749,6 +2752,28 @@ def set_tolerance(self, tolerance): ) self._tolerance = tolerance + def find_path(self, from_layer, to_layer_name): + """ + Breadth-first search to find shortest path + from from_layer to to_layer_name. + + Returns None if there is no path. + """ + # No need to put from_layer.name in path: + path = {} + path[from_layer.name] = [] + queue = [from_layer] + while queue: + current = queue.pop() + if current.name == to_layer_name: + return path[current.name] + else: + # expand: + for layer in self._get_layers_from(current.name): + path[layer.name] = path[current.name] + [layer.name] + queue.append(layer) + return None + class SimpleNetwork(Network): def __init__( diff --git a/aitk/networks/utils.py b/aitk/networks/utils.py index aae2496..45a40e1 100644 --- a/aitk/networks/utils.py +++ b/aitk/networks/utils.py @@ -113,28 +113,6 @@ def make_input_from_shape(shape): return Input(input_shape, name="input") -def find_path(network, from_layer, to_layer_name): - """ - Breadth-first search to find shortest path - from from_layer to to_layer_name. - - Returns None if there is no path. - """ - # No need to put from_layer.name in path: - from_layer.path = [] - queue = [from_layer] - while len(queue) > 0: - current = queue.pop() - if current.name == to_layer_name: - return current.path - else: - # expand: - for layer in network._get_layers_from(current.name): - layer.path = current.path + [layer.name] - queue.append(layer) - return None - - def scale_output_for_image(vector, minmax, truncate=False): """ Given an activation name (or something else) and an output From 6d17a5fa9ab2efd7082c2777ba39d60853d718f4 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 10:11:57 -0400 Subject: [PATCH 28/37] Removed aitk.keras --- aitk/keras/README.md | 93 - aitk/keras/__init__.py | 30 - aitk/keras/activations/README.md | 20 - aitk/keras/activations/__init__.py | 1 - aitk/keras/activations/activations.py | 627 --- aitk/keras/activations/img/plot.png | Bin 260252 -> 0 bytes aitk/keras/callbacks.py | 225 - aitk/keras/datasets/BUILD | 38 - aitk/keras/datasets/__init__.py | 2 - aitk/keras/datasets/boston_housing.py | 74 - aitk/keras/datasets/cifar.py | 42 - aitk/keras/datasets/cifar10.py | 107 - aitk/keras/datasets/cifar100.py | 92 - aitk/keras/datasets/fashion_mnist.py | 103 - aitk/keras/datasets/imdb.py | 184 - aitk/keras/datasets/mnist.py | 152 - aitk/keras/datasets/reuters.py | 163 - aitk/keras/datasets/utils.py | 871 ---- aitk/keras/initializers/README.md | 4 - aitk/keras/initializers/__init__.py | 1 - aitk/keras/initializers/initializers.py | 264 - aitk/keras/layers/README.md | 20 - aitk/keras/layers/__init__.py | 4324 ----------------- aitk/keras/losses/README.md | 10 - aitk/keras/losses/__init__.py | 8 - aitk/keras/losses/losses.py | 946 ---- aitk/keras/metrics.py | 71 - aitk/keras/models/README.md | 10 - aitk/keras/models/__init__.py | 540 -- aitk/keras/models/vae.py | 453 -- aitk/keras/models/w2v.py | 451 -- aitk/keras/models/wgan_gp.py | 528 -- aitk/keras/modules/README.md | 10 - aitk/keras/modules/__init__.py | 1 - aitk/keras/modules/modules.py | 1427 ------ aitk/keras/numpy_ml_utils/README.md | 38 - aitk/keras/numpy_ml_utils/__init__.py | 6 - aitk/keras/numpy_ml_utils/data_structures.py | 522 -- aitk/keras/numpy_ml_utils/distance_metrics.py | 132 - aitk/keras/numpy_ml_utils/graphs.py | 363 -- aitk/keras/numpy_ml_utils/kernels.py | 344 -- aitk/keras/numpy_ml_utils/testing.py | 150 - aitk/keras/numpy_ml_utils/windows.py | 156 - aitk/keras/optimizers/README.md | 8 - aitk/keras/optimizers/__init__.py | 1 - aitk/keras/optimizers/optimizers.py | 498 -- aitk/keras/preprocessing/README.md | 24 - aitk/keras/preprocessing/__init__.py | 3 - aitk/keras/preprocessing/dsp.py | 848 ---- aitk/keras/preprocessing/general.py | 388 -- aitk/keras/preprocessing/nlp.py | 1229 ----- aitk/keras/schedulers/README.md | 13 - aitk/keras/schedulers/__init__.py | 1 - aitk/keras/schedulers/img/plot.png | Bin 383571 -> 0 bytes aitk/keras/schedulers/schedulers.py | 362 -- aitk/keras/utils/README.md | 14 - aitk/keras/utils/__init__.py | 8 - aitk/keras/utils/utils.py | 1052 ---- aitk/keras/wrappers/README.md | 5 - aitk/keras/wrappers/__init__.py | 258 - 60 files changed, 18315 deletions(-) delete mode 100644 aitk/keras/README.md delete mode 100644 aitk/keras/__init__.py delete mode 100644 aitk/keras/activations/README.md delete mode 100644 aitk/keras/activations/__init__.py delete mode 100644 aitk/keras/activations/activations.py delete mode 100644 aitk/keras/activations/img/plot.png delete mode 100644 aitk/keras/callbacks.py delete mode 100644 aitk/keras/datasets/BUILD delete mode 100644 aitk/keras/datasets/__init__.py delete mode 100644 aitk/keras/datasets/boston_housing.py delete mode 100644 aitk/keras/datasets/cifar.py delete mode 100644 aitk/keras/datasets/cifar10.py delete mode 100644 aitk/keras/datasets/cifar100.py delete mode 100644 aitk/keras/datasets/fashion_mnist.py delete mode 100644 aitk/keras/datasets/imdb.py delete mode 100644 aitk/keras/datasets/mnist.py delete mode 100644 aitk/keras/datasets/reuters.py delete mode 100644 aitk/keras/datasets/utils.py delete mode 100644 aitk/keras/initializers/README.md delete mode 100644 aitk/keras/initializers/__init__.py delete mode 100644 aitk/keras/initializers/initializers.py delete mode 100644 aitk/keras/layers/README.md delete mode 100644 aitk/keras/layers/__init__.py delete mode 100644 aitk/keras/losses/README.md delete mode 100644 aitk/keras/losses/__init__.py delete mode 100644 aitk/keras/losses/losses.py delete mode 100644 aitk/keras/metrics.py delete mode 100644 aitk/keras/models/README.md delete mode 100644 aitk/keras/models/__init__.py delete mode 100644 aitk/keras/models/vae.py delete mode 100644 aitk/keras/models/w2v.py delete mode 100644 aitk/keras/models/wgan_gp.py delete mode 100644 aitk/keras/modules/README.md delete mode 100644 aitk/keras/modules/__init__.py delete mode 100644 aitk/keras/modules/modules.py delete mode 100644 aitk/keras/numpy_ml_utils/README.md delete mode 100644 aitk/keras/numpy_ml_utils/__init__.py delete mode 100644 aitk/keras/numpy_ml_utils/data_structures.py delete mode 100644 aitk/keras/numpy_ml_utils/distance_metrics.py delete mode 100644 aitk/keras/numpy_ml_utils/graphs.py delete mode 100644 aitk/keras/numpy_ml_utils/kernels.py delete mode 100644 aitk/keras/numpy_ml_utils/testing.py delete mode 100644 aitk/keras/numpy_ml_utils/windows.py delete mode 100644 aitk/keras/optimizers/README.md delete mode 100644 aitk/keras/optimizers/__init__.py delete mode 100644 aitk/keras/optimizers/optimizers.py delete mode 100644 aitk/keras/preprocessing/README.md delete mode 100644 aitk/keras/preprocessing/__init__.py delete mode 100644 aitk/keras/preprocessing/dsp.py delete mode 100644 aitk/keras/preprocessing/general.py delete mode 100644 aitk/keras/preprocessing/nlp.py delete mode 100644 aitk/keras/schedulers/README.md delete mode 100644 aitk/keras/schedulers/__init__.py delete mode 100644 aitk/keras/schedulers/img/plot.png delete mode 100644 aitk/keras/schedulers/schedulers.py delete mode 100644 aitk/keras/utils/README.md delete mode 100644 aitk/keras/utils/__init__.py delete mode 100644 aitk/keras/utils/utils.py delete mode 100644 aitk/keras/wrappers/README.md delete mode 100644 aitk/keras/wrappers/__init__.py diff --git a/aitk/keras/README.md b/aitk/keras/README.md deleted file mode 100644 index 0521ade..0000000 --- a/aitk/keras/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Neural network models -This module implements building-blocks for larger neural network models in the -Keras-style. This module does _not_ implement a general autograd system in order -emphasize conceptual understanding over flexibility. - -1. **Activations**. Common activation nonlinearities. Includes: - - Rectified linear units (ReLU) ([Hahnloser et al., 2000](http://invibe.net/biblio_database_dyva/woda/data/att/6525.file.pdf)) - - Leaky rectified linear units - ([Maas, Hannun, & Ng, 2013](https://ai.stanford.edu/~amaas/papers/relu_hybrid_icml2013_final.pdf)) - - Exponential linear units (ELU) ([Clevert, Unterthiner, & Hochreiter, 2016](http://arxiv.org/abs/1511.07289)) - - Scaled exponential linear units ([Klambauer, Unterthiner, & Mayr, 2017](https://arxiv.org/pdf/1706.02515.pdf)) - - Softplus units - - Hard sigmoid units - - Exponential units - - Hyperbolic tangent (tanh) - - Logistic sigmoid - - Affine - -2. **Losses**. Common loss functions. Includes: - - Squared error - - Categorical cross entropy - - VAE Bernoulli loss ([Kingma & Welling, 2014](https://arxiv.org/abs/1312.6114)) - - Wasserstein loss with gradient penalty ([Gulrajani et al., 2017](https://arxiv.org/pdf/1704.00028.pdf)) - - Noise contrastive estimation (NCE) loss ([Gutmann & Hyvärinen](https://www.cs.helsinki.fi/u/ahyvarin/papers/Gutmann10AISTATS.pdf); [Minh & Teh, 2012](https://www.cs.toronto.edu/~amnih/papers/ncelm.pdf)) - -3. **Wrappers**. Layer wrappers. Includes: - - Dropout ([Srivastava, et al., 2014](http://www.jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf)) - -4. **Layers**. Common layers / layer-wise operations that can be composed to - create larger neural networks. Includes: - - Fully-connected - - Sparse evolutionary ([Mocanu et al., 2018](https://www.nature.com/articles/s41467-018-04316-3)) - - Dot-product attention ([Luong, Pho, & Manning, 2015](https://arxiv.org/pdf/1508.04025.pdf); [Vaswani et al., 2017](https://arxiv.org/pdf/1706.03762.pdf)) - - 1D and 2D convolution (with stride, padding, and dilation) ([van den Oord et al., 2016](https://arxiv.org/pdf/1609.03499.pdf); [Yu & Kolton, 2016](https://arxiv.org/pdf/1511.07122.pdf)) - - 2D "deconvolution" (with stride and padding) ([Zeiler et al., 2010](https://www.matthewzeiler.com/mattzeiler/deconvolutionalnetworks.pdf)) - - Restricted Boltzmann machines (with CD-_n_ training) ([Smolensky, 1996](http://stanford.edu/~jlmcc/papers/PDP/Volume%201/Chap6_PDP86.pdf); [Carreira-Perpiñán & Hinton, 2005](http://www.cs.toronto.edu/~fritz/absps/cdmiguel.pdf)) - - Elementwise multiplication - - Embedding - - Summation - - Flattening - - Softmax - - Max & average pooling - - 1D and 2D batch normalization ([Ioffe & Szegedy, 2015](http://proceedings.mlr.press/v37/ioffe15.pdf)) - - 1D and 2D layer normalization ([Ba, Kiros, & Hinton, 2016](https://arxiv.org/pdf/1607.06450.pdf)) - - Recurrent ([Elman, 1990](https://crl.ucsd.edu/~elman/Papers/fsit.pdf)) - - Long short-term memory (LSTM) ([Hochreiter & Schmidhuber, 1997](http://www.bioinf.jku.at/publications/older/2604.pdf)) - -5. **Optimizers**. Common modifications to stochastic gradient descent. - Includes: - - SGD with momentum ([Rummelhart, Hinton, & Williams, 1986](https://www.cs.princeton.edu/courses/archive/spring18/cos495/res/backprop_old.pdf)) - - AdaGrad ([Duchi, Hazan, & Singer, 2011](http://jmlr.org/papers/volume12/duchi11a/duchi11a.pdf)) - - RMSProp ([Tieleman & Hinton, 2012](http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf)) - - Adam ([Kingma & Ba, 2015](https://arxiv.org/pdf/1412.6980v8.pdf)) - -6. **Learning Rate Schedulers**. Common learning rate decay schedules. - - Constant - - Exponential decay - - Noam/Transformer scheduler ([Vaswani et al., 2017](https://arxiv.org/pdf/1706.03762.pdf)) - - King/Dlib scheduler ([King, 2018](http://blog.dlib.net/2018/02/automatic-learning-rate-scheduling-that.html)) - -6. **Initializers**. Common weight initialization strategies. - - Glorot/Xavier uniform and normal ([Glorot & Bengio, 2010](http://jmlr.org/proceedings/papers/v9/glorot10a/glorot10a.pdf)) - - He/Kaiming uniform and normal ([He et al., 2015](https://arxiv.org/pdf/1502.01852v1.pdf)) - - Standard normal - - Truncated normal - -7. **Modules**. Common multi-layer blocks that appear across many deep networks. - Includes: - - Bidirectional LSTMs ([Schuster & Paliwal, 1997](https://pdfs.semanticscholar.org/4b80/89bc9b49f84de43acc2eb8900035f7d492b2.pdf)) - - ResNet-style "identity" (i.e., `same`-convolution) residual blocks ([He et al., 2015](https://arxiv.org/pdf/1512.03385.pdf)) - - ResNet-style "convolutional" (i.e., parametric) residual blocks ([He et al., 2015](https://arxiv.org/pdf/1512.03385.pdf)) - - WaveNet-style residual block with dilated causal convolutions ([van den Oord et al., 2016](https://arxiv.org/pdf/1609.03499.pdf)) - - Transformer-style multi-headed dot-product attention ([Vaswani et al., 2017](https://arxiv.org/pdf/1706.03762.pdf)) - -8. **Models**. Well-known network architectures. Includes: - - `vae.py`: Bernoulli variational autoencoder ([Kingma & Welling, 2014](https://arxiv.org/abs/1312.6114)) - - `wgan_gp.py`: Wasserstein generative adversarial network with gradient - penalty ([Gulrajani et al., 2017](https://arxiv.org/pdf/1704.00028.pdf); -[Goodfellow et al., 2014](https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf)) - - `w2v.py`: word2vec model with CBOW and skip-gram architectures and - training via noise contrastive estimation ([Mikolov et al., 2012](https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf)) - -8. **Utils**. Common helper functions, primarily for dealing with CNNs. - Includes: - - `im2col` - - `col2im` - - `conv1D` - - `conv2D` - - `dilate` - - `deconv2D` - - `minibatch` - - Various weight initialization utilities - - Various padding and convolution arithmetic utilities diff --git a/aitk/keras/__init__.py b/aitk/keras/__init__.py deleted file mode 100644 index 9accc14..0000000 --- a/aitk/keras/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# ************************************************************** -# aitk.keras: A Python Keras model API -# -# Copyright (c) 2021 AITK Developers -# -# https://github.com/ArtificialIntelligenceToolkit/aitk.keras -# -# ************************************************************** - -"""A module of basic building blcoks for constructing neural networks""" -from . import utils -from . import losses -from . import activations -from . import schedulers -from . import optimizers -from . import wrappers -from . import layers -from . import initializers -from . import modules -from . import models -from . import datasets - -import sys -import numpy - -# Create a fake module "backend" that is really numpy -backend = numpy -backend.image_data_format = lambda: 'channels_last' -sys.modules["aitk.keras.backend"] = backend diff --git a/aitk/keras/activations/README.md b/aitk/keras/activations/README.md deleted file mode 100644 index 6287b59..0000000 --- a/aitk/keras/activations/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Activation Functions -The `activations` module implements several common activation functions: - -- Rectified linear units (ReLU) ([Hahnloser et al., 2000](http://invibe.net/biblio_database_dyva/woda/data/att/6525.file.pdf)) -- Leaky rectified linear units - ([Maas, Hannun, & Ng, 2013](https://ai.stanford.edu/~amaas/papers/relu_hybrid_icml2013_final.pdf)) -- Exponential linear units ([Clevert, Unterthiner, & Hochreiter, 2016](https://arxiv.org/pdf/1511.07289.pdf)) -- Scaled exponential linear units ([Klambauer, Unterthiner, & Mayr, 2017](https://arxiv.org/pdf/1706.02515.pdf)) -- Softplus units -- Hard sigmoid units -- Exponential units -- Hyperbolic tangent (tanh) -- Logistic sigmoid -- Affine - - -## Plots -

- -

diff --git a/aitk/keras/activations/__init__.py b/aitk/keras/activations/__init__.py deleted file mode 100644 index 8ba160e..0000000 --- a/aitk/keras/activations/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .activations import * diff --git a/aitk/keras/activations/activations.py b/aitk/keras/activations/activations.py deleted file mode 100644 index f2c1949..0000000 --- a/aitk/keras/activations/activations.py +++ /dev/null @@ -1,627 +0,0 @@ -"""A collection of activation function objects for building neural networks""" - -from abc import ABC, abstractmethod - -import numpy as np - - -class ActivationBase(ABC): - def __init__(self, **kwargs): - """Initialize the ActivationBase object""" - super().__init__() - - def __call__(self, z): - """Apply the activation function to an input""" - if z.ndim == 1: - z = z.reshape(1, -1) - return self.fn(z) - - @abstractmethod - def fn(self, z): - """Apply the activation function to an input""" - raise NotImplementedError - - @abstractmethod - def grad(self, x, **kwargs): - """Compute the gradient of the activation function wrt the input""" - raise NotImplementedError - - -class Sigmoid(ActivationBase): - def __init__(self): - """A logistic sigmoid activation function.""" - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "Sigmoid" - - def fn(self, z): - r""" - Evaluate the logistic sigmoid, :math:`\sigma`, on the elements of input `z`. - - .. math:: - - \sigma(x_i) = \frac{1}{1 + e^{-x_i}} - """ - return 1 / (1 + np.exp(-z)) - - def grad(self, x): - r""" - Evaluate the first derivative of the logistic sigmoid on the elements of `x`. - - .. math:: - - \frac{\partial \sigma}{\partial x_i} = \sigma(x_i) (1 - \sigma(x_i)) - """ - fn_x = self.fn(x) - return fn_x * (1 - fn_x) - - def grad2(self, x): - r""" - Evaluate the second derivative of the logistic sigmoid on the elements of `x`. - - .. math:: - - \frac{\partial^2 \sigma}{\partial x_i^2} = - \frac{\partial \sigma}{\partial x_i} (1 - 2 \sigma(x_i)) - """ - fn_x = self.fn(x) - return fn_x * (1 - fn_x) * (1 - 2 * fn_x) - - -class ReLU(ActivationBase): - """ - A rectified linear activation function. - - Notes - ----- - "ReLU units can be fragile during training and can "die". For example, a - large gradient flowing through a ReLU neuron could cause the weights to - update in such a way that the neuron will never activate on any datapoint - again. If this happens, then the gradient flowing through the unit will - forever be zero from that point on. That is, the ReLU units can - irreversibly die during training since they can get knocked off the data - manifold. - - For example, you may find that as much as 40% of your network can be "dead" - (i.e. neurons that never activate across the entire training dataset) if - the learning rate is set too high. With a proper setting of the learning - rate this is less frequently an issue." [*]_ - - References - ---------- - .. [*] Karpathy, A. "CS231n: Convolutional neural networks for visual recognition". - """ - - def __init__(self): - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "ReLU" - - def fn(self, z): - r""" - Evaulate the ReLU function on the elements of input `z`. - - .. math:: - - \text{ReLU}(z_i) - &= z_i \ \ \ \ &&\text{if }z_i > 0 \\ - &= 0 \ \ \ \ &&\text{otherwise} - """ - return np.clip(z, 0, np.inf) - - def grad(self, x): - r""" - Evaulate the first derivative of the ReLU function on the elements of input `x`. - - .. math:: - - \frac{\partial \text{ReLU}}{\partial x_i} - &= 1 \ \ \ \ &&\text{if }x_i > 0 \\ - &= 0 \ \ \ \ &&\text{otherwise} - """ - return (x > 0).astype(int) - - def grad2(self, x): - r""" - Evaulate the second derivative of the ReLU function on the elements of - input `x`. - - .. math:: - - \frac{\partial^2 \text{ReLU}}{\partial x_i^2} = 0 - """ - return np.zeros_like(x) - - -class LeakyReLU(ActivationBase): - """ - 'Leaky' version of a rectified linear unit (ReLU). - - Notes - ----- - Leaky ReLUs [*]_ are designed to address the vanishing gradient problem in - ReLUs by allowing a small non-zero gradient when `x` is negative. - - Parameters - ---------- - alpha: float - Activation slope when x < 0. Default is 0.3. - - References - ---------- - .. [*] Mass, L. M., Hannun, A. Y, & Ng, A. Y. (2013). "Rectifier - nonlinearities improve neural network acoustic models". *Proceedings of - the 30th International Conference of Machine Learning, 30*. - """ - - def __init__(self, alpha=0.3): - self.alpha = alpha - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "Leaky ReLU(alpha={})".format(self.alpha) - - def fn(self, z): - r""" - Evaluate the leaky ReLU function on the elements of input `z`. - - .. math:: - - \text{LeakyReLU}(z_i) - &= z_i \ \ \ \ &&\text{if } z_i > 0 \\ - &= \alpha z_i \ \ \ \ &&\text{otherwise} - """ - _z = z.copy() - _z[z < 0] = _z[z < 0] * self.alpha - return _z - - def grad(self, x): - r""" - Evaluate the first derivative of the leaky ReLU function on the elements - of input `x`. - - .. math:: - - \frac{\partial \text{LeakyReLU}}{\partial x_i} - &= 1 \ \ \ \ &&\text{if }x_i > 0 \\ - &= \alpha \ \ \ \ &&\text{otherwise} - """ - out = np.ones_like(x) - out[x < 0] *= self.alpha - return out - - def grad2(self, x): - r""" - Evaluate the second derivative of the leaky ReLU function on the - elements of input `x`. - - .. math:: - - \frac{\partial^2 \text{LeakyReLU}}{\partial x_i^2} = 0 - """ - return np.zeros_like(x) - - -class Tanh(ActivationBase): - def __init__(self): - """A hyperbolic tangent activation function.""" - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "Tanh" - - def fn(self, z): - """Compute the tanh function on the elements of input `z`.""" - return np.tanh(z) - - def grad(self, x): - r""" - Evaluate the first derivative of the tanh function on the elements - of input `x`. - - .. math:: - - \frac{\partial \tanh}{\partial x_i} = 1 - \tanh(x)^2 - """ - return 1 - np.tanh(x) ** 2 - - def grad2(self, x): - r""" - Evaluate the second derivative of the tanh function on the elements - of input `x`. - - .. math:: - - \frac{\partial^2 \tanh}{\partial x_i^2} = - -2 \tanh(x) \left(\frac{\partial \tanh}{\partial x_i}\right) - """ - tanh_x = np.tanh(x) - return -2 * tanh_x * (1 - tanh_x ** 2) - - -class Affine(ActivationBase): - def __init__(self, slope=1, intercept=0): - """ - An affine activation function. - - Parameters - ---------- - slope: float - Activation slope. Default is 1. - intercept: float - Intercept/offset term. Default is 0. - """ - self.slope = slope - self.intercept = intercept - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "Affine(slope={}, intercept={})".format(self.slope, self.intercept) - - def fn(self, z): - r""" - Evaluate the Affine activation on the elements of input `z`. - - .. math:: - - \text{Affine}(z_i) = \text{slope} \times z_i + \text{intercept} - """ - return self.slope * z + self.intercept - - def grad(self, x): - r""" - Evaluate the first derivative of the Affine activation on the elements - of input `x`. - - .. math:: - - \frac{\partial \text{Affine}}{\partial x_i} = \text{slope} - """ - return self.slope * np.ones_like(x) - - def grad2(self, x): - r""" - Evaluate the second derivative of the Affine activation on the elements - of input `x`. - - .. math:: - - \frac{\partial^2 \text{Affine}}{\partial x_i^2} = 0 - """ - return np.zeros_like(x) - - -class Identity(Affine): - def __init__(self): - """ - Identity activation function. - - Notes - ----- - :class:`Identity` is just syntactic sugar for :class:`Affine` with - slope = 1 and intercept = 0. - """ - super().__init__(slope=1, intercept=0) - - def __str__(self): - """Return a string representation of the activation function""" - return "Identity" - - -class ELU(ActivationBase): - def __init__(self, alpha=1.0): - r""" - An exponential linear unit (ELU). - - Notes - ----- - ELUs are intended to address the fact that ReLUs are strictly nonnegative - and thus have an average activation > 0, increasing the chances of internal - covariate shift and slowing down learning. ELU units address this by (1) - allowing negative values when :math:`x < 0`, which (2) are bounded by a value - :math:`-\alpha`. Similar to :class:`LeakyReLU`, the negative activation - values help to push the average unit activation towards 0. Unlike - :class:`LeakyReLU`, however, the boundedness of the negative activation - allows for greater robustness in the face of large negative values, - allowing the function to avoid conveying the *degree* of "absence" - (negative activation) in the input. [*]_ - - Parameters - ---------- - alpha : float - Slope of negative segment. Default is 1. - - References - ---------- - .. [*] Clevert, D. A., Unterthiner, T., Hochreiter, S. (2016). "Fast - and accurate deep network learning by exponential linear units - (ELUs)". *4th International Conference on Learning - Representations*. - """ - self.alpha = alpha - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "ELU(alpha={})".format(self.alpha) - - def fn(self, z): - r""" - Evaluate the ELU activation on the elements of input `z`. - - .. math:: - - \text{ELU}(z_i) - &= z_i \ \ \ \ &&\text{if }z_i > 0 \\ - &= \alpha (e^{z_i} - 1) \ \ \ \ &&\text{otherwise} - """ - # z if z > 0 else alpha * (e^z - 1) - return np.where(z > 0, z, self.alpha * (np.exp(z) - 1)) - - def grad(self, x): - r""" - Evaluate the first derivative of the ELU activation on the elements - of input `x`. - - .. math:: - - \frac{\partial \text{ELU}}{\partial x_i} - &= 1 \ \ \ \ &&\text{if } x_i > 0 \\ - &= \alpha e^{x_i} \ \ \ \ &&\text{otherwise} - """ - # 1 if x > 0 else alpha * e^(z) - return np.where(x > 0, np.ones_like(x), self.alpha * np.exp(x)) - - def grad2(self, x): - r""" - Evaluate the second derivative of the ELU activation on the elements - of input `x`. - - .. math:: - - \frac{\partial^2 \text{ELU}}{\partial x_i^2} - &= 0 \ \ \ \ &&\text{if } x_i > 0 \\ - &= \alpha e^{x_i} \ \ \ \ &&\text{otherwise} - """ - # 0 if x > 0 else alpha * e^(z) - return np.where(x >= 0, np.zeros_like(x), self.alpha * np.exp(x)) - - -class Exponential(ActivationBase): - def __init__(self): - """An exponential (base e) activation function""" - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "Exponential" - - def fn(self, z): - r""" - Evaluate the activation function - - .. math:: - \text{Exponential}(z_i) = e^{z_i} - """ - return np.exp(z) - - def grad(self, x): - r""" - Evaluate the first derivative of the exponential activation on the elements - of input `x`. - - .. math:: - - \frac{\partial \text{Exponential}}{\partial x_i} = e^{x_i} - """ - return np.exp(x) - - def grad2(self, x): - r""" - Evaluate the second derivative of the exponential activation on the elements - of input `x`. - - .. math:: - - \frac{\partial^2 \text{Exponential}}{\partial x_i^2} = e^{x_i} - """ - return np.exp(x) - - -class SELU(ActivationBase): - r""" - A scaled exponential linear unit (SELU). - - Notes - ----- - SELU units, when used in conjunction with proper weight initialization and - regularization techniques, encourage neuron activations to converge to - zero-mean and unit variance without explicit use of e.g., batchnorm. - - For SELU units, the :math:`\alpha` and :math:`\text{scale}` values are - constants chosen so that the mean and variance of the inputs are preserved - between consecutive layers. As such the authors propose weights be - initialized using Lecun-Normal initialization: :math:`w_{ij} \sim - \mathcal{N}(0, 1 / \text{fan_in})`, and to use the dropout variant - :math:`\alpha`-dropout during regularization. [*]_ - - See the reference for more information (especially the appendix ;-) ). - - References - ---------- - .. [*] Klambauer, G., Unterthiner, T., & Hochreiter, S. (2017). - "Self-normalizing neural networks." *Advances in Neural Information - Processing Systems, 30.* - """ - - def __init__(self): - self.alpha = 1.6732632423543772848170429916717 - self.scale = 1.0507009873554804934193349852946 - self.elu = ELU(alpha=self.alpha) - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "SELU" - - def fn(self, z): - r""" - Evaluate the SELU activation on the elements of input `z`. - - .. math:: - - \text{SELU}(z_i) = \text{scale} \times \text{ELU}(z_i, \alpha) - - which is simply - - .. math:: - - \text{SELU}(z_i) - &= \text{scale} \times z_i \ \ \ \ &&\text{if }z_i > 0 \\ - &= \text{scale} \times \alpha (e^{z_i} - 1) \ \ \ \ &&\text{otherwise} - """ - return self.scale * self.elu.fn(z) - - def grad(self, x): - r""" - Evaluate the first derivative of the SELU activation on the elements - of input `x`. - - .. math:: - - \frac{\partial \text{SELU}}{\partial x_i} - &= \text{scale} \ \ \ \ &&\text{if } x_i > 0 \\ - &= \text{scale} \times \alpha e^{x_i} \ \ \ \ &&\text{otherwise} - """ - return np.where( - x >= 0, np.ones_like(x) * self.scale, np.exp(x) * self.alpha * self.scale, - ) - - def grad2(self, x): - r""" - Evaluate the second derivative of the SELU activation on the elements - of input `x`. - - .. math:: - - \frac{\partial^2 \text{SELU}}{\partial x_i^2} - &= 0 \ \ \ \ &&\text{if } x_i > 0 \\ - &= \text{scale} \times \alpha e^{x_i} \ \ \ \ &&\text{otherwise} - """ - return np.where(x > 0, np.zeros_like(x), np.exp(x) * self.alpha * self.scale) - - -class HardSigmoid(ActivationBase): - def __init__(self): - """ - A "hard" sigmoid activation function. - - Notes - ----- - The hard sigmoid is a piecewise linear approximation of the logistic - sigmoid that is computationally more efficient to compute. - """ - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "Hard Sigmoid" - - def fn(self, z): - r""" - Evaluate the hard sigmoid activation on the elements of input `z`. - - .. math:: - - \text{HardSigmoid}(z_i) - &= 0 \ \ \ \ &&\text{if }z_i < -2.5 \\ - &= 0.2 z_i + 0.5 \ \ \ \ &&\text{if }-2.5 \leq z_i \leq 2.5 \\ - &= 1 \ \ \ \ &&\text{if }z_i > 2.5 - """ - return np.clip((0.2 * z) + 0.5, 0.0, 1.0) - - def grad(self, x): - r""" - Evaluate the first derivative of the hard sigmoid activation on the elements - of input `x`. - - .. math:: - - \frac{\partial \text{HardSigmoid}}{\partial x_i} - &= 0.2 \ \ \ \ &&\text{if } -2.5 \leq x_i \leq 2.5\\ - &= 0 \ \ \ \ &&\text{otherwise} - """ - return np.where((x >= -2.5) & (x <= 2.5), 0.2, 0) - - def grad2(self, x): - r""" - Evaluate the second derivative of the hard sigmoid activation on the elements - of input `x`. - - .. math:: - - \frac{\partial^2 \text{HardSigmoid}}{\partial x_i^2} = 0 - """ - return np.zeros_like(x) - - -class SoftPlus(ActivationBase): - def __init__(self): - """ - A softplus activation function. - - Notes - ----- - In contrast to :class:`ReLU`, the softplus activation is differentiable - everywhere (including 0). It is, however, less computationally efficient to - compute. - - The derivative of the softplus activation is the logistic sigmoid. - """ - super().__init__() - - def __str__(self): - """Return a string representation of the activation function""" - return "SoftPlus" - - def fn(self, z): - r""" - Evaluate the softplus activation on the elements of input `z`. - - .. math:: - - \text{SoftPlus}(z_i) = \log(1 + e^{z_i}) - """ - return np.log(np.exp(z) + 1) - - def grad(self, x): - r""" - Evaluate the first derivative of the softplus activation on the elements - of input `x`. - - .. math:: - - \frac{\partial \text{SoftPlus}}{\partial x_i} = \frac{e^{x_i}}{1 + e^{x_i}} - """ - exp_x = np.exp(x) - return exp_x / (exp_x + 1) - - def grad2(self, x): - r""" - Evaluate the second derivative of the softplus activation on the elements - of input `x`. - - .. math:: - - \frac{\partial^2 \text{SoftPlus}}{\partial x_i^2} = - \frac{e^{x_i}}{(1 + e^{x_i})^2} - """ - exp_x = np.exp(x) - return exp_x / ((exp_x + 1) ** 2) diff --git a/aitk/keras/activations/img/plot.png b/aitk/keras/activations/img/plot.png deleted file mode 100644 index e77a10f65b8adc87e326a321962a47a1f5708eac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260252 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 z 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�mjDMTd?U*e(#0a;>V)Xroox*-T?+S6$dDEmko@=0FKr7 zflbmQb|X|U@ZO+8%l=?lrrcr_6Il9o_4EvR?G2W`m3BY_fPwSG2YWrG(eoSJAw|T>at-7G^yNkZ1K!ysnCMk3s8#m~Gx?OiXRn?NkX1@9w zPMou>SloWR{gal2wj~5)7_0-JT4N#czS2WCDj&F~Wc9;k(+;cvBO3Yxwmi&C7GyO= zBv1zIaD;221k9oFe}HJreI!~$1qfAlWA6iz_Z2xzJ8MS=l;@Ks86BRoB+9*lx(?^= z3;U8dEY-R4#(#S~b*#$%5gpQGw&zCop}Y6-@%@4IX9n;|+&emwP*A}AWx~-BK^_KL z3#iD=L8f*A1|{SkAt4&X4a^xHNSDB3tOIsIh@hMv9h)t0@mk3gSJee71Z562c^2tM@f5N6WFj6?dQmY~NJ~=DElhnKF z?9jD)O{qNQY8yuVo(pw$;Yln@0CU5AFd|jCx)O+jNJKn=liAi(1r`t&cD%pqMH~pz zFmFU+-C_K)hh(8|m@B^W%?4IQWR1PNP+9&971rT%1IC(I%3Dv!lM%3w&V)4ebQO zL=nsTQg?v!iMU-};)?VLG<2{HBk=67czW9nj8+44YSuP*#g_x~@_I^6Tc3;CrBL_S zG|TH!I6r4PSt%fZN3O;%nCX47vli6>?8377KLsrG#5p|`yNH?Jk3|a0!ucAdLCg5y zBQNT?CIYDM0?NbmxhJ(MfyDXGxW?zmX7L~5Mt!gQu+rSqE&5tvyU%tKkA zAUL&?O6l+M!Nn$^vh-X?>u@lk zI}A*Zg7WYX0cT4oQBeqR-@~Q0ExsQW#>{&_{kXb)5D;URzsHv`92{q-=93Z4#_rIw zM~425(Ze5S|Fblf|HX|Ocr`Hy@~{DJGzL*gC;Nk=MMzn{fJ=g zw+Vmi8;H^@THcR3EWI`}bPol{xgz@IyTp`8>EMY4DRCPAJcd&`dg6PRKKF8R9*wOQ z9UTZI&iiP(*yR3J8jRaGHbi}CjxBt?~* zq16oV9{?3C0dt~J(b3a^xB6#J^ZQ`P%<6^)*u@A6*%p@o)oPbIeMf-zMkjV%nFAtE zwQiZ_;FFg1ghx3jwI283WLK3@7zy2CYXC9TCCcxOP*g76Z=a$rX3T3l6p%z+2pGJ( zHUian!4J(N7!&&*EL{cK){(6BuQx0d<2;xJFGyZ0WGR~-Lqz0J`*sRu&aBTNZ6?SI z_IS?Ral7I%)=msK&(9K^@t! z=OJ9gC2@P~v@i*V@Oi z7l#$FU4OR&)RPfJ9w`>pcluzLQIM>Orwc%ZKcVCMg!K{K;rrJgJsHcU*o;-_ep~b5 zj_wb*{DEP&Tx1Aj`~_17oehMXfI88^SnKH3m(d{C_-sp`ozXOVv$eLdArf*KG#?L& zYPZ(?8g%M`D|>@xeSh@u#FX$Zu{~RGi0Pkn^|$8b2`(hK@?;=EsW)x4awi{OTQN`aj@7lh{xR#eI-VJX$w3K7hkL zYM`q|p{MNy$!67aaI^6%A})ec1YdZRyQGjrzzW+vq`^DI^{(%qtP)x7?=}vAikiAs zTv{QMdL^)VgRyz`tU{p4UESbx&L3t2&i1nZ#Jqy|@*5E=A3Rl(=^i+#qhf1_Cs2FF%E7Ji9asWM4s}Dymt8U4Qv{nDy`>-2qI82&VMWW?0k;^al zTpqCA#mhU zwRNd6G*l{jHZCzpr)Lr42d=X-<_UqoCuMIKjMHj{17nhIKVi!iy*xlapd*@KJ$NiY zTG850vHPBgM)_y#H3D)vK6xAsybusy*k#bh`80V4&xTgcyD1$D{AjPmPR}%9q7$6jc4o zCE~Ly^_iT!d<+~7*059C>{d;w50GtK)Z;iPHKX(D%^`lX+W&ihhIwdhop^%3>>Rb}m4&|0|S2&u{)GtZ7vW zV9jgpyfV)*OY!ALn`uup@S+qxXgtEGM4a{h=i6DsXvwIkwl}W?DoEh2+cSZ(Nl(+Bqx8=Xv1%IM z7dP|6tF2s&TLJBP>Dv^!Hf~uYaofvwt#Ij$Bl>Lb6VR8lx$eL%BY@rO*wQAIZpkT9 z_;fXY;Q0I}a_CUTnRd2$;mq6_dx(tF%lhGRDFr+Igxm(MKccQSpN)iQ2thQ&)az8* z=l@o_ctzz7Fv5k=c8uaP>E{J~zQyXK^@21H{jJieheXE2lu9VlYinyMv|LNr`(j}U zMV{h8^L>RZZzLV{^DxOY+}tfZ+a)?zgUeAQy^zI^?R~hRtZj&0qCfK(pW_<8h9Ajf zdu6vXG~O+@Y2*JitGO}Avt{Li)?{WSA;eb5C@0y%;0-p_A&=G9ojjK02B)z?CR(|v zuDtv~zu@67u1-+2QbZO8!#0M&tb{;-IT!(_*)=fRH-G6~6cN+Yo|fnX0>9KanDU2e z#s&PdG$+Z5Fafx#YQ~{%r_wXPhQCg`hG8QrVL^}zZmA8BEWKPL+Tr1yY#e+9gsKs) zn4y?Qo#miX7~~cQl>!vh6is7daRXy!*Io2%pYu|T0h4^3L~h7dT!fzG-dq|Y+VQM& zl|-(Yr61{v9_8U!ym;TOR)D@sC?$w6w#F~@bo1MJs$(a+!kJ##^UHByIDaOw)<-~O zS5p5H(Tc0wN?@3nSbm^$VfmFzX?>P&nK1lY+NLvLf_Ejz8H2F3H8b^2Dk$qwTSt|> z%#uV6#uOYkpu{8m`S_3r!@4;E{E0OtH(4K1A&%YZ`u6hJzXkj1PhRy8-E+I0jmi?Z z{anO2kLFl0`*A)5rAQ2D0{Z?a;^>~Pp=j59wjo=HXDG!Ec}TU%^JtEmv_Dz#3DZ9< zOQ_|C6+ms`2P^FBmsoDUO&6$hHXc~q;lVJxil%AvIZJK}&gulgrWPSSUBQM1|MvI7 zgK2+t2Tk#cEod>-``??gI7rUzqv@jRz@(^0OiWAwRSN+#Y-;&&@!hF__~P2t=gH$W z&%1zi_4%p455h*$Oq4x4WT5uqw^t9}=KqKscrNFMG<@Sx6B0xqb~&>L^Q|ylKq6 z82FqWX#cb|IMEI7;YS^Ust;1Fp%>UuC7f3y{8j@4^-Pn7&VggH2_hnl^C{8U%ciUk zY@7e@yI|YIA&6~L+4Y&);|}E{>7lfm#h{~aY*_`ZURhuK;v2&nzz_TjkjNj%C5|w- z+f;H%{oIqzb%*8H0Ph-@&i3ZkE>cSYc39qGvaoFzUZ%2SKy^shFkN*v*#DDo+`z~$ z;VMqv|D3(_b5)ZH!;U<;U(*g`c}r>AYxuO<_;h34iO56huobRlgNPGZu*dZhxN zJdw}#X;KTQGn2Jd1Sr0O8{F(aWw_N71uuF*n^&rE-@|-xAfu$ga5jW_I}IHjl!^)o zOh0IE4a7o%QF?g+@&iGhg0~_pm%x3!R}3gN&QEqD4_Djko&a)B(xc=47PkuKxXp=q z=s+OFC$ zft$CWeyD*UnA6b=90B2$yWu7WbtoANlmowV=Zg4RX0|5L%pGb^iSofQy0^GnA94mV zhXuiK;lEk{m6=*y5u>J*YQ}AJOEITi-d6zhI&Ua1=LD?=E_jf=cXA?C=dx`FW|hf( zd4>k2>58kVJ@m7r^b$PiqCQ2ir(j&#V=gX2@W|%W+8T_u`OIxi0^CIqa0!8Y!U3%^ zwAM#|_guqq+n+Qg=Ck-sz2Q60(2hSYkZP_toSn?@f8Za7My$87Zl>=14|j!i^A7tx1H9Wp&0Lx7t<2Zo=gKHkR6Wyrl4a9=;gm?5-;hEJemmlH{3MvZ zP(=UL=x~7lnkx};v?CjPPE+=(WmN6wdX*EeuYkIeuHV{DL|w)IHpvGpona9H%+qRa z`}~!o^_yQBAu=#&8dg?30y;U-=g*Ony$)oorpoVvA#o^x>v;zTc%XvU>aAcJ6FwQ) z2QZ%S=D5bs4}$n!%`1!|O;b(2B4E-tDv-edo1g~(BOJPs6JO>7jxHgws_v~7q^kN( zFmgt|s76H7N(h?PzL-1!0A#&oYehcWi~8}O7!8z}KTlF5hNKV_JH_H|K;-UrsIQ0@ z0jL0BG~hTzTv{%@ChKD%EUd8E3cGtX4VVQZq6m~5V$96O&FY#48PEBWHvkL_^uMCjv9RGaFm7UIWCnuTL?K63P_6BsEk_uE{&lmVg#+KWAOywfxnKYx48&_< zV!}M70VojgsHww&1!ZPt(lRrnfphd4HE#!NQzO8_1KI2H`;qJC=F6{3EG$E=Brb=e zMmAEEmrt6q3=t54)YmqqD*b^K+L1qaWU?uA~ukR;#CTd%|gJH4x)0uJH1 zM;eXv2P*_$>Bm5BeH;NWixuOif@0^ZC2JSGNDPZ_xn$*5p5@)!zQj38$NgO14?7oS zhAi_#9^2T1RK)(SbUj|#`Zo32OnhR8jD?I%I%L0EhQB&Hgkr`h0X0>g01o~G-Qe_$ zzMjl?WgRE1dkxsq(Qz|u4E8F3{n0Ep#RjE24D9$&e_f@?78ntM1_7Ip)23>Ri#gz? z14en$c!n}3J0pU}1P6w>+|F#*{Q-aD1Z(ADV;5%9u`ohIJxX8{jJ)}ZwKUaR4EPnrjvcXy<^_*d(C+onYa&Ym>pvDik1%wGzOuK zS6Pk7Q`a6sq_%{BW`Z`J{x;E|J6C7rmz2SpWbH`=#RTuaE zO8y-~IRB3vdGDlMkt3sv(K>z8=1-`<$BfpeuF*=Xy83HV`PY5PLXP*mPJAPJdS0Ae z>Nh4|Uthz(s=hIY7xV|xaXpx@{!i{+sVxmby6u4;Dysnp5TKt(=4{bbR?ReB12Mv= z&G5Igme0S;XP!7;oli|-xZ&C+o2JzgOR+}C|4l-FwYmiUApD{Y8z_6w;4Lr0jgW2sjN!=MH@dhAeJR9$A zJQ*`1qh{g1kB0%axFA5lwhTrc{SpI&?GM6$1X@^Dxju~ti**PgqxqPw+m9fZTQMn-;Tmiolw|w#96-8G^Dqv;YX2{`{+{ zI_d;@YoE@yAiNaopWY)NFW)1@^P@>=MB%^<@=a-%WHtw#6Ifex_*x)=c;GsSKCbFY zbk8#xMIwgcMV7BPME?lEzH-bhc^C^T;hso6Av$pr<1^^4FC3)vlz7*z<>mlOwSD}% z=#6nxRpuHj3-xrT^PaMfAwkiDcK#T+Xur#K(o*d?yVfI}LuFMFu6-NuxFfSlN;o0S zjqaCuh5t)@s=vMkJJMiabtnTX>pif4L=dhz10xKRgq?71FXwNQD3St2rZ)TcmVZd6 z^u>Ffpx?88!2cPI%Y5eM)DL45nm$)jl(DMM7g%n}4l) zeTsmnqjBu=NE6IV=8oZT32pco9~)Gj((F zGCW(;eLrd689-+Z)4Y-YoJGH`7*RIcl1on2W5QCaI$4GHT7U^wRcl!D6e*;p&&vl( zhL9kpgUMmr+uQTu|CbppI-gl;58(pK*T8NkWWW|bwcTHxcYse0hBSjk*B+N_c*?pt z!9F!I?XL|I)rfFUTwmMM58^M-(25kDV(h{pMBxV>246`@*nyp3|1D4*TP`Kzz}Lsu z9?|Y5H=xKIP~N)2n-A2*=1COirf^INUcU$ooB_YTXuypwx4lSrPWQarha~A=#=ISq ztO@Y)WJ0W47Zf}75AcR%VB?_+vd6q`um%yH8iBQ?@NnQX zV}}NIf7ycRQ>_M^v`kE>%@w#}U?X-Q*gQ!EHgYxt`Ot9QXpC$R$Rwd4Mkp&Od4V-E zFD_Dm*4d`Fh~x7+LPdh6wrsKlKKai{UFD?MOVhV`d3!rKDm;J;Lg1?g%b)+zMJvx1 z^!BtZM2szqCq}d>laVf1bksS0XsuE_dZrqGW+DsF1W_k%10SN7$Qvdn|3eu??O6VE#J}fReB5)HMUjG%1L~PAN8)ZcbkLDJfk&*f0 zl5+e}okq*a;Y~LkU(@d`Y6DO5PsT7_WpNYEAhyJ8H2Vl?xM5iW;GsE8AdQU|_Y?y= zq2M~1m6erdOFkp8C{VN7mhRy25JBhTzTL(Uod_6rr9_Cr^L79zIgh1BfXIv(0)QPI zLL$M5o9Ms|r?sqG8Hb(Wx#Awor8FQVITiy#Jjs~_YNE}EE+w&?%!Gf*o(hMw0(=hk ziI9MG>+z%wmkOY(Vr-hg#XT}d%IMe~1sWG>@S2KQt?uaobrE4$+ywQ;DETado{xwo_Dg zoFhy{_xGl6c6^|oI-ofLdyg^Ww{`=nd%faoxf+H!lw6Rz1CfA5&<_q%Z@!ja!rjOx84IyN17tvQ zT{I%c|MYzp^qj^Sg#b@CU^H=_gbxU(bZ;Wr-&vzU%AXqf)GcWd%tXUcWzdh)3S_W= z#*DSOiZFF>jk~80YBfB-;+gKmoPO{Joa2hN9 z@Gi@qs3-mFW=&3S$IKUG0x!*NC1#Z3u;n6KNp|IxWq)Q=rWTq&uIouN=Uk_;; zM{fbs93k|EWN6J4TvAu=U3D*$m%cDB69@6pP43SNmbs{ZApW~}-sb2E^;CtVC(@%* z^S^l0Ud8^xx>t&`)%(8Xzio4r0@e2umDqLao*q9PA2@xfu<|t&wWa?NBslN6!Yj{L z-1b0X?bcu2JyJOXhyeP$2Wo1top8!4D|i!*fCtmWe|T*%4bV6DD+BaOHn}z7@<|aD z;qJ^}RXJKR3C2XL5+aFfbw_URW@S^l{Ma5j!Uiovmye*2(~Q75E4fz!MhxjQJYT)R-S z_Wd8;PU%q6MZ9CUn18N+?l^62%5OmNx!lsEYTX%Kb~0dsfh^~;-LJKi&3Xg;zv%Z? zu}=fwAZUyfTFI)}|AL^&+`};-T4MHVc@m-x2BJywf%dM~3_{l@o?ErlLUp*BXXr}K zKh_RIi4FFJB?d9PT4*1T<8`;d!;p{?5sJR?l?IT%q~PqFe=za zqH~MAaM!8>>-QZhvy=SCrHqWbW+9QEHtd>lNn4X*1BG2RSZ!M*7@L?F0^&2t1MnB7 zhX}-%l5UN@TGS?K_tBf}00m(U>!bG9d|MU&#UuXHChM}$eHs7&R|kHow!TrVk}1k@ zCjf}X^9dn{PT8lkfP+e=64UnqJolJRicmU3D`K(8&k+;R%)I6z4)2&^$Onu5bv|p% z&~yC24JKv^JXv^qAQ%ugd*LZy`~!0wU%n;KTv*9I-9jsLYJ}J_c=<8LtgiA$qDpEY zDz|fimz$I<(@mW?^Y)9{#t-%vK5M{pO?rCK^Ujm62^IuGV)uL_RjRi_hG}o!zSC$w zt(?*Q2R(?n)2XdU;e31Cu_AR@daD}Q<{&SP)NvZp>HuI6PyoWwKIp(34b`jF%j=vU zA^O%7ztdQx-v@n8%1hRI>1{&bjf6>+G7S_{XPsX9pAQ}IB46H_Tr6O&NWLC^f-3{u zBH~t2@rM!?a5hdCS4Us*I2daDoVF+jrWyD=y!f;aa`n@~$qzeV_$QhP_yZYZBWnnA z^PDeN6oSdBW{v>6k=&1Lr<*m7v71Ydy{oIuTWiWq2KR8;WmDTg%SEAIAc~mSaafk` zXpZNH3mQI*pe1lF9=c-bz)#&amlV=kK`YJ!7f_@6KDEzt`I3mh=8WDoSa2>9&0%}_ z1avtfh~9n->Y{_|+(uvZjFEM52Gc;8$bEWWefyyEOzam3(m5yUG3ZHjQj?$|CG}-{G1of9J z=1JNwChDF^4kj$8`H5HH+~1TViZ>x{CvBb0glBjyra(L>y<}G&-+5Y0me*Mp3N|!m zSkH8}(Yu3wihT6BjAcN1m`|uk`Y#V0c`7@1p$ppCI&l(NL45(vx+kg7b3})N=JKtA zjT53`0$c=4?Q)-ZqgVGDM6fQSLXgu~z-58$FL4I30bm$2y=`t`+)Pw7y>ZP822~4x}!hfIJJBp!?chqE_3{iLfA?Pz-!RB^VW| zR{3lZDTlN@5J8l=hBjt>#N1Xx6?PxZ>H9<0S3HM6`Bl1Txd4C25%3ITk4>?2X*S!! z@us=jwq7`oAOVg4u33gYYG>gqz1vlC{|5;u1JxKLU>p)cgT}6#b6zi9=*@NL zMa&O36YIM!L7Ys>uikQDe|CIBTLQysB<<;~l7--Pbb%nFwXI?(CQ!n%*nk@z_)>o? zynR5R7;OF;L&T#~wdSrfQ$uX}idZ%5&x_^h2=C#a)4vB&1zU9AO5Xo9KuA~o6Ch*v z;psN~bsoZ-T+jyK#B3Kw;d^I~z$VBB?Y^>qo~j#ue;k(Z0nNc8FC4*^sya8uREsf-+*;K|Fhm~nUiHS+x-dd5dfkH1V$W0Sr= zd;|(h+>8$F#|`p;h6c@oRsR;2@Go#kdX(4v<-xlIq!f?6fg(I-BWD@Evr7R%xc8~) z>9oLMSk?)$ecUw|3cSw7)`O~pX9h3=iIW6iHt}9hZ4mjznP>2#0_)M>Ik5SDZ|tA0 zU-5Iv##qvpN1WY3=B9jRw-weJ$GqRR%=1SEF(O{ep&mtG+-bAA>I&u#6yHfDx5NRgW%7E;hdJ?1Ghc`>5CWFV-8wD4UL~+bbPeNs3m&3d3 z^~wr8eN{(Y6BJ5rsYVnZbNo1!>+}2iM0p!D5hov4`&wshsl5+r=tt%M7`pD-&TSJp zSd`rUX9xTxGT(L1)lB$-z?Agn=fZ2f=%lVXQfzX98KUba)$6gU2J;2Kvz1X@{SSDS+U@^M#EgMD@S9Qc{F!`< z*sF)UJ(e(NF@jd@%6@-BRe_z}-u0?KAnwhd{^sVn@kPo-< zHx43Du$Sk6kZ+%b(w$<^r2pOaaUMRY@HX~;g%<@4DG20s=;9ROu_M#pWx_27ZJ-2HXb;nXH3nPpP=uFZo(v>j7oNY5XHpLc|b44eR5SI0++gVKQ2W}+wp`s z9EdE{o#$O)14W}v#5LwVQE`MNpZ{Grc|PJ#RbJ9b>!Dl(kjKW^l$fNh%Xq9MZF%Km zp1(~-tbH_*X*d5UMXv4sybb31IP5_w@ObX~nmJPj0>OpM;^a@WGc9MDaNrXuGEG;O zwhEbnjeww;hCq(HHzPaDvQUTS%~J=`dglAphYqwJen~pe%t!Fl5&!HYu4$n@wT23^ zTjIx%b-fcsFd9DQ`w~*}=;G^uW5p;4-58I3huJ0+@5tFbSh0zU-Gm0Pr-IQy8UGu# ze@q;+-MWqWj{`!6^Yyv)V2i#Sa%<8~u?c?Qi@ z*I8h_W-ym#B}_|{URk_LMgpE=7~{Sw4 zm>WX4wyPNK(9CcYfQ_A=c}!DOrzrtl=3rd@r#!XS=;1lF6;1ekL_i6TG=lW6i*Z*;<>Si*5kgMC z`Rcfhe0Y+-8Zar!pxm4CNe^6-N&kVQ?1alw!cx36QlEPfom)@MmkWnatCAg-2D26t zGn?t_?~a?JdAv*|t=$cwP$>g%VZ33gS5eC)jE8=2PEYKkLR)s$rfBni4O@I^s(5J| z+TSaDjpw8Wb8&34M=#u95ddR1(#z<0gsCXXoFEsiROwI3LcPN}ron$RVqSrcj$YzO z3jezdOo`98e2iH-d05D?`RVjNg%VlTcy0lnptJX-QQD2IT9+EAs{?mUdXLk@38G*F zo0>Yc9_14?$*7_o;zZ$M{dto4zx+bLZ;~|LnB!y>iWdoqos}Ah;4gXVr#xn)c_EB+ zHAC`T{T2+7PLA^e~Y~v{B|lPi#bg;NUHm|M3l;Z{#5kLPsx`Z zL=K#=7B`3LX1^lpOs8jk*2XUg{w@V?=JLirr@!L-O+SyelOXy?0N5j1BA-<&Kx07T zQUK^}Q!Ugcnyqt^xxKkwTzYj;#06bRvM-N@Y5g*l&^UAYP{_7ip|0{1P0jxuZ;#+N zEk4Zf%p-gihtKf|83JQ`Y)ZCIpjDhS^!9FU;j(_ANpAI3Fah;z_gv`VbO74Y_3p9q zWG77F4Jea4+uXWt2;(1wUYtL)CVp5v?x%6k8jtfqC;z=YPl-LJK_8oo-m#3A-aL{X z(I-DC=zureP;d8IKh0`>jmvaKR`@6@eVY?ubzkjIE^+nCa5d0jrJ84sNJG@(2L>E< zk+Wwfb()TddllJoebI~MDj3D0fJ#A642ILC;H_C;^6w&Olm$D@4Q{T^LAxXJ+Car^ zbWz~*(Dv6dbW&;6RFK+Q^=Gz)Cq4|U)4%V{P^J0SX`g-wLv>C<*)?Y93kam7Niiy8 z(uysGO%Taxkp%qSzIwBM`LX)Viz=55a;>lO@B)c+P4eVzOQp&g^Pk$K_KOjhg-dB&s>2_ijNxfiD8?zSX8>pvv%-mIV2$Tp|&XM;PHC zgMI0J8M{1ASbQ1l@HCoy6-5Rl3#xo>WjAUwN)JJ^$rwt^zAcoZ1*ATOV2(TzkXxLs z#_1207)r9~RNn{OR8?mpzYUI7EDX#8HURtKb2KA4((q#(S-S1c*cnij1jq)ZV!7(E z^}i6vXDiQLz4yb}9ftL6J&ROx$6GMDPl+9)_V?5ASIteS=X41|3Yot+#Jf7$5c&n_ z(l4wpny+@jJ&}=^@%+g***2e@Y_-l#CoCDw2OQpbF-mjT2FIMVhDO7ON4pZ^A0^t> z+kZ+y74E(o65hey#^m6?q)EiHuTv-J!%*uvptN8eYiy}9*=0#Wjo$ELkqWqs*{1kt zSG+<&v{YN=ggVB>Q5+hKh@e1A$tXsEEu(4|5<71GM)0cR6TKnA!$W{nf7l`C7YjsB zv8MQxc&~j2vY-BzO3Dxq%*>>P$U>j0BBSsUP~zn}+M-!Il2(i>q)kc_;4o!93a>Ca z{ebUxem`<~*+GuJt1NUiAd1sy8N-+?WIiB&Y1#kJEJdo7fCF$IWn$p6AB!^6WO#@n zYJETdm_Kzxy8m20jVtDPkL97+}@knOx+vt>@_R*Yrr6MTZE zG&sd;R0ghi zTs%QBu})-7C-)569*(uyeEnW>(s3@zWYo*P7Vz@UIcduD%h-%&in*#k zwB-owF7Nu_y7Ky|fBy=7r}=~?_!e7}7rASY)kW{QYw9|`L`^G)>V3QzR8Lp>bnd-V zT@}0xwcR`=nP2G+TP$yoTqM9jObSlI55kjDV|UJa$YNONi3IRApvbYg9*~-_QPK-N z;CgwcUp0svaSehP+*|-szFw^!-uz{g4O7G4hkt^VeKI#%V4AZr-+D% zoT0!j1^0RyyHsrmom%tV3Xxegt=ddG^dKp zwHKnWo@)?L5aLa$+ELK_`SZv5cvM5VP@5ODX{#HXL5%}WC@%IiH z<+*~vM#^|Zcm8hWs!>yzni;!iHY;x(DZ_>RJ|!Mr(C6gjaL^u-4tlTyLqnn23g1fJ zwBLhB*|-_MeQUJ8(85U`cD9yiv+(DSme~O4RlHA3><{*N=Ff?Pwlzf&7;AArxU$jpZBYaCD-ZBO0*1rE;kZ` zuAH4wMnC+BV(L~ch%hTQtDC-ba`ALd^K>eM^wL-$1fc9)dGWY{u`_N2(ldmAwh=6vY0%9;m>M?$i4I=m1Kq(F?RlZ|kOq6$2&UR)Yrm3$tt!;ms9^2GdiSF1i9UQpS|V3nPAem&i3$#(Eb zMEwH+`r2sqWoHx+HM)bxbse>TklUO7Cu$Ts{BgE0!5YYX$p5xB`e||dMvfAe zxf-Sml`A%w$mYmJV=po?Mx)WsGNbcDb0|CVx}MSro)+9Us8>F!7g9tNZA6@Lzq6ow zm@`2+ecB~Ep4GW``D;vm(1P|3vWziVxc-6%$n7%B8`1Z<(Zk{7ZcV?gAZ0hmTCopx zhEG|DzvBree)&R(MZt#!pPwP-CIA@RdrnSH&!hanhZ_MkEijvX80_h4PmIm2X!&I5 zaE8n>$Q?B6ow=Spd#1X#xV06Mz-zHIQ{y1uaV3zzXXP^P4734_XG@sX)zt=l@m%Vc z+Cg2lW#vT%H=pFadb*vB62g6@d()|3+h-x3uC;i8DQCjIY`*`77-I^M2(wU&Z;Rt< zv3qzNYJ0?w^59ooee{7R)-;E7o#hu@Qf%U1GKIu)UyAyvAK{Zm-qS+;;EoPeL{pN( z|LsD#e}?R3;RQ8*aJBmkYVs?{uR_yEl|Az$))*^7n^u6nci2Mp-pqqb4gi{PFFpU| z*j}20zFOrEQpJs%BoR+S&#q3Q&i7@ZhW%OhKs^bCj}ieJdN|F7XhX?)@9pk_iM)mC zA#e=<$SNRSB_$#pRZ1PV)Ul{VNTV1RH-mTxrlTcRAmgfx^ipReCg&XKWBSlgHcWVRM=-aZT7)C6(bmbus9uRG#3Sy@?b!0s&F?D~%1Z$m>Jcv?3uV?yKf zNJbh8J)&8Y41d5s?}zvDwxb_$HQ^hpn05Rp13q~SIg{b9LBw8t1s@vzb(Ae+l3%lr4GgXvLSh* zyqbMkSU!kG;|3q&0_V`@=H|X%$kCVKmN6Qx4PbEQTGlqK5c%KLsli{&LIj#e*(UWF z{5A-H$MHoHQOZE9ON%o3TZ(j(8*6p=$3KcMW5;(!mD8pOh#oHkMz4*%)-;4JwyJ|H;}|hA?WM1edQe0nTKs`<=#Zj$RznUbr`AfF zzXkSunb|gWv)z0vv5LOK<#^fFzA!;ynuoGm=1x+*PAWg)D5Pe`mPcI&=RUPwcr@ba ze>=NhmTxb1^yMYP?|W~?QRv^kUuZ^vNQ4k?e#!>&Qe-9P<%}P#ZEc2}q_&H#C?G>e z0-rBny8r<_xM2AiHdsWs?k$!QX!&+}ZgL_bG1$o4luR<^>X|JxGb;yY*Iv0(Zd#&I zw$(buG3{h=drl#??eCL$Sr_$@fMERRoBW>M52u@R577zgpv{AAqdk$pv#=P6tq7=J z#rWyt>Z?-GoEnqD>vPg!QDd@+I>>|&*~8r*P3ks~ojH^rLR_UFsh0X-3n=hsnEbes zWiRYp9jX^$Yc}ZNT?e3A+9~B3|MubAi>HmUf`+^ITkg<{m*>W-&y(!b*v;}@(JqH2 zv%O%jSJx=gL4bhTQ_W69RFnz^gAHvR=jhg*AkA*g8`+v@Ndv_(xIrM3K!DgZo-^s_ zSk6TH2k!Rv&%EywtdVnclY6fC<nNL?4n@AuoS4XcUm8qe zZ~*tL5%c{aubGH-XLeuVY#fX3|1>79B8arzUQmFVbWHxVI^)`6Otn=_Rfy#(8hv8+ zBm8zQB1HY11l@IS;ieO25j=h^stZ%9H~=0DfH(?4aZ9BqYt$0iNry*8MJ>u$d9Id_+9Ci7jeOy6d z@yk>fRXq&3=t|`XI!`XKXCm?a;te7LeJz?N5<@!oQ{Y7>1gV)~!WzV=>Cg+Wzbxbw zNEZ6m$(GF{Iz?+edtZMbZF;#MJT&<*7XV}Se4vMjkY6VzWSU~N#?T-x2<}KD5fKq> zZ>~OG9S(};=-=+5{DIGA0*l!ZA%34eX{~r*Q}7`}D23jmV_;wmnLPo+p6aE<5(9tM zIopX7n^3D*Ql`e3$T=Gat31K6Wzr72xW#qdT4)q~`cZeIRLQeCr#?ycz4+hLIFfjx zedWAyuW`l|Lpny~(;gw^f;YgQ&RapXIY|WSI{8?W(AQ+XJS>92%Pxhem={lcYy_v1 z&`0uWaaJ`%kY?D{q?)v?tR1a6Iz<8lEdw64K~JdiXn|VU=qS%ntONoiD2P8SlzDki?9k5mh3aR|nZXKl`qPlmP-MvVObr28`zX0S@Xbb_fxeUH?Ir0-{u3PMfd--0 zXLab|fx#IgIW!cHi5!o97=U|!E~hqkWJNOINLR-8H@lzZi1X6UIr^p3H~&@D(ymS+ zza`&Fa({X&qO=+S8QVCAbeAVPyHLx}nsgztn_OEFore16z#E<)rI_iLF}{`6ODD0# z2<{s2pIe4g%>;G*SvA&gPa1gy`3B-?VvFd)A2lso)v=7Rmf1NWhuL?%@Ru>G``%2g zQ~#fGj#+#@&}LcO+fr#DAQ}3QI^2vwfR!HK3RwCX5ZXlo5s39{EuDY>sdNS&3A>I& zh56`xa`Ir1$7)+HXPA%X!6p0V=Bzrpx-c++MRUa==z0A&v^6^I+B5-my(rjJbP8s8 z;EQLkp`H}3+RU$nM-?*=sBtj?vLPt^_b6f#22dI!L~!@k2A^d-Y7|NL_{?v58VYS- z6+?uZCaWm0hppzxCw*6GXQ<)Lt4utExA>P!wwnpD(|u?bw?2DmwJ`9}K|= z4a^~hy9$LpfEdteeXnlFwUo#nQ~8N%YyzT1)U~C~oNTPgDB@}7=2=_6RKqkW@W(NI zGEL#lWWEIg>NOG_)q3;b76UEQUc|5hH3y4!l{GdPwUU|1xN+=Ke|6BSS^5SA>{CS0 zEGox^AfurnK>)HBkeW&x;?9m26rGqD{KsyyrgckAxVf_`tJ*8)=MVDZRZrK=1!}6| zdUissQIeZv)c+}HRNh@Vuwbg84g?G`G=x01=HIA@-&YkF3w8c-=Pk$MaVN7a_`z)D zG6v3AA;S1yBbAjOCR=V7gpujz|5YyR-9ev%5Q~lbAAs5 zvu!HeY6J9QVJAjBsl!6!bz-2(CvTWwtjWYLGFg1)QUq{wml$L?!nNWyz|?N2JLduS zm-VXOIow3P?FF_RtoAbS@l_7-rk52dd=okY+j)pOLErq_Vb^csp40)jG}0i0;u=~+3Q}VR3{3j~(!J3AiXlHpG;;Cf@1{y(3M7uQ&^+N%2)<=Jx zf@%_F28IJ4vjMEp!8TW^aN@k*;@l9__rDKLOFV1={y^O9*44O@$QY`5!4Fu3;AMcw zV_x$4D+-d7dh3c{HO08^n;pM-0nM*MPQFCgU(v?Gn#`io=l{B*|O= z&Dk8yf4jvQ*z*Hd>BUE2sZ%KuNHyc2JVWCEWW@b#b_aqVWtucOA(U_-* zee>34`+KwdX)YmQe2j3Soo-+f!XX`fXXp9^?CR_1P$%`yrC-O1?E}Y8Hn`GM1oX_b zskCFRp>S+G{9FlUdi4P@VDN2Dgp&>JjbD65uh(%#D%T^D+4->*qSR$JFAFySF=)c= zeQW)3#(qSlgdN)un~`QG2eJ&KzqWUsEH_4Yrg3GG61%OTmXt zj9rR<8pX8Wwsb7PfwZXxBc{ZTG~xOOk*0bntspixZA+OSuERQfb+YK~+~n%6 zdSX{)ZwxLnJOq52I!#UB-f6N5)!uZvkPBZCD?4pD#@Vv&f&>?@vNlQTVSvOk!n0O- z`dS*0R?ug^KkmVEvtJ#2ZPiQ^S~!bIogDKM^+6zr{LdL&*=(BQB1a= zE|16a2jV~9#e}3H^>gcazBnhfETa($Q*i79ixXZ(h3;Um1RL$NBBdz{2zqtR{rkZY zY`qir9Z*!p+UXuMH%*)qiRf_EycT^QR?z3>3y^7q!?dW78fPk-LXj+?xtv-KCPzHy zDO&~d{VfQs-P_?q5k&8GPvpF+`+)cwzJ3E>20<#VsQsH*ye>5sYfr+}edGKfpA3AN zV&$7LJz5s0@~Q_FyE~Bk?eJ_b9gW-F#FMe^AqSs&ZyZ++S=QT z2_^=%#&a3n$H#xORT8)%rqf`Ld|15u%8HUx><4ary)Uwi)OR&iP=ex?<36j5>A z<_|NqX?QG*ZTTl@|9}<6Xmp;&*mt#qTo&5C=cxv>YdfOG9^wn-7zR;-C;-@PC=-uA z$2o%uzRv50uyKH~u*3u~=XA86;C}2`$9eF% z4*Twn;usaHxgkG&+K(*rAXYE`s&Ll0vKaBSpdN8sI66DtGUXC@3ODacrT@0T?$s2Q07)ya>0;v0}=xnkKD5RVH7+WXeye+tJX_`>qv zl-ydn1g$w?96r01UR56W;!|BsScD4-D$+x`HA#~&w%+)9J{-758u+@esw;v|zp(1w zjb)jFvz_pI<%w2CTSb>fr26{Dg%vUo-&Z)#z6A#+^#nBGO4ov_#a%XnMA(g0fF1^_ zmN2GOh7>E4&TilvO#mbk_~M_0otnp!+Ut-tn++|Vv3}OQbi&J$5p?t%Z;dx?X@fTl zQdLVryz8y3KTJ$JU;TDurwyM>YlO2VJbf!MZEnT2GyKkW9CLH!8C2D8i!8`_@Aofy z3N+QCUz)b6@0+-}xoHzk%-(;x^_;5pb+uQge#36c8eW0vbTg3G7jOgWH%g+E<=cHh zCS^HOjQL%KpE9o%TFqE*Od1v-xN>G1R8-VBEpMH4)-OQr4hr83+{3golkS^CO^=7N zj`iSKV7U~;9wXI!q|)Ri6*!iO9AWh3NN;TP+xU5kP><_%ejiT|=g#&goSf;#h;>vb zM|!!lL@ub`yTq7tFsF~_ytj?p&iB2fvFo`lW_6*sPqxDFkMWmWZrGg3M?)lP9`1Y( zZcy%SN|;Cu+~s;V$Y?24)jei7fs!%ld^!I{AyJSIEAdtdp_zKe%(|mCwvKP@(%*V03hSaQy7p zV5~a_wB<+fxYyt%OqSkSl4lhGHkDdy6phFAiI;RneWNKC3r4dd2Lxy)l1Vgi$*72F zFLs#5K%;ioi@P{Q$9&0}b#+LVszOb(p-W@n#k^QO9= zx(N?l0rq=!odO1H!Fb5|@Rrib^yp_ZI{}J})O-Gn$v|-RR#g78=T*IU5nz_d>#54O z#2);QNFeV_$f92>_{_D-+o~Qlt8PwM>Hf{UK%qfSB!Vo%KzvAN(ihMbxi6^Ru^wMQ z2WVWy3`L{Ga2`(Co@~VV-2bGZRpcxtTEY^5F9|;X%)KT#u;ZqUjkPv<5O1{7P zGHh`z{Z+Lw3#8K<-5YBEfO?n7b(+DJn7l^Dn|F<}iyUnFhApe3HQ{RU_=W_58&?EqN&Ztg~41C8N z2Vvz%!ri|o*|1OTlR^LTvl^eSmDWPCO;pL&7kjkR6BDrf4g5Z)Pk;Rv3~&fZvfV5n ziU9gcp`7OY9WG$dMi>S^P}Yk2SPtRv^h77CK?z=F7s4h!aVG^zhC&iQMg(DT$W4+~ z9~66NmujJuA0Z4C0{;>_I2=PcSLAzG%6%KWd6pR<)3&h2z)3&-YHPtdYCF5xy%&}r zJT8ju@&hZE_RelQPuKPHOZCDpZ$IU=CLcRfrt#8)TU3qz=goe6zau)OZeej;BxY!6 zsDAARCf`0UaYTojjD?0FeJ*-6Lv1-`09}^?1wn}NyZ7e3(KmoEWMI}mzp?5C8JoYfPsi zyte5Yid@k3y`fw}2eQ1>Kaw1n;XReWr&-n5ePjkC_39aKdnsQU0_R4dn+^b^BBQ(e zds{kXn9`cn1ZnbY-nZm59)bhNw4TZsfm}K1SZJN>Ne?yMGC`r0C02uMSJny|lp{Ud zb2ehRa=bXUd?ynvqC#R?u-dq%5m{q1Se1Yj|6gbI@`8tsY_A}^{reVw$hR$%A){y6 zwHvcP$bYbMSOBE|_|8$$tv>*srXZrNNF_JV;i57poFqZ5O7DJdw%4k=7o0H7S3+}M zWNvN>KYl|;j}IrvR)$QiHX@7xWkk!fZyMJQ%4$&nwNN{qqq4tUV+og%QYC~dOCpzD z_p(Xs#?(IcNk(xl!W7d+VJO^XG`lWNeeZbCq|5Pg`A11U^&)yYdFDGEoyV0s_S4b# z{>P+Io+8z^4yEdGv?xbP<^C?6F2id*L0&~X9ZN&79e#%*V?^=)eR5YK4Q@Q_SnVJ4~eoe5r-F48g`vx;_Iby8Ht6O(Vi^I!LzB#YO3no2$_$&p{ z#SNoG76ng`4DOQKra<`Qi~sGZ?6S8wyuV+-$Vq>hs=dITNZZT(gnar#snXV5(TMpF zU*CB|8cOjG{!_3*VuN^1a;2b>nbByrct1%djkUeRc)zJNovxu~Z3SGFdq`(|YLwH)5`Qvvq2#T*jF5=!h`fMaXkC-yXeaPZ>Q$?bQL*z33 zRobhCIj}GosDA#Ta_me@vyD7v4$}e-ut-Opl0haNcvJpvgb6;46G$Jry1FF5 z;@M%a+w*8|o`#*h?>flRsx3Td!gxUG&XO&}FaT|0@AhG=MVa-F-M?A@Q6aq=*E(q= z5o%(%uh{xa@x2}#?Gvf5VQhJY85U$| z`QAEeu?etVrj4~wo!ufjQFv@Az8Gu|dekGs>=>8>s#B0}cAm@~kVIy_n%T9A-M0=X zP8AMfM?H1{z+=I)@6EbVYN!R4u0HnOu5EdZ`=^8sNy3rO|8jP{(TrrhxDO21Y?wcr zJRuTp3XY)2a0nKKHhWU2;Pga5#E7a;U#d7J+Qf9H^FMc8V;i4NMaKv|0zI7%V^lOkNKhUQ5dM- zcK9$uHd|rHz{v*&zOX!}p;^Xt$A_$@{~&P}W++f+D1C|-sRFjIqk&nh`@I}ZU_873 z=`k6A3jI=2=67zb=jsIkdJHCT`vAZ+?Tyq8j)XC0mXC4#LeQa zNq%==D25<43H#;qAE>U&?ae_R`#jRTS^FDP%eyXaK#%h2Q=F^naxrbiiysS2GlVYJ0jN?)lRBOaz%`M`9d&0`x%L8vxG5;i3gKP}~wn0A6ZR-IEe`~ z<}*6zi2H1+#BR-D6SZ$0x7Use1VhYRTzFt*VvbfhQ%_9!c1R9DeEju_Wx~jL*)D3J zs@z<5;zouY$`Tp%mE2{|c%Xjb089jet?#KbY#|_8P^A<&H>6h~9OpS>hWqE3{k8T> zJMf0~ z(TlMSu>P&8+RBtEog)zv+=Z`$-Yz;-4C!T`?!H@0Yn%k8y;_9BUKUCgp%9SY^3qoI zq*G70+D7rYzm|zg4EPxo`hdsvoR*R+hbBazL)@X{k3_KL$1A-P8U@ z(osuW1^{iiq@#3al_8%7Pp)gu{oD}`8SQ3 zLN~UK`h8oo=-M&I9gcHaIfVd)rf2_OCdH^%C{6QI5{ekJMEv!qpbC4sfgtqdd!E1?CY zut=ci(9m}$0{*)V@a>OJz=hhEkDp3Ky&#a|knIQ!*O$z8BBCJFN_p}4>jqd$$!0tyVm6=Kn11+>)8aj&}K)m<<^T;hy^9&fVY z_iyKVg46GtXGY|>^=B*Vhzw;mE@9V1)fDokSGm8wZw1wE|B?dwyreCy+_U`+!j*i& zs_`zgtcH?j;M)5;JJF`AY?`(r%HQMDmH8bVz3)pBB-oj%kOXA8kmIHw(Iabj8s(|8 zGtgy@2h=Lyy47Ka@QVliZa?0QppF;KUfWxI_`rvXON|9)kpOlKH90w1{UR^$d4GpE zv!P?-LC89UlO>acRm~b?4frkK4{hP~CF*3i>^moL@WAJ{a?TlSzXI`c5# zIs2%Y!{RrIZ0UifdHw>{zEjwVz;Sie+`a=|R##*xn65I(+ilreM{&uZ`#nD2N2|ki z*27D>_tf#``$%3BuX#S=X~WxJpQDRs$OGLC%=QieOP4N$UwxgzrQc>{5d+ieq*T9ygSKwv3_TFG&90X1B}a{ z8N#Mjh72FW0Z?MNLTuUyFwpH_Ozy(!>L&o5k`p~ruNRH||HUwGZWI}(fVN&vUgx{Z zJvsh3$a_msKx8obYBuytYr{8U|QK{u41X>TNV!q$-x@B7dLQ^&}7~IH^;$ zt@hQwS$6?Kg`r$_7yd%j!WYTr7&H{<48&NJRPdtFno8*4@me<3E2=$xcI1dB?rH|m@TbLUdoCs+IJ>}Q=D+ZEo!*zI z)?DlYjq~(xOHWhgVb{lwdCOskGh8R<94J6Z5pLb{h2^=dY)k4>UrzJi53B3ykRU&_ zAJ4fR%AcLjx*+hfu@wx>yMy63c%-C(z*30A3M1{Z&*f6P!|?u{RPFCx;w`Y1fwVd&i##R~$h zf8-yeAaE1^MMY1Z#+npdu5+i0O?i*Xu!DrnC*Tor{SdnTsfACE*e)VHeBh^EU%>AC zryn{;`tQ-Tq>nGY=FV;|cDoR7yyEC>ZYlQOOGYfamr~*sSF3A7RE1X3&5SqPn$?t&GC)UT zHLm4K9zW}YE@GiVjag9Oh92PhCu$uZU0q*u*{^gxTV`ix2k1(3%p(n@D(l&2%c#Bx zZ2`EQKvcwKGbaM}sAYctZU_Wb8(|iRP>u>)79RhWZa?7($H-ead6Ff1U?)so0 zi#Mgmeg}i+uE%C~f@Y<;zXAzC@ioa)jt9Mgal}*-=9Tu?n2`FpBEE}?g7XFb-a!<; zMue+F6)pl^iA!0D{NXC?P6E7PD>zY5XT;joHZgy}07Oo+x>2yF(;scQY?0)G^fGP= z8zflo|39kUI;slq`2xLkcQ?|~At2o#C?zS4q=0lucZW1eg9y?g-5`yGlG2UDrMup_ ze!lOw-djtT{&B^7&Y3f_XV0EFq=Ov#amHG$(EQ)I%GLoZ)?gnfg8}a8<`Q#S$`!2u zq;jP=A%96#Gy-1A0=TIV2zb^iI3putjk4`w`j7xHUgC)a(TZGTkuwh{HtHHTWueeiNp!~hzMtk@q7a^qU7lD~gHI9TVE zYxz?Ep5B>VxE!0@npTGMnI*V>`f+R?(Y9o3I9!4xT@wId3xzdx#B_@HU`-v{JCkNd zdl`b;s*uP4Y+})^Tsjne+$Ke2jUWXH1)CBQAgwU6^cScZA(MjQV6?>Qs?(l@84eE4 zt90(JZgu>L;Gv(&z3EhDjv9qdl8m)6WQ+bm+Wm#5R?Ld{BTSpp?&?$vIEDfOd_(%9yUeA7^j3ZJtSX5c6}_n>`oBsvj0<5(k)RG~!zAuLXtj&f}s~ zW@CQ)<#e?XW9#E9->Kh6O6-30RbJ2kbvJB=b-GBvZ`z8^-bVqhKZ261Lj$}jIoEzD z!#*VbP57ZzpWoHb=W}NSWs*^8t1e4(=RF|#RE&nTDWju`S)LJt15l{`u*}OoHeVpT z#63o&VWh{&P1m2Cni|&G+1c{2&=R1aIc?D7j091uv(f531{ec8SR_+9Qve8 zB9M80uuhWkG6rluUaIcJ}5UgB7jlEDHZR}<$wGFHK?WrpR1H?c_ zTIF$R)XM{Qs9-&bs1iu;to44YzQC0A_@B-u+KTt3ZsR(puwPBBZ0`ZMX}Ub$8NZ4f zE`J5W`tEun1dnaI;F;EVr?ESBPFj?&?swmJ;6478pS05c;pj`H zW-nrzzMqoqL-2aJirr@w5>l1hPg!I&y@Z5>AOWBef;H~(WS9%QVgnup3o9+P%{vBQ z!3?MW()+BxuK+G5+D_Jjq*IUcG@kNxE6)5^uY?zv2 z98~pv$8)nT?;kPbb?5ZeDu1v3akT`YEq$;}k2^|E_RGb2vWIt9&4+ zHPxcgJaHl*#{k4w+whp++q*wTjl21oIDak39008t%bFKNPfurshl>k=Ty5w0qsJOH zI>H(Io)bZ6VNvX=D910#*#DlWtE=k@=yE2$X)ptmUja}7$c8_;Au#9T&)v>!Up;s0 z*u7dO=Q{J=eHjRNO*J-il8LT+J0*e9!c9=wz{2@{b03-fzdW;dfQ{R1bj#EA))nV1 z*CLC%Uob6AnRhwSk`M2GS5yyhhQ+!g;Yxk%gQWMT>UOFx)Uz~(ml41`19XQ56l-=j zKdvJS+m(fi{Ou1qNJdMD&!{2R)VUg>cIzFy(13ibv7<3YIcj@V8U@n6olxOYI4BNdb36C)v<8dG>bu&f?5Fenx zZ+PScLapBDTOR1__D4;D{*v^-%N7ox1m0ZXRj_DMPK<_j+MxZDP4knl99Ka~mvdgA z{f~UO!OnWAP;}jLG6brysk&mso4+6|Uz z^uN5CI)1W*XT5mm51g`(aICWjW&#p)Ud15zPqP(h05o-idE+1?HhHt!hx}d7Q&H8{ zsauLbU{rkM!mcbnK0crg5GK>fdzr==5fvp3I#co{tZ~V`_Cg{=1jXGCmtrFcn7bDj zsQ^tQ3lu!|@pTT%)aScXGT`|nzpGW0Rbw2y#YCW4d3;Y6^(sZhe{xYK>pq&ja z9{tjT3shQSI^N+YD8HWzr8L*Pvta>0vh2SFcAtML9aGyu8uoi?K0iK$w^Jf z<9naX=T7i2DF=gMHxQQNk&(UG;ZumCM1$ylx5ne;n=&?FqTBn(h?b;7 zxc@FtJ;AVA9R*gnrF^9+K38eznX7fEtV#I4M8wT5UR6Fk-hCz9cl$p&-ZCCpSD+Zl z=EfT}0*H$upg(+GWHAhSS}C)q(kQ*oTGt;%dWpiXP=5QIegKOLh zY5d~7B8mePh`Yqw7U8 z)!LBw&m(ff193t0E%}7*w@-` zS=)FoJ_jnEP>pXN&8Hktt#R*{2YF{N=uoQMc`w<3Rf^#rk`5z83EKSaz}yP(p)ViN5CLLtHbJH;MBvA)V01L#tcZKTlICVpMl#YH|FQ3nt?fGT@=~4+FA?uV z4azBD;~dog4%WoHqljAv=B>3&dZyd7c87bsT4u@CvOQGC7QQDKH zU&}xOL-=o1iHv9(I^waxw6&HO}lQ&Ut>v5z@kW&4|_Ycv)p zrnV%*DL|2sz}3PQ6maN~0QWFVrtRID zz7KRh=7W5n@iNnGph1$~$UM`VCu*c=`WW~uWu09_RUACdyzO zjANvWQC<Xwd;3bkm{vHtb z%tTA@fz|HVTWW~+m3xt+>79?x)BBL;YGix^=GbBtj12|yLoiQvrHf7otR)X7v)EVp z(f^;!V%y&_07ZO?_(_xsgJVPBy{?AEX4&Ps&9 z7#F#*-(&%Ce1S|{rO;a#WxQ&`i-+d4vqSevxI>=*8ynYP`r{AjX$%$cF^4f1>c$BHDE|!w03oxJ)wwB z<&=qT25#$gg}{P=X9+Frf>pxThNw%>^!BB*mADwVlKV_tnttc_sTTr<2S}jQze0{G zEeA7Iy?k*r?R8Jaz*ywhW`$4i-Yf!c?Cx+tfg4EF=CKlEu9cVN>uflX6t-H=$TuAh zndX|Q7F3z7*6nQK)^s&xz`&dTOWI47l!XHE<{!h^vu>P3`D`xxWX^A4jsezhKF#5%1B$K_xvm|e zFgTR(eNTtSUW3Mfr+8at%x2fr`tsBN99^~Ftw?%?yQG`w0OpAI776Ks52CzWoHWqG zzY);i^qXHxnF*7I2VE;c;aX5dU|f$vuAD%X7t=LIxM?Zt!MjuENzY1H`Mm_GZ74Dr zd_eYs4z%v%Nl^FwtC?eZakTq<wS3<*%F{76Hqn`46q5F$_ifDs7VVEP`29r zFd+Py;*%n2eV!mEkyGMoI{MFv)oPDkBrW;Irsk(c4Ss>QTD^O*CD(MF!zNSSfWMh5 z$XsA=eQ>eLbk;abonl(1Auu*#7pNo*13_i4YutUYN@UEeLW| zG?o_;vK;=~esHlcnpt-A310&!sYyh1a2e=itwZyB8tyYz68=0e-m2B2&GKN(UO&-{ z@4QVj=04i@kW2|zqW+!*Qgzulm`cE$=z}01xAQsQi}4bNR56F*sE4d}d{V8s)e;Y_ zR{Njumm6iGl!~t(Ea+JC6Q`gr!O>T~`U-z{*pD6IG^~^L8zj{<*t4j>DGI{>Z70P< zKHHrd28=T(_%+O~vmMDVr2RAc^O_hOiWopVCxb<+gU^LbqxI$ygkmg|gS1EOEwb}T zibMm)zba;#rMEQi(i=C-G``a)m?~oZVASo{C&^~KWaHCl$iH>Rab3$(-*5;=T$R2l zaS_Gx@)4|h`g!_{Z7$q06Ga;vo|Bs0&pvoN0H8ADN_=@cW5E0YJ{Z)}-xx?qarRa0 z#B}iQ_8P>$6XkB-wz2*$dNNLarUx$LG`r)nI~`fg(93eFU9Inbd|_cA0Z>gN9#l@9 z9Qtl5y4Y9sOs-tlMm(JRY?TS}P#c-sr6QjRsz zfcM8>D1mp=-%cWt!psFyBFPz3rH+>cA}AtCtE()h1u&y)pP zL{4uJ&Sl-*1v3_ZGF^jlFh;v0>m!2bgZrmGTeqGFd`9qLODrudbl@VBAb<+x-kh}b z|EmR1%O8x8Gd7N7SA6(6$eU0E^ zNIW-uq;2w}865Y#8YwOjrrCakoCN`LgaU*zW3^?7oe$>otEThn)xpl32ZRvDStD++ zL`v8axf5B|Hm6*wM%5Taz80Z*-tMLxU2N9>82zGM{FB;4yR%Kv;YWSal;xPRqU9wR z=DBNRbakg)rjwa0Mq`3s?RmwExx@D>R11c@tkev%;za}&LS7f%rFVE;_HpoOH&(Fh zP7?CAFh?csjNcM!^XKohUfbLJjm$)Wkvu6{bT5hT}R11vThzl+%nt4;!ex|OhU=L&` zV_PrI5w0ptmQx*FQ7$n*eaxbTd_yzEZUe6gfiA5)A0v# zeTHq*_R0OKv*^{P)Ep){DQfq3?g)J9nUi)@n?>} z@Kz+)QQ;m|b|5)SQ74KWGArqdA6v*O#JihnEv6ZCF4B@`I(Oupu#QmUoS%1&&(anx z0Bk?7*jjg^?lb!xDe+aKA)}&aRsZ|+PtMV{7%1fm{KJwF57fb62OsTMu8u`g+hs%?235u(Xbqo*h@2V6V3 zxF8sx2v#+5@mu%KkecE80p~^BNk21KRdL;Q*k``Z&n*FKRMQTWY49U{IH(c!`dt7g zYucU5$R1oAhlw12cF)=Wz@^?jUyv=^(f->|SL<^6&o7&cMs-&o6psSMWWyhsnWS)q z-97`b+7;WI_6x8IrUlLy_)yG7bir2{higO4l_e#ts6mbs;LnmcPB4pL2VdBb zA8P2!AIG}U@&XGt#nWO`xR_s@vn)oq1$o081Y!J20P8Op?5W6KVs#bTK7 z4lQ%Ki7Gc|3GELFwt_1U$;0Jza7-HSNit!zx?M_H^=^!1njXGAj_`SfeiXA!Lo@I< zMzrTgLPw*Xd#mnJXkR!WTjhH-tM209xh<lQA zmF|)kjOmXT_V`pS_s<~vVj97ES=XaS8j19jez2!7Jx(8*i;nFNgwxHt44SYa0u0Zhf(aubhu1E36 zTH|2ks^@9g>d{HnQR{C_tTKPdz~ksmEW&z#&>=!~!MrXH7^8;{ezbW*rJoPvC7X-+ z7)D|UW7`(3#o4lIi@LK9%BAuLBFlL1Zz(VSlI$Jx|#znYThnfL$52q4lamo-u2BGHyNjCwW0m$cEy zgz7AED|cnfog3cK0|j7VlIlC@D^K5HH1=gZ)T9v%^A5r1OpxI%O!?P@{#*Ynwcx!? zuVD_fH#?s^dcA^%D%z@&3^nET6N#Ox_|Aq)IOVPFr<}YS$SYA60n9mh-~y7t)|NJv zLDN;c&HHa{R^CRDWJ%e;ai6g@VMFC7s^L5o6OS~OfH^T;;8+yl0vq`yKHrF0sq;W0 zAL`S1uT(9V-F9wEq7BplvRQ#DIdEfxa|vI=(SM8h5o!RwzYSL1w9A1~U0S`yCPs;oEFA z9C06ny~ryRjd$~-cH)!KPrIBIP!=#)$<^}T3wYZr@iwRcm|wBia~^%XbzNyvg=?Q< zzaPoiPzF5Ezk}K#XLA}8TB(N~ zWi2f)mdn?>VL74%p^Dv=_P^GlbL;4V(docU+}dil&{}mGsO2#hS&GQLOya@Cv# z3BYdNfE!3lP`{<9tX6t$ezMYunIK@TCKF zaEtKSaxalRVOo?~*Usn+V<__T!5Dtm~_7f(zN{<|1RU|yV`7E)1-v=MPz7-zR zrL5DsmtQh~UuYt(wmpCi-QLyv=v_a$-}G$|54#04dnB}Y9(Q=G@!gMoWDl8Vt_xe6 zn)fsRZ%O%6**_kAy{S$JPx97qlR|Kq+M>jLzcvCdxVs@-7oGU(qz#L8Pv5a|@@`?= z9J&vZVe?c5Mco|zIJvzz>3N2Z(y>ZuAJL-`egtkXdL9wOp=ByooxYEY@tFiu z#Qlu&H><(=@=t`v_kJaX5!3E{SlJeYl%A5qJI*b0x!UWz+vmyzD05vL(;p!+2XnJjQM);wG-k%h>7uk>-8KBm1DwY z1x)k~4PUH|(%4HSgert^Cxu-8Hc~h=`dh&k(~QFG?l{KIz1&6x`9&Zk2)^c-!lv6# z-Np-40h**ovK#N(a0Ro~5pzVRegkkuyFXMkpRG9L?8{Aj$m(@^hi2-z4m>)41HHkZ z{mQ&|;O_dg1L-(H`Z+dk&ReTqV5*#?pl1CBIT-u#&W5R-PtPy-cADAM&+SX{P>h-nyu)BK=6C2q ziyBr&M^M!AlKmAq7Z_H11`laD?SnAb3ZT;XS%6KVU-o%kshW=C73SG+djr;G7|Nn_ zb(sCO^~hJ8{Cs-mT|PovmHD*f7bZsqLfFa{1U9u+x>8WaCvC3fZuL2%`-46ysy!tl z$>Dy6KsE*^vIKA97q?E=vkmt<6+$j?8x8zlLl@ZjPldK+KYB_ly4?plq@KRO&yLIr ze3NACYHDGQkwQ!0=%I#kc|(lgp2_>Th5$=d)paKFXI59^i|7?J%$ycdz8dK|?~(s+ zX$mLpeUArT$aTcs2MJ7QG%9c6W@nE9(r6|E5#sHwaN}MqlU>rBwvRLZHW(Zd;pf)e zj22%x)LZdeuha|)uJMB%C3RM|b-N3gIgY2!{3Hs0X+$(qN8k;#u>v=ssFFP3Lt3w~ z*U3m6hT(i&QQ7i17kc9jR_-$4EjnyXKUDiS`8ee$Bp5jfg;%Pl^DZpszF| zPR8MTyNgl|H%+VOqDf2yhWmfuVpOBlwpYfOTGmo3rPm40$for}5BIAb68GcCR4j5#MOno1RnkL$_O~dIk&9 z=_3Lak>hf*j_HMGNC$FoJ_4mP4&Z6yUOQLd;#6kD`lt>Ya%{7PnRXdVIH~^&TW@^Q z>Toflr#r2mEG+40np}^J;ZgI9ho-1EFeI4?Djw{uWG5KEyJ*aj4^22WPy;ardSLyA zidsJp~~+8OKI{)4_>^ZfZrf@khguhAsNTzDO9Qro)jx>~E)p86i{V~nM zKnK=3-Xx|-SiRQr!LwkCmUh5j06f)lJY6=0iW=i#=rs9$(9MxVZZFH+qVlO?4cuiU znyDHak-IVAF-4^lk! zE$&ZnNdogj8sxcttiZV#>XOaL6gnDp`s(BnV{vR%@-YhKFV28^$1cmV!!6=VjMTh7 z8S#$=c#I1KH_Qv}Q@_Ez0#|4ut1Gw1W!WHjs(hP0jsnM_mDdBIwN1uQlg!PV*LLXO z?~0M`VhG>1o8?#`bI81}A?$?yVX5*VIa95qscP~R_|u|`{`c$NQoSVqip)u1^;Oo3 z>SYTWZ{7Cq|N0)UY~DZ4JXcQ)2+l!};DCI#En6NDgl{}qmRyT**H#8_4i32c#Y~2% ztGj;hqu0Fx5yZj{3ZEESGcJU^E|ok?-ORa+9gJ)HV-xWb8KkPl6-PfSf7l|~^W|ra z*&}#+=?1)`@UN3P)wgm#g?F@0qNDtn@J`ncdo$0x0W6#wo|z#5!p<$mez0oF^6ETF zoXU1&5t9#ln{3Hlx+g`4dDB$$%&`3(j1@}6& zlM@*=+jCct5}tPiPca9aXgG9A-P`lSht(zT!-E}OV4u-!T=U_#eyv#j zM5#U9{>L%B1T*Eq(u03>ovFCExJJ^&5i)8=5EnM}TIOkGm{!D}3E)0yCvT|)+}`Az z`IgSwH~w&S?U}c3W#DErTQRM26}+9F3W@j=cKVNSJpI{T3yYyw5*UdzxO*URnDMRuNhvP;4P=tIj$y!;!j>d~C-ZuUdZ&C=P&0 z)de**lVeeMAL6770VdV7piLclgQ8kV3tQ#erz`gO935h|gQ~oX^IGU|?+ku0(B-Ra zTzxVSJzxJhFr)j>lwPc}*87QVf=zfrE(I3xtd4$u4X^%k#!Si)GB3seLf5D0cLc;B zkcBn-%TO!ea+JQXOlAs(G}DI zpMvve>tJBFIfTB@t;!gkv-d{^H2DeCVCw*>XSq|8H}lIG1-OK2Y?I*Ek($@t#idf{uig{_yg`sf6=%#f z?`erbO5^Sy#&VM<` zp%j(q3n2Ol+fVBFqZt!P9RHRIP%oGyOP=93>-tPTOLtm(6mN+W-`D+V5cTBP@qg(Q zGk_DrPk(gyu?BRRH+{0y3}9Yr^dpt;LqTAoud+h=SdD1?zRHij5QTdck9Jc^vd~HG zJS&Nlg(wqHC-}AJ;#(Q_F!q%G{U~xjf~*z+hA1} zd=g2g?IVceV1pxBGj8|_a8<(YOchzJBcW3X1>cBHOkE1hfoaQ@_EevE)oy=fkeh{> zHl5DxrH+4pk@6>NtvA>W#vB(twGIAQp^1j*;NW+H02C6fvFCKh@QPmd`W(=}+!5T^ zh{jnG^CFvGq%k@_qzp8xw4eQObpOgom(Qt4$mmnuLi$p0t3chs*OyUv9Aj*ZIz;il z$3P7>Z8qUFWlCUuwG#WSRo?XOEQ}VpR+Zsw-Oy>pxbhQsgjT&5jLLx+T(jc-smvm3 zY3Q4l&wMW${j2YFrmb*zO&@QvU`j!2HDyY(Ql^8@;m6$OHoaFI);x8R^Kb7TFAuLv zeDQXPqF&^@I6FFUTCEfp!`&nZCG8-U^;D=55yh*65fNaZOf^2n^hh65_2^p<9)I=j zj&Si1^&Wc=Wi~KbL)SqgiMVCEw+O@_djytWGzl8MB*?xyBey#)h5*mH{Y0s8%3m0o zaaw~>3Znu&Jg#3TRtBqiS}qiGLp&@*Q1`mi z>^Fgvw-Og>EK%pKKYWw%A9sRE6O%vqCIjche9BphrQe9;&gEFj*ksiW@s!Aw)C`Pc z*22~VQ2Irpz{i`UzzT#deu3t8;mi*A~W*y+J(hiaUn4Z)lK2VY1mLBs&N zUo+G0no8E6kn6cqC#82ECQ1Oc&3CVS7NzQ@Y`UR&UbiJujsPzAtlLizg%O|#URU%? z1M?NOAPhBiS9kkfZEwvdR8}t>(2g^#B`o2re4Z2mm_kUy&5a+t7L~Y2@7*q z17yx5nPPqfm%kTec+{fXnsMR5O8FUeP93^UY*Bfz8WH{u;(^)))^0xkhnQQ=tT^4+M?vO`H&>)R69w{A zm=iBGGYf7z>O1e@c{W6Gb?rubE07>?###&0l(dfoSLC$jN5mjcjL^tI9@0h@*SOiJ2aCtXkBQi zp0?ubtd+B!u(N;@KoZv#-eyBYJabjI*sxQQ^3$jB7H*vRi#f~q zJI@>)mM0}p+^+Sf9jW~FS1aXNZaUHNJ%cAMs_0yKNNf7a?JTUAsAg#BJIDiUPyes$ zl&9rSx2q-3rMH(XDGaN}#jYb+7tO#lYg7^$Ca0#KkfwzlFueH4@J)HKniob*c!zg} zXW4)rX!PdB)%VKXAP6t{Kyf^55Ro8Or-1Q5(CFreJxMnpt7Tb)VKE zKgoSY;koU!a)&l=kDrP-az}zI(VjKctP)*~gv5{ZT+f%V|9ih<>(+CXsqCO7^ZO2h zH`C*P0)n+roT_$L2>%GRXbYa;b*Q~%@2*m!p=x3w2udi*#X{`QjD+iuhAqx0syK;b@jW)XWK5- zQqR#8-w7>5CH>m`=?R9GIVjGvpawsNqnO!fStA|0m z;3h|8R<`#BSj}v&?)h4k$DL0VAB{{QaJN_ygiHxSnEh%Fm~3ch5710T76V={jz(qm z(~>*pZy{pqQ1z|u_6+>q%B!j$ub?^}@B^+*bC)$iVSV$BytO{>Bihj6NP-B_1fKAbdfP6>b6g(q$ z*EU6Ud=rV@DsCb4_HAEtv2cEUEuo{su=d<1q^*_)1W7Nt3>sWewWu$85#8TDC}-;S z|4#)h3ctbsgCy1GoZP##pcd$%)&^xCwm3~cio)t<;x{8## zweD`&P!@{@ER+~*Xm8RT`cUh=nts%Oo&kZA%9cXCz?2VQg8VNU=As33giEw)Egfll z_b417IHU-IfC#XKWVxD5xu#6HT0!1dIGHfark&<+XPhV8EV#%Mkl;Z`u(f3AEP9(d zPL?`KmQ*;C8G$P@6>yBjGcyI+#?ivQRIngDF(WyjFIQVVxA~}SbH+bwR8KWOT>t3K z{zs5JSO&#RZI%`jgC-1^ZZQ$`79I(2E)LEkpHmuNu53&%fYEAN%Bs*7mh8Xp=4Cj` z_?y2wFhx1nW$5ti$xBN-7bR!Jh6m8{+i)`1XQ8Wpf%ctz7$XLb3GcFZA+Y0>@4!De zm(XHc%+*`FXWuEGG`$<=sq?M7%4AyS=)%&2*4c%acmF+Fqt!-64#h47G&=jq())5C z`554M_MP--4F~DUb(<`sFaCfkrmz-;UVO5D5C#$Pn&itXh{pUpFiS7=5C2qXqIz=I zkwA}^(Km|(AwT)+7a5+xHNGVqXD-j05)NZfd~WDZj>3v<;=kz!JDbEX{Ir?8*TuMh zsUlgHS!Aybu895;L^>WihIob2jj&;YlTv&7hvcx#=37wp#agCXx)o`+&wK-Y4uj%2 znOpHMt-R(Y@~NLw#p7DJt=}j&V>d}2{%FSO`Mz&~n!zG{vOwxvvk7@f0C?{>Seh$j zF+3Ef=VR5Aqw^y-FDls$Pgte|=jPVyX@9SL)un0X1}C6()Z#-@_}F+wJD3GKfx?p;xrr8RHV}&niO4@TeBf%a$5+D;?wHH@$xdC_ew-z#luK?T!-7V`ltyu6vZ{^?vneR0;t&F4_fp;h85d>hUZ zA|Q6>l|}CYCi??e~f-(A%L#`}8>)J-z%}%?%dJXi{W<9RH$)j0nnTV9F}s$r+sEjRzga z(V_Y|CB<$T({;yFqTwdUJeICkaBVr&g5aiBx)}8ETX(Th*ZXErMNi-}KQIhV!?`1{ z5-p2wTfhz7w%0?Vagf+1k(Ui%e_HeZY5`K2*ygjdr=0LnWRORb6j2!@pr*(#2wdcKhnV7ES@2lY?x}$|U1U#~p zzZ*kfry~psS}VvfW~jTi%(gsiZ^fh_#Ml$7sb2+iXKQ*cGBBIq8h!9qwr{>hCNks| zx1+WUd6?>4bMBYV?wxV2DM>|oK6;+v?8diC`A&%B3VNZO$(Q-FPe#RIT(%(4)ZyR< z6C{vkdJZ;yP9-OtCd#j>z%8;-T{ZuyNH|IO{O&Q0CGxq~14|sx-Nv7ahe6;XyCO-c zgvibE`%Gkipn8@z$vjCA0YqGJ67NpN(S^t9k(7)$0~Guq;#AXA z*5IQUjHqnN1XR99G3Hp=SaAKIR~o2r;IKD)K`gPOf=o8wB>b~wP^j>!S;sV*bES;+ z+Ka{ON1j?QkHA75G9lVe#Y}oWuEH{ms}P zE3ypX4D?T)!=Xu`?NH59kQes7yWB4U(=@odT|gU1%+*0UgJQ8wMfdUNb(V2rTH}oP z*3%Cx_AKtRzKk!Pe-#Te`(t+X{Lrizkq9{yt2@1zhm?Y!IJgJddw;5IqT{Z%DhAdQBrrZ2jTno zEXkQlg`*1ioBECOzq$+)8Ri0Re>|Al`GJyEmh=ZhKH@T=AP!!M-w)OoN7n3_pJHff zDC#i)+ex>JctJ8_0iD4T@F<@ged5e-|%k4H*2BG;7pMH9u5u>fh@}DjVE$NU%;3oVO~&^W2Ub zIUHO5G50}@ZEkR$>dO1d%yrT_CXW1mz8b(47*1(mB?&LVUVU+n_dQ+jk8dS#zcr(l zPxkDHA+$rx=<5aj^o+ttW_10XGd&H9m$Y>A+~-P@iiDJp*ckN^p*|GN_5B%$>@E9m zI6K{45(n>!lIoEb*hZ*3${bLA`0wzfWo0Yes!S+SExl{?Xat6h)6c za#P^$CWs`-;*~atMx~8CM98ysxkDPYsREg|eBd06!!n9pYA3rEQvgkq& zf)zf<&d;*{zBv4Z4dISJ-pCp2e;B%duKNM^mhDmoB=M&K{F};~S6f$tu-|HEte99d zQO6VOJWssfZ&1#yJUr-_*O*=D1ZPSdI#DPNYrffvKt`s1m^@77^bCv4B26XeC(T*> znGWz)cO!_?dG=XMkWr|6sRS6?pAqY4E1Q+jKJt5&jOKbAhvOglY!Y&nJF@?A@=)l= zCrZ(zYupP|uTsN#UpX`k@3F2J#W9d^&@tDJXZ-}581|~u*V>L&+K2xFCPeUewPvik zq|asTC}8`0#sKjuh%gC8JA?~Jq0vE?3TOz&FA;?saFGh!c>N?X@<_h#gmdbvajDxs*xM%hCu&Emk>5-KMG4;t6@smX2sydywhngn4 zE|MF$7pY!&iiwWR&}a48i&2@sDEBL8aSF`65fp4SgV2E&Ffsq}0>}zOf&H!)xEsxU za2BhRj+HX~mq2V`rK5o4QPVg?Z2Q8*q1R|%qyZ=8=hk+!okeoYtbR3#H`~Qp-F@Yg zZV`Bbw@fC|XNWPe)35RuR3giA zQwc3e2P^jLD3B&FztZsoJ}hi+kiYSsS&6-mb~K>ZJNN^As&rMm?K#r%7r0F&60H}& z-k?J!+GOZ85B@{Ea{Zf_ib&=d*(t>*26T9&y zHm)M9cwSg%Ca91i-V_i4(d)_FruDoiI3?4xdc`$Y&UbR%Nr6wt;C-1z_ARQlHP|jP z=Y#ia=Vra!*_0?=QaKw!)KJl3`RYVM0gtfFv#>BMi>t;AJVdxQc&|JiNv=Dg)iD71 zda-(EUF>n(Qx3ZU;+kJvZIjkH*7xuLN1>ijz|CVx$JF~uI>tCAxE((q3LqDqpr(#~ zh>pMmR;m6NoPc>P6Wd+2$rcJKmPOj@8=AR*mRPEVO?arxTLiq-!*?Dq=viOlTC6#eb>hG> z`!24T97Fk!+yoda_a3HhLK`Sz2AL%gHHs`B?4W}SL%F`i4>#*>&qjhBo9tVpkF;)J*=|T*k(qBhvC|7n()0FV_=_-`4@<= zovS!8gl(d$eAeQOUGZ@utWWl(SmOph3N@(MY3%~3%IyjrnAnh#ejiu}WHdLjka z4I`h$^k?Af);kTjp*^6>`eyP2wv9yw(a%G#$8Ib?Ai)t(!^tng6(2YRv49x{Gn5J! zb9Uxw=JXB1Gf>-m^^Z)bX5KNqjm1u+GiH=@zI0UX?{{wyr6fV1g3(X@$`5RqObFOo zRNZkWRI}>!2nu5~p)7CgaZLxmHETTA@<6Adx<-Zj40#ok9idpA#NFQSdF6E_e8l!- zw;6WF3Hnv-e_+fLzpcz|`11x6@(a=b?sVu9k34lFXt%ib_~Js|+L48P+;LDyA0Bip zX-V4OyTBA|vKtI281<{5nSXZg8^UD0+xr{|cd|aLZ0P^~T_2ck@sGLM{Vbb=w`?gM zsPj@FU;c^iBGa5qqNC~n%Yt*SGm92AXG!8fU29t#&*SR#Yyg(!STymUmj5W{=%sg& zrnr>fiG=Xf8)h`8CWt`UO-S2}qFI_CVX;1^tS=F(|D%o(mxw$Tz2#FE#~f!4FV;Ut z3*Rn~`<4)5I%tkCt4hU#2LsxOyc4t%L(3A`llfET+_HcuRS6S~A}D!>(16&NEHNl4 z7k3tS62BsCPNiJa%zWcTOblgY2hObspb2Kx+Y#npHkf(;b=Tydf4nYNF%CZu-K%bQ z6w^c}l9!8-wy|UV_4YUJCdFoFWl*uLDZu{0Q!^pVN?BT=9-Y?yK;b6|ca}Y! z6aFkeq%^F#%o;ybE&kgl@|{|H3^x`OM_Ym#+NpI&*bd z&7w3kGn~z?2jo6q_o(R%0#ZMHc@&8QMMm z6@NXz{t@Somvnpw3!hWbuE-hg_j+T4Z@^Hozlb7Ug*7zQ-%NYiiL_w7VSEDv0$5=$ zDR}?TFR)QQ<$3xdQl)FQxccby$YsE-1q19Smn1>J-{aprVtzLcB6xOO^KqHALeJGc zbnZeUoMZBjrPHoXs60(CF{0!lSaJvX8*aVZyOkceJe6#;<_u|qOD%7Q*7(?(`7(BauQBhq{US-vTNrH|Ks zXxiF7q0V)X&YQ9HE)ME+iB%#@)$ENsctE}nlxyj1um4oOG^=aJOvLJPmr(D%OMjQ1 z(vFGFSS0t&TTQZEDy&ehF~;XKj}RvXe*HG=dkZJfzJ(f~s;R;I-=Nlyq_{EziJnj0 zA-j7{Jx4J=sG`oqLn9@*>XO*#>2||-b5Kg+481bB9PotX09=9PReRWj9@3Y=*#27f z-S10cQHeH0T8yRh;KRlcm_|_G^XD~Zn!dUWu#8GtDl%PU&)f3Hzts>5f|uzIg9YnX zF`ptHr0zdyo2dbrXT|-PPt>3S)|Z$ju7sIA{?%720N^!cbjWiRqGN|IS#au3a;4$w`Uk? z*SQE@R|U~y!j8M6Q;1QhX3-7cQMk^pS;lKU3x&_~dEjmK#$*DS@LUioI>iJzf~CK? zN`->hwNY`#;dW*BJ6Kn}4MkZ%04u%hs6Xj<8olV-TnttQXBu=25y_}TAb5rCaB_8< z%*sKR1ri-ycmf98O&G%9Ok+K03Ve&U>ozvUDwh!ybt-W6h!Y4f;G5IACkn5T8kYe1T zYn5zHTl<`#|GQ;7&TQ9M56;txr|E=zXzSiF3b|IfG8Hd3*dM4L(upOoeyaK)-f%3tYgT z?VWFJo@T=`S91l(iGB4*eP}ndqE1sN5wW}58~<3WEKpBTp3muLV{VuIhA+U66(Il@ z&%7KoS$&#iOq)RQ8vTSTd6@I-xPp?$|CyUN_(Zg{qn6gxkoi% zYImU*lvj{P@hc{0BKgj+p9YhOA@lIaYjJ-MtKLD}Y|--$KuH;61L5gkKu_Wcr2;3w z`D`{8P$^5{s78g+0Q zDYt*zokGz`Pi(8&(t7iRgRw=1+-VCK5P*HD7ggHMw~eTQAQev0CW23uq z(g0Pccet6#qfq*9e{8)Nj#*!O!5XESkA{h{LzcHjha@+eTLTOG`C&J&@2rMB^^r&NY~OS zNG|NswZwP!{eC{r=lT8dyRYlsy>_oVX3m^B^E&6eW@dcBTfJb?MLkL&O-V}1PJ2AJ zoyLZ)IU?{pSChq9zI1kganZVuQrP=kXnT9`dDAOw_(bnoXH#2&P)fVon(Y)JG1SpU zqnrs31^MKDw(wliQsAR9CiK$9+4v6f&rp=L%n{{*oP=)+5z3rQi+6EHGpMZol?|-$ zKD5`-zBFWYV|&PP9W0e+Hqny&I}H$1OT7!cOAW3um_K$~avMFOOn+=U-5x^+T-EpA zg8c8FsMJ5$&z4)<7aI zspP9-%cKG?%9TQ5s3I0W<{RXyHY$>Br}MF|mvih8P9YLFcA3(t!v&NSY;5xw3N z@N`lG6(*P&*rk>9k^Vi~-mb0}j*gCaSZbb~s@hLl2Rj3QG@<5K+f7kY(<^kcHMiH7 zE?Tm2q>h(8EQhZ?rsKL?ZPCYbhYx+SV=m|mx4_O&Vm8J*3ZYbZsP*^m;(U_wk5Svs z;gEwHdo3a3gdLxs`;Ia1Jr|SuD;0YMm5#sdA`q!4S0VUx-t(r>mWa*aNhf_+&5MNh zNt$A5LB{9HO(D5H{*l#JmQc878NEbLiaB0_aqC$yx8sjpi1F2-HxMw$AqT53kv&}6`-&mCii0b0-B|oLG$XWc_>DLAK{+ES7F>AQbA(16Bp37wsQX=~ zr#;8Vo-HXYufi0iJ#7U$q{80kbdR(cU5m8Ant$=4)9Z!(-gnS3X6bTPdZn}wyY|(E zG+Ftfa*N<-)=X6E+h=KwRdhHt`&-)b|9DxXrva_gAH@Jy>UZ6kmWY;jHH56BQOzfZ zV(g+PWIdVeX^tOf8@Xlt&+QL5IM7k1U{srvlXF{ue-F@^_au7MQZ>8nEq%j1AgrBG z-7p}iW8j+R9T*WzKQ+OU3Pm%V7HlxqQVcYr2!121s|u+ufcFtBpTcsrSLZP?c&?5F3<>< z1^kb3tVB3IO+|l*zCSTh`r&)TfF!CQ$bon!-j%^#q3-rU(ZPh98_hHMXCOKG@sVV- z!8oGIvh4Jib)je(wIK^fD4L7TN@RqS%k$wC5nS3cG7Jz8 zL;}!AzCVS!5TZ(iSek}WpF$L`y6=qnt{j-W1ZZ8_Sy}Zvb7MKBN=8H(VJ&We#wj>J zIpcDI#J{nH{=o7@@Bu;CcrVOx;5Yxg?8Jb2z-^}&;rYS9xG+t(z%dOP2ougf2W-rx z-Zp8Ij^p%%;WwIp3Ve*byZldf_f>3GTp2YhXr5*BuC;Pz~l*oqW1iI}~hg%FRdN(6O@Ehc);F^oP@_ zlR8a0cz)NwYAFB$3-KT;jvpa!c)QwT0>ZcWu1|Z#`{SMVcIX;vu!&PxVW>#lQWq!w^2aj8eE zC(q_A=koP+)E6+MrEzaeVa%H4mji&~mf4>D#-vy3UsL|D?px3>E(pS!dhfpz7Fdz} zn>---(&=Trn*pcnBar(E0WRRq@Lb#=e=uNs+g zG+T^f92*H+4gs5N>y4??`SRt5j%Z@fzxsB>HL2WQ-ckeiKb>0N(*hG1VT6^0qwn0$ zO@9w=Nq7JOr=)-4;UYRRIv_V`T}swt#AaKHfOB?iJkWF8l^ZY-XpA9I4GRhsYo6Wy zJSBdo4{wHPeX%wJ%vJg>;NaULB$9@4z4PdyGZl!diN_Jw@c(~uyhz#)6z0Sd-j9Iew;kb^*rP$c|xm%_e{{V!>lB1ONplRhLTg5%;)vKRhsorY|>t?IbFC%98 zGi)$cxWRoQ(RnDk?tlMBP2+Jj`xYJxvHq?}y~qcVkA4I#;a{k6m18V`&Pr9O zi}v&TG+d|4ZHynrB}Mi7d)yej`>&@OhdxACGCTb|WYoH6_+F(%MIkuh6A1T$ELfsn ze^(KhTZ*_&Mpk?Lbk)jx*71ts&FPO@bLpRwaPi*EEtfQpb98rU-Z2;iVOD@GiQ+xf zDIs7Cw!Zvh45C~DbS~O5CRkQ7UgnhwOcX~kD)2}x%;jB5T-Ky16-Qz736YG!&mE7s z8~oPn{=RB*U9aGpnCoO*xcHUymGAI2{!O>&2iBwsuwpTGSBq{}2a2orc^~u+X#y

WhBw~+Be=nM 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 0: - last_ones.append(self.queue.get(block=True)) - # Wait for them to complete - for f in last_ones: - f.wait() - # Keep the good ones - last_ones = [future.get() for future in last_ones if future.successful()] - for inputs in last_ones: - if inputs is not None: - yield inputs - except Exception as e: # pylint: disable=broad-except - self.stop() - if 'generator already executing' in str(e): - raise RuntimeError( - 'Your generator is NOT thread-safe. ' - 'Keras requires a thread-safe generator when ' - '`use_multiprocessing=False, workers > 1`. ') - raise e diff --git a/aitk/keras/initializers/README.md b/aitk/keras/initializers/README.md deleted file mode 100644 index ebbe2f0..0000000 --- a/aitk/keras/initializers/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Initializers -The `initializers.py` module contains objects for initializing optimizers, -activation functions, weight initializers, and learning rate schedulers from -strings or parameter dictionaries. diff --git a/aitk/keras/initializers/__init__.py b/aitk/keras/initializers/__init__.py deleted file mode 100644 index 91c82ab..0000000 --- a/aitk/keras/initializers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .initializers import * diff --git a/aitk/keras/initializers/initializers.py b/aitk/keras/initializers/initializers.py deleted file mode 100644 index a828fda..0000000 --- a/aitk/keras/initializers/initializers.py +++ /dev/null @@ -1,264 +0,0 @@ -import re -from functools import partial -from ast import literal_eval as eval - -import numpy as np - -from ..optimizers import OptimizerBase, SGD, AdaGrad, RMSProp, Adam -from ..activations import ActivationBase, Affine, ReLU, Tanh, Sigmoid, LeakyReLU -from ..schedulers import ( - SchedulerBase, - ConstantScheduler, - ExponentialScheduler, - NoamScheduler, - KingScheduler, -) - -from ..utils import ( - he_normal, - he_uniform, - glorot_normal, - glorot_uniform, - truncated_normal, -) - - -class ActivationInitializer(object): - def __init__(self, param=None): - """ - A class for initializing activation functions. Valid inputs are: - (a) __str__ representations of `ActivationBase` instances - (b) `ActivationBase` instances - - If `param` is `None`, return the identity function: f(X) = X - """ - self.param = param - - def __call__(self): - param = self.param - if param is None: - act = Affine(slope=1, intercept=0) - elif isinstance(param, ActivationBase): - act = param.copy() - elif isinstance(param, str): - act = self.init_from_str(param) - else: - raise ValueError("Unknown activation: {}".format(param)) - return act - - def init_from_str(self, act_str): - act_str = act_str.lower() - if act_str == "relu": - act_fn = ReLU() - elif act_str == "tanh": - act_fn = Tanh() - elif act_str == "sigmoid": - act_fn = Sigmoid() - elif "affine" in act_str: - r = r"affine\(slope=(.*), intercept=(.*)\)" - slope, intercept = re.match(r, act_str).groups() - act_fn = Affine(float(slope), float(intercept)) - elif "leaky relu" in act_str: - r = r"leaky relu\(alpha=(.*)\)" - alpha = re.match(r, act_str).groups()[0] - act_fn = LeakyReLU(float(alpha)) - else: - raise ValueError("Unknown activation: {}".format(act_str)) - return act_fn - - -class SchedulerInitializer(object): - def __init__(self, param=None, lr=None): - """ - A class for initializing learning rate schedulers. Valid inputs are: - (a) __str__ representations of `SchedulerBase` instances - (b) `SchedulerBase` instances - (c) Parameter dicts (e.g., as produced via the `summary` method in - `LayerBase` instances) - - If `param` is `None`, return the ConstantScheduler with learning rate - equal to `lr`. - """ - if all([lr is None, param is None]): - raise ValueError("lr and param cannot both be `None`") - - self.lr = lr - self.param = param - - def __call__(self): - param = self.param - if param is None: - scheduler = ConstantScheduler(self.lr) - elif isinstance(param, SchedulerBase): - scheduler = param.copy() - elif isinstance(param, str): - scheduler = self.init_from_str() - elif isinstance(param, dict): - scheduler = self.init_from_dict() - return scheduler - - def init_from_str(self): - r = r"([a-zA-Z]*)=([^,)]*)" - sch_str = self.param.lower() - kwargs = dict([(i, eval(j)) for (i, j) in re.findall(r, sch_str)]) - - if "constant" in sch_str: - scheduler = ConstantScheduler(**kwargs) - elif "exponential" in sch_str: - scheduler = ExponentialScheduler(**kwargs) - elif "noam" in sch_str: - scheduler = NoamScheduler(**kwargs) - elif "king" in sch_str: - scheduler = KingScheduler(**kwargs) - else: - raise NotImplementedError("{}".format(sch_str)) - return scheduler - - def init_from_dict(self): - S = self.param - sc = S["hyperparameters"] if "hyperparameters" in S else None - - if sc is None: - raise ValueError("Must have `hyperparameters` key: {}".format(S)) - - if sc and sc["id"] == "ConstantScheduler": - scheduler = ConstantScheduler() - elif sc and sc["id"] == "ExponentialScheduler": - scheduler = ExponentialScheduler() - elif sc and sc["id"] == "NoamScheduler": - scheduler = NoamScheduler() - elif sc: - raise NotImplementedError("{}".format(sc["id"])) - scheduler.set_params(sc) - return scheduler - - -class OptimizerInitializer(object): - def __init__(self, param=None): - """ - A class for initializing optimizers. Valid inputs are: - (a) __str__ representations of `OptimizerBase` instances - (b) `OptimizerBase` instances - (c) Parameter dicts (e.g., as produced via the `summary` method in - `LayerBase` instances) - - If `param` is `None`, return the SGD optimizer with default parameters. - """ - self.param = param - - def __call__(self): - param = self.param - if param is None: - opt = SGD() - elif isinstance(param, OptimizerBase): - opt = param.copy() - elif isinstance(param, str): - opt = self.init_from_str() - elif isinstance(param, dict): - opt = self.init_from_dict() - return opt - - def init_from_str(self): - r = r"([a-zA-Z]*)=([^,)]*)" - opt_str = self.param.lower() - kwargs = dict([(i, eval(j)) for (i, j) in re.findall(r, opt_str)]) - if "sgd" in opt_str: - optimizer = SGD(**kwargs) - elif "adagrad" in opt_str: - optimizer = AdaGrad(**kwargs) - elif "rmsprop" in opt_str: - optimizer = RMSProp(**kwargs) - elif "adam" in opt_str: - optimizer = Adam(**kwargs) - else: - raise NotImplementedError("{}".format(opt_str)) - return optimizer - - def init_from_dict(self): - O = self.param - cc = O["cache"] if "cache" in O else None - op = O["hyperparameters"] if "hyperparameters" in O else None - - if op is None: - raise ValueError("Must have `hyperparemeters` key: {}".format(O)) - - if op and op["id"] == "SGD": - optimizer = SGD() - elif op and op["id"] == "RMSProp": - optimizer = RMSProp() - elif op and op["id"] == "AdaGrad": - optimizer = AdaGrad() - elif op and op["id"] == "Adam": - optimizer = Adam() - elif op: - raise NotImplementedError("{}".format(op["id"])) - optimizer.set_params(op, cc) - return optimizer - - -class WeightInitializer(object): - def __init__(self, act_fn_str, mode="glorot_uniform"): - """ - A factory for weight initializers. - - Parameters - ---------- - act_fn_str : str - The string representation for the layer activation function - mode : str (default: 'glorot_uniform') - The weight initialization strategy. Valid entries are {"he_normal", - "he_uniform", "glorot_normal", glorot_uniform", "std_normal", - "trunc_normal"} - """ - if mode not in [ - "he_normal", - "he_uniform", - "glorot_normal", - "glorot_uniform", - "std_normal", - "trunc_normal", - ]: - raise ValueError("Unrecognize initialization mode: {}".format(mode)) - - self.mode = mode - self.act_fn = act_fn_str - - if mode == "glorot_uniform": - self._fn = glorot_uniform - elif mode == "glorot_normal": - self._fn = glorot_normal - elif mode == "he_uniform": - self._fn = he_uniform - elif mode == "he_normal": - self._fn = he_normal - elif mode == "std_normal": - self._fn = np.random.randn - elif mode == "trunc_normal": - self._fn = partial(truncated_normal, mean=0, std=1) - - def __call__(self, weight_shape): - if "glorot" in self.mode: - gain = self._calc_glorot_gain() - W = self._fn(weight_shape, gain) - elif self.mode == "std_normal": - W = self._fn(*weight_shape) - else: - W = self._fn(weight_shape) - return W - - def _calc_glorot_gain(self): - """ - Values from: - https://pytorch.org/docs/stable/nn.html?#torch.nn.init.calculate_gain - """ - gain = 1.0 - act_str = self.act_fn.lower() - if act_str == "tanh": - gain = 5.0 / 3.0 - elif act_str == "relu": - gain = np.sqrt(2) - elif "leaky relu" in act_str: - r = r"leaky relu\(alpha=(.*)\)" - alpha = re.match(r, act_str).groups()[0] - gain = np.sqrt(2 / 1 + float(alpha) ** 2) - return gain diff --git a/aitk/keras/layers/README.md b/aitk/keras/layers/README.md deleted file mode 100644 index 81e888c..0000000 --- a/aitk/keras/layers/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Layers -The `layers.py` module implements common layers / layer-wise operations that can -be composed to create larger neural networks. It includes: - -- Fully-connected layers -- Sparse evolutionary layers ([Mocanu et al., 2018](https://www.nature.com/articles/s41467-018-04316-3)) -- Dot-product attention layers ([Luong, Pho, & Manning, 2015](https://arxiv.org/pdf/1508.04025.pdf); [Vaswani et al., 2017](https://arxiv.org/pdf/1706.03762.pdf)) -- 1D and 2D convolution (with stride, padding, and dilation) layers ([van den Oord et al., 2016](https://arxiv.org/pdf/1609.03499.pdf); [Yu & Kolton, 2016](https://arxiv.org/pdf/1511.07122.pdf)) -- 2D "deconvolution" (with stride and padding) layers ([Zeiler et al., 2010](https://www.matthewzeiler.com/mattzeiler/deconvolutionalnetworks.pdf)) -- Restricted Boltzmann machines (with CD-_n_ training) ([Smolensky, 1996](http://stanford.edu/~jlmcc/papers/PDP/Volume%201/Chap6_PDP86.pdf); [Carreira-Perpiñán & Hinton, 2005](http://www.cs.toronto.edu/~fritz/absps/cdmiguel.pdf)) -- Elementwise multiplication operation -- Summation operation -- Flattening operation -- Embedding layer -- Softmax layer -- Max & average pooling layer -- 1D and 2D batch normalization layers ([Ioffe & Szegedy, 2015](http://proceedings.mlr.press/v37/ioffe15.pdf)) -- 1D and 2D layer normalization layers ([Ba, Kiros, & Hinton, 2016](https://arxiv.org/pdf/1607.06450.pdf)) -- Recurrent layers ([Elman, 1990](https://crl.ucsd.edu/~elman/Papers/fsit.pdf)) -- Long short-term memory (LSTM) layers ([Hochreiter & Schmidhuber, 1997](http://www.bioinf.jku.at/publications/older/2604.pdf)) diff --git a/aitk/keras/layers/__init__.py b/aitk/keras/layers/__init__.py deleted file mode 100644 index 790b4fa..0000000 --- a/aitk/keras/layers/__init__.py +++ /dev/null @@ -1,4324 +0,0 @@ -# -*- coding: utf-8 -*- -# ************************************************************** -# aitk.keras: A Python Keras model API -# -# Copyright (c) 2021 AITK Developers -# -# https://github.com/ArtificialIntelligenceToolkit/aitk.keras -# -# ************************************************************** - -"""A collection of composable layer objects for building neural networks""" -from abc import ABC, abstractmethod - -import numpy as np - -from ..wrappers import init_wrappers, Dropout - -from ..initializers import ( - WeightInitializer, - OptimizerInitializer, - ActivationInitializer, -) - -from ..utils import ( - pad1D, - pad2D, - conv1D, - conv2D, - im2col, - col2im, - dilate, - deconv2D_naive, - calc_pad_dims_2D, -) - -class Activation(): - def __init__(self, activation): - self.activation = activation - -NAME_CACHE = {} - -class LayerBase(ABC): - def __init__(self, name=None): - """An abstract base class inherited by all neural network layers""" - self.X = [] - self.act_fn = None - self.trainable = True - self.name = self.make_name(name) - self.optimizer = None - self.default_kernel_optimizer = "glorot_uniform" - - self.gradients = {} - self.parameters = {} - self.derived_variables = {} - self.input_layers = [] - self.output_layers = [] - - super().__init__() - - def __call__(self, input_layer): - if isinstance(input_layer, (list, tuple)): - for layer in input_layer: - layer.output_layers.append(self) - self.input_layers.append(layer) - else: - input_layer.output_layers.append(self) - self.input_layers.append(input_layer) - return self - - def __str__(self): - return f"<{self.__class__.__name__}(name='{self.name}')>" - - def make_name(self, name): - if name is None: - class_name = self.__class__.__name__.lower() - count = NAME_CACHE.get(class_name, 0) - if count == 0: - new_name = class_name - else: - new_name = "%s_%s" % (class_name, count) - NAME_CACHE[class_name] = count + 1 - return new_name - else: - return name - - def set_optimizer(self, optimizer=None): - optimizer = optimizer or self.default_kernel_optimizer - self.optimizer = OptimizerInitializer(optimizer)() - - def has_trainable_params(self): - return self.parameters != {} - - @abstractmethod - def _init_params(self, **kwargs): - raise NotImplementedError - - @abstractmethod - def forward(self, z, **kwargs): - """Perform a forward pass through the layer""" - raise NotImplementedError - - @abstractmethod - def backward(self, out, **kwargs): - """Perform a backward pass through the layer""" - raise NotImplementedError - - def freeze(self): - """ - Freeze the layer parameters at their current values so they can no - longer be updated. - """ - self.trainable = False - - def unfreeze(self): - """Unfreeze the layer parameters so they can be updated.""" - self.trainable = True - - def flush_gradients(self): - """Erase all the layer's derived variables and gradients.""" - assert self.trainable, "Layer is frozen" - self.X = [] - for k, v in self.derived_variables.items(): - self.derived_variables[k] = [] - - for k, v in self.gradients.items(): - self.gradients[k] = np.zeros_like(v) - - def update(self, cur_loss=None): - """ - Update the layer parameters using the accrued gradients and layer - optimizer. Flush all gradients once the update is complete. - """ - assert self.trainable, "Layer is frozen" - self.optimizer.step() - for k, v in self.gradients.items(): - if k in self.parameters: - self.parameters[k] = self.optimizer(self.parameters[k], v, k, cur_loss) - self.flush_gradients() - - def set_params(self, summary_dict): - """ - Set the layer parameters from a dictionary of values. - - Parameters - ---------- - summary_dict : dict - A dictionary of layer parameters and hyperparameters. If a required - parameter or hyperparameter is not included within `summary_dict`, - this method will use the value in the current layer's - :meth:`summary` method. - - Returns - ------- - layer : :doc:`Layer ` object - The newly-initialized layer. - """ - layer, sd = self, summary_dict - - # collapse `parameters` and `hyperparameters` nested dicts into a single - # merged dictionary - flatten_keys = ["parameters", "hyperparameters"] - for k in flatten_keys: - if k in sd: - entry = sd[k] - sd.update(entry) - del sd[k] - - for k, v in sd.items(): - if k in self.parameters: - layer.parameters[k] = v - if k in self.hyperparameters: - if k == "act_fn": - layer.act_fn = ActivationInitializer(v)() - elif k == "optimizer": - layer.optimizer = OptimizerInitializer(sd[k])() - elif k == "wrappers": - layer = init_wrappers(layer, sd[k]) - elif k not in ["wrappers", "optimizer"]: - setattr(layer, k, v) - return layer - - def get_weights(self): - # Returns pointers to weight matrices, in order: - return [self.parameters[key] for key in self.parameters] - - def set_weights(self, weights, copy=True): - # Ordered set of parameters: - for i, key in enumerate(self.parameters): - if copy: - self.parameters[key] = weights[i].copy() - else: - self.parameters[key] = weights[i] - self.weights_set = True - - def summary(self): - """Return a dict of the layer parameters, hyperparameters, and ID.""" - return { - "layer": self.hyperparameters["layer"], - "parameters": self.parameters, - "hyperparameters": self.hyperparameters, - } - - -class Input(LayerBase): - def __init__(self, input_shape, batch_size=None, name=None): - super().__init__(name=name) - self.n_out = input_shape - self.trainable = False - - def forward(self, z, **kwargs): - """Perform a forward pass through the layer""" - return z - - def backward(self, out, **kwargs): - """Perform a backward pass through the layer""" - raise NotImplementedError - - def _init_params(self, **kwargs): - raise NotImplementedError - -InputLayer = Input - -class DotProductAttention(LayerBase): - def __init__(self, scale=True, dropout_p=0, kernel_initializer="glorot_uniform", name=None): - r""" - A single "attention head" layer using a dot-product for the scoring function. - - Notes - ----- - The equations for a dot product attention layer are: - - .. math:: - - \mathbf{Z} &= \mathbf{K Q}^\\top \ \ \ \ &&\text{if scale = False} \\ - &= \mathbf{K Q}^\top / \sqrt{d_k} \ \ \ \ &&\text{if scale = True} \\ - \mathbf{Y} &= \text{dropout}(\text{softmax}(\mathbf{Z})) \mathbf{V} - - Parameters - ---------- - scale : bool - Whether to scale the the key-query dot product by the square root - of the key/query vector dimensionality before applying the Softmax. - This is useful, since the scale of dot product will otherwise - increase as query / key dimensions grow. Default is True. - dropout_p : float in [0, 1) - The dropout propbability during training, applied to the output of - the softmax. If 0, no dropout is applied. Default is 0. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - Unused. - """ # noqa: E501 - super().__init__(name=name) - - self.kernel_initializer = kernel_initializer - self.scale = scale - self.dropout_p = dropout_p - self._init_params() - - def _init_params(self): - self.softmax = Dropout(Softmax(), self.dropout_p) - smdv = self.softmax.derived_variables - self.derived_variables = { - "attention_weights": [], - "dropout_mask": smdv["wrappers"][0]["dropout_mask"], - } - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "DotProductAttention", - "kernel_initializer": self.kernel_initializer, - "scale": self.scale, - "dropout_p": self.dropout_p, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def freeze(self): - """ - Freeze the layer parameters at their current values so they can no - longer be updated. - """ - self.trainable = False - self.softmax.freeze() - - def unfreeze(self): - """Unfreeze the layer parameters so they can be updated.""" - self.trainable = True - self.softmax.unfreeze() - - def forward(self, Q, K, V, retain_derived=True): - r""" - Compute the attention-weighted output of a collection of keys, values, - and queries. - - Notes - ----- - In the most abstract (ie., hand-wave-y) sense: - - - Query vectors ask questions - - Key vectors advertise their relevancy to questions - - Value vectors give possible answers to questions - - The dot product between Key and Query vectors provides scores for - each of the the `n_ex` different Value vectors - - For a single query and `n` key-value pairs, dot-product attention (with - scaling) is:: - - w0 = dropout(softmax( (query @ key[0]) / sqrt(d_k) )) - w1 = dropout(softmax( (query @ key[1]) / sqrt(d_k) )) - ... - wn = dropout(softmax( (query @ key[n]) / sqrt(d_k) )) - - y = np.array([w0, ..., wn]) @ values - (1 × n_ex) (n_ex × d_v) - - In words, keys and queries are combined via dot-product to produce a - score, which is then passed through a softmax to produce a weight on - each value vector in Values. We elementwise multiply each value vector - by its weight, and then take the elementwise sum of each weighted value - vector to get the :math:`1 \times d_v` output for the current example. - - In vectorized form, - - .. math:: - - \mathbf{Y} = \text{dropout}( - \text{softmax}(\mathbf{KQ}^\top / \sqrt{d_k}) - ) \mathbf{V} - - Parameters - ---------- - Q : :py:class:`ndarray ` of shape `(n_ex, *, d_k)` - A set of `n_ex` query vectors packed into a single matrix. - Optional middle dimensions can be used to specify, e.g., the number - of parallel attention heads. - K : :py:class:`ndarray ` of shape `(n_ex, *, d_k)` - A set of `n_ex` key vectors packed into a single matrix. Optional - middle dimensions can be used to specify, e.g., the number of - parallel attention heads. - V : :py:class:`ndarray ` of shape `(n_ex, *, d_v)` - A set of `n_ex` value vectors packed into a single matrix. Optional - middle dimensions can be used to specify, e.g., the number of - parallel attention heads. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, *, d_v)` - The attention-weighted output values - """ - Y, weights = self._fwd(Q, K, V) - - if retain_derived: - self.X.append((Q, K, V)) - self.derived_variables["attention_weights"].append(weights) - - return Y - - def _fwd(self, Q, K, V): - """Actual computation of forward pass""" - scale = 1 / np.sqrt(Q.shape[-1]) if self.scale else 1 - scores = Q @ K.swapaxes(-2, -1) * scale # attention scores - weights = self.softmax.forward(scores) # attention weights - Y = weights @ V - return Y, weights - - def backward(self, dLdy, retain_grads=True): - r""" - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, *, d_v)` - The gradient of the loss wrt. the layer output `Y` - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dQ : :py:class:`ndarray ` of shape `(n_ex, *, d_k)` or list of arrays - The gradient of the loss wrt. the layer query matrix/matrices `Q`. - dK : :py:class:`ndarray ` of shape `(n_ex, *, d_k)` or list of arrays - The gradient of the loss wrt. the layer key matrix/matrices `K`. - dV : :py:class:`ndarray ` of shape `(n_ex, *, d_v)` or list of arrays - The gradient of the loss wrt. the layer value matrix/matrices `V`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dQ, dK, dV = [], [], [] - weights = self.derived_variables["attention_weights"] - for dy, (q, k, v), w in zip(dLdy, self.X, weights): - dq, dk, dv = self._bwd(dy, q, k, v, w) - dQ.append(dq) - dK.append(dk) - dV.append(dv) - - if len(self.X) == 1: - dQ, dK, dV = dQ[0], dK[0], dV[0] - - return dQ, dK, dV - - def _bwd(self, dy, q, k, v, weights): - """Actual computation of the gradient of the loss wrt. q, k, and v""" - d_k = k.shape[-1] - scale = 1 / np.sqrt(d_k) if self.scale else 1 - - dV = weights.swapaxes(-2, -1) @ dy - dWeights = dy @ v.swapaxes(-2, -1) - dScores = self.softmax.backward(dWeights) - dQ = dScores @ k * scale - dK = dScores.swapaxes(-2, -1) @ q * scale - return dQ, dK, dV - - -class RBM(LayerBase): - def __init__(self, n_out, K=1, kernel_initializer="glorot_uniform", name=None): - """ - A Restricted Boltzmann machine with Bernoulli visible and hidden units. - - Parameters - ---------- - n_out : int - The number of output dimensions/units. - K : int - The number of contrastive divergence steps to run before computing - a single gradient update. Default is 1. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.K = K # CD-K - self.kernel_initializer = kernel_initializer - self.n_in = None - self.n_out = n_out - self.is_initialized = False - self.weights_set = False - self.act_fn_V = ActivationInitializer("Sigmoid")() - self.act_fn_H = ActivationInitializer("Sigmoid")() - self.parameters = {"W": None, "b_in": None, "b_out": None} - - self._init_params() - - def _init_params(self): - if not self.weights_set: - b_in = np.zeros((1, self.n_in)) - b_out = np.zeros((1, self.n_out)) - init_weights = WeightInitializer(str(self.act_fn_V), mode=self.kernel_initializer) - W = init_weights((self.n_in, self.n_out)) - else: - W, b_in, b_out = self.get_weights() - - self.parameters = {"W": W, "b_in": b_in, "b_out": b_out} - self.gradients = { - "W": np.zeros_like(W), - "b_in": np.zeros_like(b_in), - "b_out": np.zeros_like(b_out), - } - - self.derived_variables = { - "V": None, - "p_H": None, - "p_V_prime": None, - "p_H_prime": None, - "positive_grad": None, - "negative_grad": None, - } - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "RBM", - "K": self.K, - "n_in": self.n_in, - "n_out": self.n_out, - "kernel_initializer": self.kernel_initializer, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameterse, - }, - } - - def CD_update(self, X): - """ - Perform a single contrastive divergence-`k` training update using the - visible inputs `X` as a starting point for the Gibbs sampler. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. Each feature in X should ideally be - binary-valued, although it is possible to also train on real-valued - features ranging between (0, 1) (e.g., grayscale images). - """ - self.forward(X) - self.backward() - - def forward(self, V, K=None, retain_derived=True): - """ - Perform the CD-`k` "forward pass" of visible inputs into hidden units - and back. - - Notes - ----- - This implementation follows [1]_'s recommendations for the RBM forward - pass: - - - Use real-valued probabilities for both the data and the visible - unit reconstructions. - - Only the final update of the hidden units should use the actual - probabilities -- all others should be sampled binary states. - - When collecting the pairwise statistics for learning weights or - the individual statistics for learning biases, use the - probabilities, not the binary states. - - References - ---------- - .. [1] Hinton, G. (2010). "A practical guide to training restricted - Boltzmann machines". *UTML TR 2010-003* - - Parameters - ---------- - V : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Visible input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. Each feature in V should ideally be - binary-valued, although it is possible to also train on real-valued - features ranging between (0, 1) (e.g., grayscale images). - K : int - The number of steps of contrastive divergence steps to run before - computing the gradient update. If None, use ``self.K``. Default is - None. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - """ - if not self.is_initialized: - self.n_in = V.shape[1] - self._init_params() - - # override self.K if necessary - K = self.K if K is None else K - - W = self.parameters["W"] - b_in = self.parameters["b_in"] - b_out = self.parameters["b_out"] - - # compute hidden unit probabilities - Z_H = V @ W + b_out - p_H = self.act_fn_H.fn(Z_H) - - # sample hidden states (stochastic binary values) - H = np.random.rand(*p_H.shape) <= p_H - H = H.astype(float) - - # always use probabilities when computing gradients - positive_grad = V.T @ p_H - - # perform CD-k - # TODO: use persistent CD-k - # https://www.cs.toronto.edu/~tijmen/pcd/pcd.pdf - H_prime = H.copy() - for k in range(K): - # resample v' given h (H_prime is binary for all but final step) - Z_V_prime = H_prime @ W.T + b_in - p_V_prime = self.act_fn_V.fn(Z_V_prime) - - # don't resample visual units - always use raw probabilities! - V_prime = p_V_prime - - # compute p(h' | v') - Z_H_prime = V_prime @ W + b_out - p_H_prime = self.act_fn_H.fn(Z_H_prime) - - # if this is the final iteration of CD, keep hidden state - # probabilities (don't sample) - H_prime = p_H_prime - if k != self.K - 1: - H_prime = np.random.rand(*p_H_prime.shape) <= p_H_prime - H_prime = H_prime.astype(float) - - negative_grad = p_V_prime.T @ p_H_prime - - if retain_derived: - self.derived_variables["V"] = V - self.derived_variables["p_H"] = p_H - self.derived_variables["p_V_prime"] = p_V_prime - self.derived_variables["p_H_prime"] = p_H_prime - self.derived_variables["positive_grad"] = positive_grad - self.derived_variables["negative_grad"] = negative_grad - - def backward(self, retain_grads=True, *args): - """ - Perform a gradient update on the layer parameters via the contrastive - divergence equations. - - Parameters - ---------- - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - """ - V = self.derived_variables["V"] - p_H = self.derived_variables["p_H"] - p_V_prime = self.derived_variables["p_V_prime"] - p_H_prime = self.derived_variables["p_H_prime"] - positive_grad = self.derived_variables["positive_grad"] - negative_grad = self.derived_variables["negative_grad"] - - if retain_grads: - self.gradients["b_in"] = V - p_V_prime - self.gradients["b_out"] = p_H - p_H_prime - self.gradients["W"] = positive_grad - negative_grad - - def reconstruct(self, X, n_steps=10, return_prob=False): - """ - Reconstruct an input `X` by running the trained Gibbs sampler for - `n_steps`-worth of CD-`k`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. Each feature in `X` should ideally be - binary-valued, although it is possible to also train on real-valued - features ranging between (0, 1) (e.g., grayscale images). If `X` has - missing values, it may be sufficient to mark them with random - entries and allow the reconstruction to impute them. - n_steps : int - The number of Gibbs sampling steps to perform when generating the - reconstruction. Default is 10. - return_prob : bool - Whether to return the real-valued feature probabilities for the - reconstruction or the binary samples. Default is False. - - Returns - ------- - V : :py:class:`ndarray ` of shape `(n_ex, in_ch)` - The reconstruction (or feature probabilities if `return_prob` is - true) of the visual input `X` after running the Gibbs sampler for - `n_steps`. - """ - self.forward(X, K=n_steps) - p_V_prime = self.derived_variables["p_V_prime"] - - # ignore the gradients produced during this reconstruction - self.flush_gradients() - - # sample V_prime reconstruction if return_prob is False - V = p_V_prime - if not return_prob: - V = (np.random.rand(*p_V_prime.shape) <= p_V_prime).astype(float) - return V - - -####################################################################### -# Layer Ops # -####################################################################### - - -class Add(LayerBase): - def __init__(self, act_fn=None, name=None): - """ - An "addition" layer that returns the sum of its inputs, passed through - an optional nonlinearity. - - Parameters - ---------- - act_fn : str, :doc:`Activation ` object, or None - The element-wise output nonlinearity used in computing the final - output. If None, use the identity function :math:`f(x) = x`. - Default is None. - """ # noqa: E501 - super().__init__(name=name) - self.act_fn = ActivationInitializer(act_fn)() - self._init_params() - - def _init_params(self): - self.derived_variables = {"sum": []} - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Sum", - "act_fn": str(self.act_fn), - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - r""" - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : list of length `n_inputs` - A list of tensors, all of the same shape. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, *)` - The sum over the `n_ex` examples. - """ - out = X[0].copy() - for i in range(1, len(X)): - out += X[i] - if retain_derived: - self.X.append(X) - self.derived_variables["sum"].append(out) - return self.act_fn(out) - - def backward(self, dLdY, retain_grads=True): - r""" - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, *)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : list of length `n_inputs` - The gradient of the loss wrt. each input in `X`. - """ - if not isinstance(dLdY, list): - dLdY = [dLdY] - - X = self.X - _sum = self.derived_variables["sum"] - grads = [self._bwd(dy, x, ss) for dy, x, ss in zip(dLdY, X, _sum)] - return grads[0] if len(X) == 1 else grads - - def _bwd(self, dLdY, X, _sum): - """Actual computation of gradient of the loss wrt. each input""" - grads = [dLdY * self.act_fn.grad(_sum) for _ in X] - return grads - - -class Multiply(LayerBase): - def __init__(self, act_fn=None, name=None): - """ - A multiplication layer that returns the *elementwise* product of its - inputs, passed through an optional nonlinearity. - - Parameters - ---------- - act_fn : str, :doc:`Activation ` object, or None - The element-wise output nonlinearity used in computing the final - output. If None, use the identity function :math:`f(x) = x`. - Default is None. - """ # noqa: E501 - super().__init__(name=name) - self.act_fn = ActivationInitializer(act_fn)() - self._init_params() - - def _init_params(self): - self.derived_variables = {"product": []} - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Multiply", - "act_fn": str(self.act_fn), - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - r""" - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : list of length `n_inputs` - A list of tensors, all of the same shape. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, *)` - The product over the `n_ex` examples. - """ # noqa: E501 - out = X[0].copy() - for i in range(1, len(X)): - out *= X[i] - if retain_derived: - self.X.append(X) - self.derived_variables["product"].append(out) - return self.act_fn(out) - - def backward(self, dLdY, retain_grads=True): - r""" - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, *)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : list of length `n_inputs` - The gradient of the loss wrt. each input in `X`. - """ - if not isinstance(dLdY, list): - dLdY = [dLdY] - - X = self.X - _prod = self.derived_variables["product"] - grads = [self._bwd(dy, x, pr) for dy, x, pr in zip(dLdY, X, _prod)] - return grads[0] if len(X) == 1 else grads - - def _bwd(self, dLdY, X, prod): - """Actual computation of gradient of loss wrt. each input""" - grads = [dLdY * self.act_fn.grad(prod)] * len(X) - for i, x in enumerate(X): - grads = [g * x if j != i else g for j, g in enumerate(grads)] - return grads - - -class Flatten(LayerBase): - def __init__(self, keep_dim="first", name=None): - """ - Flatten a multidimensional input into a 2D matrix. - - Parameters - ---------- - keep_dim : {'first', 'last', -1} - The dimension of the original input to retain. Typically used for - retaining the minibatch dimension.. If -1, flatten all dimensions. - Default is 'first'. - """ # noqa: E501 - super().__init__(name=name) - self.n_out = 0 - self.n_in = [] - - self.keep_dim = keep_dim - self._init_params() - - def _init_params(self): - self.X = [] - self.gradients = {} - self.parameters = {} - self.derived_variables = {"in_dims": []} - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Flatten", - "keep_dim": self.keep_dim, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - r""" - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` - Input volume to flatten. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(*out_dims)` - Flattened output. If `keep_dim` is `'first'`, `X` is reshaped to - ``(X.shape[0], -1)``, otherwise ``(-1, X.shape[0])``. - """ - self.n_in = X.shape - if retain_derived: - self.derived_variables["in_dims"].append(X.shape) - if self.keep_dim == -1: - return X.flatten().reshape(1, -1) - rs = (X.shape[0], -1) if self.keep_dim == "first" else (-1, X.shape[-1]) - self.n_out = rs - return X.reshape(*rs) - - def backward(self, dLdy, retain_grads=True): - r""" - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(*out_dims)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(*in_dims)` or list of arrays - The gradient of the loss wrt. the layer input(s) `X`. - """ # noqa: E501 - if not isinstance(dLdy, list): - dLdy = [dLdy] - in_dims = self.derived_variables["in_dims"] - out = [dy.reshape(*dims) for dy, dims in zip(dLdy, in_dims)] - return out[0] if len(dLdy) == 1 else out - -class Concatenate(LayerBase): - def __init__(self, name=None): - """ - Concatenate a list of input layers into one. - """ # noqa: E501 - super().__init__(name=name) - self.n_out = 0 - self.n_in = [] - - self._init_params() - - def _init_params(self): - self.X = [] - self.gradients = {} - self.parameters = {} - self.derived_variables = {} - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Concatenate", - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - r""" - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` - Input volume to flatten. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : - """ - result = np.concatenate(X, -1) - self.n_out = result.shape[1:] - self.n_in = [layer.n_out for layer in self.input_layers] - return result - - def backward(self, dLdy, retain_grads=True): - r""" - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(*out_dims)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : - """ # noqa: E501 - return dLdy - - -####################################################################### -# Normalization Layers # -####################################################################### - - -class BatchNorm2D(LayerBase): - def __init__(self, momentum=0.9, epsilon=1e-5, name=None): - """ - A batch normalization layer for two-dimensional inputs with an - additional channel dimension. - - Notes - ----- - BatchNorm is an attempt address the problem of internal covariate - shift (ICS) during training by normalizing layer inputs. - - ICS refers to the change in the distribution of layer inputs during - training as a result of the changing parameters of the previous - layer(s). ICS can make it difficult to train models with saturating - nonlinearities, and in general can slow training by requiring a lower - learning rate. - - Equations [train]:: - - Y = scaler * norm(X) + intercept - norm(X) = (X - mean(X)) / sqrt(var(X) + epsilon) - - Equations [test]:: - - Y = scaler * running_norm(X) + intercept - running_norm(X) = (X - running_mean) / sqrt(running_var + epsilon) - - In contrast to :class:`LayerNorm2D`, the BatchNorm layer calculates - the mean and var across the *batch* rather than the output features. - This has two disadvantages: - - 1. It is highly affected by batch size: smaller mini-batch sizes - increase the variance of the estimates for the global mean and - variance. - - 2. It is difficult to apply in RNNs -- one must fit a separate - BatchNorm layer for *each* time-step. - - Parameters - ---------- - momentum : float - The momentum term for the running mean/running std calculations. - The closer this is to 1, the less weight will be given to the - mean/std of the current batch (i.e., higher smoothing). Default is - 0.9. - epsilon : float - A small smoothing constant to use during computation of ``norm(X)`` - to avoid divide-by-zero errors. Default is 1e-5. - """ # noqa: E501 - super().__init__(name=name) - - self.in_ch = None - self.out_ch = None - self.epsilon = epsilon - self.momentum = momentum - self.parameters = { - "scaler": None, - "intercept": None, - "running_var": None, - "running_mean": None, - } - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - scaler = np.random.rand(self.in_ch) - intercept = np.zeros(self.in_ch) - - # init running mean and std at 0 and 1, respectively - running_mean = np.zeros(self.in_ch) - running_var = np.ones(self.in_ch) - - self.parameters = { - "scaler": scaler, - "intercept": intercept, - "running_var": running_var, - "running_mean": running_mean, - } - - self.gradients = { - "scaler": np.zeros_like(scaler), - "intercept": np.zeros_like(intercept), - } - - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "BatchNorm2D", - "act_fn": None, - "in_ch": self.in_ch, - "out_ch": self.out_ch, - "epsilon": self.epsilon, - "momentum": self.momentum, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def reset_running_stats(self): - """Reset the running mean and variance estimates to 0 and 1.""" - assert self.trainable, "Layer is frozen" - self.parameters["running_mean"] = np.zeros(self.in_ch) - self.parameters["running_var"] = np.ones(self.in_ch) - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Notes - ----- - Equations [train]:: - - Y = scaler * norm(X) + intercept - norm(X) = (X - mean(X)) / sqrt(var(X) + epsilon) - - Equations [test]:: - - Y = scaler * running_norm(X) + intercept - running_norm(X) = (X - running_mean) / sqrt(running_var + epsilon) - - In contrast to :class:`LayerNorm2D`, the BatchNorm layer calculates the - mean and var across the *batch* rather than the output features. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume containing the `in_rows` x `in_cols`-dimensional - features for a minibatch of `n_ex` examples. - retain_derived : bool - Whether to use the current intput to adjust the running mean and - running_var computations. Setting this to True is the same as - freezing the layer for the current input. Default is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Layer output for each of the `n_ex` examples. - """ # noqa: E501 - if not self.is_initialized: - self.in_ch = self.out_ch = X.shape[3] - self._init_params() - - ep = self.hyperparameters["epsilon"] - mm = self.hyperparameters["momentum"] - rm = self.parameters["running_mean"] - rv = self.parameters["running_var"] - - scaler = self.parameters["scaler"] - intercept = self.parameters["intercept"] - - # if the layer is frozen, use our running mean/std values rather - # than the mean/std values for the new batch - X_mean = self.parameters["running_mean"] - X_var = self.parameters["running_var"] - - if self.trainable and retain_derived: - X_mean, X_var = X.mean(axis=(0, 1, 2)), X.var(axis=(0, 1, 2)) # , ddof=1) - self.parameters["running_mean"] = mm * rm + (1.0 - mm) * X_mean - self.parameters["running_var"] = mm * rv + (1.0 - mm) * X_var - - if retain_derived: - self.X.append(X) - - N = (X - X_mean) / np.sqrt(X_var + ep) - y = scaler * N + intercept - return y - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss wrt. the layer input `X`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - for dy, x in zip(dLdy, X): - dx, dScaler, dIntercept = self._bwd(dy, x) - dX.append(dx) - - if retain_grads: - self.gradients["scaler"] += dScaler - self.gradients["intercept"] += dIntercept - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X): - """Computation of gradient of loss wrt. X, scaler, and intercept""" - scaler = self.parameters["scaler"] - ep = self.hyperparameters["epsilon"] - - # reshape to 2D, retaining channel dim - X_shape = X.shape - X = np.reshape(X, (-1, X.shape[3])) - dLdy = np.reshape(dLdy, (-1, dLdy.shape[3])) - - # apply 1D batchnorm backward pass on reshaped array - n_ex, in_ch = X.shape - X_mean, X_var = X.mean(axis=0), X.var(axis=0) # , ddof=1) - - N = (X - X_mean) / np.sqrt(X_var + ep) - dIntercept = dLdy.sum(axis=0) - dScaler = np.sum(dLdy * N, axis=0) - - dN = dLdy * scaler - dX = (n_ex * dN - dN.sum(axis=0) - N * (dN * N).sum(axis=0)) / ( - n_ex * np.sqrt(X_var + ep) - ) - - return np.reshape(dX, X_shape), dScaler, dIntercept - - -class BatchNorm1D(LayerBase): - def __init__(self, momentum=0.9, epsilon=1e-5, name=None): - """ - A batch normalization layer for 1D inputs. - - Notes - ----- - BatchNorm is an attempt address the problem of internal covariate - shift (ICS) during training by normalizing layer inputs. - - ICS refers to the change in the distribution of layer inputs during - training as a result of the changing parameters of the previous - layer(s). ICS can make it difficult to train models with saturating - nonlinearities, and in general can slow training by requiring a lower - learning rate. - - Equations [train]:: - - Y = scaler * norm(X) + intercept - norm(X) = (X - mean(X)) / sqrt(var(X) + epsilon) - - Equations [test]:: - - Y = scaler * running_norm(X) + intercept - running_norm(X) = (X - running_mean) / sqrt(running_var + epsilon) - - In contrast to :class:`LayerNorm1D`, the BatchNorm layer calculates - the mean and var across the *batch* rather than the output features. - This has two disadvantages: - - 1. It is highly affected by batch size: smaller mini-batch sizes - increase the variance of the estimates for the global mean and - variance. - - 2. It is difficult to apply in RNNs -- one must fit a separate - BatchNorm layer for *each* time-step. - - Parameters - ---------- - momentum : float - The momentum term for the running mean/running std calculations. - The closer this is to 1, the less weight will be given to the - mean/std of the current batch (i.e., higher smoothing). Default is - 0.9. - epsilon : float - A small smoothing constant to use during computation of ``norm(X)`` - to avoid divide-by-zero errors. Default is 1e-5. - """ # noqa: E501 - super().__init__(name=name) - - self.n_in = None - self.n_out = None - self.epsilon = epsilon - self.momentum = momentum - self.parameters = { - "scaler": None, - "intercept": None, - "running_var": None, - "running_mean": None, - } - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - scaler = np.random.rand(self.n_in) - intercept = np.zeros(self.n_in) - - # init running mean and std at 0 and 1, respectively - running_mean = np.zeros(self.n_in) - running_var = np.ones(self.n_in) - - self.parameters = { - "scaler": scaler, - "intercept": intercept, - "running_mean": running_mean, - "running_var": running_var, - } - - self.gradients = { - "scaler": np.zeros_like(scaler), - "intercept": np.zeros_like(intercept), - } - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "BatchNorm1D", - "act_fn": None, - "n_in": self.n_in, - "n_out": self.n_out, - "epsilon": self.epsilon, - "momentum": self.momentum, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def reset_running_stats(self): - """Reset the running mean and variance estimates to 0 and 1.""" - assert self.trainable, "Layer is frozen" - self.parameters["running_mean"] = np.zeros(self.n_in) - self.parameters["running_var"] = np.ones(self.n_in) - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. - retain_derived : bool - Whether to use the current intput to adjust the running mean and - running_var computations. Setting this to True is the same as - freezing the layer for the current input. Default is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer output for each of the `n_ex` examples - """ - if not self.is_initialized: - self.n_in = self.n_out = X.shape[1] - self._init_params() - - ep = self.hyperparameters["epsilon"] - mm = self.hyperparameters["momentum"] - rm = self.parameters["running_mean"] - rv = self.parameters["running_var"] - - scaler = self.parameters["scaler"] - intercept = self.parameters["intercept"] - - # if the layer is frozen, use our running mean/std values rather - # than the mean/std values for the new batch - X_mean = self.parameters["running_mean"] - X_var = self.parameters["running_var"] - - if self.trainable and retain_derived: - X_mean, X_var = X.mean(axis=0), X.var(axis=0) # , ddof=1) - self.parameters["running_mean"] = mm * rm + (1.0 - mm) * X_mean - self.parameters["running_var"] = mm * rv + (1.0 - mm) * X_var - - if retain_derived: - self.X.append(X) - - N = (X - X_mean) / np.sqrt(X_var + ep) - y = scaler * N + intercept - return y - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer input `X`. - """ - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - for dy, x in zip(dLdy, X): - dx, dScaler, dIntercept = self._bwd(dy, x) - dX.append(dx) - - if retain_grads: - self.gradients["scaler"] += dScaler - self.gradients["intercept"] += dIntercept - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X): - """Computation of gradient of loss wrt X, scaler, and intercept""" - scaler = self.parameters["scaler"] - ep = self.hyperparameters["epsilon"] - - n_ex, n_in = X.shape - X_mean, X_var = X.mean(axis=0), X.var(axis=0) # , ddof=1) - - N = (X - X_mean) / np.sqrt(X_var + ep) - dIntercept = dLdy.sum(axis=0) - dScaler = np.sum(dLdy * N, axis=0) - - dN = dLdy * scaler - dX = (n_ex * dN - dN.sum(axis=0) - N * (dN * N).sum(axis=0)) / ( - n_ex * np.sqrt(X_var + ep) - ) - - return dX, dScaler, dIntercept - - -class LayerNorm2D(LayerBase): - def __init__(self, epsilon=1e-5, name=None): - """ - A layer normalization layer for 2D inputs with an additional channel - dimension. - - Notes - ----- - In contrast to :class:`BatchNorm2D`, the LayerNorm layer calculates the - mean and variance across *features* rather than examples in the batch - ensuring that the mean and variance estimates are independent of batch - size and permitting straightforward application in RNNs. - - Equations [train & test]:: - - Y = scaler * norm(X) + intercept - norm(X) = (X - mean(X)) / sqrt(var(X) + epsilon) - - Also in contrast to :class:`BatchNorm2D`, `scaler` and `intercept` are applied - *elementwise* to ``norm(X)``. - - Parameters - ---------- - epsilon : float - A small smoothing constant to use during computation of ``norm(X)`` - to avoid divide-by-zero errors. Default is 1e-5. - """ # noqa: E501 - super().__init__(name=name) - - self.in_ch = None - self.out_ch = None - self.epsilon = epsilon - self.parameters = {"scaler": None, "intercept": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self, X_shape): - n_ex, in_rows, in_cols, in_ch = X_shape - - scaler = np.random.rand(in_rows, in_cols, in_ch) - intercept = np.zeros((in_rows, in_cols, in_ch)) - - self.parameters = {"scaler": scaler, "intercept": intercept} - - self.gradients = { - "scaler": np.zeros_like(scaler), - "intercept": np.zeros_like(intercept), - } - - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "LayerNorm2D", - "act_fn": None, - "in_ch": self.in_ch, - "out_ch": self.out_ch, - "epsilon": self.epsilon, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Notes - ----- - Equations [train & test]:: - - Y = scaler * norm(X) + intercept - norm(X) = (X - mean(X)) / sqrt(var(X) + epsilon) - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume containing the `in_rows` by `in_cols`-dimensional - features for a minibatch of `n_ex` examples. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Layer output for each of the `n_ex` examples. - """ # noqa: E501 - if not self.is_initialized: - self.in_ch = self.out_ch = X.shape[3] - self._init_params(X.shape) - - scaler = self.parameters["scaler"] - ep = self.hyperparameters["epsilon"] - intercept = self.parameters["intercept"] - - if retain_derived: - self.X.append(X) - - X_var = X.var(axis=(1, 2, 3), keepdims=True) - X_mean = X.mean(axis=(1, 2, 3), keepdims=True) - lnorm = (X - X_mean) / np.sqrt(X_var + ep) - y = scaler * lnorm + intercept - return y - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss wrt. the layer input `X`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - for dy, x in zip(dLdy, X): - dx, dScaler, dIntercept = self._bwd(dy, x) - dX.append(dx) - - if retain_grads: - self.gradients["scaler"] += dScaler - self.gradients["intercept"] += dIntercept - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dy, X): - """Computation of gradient of the loss wrt X, scaler, intercept""" - scaler = self.parameters["scaler"] - ep = self.hyperparameters["epsilon"] - - X_mean = X.mean(axis=(1, 2, 3), keepdims=True) - X_var = X.var(axis=(1, 2, 3), keepdims=True) - lnorm = (X - X_mean) / np.sqrt(X_var + ep) - - dLnorm = dy * scaler - dIntercept = dy.sum(axis=0) - dScaler = np.sum(dy * lnorm, axis=0) - - n_in = np.prod(X.shape[1:]) - lnorm = lnorm.reshape(-1, n_in) - dLnorm = dLnorm.reshape(lnorm.shape) - X_var = X_var.reshape(X_var.shape[:2]) - - dX = ( - n_in * dLnorm - - dLnorm.sum(axis=1, keepdims=True) - - lnorm * (dLnorm * lnorm).sum(axis=1, keepdims=True) - ) / (n_in * np.sqrt(X_var + ep)) - - # reshape X gradients back to proper dimensions - return np.reshape(dX, X.shape), dScaler, dIntercept - - -class LayerNorm1D(LayerBase): - def __init__(self, epsilon=1e-5, name=None): - """ - A layer normalization layer for 1D inputs. - - Notes - ----- - In contrast to :class:`BatchNorm1D`, the LayerNorm layer calculates the - mean and variance across *features* rather than examples in the batch - ensuring that the mean and variance estimates are independent of batch - size and permitting straightforward application in RNNs. - - Equations [train & test]:: - - Y = scaler * norm(X) + intercept - norm(X) = (X - mean(X)) / sqrt(var(X) + epsilon) - - Also in contrast to :class:`BatchNorm1D`, `scaler` and `intercept` are applied - *elementwise* to ``norm(X)``. - - Parameters - ---------- - epsilon : float - A small smoothing constant to use during computation of ``norm(X)`` - to avoid divide-by-zero errors. Default is 1e-5. - """ # noqa: E501 - super().__init__(name=name) - - self.n_in = None - self.n_out = None - self.epsilon = epsilon - self.parameters = {"scaler": None, "intercept": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - scaler = np.random.rand(self.n_in) - intercept = np.zeros(self.n_in) - - self.parameters = {"scaler": scaler, "intercept": intercept} - - self.gradients = { - "scaler": np.zeros_like(scaler), - "intercept": np.zeros_like(intercept), - } - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "LayerNorm1D", - "act_fn": None, - "n_in": self.n_in, - "n_out": self.n_out, - "epsilon": self.epsilon, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer output for each of the `n_ex` examples. - """ - if not self.is_initialized: - self.n_in = self.n_out = X.shape[1] - self._init_params() - - scaler = self.parameters["scaler"] - ep = self.hyperparameters["epsilon"] - intercept = self.parameters["intercept"] - - if retain_derived: - self.X.append(X) - - X_mean, X_var = X.mean(axis=1, keepdims=True), X.var(axis=1, keepdims=True) - lnorm = (X - X_mean) / np.sqrt(X_var + ep) - y = scaler * lnorm + intercept - return y - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer input `X`. - """ - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - for dy, x in zip(dLdy, X): - dx, dScaler, dIntercept = self._bwd(dy, x) - dX.append(dx) - - if retain_grads: - self.gradients["scaler"] += dScaler - self.gradients["intercept"] += dIntercept - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X): - """Computation of gradient of the loss wrt X, scaler, intercept""" - scaler = self.parameters["scaler"] - ep = self.hyperparameters["epsilon"] - - n_ex, n_in = X.shape - X_mean, X_var = X.mean(axis=1, keepdims=True), X.var(axis=1, keepdims=True) - - lnorm = (X - X_mean) / np.sqrt(X_var + ep) - dIntercept = dLdy.sum(axis=0) - dScaler = np.sum(dLdy * lnorm, axis=0) - - dLnorm = dLdy * scaler - dX = ( - n_in * dLnorm - - dLnorm.sum(axis=1, keepdims=True) - - lnorm * (dLnorm * lnorm).sum(axis=1, keepdims=True) - ) / (n_in * np.sqrt(X_var + ep)) - - return dX, dScaler, dIntercept - - -####################################################################### -# MLP Layers # -####################################################################### - - -class Embedding(LayerBase): - def __init__( - self, n_out, vocab_size, pool=None, kernel_initializer="glorot_uniform", name=None - ): - """ - An embedding layer. - - Notes - ----- - Equations:: - - Y = W[x] - - NB. This layer must be the first in a neural network as the gradients - do not get passed back through to the inputs. - - Parameters - ---------- - n_out : int - The dimensionality of the embeddings - vocab_size : int - The total number of items in the vocabulary. All integer indices - are expected to range between 0 and `vocab_size - 1`. - pool : {'sum', 'mean', None} - If not None, apply this function to the collection of `n_in` - encodings in each example to produce a single, pooled embedding. - Default is None. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - fstr = "'pool' must be either 'sum', 'mean', or None but got '{}'" - assert pool in ["sum", "mean", None], fstr.format(pool) - - self.kernel_initializer = kernel_initializer - self.pool = pool - self.n_out = n_out - self.vocab_size = vocab_size - self.parameters = {"W": None} - self.is_initialized = False - self.weights_set = False - self._init_params() - - def _init_params(self): - if not self.weights_set: - init_weights = WeightInitializer("Affine(slope=1, intercept=0)", mode=self.kernel_initializer) - W = init_weights((self.vocab_size, self.n_out)) - else: - W = self.get_weights() - - self.parameters = {"W": W} - self.derived_variables = {} - self.gradients = {"W": np.zeros_like(W)} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Embedding", - "kernel_initializer": self.kernel_initializer, - "pool": self.pool, - "n_out": self.n_out, - "vocab_size": self.vocab_size, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def lookup(self, ids): - """ - Return the embeddings associated with the IDs in `ids`. - - Parameters - ---------- - word_ids : :py:class:`ndarray ` of shape (`M`,) - An array of `M` IDs to retrieve embeddings for. - - Returns - ------- - embeddings : :py:class:`ndarray ` of shape (`M`, `n_out`) - The embedding vectors for each of the `M` IDs. - """ - return self.parameters["W"][ids] - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Notes - ----- - Equations: - Y = W[x] - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` or list of length `n_ex` - Layer input, representing a minibatch of `n_ex` examples. If - ``self.pool`` is None, each example must consist of exactly `n_in` - integer token IDs. Otherwise, `X` can be a ragged array, with each - example consisting of a variable number of token IDs. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through with regard to this input. - Default is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_in, n_out)` - Embeddings for each coordinate of each of the `n_ex` examples - """ # noqa: E501 - # if X is a ragged array - if isinstance(X, list) and not issubclass(X[0].dtype.type, np.integer): - fstr = "Input to Embedding layer must be an array of integers, got '{}'" - raise TypeError(fstr.format(X[0].dtype.type)) - - # otherwise - if isinstance(X, np.ndarray) and not issubclass(X.dtype.type, np.integer): - fstr = "Input to Embedding layer must be an array of integers, got '{}'" - raise TypeError(fstr.format(X.dtype.type)) - - Y = self._fwd(X) - if retain_derived: - self.X.append(X) - return Y - - def _fwd(self, X): - """Actual computation of forward pass""" - W = self.parameters["W"] - if self.pool is None: - emb = W[X] - elif self.pool == "sum": - emb = np.array([W[x].sum(axis=0) for x in X])[:, None, :] - elif self.pool == "mean": - emb = np.array([W[x].mean(axis=0) for x in X])[:, None, :] - return emb - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to embedding weights. - - Notes - ----- - Because the items in `X` are interpreted as indices, we cannot compute - the gradient of the layer output wrt. `X`. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, n_in, n_out)` or list of arrays - The gradient(s) of the loss wrt. the layer output(s) - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - for dy, x in zip(dLdy, self.X): - dw = self._bwd(dy, x) - - if retain_grads: - self.gradients["W"] += dw - - def _bwd(self, dLdy, X): - """Actual computation of gradient of the loss wrt. W""" - dW = np.zeros_like(self.parameters["W"]) - dLdy = dLdy.reshape(-1, self.n_out) - - if self.pool is None: - for ix, v_id in enumerate(X.flatten()): - dW[v_id] += dLdy[ix] - elif self.pool == "sum": - for ix, v_ids in enumerate(X): - dW[v_ids] += dLdy[ix] - elif self.pool == "mean": - for ix, v_ids in enumerate(X): - dW[v_ids] += dLdy[ix] / len(v_ids) - return dW - - -class Dense(LayerBase): - def __init__(self, n_out, activation=None, kernel_initializer="glorot_uniform", name=None): - r""" - A fully-connected (dense) layer. - - Notes - ----- - A fully connected layer computes the function - - .. math:: - - \mathbf{Y} = f( \mathbf{WX} + \mathbf{b} ) - - where `f` is the activation nonlinearity, **W** and **b** are - parameters of the layer, and **X** is the minibatch of input examples. - - Parameters - ---------- - n_out : int - The dimensionality of the layer output - act_fn : str, :doc:`Activation ` object, or None - The element-wise output nonlinearity used in computing `Y`. If None, - use the identity function :math:`f(X) = X`. Default is None. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.kernel_initializer = kernel_initializer - self.n_in = None - self.n_out = n_out - self.act_fn = ActivationInitializer(activation)() - self.parameters = {"W": None, "b": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - if not self.weights_set: - init_weights = WeightInitializer(str(self.act_fn), mode=self.kernel_initializer) - W = init_weights((self.n_in, self.n_out)) - b = np.zeros((1, self.n_out)) - else: - W, b = self.get_weights() - - self.parameters = {"W": W, "b": b} - self.derived_variables = {"Z": []} - self.gradients = {"W": np.zeros_like(W), "b": np.zeros_like(b)} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Dense", - "kernel_initializer": self.kernel_initializer, - "n_in": self.n_in, - "n_out": self.n_out, - "act_fn": str(self.act_fn), - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_out)` - Layer output for each of the `n_ex` examples. - """ - if not self.is_initialized: - self.n_in = X.shape[1] - self._init_params() - - Y, Z = self._fwd(X) - - if retain_derived: - self.X.append(X) - self.derived_variables["Z"].append(Z) - - return Y - - def _fwd(self, X): - """Actual computation of forward pass""" - W = self.parameters["W"] - b = self.parameters["b"] - - Z = X @ W + b - Y = self.act_fn(Z) - return Y, Z - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, n_out)` or list of arrays - The gradient(s) of the loss wrt. the layer output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape `(n_ex, n_in)` or list of arrays - The gradient of the loss wrt. the layer input(s) `X`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - for dy, x in zip(dLdy, X): - dx, dw, db = self._bwd(dy, x) - dX.append(dx) - - if retain_grads: - self.gradients["W"] += dw - self.gradients["b"] += db - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X): - """Actual computation of gradient of the loss wrt. X, W, and b""" - W = self.parameters["W"] - b = self.parameters["b"] - - Z = X @ W + b - dZ = dLdy * self.act_fn.grad(Z) - - dX = dZ @ W.T - dW = X.T @ dZ - dB = dZ.sum(axis=0) # don't keep dimensions - return dX, dW, dB - - def _bwd2(self, dLdy, X, dLdy_bwd): - """Compute second derivatives / deriv. of loss wrt. dX, dW, and db""" - W = self.parameters["W"] - b = self.parameters["b"] - - dZ = self.act_fn.grad(X @ W + b) - ddZ = self.act_fn.grad2(X @ W + b) - - ddX = dLdy @ W * dZ - ddW = dLdy.T @ (dLdy_bwd * dZ) - ddB = np.sum(dLdy @ W * dLdy_bwd * ddZ, axis=0, keepdims=True) - return ddX, ddW, ddB - - -class Softmax(LayerBase): - def __init__(self, dim=-1, name=None): - r""" - A softmax nonlinearity layer. - - Notes - ----- - This is implemented as a layer rather than an activation primarily - because it requires retaining the layer input in order to compute the - softmax gradients properly. In other words, in contrast to other - simple activations, the softmax function and its gradient are not - computed elementwise, and thus are more easily expressed as a layer. - - The softmax function computes: - - .. math:: - - y_i = \frac{e^{x_i}}{\sum_j e^{x_j}} - - where :math:`x_i` is the `i` th element of input example **x**. - - Parameters - ---------- - dim: int - The dimension in `X` along which the softmax will be computed. - Default is -1. - """ # noqa: E501 - super().__init__(name=name) - - self.dim = dim - self.n_in = None - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - self.gradients = {} - self.parameters = {} - self.derived_variables = {} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "SoftmaxLayer", - "n_in": self.n_in, - "n_out": self.n_in, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_out)` - Layer output for each of the `n_ex` examples. - """ - if not self.is_initialized: - self.n_in = X.shape[1] - self._init_params() - - Y = self._fwd(X) - - if retain_derived: - self.X.append(X) - - return Y - - def _fwd(self, X): - """Actual computation of softmax forward pass""" - # center data to avoid overflow - e_X = np.exp(X - np.max(X, axis=self.dim, keepdims=True)) - return e_X / e_X.sum(axis=self.dim, keepdims=True) - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to inputs. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, n_out)` or list of arrays - The gradient(s) of the loss wrt. the layer output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer input `X`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - for dy, x in zip(dLdy, X): - dx = self._bwd(dy, x) - dX.append(dx) - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X): - """ - Actual computation of the gradient of the loss wrt. the input X. - - The Jacobian, J, of the softmax for input x = [x1, ..., xn] is: - J[i, j] = - softmax(x_i) * (1 - softmax(x_j)) if i = j - -softmax(x_i) * softmax(x_j) if i != j - where - x_n is input example n (ie., the n'th row in X) - """ - dX = [] - for dy, x in zip(dLdy, X): - dxi = [] - for dyi, xi in zip(*np.atleast_2d(dy, x)): - yi = self._fwd(xi.reshape(1, -1)).reshape(-1, 1) - dyidxi = np.diagflat(yi) - yi @ yi.T # jacobian wrt. input sample xi - dxi.append(dyi @ dyidxi) - dX.append(dxi) - return np.array(dX).reshape(*X.shape) - - -class SparseEvolution(LayerBase): - def __init__( - self, - n_out, - zeta=0.3, - epsilon=20, - act_fn=None, - kernel_initializer="glorot_uniform", - name=None, - ): - r""" - A sparse Erdos-Renyi layer with evolutionary rewiring via the sparse - evolutionary training (SET) algorithm. - - Notes - ----- - .. math:: - - Y = f( (\mathbf{W} \odot \mathbf{W}_{mask}) \mathbf{X} + \mathbf{b} ) - - where :math:`\odot` is the elementwise multiplication operation, `f` is - the layer activation function, and :math:`\mathbf{W}_{mask}` is an - evolved binary mask. - - Parameters - ---------- - n_out : int - The dimensionality of the layer output - zeta : float - Proportion of the positive and negative weights closest to zero to - drop after each training update. Default is 0.3. - epsilon : float - Layer sparsity parameter. Default is 20. - act_fn : str, :doc:`Activation ` object, or None - The element-wise output nonlinearity used in computing `Y`. If None, - use the identity function :math:`f(X) = X`. Default is None. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.kernel_initializer = kernel_initializer - self.n_in = None - self.zeta = zeta - self.n_out = n_out - self.epsilon = epsilon - self.act_fn = ActivationInitializer(act_fn)() - self.parameters = {"W": None, "b": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - if not self.weights_set: - init_weights = WeightInitializer(str(self.act_fn), mode=self.kernel_initializer) - W = init_weights((self.n_in, self.n_out)) - b = np.zeros((1, self.n_out)) - # convert a fully connected base layer into a sparse layer - n_in, n_out = W.shape - p = (self.epsilon * (n_in + n_out)) / (n_in * n_out) - mask = np.random.binomial(1, p, shape=W.shape) - else: - W, b, mask = self.get_weights() - - self.derived_variables = {"Z": []} - self.parameters = {"W": W, "b": b, "W_mask": mask} - self.gradients = {"W": np.zeros_like(W), "b": np.zeros_like(b)} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "SparseEvolutionary", - "kernel_initializer": self.kernel_initializer, - "zeta": self.zeta, - "n_in": self.n_in, - "n_out": self.n_out, - "epsilon": self.epsilon, - "act_fn": str(self.act_fn), - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_out)` - Layer output for each of the `n_ex` examples. - """ - if not self.is_initialized: - self.n_in = X.shape[1] - self._init_params() - - Y, Z = self._fwd(X) - - if retain_derived: - self.X.append(X) - self.derived_variables["Z"].append(Z) - - return Y - - def _fwd(self, X): - """Actual computation of forward pass""" - W = self.parameters["W"] - b = self.parameters["b"] - W_mask = self.parameters["W_mask"] - - Z = X @ (W * W_mask) + b - Y = self.act_fn(Z) - return Y, Z - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from layer outputs to inputs - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, n_out)` or list of arrays - The gradient(s) of the loss wrt. the layer output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer input `X`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - for dy, x in zip(dLdy, X): - dx, dw, db = self._bwd(dy, x) - dX.append(dx) - - if retain_grads: - self.gradients["W"] += dw - self.gradients["b"] += db - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X): - """Actual computation of gradient of the loss wrt. X, W, and b""" - W = self.parameters["W"] - b = self.parameters["b"] - W_sparse = W * self.parameters["W_mask"] - - Z = X @ W_sparse + b - dZ = dLdy * self.act_fn.grad(Z) - - dX = dZ @ W_sparse.T - dW = X.T @ dZ - dB = dZ.sum(axis=0, keepdims=True) - return dX, dW, dB - - def _bwd2(self, dLdy, X, dLdy_bwd): - """Compute second derivatives / deriv. of loss wrt. dX, dW, and db""" - W = self.parameters["W"] - b = self.parameters["b"] - W_sparse = W * self.parameters["W_mask"] - - dZ = self.act_fn.grad(X @ W_sparse + b) - ddZ = self.act_fn.grad2(X @ W_sparse + b) - - ddX = dLdy @ W * dZ - ddW = dLdy.T @ (dLdy_bwd * dZ) - ddB = np.sum(dLdy @ W_sparse * dLdy_bwd * ddZ, axis=0, keepdims=True) - return ddX, ddW, ddB - - def update(self): - """ - Update parameters using current gradients and evolve network - connections via SET. - """ - assert self.trainable, "Layer is frozen" - for k, v in self.gradients.items(): - if k in self.parameters: - self.parameters[k] = self.optimizer(self.parameters[k], v, k) - self.flush_gradients() - self._evolve_connections() - - def _evolve_connections(self): - assert self.trainable, "Layer is frozen" - W = self.parameters["W"] - W_mask = self.parameters["W_mask"] - W_flat = (W * W_mask).reshape(-1) - - k = int(np.prod(W.shape) * self.zeta) - - (p_ix,) = np.where(W_flat > 0) - (n_ix,) = np.where(W_flat < 0) - - # remove the k largest negative and k smallest positive weights - k_smallest_p = p_ix[np.argsort(W_flat[p_ix])][:k] - k_largest_n = n_ix[np.argsort(W_flat[n_ix])][-k:] - n_rewired = len(k_smallest_p) + len(k_largest_n) - - self.mask = np.ones_like(W_flat) - self.mask[k_largest_n] = 0 - self.mask[k_smallest_p] = 0 - - zero_ixs = np.where(self.mask == 0) - - # resample new connections and update mask - np.shuffle(zero_ixs) - self.mask[zero_ixs[:n_rewired]] = 1 - self.mask = self.mask.reshape(*W.shape) - - -####################################################################### -# Convolutional Layers # -####################################################################### - - -class Conv1D(LayerBase): - def __init__( - self, - out_ch, - kernel_width, - pad=0, - stride=1, - dilation=0, - act_fn=None, - kernel_initializer="glorot_uniform", - name=None, - ): - """ - Apply a one-dimensional convolution kernel over an input volume. - - Notes - ----- - Equations:: - - out = act_fn(pad(X) * W + b) - out_dim = floor(1 + (n_rows_in + pad_left + pad_right - kernel_width) / stride) - - where '`*`' denotes the cross-correlation operation with stride `s` and dilation `d`. - - Parameters - ---------- - out_ch : int - The number of filters/kernels to compute in the current layer - kernel_width : int - The width of a single 1D filter/kernel in the current layer - act_fn : str, :doc:`Activation ` object, or None - The activation function for computing ``Y[t]``. If None, use the - identity function :math:`f(x) = x` by default. Default is None. - pad : int, tuple, or {'same', 'causal'} - The number of rows/columns to zero-pad the input with. If `'same'`, - calculate padding to ensure the output length matches in the input - length. If `'causal'` compute padding such that the output both has - the same length as the input AND ``output[t]`` does not depend on - ``input[t + 1:]``. Default is 0. - stride : int - The stride/hop of the convolution kernels as they move over the - input volume. Default is 1. - dilation : int - Number of pixels inserted between kernel elements. Effective kernel - shape after dilation is: ``[kernel_rows * (d + 1) - d, kernel_cols - * (d + 1) - d]``. Default is 0. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.pad = pad - self.kernel_initializer = kernel_initializer - self.in_ch = None - self.out_ch = out_ch - self.stride = stride - self.dilation = dilation - self.kernel_width = kernel_width - self.act_fn = ActivationInitializer(act_fn)() - self.parameters = {"W": None, "b": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - if not self.weights_set: - init_weights = WeightInitializer(str(self.act_fn), mode=self.kernel_initializer) - W = init_weights((self.kernel_width, self.in_ch, self.out_ch)) - b = np.zeros((1, 1, self.out_ch)) - else: - W, b = self.get_weights() - - self.parameters = {"W": W, "b": b} - self.gradients = {"W": np.zeros_like(W), "b": np.zeros_like(b)} - self.derived_variables = {"Z": [], "out_rows": [], "out_cols": []} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Conv1D", - "pad": self.pad, - "kernel_initializer": self.kernel_initializer, - "in_ch": self.in_ch, - "out_ch": self.out_ch, - "stride": self.stride, - "dilation": self.dilation, - "act_fn": str(self.act_fn), - "kernel_width": self.kernel_width, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output given input volume `X`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, l_in, in_ch)` - The input volume consisting of `n_ex` examples, each of length - `l_in` and with `in_ch` input channels - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, l_out, out_ch)` - The layer output. - """ - if not self.is_initialized: - self.in_ch = X.shape[2] - self._init_params() - - W = self.parameters["W"] - b = self.parameters["b"] - - n_ex, l_in, in_ch = X.shape - s, p, d = self.stride, self.pad, self.dilation - - # pad the input and perform the forward convolution - Z = conv1D(X, W, s, p, d) + b - Y = self.act_fn(Z) - - if retain_derived: - self.X.append(X) - self.derived_variables["Z"].append(Z) - self.derived_variables["out_rows"].append(Z.shape[1]) - self.derived_variables["out_cols"].append(Z.shape[2]) - - return Y - - def backward(self, dLdy, retain_grads=True): - """ - Compute the gradient of the loss with respect to the layer parameters. - - Notes - ----- - Relies on :meth:`~numpy_ml.neural_nets.utils.im2col` and - :meth:`~numpy_ml.neural_nets.utils.col2im` to vectorize the - gradient calculation. See the private method :meth:`_backward_naive` - for a more straightforward implementation. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, l_out, out_ch)` or list of arrays - The gradient(s) of the loss with respect to the layer output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, l_in, in_ch)` - The gradient of the loss with respect to the layer input volume. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - X = self.X - Z = self.derived_variables["Z"] - - dX = [] - for dy, x, z in zip(dLdy, X, Z): - dx, dw, db = self._bwd(dy, x, z) - dX.append(dx) - - if retain_grads: - self.gradients["W"] += dw - self.gradients["b"] += db - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X, Z): - """Actual computation of gradient of the loss wrt. X, W, and b""" - W = self.parameters["W"] - - # add a row dimension to X, W, and dZ to permit us to use im2col/col2im - X2D = np.expand_dims(X, axis=1) - W2D = np.expand_dims(W, axis=0) - dLdZ = np.expand_dims(dLdy * self.act_fn.grad(Z), axis=1) - - d = self.dilation - fr, fc, in_ch, out_ch = W2D.shape - n_ex, l_out, out_ch = dLdy.shape - fr, fc, s = 1, self.kernel_width, self.stride - - # use pad1D here in order to correctly handle self.pad = 'causal', - # which isn't defined for pad2D - _, p = pad1D(X, self.pad, self.kernel_width, s, d) - p2D = (0, 0, p[0], p[1]) - - # columnize W, X, and dLdy - dLdZ_col = dLdZ.transpose(3, 1, 2, 0).reshape(out_ch, -1) - W_col = W2D.transpose(3, 2, 0, 1).reshape(out_ch, -1).T - X_col, _ = im2col(X2D, W2D.shape, p2D, s, d) - - # compute gradients via matrix multiplication and reshape - dB = dLdZ_col.sum(axis=1).reshape(1, 1, -1) - dW = (dLdZ_col @ X_col.T).reshape(out_ch, in_ch, fr, fc).transpose(2, 3, 1, 0) - - # reshape columnized dX back into the same format as the input volume - dX_col = W_col @ dLdZ_col - dX = col2im(dX_col, X2D.shape, W2D.shape, p2D, s, d).transpose(0, 2, 3, 1) - - return np.squeeze(dX, axis=1), np.squeeze(dW, axis=0), dB - - def _backward_naive(self, dLdy, retain_grads=True): - """ - A slower (ie., non-vectorized) but more straightforward implementation - of the gradient computations for a 2D conv layer. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, l_out, out_ch)` or list of arrays - The gradient(s) of the loss with respect to the layer output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, l_in, in_ch)` - The gradient of the loss with respect to the layer input volume. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - W = self.parameters["W"] - b = self.parameters["b"] - Zs = self.derived_variables["Z"] - - Xs, d = self.X, self.dilation - fw, s, p = self.kernel_width, self.stride, self.pad - - dXs = [] - for X, Z, dy in zip(Xs, Zs, dLdy): - n_ex, l_out, out_ch = dy.shape - X_pad, (pr1, pr2) = pad1D(X, p, self.kernel_width, s, d) - - dX = np.zeros_like(X_pad) - dZ = dy * self.act_fn.grad(Z) - - dW, dB = np.zeros_like(W), np.zeros_like(b) - for m in range(n_ex): - for i in range(l_out): - for c in range(out_ch): - # compute window boundaries w. stride and dilation - i0, i1 = i * s, (i * s) + fw * (d + 1) - d - - wc = W[:, :, c] - kernel = dZ[m, i, c] - window = X_pad[m, i0 : i1 : (d + 1), :] - - dB[:, :, c] += kernel - dW[:, :, c] += window * kernel - dX[m, i0 : i1 : (d + 1), :] += wc * kernel - - if retain_grads: - self.gradients["W"] += dW - self.gradients["b"] += dB - - pr2 = None if pr2 == 0 else -pr2 - dXs.append(dX[:, pr1:pr2, :]) - return dXs[0] if len(Xs) == 1 else dXs - - -class Conv2D(LayerBase): - def __init__( - self, - out_ch, - kernel_shape, - pad=0, - stride=1, - dilation=0, - act_fn=None, - kernel_initializer="glorot_uniform", - name=None, - ): - """ - Apply a two-dimensional convolution kernel over an input volume. - - Notes - ----- - Equations:: - - out = act_fn(pad(X) * W + b) - n_rows_out = floor(1 + (n_rows_in + pad_left + pad_right - filter_rows) / stride) - n_cols_out = floor(1 + (n_cols_in + pad_top + pad_bottom - filter_cols) / stride) - - where `'*'` denotes the cross-correlation operation with stride `s` and - dilation `d`. - - Parameters - ---------- - out_ch : int - The number of filters/kernels to compute in the current layer - kernel_shape : 2-tuple - The dimension of a single 2D filter/kernel in the current layer - act_fn : str, :doc:`Activation ` object, or None - The activation function for computing ``Y[t]``. If None, use the - identity function :math:`f(X) = X` by default. Default is None. - pad : int, tuple, or 'same' - The number of rows/columns to zero-pad the input with. Default is - 0. - stride : int - The stride/hop of the convolution kernels as they move over the - input volume. Default is 1. - dilation : int - Number of pixels inserted between kernel elements. Effective kernel - shape after dilation is: ``[kernel_rows * (d + 1) - d, kernel_cols - * (d + 1) - d]``. Default is 0. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.pad = pad - self.kernel_initializer = kernel_initializer - self.in_ch = None - self.out_ch = out_ch - self.stride = stride - self.dilation = dilation - self.kernel_shape = kernel_shape - self.act_fn = ActivationInitializer(act_fn)() - self.parameters = {"W": None, "b": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - fr, fc = self.kernel_shape - if not self.weights_set: - init_weights = WeightInitializer(str(self.act_fn), mode=self.kernel_initializer) - W = init_weights((fr, fc, self.in_ch, self.out_ch)) - b = np.zeros((1, 1, 1, self.out_ch)) - else: - W, b = self.get_weights() - - self.parameters = {"W": W, "b": b} - self.gradients = {"W": np.zeros_like(W), "b": np.zeros_like(b)} - self.derived_variables = {"Z": [], "out_rows": [], "out_cols": []} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Conv2D", - "pad": self.pad, - "kernel_initializer": self.kernel_initializer, - "in_ch": self.in_ch, - "out_ch": self.out_ch, - "stride": self.stride, - "dilation": self.dilation, - "act_fn": str(self.act_fn), - "kernel_shape": self.kernel_shape, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output given input volume `X`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The input volume consisting of `n_ex` examples, each with dimension - (`in_rows`, `in_cols`, `in_ch`). - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The layer output. - """ # noqa: E501 - if not self.is_initialized: - self.in_ch = X.shape[3] - self._init_params() - - W = self.parameters["W"] - b = self.parameters["b"] - - n_ex, in_rows, in_cols, in_ch = X.shape - s, p, d = self.stride, self.pad, self.dilation - - # pad the input and perform the forward convolution - Z = conv2D(X, W, s, p, d) + b - Y = self.act_fn(Z) - - if retain_derived: - self.X.append(X) - self.derived_variables["Z"].append(Z) - self.derived_variables["out_rows"].append(Z.shape[1]) - self.derived_variables["out_cols"].append(Z.shape[2]) - - return Y - - def backward(self, dLdy, retain_grads=True): - """ - Compute the gradient of the loss with respect to the layer parameters. - - Notes - ----- - Relies on :meth:`~numpy_ml.neural_nets.utils.im2col` and - :meth:`~numpy_ml.neural_nets.utils.col2im` to vectorize the - gradient calculation. - - See the private method :meth:`_backward_naive` for a more straightforward - implementation. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, out_rows, - out_cols, out_ch)` or list of arrays - The gradient(s) of the loss with respect to the layer output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss with respect to the layer input volume. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - dX = [] - X = self.X - Z = self.derived_variables["Z"] - - for dy, x, z in zip(dLdy, X, Z): - dx, dw, db = self._bwd(dy, x, z) - dX.append(dx) - - if retain_grads: - self.gradients["W"] += dw - self.gradients["b"] += db - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdy, X, Z): - """Actual computation of gradient of the loss wrt. X, W, and b""" - W = self.parameters["W"] - - d = self.dilation - fr, fc, in_ch, out_ch = W.shape - n_ex, out_rows, out_cols, out_ch = dLdy.shape - (fr, fc), s, p = self.kernel_shape, self.stride, self.pad - - # columnize W, X, and dLdy - dLdZ = dLdy * self.act_fn.grad(Z) - dLdZ_col = dLdZ.transpose(3, 1, 2, 0).reshape(out_ch, -1) - W_col = W.transpose(3, 2, 0, 1).reshape(out_ch, -1).T - X_col, p = im2col(X, W.shape, p, s, d) - - # compute gradients via matrix multiplication and reshape - dB = dLdZ_col.sum(axis=1).reshape(1, 1, 1, -1) - dW = (dLdZ_col @ X_col.T).reshape(out_ch, in_ch, fr, fc).transpose(2, 3, 1, 0) - - # reshape columnized dX back into the same format as the input volume - dX_col = W_col @ dLdZ_col - dX = col2im(dX_col, X.shape, W.shape, p, s, d).transpose(0, 2, 3, 1) - - return dX, dW, dB - - def _backward_naive(self, dLdy, retain_grads=True): - """ - A slower (ie., non-vectorized) but more straightforward implementation - of the gradient computations for a 2D conv layer. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The gradient of the loss with respect to the layer output. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss with respect to the layer input volume. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdy, list): - dLdy = [dLdy] - - W = self.parameters["W"] - b = self.parameters["b"] - Zs = self.derived_variables["Z"] - - Xs, d = self.X, self.dilation - (fr, fc), s, p = self.kernel_shape, self.stride, self.pad - - dXs = [] - for X, Z, dy in zip(Xs, Zs, dLdy): - n_ex, out_rows, out_cols, out_ch = dy.shape - X_pad, (pr1, pr2, pc1, pc2) = pad2D(X, p, self.kernel_shape, s, d) - - dZ = dLdy * self.act_fn.grad(Z) - - dX = np.zeros_like(X_pad) - dW, dB = np.zeros_like(W), np.zeros_like(b) - for m in range(n_ex): - for i in range(out_rows): - for j in range(out_cols): - for c in range(out_ch): - # compute window boundaries w. stride and dilation - i0, i1 = i * s, (i * s) + fr * (d + 1) - d - j0, j1 = j * s, (j * s) + fc * (d + 1) - d - - wc = W[:, :, :, c] - kernel = dZ[m, i, j, c] - window = X_pad[m, i0 : i1 : (d + 1), j0 : j1 : (d + 1), :] - - dB[:, :, :, c] += kernel - dW[:, :, :, c] += window * kernel - dX[m, i0 : i1 : (d + 1), j0 : j1 : (d + 1), :] += ( - wc * kernel - ) - - if retain_grads: - self.gradients["W"] += dW - self.gradients["b"] += dB - - pr2 = None if pr2 == 0 else -pr2 - pc2 = None if pc2 == 0 else -pc2 - dXs.append(dX[:, pr1:pr2, pc1:pc2, :]) - return dXs[0] if len(Xs) == 1 else dXs - - -class Pool2D(LayerBase): - def __init__(self, kernel_shape, stride=1, pad=0, mode="max", name=None): - """ - A single two-dimensional pooling layer. - - Parameters - ---------- - kernel_shape : 2-tuple - The dimension of a single 2D filter/kernel in the current layer - stride : int - The stride/hop of the convolution kernels as they move over the - input volume. Default is 1. - pad : int, tuple, or 'same' - The number of rows/columns of 0's to pad the input. Default is 0. - mode : {"max", "average"} - The pooling function to apply. - """ # noqa: E501 - super().__init__(name=name) - - self.pad = pad - self.mode = mode - self.in_ch = None - self.out_ch = None - self.stride = stride - self.kernel_shape = kernel_shape - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - self.derived_variables = {"out_rows": [], "out_cols": []} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Pool2D", - "act_fn": None, - "pad": self.pad, - "mode": self.mode, - "in_ch": self.in_ch, - "out_ch": self.out_ch, - "stride": self.stride, - "kernel_shape": self.kernel_shape, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output given input volume `X`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The input volume consisting of `n_ex` examples, each with dimension - (`in_rows`,`in_cols`, `in_ch`) - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The layer output. - """ # noqa: E501 - if not self.is_initialized: - self.in_ch = self.out_ch = X.shape[3] - self._init_params() - - n_ex, in_rows, in_cols, nc_in = X.shape - (fr, fc), s, p = self.kernel_shape, self.stride, self.pad - X_pad, (pr1, pr2, pc1, pc2) = pad2D(X, p, self.kernel_shape, s) - - out_rows = np.floor(1 + (in_rows + pr1 + pr2 - fr) / s).astype(int) - out_cols = np.floor(1 + (in_cols + pc1 + pc2 - fc) / s).astype(int) - - if self.mode == "max": - pool_fn = np.max - elif self.mode == "average": - pool_fn = np.mean - - Y = np.zeros((n_ex, out_rows, out_cols, self.out_ch)) - for m in range(n_ex): - for i in range(out_rows): - for j in range(out_cols): - for c in range(self.out_ch): - # calculate window boundaries, incorporating stride - i0, i1 = i * s, (i * s) + fr - j0, j1 = j * s, (j * s) + fc - - xi = X_pad[m, i0:i1, j0:j1, c] - Y[m, i, j, c] = pool_fn(xi) - - if retain_derived: - self.X.append(X) - self.derived_variables["out_rows"].append(out_rows) - self.derived_variables["out_cols"].append(out_cols) - - return Y - - def backward(self, dLdY, retain_grads=True): - """ - Backprop from layer outputs to inputs - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss wrt. the layer output `Y`. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss wrt. the layer input `X`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdY, list): - dLdY = [dLdY] - - Xs = self.X - out_rows = self.derived_variables["out_rows"] - out_cols = self.derived_variables["out_cols"] - - (fr, fc), s, p = self.kernel_shape, self.stride, self.pad - - dXs = [] - for X, dy, out_row, out_col in zip(Xs, dLdY, out_rows, out_cols): - n_ex, in_rows, in_cols, nc_in = X.shape - X_pad, (pr1, pr2, pc1, pc2) = pad2D(X, p, self.kernel_shape, s) - - dX = np.zeros_like(X_pad) - for m in range(n_ex): - for i in range(out_row): - for j in range(out_col): - for c in range(self.out_ch): - # calculate window boundaries, incorporating stride - i0, i1 = i * s, (i * s) + fr - j0, j1 = j * s, (j * s) + fc - - if self.mode == "max": - xi = X[m, i0:i1, j0:j1, c] - - # enforce that the mask can only consist of a - # single `True` entry, even if multiple entries in - # xi are equal to max(xi) - mask = np.zeros_like(xi).astype(bool) - x, y = np.argwhere(xi == np.max(xi))[0] - mask[x, y] = True - - dX[m, i0:i1, j0:j1, c] += mask * dy[m, i, j, c] - elif self.mode == "average": - frame = np.ones((fr, fc)) * dy[m, i, j, c] - dX[m, i0:i1, j0:j1, c] += frame / np.prod((fr, fc)) - - pr2 = None if pr2 == 0 else -pr2 - pc2 = None if pc2 == 0 else -pc2 - dXs.append(dX[:, pr1:pr2, pc1:pc2, :]) - return dXs[0] if len(Xs) == 1 else dXs - - -class Deconv2D(LayerBase): - def __init__( - self, - out_ch, - kernel_shape, - pad=0, - stride=1, - act_fn=None, - kernel_initializer="glorot_uniform", - name=None, - ): - """ - Apply a two-dimensional "deconvolution" to an input volume. - - Notes - ----- - The term "deconvolution" in this context does not correspond with the - deconvolution operation in mathematics. More accurately, this layer is - computing a transposed convolution / fractionally-strided convolution. - - Parameters - ---------- - out_ch : int - The number of filters/kernels to compute in the current layer - kernel_shape : 2-tuple - The dimension of a single 2D filter/kernel in the current layer - act_fn : str, :doc:`Activation ` object, or None - The activation function for computing ``Y[t]``. If None, use - :class:`~numpy_ml.neural_nets.activations.Affine` - activations by default. Default is None. - pad : int, tuple, or 'same' - The number of rows/columns to zero-pad the input with. Default is 0. - stride : int - The stride/hop of the convolution kernels as they move over the - input volume. Default is 1. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.pad = pad - self.kernel_initializer = kernel_initializer - self.in_ch = None - self.stride = stride - self.out_ch = out_ch - self.kernel_shape = kernel_shape - self.act_fn = ActivationInitializer(act_fn)() - self.parameters = {"W": None, "b": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - fr, fc = self.kernel_shape - if not self.weights_set: - init_weights = WeightInitializer(str(self.act_fn), mode=self.kernel_initializer) - W = init_weights((fr, fc, self.in_ch, self.out_ch)) - b = np.zeros((1, 1, 1, self.out_ch)) - else: - W, b = self.get_weights() - - self.parameters = {"W": W, "b": b} - self.gradients = {"W": np.zeros_like(W), "b": np.zeros_like(b)} - self.derived_variables = {"Z": [], "out_rows": [], "out_cols": []} - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "Deconv2D", - "pad": self.pad, - "kernel_initializer": self.kernel_initializer, - "in_ch": self.in_ch, - "out_ch": self.out_ch, - "stride": self.stride, - "act_fn": str(self.act_fn), - "kernel_shape": self.kernel_shape, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output given input volume `X`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The input volume consisting of `n_ex` examples, each with dimension - (`in_rows`, `in_cols`, `in_ch`). - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The layer output. - """ # noqa: E501 - if not self.is_initialized: - self.in_ch = X.shape[3] - self._init_params() - - W = self.parameters["W"] - b = self.parameters["b"] - - s, p = self.stride, self.pad - n_ex, in_rows, in_cols, in_ch = X.shape - - # pad the input and perform the forward deconvolution - Z = deconv2D_naive(X, W, s, p, 0) + b - Y = self.act_fn(Z) - - if retain_derived: - self.X.append(X) - self.derived_variables["Z"].append(Z) - self.derived_variables["out_rows"].append(Z.shape[1]) - self.derived_variables["out_cols"].append(Z.shape[2]) - - return Y - - def backward(self, dLdY, retain_grads=True): - """ - Compute the gradient of the loss with respect to the layer parameters. - - Notes - ----- - Relies on :meth:`~numpy_ml.neural_nets.utils.im2col` and - :meth:`~numpy_ml.neural_nets.utils.col2im` to vectorize the - gradient calculations. - - Parameters - ---------- - dLdY : :py:class:`ndarray ` of shape (`n_ex, out_rows, out_cols, out_ch`) - The gradient of the loss with respect to the layer output. - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape (`n_ex, in_rows, in_cols, in_ch`) - The gradient of the loss with respect to the layer input volume. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - if not isinstance(dLdY, list): - dLdY = [dLdY] - - dX = [] - X, Z = self.X, self.derived_variables["Z"] - - for dy, x, z in zip(dLdY, X, Z): - dx, dw, db = self._bwd(dy, x, z) - dX.append(dx) - - if retain_grads: - self.gradients["W"] += dw - self.gradients["b"] += db - - return dX[0] if len(X) == 1 else dX - - def _bwd(self, dLdY, X, Z): - """Actual computation of gradient of the loss wrt. X, W, and b""" - W = np.rot90(self.parameters["W"], 2) - - s = self.stride - if self.stride > 1: - X = dilate(X, s - 1) - s = 1 - - fr, fc, in_ch, out_ch = W.shape - (fr, fc), p = self.kernel_shape, self.pad - n_ex, out_rows, out_cols, out_ch = dLdY.shape - - # pad X the first time - X_pad, p = pad2D(X, p, W.shape[:2], s) - n_ex, in_rows, in_cols, in_ch = X_pad.shape - pr1, pr2, pc1, pc2 = p - - # compute additional padding to produce the deconvolution - out_rows = s * (in_rows - 1) - pr1 - pr2 + fr - out_cols = s * (in_cols - 1) - pc1 - pc2 + fc - out_dim = (out_rows, out_cols) - - # add additional "deconvolution" padding - _p = calc_pad_dims_2D(X_pad.shape, out_dim, W.shape[:2], s, 0) - X_pad, _ = pad2D(X_pad, _p, W.shape[:2], s) - - # columnize W, X, and dLdY - dLdZ = dLdY * self.act_fn.grad(Z) - dLdZ, _ = pad2D(dLdZ, p, W.shape[:2], s) - - dLdZ_col = dLdZ.transpose(3, 1, 2, 0).reshape(out_ch, -1) - W_col = W.transpose(3, 2, 0, 1).reshape(out_ch, -1) - X_col, _ = im2col(X_pad, W.shape, 0, s, 0) - - # compute gradients via matrix multiplication and reshape - dB = dLdZ_col.sum(axis=1).reshape(1, 1, 1, -1) - dW = (dLdZ_col @ X_col.T).reshape(out_ch, in_ch, fr, fc).transpose(2, 3, 1, 0) - dW = np.rot90(dW, 2) - - # reshape columnized dX back into the same format as the input volume - dX_col = W_col.T @ dLdZ_col - - total_pad = tuple(i + j for i, j in zip(p, _p)) - dX = col2im(dX_col, X.shape, W.shape, total_pad, s, 0).transpose(0, 2, 3, 1) - dX = dX[:, :: self.stride, :: self.stride, :] - - return dX, dW, dB - - -####################################################################### -# Recurrent Layers # -####################################################################### - - -class RNNCell(LayerBase): - def __init__(self, n_out, act_fn="Tanh", kernel_initializer="glorot_uniform", name=None): - r""" - A single step of a vanilla (Elman) RNN. - - Notes - ----- - At timestep `t`, the vanilla RNN cell computes - - .. math:: - - \mathbf{Z}^{(t)} &= - \mathbf{W}_{ax} \mathbf{X}^{(t)} + \mathbf{b}_{ax} + - \mathbf{W}_{aa} \mathbf{A}^{(t-1)} + \mathbf{b}_{aa} \\ - \mathbf{A}^{(t)} &= f(\mathbf{Z}^{(t)}) - - where - - - :math:`\mathbf{X}^{(t)}` is the input at time `t` - - :math:`\mathbf{A}^{(t)}` is the hidden state at timestep `t` - - `f` is the layer activation function - - :math:`\mathbf{W}_{ax}` and :math:`\mathbf{b}_{ax}` are the weights - and bias for the input to hidden layer - - :math:`\mathbf{W}_{aa}` and :math:`\mathbf{b}_{aa}` are the weights - and biases for the hidden to hidden layer - - Parameters - ---------- - n_out : int - The dimension of a single hidden state / output on a given timestep - act_fn : str, :doc:`Activation ` object, or None - The activation function for computing ``A[t]``. Default is `'Tanh'`. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.kernel_initializer = kernel_initializer - self.n_in = None - self.n_out = n_out - self.n_timesteps = None - self.act_fn = ActivationInitializer(act_fn)() - self.parameters = {"Waa": None, "Wax": None, "ba": None, "bx": None} - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - self.X = [] - if not self.weights_set: - init_weights = WeightInitializer(str(self.act_fn), mode=self.kernel_initializer) - Wax = init_weights((self.n_in, self.n_out)) - Waa = init_weights((self.n_out, self.n_out)) - ba = np.zeros((self.n_out, 1)) - bx = np.zeros((self.n_out, 1)) - else: - Waa, ba, Wax, bx = self.get_weights() - - self.parameters = {"Waa": Waa, "ba": ba, "Wax": Wax, "bx": bx} - - self.gradients = { - "Waa": np.zeros_like(Waa), - "Wax": np.zeros_like(Wax), - "ba": np.zeros_like(ba), - "bx": np.zeros_like(bx), - } - - self.derived_variables = { - "A": [], - "Z": [], - "n_timesteps": 0, - "current_step": 0, - "dLdA_accumulator": None, - } - - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "RNNCell", - "kernel_initializer": self.kernel_initializer, - "n_in": self.n_in, - "n_out": self.n_out, - "act_fn": str(self.act_fn), - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, Xt): - """ - Compute the network output for a single timestep. - - Parameters - ---------- - Xt : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Input at timestep `t` consisting of `n_ex` examples each of - dimensionality `n_in`. - - Returns - ------- - At: :py:class:`ndarray ` of shape `(n_ex, n_out)` - The value of the hidden state at timestep `t` for each of the - `n_ex` examples. - """ - if not self.is_initialized: - self.n_in = Xt.shape[1] - self._init_params() - - # increment timestep - self.derived_variables["n_timesteps"] += 1 - self.derived_variables["current_step"] += 1 - - # Retrieve parameters - ba = self.parameters["ba"] - bx = self.parameters["bx"] - Wax = self.parameters["Wax"] - Waa = self.parameters["Waa"] - - # initialize the hidden state to zero - As = self.derived_variables["A"] - if len(As) == 0: - n_ex, n_in = Xt.shape - A0 = np.zeros((n_ex, self.n_out)) - As.append(A0) - - # compute next hidden state - Zt = As[-1] @ Waa + ba.T + Xt @ Wax + bx.T - At = self.act_fn(Zt) - - self.derived_variables["Z"].append(Zt) - self.derived_variables["A"].append(At) - - # store intermediate variables - self.X.append(Xt) - return At - - def backward(self, dLdAt): - """ - Backprop for a single timestep. - - Parameters - ---------- - dLdAt : :py:class:`ndarray ` of shape `(n_ex, n_out)` - The gradient of the loss wrt. the layer outputs (ie., hidden - states) at timestep `t`. - - Returns - ------- - dLdXt : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer inputs at timestep `t`. - """ - assert self.trainable, "Layer is frozen" - - # decrement current step - self.derived_variables["current_step"] -= 1 - - # extract context variables - Zs = self.derived_variables["Z"] - As = self.derived_variables["A"] - t = self.derived_variables["current_step"] - dA_acc = self.derived_variables["dLdA_accumulator"] - - # initialize accumulator - if dA_acc is None: - dA_acc = np.zeros_like(As[0]) - - # get network weights for gradient calcs - Wax = self.parameters["Wax"] - Waa = self.parameters["Waa"] - - # compute gradient components at timestep t - dA = dLdAt + dA_acc - dZ = self.act_fn.grad(Zs[t]) * dA - dXt = dZ @ Wax.T - - # update parameter gradients with signal from current step - self.gradients["Waa"] += As[t].T @ dZ - self.gradients["Wax"] += self.X[t].T @ dZ - self.gradients["ba"] += dZ.sum(axis=0, keepdims=True).T - self.gradients["bx"] += dZ.sum(axis=0, keepdims=True).T - - # update accumulator variable for hidden state - self.derived_variables["dLdA_accumulator"] = dZ @ Waa.T - return dXt - - def flush_gradients(self): - """Erase all the layer's derived variables and gradients.""" - assert self.trainable, "Layer is frozen" - - self.X = [] - for k, v in self.derived_variables.items(): - self.derived_variables[k] = [] - - self.derived_variables["n_timesteps"] = 0 - self.derived_variables["current_step"] = 0 - - # reset parameter gradients to 0 - for k, v in self.parameters.items(): - self.gradients[k] = np.zeros_like(v) - - -class LSTMCell(LayerBase): - def __init__( - self, - n_out, - act_fn="Tanh", - gate_fn="Sigmoid", - kernel_initializer="glorot_uniform", - name=None, - ): - """ - A single step of a long short-term memory (LSTM) RNN. - - Notes - ----- - Notation: - - - ``Z[t]`` is the input to each of the gates at timestep `t` - - ``A[t]`` is the value of the hidden state at timestep `t` - - ``Cc[t]`` is the value of the *candidate* cell/memory state at timestep `t` - - ``C[t]`` is the value of the *final* cell/memory state at timestep `t` - - ``Gf[t]`` is the output of the forget gate at timestep `t` - - ``Gu[t]`` is the output of the update gate at timestep `t` - - ``Go[t]`` is the output of the output gate at timestep `t` - - Equations:: - - Z[t] = stack([A[t-1], X[t]]) - Gf[t] = gate_fn(Wf @ Z[t] + bf) - Gu[t] = gate_fn(Wu @ Z[t] + bu) - Go[t] = gate_fn(Wo @ Z[t] + bo) - Cc[t] = act_fn(Wc @ Z[t] + bc) - C[t] = Gf[t] * C[t-1] + Gu[t] * Cc[t] - A[t] = Go[t] * act_fn(C[t]) - - where `@` indicates dot/matrix product, and '*' indicates elementwise - multiplication. - - Parameters - ---------- - n_out : int - The dimension of a single hidden state / output on a given timestep. - act_fn : str, :doc:`Activation ` object, or None - The activation function for computing ``A[t]``. Default is - `'Tanh'`. - gate_fn : str, :doc:`Activation ` object, or None - The gate function for computing the update, forget, and output - gates. Default is `'Sigmoid'`. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.kernel_initializer = kernel_initializer - self.n_in = None - self.n_out = n_out - self.n_timesteps = None - self.act_fn = ActivationInitializer(act_fn)() - self.gate_fn = ActivationInitializer(gate_fn)() - self.parameters = { - "Wf": None, - "Wu": None, - "Wc": None, - "Wo": None, - "bf": None, - "bu": None, - "bc": None, - "bo": None, - } - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - self.X = [] - if not self.weights_set: - init_weights_gate = WeightInitializer(str(self.gate_fn), mode=self.kernel_initializer) - init_weights_act = WeightInitializer(str(self.act_fn), mode=self.kernel_initializer) - - Wf = init_weights_gate((self.n_in + self.n_out, self.n_out)) - Wu = init_weights_gate((self.n_in + self.n_out, self.n_out)) - Wc = init_weights_act((self.n_in + self.n_out, self.n_out)) - Wo = init_weights_gate((self.n_in + self.n_out, self.n_out)) - - bf = np.zeros((1, self.n_out)) - bu = np.zeros((1, self.n_out)) - bc = np.zeros((1, self.n_out)) - bo = np.zeros((1, self.n_out)) - else: - Wf, bf, Wu, bu, Wc, bc, Wo, bo = self.get_weights() - - self.parameters = { - "Wf": Wf, - "bf": bf, - "Wu": Wu, - "bu": bu, - "Wc": Wc, - "bc": bc, - "Wo": Wo, - "bo": bo, - } - - self.gradients = { - "Wf": np.zeros_like(Wf), - "Wu": np.zeros_like(Wu), - "Wc": np.zeros_like(Wc), - "Wo": np.zeros_like(Wo), - "bf": np.zeros_like(bf), - "bu": np.zeros_like(bu), - "bc": np.zeros_like(bc), - "bo": np.zeros_like(bo), - } - - self.derived_variables = { - "C": [], - "A": [], - "Gf": [], - "Gu": [], - "Go": [], - "Gc": [], - "Cc": [], - "n_timesteps": 0, - "current_step": 0, - "dLdA_accumulator": None, - "dLdC_accumulator": None, - } - - self.is_initialized = True - self.weights_set = True - - def _get_params(self): - Wf = self.parameters["Wf"] - Wu = self.parameters["Wu"] - Wc = self.parameters["Wc"] - Wo = self.parameters["Wo"] - bf = self.parameters["bf"] - bu = self.parameters["bu"] - bc = self.parameters["bc"] - bo = self.parameters["bo"] - return Wf, Wu, Wc, Wo, bf, bu, bc, bo - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "LSTMCell", - "kernel_initializer": self.kernel_initializer, - "n_in": self.n_in, - "n_out": self.n_out, - "act_fn": str(self.act_fn), - "gate_fn": str(self.gate_fn), - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def forward(self, Xt): - """ - Compute the layer output for a single timestep. - - Parameters - ---------- - Xt : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Input at timestep t consisting of `n_ex` examples each of - dimensionality `n_in`. - - Returns - ------- - At: :py:class:`ndarray ` of shape `(n_ex, n_out)` - The value of the hidden state at timestep `t` for each of the `n_ex` - examples. - Ct: :py:class:`ndarray ` of shape `(n_ex, n_out)` - The value of the cell/memory state at timestep `t` for each of the - `n_ex` examples. - """ - if not self.is_initialized: - self.n_in = Xt.shape[1] - self._init_params() - - Wf, Wu, Wc, Wo, bf, bu, bc, bo = self._get_params() - - self.derived_variables["n_timesteps"] += 1 - self.derived_variables["current_step"] += 1 - - if len(self.derived_variables["A"]) == 0: - n_ex, n_in = Xt.shape - init = np.zeros((n_ex, self.n_out)) - self.derived_variables["A"].append(init) - self.derived_variables["C"].append(init) - - A_prev = self.derived_variables["A"][-1] - C_prev = self.derived_variables["C"][-1] - - # concatenate A_prev and Xt to create Zt - Zt = np.hstack([A_prev, Xt]) - - Gft = self.gate_fn(Zt @ Wf + bf) - Gut = self.gate_fn(Zt @ Wu + bu) - Got = self.gate_fn(Zt @ Wo + bo) - Cct = self.act_fn(Zt @ Wc + bc) - Ct = Gft * C_prev + Gut * Cct - At = Got * self.act_fn(Ct) - - # bookkeeping - self.X.append(Xt) - self.derived_variables["A"].append(At) - self.derived_variables["C"].append(Ct) - self.derived_variables["Gf"].append(Gft) - self.derived_variables["Gu"].append(Gut) - self.derived_variables["Go"].append(Got) - self.derived_variables["Cc"].append(Cct) - return At, Ct - - def backward(self, dLdAt): - """ - Backprop for a single timestep. - - Parameters - ---------- - dLdAt : :py:class:`ndarray ` of shape `(n_ex, n_out)` - The gradient of the loss wrt. the layer outputs (ie., hidden - states) at timestep `t`. - - Returns - ------- - dLdXt : :py:class:`ndarray ` of shape `(n_ex, n_in)` - The gradient of the loss wrt. the layer inputs at timestep `t`. - """ - assert self.trainable, "Layer is frozen" - - Wf, Wu, Wc, Wo, bf, bu, bc, bo = self._get_params() - - self.derived_variables["current_step"] -= 1 - t = self.derived_variables["current_step"] - - Got = self.derived_variables["Go"][t] - Gft = self.derived_variables["Gf"][t] - Gut = self.derived_variables["Gu"][t] - Cct = self.derived_variables["Cc"][t] - At = self.derived_variables["A"][t + 1] - Ct = self.derived_variables["C"][t + 1] - C_prev = self.derived_variables["C"][t] - A_prev = self.derived_variables["A"][t] - - Xt = self.X[t] - Zt = np.hstack([A_prev, Xt]) - - dA_acc = self.derived_variables["dLdA_accumulator"] - dC_acc = self.derived_variables["dLdC_accumulator"] - - # initialize accumulators - if dA_acc is None: - dA_acc = np.zeros_like(At) - - if dC_acc is None: - dC_acc = np.zeros_like(Ct) - - # Gradient calculations - # --------------------- - - dA = dLdAt + dA_acc - dC = dC_acc + dA * Got * self.act_fn.grad(Ct) - - # compute the input to the gate functions at timestep t - _Go = Zt @ Wo + bo - _Gf = Zt @ Wf + bf - _Gu = Zt @ Wu + bu - _Gc = Zt @ Wc + bc - - # compute gradients wrt the *input* to each gate - dGot = dA * self.act_fn(Ct) * self.gate_fn.grad(_Go) - dCct = dC * Gut * self.act_fn.grad(_Gc) - dGut = dC * Cct * self.gate_fn.grad(_Gu) - dGft = dC * C_prev * self.gate_fn.grad(_Gf) - - dZ = dGft @ Wf.T + dGut @ Wu.T + dCct @ Wc.T + dGot @ Wo.T - dXt = dZ[:, self.n_out :] - - self.gradients["Wc"] += Zt.T @ dCct - self.gradients["Wu"] += Zt.T @ dGut - self.gradients["Wf"] += Zt.T @ dGft - self.gradients["Wo"] += Zt.T @ dGot - self.gradients["bo"] += dGot.sum(axis=0, keepdims=True) - self.gradients["bu"] += dGut.sum(axis=0, keepdims=True) - self.gradients["bf"] += dGft.sum(axis=0, keepdims=True) - self.gradients["bc"] += dCct.sum(axis=0, keepdims=True) - - self.derived_variables["dLdA_accumulator"] = dZ[:, : self.n_out] - self.derived_variables["dLdC_accumulator"] = Gft * dC - return dXt - - def flush_gradients(self): - """Erase all the layer's derived variables and gradients.""" - assert self.trainable, "Layer is frozen" - - self.X = [] - for k, v in self.derived_variables.items(): - self.derived_variables[k] = [] - - self.derived_variables["n_timesteps"] = 0 - self.derived_variables["current_step"] = 0 - - # reset parameter gradients to 0 - for k, v in self.parameters.items(): - self.gradients[k] = np.zeros_like(v) - - -class RNN(LayerBase): - def __init__(self, n_out, act_fn="Tanh", kernel_initializer="glorot_uniform", name=None): - """ - A single vanilla (Elman)-RNN layer. - - Parameters - ---------- - n_out : int - The dimension of a single hidden state / output on a given - timestep. - act_fn : str, :doc:`Activation ` object, or None - The activation function for computing ``A[t]``. Default is - `'Tanh'`. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.kernel_initializer = kernel_initializer - self.n_in = None - self.n_out = n_out - self.n_timesteps = None - self.act_fn = ActivationInitializer(act_fn)() - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - self.cell = RNNCell( - n_in=self.n_in, - n_out=self.n_out, - act_fn=self.act_fn, - kernel_initializer=self.kernel_initializer, - ) - self.cell.set_optimizer() # FIXME - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "RNN", - "kernel_initializer": self.kernel_initializer, - "n_in": self.n_in, - "n_out": self.n_out, - "act_fn": str(self.act_fn), - "optimizer": self.cell.hyperparameters["optimizer"], - } - - def forward(self, X): - """ - Run a forward pass across all timesteps in the input. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in, n_t)` - Input consisting of `n_ex` examples each of dimensionality `n_in` - and extending for `n_t` timesteps. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_out, n_t)` - The value of the hidden state for each of the `n_ex` examples - across each of the `n_t` timesteps. - """ - if not self.is_initialized: - self.n_in = X.shape[1] - self._init_params() - - Y = [] - n_ex, n_in, n_t = X.shape - for t in range(n_t): - yt = self.cell.forward(X[:, :, t]) - Y.append(yt) - return np.dstack(Y) - - def backward(self, dLdA): - """ - Run a backward pass across all timesteps in the input. - - Parameters - ---------- - dLdA : :py:class:`ndarray ` of shape `(n_ex, n_out, n_t)` - The gradient of the loss with respect to the layer output for each - of the `n_ex` examples across all `n_t` timesteps. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape `(n_ex, n_in, n_t)` - The value of the hidden state for each of the `n_ex` examples - across each of the `n_t` timesteps. - """ - assert self.cell.trainable, "Layer is frozen" - dLdX = [] - n_ex, n_out, n_t = dLdA.shape - for t in reversed(range(n_t)): - dLdXt = self.cell.backward(dLdA[:, :, t]) - dLdX.insert(0, dLdXt) - dLdX = np.dstack(dLdX) - return dLdX - - @property - def derived_variables(self): - """ - Return a dictionary containing any intermediate variables computed - during the forward / backward passes. - """ - return self.cell.derived_variables - - @property - def gradients(self): - """ - Return a dictionary of the gradients computed during the backward - pass - """ - return self.cell.gradients - - @property - def parameters(self): - """Return a dictionary of the current layer parameters""" - return self.cell.parameters - - def set_params(self, summary_dict): - """ - Set the layer parameters from a dictionary of values. - - Parameters - ---------- - summary_dict : dict - A dictionary of layer parameters and hyperparameters. If a required - parameter or hyperparameter is not included within `summary_dict`, - this method will use the value in the current layer's - :meth:`summary` method. - - Returns - ------- - layer : :doc:`Layer ` object - The newly-initialized layer. - """ - self = super().set_params(summary_dict) - return self.cell.set_parameters(summary_dict) - - def freeze(self): - """ - Freeze the layer parameters at their current values so they can no - longer be updated. - """ - self.cell.freeze() - - def unfreeze(self): - """Unfreeze the layer parameters so they can be updated.""" - self.cell.unfreeze() - - def flush_gradients(self): - """Erase all the layer's derived variables and gradients.""" - self.cell.flush_gradients() - - def update(self): - """ - Update the layer parameters using the accrued gradients and layer - optimizer. Flush all gradients once the update is complete. - """ - self.cell.update() - self.flush_gradients() - - -class LSTM(LayerBase): - def __init__( - self, - n_out, - act_fn="Tanh", - gate_fn="Sigmoid", - kernel_initializer="glorot_uniform", - name=None, - ): - """ - A single long short-term memory (LSTM) RNN layer. - - Parameters - ---------- - n_out : int - The dimension of a single hidden state / output on a given timestep. - act_fn : str, :doc:`Activation ` object, or None - The activation function for computing ``A[t]``. Default is `'Tanh'`. - gate_fn : str, :doc:`Activation ` object, or None - The gate function for computing the update, forget, and output - gates. Default is `'Sigmoid'`. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is `'glorot_uniform'`. - """ # noqa: E501 - super().__init__(name=name) - - self.kernel_initializer = kernel_initializer - self.n_in = None - self.n_out = n_out - self.n_timesteps = None - self.act_fn = ActivationInitializer(act_fn)() - self.gate_fn = ActivationInitializer(gate_fn)() - self.is_initialized = False - self.weights_set = False - - def _init_params(self): - self.cell = LSTMCell( - n_in=self.n_in, - n_out=self.n_out, - act_fn=self.act_fn, - gate_fn=self.gate_fn, - kernel_initializer=self.kernel_initializer, - ) - ## FIXME: does LSTMCell need optimizer? - self.is_initialized = True - self.weights_set = True - - @property - def hyperparameters(self): - """Return a dictionary containing the layer hyperparameters.""" - return { - "layer": "LSTM", - "kernel_initializer": self.kernel_initializer, - "n_in": self.n_in, - "n_out": self.n_out, - "act_fn": str(self.act_fn), - "gate_fn": str(self.gate_fn), - "optimizer": self.cell.hyperparameters["optimizer"], - } - - def forward(self, X): - """ - Run a forward pass across all timesteps in the input. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in, n_t)` - Input consisting of `n_ex` examples each of dimensionality `n_in` - and extending for `n_t` timesteps. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_out, n_t)` - The value of the hidden state for each of the `n_ex` examples - across each of the `n_t` timesteps. - """ - if not self.is_initialized: - self.n_in = X.shape[1] - self._init_params() - - Y = [] - n_ex, n_in, n_t = X.shape - for t in range(n_t): - yt, _ = self.cell.forward(X[:, :, t]) - Y.append(yt) - return np.dstack(Y) - - def backward(self, dLdA): - """ - Run a backward pass across all timesteps in the input. - - Parameters - ---------- - dLdA : :py:class:`ndarray ` of shape `(n_ex, n_out, n_t)` - The gradient of the loss with respect to the layer output for each - of the `n_ex` examples across all `n_t` timesteps. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape (`n_ex`, `n_in`, `n_t`) - The value of the hidden state for each of the `n_ex` examples - across each of the `n_t` timesteps. - """ # noqa: E501 - assert self.cell.trainable, "Layer is frozen" - dLdX = [] - n_ex, n_out, n_t = dLdA.shape - for t in reversed(range(n_t)): - dLdXt, _ = self.cell.backward(dLdA[:, :, t]) - dLdX.insert(0, dLdXt) - dLdX = np.dstack(dLdX) - return dLdX - - @property - def derived_variables(self): - """ - Return a dictionary containing any intermediate variables computed - during the forward / backward passes. - """ - return self.cell.derived_variables - - @property - def gradients(self): - """ - Return a dictionary of the gradients computed during the backward - pass - """ - return self.cell.gradients - - @property - def parameters(self): - """Return a dictionary of the current layer parameters""" - return self.cell.parameters - - def freeze(self): - """ - Freeze the layer parameters at their current values so they can no - longer be updated. - """ - self.cell.freeze() - - def unfreeze(self): - """Unfreeze the layer parameters so they can be updated.""" - self.cell.unfreeze() - - def set_params(self, summary_dict): - """ - Set the layer parameters from a dictionary of values. - - Parameters - ---------- - summary_dict : dict - A dictionary of layer parameters and hyperparameters. If a required - parameter or hyperparameter is not included within `summary_dict`, - this method will use the value in the current layer's - :meth:`summary` method. - - Returns - ------- - layer : :doc:`Layer ` object - The newly-initialized layer. - """ - self = super().set_params(summary_dict) - return self.cell.set_parameters(summary_dict) - - def flush_gradients(self): - """Erase all the layer's derived variables and gradients.""" - self.cell.flush_gradients() - - def update(self): - """ - Update the layer parameters using the accrued gradients and layer - optimizer. Flush all gradients once the update is complete. - """ - self.cell.update() - self.flush_gradients() diff --git a/aitk/keras/losses/README.md b/aitk/keras/losses/README.md deleted file mode 100644 index 59e1008..0000000 --- a/aitk/keras/losses/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Losses - -The `losses.py` module implements several common loss functions, including: - -- Squared error -- Cross-entropy -- Variational lower-bound for binary VAE ([Kingma & Welling, 2014](https://arxiv.org/abs/1312.6114)) -- WGAN-GP loss for generator and critic ([Gulrajani et al., 2017](https://arxiv.org/pdf/1704.00028.pdf)) -- Noise contrastive estimation (NCE) loss ([Gutmann & - Hyvärinen, 2010](https://www.cs.helsinki.fi/u/ahyvarin/papers/Gutmann10AISTATS.pdf); [Minh & Teh, 2012](https://www.cs.toronto.edu/~amnih/papers/ncelm.pdf)) diff --git a/aitk/keras/losses/__init__.py b/aitk/keras/losses/__init__.py deleted file mode 100644 index 908ff51..0000000 --- a/aitk/keras/losses/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Common neural network loss functions. - -This module implements loss objects that can be used during neural network -training. -""" - -from .losses import * diff --git a/aitk/keras/losses/losses.py b/aitk/keras/losses/losses.py deleted file mode 100644 index 23f7fc8..0000000 --- a/aitk/keras/losses/losses.py +++ /dev/null @@ -1,946 +0,0 @@ -from abc import ABC, abstractmethod - -import numpy as np - -from ..numpy_ml_utils.testing import is_binary, is_stochastic -from ..initializers import ( - WeightInitializer, - ActivationInitializer, - OptimizerInitializer, -) - - -class ObjectiveBase(ABC): - def __init__(self): - super().__init__() - self.name = "base_loss" - - @abstractmethod - def loss(self, y_true, y_pred): - pass - - @abstractmethod - def grad(self, y_true, y_pred, **kwargs): - pass - - -class MeanSquaredError(ObjectiveBase): - def __init__(self): - super().__init__() - self.name = "mean_squared_error" - - def loss(self, y, y_pred): - squared_error = np.square(y_pred - y) - mse = np.mean(squared_error) - return mse - - def __call__(self, y, y_pred): - return self.loss(y, y_pred) - - def grad(self, y, y_pred): - return 2 * (y_pred - y) - -class SquaredError(ObjectiveBase): - def __init__(self): - r""" - A squared-error / `L2` loss. - - Notes - ----- - For real-valued target **y** and predictions :math:`\hat{\mathbf{y}}`, the - squared error is - - .. math:: - \mathcal{L}(\mathbf{y}, \hat{\mathbf{y}}) - = 0.5 ||\hat{\mathbf{y}} - \mathbf{y}||_2^2 - """ - super().__init__() - self.name = "squared_error" - - def __call__(self, y, y_pred): - return self.loss(y, y_pred) - - def __str__(self): - return "SquaredError" - - @staticmethod - def loss(y, y_pred): - """ - Compute the squared error between `y` and `y_pred`. - - Parameters - ---------- - y : :py:class:`ndarray ` of shape (n, m) - Ground truth values for each of `n` examples - y_pred : :py:class:`ndarray ` of shape (n, m) - Predictions for the `n` examples in the batch. - - Returns - ------- - loss : float - The sum of the squared error across dimensions and examples. - """ - return 0.5 * np.linalg.norm(y_pred - y) ** 2 - - @staticmethod - def grad(y, y_pred, z, act_fn): - r""" - Gradient of the squared error loss with respect to the pre-nonlinearity - input, `z`. - - Notes - ----- - The current method computes the gradient :math:`\\frac{\partial - \mathcal{L}}{\partial \mathbf{z}}`, where - - .. math:: - - \mathcal{L}(\mathbf{z}) - &= \\text{squared_error}(\mathbf{y}, g(\mathbf{z})) \\\\ - g(\mathbf{z}) - &= \\text{act_fn}(\mathbf{z}) - - The gradient with respect to :math:`\mathbf{z}` is then - - .. math:: - - \\frac{\partial \mathcal{L}}{\partial \mathbf{z}} - = (g(\mathbf{z}) - \mathbf{y}) \left( - \\frac{\partial g}{\partial \mathbf{z}} \\right) - - Parameters - ---------- - y : :py:class:`ndarray ` of shape (n, m) - Ground truth values for each of `n` examples. - y_pred : :py:class:`ndarray ` of shape (n, m) - Predictions for the `n` examples in the batch. - act_fn : :doc:`Activation ` object - The activation function for the output layer of the network. - - Returns - ------- - grad : :py:class:`ndarray ` of shape (n, m) - The gradient of the squared error loss with respect to `z`. - """ - return (y_pred - y) * act_fn.grad(z) - - -class CrossEntropy(ObjectiveBase): - def __init__(self): - r""" - A cross-entropy loss. - - Notes - ----- - For a one-hot target **y** and predicted class probabilities - :math:`\hat{\mathbf{y}}`, the cross entropy is - - .. math:: - \mathcal{L}(\mathbf{y}, \hat{\mathbf{y}}) - = \sum_i y_i \log \hat{y}_i - """ - super().__init__() - self.name = "cross_entropy" - - def __call__(self, y, y_pred): - return self.loss(y, y_pred) - - def __str__(self): - return "CrossEntropy" - - @staticmethod - def loss(y, y_pred): - """ - Compute the cross-entropy (log) loss. - - Notes - ----- - This method returns the sum (not the average!) of the losses for each - sample. - - Parameters - ---------- - y : :py:class:`ndarray ` of shape (n, m) - Class labels (one-hot with `m` possible classes) for each of `n` - examples. - y_pred : :py:class:`ndarray ` of shape (n, m) - Probabilities of each of `m` classes for the `n` examples in the - batch. - - Returns - ------- - loss : float - The sum of the cross-entropy across classes and examples. - """ - is_binary(y) - is_stochastic(y_pred) - - # prevent taking the log of 0 - eps = np.finfo(float).eps - - # each example is associated with a single class; sum the negative log - # probability of the correct label over all samples in the batch. - # observe that we are taking advantage of the fact that y is one-hot - # encoded - cross_entropy = -np.sum(y * np.log(y_pred + eps)) - return cross_entropy - - @staticmethod - def grad(y, y_pred): - r""" - Compute the gradient of the cross entropy loss with regard to the - softmax input, `z`. - - Notes - ----- - The gradient for this method goes through both the cross-entropy loss - AND the softmax non-linearity to return :math:`\\frac{\partial - \mathcal{L}}{\partial \mathbf{z}}` (rather than :math:`\\frac{\partial - \mathcal{L}}{\partial \\text{softmax}(\mathbf{z})}`). - - In particular, let: - - .. math:: - - \mathcal{L}(\mathbf{z}) - = \\text{cross_entropy}(\\text{softmax}(\mathbf{z})). - - The current method computes: - - .. math:: - - \\frac{\partial \mathcal{L}}{\partial \mathbf{z}} - &= \\text{softmax}(\mathbf{z}) - \mathbf{y} \\\\ - &= \hat{\mathbf{y}} - \mathbf{y} - - Parameters - ---------- - y : :py:class:`ndarray ` of shape `(n, m)` - A one-hot encoding of the true class labels. Each row constitues a - training example, and each column is a different class. - y_pred: :py:class:`ndarray ` of shape `(n, m)` - The network predictions for the probability of each of `m` class - labels on each of `n` examples in a batch. - - Returns - ------- - grad : :py:class:`ndarray ` of shape (n, m) - The gradient of the cross-entropy loss with respect to the *input* - to the softmax function. - """ - is_binary(y) - is_stochastic(y_pred) - - # derivative of xe wrt z is y_pred - y_true, hence we can just - # subtract 1 from the probability of the correct class labels - grad = y_pred - y - - # [optional] scale the gradients by the number of examples in the batch - # n, m = y.shape - # grad /= n - return grad - - -class VAELoss(ObjectiveBase): - def __init__(self): - r""" - The variational lower bound for a variational autoencoder with Bernoulli - units. - - Notes - ----- - The VLB to the sum of the binary cross entropy between the true input and - the predicted output (the "reconstruction loss") and the KL divergence - between the learned variational distribution :math:`q` and the prior, - :math:`p`, assumed to be a unit Gaussian. - - .. math:: - - \\text{VAELoss} = - \\text{cross_entropy}(\mathbf{y}, \hat{\mathbf{y}}) - + \\mathbb{KL}[q \ || \ p] - - where :math:`\mathbb{KL}[q \ || \ p]` is the Kullback-Leibler - divergence between the distributions :math:`q` and :math:`p`. - - References - ---------- - .. [1] Kingma, D. P. & Welling, M. (2014). "Auto-encoding variational Bayes". - *arXiv preprint arXiv:1312.6114.* https://arxiv.org/pdf/1312.6114.pdf - """ - super().__init__() - self.name = "vae_loss" - - def __call__(self, y, y_pred, t_mean, t_log_var): - return self.loss(y, y_pred, t_mean, t_log_var) - - def __str__(self): - return "VAELoss" - - @staticmethod - def loss(y, y_pred, t_mean, t_log_var): - r""" - Variational lower bound for a Bernoulli VAE. - - Parameters - ---------- - y : :py:class:`ndarray ` of shape `(n_ex, N)` - The original images. - y_pred : :py:class:`ndarray ` of shape `(n_ex, N)` - The VAE reconstruction of the images. - t_mean: :py:class:`ndarray ` of shape `(n_ex, T)` - Mean of the variational distribution :math:`q(t \mid x)`. - t_log_var: :py:class:`ndarray ` of shape `(n_ex, T)` - Log of the variance vector of the variational distribution - :math:`q(t \mid x)`. - - Returns - ------- - loss : float - The VLB, averaged across the batch. - """ - # prevent nan on log(0) - eps = np.finfo(float).eps - y_pred = np.clip(y_pred, eps, 1 - eps) - - # reconstruction loss: binary cross-entropy - rec_loss = -np.sum(y * np.log(y_pred) + (1 - y) * np.log(1 - y_pred), axis=1) - - # KL divergence between the variational distribution q and the prior p, - # a unit gaussian - kl_loss = -0.5 * np.sum(1 + t_log_var - t_mean ** 2 - np.exp(t_log_var), axis=1) - loss = np.mean(kl_loss + rec_loss) - return loss - - @staticmethod - def grad(y, y_pred, t_mean, t_log_var): - """ - Compute the gradient of the VLB with regard to the network parameters. - - Parameters - ---------- - y : :py:class:`ndarray ` of shape `(n_ex, N)` - The original images. - y_pred : :py:class:`ndarray ` of shape `(n_ex, N)` - The VAE reconstruction of the images. - t_mean: :py:class:`ndarray ` of shape `(n_ex, T)` - Mean of the variational distribution :math:`q(t | x)`. - t_log_var: :py:class:`ndarray ` of shape `(n_ex, T)` - Log of the variance vector of the variational distribution - :math:`q(t | x)`. - - Returns - ------- - dY_pred : :py:class:`ndarray ` of shape `(n_ex, N)` - The gradient of the VLB with regard to `y_pred`. - dLogVar : :py:class:`ndarray ` of shape `(n_ex, T)` - The gradient of the VLB with regard to `t_log_var`. - dMean : :py:class:`ndarray ` of shape `(n_ex, T)` - The gradient of the VLB with regard to `t_mean`. - """ - N = y.shape[0] - eps = np.finfo(float).eps - y_pred = np.clip(y_pred, eps, 1 - eps) - - dY_pred = -y / (N * y_pred) - (y - 1) / (N - N * y_pred) - dLogVar = (np.exp(t_log_var) - 1) / (2 * N) - dMean = t_mean / N - return dY_pred, dLogVar, dMean - - -class WGAN_GPLoss(ObjectiveBase): - def __init__(self, lambda_=10): - r""" - The loss function for a Wasserstein GAN [*]_ [*]_ with gradient penalty. - - Notes - ----- - Assuming an optimal critic, minimizing this quantity wrt. the generator - parameters corresponds to minimizing the Wasserstein-1 (earth-mover) - distance between the fake and real data distributions. - - The formula for the WGAN-GP critic loss is - - .. math:: - - \\text{WGANLoss} - &= \sum_{x \in X_{real}} p(x) D(x) - - \sum_{x' \in X_{fake}} p(x') D(x') \\\\ - \\text{WGANLossGP} - &= \\text{WGANLoss} + \lambda - (||\\nabla_{X_{interp}} D(X_{interp})||_2 - 1)^2 - - where - - .. math:: - - X_{fake} &= \\text{Generator}(\mathbf{z}) \\\\ - X_{interp} &= \\alpha X_{real} + (1 - \\alpha) X_{fake} \\\\ - - and - - .. math:: - - \mathbf{z} &\sim \mathcal{N}(0, \mathbb{1}) \\\\ - \\alpha &\sim \\text{Uniform}(0, 1) - - References - ---------- - .. [*] Gulrajani, I., Ahmed, F., Arjovsky, M., Dumoulin, V., & - Courville, A. (2017) "Improved training of Wasserstein GANs" - *Advances in Neural Information Processing Systems, 31*: 5769-5779. - .. [*] Goodfellow, I. J, Abadie, P. A., Mirza, M., Xu, B., Farley, D. - W., Ozair, S., Courville, A., & Bengio, Y. (2014) "Generative - adversarial nets" *Advances in Neural Information Processing - Systems, 27*: 2672-2680. - - Parameters - ---------- - lambda_ : float - The gradient penalty coefficient. Default is 10. - """ - self.lambda_ = lambda_ - super().__init__() - self.name = "wgan_gp_loss" - - def __call__(self, Y_fake, module, Y_real=None, gradInterp=None): - """ - Computes the generator and critic loss using the WGAN-GP value - function. - - Parameters - ---------- - Y_fake : :py:class:`ndarray ` of shape `(n_ex,)` - The output of the critic for `X_fake`. - module : {'C', 'G'} - Whether to calculate the loss for the critic ('C') or the generator - ('G'). If calculating loss for the critic, `Y_real` and - `gradInterp` must not be None. - Y_real : :py:class:`ndarray ` of shape `(n_ex,)`, or None - The output of the critic for `X_real`. Default is None. - gradInterp : :py:class:`ndarray ` of shape `(n_ex, n_feats)`, or None - The gradient of the critic output for `X_interp` wrt. `X_interp`. - Default is None. - - Returns - ------- - loss : float - Depending on the setting for `module`, either the critic or - generator loss, averaged over examples in the minibatch. - """ - return self.loss(Y_fake, module, Y_real=Y_real, gradInterp=gradInterp) - - def __str__(self): - return "WGANLossGP(lambda_={})".format(self.lambda_) - - def loss(self, Y_fake, module, Y_real=None, gradInterp=None): - """ - Computes the generator and critic loss using the WGAN-GP value - function. - - Parameters - ---------- - Y_fake : :py:class:`ndarray ` of shape (n_ex,) - The output of the critic for `X_fake`. - module : {'C', 'G'} - Whether to calculate the loss for the critic ('C') or the generator - ('G'). If calculating loss for the critic, `Y_real` and - `gradInterp` must not be None. - Y_real : :py:class:`ndarray ` of shape `(n_ex,)` or None - The output of the critic for `X_real`. Default is None. - gradInterp : :py:class:`ndarray ` of shape `(n_ex, n_feats)` or None - The gradient of the critic output for `X_interp` wrt. `X_interp`. - Default is None. - - Returns - ------- - loss : float - Depending on the setting for `module`, either the critic or - generator loss, averaged over examples in the minibatch. - """ - # calc critic loss including gradient penalty - if module == "C": - X_interp_norm = np.linalg.norm(gradInterp, axis=1, keepdims=True) - gradient_penalty = (X_interp_norm - 1) ** 2 - loss = ( - Y_fake.mean() - Y_real.mean() + self.lambda_ * gradient_penalty.mean() - ) - - # calc generator loss - elif module == "G": - loss = -Y_fake.mean() - - else: - raise ValueError("Unrecognized module: {}".format(module)) - - return loss - - def grad(self, Y_fake, module, Y_real=None, gradInterp=None): - """ - Computes the gradient of the generator or critic loss with regard to - its inputs. - - Parameters - ---------- - Y_fake : :py:class:`ndarray ` of shape `(n_ex,)` - The output of the critic for `X_fake`. - module : {'C', 'G'} - Whether to calculate the gradient for the critic loss ('C') or the - generator loss ('G'). If calculating grads for the critic, `Y_real` - and `gradInterp` must not be None. - Y_real : :py:class:`ndarray ` of shape `(n_ex,)` or None - The output of the critic for `X_real`. Default is None. - gradInterp : :py:class:`ndarray ` of shape `(n_ex, n_feats)` or None - The gradient of the critic output on `X_interp` wrt. `X_interp`. - Default is None. - - Returns - ------- - grads : tuple - If `module` == 'C', returns a 3-tuple containing the gradient of - the critic loss with regard to (`Y_fake`, `Y_real`, `gradInterp`). - If `module` == 'G', returns the gradient of the generator with - regard to `Y_fake`. - """ - eps = np.finfo(float).eps - n_ex_fake = Y_fake.shape[0] - - # calc gradient of the critic loss - if module == "C": - n_ex_real = Y_real.shape[0] - - dY_fake = -1 / n_ex_fake * np.ones_like(Y_fake) - dY_real = 1 / n_ex_real * np.ones_like(Y_real) - - # differentiate through gradient penalty - X_interp_norm = np.linalg.norm(gradInterp, axis=1, keepdims=True) + eps - - dGradInterp = ( - (2 / n_ex_fake) - * self.lambda_ - * (X_interp_norm - 1) - * (gradInterp / X_interp_norm) - ) - grad = (dY_fake, dY_real, dGradInterp) - - # calc gradient of the generator loss - elif module == "G": - grad = -1 / n_ex_fake * np.ones_like(Y_fake) - - else: - raise ValueError("Unrecognized module: {}".format(module)) - return grad - - -class NCELoss(ObjectiveBase): - """ - """ - - def __init__( - self, - n_classes, - noise_sampler, - num_negative_samples, - optimizer=None, - init="glorot_uniform", - subtract_log_label_prob=True, - ): - r""" - A noise contrastive estimation (NCE) loss function. - - Notes - ----- - Noise contrastive estimation is a candidate sampling method often - used to reduce the computational challenge of training a softmax - layer on problems with a large number of output classes. It proceeds by - training a logistic regression model to discriminate between samples - from the true data distribution and samples from an artificial noise - distribution. - - It can be shown that as the ratio of negative samples to data samples - goes to infinity, the gradient of the NCE loss converges to the - original softmax gradient. - - For input data **X**, target labels `targets`, loss parameters **W** and - **b**, and noise samples `noise` sampled from the noise distribution `Q`, - the NCE loss is - - .. math:: - - \\text{NCE}(X, targets) = - \\text{cross_entropy}(\mathbf{y}_{targets}, \hat{\mathbf{y}}_{targets}) + - \\text{cross_entropy}(\mathbf{y}_{noise}, \hat{\mathbf{y}}_{noise}) - - where - - .. math:: - - \hat{\mathbf{y}}_{targets} - &= \sigma(\mathbf{W}[targets] \mathbf{X} + \mathbf{b}[targets] - \log Q(targets)) \\\\ - \hat{\mathbf{y}}_{noise} - &= \sigma(\mathbf{W}[noise] \mathbf{X} + \mathbf{b}[noise] - \log Q(noise)) - - In the above equations, :math:`\sigma` is the logistic sigmoid - function, and :math:`Q(x)` corresponds to the probability of the values - in `x` under `Q`. - - References - ---------- - .. [1] Gutmann, M. & Hyvarinen, A. (2010). Noise-contrastive - estimation: A new estimation principle for unnormalized statistical - models. *AISTATS, 13*: 297-304. - .. [2] Minh, A. & Teh, Y. W. (2012). A fast and simple algorithm for - training neural probabilistic language models. *ICML, 29*: 1751-1758. - - Parameters - ---------- - n_classes : int - The total number of output classes in the model. - noise_sampler : :class:`~numpy_ml.utils.data_structures.DiscreteSampler` instance - The negative sampler. Defines a distribution over all classes in - the dataset. - num_negative_samples : int - The number of negative samples to draw for each target / batch of - targets. - init : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is 'glorot_uniform'. - optimizer : str, :doc:`Optimizer ` object, or None - The optimization strategy to use when performing gradient updates - within the :meth:`update` method. If None, use the :class:`SGD - ` optimizer with - default parameters. Default is None. - subtract_log_label_prob : bool - Whether to subtract the log of the probability of each label under - the noise distribution from its respective logit. Set to False for - negative sampling, True for NCE. Default is True. - - Attributes - ---------- - gradients : dict - The accumulated parameter gradients. - parameters: dict - The loss parameter values. - hyperparameters: dict - The loss hyperparameter values. - derived_variables: dict - Useful intermediate values computed during the loss computation. - """ - super().__init__() - self.name = "nce_loss" - - self.init = init - self.n_in = None - self.trainable = True - self.n_classes = n_classes - self.noise_sampler = noise_sampler - self.num_negative_samples = num_negative_samples - self.act_fn = ActivationInitializer("Sigmoid")() - self.optimizer = OptimizerInitializer(optimizer)() - self.subtract_log_label_prob = subtract_log_label_prob - - self.is_initialized = False - - def _init_params(self): - init_weights = WeightInitializer(str(self.act_fn), mode=self.init) - - self.X = [] - b = np.zeros((1, self.n_classes)) - W = init_weights((self.n_classes, self.n_in)) - - self.parameters = {"W": W, "b": b} - - self.gradients = {"W": np.zeros_like(W), "b": np.zeros_like(b)} - - self.derived_variables = { - "y_pred": [], - "target": [], - "true_w": [], - "true_b": [], - "sampled_b": [], - "sampled_w": [], - "out_labels": [], - "target_logits": [], - "noise_samples": [], - "noise_logits": [], - } - - self.is_initialized = True - - @property - def hyperparameters(self): - return { - "id": "NCELoss", - "n_in": self.n_in, - "init": self.init, - "n_classes": self.n_classes, - "noise_sampler": self.noise_sampler, - "num_negative_samples": self.num_negative_samples, - "subtract_log_label_prob": self.subtract_log_label_prob, - "optimizer": { - "cache": self.optimizer.cache, - "hyperparameters": self.optimizer.hyperparameters, - }, - } - - def __call__(self, target, X, neg_samples=None, retain_derived=True): - return self.loss(target, X, neg_samples, retain_derived) - - def __str__(self): - keys = [ - "{}={}".format(k, v) - for k, v in self.hyperparameters.items() - if k not in ["id", "optimizer"] - ] + ["optimizer={}".format(self.optimizer)] - return "NCELoss({})".format(", ".join(keys)) - - def freeze(self): - """ - Freeze the loss parameters at their current values so they can no - longer be updated. - """ - self.trainable = False - - def unfreeze(self): - """Unfreeze the layer parameters so they can be updated.""" - self.trainable = True - - def flush_gradients(self): - """Erase all the layer's derived variables and gradients.""" - assert self.trainable, "NCELoss is frozen" - self.X = [] - for k, v in self.derived_variables.items(): - self.derived_variables[k] = [] - - for k, v in self.gradients.items(): - self.gradients[k] = np.zeros_like(v) - - def update(self, cur_loss=None): - """ - Update the loss parameters using the accrued gradients and optimizer. - Flush all gradients once the update is complete. - """ - assert self.trainable, "NCELoss is frozen" - self.optimizer.step() - for k, v in self.gradients.items(): - if k in self.parameters: - self.parameters[k] = self.optimizer(self.parameters[k], v, k, cur_loss) - self.flush_gradients() - - def loss(self, target, X, neg_samples=None, retain_derived=True): - """ - Compute the NCE loss for a collection of inputs and associated targets. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_c, n_in)` - Layer input. A minibatch of `n_ex` examples, where each example is - an `n_c` by `n_in` matrix (e.g., the matrix of `n_c` context - embeddings, each of dimensionality `n_in`, for a CBOW model). - target : :py:class:`ndarray ` of shape `(n_ex,)` - Integer indices of the target class(es) for each example in the - minibatch (e.g., the target word id for an example in a CBOW model). - neg_samples : :py:class:`ndarray ` of shape (`num_negative_samples`,) or None - An optional array of negative samples to use during the loss - calculation. These will be used instead of samples draw from - ``self.noise_sampler``. Default is None. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through with regard to this input. - Default is True. - - Returns - ------- - loss : float - The NCE loss summed over the minibatch and samples. - y_pred : :py:class:`ndarray ` of shape (`n_ex`, `n_c`) - The network predictions for the conditional probability of each - target given each context: entry (`i`, `j`) gives the predicted - probability of target `i` under context vector `j`. - """ - if not self.is_initialized: - self.n_in = X.shape[-1] - self._init_params() - - loss, Z_target, Z_neg, y_pred, y_true, noise_samples = self._loss( - X, target, neg_samples - ) - - # cache derived variables for gradient calculation - if retain_derived: - self.X.append(X) - - self.derived_variables["y_pred"].append(y_pred) - self.derived_variables["target"].append(target) - self.derived_variables["out_labels"].append(y_true) - self.derived_variables["target_logits"].append(Z_target) - self.derived_variables["noise_samples"].append(noise_samples) - self.derived_variables["noise_logits"].append(Z_neg) - - return loss, np.squeeze(y_pred[..., :1], -1) - - def _loss(self, X, target, neg_samples): - """Actual computation of NCE loss""" - fstr = "X must have shape (n_ex, n_c, n_in), but got {} dims instead" - assert X.ndim == 3, fstr.format(X.ndim) - - W = self.parameters["W"] - b = self.parameters["b"] - - # sample negative samples from the noise distribution - if neg_samples is None: - neg_samples = self.noise_sampler(self.num_negative_samples) - assert len(neg_samples) == self.num_negative_samples - - # get the probability of the negative sample class and the target - # class under the noise distribution - p_neg_samples = self.noise_sampler.probs[neg_samples] - p_target = np.atleast_2d(self.noise_sampler.probs[target]) - - # save the noise samples for debugging - noise_samples = (neg_samples, p_target, p_neg_samples) - - # compute the logit for the negative samples and target - Z_target = X @ W[target].T + b[0, target] - Z_neg = X @ W[neg_samples].T + b[0, neg_samples] - - # subtract the log probability of each label under the noise dist - if self.subtract_log_label_prob: - n, m = Z_target.shape[0], Z_neg.shape[0] - Z_target[range(n), ...] -= np.log(p_target) - Z_neg[range(m), ...] -= np.log(p_neg_samples) - - # only retain the probability of the target under its associated - # minibatch example - aa, _, cc = Z_target.shape - Z_target = Z_target[range(aa), :, range(cc)][..., None] - - # p_target = (n_ex, n_c, 1) - # p_neg = (n_ex, n_c, n_samples) - pred_p_target = self.act_fn(Z_target) - pred_p_neg = self.act_fn(Z_neg) - - # if we're in evaluation mode, ignore the negative samples - just - # return the binary cross entropy on the targets - y_pred = pred_p_target - if self.trainable: - # (n_ex, n_c, 1 + n_samples) (target is first column) - y_pred = np.concatenate((y_pred, pred_p_neg), axis=-1) - - n_targets = 1 - y_true = np.zeros_like(y_pred) - y_true[..., :n_targets] = 1 - - # binary cross entropy - eps = np.finfo(float).eps - np.clip(y_pred, eps, 1 - eps, y_pred) - loss = -np.sum(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)) - return loss, Z_target, Z_neg, y_pred, y_true, noise_samples - - def grad(self, retain_grads=True, update_params=True): - """ - Compute the gradient of the NCE loss with regard to the inputs, - weights, and biases. - - Parameters - ---------- - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - update_params : bool - Whether to perform a single step of gradient descent on the layer - weights and bias using the calculated gradients. If `retain_grads` - is False, this option is ignored and the parameter gradients are - not updated. Default is True. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape (`n_ex`, `n_in`) or list of arrays - The gradient of the loss with regard to the layer input(s) `X`. - """ - assert self.trainable, "NCE loss is frozen" - - dX = [] - for input_idx, x in enumerate(self.X): - dx, dw, db = self._grad(x, input_idx) - dX.append(dx) - - if retain_grads: - self.gradients["W"] += dw - self.gradients["b"] += db - - dX = dX[0] if len(self.X) == 1 else dX - - if retain_grads and update_params: - self.update() - - return dX - - def _grad(self, X, input_idx): - """Actual computation of gradient wrt. loss weights + input""" - W, b = self.parameters["W"], self.parameters["b"] - - y_pred = self.derived_variables["y_pred"][input_idx] - target = self.derived_variables["target"][input_idx] - y_true = self.derived_variables["out_labels"][input_idx] - Z_neg = self.derived_variables["noise_logits"][input_idx] - Z_target = self.derived_variables["target_logits"][input_idx] - neg_samples = self.derived_variables["noise_samples"][input_idx][0] - - # the number of target classes per minibatch example - n_targets = 1 - - # calculate the grad of the binary cross entropy wrt. the network - # predictions - preds, classes = y_pred.flatten(), y_true.flatten() - - dLdp_real = ((1 - classes) / (1 - preds)) - (classes / preds) - dLdp_real = dLdp_real.reshape(*y_pred.shape) - - # partition the gradients into target and negative sample portions - dLdy_pred_target = dLdp_real[..., :n_targets] - dLdy_pred_neg = dLdp_real[..., n_targets:] - - # compute gradients of the loss wrt the data and noise logits - dLdZ_target = dLdy_pred_target * self.act_fn.grad(Z_target) - dLdZ_neg = dLdy_pred_neg * self.act_fn.grad(Z_neg) - - # compute param gradients on target + negative samples - dB_neg = dLdZ_neg.sum(axis=(0, 1)) - dB_target = dLdZ_target.sum(axis=(1, 2)) - - dW_neg = (dLdZ_neg.transpose(0, 2, 1) @ X).sum(axis=0) - dW_target = (dLdZ_target.transpose(0, 2, 1) @ X).sum(axis=1) - - # TODO: can this be done with np.einsum instead? - dX_target = np.vstack( - [dLdZ_target[[ix]] @ W[[t]] for ix, t in enumerate(target)] - ) - dX_neg = dLdZ_neg @ W[neg_samples] - - hits = list(set(target).intersection(set(neg_samples))) - hit_ixs = [np.where(target == h)[0] for h in hits] - - # adjust param gradients if there's an accidental hit - if len(hits) != 0: - hit_ixs = np.concatenate(hit_ixs) - target = np.delete(target, hit_ixs) - dB_target = np.delete(dB_target, hit_ixs) - dW_target = np.delete(dW_target, hit_ixs, 0) - - dX = dX_target + dX_neg - - # use np.add.at to ensure that repeated indices in the target (or - # possibly in neg_samples if sampling is done with replacement) are - # properly accounted for - dB = np.zeros_like(b).flatten() - np.add.at(dB, target, dB_target) - np.add.at(dB, neg_samples, dB_neg) - dB = dB.reshape(*b.shape) - - dW = np.zeros_like(W) - np.add.at(dW, target, dW_target) - np.add.at(dW, neg_samples, dW_neg) - - return dX, dW, dB diff --git a/aitk/keras/metrics.py b/aitk/keras/metrics.py deleted file mode 100644 index 4bcf51c..0000000 --- a/aitk/keras/metrics.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# ************************************************************** -# aitk.keras: A Python Keras model API -# -# Copyright (c) 2021 AITK Developers -# -# https://github.com/ArtificialIntelligenceToolkit/aitk.keras -# -# ************************************************************** - -""" -Metrics can be computed as a stateless function: - -metric(targets, outputs) - -or as a stateful subclass of Metric. -""" - -import numpy as np -from abc import ABC, abstractmethod - -class Metric(ABC): - def __init__(self, name): - super().__init__() - self.name = name - - @abstractmethod - def reset_state(self): - raise NotImplementedError - - @abstractmethod - def update_state(self, targets, outputs): - raise NotImplementedError - - @abstractmethod - def result(self): - raise NotImplementedError - - def __str__(self): - return self.name - -class ToleranceAccuracy(Metric): - def __init__(self, tolerance): - super().__init__("tolerance_accuracy") - self.tolerance = tolerance - self.reset_state() - - def reset_state(self): - self.accurate = 0 - self.total = 0 - - def update_state(self, targets, outputs): - results = np.all( - np.less_equal(np.abs(targets - outputs), - self.tolerance), axis=-1) - self.accurate += sum(results) - self.total += len(results) - - def result(self): - return self.accurate / self.total - -def tolerance_accuracy(targets, outputs): - return np.mean( - np.all( - np.less_equal(np.abs(targets - outputs), - tolerance_accuracy.tolerance), - axis=-1), - axis=-1, - ) -# Needs the tolerance from somewhere: -tolerance_accuracy.tolerance = 0.1 diff --git a/aitk/keras/models/README.md b/aitk/keras/models/README.md deleted file mode 100644 index 1a15ce7..0000000 --- a/aitk/keras/models/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Models - -The models module implements popular full neural networks. It includes: - -- `vae.py`: A Bernoulli variational autoencoder ([Kingma & Welling, 2014](https://arxiv.org/abs/1312.6114)) -- `wgan_gp.py`: A Wasserstein generative adversarial network with gradient - penalty ([Gulrajani et al., 2017](https://arxiv.org/pdf/1704.00028.pdf); -[Goodfellow et al., 2014](https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf)) -- `w2v.py`: word2vec model with CBOW and skip-gram architectures and - training via noise contrastive estimation ([Mikolov et al., 2012](https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf)) diff --git a/aitk/keras/models/__init__.py b/aitk/keras/models/__init__.py deleted file mode 100644 index af5d12c..0000000 --- a/aitk/keras/models/__init__.py +++ /dev/null @@ -1,540 +0,0 @@ -# -*- coding: utf-8 -*- -# ************************************************************** -# aitk.keras: A Python Keras model API -# -# Copyright (c) 2021 AITK Developers -# -# https://github.com/ArtificialIntelligenceToolkit/aitk.keras -# -# ************************************************************** - -from ..layers import Input, Activation, Concatenate -from ..losses import MeanSquaredError, CrossEntropy -from ..initializers import OptimizerInitializer -from ..callbacks import History -from ..utils import topological_sort - -import numpy as np -import time -import math -import numbers -import functools -import operator -from collections import defaultdict - -LOSS_FUNCTIONS = { - "mse": MeanSquaredError, - "mean_squared_error": MeanSquaredError, - "crossentropy": CrossEntropy, - # FIXME: add more error functions -} - -NAME_CACHE = {} - -def get_metric_name(metric): - if hasattr(metric, "name"): - return metric.name - elif hasattr(metric, "__name__"): - return metric.__name__ - else: - return str(metric) - - -class Model(): - def __init__(self, inputs=None, outputs=None, name=None): - self.stop_training = False - self.built = False - self.sequential = False - self.history = History() - self.name = self.make_name(name) - self.layers = [] - self.layer_map = {} - self._input_layers = None - self._output_layers = None - self.step = 0 - # Build a model graph from inputs to outputs: - if inputs is not None and outputs is not None: - if not isinstance(outputs, (list, tuple)): - outputs = [outputs] - queue = [] if inputs is None else inputs - if not isinstance(queue, (list, tuple)): - queue = [queue] - while len(queue) > 0: - layer = queue.pop(0) - if layer not in self.layers: - if layer.name in self.layer_map: - raise AttributeError("duplicate layer name: '%s'" % layer.name) - self.layers.append(layer) - self.layer_map[layer.name] = layer - if layer in outputs: - # Make sure no more layers: - layer.output_layers = [] - else: - queue.extend(layer.output_layers) - self.sequential = self.is_sequential() - self.build() - - def is_sequential(self): - return ((len(self.get_input_layers()) == 1) and - (len(self.get_output_layers()) == 1) and - (not any([isinstance(layer, Concatenate) - for layer in self.layers]))) - - def get_input_layers(self): - if self._input_layers is None: - return [layer for layer in self.layers if len(layer.input_layers) == 0] - else: - return self._input_layers - - def get_output_layers(self): - if self._output_layers is None: - return [layer for layer in self.layers if len(layer.output_layers) == 0] - else: - return self._output_layers - - def connect(self, in_layer, out_layer): - """ - Connect first layer to second layer. - """ - if in_layer not in out_layer.input_layers: - out_layer.input_layers.append(in_layer) - if out_layer not in in_layer.output_layers: - in_layer.output_layers.append(out_layer) - - def make_name(self, name): - if name is None: - class_name = self.__class__.__name__.lower() - count = NAME_CACHE.get(class_name, 0) - if count == 0: - new_name = class_name - else: - new_name = "%s_%s" % (class_name, count) - NAME_CACHE[class_name] = count + 1 - return new_name - else: - return name - - def summary(self): - if not self.built: - print(f'Model: "{self.name}" (unbuilt)') - else: - print(f'Model: "{self.name}"') - print('_' * 65) - print("Layer (type) Output Shape Param #") - print("=" * 65) - total_parameters = 0 - # FIXME: sum up other, non-trainable params - other_params = 0 - for i, layer in enumerate(topological_sort(self.get_input_layers())): - layer_name = ("%s (%s)" % (layer.name, layer.__class__.__name__))[:25] - output_shape = (None, layer.n_out) if isinstance(layer.n_out, numbers.Number) else layer.n_out - if self.built: - parameters = sum([np.prod(item.shape) for item in layer.parameters.values() if item is not None]) - total_parameters += parameters - print(f"{layer_name:25s} {str(output_shape)[:15]:>15s} {parameters:>20,}") - else: - print(f"{layer_name:25s} {str(output_shape)[:15]:>15s} {'(unbuilt)':>20}") - if i != len(self.layers) - 1: - print("_" * 65) - print("=" * 65) - if self.built: - print(f"Total params: {total_parameters:,}") - print(f"Trainable params: {total_parameters + other_params:,}") - print(f"Non-trainable params: {other_params:,}") - print("_" * 65) - - def build(self): - self._input_layers = [layer for layer in self.layers if len(layer.input_layers) == 0] - self._output_layers = [layer for layer in self.layers if len(layer.output_layers) == 0] - for layer in self.layers: - if not isinstance(layer, Input): - self.is_initialized = False - # now, let's force the layers to initialize: - inputs = self.build_inputs() - self.predict(inputs) - self.built = True - - def compile(self, optimizer, loss, metrics=None): - for layer in self.layers: - if not isinstance(layer, Input): - self.is_initialized = False - layer.optimizer = OptimizerInitializer(optimizer)() - loss_function = LOSS_FUNCTIONS[loss] - self.loss_function = loss_function() - self.metrics = metrics if metrics is not None else [] - self.build() - - def get_layer_output_shape(self, layer, n=1): - """ - Get the shape of the layer with a dataset - size of n. - """ - if isinstance(layer.n_out, numbers.Number): - shape = (n, layer.n_out) - else: - shape = tuple([n] + list(layer.n_out)) - return shape - - def get_layer_output_array(self, layer): - """ - Get an output array of a layer (dataset, n = 1). - """ - shape = self.get_layer_output_shape(layer) - output = np.ndarray(shape) - return output - - def build_inputs(self): - """ - Build a dataset of dummy inputs. - """ - if self.sequential: - inputs = self.get_layer_output_array(self.layers[0]) - else: - if len(self.get_input_layers()) > 1: - inputs = [self.get_layer_output_array(input) - for input in self._input_layers] - else: - inputs = self.get_layer_output_array(self._input_layers[0]) - return inputs - - def get_weights(self, flat=False): - """ - Get the weights from the model. - """ - array = [] - if flat: - for layer in self.layers: - if layer.has_trainable_params(): - for weight in layer.get_weights(): - if isinstance(weight, numbers.Number): - array.extend(weight) - else: - array.extend(weight.flatten()) - else: - for layer in self.layers: - if layer.has_trainable_params(): - array.extend(layer.get_weights()) - return array - - def copy_weights(self, model): - """ - Copy the weights from another model by layer name. - """ - for layer in model.layers: - weights = layer.get_weights() - self.layer_map[layer.name].set_weights(weights) - - def get_weights_by_name(self): - """ - Copy the weights from another model by layer name. - """ - return {layer.name: layer.get_weights() for layer in self.layers} - - def set_weights(self, weights): - """ - Set the weights in a network. - - Args: - weights: a list of pairs of weights and biases for each layer, - or a single (flat) array of values - """ - if len(weights) > 0 and isinstance(weights[0], numbers.Number): - # Flat - current = 0 - for layer in self.layers: - if layer.has_trainable_params(): - orig = layer.get_weights() - new_weights = [] - for item in orig: - if isinstance(item, numbers.Number): - total = 1 - new_weights.append(item) - else: - total = functools.reduce(operator.mul, item.shape, 1) - w = np.array(weights[current:current + total], dtype=float) - new_weights.append(w.reshape(item.shape)) - current += total - layer.set_weights(new_weights) - else: - i = 0 - for layer in self.layers: - if layer.has_trainable_params(): - orig = layer.get_weights() - count = len(orig) - layer.set_weights(weights[i:i+count]) - i += count - - def format_time(self, seconds): - """ - Format time for easy human reading. - """ - if seconds > 1: - return f"{seconds:.0f}s" - elif seconds * 1000 > 1: - return f"{seconds * 1000:.0f}ms" - else: - return f"{seconds * 1000000:.0f}µs" - - def fit(self, inputs, targets, batch_size=32, epochs=1, verbose="auto", callbacks=None, - initial_epoch=0, shuffle=True): - """ - The training loop for all models. - """ - self.history = History() - self.stop_training = False - verbose = 1 if verbose == "auto" else verbose - callbacks = [] if callbacks is None else callbacks - callbacks.append(self.history) - inputs = np.array(inputs, dtype=float) - targets = np.array(targets, dtype=float) - self.flush_gradients() - for callback in callbacks: - callback.set_model(self) - callback.on_train_begin() - for epoch in range(initial_epoch, epochs): - if self.stop_training: - break - epoch_metric_values = {} - for metric in self.metrics: - if hasattr(metric, "reset_state"): - metric.reset_state() - else: - epoch_metric_values[get_metric_name(metric)] = 0 - - for callback in callbacks: - callback.on_epoch_begin(epoch) - - loss = 0 - total_batches = math.ceil(self.get_length_of_inputs(inputs) / batch_size) - if verbose: - print(f"Epoch {epoch+1}/{epochs}") - for batch, length, batch_data in self.enumerate_batches(inputs, targets, batch_size, shuffle): - start_time = time.monotonic() - batch_loss, batch_metric_values = self.train_batch(batch_data, batch, length, batch_size, callbacks) - loss += batch_loss - for metric in batch_metric_values: - # FIXME: Need to account for uneven batch sizes? - epoch_metric_values[metric] += batch_metric_values[metric] - end_time = time.monotonic() - self.step += length - if verbose: - logs = {} - ftime = self.format_time((end_time - start_time) / length) - for metric in self.metrics: - if hasattr(metric, "result"): - logs[metric.name] = metric.result() - else: - if get_metric_name(metric) in batch_metric_values: - logs[get_metric_name(metric)] = batch_metric_values[get_metric_name(metric)] - metrics = " - ".join(["%s: %.4f" % (metric, logs[metric]) for metric in batch_metric_values]) - if metrics: - metrics = " - " + metrics - # ideally update output here - logs = { - "loss": loss, - } - for metric in self.metrics: - if hasattr(metric, "result"): - logs[metric.name] = metric.result() - else: - if get_metric_name(metric) in epoch_metric_values: - logs[get_metric_name(metric)] = epoch_metric_values[get_metric_name(metric)] / total_batches - if verbose: - metrics = " - ".join(["%s: %.4f" % (metric, logs[metric]) for metric in logs]) - if metrics: - metrics = " - " + metrics - # Until we have output screen formatting; uses the last computed times, metrics - print(f"{batch + 1}/{total_batches} [==============================] - {end_time - start_time:.0f}s {ftime}/step{metrics}") - for callback in callbacks: - callback.on_epoch_end( - epoch, - logs - ) - if self.stop_training: - print("Training stopped early.") - for callback in callbacks: - callback.on_train_end() - return self.history - - def flush_gradients(self): - for layer in self.layers: - if layer.has_trainable_params(): - layer.flush_gradients() - - def enumerate_batches(self, inputs, targets, batch_size, shuffle): - indexes = np.arange(self.get_length_of_inputs(inputs)) - if shuffle: - # In place shuffle - np.random.shuffle(indexes) - current_row = 0 - batch = 0 - while (current_row * batch_size) < self.get_length_of_inputs(inputs): - batch_inputs = self.get_batch_inputs( - inputs, indexes, current_row, batch_size) - batch_targets = self.get_batch_targets( - targets, indexes, current_row, batch_size) - current_row += 1 - yield batch, self.get_length_of_inputs(batch_inputs), (batch_inputs, batch_targets) - batch += 1 - - def get_length_of_inputs(self, inputs): - if len(self.get_input_layers()) == 1: - return len(inputs) - else: - return len(inputs[0]) - - def get_batch_inputs(self, inputs, indexes, current_row, batch_size): - batch_indexes = indexes[current_row:current_row + batch_size] - if len(self.get_input_layers()) == 1: - return inputs[batch_indexes] - else: - return [np.array(inputs[i][batch_indexes]) - for i in range(len(self.get_input_layers()))] - - def get_batch_targets(self, targets, indexes, current_row, batch_size): - batch_indexes = indexes[current_row:current_row + batch_size] - if self.sequential: - # Numpy, one bank: - return targets[batch_indexes] - else: - return [np.array(targets[i][batch_indexes]) - for i in range(len(self.get_output_layers()))] - - def train_batch(self, dataset, batch, length, batch_size, callbacks): - """ - dataset = (inputs, targets) - batch = batch number (eg, step) - length = the actual size of the batch - batch_size = desired size of batch - """ - inputs, targets = dataset - # If the size of this batch is less than desired, scale it? - #scale = length / batch_size - scale = 1.0 - # Use predict to forward the activations, saving - # needed information: - outputs = self.predict(inputs, True) - # Compute the derivative with respect - # to this batch of the dataset: - batch_loss = 0 - batch_metric_values = defaultdict(int) - for callback in callbacks: - callback.on_train_batch_begin(batch) - results = 0 - # FIXME: If batch_size is different from others? Scale it? - if self.sequential: - dY_pred = self.loss_function.grad( - targets, - outputs, - ) - queue = [(self.get_output_layers()[0], dY_pred)] - while len(queue) > 0: - layer, dY_pred = queue.pop(0) - if not isinstance(layer, Input): - dY_pred = layer.backward(dY_pred) - for input_layer in layer.input_layers: - queue.append((input_layer, dY_pred)) - - batch_loss = self.loss_function(targets, outputs) * scale - for metric in self.metrics: - if hasattr(metric, "update_state"): - metric.update_state(targets, outputs) - else: - batch_metric_values[get_metric_name(metric)] = metric(targets, outputs) - else: - for out_n in range(len(self.get_output_layers())): - dY_pred = self.loss_function.grad( - targets[out_n], - outputs[out_n], - ) * scale - queue = [(self.get_output_layers()[out_n], dY_pred)] - while len(queue) > 0: - layer, dY_pred = queue.pop(0) - if not isinstance(layer, Input): - dY_pred = layer.backward(dY_pred) - for input_layer in layer.input_layers: - queue.append((input_layer, dY_pred)) - - batch_loss += self.loss_function(targets[out_n], outputs[out_n]) * scale - for metric in self.metrics: - if hasattr(metric, "update_state"): - metric.update_state(targets[out_n], outputs[out_n]) - else: - batch_metric_values[get_metric_name(metric)] += metric(targets, outputs) - - for callback in callbacks: - logs = {"batch_loss": batch_loss} - logs.update(batch_metric_values) - callback.on_train_batch_end(batch, logs) - self.update(batch_loss) - return batch_loss, batch_metric_values - - def update(self, batch_loss): - """ - Update the weights based on the batch_loss. - The weight delatas were computed in train_batch(). - """ - # FIXME? Need to pass the batch_loss to just the layers - # responsible for this loss (eg, in case of multiple - # output layers) - # FIXME: layers need to be able to accumulate delta changes - for layer in self.layers: - if not isinstance(layer, Input): - layer.update(batch_loss) - - def predict(self, inputs, retain_derived=False): - inputs = np.array(inputs, dtype=float) - results = [] - # First, load the outputs of the input layers: - if self.sequential: - outputs = {self._input_layers[0].name: inputs} - else: - if len(self._input_layers) > 1: - outputs = {self._input_layers[i].name: input for i, input in enumerate(inputs)} - else: - outputs = {self._input_layers[0].name: inputs} - - # Propagate in topological order: - for layer in topological_sort(self.get_input_layers()): - if not isinstance(layer, Input): - inputs = [outputs[in_layer.name] for in_layer in layer.input_layers] - if len(inputs) == 1: - outputs[layer.name] = layer.forward(inputs[0], retain_derived=retain_derived) - else: - outputs[layer.name] = layer.forward(inputs, retain_derived=retain_derived) - - for layer in self.get_output_layers(): - results.append(outputs[layer.name]) - if self.sequential: - return results[0] - else: - return results - -class Sequential(Model): - def __init__(self, layers=None, name="sequential"): - super().__init__(name=name) - self.sequential = True - if layers is not None: - for layer in layers: - self.add(layer) - self.build() - - def add(self, layer): - if layer.name in self.layer_map: - raise AttributeError("duplicate layer name: '%s'" % layer.name) - self.layer_map[layer.name] = layer - if len(self.layers) == 0: - if isinstance(layer, Input): - self.layers.append(layer) - else: - input_layer = Input(input_shape=layer.input_shape) - self.connect(input_layer, layer) - self.layers.append(input_layer) - self.layers.append(layer) - elif isinstance(layer, Activation): - self.layers[-1].act_fn = layer.activation - else: - input_layer = self.layers[-1] - self.connect(input_layer, layer) - self.layers.append(layer) - self.build() diff --git a/aitk/keras/models/vae.py b/aitk/keras/models/vae.py deleted file mode 100644 index e136355..0000000 --- a/aitk/keras/models/vae.py +++ /dev/null @@ -1,453 +0,0 @@ -from time import time -from collections import OrderedDict - -import numpy as np - -from ..losses import VAELoss -from ..utils import minibatch -from ..activations import ReLU, Affine, Sigmoid -from ..layers import Conv2D, Pool2D, Flatten, FullyConnected - - -class BernoulliVAE(object): - def __init__( - self, - T=5, - latent_dim=256, - enc_conv1_pad=0, - enc_conv2_pad=0, - enc_conv1_out_ch=32, - enc_conv2_out_ch=64, - enc_conv1_stride=1, - enc_pool1_stride=2, - enc_conv2_stride=1, - enc_pool2_stride=1, - enc_conv1_kernel_shape=(5, 5), - enc_pool1_kernel_shape=(2, 2), - enc_conv2_kernel_shape=(5, 5), - enc_pool2_kernel_shape=(2, 2), - optimizer="RMSProp(lr=0.0001)", - init="glorot_uniform", - ): - """ - A variational autoencoder (VAE) with 2D convolutional encoder and Bernoulli - input and output units. - - Notes - ----- - The VAE architecture is - - .. code-block:: text - - |-- t_mean ----| - X -> [Encoder] -| |--> [Sampler] -> [Decoder] -> X_recon - |-- t_log_var -| - - where ``[Encoder]`` is - - .. code-block:: text - - Conv1 -> ReLU -> MaxPool1 -> Conv2 -> ReLU -> - MaxPool2 -> Flatten -> FC1 -> ReLU -> FC2 - - ``[Decoder]`` is - - .. code-block:: text - - FC1 -> FC2 -> Sigmoid - - and ``[Sampler]`` draws a sample from the distribution - - .. math:: - - \mathcal{N}(\\text{t_mean}, \exp \left\{\\text{t_log_var}\\right\} I) - - using the reparameterization trick. - - Parameters - ---------- - T : int - The dimension of the variational parameter `t`. Default is 5. - enc_conv1_pad : int - The padding for the first convolutional layer of the encoder. Default is 0. - enc_conv1_stride : int - The stride for the first convolutional layer of the encoder. Default is 1. - enc_conv1_out_ch : int - The number of output channels for the first convolutional layer of - the encoder. Default is 32. - enc_conv1_kernel_shape : tuple - The number of rows and columns in each filter of the first - convolutional layer of the encoder. Default is (5, 5). - enc_pool1_kernel_shape : tuple - The number of rows and columns in the receptive field of the first - max pool layer of the encoder. Default is (2, 3). - enc_pool1_stride : int - The stride for the first MaxPool layer of the encoder. Default is - 2. - enc_conv2_pad : int - The padding for the second convolutional layer of the encoder. - Default is 0. - enc_conv2_out_ch : int - The number of output channels for the second convolutional layer of - the encoder. Default is 64. - enc_conv2_kernel_shape : tuple - The number of rows and columns in each filter of the second - convolutional layer of the encoder. Default is (5, 5). - enc_conv2_stride : int - The stride for the second convolutional layer of the encoder. - Default is 1. - enc_pool2_stride : int - The stride for the second MaxPool layer of the encoder. Default is - 1. - enc_pool2_kernel_shape : tuple - The number of rows and columns in the receptive field of the second - max pool layer of the encoder. Default is (2, 3). - latent_dim : int - The dimension of the output for the first FC layer of the encoder. - Default is 256. - optimizer : str or :doc:`Optimizer ` object or None - The optimization strategy to use when performing gradient updates. - If None, use the :class:`~numpy_ml.neural_nets.optimizers.SGD` - optimizer with default parameters. Default is "RMSProp(lr=0.0001)". - init : str - The weight initialization strategy. Valid entries are - {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform', - 'std_normal', 'trunc_normal'}. Default is 'glorot_uniform'. - """ - self.T = T - self.init = init - self.loss = VAELoss() - self.optimizer = optimizer - self.latent_dim = latent_dim - self.enc_conv1_pad = enc_conv1_pad - self.enc_conv2_pad = enc_conv2_pad - self.enc_conv1_stride = enc_conv1_stride - self.enc_conv1_out_ch = enc_conv1_out_ch - self.enc_pool1_stride = enc_pool1_stride - self.enc_conv2_out_ch = enc_conv2_out_ch - self.enc_conv2_stride = enc_conv2_stride - self.enc_pool2_stride = enc_pool2_stride - self.enc_conv2_kernel_shape = enc_conv2_kernel_shape - self.enc_pool2_kernel_shape = enc_pool2_kernel_shape - self.enc_conv1_kernel_shape = enc_conv1_kernel_shape - self.enc_pool1_kernel_shape = enc_pool1_kernel_shape - - self._init_params() - - def _init_params(self): - self._dv = {} - self._build_encoder() - self._build_decoder() - - def _build_encoder(self): - """ - CNN encoder - - Conv1 -> ReLU -> MaxPool1 -> Conv2 -> ReLU -> MaxPool2 -> - Flatten -> FC1 -> ReLU -> FC2 - """ - self.encoder = OrderedDict() - self.encoder["Conv1"] = Conv2D( - act_fn=ReLU(), - init=self.init, - pad=self.enc_conv1_pad, - optimizer=self.optimizer, - out_ch=self.enc_conv1_out_ch, - stride=self.enc_conv1_stride, - kernel_shape=self.enc_conv1_kernel_shape, - ) - self.encoder["Pool1"] = Pool2D( - mode="max", - optimizer=self.optimizer, - stride=self.enc_pool1_stride, - kernel_shape=self.enc_pool1_kernel_shape, - ) - self.encoder["Conv2"] = Conv2D( - act_fn=ReLU(), - init=self.init, - pad=self.enc_conv2_pad, - optimizer=self.optimizer, - out_ch=self.enc_conv2_out_ch, - stride=self.enc_conv2_stride, - kernel_shape=self.enc_conv2_kernel_shape, - ) - self.encoder["Pool2"] = Pool2D( - mode="max", - optimizer=self.optimizer, - stride=self.enc_pool2_stride, - kernel_shape=self.enc_pool2_kernel_shape, - ) - self.encoder["Flatten3"] = Flatten(optimizer=self.optimizer) - self.encoder["FC4"] = FullyConnected( - n_out=self.latent_dim, act_fn=ReLU(), optimizer=self.optimizer - ) - self.encoder["FC5"] = FullyConnected( - n_out=self.T * 2, - optimizer=self.optimizer, - act_fn=Affine(slope=1, intercept=0), - init=self.init, - ) - - def _build_decoder(self): - """ - MLP decoder - - FC1 -> ReLU -> FC2 -> Sigmoid - """ - self.decoder = OrderedDict() - self.decoder["FC1"] = FullyConnected( - act_fn=ReLU(), - init=self.init, - n_out=self.latent_dim, - optimizer=self.optimizer, - ) - # NB. `n_out` is dependent on the dimensionality of X. we use a - # placeholder for now, and update it within the `forward` method - self.decoder["FC2"] = FullyConnected( - n_out=None, act_fn=Sigmoid(), optimizer=self.optimizer, init=self.init - ) - - @property - def parameters(self): - return { - "components": { - "encoder": {k: v.parameters for k, v in self.encoder.items()}, - "decoder": {k: v.parameters for k, v in self.decoder.items()}, - } - } - - @property - def hyperparameters(self): - return { - "layer": "BernoulliVAE", - "T": self.T, - "init": self.init, - "loss": str(self.loss), - "optimizer": self.optimizer, - "latent_dim": self.latent_dim, - "enc_conv1_pad": self.enc_conv1_pad, - "enc_conv2_pad": self.enc_conv2_pad, - "enc_conv1_in_ch": self.enc_conv1_in_ch, - "enc_conv1_stride": self.enc_conv1_stride, - "enc_conv1_out_ch": self.enc_conv1_out_ch, - "enc_pool1_stride": self.enc_pool1_stride, - "enc_conv2_out_ch": self.enc_conv2_out_ch, - "enc_conv2_stride": self.enc_conv2_stride, - "enc_pool2_stride": self.enc_pool2_stride, - "enc_conv2_kernel_shape": self.enc_conv2_kernel_shape, - "enc_pool2_kernel_shape": self.enc_pool2_kernel_shape, - "enc_conv1_kernel_shape": self.enc_conv1_kernel_shape, - "enc_pool1_kernel_shape": self.enc_pool1_kernel_shape, - "encoder_ids": list(self.encoder.keys()), - "decoder_ids": list(self.decoder.keys()), - "components": { - "encoder": {k: v.hyperparameters for k, v in self.encoder.items()}, - "decoder": {k: v.hyperparameters for k, v in self.decoder.items()}, - }, - } - - @property - def derived_variables(self): - dv = { - "noise": None, - "t_mean": None, - "t_log_var": None, - "dDecoder_FC1_in": None, - "dDecoder_t_mean": None, - "dEncoder_FC5_out": None, - "dDecoder_FC1_out": None, - "dEncoder_FC4_out": None, - "dEncoder_Pool2_out": None, - "dEncoder_Conv2_out": None, - "dEncoder_Pool1_out": None, - "dEncoder_Conv1_out": None, - "dDecoder_t_log_var": None, - "dEncoder_Flatten3_out": None, - "components": { - "encoder": {k: v.derived_variables for k, v in self.encoder.items()}, - "decoder": {k: v.derived_variables for k, v in self.decoder.items()}, - }, - } - dv.update(self._dv) - return dv - - @property - def gradients(self): - return { - "components": { - "encoder": {k: v.gradients for k, v in self.encoder.items()}, - "decoder": {k: v.gradients for k, v in self.decoder.items()}, - } - } - - def _sample(self, t_mean, t_log_var): - """ - Returns a sample from the distribution - - q(t | x) = N(t_mean, diag(exp(t_log_var))) - - using the reparameterization trick. - - Parameters - ---------- - t_mean : :py:class:`ndarray ` of shape `(n_ex, latent_dim)` - Mean of the desired distribution. - t_log_var : :py:class:`ndarray ` of shape `(n_ex, latent_dim)` - Log variance vector of the desired distribution. - - Returns - ------- - samples: :py:class:`ndarray ` of shape `(n_ex, latent_dim)` - """ - noise = np.random.normal(loc=0.0, scale=1.0, size=t_mean.shape) - samples = noise * np.exp(t_log_var) + t_mean - # save sampled noise for backward pass - self._dv["noise"] = noise - return samples - - def forward(self, X_train): - """VAE forward pass""" - if self.decoder["FC2"].n_out is None: - fc2 = self.decoder["FC2"] - self.decoder["FC2"] = fc2.set_params({"n_out": self.N}) - - # assume each image is represented as a flattened row vector, - n_ex, in_rows, N, in_ch = X_train.shape - - # encode the training batch to estimate the mean and variance of the - # variational distribution - out = X_train - for k, v in self.encoder.items(): - out = v.forward(out) - - # extract the mean and log variance of the variational distribution - # q(t | x) from the encoder output - t_mean = out[:, : self.T] - t_log_var = out[:, self.T :] - - # sample t from q(t | x) using reparamterization trick - t = self._sample(t_mean, t_log_var) - - # pass the sampled latent value, t, through the decoder - # to generate the average reconstruction - X_recon = t - for k, v in self.decoder.items(): - X_recon = v.forward(X_recon) - - self._dv["t_mean"] = t_mean - self._dv["t_log_var"] = t_log_var - return X_recon - - def backward(self, X_train, X_recon): - """VAE backward pass""" - n_ex = X_train.shape[0] - D, E = self.decoder, self.encoder - noise = self.derived_variables["noise"] - t_mean = self.derived_variables["t_mean"] - t_log_var = self.derived_variables["t_log_var"] - - # compute gradients through the VAE loss - dY_pred, dLogVar, dMean = self.loss.grad( - X_train.reshape(n_ex, -1), X_recon, t_mean, t_log_var - ) - - # backprop through the decoder - dDecoder_FC1_out = D["FC2"].backward(dY_pred) - dDecoder_FC1_in = D["FC1"].backward(dDecoder_FC1_out) - - # backprop through the sampler - dDecoder_t_log_var = dDecoder_FC1_in * (noise * np.exp(t_log_var)) - dDecoder_t_mean = dDecoder_FC1_in - - # backprop through the encoder - dEncoder_FC5_out = np.hstack( - [dDecoder_t_mean + dMean, dDecoder_t_log_var + dLogVar] - ) - dEncoder_FC4_out = E["FC5"].backward(dEncoder_FC5_out) - dEncoder_Flatten3_out = E["FC4"].backward(dEncoder_FC4_out) - dEncoder_Pool2_out = E["Flatten3"].backward(dEncoder_Flatten3_out) - dEncoder_Conv2_out = E["Pool2"].backward(dEncoder_Pool2_out) - dEncoder_Pool1_out = E["Conv2"].backward(dEncoder_Conv2_out) - dEncoder_Conv1_out = E["Pool1"].backward(dEncoder_Pool1_out) - dX = E["Conv1"].backward(dEncoder_Conv1_out) - - self._dv["dDecoder_t_mean"] = dDecoder_t_mean - self._dv["dDecoder_FC1_in"] = dDecoder_FC1_in - self._dv["dDecoder_FC1_out"] = dDecoder_FC1_out - self._dv["dEncoder_FC5_out"] = dEncoder_FC5_out - self._dv["dEncoder_FC4_out"] = dEncoder_FC4_out - self._dv["dDecoder_t_log_var"] = dDecoder_t_log_var - self._dv["dEncoder_Pool2_out"] = dEncoder_Pool2_out - self._dv["dEncoder_Conv2_out"] = dEncoder_Conv2_out - self._dv["dEncoder_Pool1_out"] = dEncoder_Pool1_out - self._dv["dEncoder_Conv1_out"] = dEncoder_Conv1_out - self._dv["dEncoder_Flatten3_out"] = dEncoder_Flatten3_out - return dX - - def update(self, cur_loss=None): - """Perform gradient updates""" - for k, v in reversed(list(self.decoder.items())): - v.update(cur_loss) - for k, v in reversed(list(self.encoder.items())): - v.update(cur_loss) - self.flush_gradients() - - def flush_gradients(self): - """Reset parameter gradients after update""" - for k, v in self.decoder.items(): - v.flush_gradients() - for k, v in self.encoder.items(): - v.flush_gradients() - - def fit(self, X_train, n_epochs=20, batchsize=128, verbose=True): - """ - Fit the VAE to a training dataset. - - Parameters - ---------- - X_train : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The input volume - n_epochs : int - The maximum number of training epochs to run. Default is 20. - batchsize : int - The desired number of examples in each training batch. Default is 128. - verbose : bool - Print batch information during training. Default is True. - """ - self.verbose = verbose - self.n_epochs = n_epochs - self.batchsize = batchsize - - _, self.in_rows, self.in_cols, self.in_ch = X_train.shape - self.N = self.in_rows * self.in_cols * self.in_ch - - prev_loss = np.inf - for i in range(n_epochs): - loss, estart = 0.0, time() - batch_generator, nb = minibatch(X_train, batchsize, shuffle=True) - - # TODO: parallelize inner loop - for j, b_ix in enumerate(batch_generator): - bsize, bstart = len(b_ix), time() - - X_batch = X_train[b_ix] - X_batch_col = X_train[b_ix].reshape(bsize, -1) - - X_recon = self.forward(X_batch) - t_mean = self.derived_variables["t_mean"] - t_log_var = self.derived_variables["t_log_var"] - - self.backward(X_batch, X_recon) - batch_loss = self.loss(X_batch_col, X_recon, t_mean, t_log_var) - loss += batch_loss - - self.update(batch_loss) - - if self.verbose: - fstr = "\t[Batch {}/{}] Train loss: {:.3f} ({:.1f}s/batch)" - print(fstr.format(j + 1, nb, batch_loss, time() - bstart)) - - loss /= nb - fstr = "[Epoch {}] Avg. loss: {:.3f} Delta: {:.3f} ({:.2f}m/epoch)" - print(fstr.format(i + 1, loss, prev_loss - loss, (time() - estart) / 60.0)) - prev_loss = loss diff --git a/aitk/keras/models/w2v.py b/aitk/keras/models/w2v.py deleted file mode 100644 index b14ae74..0000000 --- a/aitk/keras/models/w2v.py +++ /dev/null @@ -1,451 +0,0 @@ -from time import time - -import numpy as np - -from ..layers import Embedding -from ..losses import NCELoss - -from ..preprocessing.nlp import Vocabulary, tokenize_words -from ..numpy_ml_utils.data_structures import DiscreteSampler - - -class Word2Vec(object): - def __init__( - self, - context_len=5, - min_count=None, - skip_gram=False, - max_tokens=None, - embedding_dim=300, - filter_stopwords=True, - noise_dist_power=0.75, - kernel_initializer="glorot_uniform", - num_negative_samples=64, - optimizer="SGD(lr=0.1)", - ): - """ - A word2vec model supporting both continuous bag of words (CBOW) and - skip-gram architectures, with training via noise contrastive - estimation. - - Parameters - ---------- - context_len : int - The number of words to the left and right of the current word to - use as context during training. Larger values result in more - training examples and thus can lead to higher accuracy at the - expense of additional training time. Default is 5. - min_count : int or None - Minimum number of times a token must occur in order to be included - in vocab. If None, include all tokens from `corpus_fp` in vocab. - Default is None. - skip_gram : bool - Whether to train the skip-gram or CBOW model. The skip-gram model - is trained to predict the target word i given its surrounding - context, ``words[i - context:i]`` and ``words[i + 1:i + 1 + - context]`` as input. Default is False. - max_tokens : int or None - Only add the first `max_tokens` most frequent tokens that occur - more than `min_count` to the vocabulary. If None, add all tokens - that occur more than than `min_count`. Default is None. - embedding_dim : int - The number of dimensions in the final word embeddings. Default is - 300. - filter_stopwords : bool - Whether to remove stopwords before encoding the words in the - corpus. Default is True. - noise_dist_power : float - The power the unigram count is raised to when computing the noise - distribution for negative sampling. A value of 0 corresponds to a - uniform distribution over tokens, and a value of 1 corresponds to a - distribution proportional to the token unigram counts. Default is - 0.75. - kernel_initializer : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is 'glorot_uniform'. - num_negative_samples: int - The number of negative samples to draw from the noise distribution - for each positive training sample. If 0, use the hierarchical - softmax formulation of the model instead. Default is 5. - optimizer : str, :doc:`Optimizer ` object, or None - The optimization strategy to use when performing gradient updates - within the `update` method. If None, use the - :class:`~numpy_ml.neural_nets.optimizers.SGD` optimizer with - default parameters. Default is None. - - Attributes - ---------- - parameters : dict - hyperparameters : dict - derived_variables : dict - gradients : dict - - Notes - ----- - The word2vec model is outlined in in [1]. - - CBOW architecture:: - - w_{t-R} ----| - w_{t-R+1} ----| - ... --> Average --> Embedding layer --> [NCE Layer / HSoftmax] --> P(w_{t} | w_{...}) - w_{t+R-1} ----| - w_{t+R} ----| - - Skip-gram architecture:: - - |--> P(w_{t-R} | w_{t}) - |--> P(w_{t-R+1} | w_{t}) - w_{t} --> Embedding layer --> [NCE Layer / HSoftmax] --| ... - |--> P(w_{t+R-1} | w_{t}) - |--> P(w_{t+R} | w_{t}) - - where :math:`w_{i}` is the one-hot representation of the word at position - `i` within a sentence in the corpus and `R` is the length of the context - window on either side of the target word. - - References - ---------- - .. [1] Mikolov et al. (2013). "Distributed representations of words - and phrases and their compositionality," Proceedings of the 26th - International Conference on Neural Information Processing Systems. - https://arxiv.org/pdf/1310.4546.pdf - """ - self.kernel_initializer = kernel_initializer - self.optimizer = optimizer - self.skip_gram = skip_gram - self.min_count = min_count - self.max_tokens = max_tokens - self.context_len = context_len - self.embedding_dim = embedding_dim - self.filter_stopwords = filter_stopwords - self.noise_dist_power = noise_dist_power - self.num_negative_samples = num_negative_samples - self.special_chars = set(["", "", ""]) - - def _init_params(self): - self._dv = {} - self._build_noise_distribution() - - self.embeddings = Embedding( - kernel_initializer=self.kernel_initializer, - vocab_size=self.vocab_size, - n_out=self.embedding_dim, - optimizer=self.optimizer, - pool=None if self.skip_gram else "mean", - ) - - self.loss = NCELoss( - kernel_initializer=self.kernel_initializer, - optimizer=self.optimizer, - n_classes=self.vocab_size, - subtract_log_label_prob=False, - noise_sampler=self._noise_sampler, - num_negative_samples=self.num_negative_samples, - ) - - @property - def parameters(self): - """Model parameters""" - param = {"components": {"embeddings": {}, "loss": {}}} - if hasattr(self, "embeddings"): - param["components"] = { - "embeddings": self.embeddings.parameters, - "loss": self.loss.parameters, - } - return param - - @property - def hyperparameters(self): - """Model hyperparameters""" - hp = { - "layer": "Word2Vec", - "kernel_initializer": self.kernel_initializer, - "skip_gram": self.skip_gram, - "optimizer": self.optimizer, - "max_tokens": self.max_tokens, - "context_len": self.context_len, - "embedding_dim": self.embedding_dim, - "noise_dist_power": self.noise_dist_power, - "filter_stopwords": self.filter_stopwords, - "num_negative_samples": self.num_negative_samples, - "vocab_size": self.vocab_size if hasattr(self, "vocab_size") else None, - "components": {"embeddings": {}, "loss": {}}, - } - - if hasattr(self, "embeddings"): - hp["components"] = { - "embeddings": self.embeddings.hyperparameters, - "loss": self.loss.hyperparameters, - } - return hp - - @property - def derived_variables(self): - """Variables computed during model operation""" - dv = {"components": {"embeddings": {}, "loss": {}}} - dv.update(self._dv) - - if hasattr(self, "embeddings"): - dv["components"] = { - "embeddings": self.embeddings.derived_variables, - "loss": self.loss.derived_variables, - } - return dv - - @property - def gradients(self): - """Model parameter gradients""" - grad = {"components": {"embeddings": {}, "loss": {}}} - if hasattr(self, "embeddings"): - grad["components"] = { - "embeddings": self.embeddings.gradients, - "loss": self.loss.gradients, - } - return grad - - def forward(self, X, targets, retain_derived=True): - """ - Evaluate the network on a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing a minibatch of `n_ex` examples, each - consisting of `n_in` integer word indices - targets : :py:class:`ndarray ` of shape `(n_ex,)` - Target word index for each example in the minibatch. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If `False`, this suggests the layer - will not be expected to backprop through wrt. this input. Default - True. - - Returns - ------- - loss : float - The loss associated with the current minibatch - y_pred : :py:class:`ndarray ` of shape `(n_ex,)` - The conditional probabilities of the words in `targets` given the - corresponding example / context in `X`. - """ - X_emb = self.embeddings.forward(X, retain_derived=True) - loss, y_pred = self.loss.loss(X_emb, targets.flatten(), retain_derived=True) - return loss, y_pred - - def backward(self): - """ - Compute the gradient of the loss wrt the current network parameters. - """ - dX_emb = self.loss.grad(retain_grads=True, update_params=False) - self.embeddings.backward(dX_emb) - - def update(self, cur_loss=None): - """Perform gradient updates""" - self.loss.update(cur_loss) - self.embeddings.update(cur_loss) - self.flush_gradients() - - def flush_gradients(self): - """Reset parameter gradients after update""" - self.loss.flush_gradients() - self.embeddings.flush_gradients() - - def get_embedding(self, word_ids): - """ - Retrieve the embeddings for a collection of word IDs. - - Parameters - ---------- - word_ids : :py:class:`ndarray ` of shape `(M,)` - An array of word IDs to retrieve embeddings for. - - Returns - ------- - embeddings : :py:class:`ndarray ` of shape `(M, n_out)` - The embedding vectors for each of the `M` word IDs. - """ - if isinstance(word_ids, list): - word_ids = np.array(word_ids) - return self.embeddings.lookup(word_ids) - - def _build_noise_distribution(self): - """ - Construct the noise distribution for use during negative sampling. - - For a word ``w`` in the corpus, the noise distribution is:: - - P_n(w) = Count(w) ** noise_dist_power / Z - - where ``Z`` is a normalizing constant, and `noise_dist_power` is a - hyperparameter of the model. Mikolov et al. report best performance - using a `noise_dist_power` of 0.75. - """ - if not hasattr(self, "vocab"): - raise ValueError("Must call `fit` before constructing noise distribution") - - probs = np.zeros(len(self.vocab)) - power = self.hyperparameters["noise_dist_power"] - - for ix, token in enumerate(self.vocab): - count = token.count - probs[ix] = count ** power - - probs /= np.sum(probs) - self._noise_sampler = DiscreteSampler(probs, log=False, with_replacement=False) - - def _train_epoch(self, corpus_fps, encoding): - total_loss = 0 - batch_generator = self.minibatcher(corpus_fps, encoding) - for ix, (X, target) in enumerate(batch_generator): - loss = self._train_batch(X, target) - total_loss += loss - if self.verbose: - smooth_loss = 0.99 * smooth_loss + 0.01 * loss if ix > 0 else loss - fstr = "[Batch {}] Loss: {:.5f} | Smoothed Loss: {:.5f}" - print(fstr.format(ix + 1, loss, smooth_loss)) - return total_loss / (ix + 1) - - def _train_batch(self, X, target): - loss, _ = self.forward(X, target) - self.backward() - self.update(loss) - return loss - - def minibatcher(self, corpus_fps, encoding): - """ - A minibatch generator for skip-gram and CBOW models. - - Parameters - ---------- - corpus_fps : str or list of strs - The filepath / list of filepaths to the document(s) to be encoded. - Each document is expected to be encoded as newline-separated - string of text, with adjacent tokens separated by a whitespace - character. - encoding : str - Specifies the text encoding for corpus. This value is passed - directly to Python's `open` builtin. Common entries are either - 'utf-8' (no header byte), or 'utf-8-sig' (header byte). - - Yields - ------ - X : list of length `batchsize` or :py:class:`ndarray ` of shape (`batchsize`, `n_in`) - The context IDs for a minibatch of `batchsize` examples. If - ``self.skip_gram`` is False, `X` will be a ragged list consisting - of `batchsize` variable-length lists. If ``self.skip_gram`` is - `True`, all sublists will be of the same length (`n_in`) and `X` - will be returned as a :py:class:`ndarray ` of shape (`batchsize`, `n_in`). - target : :py:class:`ndarray ` of shape (`batchsize`, 1) - The target IDs associated with each example in `X` - """ - batchsize = self.batchsize - X_mb, target_mb, mb_ready = [], [], False - - for d_ix, doc_fp in enumerate(corpus_fps): - with open(doc_fp, "r", encoding=encoding) as doc: - for line in doc: - words = tokenize_words( - line, lowercase=True, filter_stopwords=self.filter_stopwords - ) - word_ixs = self.vocab.words_to_indices( - self.vocab.filter(words, unk=False) - ) - for word_loc, word in enumerate(word_ixs): - # since more distant words are usually less related to - # the target word, we downweight them by sampling from - # them less frequently during training. - R = np.random.randint(1, self.context_len) - left = word_ixs[max(word_loc - R, 0) : word_loc] - right = word_ixs[word_loc + 1 : word_loc + 1 + R] - context = left + right - - if len(context) == 0: - continue - - # in the skip-gram architecture we use each of the - # surrounding context to predict `word` / avoid - # predicting negative samples - if self.skip_gram: - X_mb.extend([word] * len(context)) - target_mb.extend(context) - mb_ready = len(target_mb) >= batchsize - - # in the CBOW architecture we use the average of the - # context embeddings to predict the target `word` / avoid - # predicting the negative samples - else: - context = np.array(context) - X_mb.append(context) # X_mb will be a ragged array - target_mb.append(word) - mb_ready = len(X_mb) == batchsize - - if mb_ready: - mb_ready = False - X_batch, target_batch = X_mb.copy(), target_mb.copy() - X_mb, target_mb = [], [] - if self.skip_gram: - X_batch = np.array(X_batch)[:, None] - target_batch = np.array(target_batch)[:, None] - yield X_batch, target_batch - - # if we've reached the end of our final document and there are - # remaining examples, yield the stragglers as a partial minibatch - if len(X_mb) > 0: - if self.skip_gram: - X_mb = np.array(X_mb)[:, None] - target_mb = np.array(target_mb)[:, None] - yield X_mb, target_mb - - def fit( - self, corpus_fps, encoding="utf-8-sig", n_epochs=20, batchsize=128, verbose=True - ): - """ - Learn word2vec embeddings for the examples in `X_train`. - - Parameters - ---------- - corpus_fps : str or list of strs - The filepath / list of filepaths to the document(s) to be encoded. - Each document is expected to be encoded as newline-separated - string of text, with adjacent tokens separated by a whitespace - character. - encoding : str - Specifies the text encoding for corpus. Common entries are either - 'utf-8' (no header byte), or 'utf-8-sig' (header byte). Default - value is 'utf-8-sig'. - n_epochs : int - The maximum number of training epochs to run. Default is 20. - batchsize : int - The desired number of examples in each training batch. Default is - 128. - verbose : bool - Print batch information during training. Default is True. - """ - self.verbose = verbose - self.n_epochs = n_epochs - self.batchsize = batchsize - - self.vocab = Vocabulary( - lowercase=True, - min_count=self.min_count, - max_tokens=self.max_tokens, - filter_stopwords=self.filter_stopwords, - ) - self.vocab.fit(corpus_fps, encoding=encoding) - self.vocab_size = len(self.vocab) - - # ignore special characters when training the model - for sp in self.special_chars: - self.vocab.counts[sp] = 0 - - # now that we know our vocabulary size, we can initialize the embeddings - self._init_params() - - prev_loss = np.inf - for i in range(n_epochs): - loss, estart = 0.0, time() - loss = self._train_epoch(corpus_fps, encoding) - - fstr = "[Epoch {}] Avg. loss: {:.3f} Delta: {:.3f} ({:.2f}m/epoch)" - print(fstr.format(i + 1, loss, prev_loss - loss, (time() - estart) / 60.0)) - prev_loss = loss diff --git a/aitk/keras/models/wgan_gp.py b/aitk/keras/models/wgan_gp.py deleted file mode 100644 index a48e194..0000000 --- a/aitk/keras/models/wgan_gp.py +++ /dev/null @@ -1,528 +0,0 @@ -from time import time -from collections import OrderedDict - -import numpy as np - -from ..utils import minibatch -from ..layers import Dense -from ..losses import WGAN_GPLoss - - -class WGAN_GP(object): - """ - A Wasserstein generative adversarial network (WGAN) architecture with - gradient penalty (GP). - - Notes - ----- - In contrast to a regular WGAN, WGAN-GP uses gradient penalty on the - generator rather than weight clipping to encourage the 1-Lipschitz - constraint: - - .. math:: - - | \\text{Generator}(\mathbf{x}_1) - \\text{Generator}(\mathbf{x}_2) | - \leq |\mathbf{x}_1 - \mathbf{x}_2 | \ \ \ \ \\forall \mathbf{x}_1, \mathbf{x}_2 - - In other words, the generator must have input gradients with a norm of at - most 1 under the :math:`\mathbf{X}_{real}` and :math:`\mathbf{X}_{fake}` - data distributions. - - To enforce this constraint, WGAN-GP penalizes the model if the generator - gradient norm moves away from a target norm of 1. See - :class:`~numpy_ml.neural_nets.losses.WGAN_GPLoss` for more details. - - In contrast to a standard WGAN, WGAN-GP avoids using BatchNorm in the - critic, as correlation between samples in a batch can impact the stability - of the gradient penalty. - - WGAP-GP architecture: - - .. code-block:: text - - X_real ------------------------| - >---> [Critic] --> Y_out - Z --> [Generator] --> X_fake --| - - where ``[Generator]`` is - - .. code-block:: text - - FC1 -> ReLU -> FC2 -> ReLU -> FC3 -> ReLU -> FC4 - - and ``[Critic]`` is - - .. code-block:: text - - FC1 -> ReLU -> FC2 -> ReLU -> FC3 -> ReLU -> FC4 - - and - - .. math:: - - Z \sim \mathcal{N}(0, 1) - """ - - def __init__( - self, - g_hidden=512, - kernel_initializer="he_uniform", - optimizer="RMSProp(lr=0.0001)", - debug=False, - ): - """ - Wasserstein generative adversarial network with gradient penalty. - - Parameters - ---------- - g_hidden : int - The number of units in the critic and generator hidden layers. - Default is 512. - kernel_initializer : str - The weight initialization strategy. Valid entries are - {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform', - 'std_normal', 'trunc_normal'}. Default is "he_uniform". - optimizer : str or :doc:`Optimizer ` object or None - The optimization strategy to use when performing gradient updates. - If None, use the :class:`~numpy_ml.neural_nets.optimizers.SGD` - optimizer with default parameters. Default is "RMSProp(lr=0.0001)". - debug : bool - Whether to store additional intermediate output within - ``self.derived_variables``. Default is False. - """ - self.kernel_initializer = kernel_initializer - self.debug = debug - self.g_hidden = g_hidden - self.optimizer = optimizer - - self.lambda_ = None - self.n_steps = None - self.batchsize = None - - self.is_initialized = False - - def _init_params(self): - self._dv = {} - self._gr = {} - self._build_critic() - self._build_generator() - self.is_initialized = True - - def _build_generator(self): - """ - FC1 -> ReLU -> FC2 -> ReLU -> FC3 -> ReLU -> FC4 - """ - self.generator = OrderedDict() - self.generator["FC1"] = Dense( - self.g_hidden, act_fn="ReLU", optimizer=self.optimizer, kernel_initializer=self.kernel_initializer - ) - self.generator["FC2"] = Dense( - self.g_hidden, act_fn="ReLU", optimizer=self.optimizer, kernel_initializer=self.kernel_initializer - ) - self.generator["FC3"] = Dense( - self.g_hidden, act_fn="ReLU", optimizer=self.optimizer, kernel_initializer=self.kernel_initializer - ) - self.generator["FC4"] = Dense( - self.n_feats, - act_fn="Affine(slope=1, intercept=0)", - optimizer=self.optimizer, - kernel_initializer=self.kernel_initializer, - ) - - def _build_critic(self): - """ - FC1 -> ReLU -> FC2 -> ReLU -> FC3 -> ReLU -> FC4 - """ - self.critic = OrderedDict() - self.critic["FC1"] = Dense( - self.g_hidden, act_fn="ReLU", optimizer=self.optimizer, kernel_initializer=self.kernel_initializer - ) - self.critic["FC2"] = Dense( - self.g_hidden, act_fn="ReLU", optimizer=self.optimizer, kernel_initializer=self.kernel_initializer - ) - self.critic["FC3"] = Dense( - self.g_hidden, act_fn="ReLU", optimizer=self.optimizer, kernel_initializer=self.kernel_initializer - ) - self.critic["FC4"] = Dense( - 1, - act_fn="Affine(slope=1, intercept=0)", - optimizer=self.optimizer, - kernel_initializer=self.kernel_initializer, - ) - - @property - def hyperparameters(self): - return { - "kernel_initializer": self.kernel_initializer, - "lambda_": self.lambda_, - "g_hidden": self.g_hidden, - "n_steps": self.n_steps, - "optimizer": self.optimizer, - "batchsize": self.batchsize, - "c_updates_per_epoch": self.c_updates_per_epoch, - "components": { - "critic": {k: v.hyperparameters for k, v in self.critic.items()}, - "generator": {k: v.hyperparameters for k, v in self.generator.items()}, - }, - } - - @property - def parameters(self): - return { - "components": { - "critic": {k: v.parameters for k, v in self.critic.items()}, - "generator": {k: v.parameters for k, v in self.generator.items()}, - } - } - - @property - def derived_variables(self): - C = self.critic.items() - G = self.generator.items() - dv = { - "components": { - "critic": {k: v.derived_variables for k, v in C}, - "generator": {k: v.derived_variables for k, v in G}, - } - } - dv.update(self._dv) - return dv - - @property - def gradients(self): - grads = { - "dC_Y_fake": None, - "dC_Y_real": None, - "dG_Y_fake": None, - "dC_gradInterp": None, - "components": { - "critic": {k: v.gradients for k, v in self.critic.items()}, - "generator": {k: v.gradients for k, v in self.generator.items()}, - }, - } - grads.update(self._gr) - return grads - - def forward(self, X, module, retain_derived=True): - """ - Perform the forward pass for either the generator or the critic. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(batchsize, \*)` - Input data - module : {'C' or 'G'} - Whether to perform the forward pass for the critic ('C') or for the - generator ('G'). - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - out : :py:class:`ndarray ` of shape `(batchsize, \*)` - The output of the final layer of the module. - Xs : dict - A dictionary with layer ids as keys and values corresponding to the - input to each intermediate layer during the forward pass. Useful - during debugging. - """ - if module == "G": - mod = self.generator - elif module == "C": - mod = self.critic - else: - raise ValueError("Unrecognized module name: {}".format(module)) - - Xs = {} - out, rd = X, retain_derived - for k, v in mod.items(): - Xs[k] = out - out = v.forward(out, retain_derived=rd) - return out, Xs - - def backward(self, grad, module, retain_grads=True): - """ - Perform the backward pass for either the generator or the critic. - - Parameters - ---------- - grad : :py:class:`ndarray ` of shape `(batchsize, \*)` or list of arrays - Gradient of the loss with respect to module output(s). - module : {'C' or 'G'} - Whether to perform the backward pass for the critic ('C') or for the - generator ('G'). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is True. - - Returns - ------- - out : :py:class:`ndarray ` of shape `(batchsize, \*)` - The gradient of the loss with respect to the module input. - dXs : dict - A dictionary with layer ids as keys and values corresponding to the - input to each intermediate layer during the backward pass. Useful - during debugging. - """ - if module == "G": - mod = self.generator - elif module == "C": - mod = self.critic - else: - raise ValueError("Unrecognized module name: {}".format(module)) - - dXs = {} - out, rg = grad, retain_grads - for k, v in reversed(list(mod.items())): - dXs[k] = out - out = v.backward(out, retain_grads=rg) - return out, dXs - - def _dGradInterp(self, dLdGradInterp, dYi_outs): - """ - Compute the gradient penalty's contribution to the critic loss and - update the parameter gradients accordingly. - - Parameters - ---------- - dLdGradInterp : :py:class:`ndarray ` of shape `(batchsize, critic_in_dim)` - Gradient of `Y_interp` with respect to `X_interp`. - dYi_outs : dict - The intermediate outputs generated during the backward pass when - computing `dLdGradInterp`. - """ - dy = dLdGradInterp - for k, v in self.critic.items(): - X = v.X[-1] # layer input during forward pass - dy, dW, dB = v._bwd2(dy, X, dYi_outs[k][2]) - self.critic[k].gradients["W"] += dW - self.critic[k].gradients["b"] += dB - - def update_critic(self, X_real): - """ - Compute parameter gradients for the critic on a single minibatch. - - Parameters - ---------- - X_real : :py:class:`ndarray ` of shape `(batchsize, n_feats)` - Input data. - - Returns - ------- - C_loss : float - The critic loss on the current data. - """ - self.flush_gradients("C") - - n_ex = X_real.shape[0] - noise = np.random.randn(*X_real.shape) - - # generate and score the real and fake data - X_fake, Xf_outs = self.forward(noise, "G") - Y_real, Yr_outs = self.forward(X_real, "C") - Y_fake, Yf_outs = self.forward(X_fake, "C") - - # sample a random point on the linear interpolation between real and - # fake data and compute its score - alpha = np.random.rand(n_ex, 1) - X_interp = alpha * X_real + (1 - alpha) * X_fake - Y_interp, Yi_outs = self.forward(X_interp, "C") - - # compute the gradient of Y_interp wrt. X_interp - # Note that we don't save intermediate gradients here since this is not - # the real backward pass - dLdy = [0, 0, np.ones_like(Y_interp)] - (_, _, gradInterp), dYi_outs = self.backward(dLdy, "C", retain_grads=False) - - # calculate critic loss and differentiate with respect to each term - C_loss = self.loss(Y_fake, "C", Y_real, gradInterp) - dY_real, dY_fake, dGrad_interp = self.loss.grad(Y_fake, "C", Y_real, gradInterp) - - # compute `dY_real` and `dY_fake` contributions to critic loss, update - # param gradients accordingly - self.backward([dY_real, dY_fake, 0], "C") - - # compute `gradInterp`'s contribution to the critic loss, updating - # param gradients accordingly - self._dGradInterp(dGrad_interp, dYi_outs) - - # cache intermediate vars for the generator update - self._dv["alpha"] = alpha - self._dv["Y_fake"] = Y_fake - - # log additional intermediate values for debugging - if self.debug: - self._dv["G_fwd_X_fake"] = {} - self._dv["C_fwd_Y_real"] = {} - self._dv["C_fwd_Y_fake"] = {} - self._dv["C_fwd_Y_interp"] = {} - - N = len(self.critic.keys()) - N2 = len(self.generator.keys()) - - for i in range(N2): - self._dv["G_fwd_X_fake"]["FC" + str(i)] = Xf_outs["FC" + str(i + 1)] - - for i in range(N): - self._dv["C_fwd_Y_real"]["FC" + str(i)] = Yr_outs["FC" + str(i + 1)] - self._dv["C_fwd_Y_fake"]["FC" + str(i)] = Yf_outs["FC" + str(i + 1)] - self._dv["C_fwd_Y_interp"]["FC" + str(i)] = Yi_outs["FC" + str(i + 1)] - - self._dv["C_fwd_Y_real"]["FC" + str(N)] = Y_real - self._dv["C_fwd_Y_fake"]["FC" + str(N)] = Y_fake - self._dv["G_fwd_X_fake"]["FC" + str(N2)] = X_fake - self._dv["C_fwd_Y_interp"]["FC" + str(N)] = Y_interp - self._dv["C_dY_interp_wrt"] = {k: v[2] for k, v in dYi_outs.items()} - - self._dv["noise"] = noise - self._dv["X_fake"] = X_fake - self._dv["X_real"] = X_real - self._dv["Y_real"] = Y_real - self._dv["Y_fake"] = Y_fake - self._dv["C_loss"] = C_loss - self._dv["dY_real"] = dY_real - self._dv["dC_Y_fake"] = dY_fake - self._dv["X_interp"] = X_interp - self._dv["Y_interp"] = Y_interp - self._dv["gradInterp"] = gradInterp - self._dv["dGrad_interp"] = dGrad_interp - - return C_loss - - def update_generator(self, X_shape): - """ - Compute parameter gradients for the generator on a single minibatch. - - Parameters - ---------- - X_shape : tuple of `(batchsize, n_feats)` - Shape for the input batch. - - Returns - ------- - G_loss : float - The generator loss on the fake data (generated during the critic - update) - """ - self.flush_gradients("G") - Y_fake = self.derived_variables["Y_fake"] - - n_ex, _ = Y_fake.shape - G_loss = -Y_fake.mean() - dG_loss = -np.ones_like(Y_fake) / n_ex - self.backward(dG_loss, "G") - - if self.debug: - self._dv["G_loss"] = G_loss - self._dv["dG_Y_fake"] = dG_loss - - return G_loss - - def flush_gradients(self, module): - """Reset parameter gradients to 0 after an update.""" - if module == "G": - mod = self.generator - elif module == "C": - mod = self.critic - else: - raise ValueError("Unrecognized module name: {}".format(module)) - - for k, v in mod.items(): - v.flush_gradients() - - def update(self, module, module_loss=None): - """Perform gradient updates and flush gradients upon completion""" - if module == "G": - mod = self.generator - elif module == "C": - mod = self.critic - else: - raise ValueError("Unrecognized module name: {}".format(module)) - - for k, v in reversed(list(mod.items())): - v.update(module_loss) - self.flush_gradients(module) - - def fit( - self, - X_real, - lambda_, - n_steps=1000, - batchsize=128, - c_updates_per_epoch=5, - verbose=True, - ): - """ - Fit WGAN_GP on a training dataset. - - Parameters - ---------- - X_real : :py:class:`ndarray ` of shape `(n_ex, n_feats)` - Training dataset - lambda_ : float - Gradient penalty coefficient for the critic loss - n_steps : int - The maximum number of generator updates to perform. Default is - 1000. - batchsize : int - Number of examples to use in each training minibatch. Default is - 128. - c_updates_per_epoch : int - The number of critic updates to perform at each generator update. - verbose : bool - Print loss values after each update. If False, only print loss - every 100 steps. Default is True. - """ - self.lambda_ = lambda_ - self.verbose = verbose - self.n_steps = n_steps - self.batchsize = batchsize - self.c_updates_per_epoch = c_updates_per_epoch - - # adjust output of the generator to match the dimensionality of X - if not self.is_initialized: - self.n_feats = X_real.shape[1] - self._init_params() - - # (re-)initialize loss - prev_C, prev_G = np.inf, np.inf - self.loss = WGAN_GPLoss(lambda_=self.lambda_) - - # training loop - NC, NG = self.c_updates_per_epoch, self.n_steps - for i in range(NG): - estart = time() - batch_generator, _ = minibatch(X_real, batchsize, shuffle=False) - - for j, b_ix in zip(range(NC), batch_generator): - bstart = time() - X_batch = X_real[b_ix] - C_loss = self.update_critic(X_batch) - - # for testing, don't perform gradient update so we can inspect each grad - if not self.debug: - self.update("C", C_loss) - - if self.verbose: - fstr = "\t[Critic batch {}] Critic loss: {:.3f} {:.3f}∆ ({:.1f}s/batch)" - print(fstr.format(j + 1, C_loss, prev_C - C_loss, time() - bstart)) - prev_C = C_loss - - # generator update - G_loss = self.update_generator(X_batch.shape) - - # for testing, don't perform gradient update so we can inspect each grad - if not self.debug: - self.update("G", G_loss) - - if i % 99 == 0: - fstr = "[Epoch {}] Gen. loss: {:.3f} Critic loss: {:.3f}" - print(fstr.format(i + 1, G_loss, C_loss)) - - elif self.verbose: - fstr = "[Epoch {}] Gen. loss: {:.3f} {:.3f}∆ ({:.1f}s/epoch)" - print(fstr.format(i + 1, G_loss, prev_G - G_loss, time() - estart)) - prev_G = G_loss diff --git a/aitk/keras/modules/README.md b/aitk/keras/modules/README.md deleted file mode 100644 index 8590b6b..0000000 --- a/aitk/keras/modules/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Modules - -The `modules.py` module implements common multi-layer blocks that appear across -many modern deep networks. It includes: - -- Bidirectional LSTMs ([Schuster & Paliwal, 1997](https://pdfs.semanticscholar.org/4b80/89bc9b49f84de43acc2eb8900035f7d492b2.pdf)) -- ResNet-style "identity" (i.e., `same`-convolution) residual blocks ([He et al., 2015](https://arxiv.org/pdf/1512.03385.pdf)) -- ResNet-style "convolutional" (i.e., parametric) residual blocks ([He et al., 2015](https://arxiv.org/pdf/1512.03385.pdf)) -- WaveNet-style residual block with dilated causal convolutions ([van den Oord et al., 2016](https://arxiv.org/pdf/1609.03499.pdf)) -- Transformer-style multi-headed dot-product attention ([Vaswani et al., 2017](https://arxiv.org/pdf/1706.03762.pdf)) diff --git a/aitk/keras/modules/__init__.py b/aitk/keras/modules/__init__.py deleted file mode 100644 index 270dceb..0000000 --- a/aitk/keras/modules/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .modules import * diff --git a/aitk/keras/modules/modules.py b/aitk/keras/modules/modules.py deleted file mode 100644 index cc31ea7..0000000 --- a/aitk/keras/modules/modules.py +++ /dev/null @@ -1,1427 +0,0 @@ -from abc import ABC, abstractmethod - -import re -import numpy as np - -from ..wrappers import Dropout -from ..utils import calc_pad_dims_2D -from ..activations import Tanh, Sigmoid, ReLU, LeakyReLU, Affine -from ..layers import ( - DotProductAttention, - Dense, - BatchNorm2D, - Conv1D, - Conv2D, - Multiply, - LSTMCell, - Add, -) - - -class ModuleBase(ABC): - def __init__(self): - self.X = None - self.trainable = True - - super().__init__() - - @abstractmethod - def _init_params(self, **kwargs): - raise NotImplementedError - - @abstractmethod - def forward(self, z, **kwargs): - raise NotImplementedError - - @abstractmethod - def backward(self, out, **kwargs): - raise NotImplementedError - - @property - def components(self): - comps = [] - for c in self.hyperparameters["component_ids"]: - if hasattr(self, c): - comps.append(getattr(self, c)) - return comps - - def freeze(self): - self.trainable = False - for c in self.components: - c.freeze() - - def unfreeze(self): - self.trainable = True - for c in self.components: - c.unfreeze() - - def update(self, cur_loss=None): - assert self.trainable, "Layer is frozen" - for c in self.components: - c.update(cur_loss) - self.flush_gradients() - - def flush_gradients(self): - assert self.trainable, "Layer is frozen" - - self.X = [] - self._dv = {} - for c in self.components: - for k, v in c.derived_variables.items(): - c.derived_variables[k] = None - - for k, v in c.gradients.items(): - c.gradients[k] = np.zeros_like(v) - - def set_params(self, summary_dict): - cids = self.hyperparameters["component_ids"] - for k, v in summary_dict["parameters"].items(): - if k == "components": - for c, cd in summary_dict["parameters"][k].items(): - if c in cids: - getattr(self, c).set_params(cd) - - elif k in self.parameters: - self.parameters[k] = v - - for k, v in summary_dict["hyperparameters"].items(): - if k == "components": - for c, cd in summary_dict["hyperparameters"][k].items(): - if c in cids: - getattr(self, c).set_params(cd) - - if k in self.hyperparameters: - if k == "act_fn" and v == "ReLU": - self.hyperparameters[k] = ReLU() - elif v == "act_fn" and v == "Sigmoid": - self.hyperparameters[k] = Sigmoid() - elif v == "act_fn" and v == "Tanh": - self.hyperparameters[k] = Tanh() - elif v == "act_fn" and "Affine" in v: - r = r"Affine\(slope=(.*), intercept=(.*)\)" - slope, intercept = re.match(r, v).groups() - self.hyperparameters[k] = Affine(float(slope), float(intercept)) - elif v == "act_fn" and "Leaky ReLU" in v: - r = r"Leaky ReLU\(alpha=(.*)\)" - alpha = re.match(r, v).groups()[0] - self.hyperparameters[k] = LeakyReLU(float(alpha)) - else: - self.hyperparameters[k] = v - - def summary(self): - return { - "parameters": self.parameters, - "layer": self.hyperparameters["layer"], - "hyperparameters": self.hyperparameters, - } - - -class WavenetResidualModule(ModuleBase): - def __init__( - self, - ch_residual, - ch_dilation, - dilation, - kernel_width, - optimizer=None, - init="glorot_uniform", - ): - """ - A WaveNet-like residual block with causal dilated convolutions. - - .. code-block:: text - - *Skip path in* >-------------------------------------------> + ---> *Skip path out* - Causal |--> Tanh --| | - *Main |--> Dilated Conv1D -| * --> 1x1 Conv1D --| - path >--| |--> Sigm --| | - in* |-------------------------------------------------> + ---> *Main path out* - *Residual path* - - On the final block, the output of the skip path is further processed to - produce the network predictions. - - References - ---------- - .. [1] van den Oord et al. (2016). "Wavenet: a generative model for raw - audio". https://arxiv.org/pdf/1609.03499.pdf - - Parameters - ---------- - ch_residual : int - The number of output channels for the 1x1 - :class:`~numpy_ml.neural_nets.layers.Conv1D` layer in the main path. - ch_dilation : int - The number of output channels for the causal dilated - :class:`~numpy_ml.neural_nets.layers.Conv1D` layer in the main path. - dilation : int - The dilation rate for the causal dilated - :class:`~numpy_ml.neural_nets.layers.Conv1D` layer in the main path. - kernel_width : int - The width of the causal dilated - :class:`~numpy_ml.neural_nets.layers.Conv1D` kernel in the main - path. - init : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is 'glorot_uniform'. - optimizer : str or :doc:`Optimizer ` object or None - The optimization strategy to use when performing gradient updates - within the :meth:`update` method. If None, use the - :class:`~numpy_ml.neural_nets.optimizers.SGD` optimizer with default - parameters. Default is None. - """ - super().__init__() - - self.init = init - self.dilation = dilation - self.optimizer = optimizer - self.ch_residual = ch_residual - self.ch_dilation = ch_dilation - self.kernel_width = kernel_width - - self._init_params() - - def _init_params(self): - self._dv = {} - - self.conv_dilation = Conv1D( - stride=1, - pad="causal", - init=self.init, - kernel_width=2, - dilation=self.dilation, - out_ch=self.ch_dilation, - optimizer=self.optimizer, - act_fn=Affine(slope=1, intercept=0), - ) - - self.tanh = Tanh() - self.sigm = Sigmoid() - self.multiply_gate = Multiply(act_fn=Affine(slope=1, intercept=0)) - - self.conv_1x1 = Conv1D( - stride=1, - pad="same", - dilation=0, - init=self.init, - kernel_width=1, - out_ch=self.ch_residual, - optimizer=self.optimizer, - act_fn=Affine(slope=1, intercept=0), - ) - - self.add_residual = Add(act_fn=Affine(slope=1, intercept=0)) - self.add_skip = Add(act_fn=Affine(slope=1, intercept=0)) - - @property - def parameters(self): - """A dictionary of the module parameters.""" - return { - "components": { - "conv_1x1": self.conv_1x1.parameters, - "add_skip": self.add_skip.parameters, - "add_residual": self.add_residual.parameters, - "conv_dilation": self.conv_dilation.parameters, - "multiply_gate": self.multiply_gate.parameters, - } - } - - @property - def hyperparameters(self): - """A dictionary of the module hyperparameters""" - return { - "layer": "WavenetResidualModule", - "init": self.init, - "dilation": self.dilation, - "optimizer": self.optimizer, - "ch_residual": self.ch_residual, - "ch_dilation": self.ch_dilation, - "kernel_width": self.kernel_width, - "component_ids": [ - "conv_1x1", - "add_skip", - "add_residual", - "conv_dilation", - "multiply_gate", - ], - "components": { - "conv_1x1": self.conv_1x1.hyperparameters, - "add_skip": self.add_skip.hyperparameters, - "add_residual": self.add_residual.hyperparameters, - "conv_dilation": self.conv_dilation.hyperparameters, - "multiply_gate": self.multiply_gate.hyperparameters, - }, - } - - @property - def derived_variables(self): - """A dictionary of intermediate values computed during the - forward/backward passes.""" - dv = { - "conv_1x1_out": None, - "conv_dilation_out": None, - "multiply_gate_out": None, - "components": { - "conv_1x1": self.conv_1x1.derived_variables, - "add_skip": self.add_skip.derived_variables, - "add_residual": self.add_residual.derived_variables, - "conv_dilation": self.conv_dilation.derived_variables, - "multiply_gate": self.multiply_gate.derived_variables, - }, - } - dv.update(self._dv) - return dv - - @property - def gradients(self): - """A dictionary of the module parameter gradients.""" - return { - "components": { - "conv_1x1": self.conv_1x1.gradients, - "add_skip": self.add_skip.gradients, - "add_residual": self.add_residual.gradients, - "conv_dilation": self.conv_dilation.gradients, - "multiply_gate": self.multiply_gate.gradients, - } - } - - def forward(self, X_main, X_skip=None): - """ - Compute the module output on a single minibatch. - - Parameters - ---------- - X_main : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The input volume consisting of `n_ex` examples, each with dimension - (`in_rows`, `in_cols`, `in_ch`). - X_skip : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)`, or None - The output of the preceding skip-connection if this is not the - first module in the network. - - Returns - ------- - Y_main : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The output of the main pathway. - Y_skip : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The output of the skip-connection pathway. - """ - self.X_main, self.X_skip = X_main, X_skip - conv_dilation_out = self.conv_dilation.forward(X_main) - - tanh_gate = self.tanh.fn(conv_dilation_out) - sigm_gate = self.sigm.fn(conv_dilation_out) - - multiply_gate_out = self.multiply_gate.forward([tanh_gate, sigm_gate]) - conv_1x1_out = self.conv_1x1.forward(multiply_gate_out) - - # if this is the first wavenet block, initialize the "previous" skip - # connection sum to 0 - self.X_skip = np.zeros_like(conv_1x1_out) if X_skip is None else X_skip - - Y_skip = self.add_skip.forward([X_skip, conv_1x1_out]) - Y_main = self.add_residual.forward([X_main, conv_1x1_out]) - - self._dv["tanh_out"] = tanh_gate - self._dv["sigm_out"] = sigm_gate - self._dv["conv_dilation_out"] = conv_dilation_out - self._dv["multiply_gate_out"] = multiply_gate_out - self._dv["conv_1x1_out"] = conv_1x1_out - return Y_main, Y_skip - - def backward(self, dY_skip, dY_main=None): - dX_skip, dConv_1x1_out = self.add_skip.backward(dY_skip) - - # if this is the last wavenet block, dY_main will be None. if not, - # calculate the error contribution from dY_main and add it to the - # contribution from the skip path - dX_main = np.zeros_like(self.X_main) - if dY_main is not None: - dX_main, dConv_1x1_main = self.add_residual.backward(dY_main) - dConv_1x1_out += dConv_1x1_main - - dMultiply_out = self.conv_1x1.backward(dConv_1x1_out) - dTanh_out, dSigm_out = self.multiply_gate.backward(dMultiply_out) - - conv_dilation_out = self.derived_variables["conv_dilation_out"] - dTanh_in = dTanh_out * self.tanh.grad(conv_dilation_out) - dSigm_in = dSigm_out * self.sigm.grad(conv_dilation_out) - dDilation_out = dTanh_in + dSigm_in - - conv_back = self.conv_dilation.backward(dDilation_out) - dX_main += conv_back - - self._dv["dLdTanh"] = dTanh_out - self._dv["dLdSigmoid"] = dSigm_out - self._dv["dLdConv_1x1"] = dConv_1x1_out - self._dv["dLdMultiply"] = dMultiply_out - self._dv["dLdConv_dilation"] = dDilation_out - return dX_main, dX_skip - - -class SkipConnectionIdentityModule(ModuleBase): - def __init__( - self, - out_ch, - kernel_shape1, - kernel_shape2, - stride1=1, - stride2=1, - act_fn=None, - epsilon=1e-5, - momentum=0.9, - optimizer=None, - init="glorot_uniform", - ): - """ - A ResNet-like "identity" shortcut module. - - Notes - ----- - The identity module enforces `same` padding during each convolution to - ensure module output has same dims as its input. - - .. code-block:: text - - X -> Conv2D -> Act_fn -> BatchNorm2D -> Conv2D -> BatchNorm2D -> + -> Act_fn - \______________________________________________________________/ - - References - ---------- - .. [1] He et al. (2015). "Deep residual learning for image - recognition." https://arxiv.org/pdf/1512.03385.pdf - - Parameters - ---------- - out_ch : int - The number of filters/kernels to compute in the first convolutional - layer. - kernel_shape1 : 2-tuple - The dimension of a single 2D filter/kernel in the first - convolutional layer. - kernel_shape2 : 2-tuple - The dimension of a single 2D filter/kernel in the second - convolutional layer. - stride1 : int - The stride/hop of the convolution kernels in the first - convolutional layer. Default is 1. - stride2 : int - The stride/hop of the convolution kernels in the second - convolutional layer. Default is 1. - act_fn : :doc:`Activation ` object or None - The activation function for computing Y[t]. If None, use the - identity :math:`f(x) = x` by default. Default is None. - epsilon : float - A small smoothing constant to use during - :class:`~numpy_ml.neural_nets.layers.BatchNorm2D` computation to - avoid divide-by-zero errors. Default is 1e-5. - momentum : float - The momentum term for the running mean/running std calculations in - the :class:`~numpy_ml.neural_nets.layers.BatchNorm2D` layers. The - closer this is to 1, the less weight will be given to the mean/std - of the current batch (i.e., higher smoothing). Default is 0.9. - optimizer : str or :doc:`Optimizer ` object or None - The optimization strategy to use when performing gradient updates - within the :meth:`update` method. If None, use the - :class:`~numpy_ml.neural_nets.optimizers.SGD` optimizer with - default parameters. Default is None. - init : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is 'glorot_uniform'. - """ - super().__init__() - - self.init = init - self.in_ch = None - self.out_ch = out_ch - self.epsilon = epsilon - self.stride1 = stride1 - self.stride2 = stride2 - self.optimizer = optimizer - self.momentum = momentum - self.kernel_shape1 = kernel_shape1 - self.kernel_shape2 = kernel_shape2 - self.act_fn = Affine(slope=1, intercept=0) if act_fn is None else act_fn - - self._init_params() - - def _init_params(self): - self._dv = {} - - self.conv1 = Conv2D( - pad="same", - init=self.init, - out_ch=self.out_ch, - act_fn=self.act_fn, - stride=self.stride1, - optimizer=self.optimizer, - kernel_shape=self.kernel_shape1, - ) - # we can't initialize `conv2` without X's dimensions; see `forward` - # for further details - self.batchnorm1 = BatchNorm2D(epsilon=self.epsilon, momentum=self.momentum) - self.batchnorm2 = BatchNorm2D(epsilon=self.epsilon, momentum=self.momentum) - self.add3 = Add(self.act_fn) - - def _init_conv2(self): - self.conv2 = Conv2D( - pad="same", - init=self.init, - out_ch=self.in_ch, - stride=self.stride2, - optimizer=self.optimizer, - kernel_shape=self.kernel_shape2, - act_fn=Affine(slope=1, intercept=0), - ) - - @property - def parameters(self): - """A dictionary of the module parameters.""" - return { - "components": { - "add3": self.add3.parameters, - "conv1": self.conv1.parameters, - "conv2": self.conv2.parameters, - "batchnorm1": self.batchnorm1.parameters, - "batchnorm2": self.batchnorm2.parameters, - } - } - - @property - def hyperparameters(self): - """A dictionary of the module hyperparameters.""" - return { - "layer": "SkipConnectionIdentityModule", - "init": self.init, - "in_ch": self.in_ch, - "out_ch": self.out_ch, - "epsilon": self.epsilon, - "stride1": self.stride1, - "stride2": self.stride2, - "momentum": self.momentum, - "optimizer": self.optimizer, - "act_fn": str(self.act_fn), - "kernel_shape1": self.kernel_shape1, - "kernel_shape2": self.kernel_shape2, - "component_ids": ["conv1", "batchnorm1", "conv2", "batchnorm2", "add3"], - "components": { - "add3": self.add3.hyperparameters, - "conv1": self.conv1.hyperparameters, - "conv2": self.conv2.hyperparameters, - "batchnorm1": self.batchnorm1.hyperparameters, - "batchnorm2": self.batchnorm2.hyperparameters, - }, - } - - @property - def derived_variables(self): - """A dictionary of intermediate values computed during the - forward/backward passes.""" - dv = { - "conv1_out": None, - "conv2_out": None, - "batchnorm1_out": None, - "batchnorm2_out": None, - "components": { - "add3": self.add3.derived_variables, - "conv1": self.conv1.derived_variables, - "conv2": self.conv2.derived_variables, - "batchnorm1": self.batchnorm1.derived_variables, - "batchnorm2": self.batchnorm2.derived_variables, - }, - } - dv.update(self._dv) - return dv - - @property - def gradients(self): - """A dictionary of the accumulated module parameter gradients.""" - return { - "components": { - "add3": self.add3.gradients, - "conv1": self.conv1.gradients, - "conv2": self.conv2.gradients, - "batchnorm1": self.batchnorm1.gradients, - "batchnorm2": self.batchnorm2.gradients, - } - } - - def forward(self, X, retain_derived=True): - """ - Compute the module output given input volume `X`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape (n_ex, in_rows, in_cols, in_ch) - The input volume consisting of `n_ex` examples, each with dimension - (`in_rows`, `in_cols`, `in_ch`). - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape (n_ex, out_rows, out_cols, out_ch) - The module output volume. - """ - if not hasattr(self, "conv2"): - self.in_ch = X.shape[3] - self._init_conv2() - - conv1_out = self.conv1.forward(X, retain_derived) - bn1_out = self.batchnorm1.forward(conv1_out, retain_derived) - conv2_out = self.conv2.forward(bn1_out, retain_derived) - bn2_out = self.batchnorm2.forward(conv2_out, retain_derived) - Y = self.add3.forward([X, bn2_out], retain_derived) - - if retain_derived: - self._dv["conv1_out"] = conv1_out - self._dv["conv2_out"] = conv2_out - self._dv["batchnorm1_out"] = bn1_out - self._dv["batchnorm2_out"] = bn2_out - return Y - - def backward(self, dLdY, retain_grads=True): - """ - Compute the gradient of the loss with respect to the layer parameters. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape (`n_ex, out_rows, out_cols, out_ch`) or list of arrays - The gradient(s) of the loss with respect to the module output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape (n_ex, in_rows, in_cols, in_ch) - The gradient of the loss with respect to the module input volume. - """ - dX, dBn2_out = self.add3.backward(dLdY, retain_grads) - dConv2_out = self.batchnorm2.backward(dBn2_out, retain_grads) - dBn1_out = self.conv2.backward(dConv2_out, retain_grads) - dConv1_out = self.batchnorm1.backward(dBn1_out, retain_grads) - dX += self.conv1.backward(dConv1_out, retain_grads) - - self._dv["dLdAdd3_X"] = dX - self._dv["dLdBn2"] = dBn2_out - self._dv["dLdBn1"] = dBn1_out - self._dv["dLdConv2"] = dConv2_out - self._dv["dLdConv1"] = dConv1_out - return dX - - -class SkipConnectionConvModule(ModuleBase): - def __init__( - self, - out_ch1, - out_ch2, - kernel_shape1, - kernel_shape2, - kernel_shape_skip, - pad1=0, - pad2=0, - stride1=1, - stride2=1, - act_fn=None, - epsilon=1e-5, - momentum=0.9, - stride_skip=1, - optimizer=None, - init="glorot_uniform", - ): - """ - A ResNet-like "convolution" shortcut module. - - Notes - ----- - In contrast to :class:`SkipConnectionIdentityModule`, the additional - `conv2d_skip` and `batchnorm_skip` layers in the shortcut path allow - adjusting the dimensions of `X` to match the output of the main set of - convolutions. - - .. code-block:: text - - X -> Conv2D -> Act_fn -> BatchNorm2D -> Conv2D -> BatchNorm2D -> + -> Act_fn - \_____________________ Conv2D -> Batchnorm2D __________________/ - - References - ---------- - .. [1] He et al. (2015). "Deep residual learning for image - recognition." https://arxiv.org/pdf/1512.03385.pdf - - Parameters - ---------- - out_ch1 : int - The number of filters/kernels to compute in the first convolutional - layer. - out_ch2 : int - The number of filters/kernels to compute in the second - convolutional layer. - kernel_shape1 : 2-tuple - The dimension of a single 2D filter/kernel in the first - convolutional layer. - kernel_shape2 : 2-tuple - The dimension of a single 2D filter/kernel in the second - convolutional layer. - kernel_shape_skip : 2-tuple - The dimension of a single 2D filter/kernel in the "skip" - convolutional layer. - stride1 : int - The stride/hop of the convolution kernels in the first - convolutional layer. Default is 1. - stride2 : int - The stride/hop of the convolution kernels in the second - convolutional layer. Default is 1. - stride_skip : int - The stride/hop of the convolution kernels in the "skip" - convolutional layer. Default is 1. - pad1 : int, tuple, or 'same' - The number of rows/columns of 0's to pad the input to the first - convolutional layer with. Default is 0. - pad2 : int, tuple, or 'same' - The number of rows/columns of 0's to pad the input to the second - convolutional layer with. Default is 0. - act_fn : :doc:`Activation ` object or None - The activation function for computing ``Y[t]``. If None, use the - identity :math:`f(x) = x` by default. Default is None. - epsilon : float - A small smoothing constant to use during - :class:`~numpy_ml.neural_nets.layers.BatchNorm2D` computation to - avoid divide-by-zero errors. Default is 1e-5. - momentum : float - The momentum term for the running mean/running std calculations in - the :class:`~numpy_ml.neural_nets.layers.BatchNorm2D` layers. The - closer this is to 1, the less weight will be given to the mean/std - of the current batch (i.e., higher smoothing). Default is 0.9. - init : str - The weight initialization strategy. Valid entries are - {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'}. - optimizer : str or :doc:`Optimizer ` object - The optimization strategy to use when performing gradient updates - within the :class:`update` method. If None, use the - :class:`~numpy_ml.neural_nets.optimizers.SGD` optimizer with - default parameters. Default is None. - """ - super().__init__() - - self.init = init - self.pad1 = pad1 - self.pad2 = pad2 - self.in_ch = None - self.out_ch1 = out_ch1 - self.out_ch2 = out_ch2 - self.epsilon = epsilon - self.stride1 = stride1 - self.stride2 = stride2 - self.momentum = momentum - self.optimizer = optimizer - self.stride_skip = stride_skip - self.kernel_shape1 = kernel_shape1 - self.kernel_shape2 = kernel_shape2 - self.kernel_shape_skip = kernel_shape_skip - self.act_fn = Affine(slope=1, intercept=0) if act_fn is None else act_fn - - self._init_params() - - def _init_params(self, X=None): - self._dv = {} - self.conv1 = Conv2D( - pad=self.pad1, - init=self.init, - act_fn=self.act_fn, - out_ch=self.out_ch1, - stride=self.stride1, - optimizer=self.optimizer, - kernel_shape=self.kernel_shape1, - ) - self.conv2 = Conv2D( - pad=self.pad2, - init=self.init, - out_ch=self.out_ch2, - stride=self.stride2, - optimizer=self.optimizer, - kernel_shape=self.kernel_shape2, - act_fn=Affine(slope=1, intercept=0), - ) - # we can't initialize `conv_skip` without X's dimensions; see `forward` - # for further details - self.batchnorm1 = BatchNorm2D(epsilon=self.epsilon, momentum=self.momentum) - self.batchnorm2 = BatchNorm2D(epsilon=self.epsilon, momentum=self.momentum) - self.batchnorm_skip = BatchNorm2D(epsilon=self.epsilon, momentum=self.momentum) - self.add3 = Add(self.act_fn) - - def _calc_skip_padding(self, X): - pads = [] - for p in [self.pad1, self.pad2]: - if isinstance(p, int): - pads.append((p, p, p, p)) - elif isinstance(p, tuple) and len(p) == 2: - pads.append((p[0], p[0], p[1], p[1])) - self.pad1, self.pad2 = pads - - # compute the dimensions of the convolution1 output - s1 = self.stride1 - fr1, fc1 = self.kernel_shape1 - _, in_rows, in_cols, _ = X.shape - pr11, pr12, pc11, pc12 = self.pad1 - - out_rows1 = np.floor(1 + (in_rows + pr11 + pr12 - fr1) / s1).astype(int) - out_cols1 = np.floor(1 + (in_cols + pc11 + pc12 - fc1) / s1).astype(int) - - # compute the dimensions of the convolution2 output - s2 = self.stride2 - fr2, fc2 = self.kernel_shape2 - pr21, pr22, pc21, pc22 = self.pad2 - - out_rows2 = np.floor(1 + (out_rows1 + pr21 + pr22 - fr2) / s2).astype(int) - out_cols2 = np.floor(1 + (out_cols1 + pc21 + pc22 - fc2) / s2).astype(int) - - # finally, compute the appropriate padding dims for the skip convolution - desired_dims = (out_rows2, out_cols2) - self.pad_skip = calc_pad_dims_2D( - X.shape, - desired_dims, - stride=self.stride_skip, - kernel_shape=self.kernel_shape_skip, - ) - - def _init_conv_skip(self, X): - self._calc_skip_padding(X) - self.conv_skip = Conv2D( - init=self.init, - pad=self.pad_skip, - out_ch=self.out_ch2, - stride=self.stride_skip, - kernel_shape=self.kernel_shape_skip, - act_fn=Affine(slope=1, intercept=0), - optimizer=self.optimizer, - ) - - @property - def parameters(self): - """A dictionary of the module parameters.""" - return { - "components": { - "add3": self.add3.parameters, - "conv1": self.conv1.parameters, - "conv2": self.conv2.parameters, - "conv_skip": self.conv_skip.parameters - if hasattr(self, "conv_skip") - else None, - "batchnorm1": self.batchnorm1.parameters, - "batchnorm2": self.batchnorm2.parameters, - "batchnorm_skip": self.batchnorm_skip.parameters, - } - } - - @property - def hyperparameters(self): - """A dictionary of the module hyperparameters.""" - return { - "layer": "SkipConnectionConvModule", - "init": self.init, - "pad1": self.pad1, - "pad2": self.pad2, - "in_ch": self.in_ch, - "out_ch1": self.out_ch1, - "out_ch2": self.out_ch2, - "epsilon": self.epsilon, - "stride1": self.stride1, - "stride2": self.stride2, - "momentum": self.momentum, - "act_fn": str(self.act_fn), - "stride_skip": self.stride_skip, - "kernel_shape1": self.kernel_shape1, - "kernel_shape2": self.kernel_shape2, - "kernel_shape_skip": self.kernel_shape_skip, - "pad_skip": self.pad_skip if hasattr(self, "pad_skip") else None, - "component_ids": [ - "add3", - "conv1", - "conv2", - "conv_skip", - "batchnorm1", - "batchnorm2", - "batchnorm_skip", - ], - "components": { - "add3": self.add3.hyperparameters, - "conv1": self.conv1.hyperparameters, - "conv2": self.conv2.hyperparameters, - "conv_skip": self.conv_skip.hyperparameters - if hasattr(self, "conv_skip") - else None, - "batchnorm1": self.batchnorm1.hyperparameters, - "batchnorm2": self.batchnorm2.hyperparameters, - "batchnorm_skip": self.batchnorm_skip.hyperparameters, - }, - } - - @property - def derived_variables(self): - """A dictionary of intermediate values computed during the - forward/backward passes.""" - dv = { - "conv1_out": None, - "conv2_out": None, - "conv_skip_out": None, - "batchnorm1_out": None, - "batchnorm2_out": None, - "batchnorm_skip_out": None, - "components": { - "add3": self.add3.derived_variables, - "conv1": self.conv1.derived_variables, - "conv2": self.conv2.derived_variables, - "conv_skip": self.conv_skip.derived_variables - if hasattr(self, "conv_skip") - else None, - "batchnorm1": self.batchnorm1.derived_variables, - "batchnorm2": self.batchnorm2.derived_variables, - "batchnorm_skip": self.batchnorm_skip.derived_variables, - }, - } - dv.update(self._dv) - return dv - - @property - def gradients(self): - """A dictionary of the accumulated module parameter gradients.""" - return { - "components": { - "add3": self.add3.gradients, - "conv1": self.conv1.gradients, - "conv2": self.conv2.gradients, - "conv_skip": self.conv_skip.gradients - if hasattr(self, "conv_skip") - else None, - "batchnorm1": self.batchnorm1.gradients, - "batchnorm2": self.batchnorm2.gradients, - "batchnorm_skip": self.batchnorm_skip.gradients, - } - } - - def forward(self, X, retain_derived=True): - """ - Compute the layer output given input volume `X`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The input volume consisting of `n_ex` examples, each with dimension - (`in_rows`, `in_cols`, `in_ch`). - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The module output volume. - """ - # now that we have the input dims for X we can initialize the proper - # padding in the `conv_skip` layer - if not hasattr(self, "conv_skip"): - self._init_conv_skip(X) - self.in_ch = X.shape[3] - - conv1_out = self.conv1.forward(X, retain_derived) - bn1_out = self.batchnorm1.forward(conv1_out, retain_derived) - conv2_out = self.conv2.forward(bn1_out, retain_derived) - bn2_out = self.batchnorm2.forward(conv2_out, retain_derived) - conv_skip_out = self.conv_skip.forward(X, retain_derived) - bn_skip_out = self.batchnorm_skip.forward(conv_skip_out, retain_derived) - Y = self.add3.forward([bn_skip_out, bn2_out], retain_derived) - - if retain_derived: - self._dv["conv1_out"] = conv1_out - self._dv["conv2_out"] = conv2_out - self._dv["batchnorm1_out"] = bn1_out - self._dv["batchnorm2_out"] = bn2_out - self._dv["conv_skip_out"] = conv_skip_out - self._dv["batchnorm_skip_out"] = bn_skip_out - return Y - - def backward(self, dLdY, retain_grads=True): - """ - Compute the gradient of the loss with respect to the module parameters. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - or list of arrays - The gradient(s) of the loss with respect to the module output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dX : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The gradient of the loss with respect to the module input volume. - """ - dBnskip_out, dBn2_out = self.add3.backward(dLdY) - dConvskip_out = self.batchnorm_skip.backward(dBnskip_out) - dX = self.conv_skip.backward(dConvskip_out) - - dConv2_out = self.batchnorm2.backward(dBn2_out) - dBn1_out = self.conv2.backward(dConv2_out) - dConv1_out = self.batchnorm1.backward(dBn1_out) - dX += self.conv1.backward(dConv1_out) - - if retain_grads: - self._dv["dLdAdd3_X"] = dX - self._dv["dLdBn1"] = dBn1_out - self._dv["dLdBn2"] = dBn2_out - self._dv["dLdConv1"] = dConv1_out - self._dv["dLdConv2"] = dConv2_out - self._dv["dLdBnSkip"] = dBnskip_out - self._dv["dLdConvSkip"] = dConvskip_out - return dX - - -class BidirectionalLSTM(ModuleBase): - def __init__( - self, - n_out, - act_fn=None, - gate_fn=None, - merge_mode="concat", - init="glorot_uniform", - optimizer=None, - ): - """ - A single bidirectional long short-term memory (LSTM) layer. - - Parameters - ---------- - n_out : int - The dimension of a single hidden state / output on a given timestep - act_fn : :doc:`Activation ` object or None - The activation function for computing ``A[t]``. If not specified, - use :class:`~numpy_ml.neural_nets.activations.Tanh` by default. - gate_fn : :doc:`Activation ` object or None - The gate function for computing the update, forget, and output - gates. If not specified, use - :class:`~numpy_ml.neural_nets.activations.Sigmoid` by default. - merge_mode : {"sum", "multiply", "concat", "average"} - Mode by which outputs of the forward and backward LSTMs will be - combined. Default is 'concat'. - optimizer : str or :doc:`Optimizer ` object or None - The optimization strategy to use when performing gradient updates - within the `update` method. If None, use the - :class:`~numpy_ml.neural_nets.optimizers.SGD` optimizer with - default parameters. Default is None. - init : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is 'glorot_uniform'. - """ - super().__init__() - - self.init = init - self.n_in = None - self.n_out = n_out - self.optimizer = optimizer - self.merge_mode = merge_mode - self.act_fn = Tanh() if act_fn is None else act_fn - self.gate_fn = Sigmoid() if gate_fn is None else gate_fn - self._init_params() - - def _init_params(self): - self.cell_fwd = LSTMCell( - init=self.init, - n_out=self.n_out, - act_fn=self.act_fn, - gate_fn=self.gate_fn, - optimizer=self.optimizer, - ) - self.cell_bwd = LSTMCell( - init=self.init, - n_out=self.n_out, - act_fn=self.act_fn, - gate_fn=self.gate_fn, - optimizer=self.optimizer, - ) - - def forward(self, X): - """ - Run a forward pass across all timesteps in the input. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in, n_t)` - Input consisting of `n_ex` examples each of dimensionality `n_in` - and extending for `n_t` timesteps. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_out, n_t)` - The value of the hidden state for each of the `n_ex` examples - across each of the `n_t` timesteps. - """ - Y_fwd, Y_bwd, Y = [], [], [] - n_ex, self.n_in, n_t = X.shape - - # forward LSTM - for t in range(n_t): - yt, ct = self.cell_fwd.forward(X[:, :, t]) - Y_fwd.append(yt) - - # backward LSTM - for t in reversed(range(n_t)): - yt, ct = self.cell_bwd.forward(X[:, :, t]) - Y_bwd.insert(0, yt) - - # merge forward and backward states - for t in range(n_t): - if self.merge_mode == "concat": - Y.append(np.concatenate([Y_fwd[t], Y_bwd[t]], axis=1)) - elif self.merge_mode == "sum": - Y.append(Y_fwd[t] + Y_bwd[t]) - elif self.merge_mode == "average": - Y.append((Y_fwd[t] + Y_bwd[t]) / 2) - elif self.merge_mode == "multiply": - Y.append(Y_fwd[t] * Y_bwd[t]) - - self.Y_fwd, self.Y_bwd = Y_fwd, Y_bwd - return np.dstack(Y) - - def backward(self, dLdA): - """ - Run a backward pass across all timesteps in the input. - - Parameters - ---------- - dLdA : :py:class:`ndarray ` of shape `(n_ex, n_out, n_t)` - The gradient of the loss with respect to the layer output for each - of the `n_ex` examples across all `n_t` timesteps. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape `(n_ex, n_in, n_t)` - The value of the hidden state for each of the `n_ex` examples - across each of the `n_t` timesteps. - """ - assert self.trainable, "Layer is frozen" - - n_ex, n_out, n_t = dLdA.shape - dLdX_f, dLdX_b, dLdX = [], [], [] - - # forward LSTM - for t in reversed(range(n_t)): - if self.merge_mode == "concat": - dLdXt_f = self.cell_fwd.backward(dLdA[:, : self.n_out, t]) - elif self.merge_mode == "sum": - dLdXt_f = self.cell_fwd.backward(dLdA[:, :, t]) - elif self.merge_mode == "multiplty": - dLdXt_f = self.cell_fwd.backward(dLdA[:, :, t] * self.Y_bwd[t]) - elif self.merge_mode == "average": - dLdXt_f = self.cell_fwd.backward(dLdA[:, :, t] * 0.5) - dLdX_f.insert(0, dLdXt_f) - - # backward LSTM - for t in range(n_t): - if self.merge_mode == "concat": - dLdXt_b = self.cell_bwd.backward(dLdA[:, self.n_out :, t]) - elif self.merge_mode == "sum": - dLdXt_b = self.cell_bwd.backward(dLdA[:, :, t]) - elif self.merge_mode == "multiplty": - dLdXt_b = self.cell_bwd.backward(dLdA[:, :, t] * self.Y_fwd[t]) - elif self.merge_mode == "average": - dLdXt_b = self.cell_bwd.backward(dLdA[:, :, t] * 0.5) - dLdX_b.append(dLdXt_b) - - for t in range(n_t): - dLdX.append(dLdX_f[t] + dLdX_b[t]) - - return np.dstack(dLdX) - - @property - def derived_variables(self): - """A dictionary of intermediate values computed during the - forward/backward passes.""" - return { - "components": { - "cell_fwd": self.cell_fwd.derived_variables, - "cell_bwd": self.cell_bwd.derived_variables, - } - } - - @property - def gradients(self): - """A dictionary of the accumulated module parameter gradients.""" - return { - "components": { - "cell_fwd": self.cell_fwd.gradients, - "cell_bwd": self.cell_bwd.gradients, - } - } - - @property - def parameters(self): - """A dictionary of the module parameters.""" - return { - "components": { - "cell_fwd": self.cell_fwd.parameters, - "cell_bwd": self.cell_bwd.parameters, - } - } - - @property - def hyperparameters(self): - """A dictionary of the module hyperparameters.""" - return { - "layer": "BidirectionalLSTM", - "init": self.init, - "n_in": self.n_in, - "n_out": self.n_out, - "act_fn": str(self.act_fn), - "optimizer": self.optimizer, - "merge_mode": self.merge_mode, - "component_ids": ["cell_fwd", "cell_bwd"], - "components": { - "cell_fwd": self.cell_fwd.hyperparameters, - "cell_bwd": self.cell_bwd.hyperparameters, - }, - } - - -class MultiHeadedAttentionModule(ModuleBase): - def __init__(self, n_heads=8, dropout_p=0, init="glorot_uniform", optimizer=None): - """ - A mutli-headed attention module. - - Notes - ----- - Multi-head attention allows a model to jointly attend to information from - different representation subspaces at different positions. With a - single head, this information would get averaged away when the - attention weights are combined with the value - - .. math:: - - \\text{MultiHead}(\mathbf{Q}, \mathbf{K}, \mathbf{V}) - = [\\text{head}_1; ...; \\text{head}_h] \\mathbf{W}^{(O)} - - where - - .. math:: - - \\text{head}_i = \\text{SDP_attention}( - \mathbf{Q W}_i^{(Q)}, \mathbf{K W}_i^{(K)}, \mathbf{V W}_i^{(V)}) - - and the projection weights are parameter matrices: - - .. math:: - - \mathbf{W}_i^{(Q)} &\in - \mathbb{R}^{(\\text{kqv_dim} \ \\times \ \\text{latent_dim})} \\\\ - \mathbf{W}_i^{(K)} &\in - \mathbb{R}^{(\\text{kqv_dim} \ \\times \ \\text{latent_dim})} \\\\ - \mathbf{W}_i^{(V)} &\in - \mathbb{R}^{(\\text{kqv_dim} \ \\times \ \\text{latent_dim})} \\\\ - \mathbf{W}^{(O)} &\in - \mathbb{R}^{(\\text{n_heads} \cdot \\text{latent_dim} \ \\times \ \\text{kqv_dim})} - - Importantly, the current module explicitly assumes that - - .. math:: - - \\text{kqv_dim} = \\text{dim(query)} = \\text{dim(keys)} = \\text{dim(values)} - - and that - - .. math:: - - \\text{latent_dim} = \\text{kqv_dim / n_heads} - - **[MH Attention Head h]**: - - .. code-block:: text - - K --> W_h^(K) ------\\ - V --> W_h^(V) ------- > DP_Attention --> head_h - Q --> W_h^(Q) ------/ - - The full **[MultiHeadedAttentionModule]** then becomes - - .. code-block:: text - - ----------------- - K --> | [Attn Head 1] | --> head_1 --\\ - V --> | [Attn Head 2] | --> head_2 --\\ - Q --> | ... | ... --> Concat --> W^(O) --> MH_out - | [Attn Head Z] | --> head_Z --/ - ----------------- - - Due to the reduced dimension of each head, the total computational cost - is similar to that of a single attention head with full (i.e., kqv_dim) - dimensionality. - - Parameters - ---------- - n_heads : int - The number of simultaneous attention heads to use. Note that the - larger `n_heads`, the smaller the dimensionality of any single - head, since ``latent_dim = kqv_dim / n_heads``. Default is 8. - dropout_p : float in [0, 1) - The dropout propbability during training, applied to the output of - the softmax in each dot-product attention head. If 0, no dropout is - applied. Default is 0. - init : {'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform'} - The weight initialization strategy. Default is 'glorot_uniform'. - optimizer : str, :doc:`Optimizer ` object, or None - The optimization strategy to use when performing gradient updates - within the :meth:`update` method. If None, use the - :class:`~numpy_ml.neural_nets.optimizers.SGD` optimizer with default - parameters. Default is None. - """ - self.init = init - self.kqv_dim = None - self.projections = {} - self.n_heads = n_heads - self.optimizer = optimizer - self.dropout_p = dropout_p - self.is_initialized = False - - def _init_params(self): - self._dv = {} - - # assume dim(keys) = dim(query) = dim(values) - assert self.kqv_dim % self.n_heads == 0 - self.latent_dim = self.kqv_dim // self.n_heads - - self.attention = DotProductAttention(scale=True, dropout_p=self.dropout_p) - self.projections = { - k: Dropout( - FullyConnected( - init=self.init, - n_out=self.kqv_dim, - optimizer=self.optimizer, - act_fn="Affine(slope=1, intercept=0)", - ), - self.dropout_p, - ) - for k in ["Q", "K", "V", "O"] - } - - self.is_initialized = True - - def forward(self, Q, K, V): - if not self.is_initialized: - self.kqv_dim = Q.shape[-1] - self._init_params() - - # project queries, keys, and values into the `latent_dim`-dimensional subspace - n_ex = Q.shape[0] - for k, x in zip(["Q", "K", "V"], [Q, K, V]): - proj = self.projections[k].forward(x) - proj = proj.reshape(n_ex, -1, self.n_heads, self.latent_dim).swapaxes(1, 2) - self._dv["{}_proj".format(k)] = proj - - dv = self.derived_variables - Q_proj, K_proj, V_proj = dv["Q_proj"], dv["K_proj"], dv["V_proj"] - - # apply scaled dot-product attention to the projected vectors - attn = self.attention - attn_out = attn.forward(Q_proj, K_proj, V_proj) - self._dv["attention_weights"] = attn.derived_variables["attention_weights"] - - # concatenate the different heads using `reshape` to create an - # `kqv_dim`-dim vector - attn_out = attn_out.swapaxes(1, 2).reshape(n_ex, self.kqv_dim) - self._dv["attention_out"] = attn_out.reshape(n_ex, -1, self.kqv_dim) - - # apply the final output projection - Y = self.projections["O"].forward(attn_out) - Y = Y.reshape(n_ex, -1, self.kqv_dim) - return Y - - def backward(self, dLdy): - n_ex = dLdy.shape[0] - dLdy = dLdy.reshape(n_ex, self.kqv_dim) - dLdX = self.projections["O"].backward(dLdy) - dLdX = dLdX.reshape(n_ex, self.n_heads, -1, self.latent_dim) - - dLdQ_proj, dLdK_proj, dLdV_proj = self.attention.backward(dLdX) - - self._dv["dQ_proj"] = dLdQ_proj - self._dv["dK_proj"] = dLdK_proj - self._dv["dV_proj"] = dLdV_proj - - dLdQ_proj = dLdQ_proj.reshape(n_ex, self.kqv_dim) - dLdK_proj = dLdK_proj.reshape(n_ex, self.kqv_dim) - dLdV_proj = dLdV_proj.reshape(n_ex, self.kqv_dim) - - dLdQ = self.projections["Q"].backward(dLdQ_proj) - dLdK = self.projections["K"].backward(dLdK_proj) - dLdV = self.projections["V"].backward(dLdV_proj) - return dLdQ, dLdK, dLdV - - @property - def derived_variables(self): - """A dictionary of intermediate values computed during the - forward/backward passes.""" - dv = { - "Q_proj": None, - "K_proj": None, - "V_proj": None, - "components": { - "Q": self.projections["Q"].derived_variables, - "K": self.projections["K"].derived_variables, - "V": self.projections["V"].derived_variables, - "O": self.projections["O"].derived_variables, - "attention": self.attention.derived_variables, - }, - } - dv.update(self._dv) - return dv - - @property - def gradients(self): - """A dictionary of the accumulated module parameter gradients.""" - return { - "components": { - "Q": self.projections["Q"].gradients, - "K": self.projections["K"].gradients, - "V": self.projections["V"].gradients, - "O": self.projections["O"].gradients, - "attention": self.attention.gradients, - } - } - - @property - def parameters(self): - """A dictionary of the module parameters.""" - return { - "components": { - "Q": self.projections["Q"].parameters, - "K": self.projections["K"].parameters, - "V": self.projections["V"].parameters, - "O": self.projections["O"].parameters, - "attention": self.attention.parameters, - } - } - - @property - def hyperparameters(self): - """A dictionary of the module hyperparameters.""" - return { - "layer": "MultiHeadedAttentionModule", - "init": self.init, - "kqv_dim": self.kqv_dim, - "latent_dim": self.latent_dim, - "n_heads": self.n_heads, - "dropout_p": self.dropout_p, - "component_ids": ["attention", "Q", "K", "V", "O"], - "components": { - "Q": self.projections["Q"].hyperparameters, - "K": self.projections["K"].hyperparameters, - "V": self.projections["V"].hyperparameters, - "O": self.projections["O"].hyperparameters, - "attention": self.attention.hyperparameters, - }, - } diff --git a/aitk/keras/numpy_ml_utils/README.md b/aitk/keras/numpy_ml_utils/README.md deleted file mode 100644 index a50b58b..0000000 --- a/aitk/keras/numpy_ml_utils/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Utilities - -The utilities module implements a number of useful functions and objects that -power other ML algorithms across the repo. - -- `data_structures.py` implements a few useful data structures - - A max- and min-heap ordered priority queue - - A [ball tree](https://en.wikipedia.org/wiki/Ball_tree) with the KNS1 algorithm ([Omohundro, 1989](http://ftp.icsi.berkeley.edu/ftp/pub/techreports/1989/tr-89-063.pdf); [Moore & Gray, 2006](http://people.ee.duke.edu/~lcarin/liu06a.pdf)) - - A discrete sampler implementing Vose's algorithm for the [alias method](https://en.wikipedia.org/wiki/Alias_method) ([Walker, 1977](https://dl.acm.org/citation.cfm?id=355749); [Vose, 1991](https://pdfs.semanticscholar.org/f65b/cde1fcf82e05388b31de80cba10bf65acc07.pdf)) - -- `kernels.py` implements several general-purpose similarity kernels - - Linear kernel - - Polynomial kernel - - Radial basis function kernel - -- `distance_metrics.py` implements common distance metrics - - Euclidean (L2) distance - - Manhattan (L1) distance - - Chebyshev (L-infinity) distance - - Minkowski-p distance - - Hamming distance - -- `graphs.py` implements simple data structures and algorithms for graph - processing. - - Undirected + directed graph objects allowing for probabilistic edge weights - - Graph generators (Erdos-Renyi, random DAGs) - - Topological sorting for DAGs - - Cycle detection - - Simple path-finding - -- `windows.py` implements several common windowing functions - - Hann - - Hamming - - Blackman-Harris - - Generalized cosine - -- `testing.py` implements helper functions that prove useful when writing unit - tests, including data generators and various assert statements diff --git a/aitk/keras/numpy_ml_utils/__init__.py b/aitk/keras/numpy_ml_utils/__init__.py deleted file mode 100644 index c90b4df..0000000 --- a/aitk/keras/numpy_ml_utils/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from . import testing -from . import data_structures -from . import distance_metrics -from . import kernels -from . import windows -from . import graphs diff --git a/aitk/keras/numpy_ml_utils/data_structures.py b/aitk/keras/numpy_ml_utils/data_structures.py deleted file mode 100644 index 4a1ea31..0000000 --- a/aitk/keras/numpy_ml_utils/data_structures.py +++ /dev/null @@ -1,522 +0,0 @@ -import heapq -from copy import copy -from collections.abc import Hashable - -import numpy as np - -from .distance_metrics import euclidean - -####################################################################### -# Priority Queue # -####################################################################### - - -class PQNode(object): - def __init__(self, key, val, priority, entry_id, **kwargs): - """A generic node object for holding entries in :class:`PriorityQueue`""" - self.key = key - self.val = val - self.entry_id = entry_id - self.priority = priority - - def __repr__(self): - fstr = "PQNode(key={}, val={}, priority={}, entry_id={})" - return fstr.format(self.key, self.val, self.priority, self.entry_id) - - def to_dict(self): - """Return a dictionary representation of the node's contents""" - d = self.__dict__ - d["id"] = "PQNode" - return d - - def __gt__(self, other): - if not isinstance(other, PQNode): - return -1 - if self.priority == other.priority: - return self.entry_id > other.entry_id - return self.priority > other.priority - - def __ge__(self, other): - if not isinstance(other, PQNode): - return -1 - return self.priority >= other.priority - - def __lt__(self, other): - if not isinstance(other, PQNode): - return -1 - if self.priority == other.priority: - return self.entry_id < other.entry_id - return self.priority < other.priority - - def __le__(self, other): - if not isinstance(other, PQNode): - return -1 - return self.priority <= other.priority - - -class PriorityQueue: - def __init__(self, capacity, heap_order="max"): - """ - A priority queue implementation using a binary heap. - - Notes - ----- - A priority queue is a data structure useful for storing the top - `capacity` largest or smallest elements in a collection of values. As a - result of using a binary heap, ``PriorityQueue`` offers `O(log N)` - :meth:`push` and :meth:`pop` operations. - - Parameters - ---------- - capacity: int - The maximum number of items that can be held in the queue. - heap_order: {"max", "min"} - Whether the priority queue should retain the items with the - `capacity` smallest (`heap_order` = 'min') or `capacity` largest - (`heap_order` = 'max') priorities. - """ - assert heap_order in ["max", "min"], "heap_order must be either 'max' or 'min'" - self.capacity = capacity - self.heap_order = heap_order - - self._pq = [] - self._count = 0 - self._entry_counter = 0 - - def __repr__(self): - fstr = "PriorityQueue(capacity={}, heap_order={}) with {} items" - return fstr.format(self.capacity, self.heap_order, self._count) - - def __len__(self): - return self._count - - def __iter__(self): - return iter(self._pq) - - def push(self, key, priority, val=None): - """ - Add a new (key, value) pair with priority `priority` to the queue. - - Notes - ----- - If the queue is at capacity and `priority` exceeds the priority of the - item with the largest/smallest priority currently in the queue, replace - the current queue item with (`key`, `val`). - - Parameters - ---------- - key : hashable object - The key to insert into the queue. - priority : comparable - The priority for the `key`, `val` pair. - val : object - The value associated with `key`. Default is None. - """ - if self.heap_order == "max": - priority = -1 * priority - - item = PQNode(key=key, val=val, priority=priority, entry_id=self._entry_counter) - heapq.heappush(self._pq, item) - - self._count += 1 - self._entry_counter += 1 - - while self._count > self.capacity: - self.pop() - - def pop(self): - """ - Remove the item with the largest/smallest (depending on - ``self.heap_order``) priority from the queue and return it. - - Notes - ----- - In contrast to :meth:`peek`, this operation is `O(log N)`. - - Returns - ------- - item : :class:`PQNode` instance or None - Item with the largest/smallest priority, depending on - ``self.heap_order``. - """ - item = heapq.heappop(self._pq).to_dict() - if self.heap_order == "max": - item["priority"] = -1 * item["priority"] - self._count -= 1 - return item - - def peek(self): - """ - Return the item with the largest/smallest (depending on - ``self.heap_order``) priority *without* removing it from the queue. - - Notes - ----- - In contrast to :meth:`pop`, this operation is O(1). - - Returns - ------- - item : :class:`PQNode` instance or None - Item with the largest/smallest priority, depending on - ``self.heap_order``. - """ - item = None - if self._count > 0: - item = copy(self._pq[0].to_dict()) - if self.heap_order == "max": - item["priority"] = -1 * item["priority"] - return item - - -####################################################################### -# Ball Tree # -####################################################################### - - -class BallTreeNode: - def __init__(self, centroid=None, X=None, y=None): - self.left = None - self.right = None - self.radius = None - self.is_leaf = False - - self.data = X - self.targets = y - self.centroid = centroid - - def __repr__(self): - fstr = "BallTreeNode(centroid={}, is_leaf={})" - return fstr.format(self.centroid, self.is_leaf) - - def to_dict(self): - d = self.__dict__ - d["id"] = "BallTreeNode" - return d - - -class BallTree: - def __init__(self, leaf_size=40, metric=None): - """ - A ball tree data structure. - - Notes - ----- - A ball tree is a binary tree in which every node defines a - `D`-dimensional hypersphere ("ball") containing a subset of the points - to be searched. Each internal node of the tree partitions the data - points into two disjoint sets which are associated with different - balls. While the balls themselves may intersect, each point is assigned - to one or the other ball in the partition according to its distance - from the ball's center. Each leaf node in the tree defines a ball and - enumerates all data points inside that ball. - - Parameters - ---------- - leaf_size : int - The maximum number of datapoints at each leaf. Default is 40. - metric : :doc:`Distance metric ` or None - The distance metric to use for computing nearest neighbors. If - None, use the :func:`~numpy_ml.utils.distance_metrics.euclidean` - metric. Default is None. - - References - ---------- - .. [1] Omohundro, S. M. (1989). "Five balltree construction algorithms". *ICSI - Technical Report TR-89-063*. - .. [2] Liu, T., Moore, A., & Gray A. (2006). "New algorithms for efficient - high-dimensional nonparametric classification". *J. Mach. Learn. Res., - 7*, 1135-1158. - """ - self.root = None - self.leaf_size = leaf_size - self.metric = metric if metric is not None else euclidean - - def fit(self, X, y=None): - """ - Build a ball tree recursively using the O(M log N) `k`-d construction - algorithm. - - Notes - ----- - Recursively divides data into nodes defined by a centroid `C` and radius - `r` such that each point below the node lies within the hyper-sphere - defined by `C` and `r`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, M)` - An array of `N` examples each with `M` features. - y : :py:class:`ndarray ` of shape `(N, *)` or None - An array of target values / labels associated with the entries in - `X`. Default is None. - """ - centroid, left_X, left_y, right_X, right_y = self._split(X, y) - self.root = BallTreeNode(centroid=centroid) - self.root.radius = np.max([self.metric(centroid, x) for x in X]) - self.root.left = self._build_tree(left_X, left_y) - self.root.right = self._build_tree(right_X, right_y) - - def _build_tree(self, X, y): - centroid, left_X, left_y, right_X, right_y = self._split(X, y) - - if X.shape[0] <= self.leaf_size: - leaf = BallTreeNode(centroid=centroid, X=X, y=y) - leaf.radius = np.max([self.metric(centroid, x) for x in X]) - leaf.is_leaf = True - return leaf - - node = BallTreeNode(centroid=centroid) - node.radius = np.max([self.metric(centroid, x) for x in X]) - node.left = self._build_tree(left_X, left_y) - node.right = self._build_tree(right_X, right_y) - return node - - def _split(self, X, y=None): - # find the dimension with greatest variance - split_dim = np.argmax(np.var(X, axis=0)) - - # sort X and y along split_dim - sort_ixs = np.argsort(X[:, split_dim]) - X, y = X[sort_ixs], y[sort_ixs] if y is not None else None - - # divide at median value of split_dim - med_ix = X.shape[0] // 2 - centroid = X[med_ix] # , split_dim - - # split data into two halves at the centroid (median always appears on - # the right split) - left_X, left_y = X[:med_ix], y[:med_ix] if y is not None else None - right_X, right_y = X[med_ix:], y[med_ix:] if y is not None else None - return centroid, left_X, left_y, right_X, right_y - - def nearest_neighbors(self, k, x): - """ - Find the `k` nearest neighbors in the ball tree to a query vector `x` - using the KNS1 algorithm. - - Parameters - ---------- - k : int - The number of closest points in `X` to return - x : :py:class:`ndarray ` of shape `(1, M)` - The query vector. - - Returns - ------- - nearest : list of :class:`PQNode` s of length `k` - List of the `k` points in `X` to closest to the query vector. The - ``key`` attribute of each :class:`PQNode` contains the point itself, the - ``val`` attribute contains its target, and the ``distance`` - attribute contains its distance to the query vector. - """ - # maintain a max-first priority queue with priority = distance to x - PQ = PriorityQueue(capacity=k, heap_order="max") - nearest = self._knn(k, x, PQ, self.root) - for n in nearest: - n.distance = self.metric(x, n.key) - return nearest - - def _knn(self, k, x, PQ, root): - dist = self.metric - dist_to_ball = dist(x, root.centroid) - root.radius - dist_to_farthest_neighbor = dist(x, PQ.peek()["key"]) if len(PQ) > 0 else np.inf - - if dist_to_ball >= dist_to_farthest_neighbor and len(PQ) == k: - return PQ - if root.is_leaf: - targets = [None] * len(root.data) if root.targets is None else root.targets - for point, target in zip(root.data, targets): - dist_to_x = dist(x, point) - if len(PQ) == k and dist_to_x < dist_to_farthest_neighbor: - PQ.push(key=point, val=target, priority=dist_to_x) - else: - PQ.push(key=point, val=target, priority=dist_to_x) - else: - l_closest = dist(x, root.left.centroid) < dist(x, root.right.centroid) - PQ = self._knn(k, x, PQ, root.left if l_closest else root.right) - PQ = self._knn(k, x, PQ, root.right if l_closest else root.left) - return PQ - - -####################################################################### -# Multinomial Sampler # -####################################################################### - - -class DiscreteSampler: - def __init__(self, probs, log=False, with_replacement=True): - """ - Sample from an arbitrary multinomial PMF over the first `N` nonnegative - integers using Vose's algorithm for the alias method. - - Notes - ----- - Vose's algorithm takes `O(n)` time to initialize, requires `O(n)` memory, - and generates samples in constant time. - - References - ---------- - .. [1] Walker, A. J. (1977) "An efficient method for generating discrete - random variables with general distributions". *ACM Transactions on - Mathematical Software, 3(3)*, 253-256. - - .. [2] Vose, M. D. (1991) "A linear algorithm for generating random numbers - with a given distribution". *IEEE Trans. Softw. Eng., 9*, 972-974. - - .. [3] Schwarz, K (2011) "Darts, dice, and coins: sampling from a discrete - distribution". http://www.keithschwarz.com/darts-dice-coins/ - - Parameters - ---------- - probs : :py:class:`ndarray ` of length `(N,)` - A list of probabilities of the `N` outcomes in the sample space. - `probs[i]` returns the probability of outcome `i`. - log : bool - Whether the probabilities in `probs` are in logspace. Default is - False. - with_replacement : bool - Whether to generate samples with or without replacement. Default is - True. - """ - if not isinstance(probs, np.ndarray): - probs = np.array(probs) - - self.log = log - self.N = len(probs) - self.probs = probs - self.with_replacement = with_replacement - - alias = np.zeros(self.N) - prob = np.zeros(self.N) - scaled_probs = self.probs + np.log(self.N) if log else self.probs * self.N - - selector = scaled_probs < 0 if log else scaled_probs < 1 - small, large = np.where(selector)[0].tolist(), np.where(~selector)[0].tolist() - - while len(small) and len(large): - l, g = small.pop(), large.pop() - - alias[l] = g - prob[l] = scaled_probs[l] - - if log: - pg = np.log(np.exp(scaled_probs[g]) + np.exp(scaled_probs[l]) - 1) - else: - pg = scaled_probs[g] + scaled_probs[l] - 1 - - scaled_probs[g] = pg - to_small = pg < 0 if log else pg < 1 - if to_small: - small.append(g) - else: - large.append(g) - - while len(large): - prob[large.pop()] = 0 if log else 1 - - while len(small): - prob[small.pop()] = 0 if log else 1 - - self.prob_table = prob - self.alias_table = alias - - def __call__(self, n_samples=1): - """ - Generate random draws from the `probs` distribution over integers in - [0, N). - - Parameters - ---------- - n_samples: int - The number of samples to generate. Default is 1. - - Returns - ------- - sample : :py:class:`ndarray ` of shape `(n_samples,)` - A collection of draws from the distribution defined by `probs`. - Each sample is an int in the range `[0, N)`. - """ - return self.sample(n_samples) - - def sample(self, n_samples=1): - """ - Generate random draws from the `probs` distribution over integers in - [0, N). - - Parameters - ---------- - n_samples: int - The number of samples to generate. Default is 1. - - Returns - ------- - sample : :py:class:`ndarray ` of shape `(n_samples,)` - A collection of draws from the distribution defined by `probs`. - Each sample is an int in the range `[0, N)`. - """ - ixs = np.random.randint(0, self.N, n_samples) - p = np.exp(self.prob_table[ixs]) if self.log else self.prob_table[ixs] - flips = np.random.binomial(1, p) - samples = [ix if f else self.alias_table[ix] for ix, f in zip(ixs, flips)] - - # do recursive rejection sampling to sample without replacement - if not self.with_replacement: - unique = list(set(samples)) - while len(samples) != len(unique): - n_new = len(samples) - len(unique) - samples = unique + self.sample(n_new).tolist() - unique = list(set(samples)) - - return np.array(samples, dtype=int) - - -####################################################################### -# Dict # -####################################################################### - - -class Dict(dict): - def __init__(self, encoder=None): - """ - A dictionary subclass which returns the key value if it is not in the - dict. - - Parameters - ---------- - encoder : function or None - A function which is applied to a key before adding / retrieving it - from the dictionary. If None, the function defaults to the - identity. Default is None. - """ - super(Dict, self).__init__() - self._encoder = encoder - self._id_max = 0 - - def __setitem__(self, key, value): - if self._encoder is not None: - key = self._encoder(key) - elif not isinstance(key, Hashable): - key = tuple(key) - super(Dict, self).__setitem__(key, value) - - def _encode_key(self, key): - D = super(Dict, self) - enc_key = self._encoder(key) - if D.__contains__(enc_key): - val = D.__getitem__(enc_key) - else: - val = self._id_max - D.__setitem__(enc_key, val) - self._id_max += 1 - return val - - def __getitem__(self, key): - self._key = copy.deepcopy(key) - if self._encoder is not None: - return self._encode_key(key) - elif not isinstance(key, Hashable): - key = tuple(key) - return super(Dict, self).__getitem__(key) - - def __missing__(self, key): - return self._key diff --git a/aitk/keras/numpy_ml_utils/distance_metrics.py b/aitk/keras/numpy_ml_utils/distance_metrics.py deleted file mode 100644 index 8c51e6c..0000000 --- a/aitk/keras/numpy_ml_utils/distance_metrics.py +++ /dev/null @@ -1,132 +0,0 @@ -import numpy as np - - -def euclidean(x, y): - """ - Compute the Euclidean (`L2`) distance between two real vectors - - Notes - ----- - The Euclidean distance between two vectors **x** and **y** is - - .. math:: - - d(\mathbf{x}, \mathbf{y}) = \sqrt{ \sum_i (x_i - y_i)^2 } - - Parameters - ---------- - x,y : :py:class:`ndarray ` s of shape `(N,)` - The two vectors to compute the distance between - - Returns - ------- - d : float - The L2 distance between **x** and **y**. - """ - return np.sqrt(np.sum((x - y) ** 2)) - - -def manhattan(x, y): - """ - Compute the Manhattan (`L1`) distance between two real vectors - - Notes - ----- - The Manhattan distance between two vectors **x** and **y** is - - .. math:: - - d(\mathbf{x}, \mathbf{y}) = \sum_i |x_i - y_i| - - Parameters - ---------- - x,y : :py:class:`ndarray ` s of shape `(N,)` - The two vectors to compute the distance between - - Returns - ------- - d : float - The L1 distance between **x** and **y**. - """ - return np.sum(np.abs(x - y)) - - -def chebyshev(x, y): - """ - Compute the Chebyshev (:math:`L_\infty`) distance between two real vectors - - Notes - ----- - The Chebyshev distance between two vectors **x** and **y** is - - .. math:: - - d(\mathbf{x}, \mathbf{y}) = \max_i |x_i - y_i| - - Parameters - ---------- - x,y : :py:class:`ndarray ` s of shape `(N,)` - The two vectors to compute the distance between - - Returns - ------- - d : float - The Chebyshev distance between **x** and **y**. - """ - return np.max(np.abs(x - y)) - - -def minkowski(x, y, p): - """ - Compute the Minkowski-`p` distance between two real vectors. - - Notes - ----- - The Minkowski-`p` distance between two vectors **x** and **y** is - - .. math:: - - d(\mathbf{x}, \mathbf{y}) = \left( \sum_i |x_i - y_i|^p \\right)^{1/p} - - Parameters - ---------- - x,y : :py:class:`ndarray ` s of shape `(N,)` - The two vectors to compute the distance between - p : float > 1 - The parameter of the distance function. When `p = 1`, this is the `L1` - distance, and when `p=2`, this is the `L2` distance. For `p < 1`, - Minkowski-`p` does not satisfy the triangle inequality and hence is not - a valid distance metric. - - Returns - ------- - d : float - The Minkowski-`p` distance between **x** and **y**. - """ - return np.sum(np.abs(x - y) ** p) ** (1 / p) - - -def hamming(x, y): - """ - Compute the Hamming distance between two integer-valued vectors. - - Notes - ----- - The Hamming distance between two vectors **x** and **y** is - - .. math:: - - d(\mathbf{x}, \mathbf{y}) = \\frac{1}{N} \sum_i \mathbb{1}_{x_i \\neq y_i} - - Parameters - ---------- - x,y : :py:class:`ndarray ` s of shape `(N,)` - The two vectors to compute the distance between. Both vectors should be - integer-valued. - - Returns - ------- - d : float - The Hamming distance between **x** and **y**. - """ - return np.sum(x != y) / len(x) diff --git a/aitk/keras/numpy_ml_utils/graphs.py b/aitk/keras/numpy_ml_utils/graphs.py deleted file mode 100644 index c65f5f3..0000000 --- a/aitk/keras/numpy_ml_utils/graphs.py +++ /dev/null @@ -1,363 +0,0 @@ -from abc import ABC, abstractmethod -from collections import defaultdict -from itertools import combinations, permutations - -import numpy as np - -####################################################################### -# Graph Components # -####################################################################### - - -class Edge(object): - def __init__(self, fr, to, w=None): - """ - A generic directed edge object. - - Parameters - ---------- - fr: int - The id of the vertex the edge goes from - to: int - The id of the vertex the edge goes to - w: float, :class:`Object` instance, or None - The edge weight, if applicable. If weight is an arbitrary Object it - must have a method called 'sample' which takes no arguments and - returns a random sample from the weight distribution. If `w` is - None, no weight is assumed. Default is None. - """ - self.fr = fr - self.to = to - self._w = w - - def __repr__(self): - return "{} -> {}, weight: {}".format(self.fr, self.to, self._w) - - @property - def weight(self): - return self._w.sample() if hasattr(self._w, "sample") else self._w - - def reverse(self): - """Reverse the edge direction""" - return Edge(self.t, self.f, self.w) - - -####################################################################### -# Graph Types # -####################################################################### - - -class Graph(ABC): - def __init__(self, V, E): - self._I2V = {i: v for i, v in zip(range(len(V)), V)} - self._V2I = {v: i for i, v in zip(range(len(V)), V)} - self._G = {i: set() for i in range(len(V))} - self._V = V - self._E = E - - self._build_adjacency_list() - - def __getitem__(self, v_i): - return self.get_neighbors(v_i) - - def get_index(self, v): - """Get the internal index for a given vetex""" - return self._V2I[v] - - def get_vertex(self, v_i): - """Get the original vertex from a given internal index""" - return self._I2V[v_i] - - @property - def vertices(self): - return self._V - - @property - def indices(self): - return list(range(len(self.vertices))) - - @property - def edges(self): - return self._E - - def get_neighbors(self, v_i): - """ - Return the internal indices of the vertices reachable from the vertex - with index `v_i`. - """ - return [self._V2I[e.to] for e in self._G[v_i]] - - def to_matrix(self): - """Return an adjacency matrix representation of the graph""" - adj_mat = np.zeros((len(self._V), len(self._V))) - for e in self.edges: - fr, to = self._V2I[e.fr], self._V2I[e.to] - adj_mat[fr, to] = 1 if e.weight is None else e.weight - return adj_mat - - def to_adj_dict(self): - """Return an adjacency dictionary representation of the graph""" - adj_dict = defaultdict(lambda: list()) - for e in self.edges: - adj_dict[e.fr].append(e) - return adj_dict - - def path_exists(self, s_i, e_i): - """ - Check whether a path exists from vertex index `s_i` to `e_i`. - - Parameters - ---------- - s_i: Int - The interal index of the start vertex - e_i: Int - The internal index of the end vertex - - Returns - ------- - path_exists : Boolean - Whether or not a valid path exists between `s_i` and `e_i`. - """ - queue = [(s_i, [s_i])] - while len(queue): - c_i, path = queue.pop(0) - nbrs_not_on_path = set(self.get_neighbors(c_i)) - set(path) - - for n_i in nbrs_not_on_path: - queue.append((n_i, path + [n_i])) - if n_i == e_i: - return True - return False - - def all_paths(self, s_i, e_i): - """ - Find all simple paths between `s_i` and `e_i` in the graph. - - Notes - ----- - Uses breadth-first search. Ignores all paths with repeated vertices. - - Parameters - ---------- - s_i: Int - The interal index of the start vertex - e_i: Int - The internal index of the end vertex - - Returns - ------- - complete_paths : list of lists - A list of all paths from `s_i` to `e_i`. Each path is represented - as a list of interal vertex indices. - """ - complete_paths = [] - queue = [(s_i, [s_i])] - - while len(queue): - c_i, path = queue.pop(0) - nbrs_not_on_path = set(self.get_neighbors(c_i)) - set(path) - - for n_i in nbrs_not_on_path: - if n_i == e_i: - complete_paths.append(path + [n_i]) - else: - queue.append((n_i, path + [n_i])) - - return complete_paths - - @abstractmethod - def _build_adjacency_list(self): - pass - - -class DiGraph(Graph): - def __init__(self, V, E): - """ - A generic directed graph object. - - Parameters - ---------- - V : list - A list of vertex IDs. - E : list of :class:`Edge ` objects - A list of directed edges connecting pairs of vertices in ``V``. - """ - super().__init__(V, E) - self.is_directed = True - self._topological_ordering = [] - - def _build_adjacency_list(self): - """Encode directed graph as an adjancency list""" - # assumes no parallel edges - for e in self.edges: - fr_i = self._V2I[e.fr] - self._G[fr_i].add(e) - - def reverse(self): - """Reverse the direction of all edges in the graph""" - return DiGraph(self.vertices, [e.reverse() for e in self.edges]) - - def topological_ordering(self): - """ - Returns a (non-unique) topological sort / linearization of the nodes - IFF the graph is acyclic, otherwise returns None. - - Notes - ----- - A topological sort is an ordering on the nodes in `G` such that for every - directed edge :math:`u \\rightarrow v` in the graph, `u` appears before - `v` in the ordering. The topological ordering is produced by ordering - the nodes in `G` by their DFS "last visit time," from greatest to - smallest. - - This implementation follows a recursive, DFS-based approach [1]_ which - may break if the graph is very large. For an iterative version, see - Khan's algorithm [2]_ . - - References - ---------- - .. [1] Tarjan, R. (1976), Edge-disjoint spanning trees and depth-first - search, *Acta Informatica, 6 (2)*: 171–185. - .. [2] Kahn, A. (1962), Topological sorting of large networks, - *Communications of the ACM, 5 (11)*: 558–562. - - Returns - ------- - ordering : list or None - A topoligical ordering of the vertex indices if the graph is a DAG, - otherwise None. - """ - ordering = [] - visited = set() - - def dfs(v_i, path=None): - """A simple DFS helper routine""" - path = set([v_i]) if path is None else path - for nbr_i in self.get_neighbors(v_i): - if nbr_i in path: - return True # cycle detected! - elif nbr_i not in visited: - visited.add(nbr_i) - path.add(nbr_i) - is_cyclic = dfs(nbr_i, path) - if is_cyclic: - return True - - # insert to the beginning of the ordering - ordering.insert(0, v_i) - path -= set([v_i]) - return False - - for s_i in self.indices: - if s_i not in visited: - visited.add(s_i) - is_cyclic = dfs(s_i) - - if is_cyclic: - return None - - return ordering - - def is_acyclic(self): - """Check whether the graph contains cycles""" - return self.topological_ordering() is not None - - -class UndirectedGraph(Graph): - def __init__(self, V, E): - """ - A generic undirected graph object. - - Parameters - ---------- - V : list - A list of vertex IDs. - E : list of :class:`Edge ` objects - A list of edges connecting pairs of vertices in ``V``. For any edge - connecting vertex `u` to vertex `v`, :class:`UndirectedGraph - ` will assume that there - exists a corresponding edge connecting `v` to `u`, even if this is - not present in `E`. - """ - super().__init__(V, E) - self.is_directed = False - - def _build_adjacency_list(self): - """Encode undirected, unweighted graph as an adjancency list""" - # assumes no parallel edges - # each edge appears twice as (u,v) and (v,u) - for e in self.edges: - fr_i = self._V2I[e.fr] - to_i = self._V2I[e.to] - - self._G[fr_i].add(e) - self._G[to_i].add(e.reverse()) - - -####################################################################### -# Graph Generators # -####################################################################### - - -def random_unweighted_graph(n_vertices, edge_prob=0.5, directed=False): - """ - Generate an unweighted Erdős-Rényi random graph [*]_. - - References - ---------- - .. [*] Erdős, P. and Rényi, A. (1959). On Random Graphs, *Publ. Math. 6*, 290. - - Parameters - ---------- - n_vertices : int - The number of vertices in the graph. - edge_prob : float in [0, 1] - The probability of forming an edge between two vertices. Default is - 0.5. - directed : bool - Whether the edges in the graph should be directed. Default is False. - - Returns - ------- - G : :class:`Graph` instance - The resulting random graph. - """ - vertices = list(range(n_vertices)) - candidates = permutations(vertices, 2) if directed else combinations(vertices, 2) - - edges = [] - for (fr, to) in candidates: - if np.random.rand() <= edge_prob: - edges.append(Edge(fr, to)) - - return DiGraph(vertices, edges) if directed else UndirectedGraph(vertices, edges) - - -def random_DAG(n_vertices, edge_prob=0.5): - """ - Create a 'random' unweighted directed acyclic graph by pruning all the - backward connections from a random graph. - - Parameters - ---------- - n_vertices : int - The number of vertices in the graph. - edge_prob : float in [0, 1] - The probability of forming an edge between two vertices in the - underlying random graph, before edge pruning. Default is 0.5. - - Returns - ------- - G : :class:`Graph` instance - The resulting DAG. - """ - G = random_unweighted_graph(n_vertices, edge_prob, directed=True) - - # prune edges to remove backwards connections between vertices - G = DiGraph(G.vertices, [e for e in G.edges if e.fr < e.to]) - - # if we pruned away all the edges, generate a new graph - while not len(G.edges): - G = random_unweighted_graph(n_vertices, edge_prob, directed=True) - G = DiGraph(G.vertices, [e for e in G.edges if e.fr < e.to]) - return G diff --git a/aitk/keras/numpy_ml_utils/kernels.py b/aitk/keras/numpy_ml_utils/kernels.py deleted file mode 100644 index f346d61..0000000 --- a/aitk/keras/numpy_ml_utils/kernels.py +++ /dev/null @@ -1,344 +0,0 @@ -import re -from abc import ABC, abstractmethod - -import numpy as np - - -class KernelBase(ABC): - def __init__(self): - super().__init__() - self.parameters = {} - self.hyperparameters = {} - - @abstractmethod - def _kernel(self, X, Y): - raise NotImplementedError - - def __call__(self, X, Y=None): - """Refer to documentation for the `_kernel` method""" - return self._kernel(X, Y) - - def __str__(self): - P, H = self.parameters, self.hyperparameters - p_str = ", ".join(["{}={}".format(k, v) for k, v in P.items()]) - return "{}({})".format(H["id"], p_str) - - def summary(self): - """Return the dictionary of model parameters, hyperparameters, and ID""" - return { - "id": self.hyperparameters["id"], - "parameters": self.parameters, - "hyperparameters": self.hyperparameters, - } - - def set_params(self, summary_dict): - """ - Set the model parameters and hyperparameters using the settings in - `summary_dict`. - - Parameters - ---------- - summary_dict : dict - A dictionary with keys 'parameters' and 'hyperparameters', - structured as would be returned by the :meth:`summary` method. If - a particular (hyper)parameter is not included in this dict, the - current value will be used. - - Returns - ------- - new_kernel : :doc:`Kernel ` instance - A kernel with parameters and hyperparameters adjusted to those - specified in `summary_dict`. - """ - kr, sd = self, summary_dict - - # collapse `parameters` and `hyperparameters` nested dicts into a single - # merged dictionary - flatten_keys = ["parameters", "hyperparameters"] - for k in flatten_keys: - if k in sd: - entry = sd[k] - sd.update(entry) - del sd[k] - - for k, v in sd.items(): - if k in self.parameters: - kr.parameters[k] = v - if k in self.hyperparameters: - kr.hyperparameters[k] = v - return kr - - -class LinearKernel(KernelBase): - def __init__(self, c0=0): - """ - The linear (i.e., dot-product) kernel. - - Notes - ----- - For input vectors :math:`\mathbf{x}` and :math:`\mathbf{y}`, the linear - kernel is: - - .. math:: - - k(\mathbf{x}, \mathbf{y}) = \mathbf{x}^\\top \mathbf{y} + c_0 - - Parameters - ---------- - c0 : float - An "inhomogeneity" parameter. When `c0` = 0, the kernel is said to be - homogenous. Default is 1. - """ - super().__init__() - self.hyperparameters = {"id": "LinearKernel"} - self.parameters = {"c0": c0} - - def _kernel(self, X, Y=None): - """ - Compute the linear kernel (i.e., dot-product) between all pairs of rows in - `X` and `Y`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, C)` - Collection of `N` input vectors - Y : :py:class:`ndarray ` of shape `(M, C)` or None - Collection of `M` input vectors. If None, assume `Y` = `X`. - Default is None. - - Returns - ------- - out : :py:class:`ndarray ` of shape `(N, M)` - Similarity between `X` and `Y`, where index (`i`, `j`) gives - :math:`k(x_i, y_j)`. - """ - X, Y = kernel_checks(X, Y) - return X @ Y.T + self.parameters["c0"] - - -class PolynomialKernel(KernelBase): - def __init__(self, d=3, gamma=None, c0=1): - """ - The degree-`d` polynomial kernel. - - Notes - ----- - For input vectors :math:`\mathbf{x}` and :math:`\mathbf{y}`, the polynomial - kernel is: - - .. math:: - - k(\mathbf{x}, \mathbf{y}) = (\gamma \mathbf{x}^\\top \mathbf{y} + c_0)^d - - In contrast to the linear kernel, the polynomial kernel also computes - similarities *across* dimensions of the **x** and **y** vectors, - allowing it to account for interactions between features. As an - instance of the dot product family of kernels, the polynomial kernel is - invariant to a rotation of the coordinates about the origin, but *not* - to translations. - - Parameters - ---------- - d : int - Degree of the polynomial kernel. Default is 3. - gamma : float or None - A scaling parameter for the dot product between `x` and `y`, - determining the amount of smoothing/resonlution of the kernel. - Larger values result in greater smoothing. If None, defaults to 1 / - `C`. Sometimes referred to as the kernel bandwidth. Default is - None. - c0 : float - Parameter trading off the influence of higher-order versus lower-order - terms in the polynomial. If `c0` = 0, the kernel is said to be - homogenous. Default is 1. - """ - super().__init__() - self.hyperparameters = {"id": "PolynomialKernel"} - self.parameters = {"d": d, "c0": c0, "gamma": gamma} - - def _kernel(self, X, Y=None): - """ - Compute the degree-`d` polynomial kernel between all pairs of rows in `X` - and `Y`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, C)` - Collection of `N` input vectors - Y : :py:class:`ndarray ` of shape `(M, C)` or None - Collection of `M` input vectors. If None, assume `Y = X`. Default - is None. - - Returns - ------- - out : :py:class:`ndarray ` of shape `(N, M)` - Similarity between `X` and `Y` where index (`i`, `j`) gives - :math:`k(x_i, y_j)` (i.e., the kernel's Gram-matrix). - """ - P = self.parameters - X, Y = kernel_checks(X, Y) - gamma = 1 / X.shape[1] if P["gamma"] is None else P["gamma"] - return (gamma * (X @ Y.T) + P["c0"]) ** P["d"] - - -class RBFKernel(KernelBase): - def __init__(self, sigma=None): - """ - Radial basis function (RBF) / squared exponential kernel. - - Notes - ----- - For input vectors :math:`\mathbf{x}` and :math:`\mathbf{y}`, the radial - basis function kernel is: - - .. math:: - - k(\mathbf{x}, \mathbf{y}) = \exp \left\{ -0.5 - \left\lVert \\frac{\mathbf{x} - - \mathbf{y}}{\sigma} \\right\\rVert_2^2 \\right\} - - The RBF kernel decreases with distance and ranges between zero (in the - limit) to one (when **x** = **y**). Notably, the implied feature space - of the kernel has an infinite number of dimensions. - - Parameters - ---------- - sigma : float or array of shape `(C,)` or None - A scaling parameter for the vectors **x** and **y**, producing an - isotropic kernel if a float, or an anistropic kernel if an array of - length `C`. Larger values result in higher resolution / greater - smoothing. If None, defaults to :math:`\sqrt(C / 2)`. Sometimes - referred to as the kernel 'bandwidth'. Default is None. - """ - super().__init__() - self.hyperparameters = {"id": "RBFKernel"} - self.parameters = {"sigma": sigma} - - def _kernel(self, X, Y=None): - """ - Computes the radial basis function (RBF) kernel between all pairs of - rows in `X` and `Y`. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, C)` - Collection of `N` input vectors, each with dimension `C`. - Y : :py:class:`ndarray ` of shape `(M, C)` - Collection of `M` input vectors. If None, assume `Y` = `X`. Default - is None. - - Returns - ------- - out : :py:class:`ndarray ` of shape `(N, M)` - Similarity between `X` and `Y` where index (i, j) gives :math:`k(x_i, y_j)`. - """ - P = self.parameters - X, Y = kernel_checks(X, Y) - sigma = np.sqrt(X.shape[1] / 2) if P["sigma"] is None else P["sigma"] - return np.exp(-0.5 * pairwise_l2_distances(X / sigma, Y / sigma) ** 2) - - -class KernelInitializer(object): - def __init__(self, param=None): - """ - A class for initializing learning rate schedulers. Valid inputs are: - (a) __str__ representations of `KernelBase` instances - (b) `KernelBase` instances - (c) Parameter dicts (e.g., as produced via the :meth:`summary` method in - `KernelBase` instances) - - If `param` is None, return `LinearKernel`. - """ - self.param = param - - def __call__(self): - param = self.param - if param is None: - kernel = LinearKernel() - elif isinstance(param, KernelBase): - kernel = param - elif isinstance(param, str): - kernel = self.init_from_str() - elif isinstance(param, dict): - kernel = self.init_from_dict() - return kernel - - def init_from_str(self): - r = r"([a-zA-Z0-9]*)=([^,)]*)" - kr_str = self.param.lower() - kwargs = dict([(i, eval(j)) for (i, j) in re.findall(r, self.param)]) - - if "linear" in kr_str: - kernel = LinearKernel(**kwargs) - elif "polynomial" in kr_str: - kernel = PolynomialKernel(**kwargs) - elif "rbf" in kr_str: - kernel = RBFKernel(**kwargs) - else: - raise NotImplementedError("{}".format(kr_str)) - return kernel - - def init_from_dict(self): - S = self.param - sc = S["hyperparameters"] if "hyperparameters" in S else None - - if sc is None: - raise ValueError("Must have `hyperparameters` key: {}".format(S)) - - if sc and sc["id"] == "LinearKernel": - scheduler = LinearKernel().set_params(S) - elif sc and sc["id"] == "PolynomialKernel": - scheduler = PolynomialKernel().set_params(S) - elif sc and sc["id"] == "RBFKernel": - scheduler = RBFKernel().set_params(S) - elif sc: - raise NotImplementedError("{}".format(sc["id"])) - return scheduler - - -def kernel_checks(X, Y): - X = X.reshape(-1, 1) if X.ndim == 1 else X - Y = X if Y is None else Y - Y = Y.reshape(-1, 1) if Y.ndim == 1 else Y - - assert X.ndim == 2, "X must have 2 dimensions, but got {}".format(X.ndim) - assert Y.ndim == 2, "Y must have 2 dimensions, but got {}".format(Y.ndim) - assert X.shape[1] == Y.shape[1], "X and Y must have the same number of columns" - return X, Y - - -def pairwise_l2_distances(X, Y): - """ - A fast, vectorized way to compute pairwise l2 distances between rows in `X` - and `Y`. - - Notes - ----- - An entry of the pairwise Euclidean distance matrix for two vectors is - - .. math:: - - d[i, j] &= \sqrt{(x_i - y_i) @ (x_i - y_i)} \\\\ - &= \sqrt{sum (x_i - y_j)^2} \\\\ - &= \sqrt{sum (x_i)^2 - 2 x_i y_j + (y_j)^2} - - The code below computes the the third line using numpy broadcasting - fanciness to avoid any for loops. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, C)` - Collection of `N` input vectors - Y : :py:class:`ndarray ` of shape `(M, C)` - Collection of `M` input vectors. If None, assume `Y` = `X`. Default is - None. - - Returns - ------- - dists : :py:class:`ndarray ` of shape `(N, M)` - Pairwise distance matrix. Entry (i, j) contains the `L2` distance between - :math:`x_i` and :math:`y_j`. - """ - D = -2 * X @ Y.T + np.sum(Y ** 2, axis=1) + np.sum(X ** 2, axis=1)[:, np.newaxis] - D[D < 0] = 0 # clip any value less than 0 (a result of numerical imprecision) - return np.sqrt(D) diff --git a/aitk/keras/numpy_ml_utils/testing.py b/aitk/keras/numpy_ml_utils/testing.py deleted file mode 100644 index 67f3111..0000000 --- a/aitk/keras/numpy_ml_utils/testing.py +++ /dev/null @@ -1,150 +0,0 @@ -"""Utilities for writing unit tests""" -import numbers -import numpy as np - -MSG_CACHE = set() - -def warn_once(msg): - if msg not in MSG_CACHE: - print(msg) - MSG_CACHE.add(msg) - -####################################################################### -# Assertions # -####################################################################### - - -def is_symmetric(X): - """Check that an array `X` is symmetric along its main diagonal""" - return np.allclose(X, X.T) - - -def is_symmetric_positive_definite(X): - """Check that a matrix `X` is a symmetric and positive-definite.""" - if is_symmetric(X): - try: - # if matrix is symmetric, check whether the Cholesky decomposition - # (defined only for symmetric/Hermitian positive definite matrices) - # exists - np.linalg.cholesky(X) - return True - except np.linalg.LinAlgError: - return False - return False - - -def is_stochastic(X): - """True if `X` contains probabilities that sum to 1 along the columns""" - msg = "Array should be stochastic along the columns" - assert len(X[X < 0]) == len(X[X > 1]) == 0, msg - if not np.allclose(np.sum(X, axis=1), np.ones(X.shape[0])): - warn_once("WARNING: %s; are you using the correct activation function?" % msg) - return True - - -def is_number(a): - """Check that a value `a` is numeric""" - return isinstance(a, numbers.Number) - - -def is_one_hot(x): - """Return True if array `x` is a binary array with a single 1""" - msg = "Matrix should be one-hot binary" - assert np.array_equal(x, x.astype(bool)), msg - assert np.allclose(np.sum(x, axis=1), np.ones(x.shape[0])), msg - return True - - -def is_binary(x): - """Return True if array `x` consists only of binary values""" - msg = "Matrix must be binary" - assert np.array_equal(x, x.astype(bool)), msg - return True - - -####################################################################### -# Data Generators # -####################################################################### - - -def random_one_hot_matrix(n_examples, n_classes): - """Create a random one-hot matrix of shape (`n_examples`, `n_classes`)""" - X = np.eye(n_classes) - X = X[np.random.choice(n_classes, n_examples)] - return X - - -def random_stochastic_matrix(n_examples, n_classes): - """Create a random stochastic matrix of shape (`n_examples`, `n_classes`)""" - X = np.random.rand(n_examples, n_classes) - X /= X.sum(axis=1, keepdims=True) - return X - - -def random_tensor(shape, standardize=False): - """ - Create a random real-valued tensor of shape `shape`. If `standardize` is - True, ensure each column has mean 0 and std 1. - """ - offset = np.random.randint(-300, 300, shape) - X = np.random.rand(*shape) + offset - - if standardize: - eps = np.finfo(float).eps - X = (X - X.mean(axis=0)) / (X.std(axis=0) + eps) - return X - - -def random_binary_tensor(shape, sparsity=0.5): - """ - Create a random binary tensor of shape `shape`. `sparsity` is a value - between 0 and 1 controlling the ratio of 0s to 1s in the output tensor. - """ - return (np.random.rand(*shape) >= (1 - sparsity)).astype(float) - - -def random_paragraph(n_words, vocab=None): - """ - Generate a random paragraph consisting of `n_words` words. If `vocab` is - not None, words will be drawn at random from this list. Otherwise, words - will be sampled uniformly from a collection of 26 Latin words. - """ - if vocab is None: - vocab = [ - "at", - "stet", - "accusam", - "aliquyam", - "clita", - "lorem", - "ipsum", - "dolor", - "dolore", - "dolores", - "sit", - "amet", - "consetetur", - "sadipscing", - "elitr", - "sed", - "diam", - "nonumy", - "eirmod", - "duo", - "ea", - "eos", - "erat", - "est", - "et", - "gubergren", - ] - return [np.random.choice(vocab) for _ in range(n_words)] - - -####################################################################### -# Custom Warnings # -####################################################################### - - -class DependencyWarning(RuntimeWarning): - pass diff --git a/aitk/keras/numpy_ml_utils/windows.py b/aitk/keras/numpy_ml_utils/windows.py deleted file mode 100644 index cd3132f..0000000 --- a/aitk/keras/numpy_ml_utils/windows.py +++ /dev/null @@ -1,156 +0,0 @@ -import numpy as np - - -def blackman_harris(window_len, symmetric=False): - """ - The Blackman-Harris window. - - Notes - ----- - The Blackman-Harris window is an instance of the more general class of - cosine-sum windows where `K=3`. Additional coefficients extend the Hamming - window to further minimize the magnitude of the nearest side-lobe in the - frequency response. - - .. math:: - \\text{bh}(n) = a_0 - a_1 \cos\left(\\frac{2 \pi n}{N}\\right) + - a_2 \cos\left(\\frac{4 \pi n }{N}\\right) - - a_3 \cos\left(\\frac{6 \pi n}{N}\\right) - - where `N` = `window_len` - 1, :math:`a_0` = 0.35875, :math:`a_1` = 0.48829, - :math:`a_2` = 0.14128, and :math:`a_3` = 0.01168. - - Parameters - ---------- - window_len : int - The length of the window in samples. Should be equal to the - `frame_width` if applying to a windowed signal. - symmetric : bool - If False, create a 'periodic' window that can be used in with an FFT / - in spectral analysis. If True, generate a symmetric window that can be - used in, e.g., filter design. Default is False. - - Returns - ------- - window : :py:class:`ndarray ` of shape `(window_len,)` - The window - """ - return generalized_cosine( - window_len, [0.35875, 0.48829, 0.14128, 0.01168], symmetric - ) - - -def hamming(window_len, symmetric=False): - """ - The Hamming window. - - Notes - ----- - The Hamming window is an instance of the more general class of cosine-sum - windows where `K=1` and :math:`a_0 = 0.54`. Coefficients selected to - minimize the magnitude of the nearest side-lobe in the frequency response. - - .. math:: - - \\text{hamming}(n) = 0.54 - - 0.46 \cos\left(\\frac{2 \pi n}{\\text{window_len} - 1}\\right) - - Parameters - ---------- - window_len : int - The length of the window in samples. Should be equal to the - `frame_width` if applying to a windowed signal. - symmetric : bool - If False, create a 'periodic' window that can be used in with an FFT / - in spectral analysis. If True, generate a symmetric window that can be - used in, e.g., filter design. Default is False. - - Returns - ------- - window : :py:class:`ndarray ` of shape `(window_len,)` - The window - """ - return generalized_cosine(window_len, [0.54, 1 - 0.54], symmetric) - - -def hann(window_len, symmetric=False): - """ - The Hann window. - - Notes - ----- - The Hann window is an instance of the more general class of cosine-sum - windows where `K=1` and :math:`a_0` = 0.5. Unlike the Hamming window, the - end points of the Hann window touch zero. - - .. math:: - - \\text{hann}(n) = 0.5 - 0.5 \cos\left(\\frac{2 \pi n}{\\text{window_len} - 1}\\right) - - Parameters - ---------- - window_len : int - The length of the window in samples. Should be equal to the - `frame_width` if applying to a windowed signal. - symmetric : bool - If False, create a 'periodic' window that can be used in with an FFT / - in spectral analysis. If True, generate a symmetric window that can be - used in, e.g., filter design. Default is False. - - Returns - ------- - window : :py:class:`ndarray ` of shape `(window_len,)` - The window - """ - return generalized_cosine(window_len, [0.5, 0.5], symmetric) - - -def generalized_cosine(window_len, coefs, symmetric=False): - """ - The generalized cosine family of window functions. - - Notes - ----- - The generalized cosine window is a simple weighted sum of cosine terms. - - For :math:`n \in \{0, \ldots, \\text{window_len} \}`: - - .. math:: - - \\text{GCW}(n) = \sum_{k=0}^K (-1)^k a_k \cos\left(\\frac{2 \pi k n}{\\text{window_len}}\\right) - - Parameters - ---------- - window_len : int - The length of the window in samples. Should be equal to the - `frame_width` if applying to a windowed signal. - coefs: list of floats - The :math:`a_k` coefficient values - symmetric : bool - If False, create a 'periodic' window that can be used in with an FFT / - in spectral analysis. If True, generate a symmetric window that can be - used in, e.g., filter design. Default is False. - - Returns - ------- - window : :py:class:`ndarray ` of shape `(window_len,)` - The window - """ - window_len += 1 if not symmetric else 0 - entries = np.linspace(-np.pi, np.pi, window_len) # (-1)^k * 2pi*n / window_len - window = np.sum([ak * np.cos(k * entries) for k, ak in enumerate(coefs)], axis=0) - return window[:-1] if not symmetric else window - - -class WindowInitializer: - def __call__(self, window): - if window == "hamming": - return hamming - elif window == "blackman_harris": - return blackman_harris - elif window == "hann": - return hann - elif window == "generalized_cosine": - return generalized_cosine - else: - raise NotImplementedError("{}".format(window)) diff --git a/aitk/keras/optimizers/README.md b/aitk/keras/optimizers/README.md deleted file mode 100644 index fa815cb..0000000 --- a/aitk/keras/optimizers/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Optimizers - -The `optimizers.py` module implements common modifications to stochastic gradient descent. It includes: - -- SGD with momentum ([Rummelhart, Hinton, & Williams, 1986](https://www.cs.princeton.edu/courses/archive/spring18/cos495/res/backprop_old.pdf)) -- AdaGrad ([Duchi, Hazan, & Singer, 2011](http://jmlr.org/papers/volume12/duchi11a/duchi11a.pdf)) -- RMSProp ([Tieleman & Hinton, 2012](http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf)) -- Adam ([Kingma & Ba, 2015](https://arxiv.org/pdf/1412.6980v8.pdf)) diff --git a/aitk/keras/optimizers/__init__.py b/aitk/keras/optimizers/__init__.py deleted file mode 100644 index acd7379..0000000 --- a/aitk/keras/optimizers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .optimizers import * diff --git a/aitk/keras/optimizers/optimizers.py b/aitk/keras/optimizers/optimizers.py deleted file mode 100644 index 6651e64..0000000 --- a/aitk/keras/optimizers/optimizers.py +++ /dev/null @@ -1,498 +0,0 @@ -from copy import deepcopy -from abc import ABC, abstractmethod - -import numpy as np -from numpy.linalg import norm - - -class OptimizerBase(ABC): - def __init__(self, learning_rate, scheduler=None): - """ - An abstract base class for all Optimizer objects. - - This should never be used directly. - """ - from ..initializers import SchedulerInitializer - - self.cache = {} - self.cur_step = 0 - self.hyperparameters = {} - self.lr_scheduler = SchedulerInitializer(scheduler, lr=learning_rate)() - - def __call__(self, param, param_grad, param_name, cur_loss=None): - return self.update(param, param_grad, param_name, cur_loss) - - def step(self): - """Increment the optimizer step counter by 1""" - self.cur_step += 1 - - def reset_step(self): - """Reset the step counter to zero""" - self.cur_step = 0 - - def copy(self): - """Return a copy of the optimizer object""" - return deepcopy(self) - - def set_params(self, hparam_dict=None, cache_dict=None): - """Set the parameters of the optimizer object from a dictionary""" - from ..initializers import SchedulerInitializer - - if hparam_dict is not None: - for k, v in hparam_dict.items(): - if k in self.hyperparameters: - self.hyperparameters[k] = v - if k == "lr_scheduler": - self.lr_scheduler = SchedulerInitializer(v, lr=None)() - - if cache_dict is not None: - for k, v in cache_dict.items(): - if k in self.cache: - self.cache[k] = v - - @abstractmethod - def update(self, param, param_grad, param_name, cur_loss=None): - raise NotImplementedError - - -class SGD(OptimizerBase): - def __init__( - self, learning_rate=0.01, momentum=0.0, clip_norm=None, lr_scheduler=None, **kwargs - ): - """ - A stochastic gradient descent optimizer. - - Notes - ----- - For model parameters :math:`\\theta`, averaged parameter gradients - :math:`\\nabla_{\\theta} \mathcal{L}`, and learning rate :math:`\eta`, - the SGD update at timestep `t` is - - .. math:: - - \\text{update}^{(t)} - &= \\text{momentum} \cdot \\text{update}^{(t-1)} + \eta^{(t)} \\nabla_{\\theta} \mathcal{L}\\\\ - \\theta^{(t+1)} - &\leftarrow \\theta^{(t)} - \\text{update}^{(t)} - - Parameters - ---------- - learning_rate : float - Learning rate for SGD. If scheduler is not None, this is used as - the starting learning rate. Default is 0.01. - momentum : float in range [0, 1] - The fraction of the previous update to add to the current update. - If 0, no momentum is applied. Default is 0. - clip_norm : float - If not None, all param gradients are scaled to have maximum l2 norm of - `clip_norm` before computing update. Default is None. - lr_scheduler : str, :doc:`Scheduler ` object, or None - The learning rate scheduler. If None, use a constant learning - rate equal to `learning_rate`. Default is None. - """ - if "lr" in kwargs: - learning_rate = kwargs["lr"] - print("UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.") - - super().__init__(learning_rate, lr_scheduler) - - self.hyperparameters = { - "id": "SGD", - "learning_rate": learning_rate, - "momentum": momentum, - "clip_norm": clip_norm, - "lr_scheduler": str(self.lr_scheduler), - } - - def __str__(self): - H = self.hyperparameters - learning_rate, mm, cn, sc = H["learning_rate"], H["momentum"], H["clip_norm"], H["lr_scheduler"] - return "SGD(learning_rate={}, momentum={}, clip_norm={}, lr_scheduler={})".format( - learning_rate, mm, cn, sc - ) - - def update(self, param, param_grad, param_name, cur_loss=None): - """ - Compute the SGD update for a given parameter - - Parameters - ---------- - param : :py:class:`ndarray ` of shape (n, m) - The value of the parameter to be updated. - param_grad : :py:class:`ndarray ` of shape (n, m) - The gradient of the loss function with respect to `param_name`. - param_name : str - The name of the parameter. - cur_loss : float - The training or validation loss for the current minibatch. Used for - learning rate scheduling e.g., by - :class:`~numpy_ml.neural_nets.schedulers.KingScheduler`. - Default is None. - - Returns - ------- - updated_params : :py:class:`ndarray ` of shape (n, m) - The value of `param` after applying the momentum update. - """ - C = self.cache - H = self.hyperparameters - momentum, clip_norm = H["momentum"], H["clip_norm"] - learning_rate = self.lr_scheduler(self.cur_step, cur_loss) - - if param_name not in C: - C[param_name] = np.zeros_like(param_grad) - - # scale gradient to avoid explosion - t = np.inf if clip_norm is None else clip_norm - if norm(param_grad) > t: - param_grad = param_grad * t / norm(param_grad) - - update = momentum * C[param_name] + learning_rate * param_grad - self.cache[param_name] = update - return param - update - - -####################################################################### -# Adaptive Gradient Methods # -####################################################################### - - -class AdaGrad(OptimizerBase): - def __init__(self, learning_rate=0.01, eps=1e-7, clip_norm=None, lr_scheduler=None, **kwargs): - """ - An AdaGrad optimizer. - - Notes - ----- - Weights that receive large gradients will have their effective learning - rate reduced, while weights that receive small or infrequent updates - will have their effective learning rate increased. - - Equations:: - - cache[t] = cache[t-1] + grad[t] ** 2 - update[t] = learning_rate * grad[t] / (np.sqrt(cache[t]) + eps) - param[t+1] = param[t] - update[t] - - Note that the ``**`` and `/` operations are elementwise - - "A downside of Adagrad ... is that the monotonic learning rate usually - proves too aggressive and stops learning too early." [1] - - References - ---------- - .. [1] Karpathy, A. "CS231n: Convolutional neural networks for visual - recognition" https://cs231n.github.io/neural-networks-3/ - - Parameters - ---------- - learning_rate : float - Global learning rate - eps : float - Smoothing term to avoid divide-by-zero errors in the update calc. - Default is 1e-7. - clip_norm : float or None - If not None, all param gradients are scaled to have maximum `L2` norm of - `clip_norm` before computing update. Default is None. - lr_scheduler : str or :doc:`Scheduler ` object or None - The learning rate scheduler. If None, use a constant learning - rate equal to `learning_rate`. Default is None. - """ - if "lr" in kwargs: - learning_rate = kwargs["lr"] - print("UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.") - - super().__init__(learning_rate, lr_scheduler) - - self.cache = {} - self.hyperparameters = { - "id": "AdaGrad", - "learning_rate": learning_rate, - "eps": eps, - "clip_norm": clip_norm, - "lr_scheduler": str(self.lr_scheduler), - } - - def __str__(self): - H = self.hyperparameters - learning_rate, eps, cn, sc = H["learning_rate"], H["eps"], H["clip_norm"], H["lr_scheduler"] - return "AdaGrad(learning_rate={}, eps={}, clip_norm={}, lr_scheduler={})".format( - learning_rate, eps, cn, sc - ) - - def update(self, param, param_grad, param_name, cur_loss=None): - """ - Compute the AdaGrad update for a given parameter. - - Notes - ----- - Adjusts the learning rate of each weight based on the magnitudes of its - gradients (big gradient -> small learning_rate, small gradient -> big learning_rate). - - Parameters - ---------- - param : :py:class:`ndarray ` of shape (n, m) - The value of the parameter to be updated - param_grad : :py:class:`ndarray ` of shape (n, m) - The gradient of the loss function with respect to `param_name` - param_name : str - The name of the parameter - cur_loss : float or None - The training or validation loss for the current minibatch. Used for - learning rate scheduling e.g., by - :class:`~numpy_ml.neural_nets.schedulers.KingScheduler`. - Default is None. - - Returns - ------- - updated_params : :py:class:`ndarray ` of shape (n, m) - The value of `param` after applying the AdaGrad update - """ - C = self.cache - H = self.hyperparameters - eps, clip_norm = H["eps"], H["clip_norm"] - learning_rate = self.lr_scheduler(self.cur_step, cur_loss) - - if param_name not in C: - C[param_name] = np.zeros_like(param_grad) - - # scale gradient to avoid explosion - t = np.inf if clip_norm is None else clip_norm - if norm(param_grad) > t: - param_grad = param_grad * t / norm(param_grad) - - C[param_name] += param_grad ** 2 - update = learning_rate * param_grad / (np.sqrt(C[param_name]) + eps) - self.cache = C - return param - update - - -class RMSProp(OptimizerBase): - def __init__( - self, learning_rate=0.001, decay=0.9, eps=1e-7, clip_norm=None, lr_scheduler=None, **kwargs - ): - """ - RMSProp optimizer. - - Notes - ----- - RMSProp was proposed as a refinement of :class:`AdaGrad` to reduce its - aggressive, monotonically decreasing learning rate. - - RMSProp uses a *decaying average* of the previous squared gradients - (second moment) rather than just the immediately preceding squared - gradient for its `previous_update` value. - - Equations:: - - cache[t] = decay * cache[t-1] + (1 - decay) * grad[t] ** 2 - update[t] = learning_rate * grad[t] / (np.sqrt(cache[t]) + eps) - param[t+1] = param[t] - update[t] - - Note that the ``**`` and ``/`` operations are elementwise. - - Parameters - ---------- - learning_rate : float - Learning rate for update. Default is 0.001. - decay : float in [0, 1] - Rate of decay for the moving average. Typical values are [0.9, - 0.99, 0.999]. Default is 0.9. - eps : float - Constant term to avoid divide-by-zero errors during the update calc. Default is 1e-7. - clip_norm : float or None - If not None, all param gradients are scaled to have maximum l2 norm of - `clip_norm` before computing update. Default is None. - lr_scheduler : str or :doc:`Scheduler ` object or None - The learning rate scheduler. If None, use a constant learning - rate equal to `learning_rate`. Default is None. - """ - if "lr" in kwargs: - learning_rate = kwargs["lr"] - print("UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.") - - super().__init__(learning_rate, lr_scheduler) - - self.cache = {} - self.hyperparameters = { - "id": "RMSProp", - "learning_rate": learning_rate, - "eps": eps, - "decay": decay, - "clip_norm": clip_norm, - "lr_scheduler": str(self.lr_scheduler), - } - - def __str__(self): - H = self.hyperparameters - sc = H["lr_scheduler"] - learning_rate, eps, dc, cn = H["learning_rate"], H["eps"], H["decay"], H["clip_norm"] - return "RMSProp(learning_rate={}, eps={}, decay={}, clip_norm={}, lr_scheduler={})".format( - learning_rate, eps, dc, cn, sc - ) - - def update(self, param, param_grad, param_name, cur_loss=None): - """ - Compute the RMSProp update for a given parameter. - - Parameters - ---------- - param : :py:class:`ndarray ` of shape (n, m) - The value of the parameter to be updated - param_grad : :py:class:`ndarray ` of shape (n, m) - The gradient of the loss function with respect to `param_name` - param_name : str - The name of the parameter - cur_loss : float or None - The training or validation loss for the current minibatch. Used for - learning rate scheduling e.g., by - :class:`~numpy_ml.neural_nets.schedulers.KingScheduler`. - Default is None. - - Returns - ------- - updated_params : :py:class:`ndarray ` of shape (n, m) - The value of `param` after applying the RMSProp update. - """ - C = self.cache - H = self.hyperparameters - eps, decay, clip_norm = H["eps"], H["decay"], H["clip_norm"] - learning_rate = self.lr_scheduler(self.cur_step, cur_loss) - - if param_name not in C: - C[param_name] = np.zeros_like(param_grad) - - # scale gradient to avoid explosion - t = np.inf if clip_norm is None else clip_norm - if norm(param_grad) > t: - param_grad = param_grad * t / norm(param_grad) - - C[param_name] = decay * C[param_name] + (1 - decay) * param_grad ** 2 - update = learning_rate * param_grad / (np.sqrt(C[param_name]) + eps) - self.cache = C - return param - update - - -class Adam(OptimizerBase): - def __init__( - self, - learning_rate=0.001, - decay1=0.9, - decay2=0.999, - eps=1e-7, - clip_norm=None, - lr_scheduler=None, - **kwargs - ): - """ - Adam (adaptive moment estimation) optimization algorithm. - - Notes - ----- - Designed to combine the advantages of :class:`AdaGrad`, which works - well with sparse gradients, and :class:`RMSProp`, which works well in - online and non-stationary settings. - - Parameters - ---------- - learning_rate : float - Learning rate for update. This parameter is ignored if using - :class:`~numpy_ml.neural_nets.schedulers.NoamScheduler`. - Default is 0.001. - decay1 : float - The rate of decay to use for in running estimate of the first - moment (mean) of the gradient. Default is 0.9. - decay2 : float - The rate of decay to use for in running estimate of the second - moment (variance) of the gradient. Default is 0.999. - eps : float - Constant term to avoid divide-by-zero errors during the update - calc. Default is 1e-7. - clip_norm : float - If not None, all param gradients are scaled to have maximum l2 norm of - `clip_norm` before computing update. Default is None. - lr_scheduler : str, or :doc:`Scheduler ` object, or None - The learning rate scheduler. If None, use a constant learning rate - equal to `learning_rate`. Default is None. - """ - if "lr" in kwargs: - learning_rate = kwargs["lr"] - print("UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.") - - super().__init__(learning_rate, lr_scheduler) - - self.cache = {} - self.hyperparameters = { - "id": "Adam", - "learning_rate": learning_rate, - "eps": eps, - "decay1": decay1, - "decay2": decay2, - "clip_norm": clip_norm, - "lr_scheduler": str(self.lr_scheduler), - } - - def __str__(self): - H = self.hyperparameters - learning_rate, d1, d2 = H["learning_rate"], H["decay1"], H["decay2"] - eps, cn, sc = H["eps"], H["clip_norm"], H["lr_scheduler"] - return "Adam(learning_rate={}, decay1={}, decay2={}, eps={}, clip_norm={}, lr_scheduler={})".format( - learning_rate, d1, d2, eps, cn, sc - ) - - def update(self, param, param_grad, param_name, cur_loss=None): - """ - Compute the Adam update for a given parameter. - - Parameters - ---------- - param : :py:class:`ndarray ` of shape (n, m) - The value of the parameter to be updated. - param_grad : :py:class:`ndarray ` of shape (n, m) - The gradient of the loss function with respect to `param_name`. - param_name : str - The name of the parameter. - cur_loss : float - The training or validation loss for the current minibatch. Used for - learning rate scheduling e.g., by - :class:`~numpy_ml.neural_nets.schedulers.KingScheduler`. Default is - None. - - Returns - ------- - updated_params : :py:class:`ndarray ` of shape (n, m) - The value of `param` after applying the Adam update. - """ - C = self.cache - H = self.hyperparameters - d1, d2 = H["decay1"], H["decay2"] - eps, clip_norm = H["eps"], H["clip_norm"] - learning_rate = self.lr_scheduler(self.cur_step, cur_loss) - - if param_name not in C: - C[param_name] = { - "t": 0, - "mean": np.zeros_like(param_grad), - "var": np.zeros_like(param_grad), - } - - # scale gradient to avoid explosion - t = np.inf if clip_norm is None else clip_norm - if norm(param_grad) > t: - param_grad = param_grad * t / norm(param_grad) - - t = C[param_name]["t"] + 1 - var = C[param_name]["var"] - mean = C[param_name]["mean"] - - # update cache - C[param_name]["t"] = t - C[param_name]["var"] = d2 * var + (1 - d2) * param_grad ** 2 - C[param_name]["mean"] = d1 * mean + (1 - d1) * param_grad - self.cache = C - - # calc unbiased moment estimates and Adam update - v_hat = C[param_name]["var"] / (1 - d2 ** t) - m_hat = C[param_name]["mean"] / (1 - d1 ** t) - update = learning_rate * m_hat / (np.sqrt(v_hat) + eps) - return param - update diff --git a/aitk/keras/preprocessing/README.md b/aitk/keras/preprocessing/README.md deleted file mode 100644 index b0f90d7..0000000 --- a/aitk/keras/preprocessing/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Preprocessing -The preprocessing module implements common data preprocessing routines. - -- `nlp.py`: Routines and objects for handling text data. - - n-gram generators - - Word and character tokenization - - Punctuation and stop-word removal - - Vocabulary / unigram count objects - - [Huffman tree](https://en.wikipedia.org/wiki/Huffman_coding) encoding / decoding - - Term frequency-inverse document frequency ([tf-idf](https://en.wikipedia.org/wiki/Tf%E2%80%93idf)) encoding - -- `dsp.py`: Routines for handling audio and image data. - - Signal windowing - - Signal autocorrelation - - Discrete Fourier transform - - Discrete cosine transform (type II) - - Signal resampling via (bi-)linear interpolation and nearest neighbor - - Mel-frequency cepstral coefficients (MFCCs) ([Mermelstein, 1976](https://files.eric.ed.gov/fulltext/ED128870.pdf#page=93); [Davis & Mermelstein, 1980](https://pdfs.semanticscholar.org/24b8/7a58511919cc867a71f0b58328694dd494b3.pdf)) - -- `general.py`: General data preprocessing objects and functions. - - Feature hashing ([Moody, 1989](http://papers.nips.cc/paper/175-fast-learning-in-multi-resolution-hierarchies.pdf)) - - Mini-batch generators - - One-hot encoding / decoding - - Feature standardization diff --git a/aitk/keras/preprocessing/__init__.py b/aitk/keras/preprocessing/__init__.py deleted file mode 100644 index 021db2c..0000000 --- a/aitk/keras/preprocessing/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from . import general -from . import nlp -from . import dsp diff --git a/aitk/keras/preprocessing/dsp.py b/aitk/keras/preprocessing/dsp.py deleted file mode 100644 index 77f3c40..0000000 --- a/aitk/keras/preprocessing/dsp.py +++ /dev/null @@ -1,848 +0,0 @@ -import numpy as np -from numpy.lib.stride_tricks import as_strided - -from ..utils.windows import WindowInitializer - -####################################################################### -# Signal Resampling # -####################################################################### - - -def batch_resample(X, new_dim, mode="bilinear"): - """ - Resample each image (or similar grid-based 2D signal) in a batch to - `new_dim` using the specified resampling strategy. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_channels)` - An input image volume - new_dim : 2-tuple of `(out_rows, out_cols)` - The dimension to resample each image to - mode : {'bilinear', 'neighbor'} - The resampling strategy to employ. Default is 'bilinear'. - - Returns - ------- - resampled : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, in_channels)` - The resampled image volume. - """ - if mode == "bilinear": - interpolate = bilinear_interpolate - elif mode == "neighbor": - interpolate = nn_interpolate_2D - else: - raise NotImplementedError("Unrecognized resampling mode: {}".format(mode)) - - out_rows, out_cols = new_dim - n_ex, in_rows, in_cols, n_in = X.shape - - # compute coordinates to resample - x = np.tile(np.linspace(0, in_cols - 2, out_cols), out_rows) - y = np.repeat(np.linspace(0, in_rows - 2, out_rows), out_cols) - - # resample each image - resampled = [] - for i in range(n_ex): - r = interpolate(X[i, ...], x, y) - r = r.reshape(out_rows, out_cols, n_in) - resampled.append(r) - return np.dstack(resampled) - - -def nn_interpolate_2D(X, x, y): - """ - Estimates of the pixel values at the coordinates (x, y) in `X` using a - nearest neighbor interpolation strategy. - - Notes - ----- - Assumes the current entries in `X` reflect equally-spaced samples from a 2D - integer grid. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(in_rows, in_cols, in_channels)` - An input image sampled along a grid of `in_rows` by `in_cols`. - x : list of length `k` - A list of x-coordinates for the samples we wish to generate - y : list of length `k` - A list of y-coordinates for the samples we wish to generate - - Returns - ------- - samples : :py:class:`ndarray ` of shape `(k, in_channels)` - The samples for each (x,y) coordinate computed via nearest neighbor - interpolation - """ - nx, ny = np.around(x), np.around(y) - nx = np.clip(nx, 0, X.shape[1] - 1).astype(int) - ny = np.clip(ny, 0, X.shape[0] - 1).astype(int) - return X[ny, nx, :] - - -def nn_interpolate_1D(X, t): - """ - Estimates of the signal values at `X[t]` using a nearest neighbor - interpolation strategy. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(in_length, in_channels)` - An input image sampled along an integer `in_length` - t : list of length `k` - A list of coordinates for the samples we wish to generate - - Returns - ------- - samples : :py:class:`ndarray ` of shape `(k, in_channels)` - The samples for each (x,y) coordinate computed via nearest neighbor - interpolation - """ - nt = np.clip(np.around(t), 0, X.shape[0] - 1).astype(int) - return X[nt, :] - - -def bilinear_interpolate(X, x, y): - """ - Estimates of the pixel values at the coordinates (x, y) in `X` via bilinear - interpolation. - - Notes - ----- - Assumes the current entries in X reflect equally-spaced - samples from a 2D integer grid. - - Modified from https://bit.ly/2NMb1Dr - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(in_rows, in_cols, in_channels)` - An input image sampled along a grid of `in_rows` by `in_cols`. - x : list of length `k` - A list of x-coordinates for the samples we wish to generate - y : list of length `k` - A list of y-coordinates for the samples we wish to generate - - Returns - ------- - samples : list of length `(k, in_channels)` - The samples for each (x,y) coordinate computed via bilinear - interpolation - """ - x0 = np.floor(x).astype(int) - y0 = np.floor(y).astype(int) - x1 = x0 + 1 - y1 = y0 + 1 - - x0 = np.clip(x0, 0, X.shape[1] - 1) - y0 = np.clip(y0, 0, X.shape[0] - 1) - x1 = np.clip(x1, 0, X.shape[1] - 1) - y1 = np.clip(y1, 0, X.shape[0] - 1) - - Ia = X[y0, x0, :].T - Ib = X[y1, x0, :].T - Ic = X[y0, x1, :].T - Id = X[y1, x1, :].T - - wa = (x1 - x) * (y1 - y) - wb = (x1 - x) * (y - y0) - wc = (x - x0) * (y1 - y) - wd = (x - x0) * (y - y0) - - return (Ia * wa).T + (Ib * wb).T + (Ic * wc).T + (Id * wd).T - - -####################################################################### -# Fourier Decomposition # -####################################################################### - - -def DCT(frame, orthonormal=True): - """ - A naive :math:`O(N^2)` implementation of the 1D discrete cosine transform-II - (DCT-II). - - Notes - ----- - For a signal :math:`\mathbf{x} = [x_1, \ldots, x_N]` consisting of `N` - samples, the `k` th DCT coefficient, :math:`c_k`, is - - .. math:: - - c_k = 2 \sum_{n=0}^{N-1} x_n \cos(\pi k (2 n + 1) / (2 N)) - - where `k` ranges from :math:`0, \ldots, N-1`. - - The DCT is highly similar to the DFT -- whereas in a DFT the basis - functions are sinusoids, in a DCT they are restricted solely to cosines. A - signal's DCT representation tends to have more of its energy concentrated - in a smaller number of coefficients when compared to the DFT, and is thus - commonly used for signal compression. [1] - - .. [1] Smoother signals can be accurately approximated using fewer DFT / DCT - coefficients, resulting in a higher compression ratio. The DCT naturally - yields a continuous extension at the signal boundaries due its use of - even basis functions (cosine). This in turn produces a smoother - extension in comparison to DFT or DCT approximations, resulting in a - higher compression. - - Parameters - ---------- - frame : :py:class:`ndarray ` of shape `(N,)` - A signal frame consisting of N samples - orthonormal : bool - Scale to ensure the coefficient vector is orthonormal. Default is True. - - Returns - ------- - dct : :py:class:`ndarray ` of shape `(N,)` - The discrete cosine transform of the samples in `frame`. - """ - N = len(frame) - out = np.zeros_like(frame) - for k in range(N): - for (n, xn) in enumerate(frame): - out[k] += xn * np.cos(np.pi * k * (2 * n + 1) / (2 * N)) - scale = np.sqrt(1 / (4 * N)) if k == 0 else np.sqrt(1 / (2 * N)) - out[k] *= 2 * scale if orthonormal else 2 - return out - - -def __DCT2(frame): - """Currently broken""" - N = len(frame) # window length - - k = np.arange(N, dtype=float) - F = k.reshape(1, -1) * k.reshape(-1, 1) - K = np.divide(F, k, out=np.zeros_like(F), where=F != 0) - - FC = np.cos(F * np.pi / N + K * np.pi / 2 * N) - return 2 * (FC @ frame) - - -def DFT(frame, positive_only=True): - """ - A naive :math:`O(N^2)` implementation of the 1D discrete Fourier transform (DFT). - - Notes - ----- - The Fourier transform decomposes a signal into a linear combination of - sinusoids (ie., basis elements in the space of continuous periodic - functions). For a sequence :math:`\mathbf{x} = [x_1, \ldots, x_N]` of N - evenly spaced samples, the `k` th DFT coefficient is given by: - - .. math:: - - c_k = \sum_{n=0}^{N-1} x_n \exp(-2 \pi i k n / N) - - where `i` is the imaginary unit, `k` is an index ranging from `0, ..., N-1`, - and :math:`X_k` is the complex coefficient representing the phase - (imaginary part) and amplitude (real part) of the `k` th sinusoid in the - DFT spectrum. The frequency of the `k` th sinusoid is :math:`(k 2 \pi / N)` - radians per sample. - - When applied to a real-valued input, the negative frequency terms are the - complex conjugates of the positive-frequency terms and the overall spectrum - is symmetric (excluding the first index, which contains the zero-frequency - / intercept term). - - Parameters - ---------- - frame : :py:class:`ndarray ` of shape `(N,)` - A signal frame consisting of N samples - positive_only : bool - Whether to only return the coefficients for the positive frequency - terms. Default is True. - - Returns - ------- - spectrum : :py:class:`ndarray ` of shape `(N,)` or `(N // 2 + 1,)` if `real_only` - The coefficients of the frequency spectrum for `frame`, including - imaginary components. - """ - N = len(frame) # window length - - # F[i,j] = coefficient for basis vector i, timestep j (i.e., k * n) - F = np.arange(N).reshape(1, -1) * np.arange(N).reshape(-1, 1) - F = np.exp(F * (-1j * 2 * np.pi / N)) - - # vdot only operates on vectors (rather than ndarrays), so we have to - # loop over each basis vector in F explicitly - spectrum = np.array([np.vdot(f, frame) for f in F]) - return spectrum[: (N // 2) + 1] if positive_only else spectrum - - -def dft_bins(N, fs=44000, positive_only=True): - """ - Calc the frequency bin centers for a DFT with `N` coefficients. - - Parameters - ---------- - N : int - The number of frequency bins in the DFT - fs : int - The sample rate/frequency of the signal (in Hz). Default is 44000. - positive_only : bool - Whether to only return the bins for the positive frequency - terms. Default is True. - - Returns - ------- - bins : :py:class:`ndarray ` of shape `(N,)` or `(N // 2 + 1,)` if `positive_only` - The frequency bin centers associated with each coefficient in the - DFT spectrum - """ - if positive_only: - freq_bins = np.linspace(0, fs / 2, 1 + N // 2, endpoint=True) - else: - l, r = (1 + (N - 1) / 2, (1 - N) / 2) if N % 2 else (N / 2, -N / 2) - freq_bins = np.r_[np.arange(l), np.arange(r, 0)] * fs / N - return freq_bins - - -def magnitude_spectrum(frames): - """ - Compute the magnitude spectrum (i.e., absolute value of the DFT spectrum) - for each frame in `frames`. Assumes each frame is real-valued only. - - Parameters - ---------- - frames : :py:class:`ndarray ` of shape `(M, N)` - A sequence of `M` frames each consisting of `N` samples - - Returns - ------- - magnitude_spec : :py:class:`ndarray ` of shape `(M, N // 2 + 1)` - The magnitude spectrum for each frame in `frames`. Only includes the - coefficients for the positive spectrum frequencies. - """ - return np.vstack([np.abs(DFT(frame, positive_only=True)) for frame in frames]) - - -def power_spectrum(frames, scale=False): - """ - Compute the power spectrum for a signal represented as a collection of - frames. Assumes each frame is real-valued only. - - The power spectrum is simply the square of the magnitude spectrum, possibly - scaled by the number of FFT bins. It measures how the energy of the signal - is distributed over the frequency domain. - - Parameters - ---------- - frames : :py:class:`ndarray ` of shape `(M, N)` - A sequence of `M` frames each consisting of `N` samples - scale : bool - Whether the scale by the number of DFT bins. Default is False. - - Returns - ------- - power_spec : :py:class:`ndarray ` of shape `(M, N // 2 + 1)` - The power spectrum for each frame in `frames`. Only includes the - coefficients for the positive spectrum frequencies. - """ - scaler = frames.shape[1] // 2 + 1 if scale else 1 - return (1 / scaler) * magnitude_spectrum(frames) ** 2 - - -####################################################################### -# Preprocessing Utils # -####################################################################### - - -def to_frames(x, frame_width, stride, writeable=False): - """ - Convert a 1D signal x into overlapping windows of width `frame_width` using - a hop length of `stride`. - - Notes - ----- - If ``(len(x) - frame_width) % stride != 0`` then some number of the samples - in x will be dropped. Specifically:: - - n_dropped_frames = len(x) - frame_width - stride * (n_frames - 1) - - where:: - - n_frames = (len(x) - frame_width) // stride + 1 - - This method uses low-level stride manipulation to avoid creating an - additional copy of `x`. The downside is that if ``writeable`=True``, - modifying the `frame` output can result in unexpected behavior: - - >>> out = to_frames(np.arange(6), 5, 1) - >>> out - array([[0, 1, 2, 3, 4], - [1, 2, 3, 4, 5]]) - >>> out[0, 1] = 99 - >>> out - array([[ 0, 99, 2, 3, 4], - [99, 2, 3, 4, 5]]) - - Parameters - ---------- - x : :py:class:`ndarray ` of shape `(N,)` - A 1D signal consisting of N samples - frame_width : int - The width of a single frame window in samples - stride : int - The hop size / number of samples advanced between consecutive frames - writeable : bool - If set to False, the returned array will be readonly. Otherwise it will - be writable if `x` was. It is advisable to set this to False whenever - possible to avoid unexpected behavior (see NB 2 above). Default is False. - - Returns - ------- - frame: :py:class:`ndarray ` of shape `(n_frames, frame_width)` - The collection of overlapping frames stacked into a matrix - """ - assert x.ndim == 1 - assert stride >= 1 - assert len(x) >= frame_width - - # get the size for an element in x in bits - byte = x.itemsize - n_frames = (len(x) - frame_width) // stride + 1 - return as_strided( - x, - shape=(n_frames, frame_width), - strides=(byte * stride, byte), - writeable=writeable, - ) - - -def autocorrelate1D(x): - """ - Autocorrelate a 1D signal `x` with itself. - - Notes - ----- - The `k` th term in the 1 dimensional autocorrelation is - - .. math:: - - a_k = \sum_n x_{n + k} x_n - - NB. This is a naive :math:`O(N^2)` implementation. For a faster :math:`O(N - \log N)` approach using the FFT, see [1]. - - References - ---------- - .. [1] https://en.wikipedia.org/wiki/Autocorrelation#Efficient%computation - - Parameters - ---------- - x : :py:class:`ndarray ` of shape `(N,)` - A 1D signal consisting of N samples - - Returns - ------- - auto : :py:class:`ndarray ` of shape `(N,)` - The autocorrelation of `x` with itself - """ - N = len(x) - auto = np.zeros(N) - for k in range(N): - for n in range(N - k): - auto[k] += x[n + k] * x[n] - return auto - - -####################################################################### -# Filters # -####################################################################### - - -def preemphasis(x, alpha): - """ - Increase the amplitude of high frequency bands + decrease the amplitude of - lower bands. - - Notes - ----- - Preemphasis filtering is (was?) a common transform in speech processing, - where higher frequencies tend to be more useful during signal - disambiguation. - - .. math:: - - \\text{preemphasis}( x_t ) = x_t - \\alpha x_{t-1} - - Parameters - ---------- - x : :py:class:`ndarray ` of shape `(N,)` - A 1D signal consisting of `N` samples - alpha : float in [0, 1) - The preemphasis coefficient. A value of 0 corresponds to no - filtering - - Returns - ------- - out : :py:class:`ndarray ` of shape `(N,)` - The filtered signal - """ - return np.concatenate([x[:1], x[1:] - alpha * x[:-1]]) - - -def cepstral_lifter(mfccs, D): - """ - A simple sinusoidal filter applied in the Mel-frequency domain. - - Notes - ----- - Cepstral lifting helps to smooth the spectral envelope and dampen the - magnitude of the higher MFCC coefficients while keeping the other - coefficients unchanged. The filter function is: - - .. math:: - - \\text{lifter}( x_n ) = x_n \left(1 + \\frac{D \sin(\pi n / D)}{2}\\right) - - Parameters - ---------- - mfccs : :py:class:`ndarray ` of shape `(G, C)` - Matrix of Mel cepstral coefficients. Rows correspond to frames, columns - to cepstral coefficients - D : int in :math:`[0, +\infty]` - The filter coefficient. 0 corresponds to no filtering, larger values - correspond to greater amounts of smoothing - - Returns - ------- - out : :py:class:`ndarray ` of shape `(G, C)` - The lifter'd MFCC coefficients - """ - if D == 0: - return mfccs - n = np.arange(mfccs.shape[1]) - return mfccs * (1 + (D / 2) * np.sin(np.pi * n / D)) - - -def mel_spectrogram( - x, - window_duration=0.025, - stride_duration=0.01, - mean_normalize=True, - window="hamming", - n_filters=20, - center=True, - alpha=0.95, - fs=44000, -): - """ - Apply the Mel-filterbank to the power spectrum for a signal `x`. - - Notes - ----- - The Mel spectrogram is the projection of the power spectrum of the framed - and windowed signal onto the basis set provided by the Mel filterbank. - - Parameters - ---------- - x : :py:class:`ndarray ` of shape `(N,)` - A 1D signal consisting of N samples - window_duration : float - The duration of each frame / window (in seconds). Default is 0.025. - stride_duration : float - The duration of the hop between consecutive windows (in seconds). - Default is 0.01. - mean_normalize : bool - Whether to subtract the coefficient means from the final filter values - to improve the signal-to-noise ratio. Default is True. - window : {'hamming', 'hann', 'blackman_harris'} - The windowing function to apply to the signal before FFT. Default is - 'hamming'. - n_filters : int - The number of mel filters to include in the filterbank. Default is 20. - center : bool - Whether to the `k` th frame of the signal should *begin* at index ``x[k * - stride_len]`` (center = False) or be *centered* at ``x[k * stride_len]`` - (center = True). Default is False. - alpha : float in [0, 1) - The coefficient for the preemphasis filter. A value of 0 corresponds to - no filtering. Default is 0.95. - fs : int - The sample rate/frequency for the signal. Default is 44000. - - Returns - ------- - filter_energies : :py:class:`ndarray ` of shape `(G, n_filters)` - The (possibly mean_normalized) power for each filter in the Mel - filterbank (i.e., the Mel spectrogram). Rows correspond to frames, - columns to filters - energy_per_frame : :py:class:`ndarray ` of shape `(G,)` - The total energy in each frame of the signal - """ - eps = np.finfo(float).eps - window_fn = WindowInitializer()(window) - - stride = round(stride_duration * fs) - frame_width = round(window_duration * fs) - N = frame_width - - # add a preemphasis filter to the raw signal - x = preemphasis(x, alpha) - - # convert signal to overlapping frames and apply a window function - x = np.pad(x, N // 2, "reflect") if center else x - frames = to_frames(x, frame_width, stride, fs) - - window = np.tile(window_fn(frame_width), (frames.shape[0], 1)) - frames = frames * window - - # compute the power spectrum - power_spec = power_spectrum(frames) - energy_per_frame = np.sum(power_spec, axis=1) - energy_per_frame[energy_per_frame == 0] = eps - - # compute the power at each filter in the Mel filterbank - fbank = mel_filterbank(N, n_filters=n_filters, fs=fs) - filter_energies = power_spec @ fbank.T - filter_energies -= np.mean(filter_energies, axis=0) if mean_normalize else 0 - filter_energies[filter_energies == 0] = eps - return filter_energies, energy_per_frame - - -####################################################################### -# Mel-Frequency Features # -####################################################################### - - -def mfcc( - x, - fs=44000, - n_mfccs=13, - alpha=0.95, - center=True, - n_filters=20, - window="hann", - normalize=True, - lifter_coef=22, - stride_duration=0.01, - window_duration=0.025, - replace_intercept=True, -): - """ - Compute the Mel-frequency cepstral coefficients (MFCC) for a signal. - - Notes - ----- - Computing MFCC features proceeds in the following stages: - - 1. Convert the signal into overlapping frames and apply a window fn - 2. Compute the power spectrum at each frame - 3. Apply the mel filterbank to the power spectra to get mel filterbank powers - 4. Take the logarithm of the mel filterbank powers at each frame - 5. Take the discrete cosine transform (DCT) of the log filterbank - energies and retain only the first k coefficients to further reduce - the dimensionality - - MFCCs were developed in the context of HMM-GMM automatic speech recognition - (ASR) systems and can be used to provide a somewhat speaker/pitch - invariant representation of phonemes. - - Parameters - ---------- - x : :py:class:`ndarray ` of shape `(N,)` - A 1D signal consisting of N samples - fs : int - The sample rate/frequency for the signal. Default is 44000. - n_mfccs : int - The number of cepstral coefficients to return (including the intercept - coefficient). Default is 13. - alpha : float in [0, 1) - The preemphasis coefficient. A value of 0 corresponds to no - filtering. Default is 0.95. - center : bool - Whether to the kth frame of the signal should *begin* at index ``x[k * - stride_len]`` (center = False) or be *centered* at ``x[k * stride_len]`` - (center = True). Default is True. - n_filters : int - The number of filters to include in the Mel filterbank. Default is 20. - normalize : bool - Whether to mean-normalize the MFCC values. Default is True. - lifter_coef : int in :math:[0, + \infty]` - The cepstral filter coefficient. 0 corresponds to no filtering, larger - values correspond to greater amounts of smoothing. Default is 22. - window : {'hamming', 'hann', 'blackman_harris'} - The windowing function to apply to the signal before taking the DFT. - Default is 'hann'. - stride_duration : float - The duration of the hop between consecutive windows (in seconds). - Default is 0.01. - window_duration : float - The duration of each frame / window (in seconds). Default is 0.025. - replace_intercept : bool - Replace the first MFCC coefficient (the intercept term) with the - log of the total frame energy instead. Default is True. - - Returns - ------- - mfccs : :py:class:`ndarray ` of shape `(G, C)` - Matrix of Mel-frequency cepstral coefficients. Rows correspond to - frames, columns to cepstral coefficients - """ - # map the power spectrum for the (framed + windowed representation of) `x` - # onto the mel scale - filter_energies, frame_energies = mel_spectrogram( - x=x, - fs=fs, - alpha=alpha, - center=center, - window=window, - n_filters=n_filters, - mean_normalize=False, - window_duration=window_duration, - stride_duration=stride_duration, - ) - - log_energies = 10 * np.log10(filter_energies) - - # perform a DCT on the log-mel coefficients to further reduce the data - # dimensionality -- the early DCT coefficients will capture the majority of - # the data, allowing us to discard coefficients > n_mfccs - mfccs = np.array([DCT(frame) for frame in log_energies])[:, :n_mfccs] - - mfccs = cepstral_lifter(mfccs, D=lifter_coef) - mfccs -= np.mean(mfccs, axis=0) if normalize else 0 - - if replace_intercept: - # the 0th MFCC coefficient doesn't tell us anything about the spectrum; - # replace it with the log of the frame energy for something more - # informative - mfccs[:, 0] = np.log(frame_energies) - return mfccs - - -def mel2hz(mel, formula="htk"): - """ - Convert the mel-scale representation of a signal into Hz - - Parameters - ---------- - mel : :py:class:`ndarray ` of shape `(N, \*)` - An array of mel frequencies to convert - formula : {"htk", "slaney"} - The Mel formula to use. "htk" uses the formula used by the Hidden - Markov Model Toolkit, and described in O'Shaughnessy (1987). "slaney" - uses the formula used in the MATLAB auditory toolbox (Slaney, 1998). - Default is 'htk' - - Returns - ------- - hz : :py:class:`ndarray ` of shape `(N, \*)` - The frequencies of the items in `mel`, in Hz - """ - fstr = "formula must be either 'htk' or 'slaney' but got '{}'" - assert formula in ["htk", "slaney"], fstr.format(formula) - if formula == "htk": - return 700 * (10 ** (mel / 2595) - 1) - raise NotImplementedError("slaney") - - -def hz2mel(hz, formula="htk"): - """ - Convert the frequency representaiton of a signal in Hz into the mel scale. - - Parameters - ---------- - hz : :py:class:`ndarray ` of shape `(N, \*)` - The frequencies of the items in `mel`, in Hz - formula : {"htk", "slaney"} - The Mel formula to use. "htk" uses the formula used by the Hidden - Markov Model Toolkit, and described in O'Shaughnessy (1987). "slaney" - uses the formula used in the MATLAB auditory toolbox (Slaney, 1998). - Default is 'htk'. - - Returns - ------- - mel : :py:class:`ndarray ` of shape `(N, \*)` - An array of mel frequencies to convert. - """ - fstr = "formula must be either 'htk' or 'slaney' but got '{}'" - assert formula in ["htk", "slaney"], fstr.format(formula) - - if formula == "htk": - return 2595 * np.log10(1 + hz / 700) - raise NotImplementedError("slaney") - - -def mel_filterbank( - N, n_filters=20, fs=44000, min_freq=0, max_freq=None, normalize=True -): - """ - Compute the filters in a Mel filterbank and return the corresponding - transformation matrix - - Notes - ----- - The Mel scale is a perceptual scale designed to simulate the way the human - ear works. Pitches judged by listeners to be equal in perceptual / - psychological distance have equal distance on the Mel scale. Practically, - this corresponds to a scale with higher resolution at low frequencies and - lower resolution at higher (> 500 Hz) frequencies. - - Each filter in the Mel filterbank is triangular with a response of 1 at its - center and a linear decay on both sides until it reaches the center - frequency of the next adjacent filter. - - This implementation is based on code in the (superb) LibROSA package [1]. - - References - ---------- - .. [1] McFee et al. (2015). "librosa: Audio and music signal analysis in - Python", *Proceedings of the 14th Python in Science Conference* - https://librosa.github.io - - Parameters - ---------- - N : int - The number of DFT bins - n_filters : int - The number of mel filters to include in the filterbank. Default is 20. - min_freq : int - Minimum filter frequency (in Hz). Default is 0. - max_freq : int - Maximum filter frequency (in Hz). Default is 0. - fs : int - The sample rate/frequency for the signal. Default is 44000. - normalize : bool - If True, scale the Mel filter weights by their area in Mel space. - Default is True. - - Returns - ------- - fbank : :py:class:`ndarray ` of shape `(n_filters, N // 2 + 1)` - The mel-filterbank transformation matrix. Rows correspond to filters, - columns to DFT bins. - """ - max_freq = fs / 2 if max_freq is None else max_freq - min_mel, max_mel = hz2mel(min_freq), hz2mel(max_freq) - - fbank = np.zeros((n_filters, N // 2 + 1)) - - # uniformly spaced values on the mel scale, translated back into Hz - mel_bins = mel2hz(np.linspace(min_mel, max_mel, n_filters + 2)) - - # the centers of the frequency bins for the DFT - hz_bins = dft_bins(N, fs) - - mel_spacing = np.diff(mel_bins) - - # ramps[i] = mel_bins[i] - hz_bins - ramps = mel_bins.reshape(-1, 1) - hz_bins.reshape(1, -1) - for i in range(n_filters): - # calc the filter values on the left and right across the bins ... - left = -ramps[i] / mel_spacing[i] - right = ramps[i + 2] / mel_spacing[i + 1] - - # .. and set them zero when they cross the x-axis - fbank[i] = np.maximum(0, np.minimum(left, right)) - - if normalize: - energy_norm = 2.0 / (mel_bins[2 : n_filters + 2] - mel_bins[:n_filters]) - fbank *= energy_norm[:, np.newaxis] - - return fbank diff --git a/aitk/keras/preprocessing/general.py b/aitk/keras/preprocessing/general.py deleted file mode 100644 index a53ac2b..0000000 --- a/aitk/keras/preprocessing/general.py +++ /dev/null @@ -1,388 +0,0 @@ -import json -import hashlib -import warnings - -import numpy as np - -try: - from scipy.sparse import csr_matrix - - _SCIPY = True -except ImportError: - warnings.warn("Scipy not installed. FeatureHasher can only create dense matrices") - _SCIPY = False - - -def minibatch(X, batchsize=256, shuffle=True): - """ - Compute the minibatch indices for a training dataset. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, \*)` - The dataset to divide into minibatches. Assumes the first dimension - represents the number of training examples. - batchsize : int - The desired size of each minibatch. Note, however, that if ``X.shape[0] % - batchsize > 0`` then the final batch will contain fewer than batchsize - entries. Default is 256. - shuffle : bool - Whether to shuffle the entries in the dataset before dividing into - minibatches. Default is True. - - Returns - ------- - mb_generator : generator - A generator which yields the indices into `X` for each batch. - n_batches: int - The number of batches. - """ - N = X.shape[0] - ix = np.arange(N) - n_batches = int(np.ceil(N / batchsize)) - - if shuffle: - np.random.shuffle(ix) - - def mb_generator(): - for i in range(n_batches): - yield ix[i * batchsize : (i + 1) * batchsize] - - return mb_generator(), n_batches - - -class OneHotEncoder: - def __init__(self): - """ - Convert between category labels and their one-hot vector - representations. - - Parameters - ---------- - categories : list of length `C` - List of the unique category labels for the items to encode. - """ - self._is_fit = False - self.hyperparameters = {} - self.parameters = {"categories": None} - - def __call__(self, labels): - return self.transform(labels) - - def fit(self, categories): - """ - Create mappings between columns and category labels. - - Parameters - ---------- - categories : list of length `C` - List of the unique category labels for the items to encode. - """ - self.parameters["categories"] = categories - self.cat2idx = {c: i for i, c in enumerate(categories)} - self.idx2cat = {i: c for i, c in enumerate(categories)} - self._is_fit = True - - def transform(self, labels, categories=None): - """ - Convert a list of labels into a one-hot encoding. - - Parameters - ---------- - labels : list of length `N` - A list of category labels. - categories : list of length `C` - List of the unique category labels for the items to encode. Default - is None. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(N, C)` - The one-hot encoded labels. Each row corresponds to an example, - with a single 1 in the column corresponding to the respective - label. - """ - if not self._is_fit: - categories = set(labels) if categories is None else categories - self.fit(categories) - - unknown = list(set(labels) - set(self.cat2idx.keys())) - assert len(unknown) == 0, "Unrecognized label(s): {}".format(unknown) - - N, C = len(labels), len(self.cat2idx) - cols = np.array([self.cat2idx[c] for c in labels]) - - Y = np.zeros((N, C)) - Y[np.arange(N), cols] = 1 - return Y - - def inverse_transform(self, Y): - """ - Convert a one-hot encoding back into the corresponding labels - - Parameters - ---------- - Y : :py:class:`ndarray ` of shape `(N, C)` - One-hot encoded labels. Each row corresponds to an example, with a - single 1 in the column associated with the label for that example - - Returns - ------- - labels : list of length `N` - The list of category labels corresponding to the nonzero columns in - `Y` - """ - C = len(self.cat2idx) - assert Y.ndim == 2, "Y must be 2D, but has shape {}".format(Y.shape) - assert Y.shape[1] == C, "Y must have {} columns, got {}".format(C, Y.shape[1]) - return [self.idx2cat[ix] for ix in Y.nonzero()[1]] - - -class Standardizer: - def __init__(self, with_mean=True, with_std=True): - """ - Feature-wise standardization for vector inputs. - - Notes - ----- - Due to the sensitivity of empirical mean and standard deviation - calculations to extreme values, `Standardizer` cannot guarantee - balanced feature scales in the presence of outliers. In particular, - note that because outliers for each feature can have different - magnitudes, the spread of the transformed data on each feature can be - very different. - - Similar to sklearn, `Standardizer` uses a biased estimator for the - standard deviation: ``numpy.std(x, ddof=0)``. - - Parameters - ---------- - with_mean : bool - Whether to scale samples to have 0 mean during transformation. - Default is True. - with_std : bool - Whether to scale samples to have unit variance during - transformation. Default is True. - """ - self.with_mean = with_mean - self.with_std = with_std - self._is_fit = False - - @property - def hyperparameters(self): - H = {"with_mean": self.with_mean, "with_std": self.with_std} - return H - - @property - def parameters(self): - params = { - "mean": self._mean if hasattr(self, "mean") else None, - "std": self._std if hasattr(self, "std") else None, - } - return params - - def __call__(self, X): - return self.transform(X) - - def fit(self, X): - """ - Store the feature-wise mean and standard deviation across the samples - in `X` for future scaling. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, C)` - An array of N samples, each with dimensionality `C` - """ - if not isinstance(X, np.ndarray): - X = np.array(X) - - if X.shape[0] < 2: - raise ValueError("`X` must contain at least 2 samples") - - std = np.ones(X.shape[1]) - mean = np.zeros(X.shape[1]) - - if self.with_mean: - mean = np.mean(X, axis=0) - - if self.with_std: - std = np.std(X, axis=0, ddof=0) - - self._mean = mean - self._std = std - self._is_fit = True - - def transform(self, X): - """ - Standardize features by removing the mean and scaling to unit variance. - - For a sample `x`, the standardized score is calculated as: - - .. math:: - - z = (x - u) / s - - where `u` is the mean of the training samples or zero if `with_mean` is - False, and `s` is the standard deviation of the training samples or 1 - if `with_std` is False. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, C)` - An array of N samples, each with dimensionality `C`. - - Returns - ------- - Z : :py:class:`ndarray ` of shape `(N, C)` - The feature-wise standardized version of `X`. - """ - if not self._is_fit: - raise Exception("Must call `fit` before using the `transform` method") - return (X - self._mean) / self._std - - def inverse_transform(self, Z): - """ - Convert a collection of standardized features back into the original - feature space. - - For a standardized sample `z`, the unstandardized score is calculated as: - - .. math:: - - x = z s + u - - where `u` is the mean of the training samples or zero if `with_mean` is - False, and `s` is the standard deviation of the training samples or 1 - if `with_std` is False. - - Parameters - ---------- - Z : :py:class:`ndarray ` of shape `(N, C)` - An array of `N` standardized samples, each with dimensionality `C`. - - Returns - ------- - X : :py:class:`ndarray ` of shape `(N, C)` - The unstandardixed samples from `Z`. - """ - assert self._is_fit, "Must fit `Standardizer` before calling inverse_transform" - P = self.parameters - mean, std = P["mean"], P["std"] - return Z * std + mean - - -class FeatureHasher: - def __init__(self, n_dim=256, sparse=True): - """ - Convert a collection of features to a fixed-dimensional matrix using - the hashing trick. - - Notes - ----- - Uses the md5 hash. - - Parameters - ---------- - n_dim : int - The dimensionality of each example in the output feature matrix. - Small numbers of features are likely to cause hash collisions, but - large numbers will cause larger overall parameter dimensions for - any (linear) learning agent. Default is 256. - sparse : bool - Whether the resulting feature matrix should be a sparse - :py:class:`csr_matrix ` or dense - :py:class:`ndarray `. Default is True. - """ - self.n_dim = n_dim - self.hash = hashlib.md5 - self.sparse = sparse and _SCIPY - - def encode(self, examples): - """ - Encode a collection of multi-featured examples into a - `n_dim`-dimensional feature matrix via feature hashing. - - Notes - ----- - Feature hashing works by applying a hash function to the features of an - example and using the hash values as column indices in the resulting - feature matrix. The entries at each hashed feature column correspond to - the values for that example and feature. For example, given the - following two input examples: - - >>> examples = [ - {"furry": 1, "quadruped": 1, "domesticated": 1}, - {"nocturnal": 1, "quadruped": 1}, - ] - - and a hypothetical hash function `H` mapping strings to [0, 127], we have: - - >>> feature_mat = zeros(2, 128) - >>> ex1_cols = [H("furry"), H("quadruped"), H("domesticated")] - >>> ex2_cols = [H("nocturnal"), H("quadruped")] - >>> feat_mat[0, ex1_cols] = 1 - >>> feat_mat[1, ex2_cols] = 1 - - To better handle hash collisions, it is common to multiply the feature - value by the sign of the digest for the corresponding feature name. - - Parameters - ---------- - examples : dict or list of dicts - A collection of `N` examples, each represented as a dict where keys - correspond to the feature name and values correspond to the feature - value. - - Returns - ------- - table : :py:class:`ndarray ` or :py:class:`csr_matrix ` of shape `(N, n_dim)` - The encoded feature matrix - """ - if isinstance(examples, dict): - examples = [examples] - - sparse = self.sparse - return self._encode_sparse(examples) if sparse else self._encode_dense(examples) - - def _encode_dense(self, examples): - N = len(examples) - table = np.zeros(N, self.n_dim) # dense - - for row, feat_dict in enumerate(examples): - for f_id, val in feat_dict.items(): - if isinstance(f_id, str): - f_id = f_id.encode("utf-8") - - # use json module to convert the feature id into a unique - # string compatible with the buffer API (required by hashlib) - if isinstance(f_id, (tuple, dict, list)): - f_id = json.dumps(f_id, sort_keys=True).encode("utf-8") - - h = int(self.hash(f_id).hexdigest(), base=16) - col = h % self.n_dim - table[row, col] += np.sign(h) * val - - return table - - def _encode_sparse(self, examples): - N = len(examples) - idxs, data = [], [] - - for row, feat_dict in enumerate(examples): - for f_id, val in feat_dict.items(): - if isinstance(f_id, str): - f_id = f_id.encode("utf-8") - - # use json module to convert the feature id into a unique - # string compatible with the buffer API (required by hashlib) - if isinstance(f_id, (tuple, dict, list)): - f_id = json.dumps(f_id, sort_keys=True).encode("utf-8") - - h = int(self.hash(f_id).hexdigest(), base=16) - col = h % self.n_dim - idxs.append((row, col)) - data.append(np.sign(h) * val) - - table = csr_matrix((data, zip(*idxs)), shape=(N, self.n_dim)) - return table diff --git a/aitk/keras/preprocessing/nlp.py b/aitk/keras/preprocessing/nlp.py deleted file mode 100644 index 68fc28e..0000000 --- a/aitk/keras/preprocessing/nlp.py +++ /dev/null @@ -1,1229 +0,0 @@ -"""Common preprocessing utilities for working with text data""" -import re -import heapq -import os.path as op -from collections import Counter - -import numpy as np - - -# This list of English stop words is taken from the "Glasgow Information -# Retrieval Group". The original list can be found at -# http://ir.dcs.gla.ac.uk/resources/linguistic_utils/stop_words -_STOP_WORDS = { - "a", - "about", - "above", - "across", - "after", - "afterwards", - "again", - "against", - "all", - "almost", - "alone", - "along", - "already", - "also", - "although", - "always", - "am", - "among", - "amongst", - "amoungst", - "amount", - "an", - "and", - "another", - "any", - "anyhow", - "anyone", - "anything", - "anyway", - "anywhere", - "are", - "around", - "as", - "at", - "back", - "be", - "became", - "because", - "become", - "becomes", - "becoming", - "been", - "before", - "beforehand", - "behind", - "being", - "below", - "beside", - "besides", - "between", - "beyond", - "bill", - "both", - "bottom", - "but", - "by", - "call", - "can", - "cannot", - "cant", - "co", - "con", - "could", - "couldnt", - "cry", - "de", - "describe", - "detail", - "do", - "done", - "down", - "due", - "during", - "each", - "eg", - "eight", - "either", - "eleven", - "else", - "elsewhere", - "empty", - "enough", - "etc", - "even", - "ever", - "every", - "everyone", - "everything", - "everywhere", - "except", - "few", - "fifteen", - "fifty", - "fill", - "find", - "fire", - "first", - "five", - "for", - "former", - "formerly", - "forty", - "found", - "four", - "from", - "front", - "full", - "further", - "get", - "give", - "go", - "had", - "has", - "hasnt", - "have", - "he", - "hence", - "her", - "here", - "hereafter", - "hereby", - "herein", - "hereupon", - "hers", - "herself", - "him", - "himself", - "his", - "how", - "however", - "hundred", - "i", - "ie", - "if", - "in", - "inc", - "indeed", - "interest", - "into", - "is", - "it", - "its", - "itself", - "keep", - "last", - "latter", - "latterly", - "least", - "less", - "ltd", - "made", - "many", - "may", - "me", - "meanwhile", - "might", - "mill", - "mine", - "more", - "moreover", - "most", - "mostly", - "move", - "much", - "must", - "my", - "myself", - "name", - "namely", - "neither", - "never", - "nevertheless", - "next", - "nine", - "no", - "nobody", - "none", - "noone", - "nor", - "not", - "nothing", - "now", - "nowhere", - "of", - "off", - "often", - "on", - "once", - "one", - "only", - "onto", - "or", - "other", - "others", - "otherwise", - "our", - "ours", - "ourselves", - "out", - "over", - "own", - "part", - "per", - "perhaps", - "please", - "put", - "rather", - "re", - "same", - "see", - "seem", - "seemed", - "seeming", - "seems", - "serious", - "several", - "she", - "should", - "show", - "side", - "since", - "sincere", - "six", - "sixty", - "so", - "some", - "somehow", - "someone", - "something", - "sometime", - "sometimes", - "somewhere", - "still", - "such", - "system", - "take", - "ten", - "than", - "that", - "the", - "their", - "them", - "themselves", - "then", - "thence", - "there", - "thereafter", - "thereby", - "therefore", - "therein", - "thereupon", - "these", - "they", - "thick", - "thin", - "third", - "this", - "those", - "though", - "three", - "through", - "throughout", - "thru", - "thus", - "to", - "together", - "too", - "top", - "toward", - "towards", - "twelve", - "twenty", - "two", - "un", - "under", - "until", - "up", - "upon", - "us", - "very", - "via", - "was", - "we", - "well", - "were", - "what", - "whatever", - "when", - "whence", - "whenever", - "where", - "whereafter", - "whereas", - "whereby", - "wherein", - "whereupon", - "wherever", - "whether", - "which", - "while", - "whither", - "who", - "whoever", - "whole", - "whom", - "whose", - "why", - "will", - "with", - "within", - "without", - "would", - "yet", - "you", - "your", - "yours", - "yourself", - "yourselves", -} - -_PUNCTUATION = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" - -_WORD_REGEX = re.compile(r"(?u)\b\w\w+\b") # sklearn default -_PUNC_TABLE = str.maketrans("", "", _PUNCTUATION) - - -def ngrams(sequence, N): - """Return all `N`-grams of the elements in `sequence`""" - assert N >= 1 - return list(zip(*[sequence[i:] for i in range(N)])) - - -def tokenize_words(line, lowercase=True, filter_stopwords=True): - """ - Split a string into individual lower-case words, optionally removing - punctuation and stop-words in the process - """ - words = _WORD_REGEX.findall(line.lower() if lowercase else line) - return remove_stop_words(words) if filter_stopwords else words - - -def tokenize_chars(line, lowercase=True, filter_punctuation=True): - """ - Split a string into individual lower-case words, optionally removing - punctuation and stop-words in the process - """ - line = line.lower() if lowercase else line - line = strip_punctuation(line) if filter_punctuation else line - chars = list(re.sub(" {2,}", " ", line).strip()) - return chars - - -def remove_stop_words(words): - """Remove stop words from a list of word strings""" - return [w for w in words if w not in _STOP_WORDS] - - -def strip_punctuation(line): - """Remove punctuation from a string""" - return line.translate(_PUNC_TABLE).strip() - - -####################################################################### -# Huffman Tree # -####################################################################### - - -class Node(object): - def __init__(self, key, val): - self.key = key - self.val = val - self.left = None - self.right = None - - def __gt__(self, other): - """Greater than""" - if not isinstance(other, Node): - return -1 - return self.val > other.val - - def __ge__(self, other): - """Greater than or equal to""" - if not isinstance(other, Node): - return -1 - return self.val >= other.val - - def __lt__(self, other): - """Less than""" - if not isinstance(other, Node): - return -1 - return self.val < other.val - - def __le__(self, other): - """Less than or equal to""" - if not isinstance(other, Node): - return -1 - return self.val <= other.val - - -class HuffmanEncoder(object): - def fit(self, text): - """ - Build a Huffman tree for the tokens in `text` and compute each token's - binary encoding. - - Notes - ----- - In a Huffman code, tokens that occur more frequently are (generally) - represented using fewer bits. Huffman codes produce the minimum expected - codeword length among all methods for encoding tokens individually. - - Huffman codes correspond to paths through a binary tree, with 1 - corresponding to "move right" and 0 corresponding to "move left". In - contrast to standard binary trees, the Huffman tree is constructed from the - bottom up. Construction begins by initializing a min-heap priority queue - consisting of each token in the corpus, with priority corresponding to the - token frequency. At each step, the two most infrequent tokens in the corpus - are removed and become the children of a parent pseudotoken whose - "frequency" is the sum of the frequencies of its children. This new parent - pseudotoken is added to the priority queue and the process is repeated - recursively until no tokens remain. - - Parameters - ---------- - text: list of strs or :class:`Vocabulary` instance - The tokenized text or a pretrained :class:`Vocabulary` object to use for - building the Huffman code. - """ - self._build_tree(text) - self._generate_codes() - - def transform(self, text): - """ - Transform the words in `text` into their Huffman-code representations. - - Parameters - ---------- - text: list of `N` strings - The list of words to encode - - Returns - ------- - codes : list of `N` binary strings - The encoded words in `text` - """ - if isinstance(text, str): - text = [text] - for token in set(text): - if token not in self._item2code: - raise Warning("Token '{}' not in Huffman tree. Skipping".format(token)) - return [self._item2code.get(t, None) for t in text] - - def inverse_transform(self, codes): - """ - Transform an encoded sequence of bit-strings back into words. - - Parameters - ---------- - codes : list of `N` binary strings - A list of encoded bit-strings, represented as strings. - - Returns - ------- - text: list of `N` strings - The decoded text. - """ - if isinstance(codes, str): - codes = [codes] - for code in set(codes): - if code not in self._code2item: - raise Warning("Code '{}' not in Huffman tree. Skipping".format(code)) - return [self._code2item.get(c, None) for c in codes] - - @property - def tokens(self): - """A list the unique tokens in `text`""" - return list(self._item2code.keys()) - - @property - def codes(self): - """A list with the Huffman code for each unique token in `text`""" - return list(self._code2item.keys()) - - def _counter(self, text): - counts = {} - for item in text: - counts[item] = counts.get(item, 0) + 1 - return counts - - def _build_tree(self, text): - """Construct Huffman Tree""" - PQ = [] - - if isinstance(text, Vocabulary): - counts = text.counts - else: - counts = self._counter(text) - - for (k, c) in counts.items(): - PQ.append(Node(k, c)) - - # create a priority queue with priority = item frequency - heapq.heapify(PQ) - - while len(PQ) > 1: - node1 = heapq.heappop(PQ) # item with smallest frequency - node2 = heapq.heappop(PQ) # item with second smallest frequency - - parent = Node(None, node1.val + node2.val) - parent.left = node1 - parent.right = node2 - - heapq.heappush(PQ, parent) - - self._root = heapq.heappop(PQ) - - def _generate_codes(self): - current_code = "" - self._item2code = {} - self._code2item = {} - self._build_code(self._root, current_code) - - def _build_code(self, root, current_code): - if root is None: - return - - if root.key is not None: - self._item2code[root.key] = current_code - self._code2item[current_code] = root.key - return - - # 0 = move left, 1 = move right - self._build_code(root.left, current_code + "0") - self._build_code(root.right, current_code + "1") - - -####################################################################### -# Containers # -####################################################################### - - -class Token: - def __init__(self, word): - self.count = 0 - self.word = word - - def __repr__(self): - """A string representation of the token""" - return "Token(word='{}', count={})".format(self.word, self.count) - - -class TFIDFEncoder: - def __init__( - self, - vocab=None, - lowercase=True, - min_count=0, - smooth_idf=True, - max_tokens=None, - input_type="filename", - filter_stopwords=True, - ): - r""" - An object for compiling and encoding the term-frequency - inverse-document-frequency (TF-IDF) representation of the tokens in a - text corpus. - - Notes - ----- - TF-IDF is intended to reflect how important a word is to a document in - a collection or corpus. For a word token `w` in a document `d`, and a - corpus, :math:`D = \{d_1, \ldots, d_N\}`, we have: - - .. math:: - \text{TF}(w, d) &= \text{num. occurences of }w \text{ in document }d \\ - \text{IDF}(w, D) &= \log \frac{|D|}{|\{ d \in D: t \in d \}|} - - Parameters - ---------- - vocab : :class:`Vocabulary` object or list-like - An existing vocabulary to filter the tokens in the corpus against. - Default is None. - lowercase : bool - Whether to convert each string to lowercase before tokenization. - Default is True. - min_count : int - Minimum number of times a token must occur in order to be included - in vocab. Default is 0. - smooth_idf : bool - Whether to add 1 to the denominator of the IDF calculation to avoid - divide-by-zero errors. Default is True. - max_tokens : int - Only add the `max_tokens` most frequent tokens that occur more - than `min_count` to the vocabulary. If None, add all tokens - greater that occur more than than `min_count`. Default is None. - input_type : {'filename', 'strings'} - If 'files', the sequence input to `fit` is expected to be a list - of filepaths. If 'strings', the input is expected to be a list of - lists, each sublist containing the raw strings for a single - document in the corpus. Default is 'filename'. - filter_stopwords : bool - Whether to remove stopwords before encoding the words in the - corpus. Default is True. - """ - # create a function to filter against words in the vocab - self._filter_vocab = lambda words: words - if isinstance(vocab, Vocabulary): - self._filter_vocab = vocab.filter - elif isinstance(vocab, (list, np.ndarray, set)): - vocab = set(vocab) - self._filter_vocab = lambda words: [ - w if w in vocab else "" for w in words - ] - - if input_type not in ["files", "strings"]: - fstr = "`input_type` must be either 'files' or 'strings', but got {}" - raise ValueError(fstr.format(input_type)) - - self._tokens = None - self._idx2doc = None - self.term_freq = None - self.idx2token = None - self.token2idx = None - self.inv_doc_freq = None - - self.hyperparameters = { - "id": "TFIDFEncoder", - "encoding": None, - "vocab": vocab - if not isinstance(vocab, Vocabulary) - else vocab.hyperparameters, - "lowercase": lowercase, - "min_count": min_count, - "input_type": input_type, - "max_tokens": max_tokens, - "smooth_idf": smooth_idf, - "filter_stopwords": filter_stopwords - if not isinstance(vocab, Vocabulary) - else vocab.hyperparameters["filter_stopwords"], - } - - def fit(self, corpus_seq, encoding="utf-8-sig"): - """ - Compute term-frequencies and inverse document frequencies on a - collection of documents. - - Parameters - ---------- - corpus_seq : str or list of strs - The filepath / list of filepaths / raw string contents of the - document(s) to be encoded, in accordance with the `input_type` - parameter passed to the :meth:`__init__` method. Each document is - expected to be a newline-separated strings of text, with adjacent - tokens separated by a whitespace character. - encoding : str - Specifies the text encoding for corpus if `input_type` is `files`. - Common entries are either 'utf-8' (no header byte), or 'utf-8-sig' - (header byte). Default is 'utf-8-sig'. - """ - H = self.hyperparameters - - if isinstance(corpus_seq, str): - corpus_seq = [corpus_seq] - - if H["input_type"] == "files": - for corpus_fp in corpus_seq: - assert op.isfile(corpus_fp), "{} does not exist".format(corpus_fp) - - tokens = [] - idx2token, token2idx = {}, {} - - # encode special tokens - for tt in ["", "", ""]: - token2idx[tt] = len(tokens) - idx2token[len(tokens)] = tt - tokens.append(Token(tt)) - - min_count = H["min_count"] - max_tokens = H["max_tokens"] - H["encoding"] = encoding - - bol_ix = token2idx[""] - eol_ix = token2idx[""] - idx2doc, term_freq = {}, {} - - # encode the text in `corpus_fps` without any filtering ... - for d_ix, doc in enumerate(corpus_seq): - doc_count = {} - idx2doc[d_ix] = doc if H["input_type"] == "files" else None - token2idx, idx2token, tokens, doc_count = self._encode_document( - doc, token2idx, idx2token, tokens, doc_count, bol_ix, eol_ix, - ) - term_freq[d_ix] = doc_count - - self._tokens = tokens - self._idx2doc = idx2doc - self.token2idx = token2idx - self.idx2token = idx2token - self.term_freq = term_freq - - # ... retain only the top `max_tokens` most frequent tokens, coding - # everything else as ... - if max_tokens is not None and len(tokens) > max_tokens: - self._keep_top_n_tokens() - - # ... replace all words occurring less than `min_count` by ... - if min(self._tokens, key=lambda t: t.count).count < min_count: - self._drop_low_freq_tokens() - - # ... sort tokens alphabetically and reindex ... - self._sort_tokens() - - # ... finally, calculate inverse document frequency - self._calc_idf() - - def _encode_document( - self, doc, word2idx, idx2word, tokens, doc_count, bol_ix, eol_ix, - ): - """Perform tokenization and compute token counts for a single document""" - H = self.hyperparameters - lowercase = H["lowercase"] - filter_stop = H["filter_stopwords"] - - if H["input_type"] == "files": - with open(doc, "r", encoding=H["encoding"]) as handle: - doc = handle.read() - - n_words = 0 - lines = doc.split("\n") - for line in lines: - words = tokenize_words(line, lowercase, filter_stop) - words = self._filter_vocab(words) - n_words += len(words) - - for ww in words: - if ww not in word2idx: - word2idx[ww] = len(tokens) - idx2word[len(tokens)] = ww - tokens.append(Token(ww)) - - t_idx = word2idx[ww] - tokens[t_idx].count += 1 - doc_count[t_idx] = doc_count.get(t_idx, 0) + 1 - - # wrap line in and tags - tokens[bol_ix].count += 1 - tokens[eol_ix].count += 1 - - doc_count[bol_ix] = doc_count.get(bol_ix, 0) + 1 - doc_count[eol_ix] = doc_count.get(eol_ix, 0) + 1 - return word2idx, idx2word, tokens, doc_count - - def _keep_top_n_tokens(self): - N = self.hyperparameters["max_tokens"] - doc_counts, word2idx, idx2word = {}, {}, {} - tokens = sorted(self._tokens, key=lambda x: x.count, reverse=True) - - # reindex the top-N tokens... - unk_ix = None - for idx, tt in enumerate(tokens[:N]): - word2idx[tt.word] = idx - idx2word[idx] = tt.word - - if tt.word == "": - unk_ix = idx - - # ... if isn't in the top-N, add it, replacing the Nth - # most-frequent word and adjust the count accordingly ... - if unk_ix is None: - unk_ix = self.token2idx[""] - old_count = tokens[N - 1].count - tokens[N - 1] = self._tokens[unk_ix] - tokens[N - 1].count += old_count - word2idx[""] = N - 1 - idx2word[N - 1] = "" - - # ... and recode all dropped tokens as "" - for tt in tokens[N:]: - tokens[unk_ix].count += tt.count - - # ... finally, reindex the word counts for each document - doc_counts = {} - for d_ix in self.term_freq.keys(): - doc_counts[d_ix] = {} - for old_ix, d_count in self.term_freq[d_ix].items(): - word = self.idx2token[old_ix] - new_ix = word2idx.get(word, unk_ix) - doc_counts[d_ix][new_ix] = doc_counts[d_ix].get(new_ix, 0) + d_count - - self._tokens = tokens[:N] - self.token2idx = word2idx - self.idx2token = idx2word - self.term_freq = doc_counts - - assert len(self._tokens) <= N - - def _drop_low_freq_tokens(self): - """ - Replace all tokens that occur less than `min_count` with the `` - token. - """ - H = self.hyperparameters - unk_token = self._tokens[self.token2idx[""]] - eol_token = self._tokens[self.token2idx[""]] - bol_token = self._tokens[self.token2idx[""]] - tokens = [unk_token, eol_token, bol_token] - - unk_idx = 0 - word2idx = {"": 0, "": 1, "": 2} - idx2word = {0: "", 1: "", 2: ""} - special = {"", "", ""} - - for tt in self._tokens: - if tt.word not in special: - if tt.count < H["min_count"]: - tokens[unk_idx].count += tt.count - else: - word2idx[tt.word] = len(tokens) - idx2word[len(tokens)] = tt.word - tokens.append(tt) - - # reindex document counts - doc_counts = {} - for d_idx in self.term_freq.keys(): - doc_counts[d_idx] = {} - for old_idx, d_count in self.term_freq[d_idx].items(): - word = self.idx2token[old_idx] - new_idx = word2idx.get(word, unk_idx) - doc_counts[d_idx][new_idx] = doc_counts[d_idx].get(new_idx, 0) + d_count - - self._tokens = tokens - self.token2idx = word2idx - self.idx2token = idx2word - self.term_freq = doc_counts - - def _sort_tokens(self): - # sort tokens alphabetically and recode - ix = 0 - token2idx, idx2token, = {}, {} - special = ["", "", ""] - words = sorted(self.token2idx.keys()) - term_freq = {d: {} for d in self.term_freq.keys()} - - for w in words: - if w not in special: - old_ix = self.token2idx[w] - token2idx[w], idx2token[ix] = ix, w - for d in self.term_freq.keys(): - if old_ix in self.term_freq[d]: - count = self.term_freq[d][old_ix] - term_freq[d][ix] = count - ix += 1 - - for w in special: - token2idx[w] = len(token2idx) - idx2token[len(idx2token)] = w - - self.token2idx = token2idx - self.idx2token = idx2token - self.term_freq = term_freq - self.vocab_counts = Counter({t.word: t.count for t in self._tokens}) - - def _calc_idf(self): - """ - Compute the (smoothed-) inverse-document frequency for each token in - the corpus. - - For a word token `w`, the IDF is simply - - IDF(w) = log ( |D| / |{ d in D: w in d }| ) + 1 - - where D is the set of all documents in the corpus, - - D = {d1, d2, ..., dD} - - If `smooth_idf` is True, we perform additive smoothing on the number of - documents containing a given word, equivalent to pretending that there - exists a final D+1st document that contains every word in the corpus: - - SmoothedIDF(w) = log ( |D| + 1 / [1 + |{ d in D: w in d }|] ) + 1 - """ - inv_doc_freq = {} - smooth_idf = self.hyperparameters["smooth_idf"] - tf, doc_idxs = self.term_freq, self._idx2doc.keys() - - D = len(self._idx2doc) + int(smooth_idf) - for word, w_ix in self.token2idx.items(): - d_count = int(smooth_idf) - d_count += np.sum([1 if w_ix in tf[d_ix] else 0 for d_ix in doc_idxs]) - inv_doc_freq[w_ix] = 1 if d_count == 0 else np.log(D / d_count) + 1 - self.inv_doc_freq = inv_doc_freq - - def transform(self, ignore_special_chars=True): - """ - Generate the term-frequency inverse-document-frequency encoding of a - text corpus. - - Parameters - ---------- - ignore_special_chars : bool - Whether to drop columns corresponding to "", "", and - "" tokens from the final tfidf encoding. Default is True. - - Returns - ------- - tfidf : numpy array of shape `(D, M [- 3])` - The encoded corpus, with each row corresponding to a single - document, and each column corresponding to a token id. The mapping - between column numbers and tokens is stored in the `idx2token` - attribute IFF `ignore_special_chars` is False. Otherwise, the - mappings are not accurate. - """ - D, N = len(self._idx2doc), len(self._tokens) - tf = np.zeros((D, N)) - idf = np.zeros((D, N)) - - for d_ix in self._idx2doc.keys(): - words, counts = zip(*self.term_freq[d_ix].items()) - docs = np.ones(len(words), dtype=int) * d_ix - tf[docs, words] = counts - - words = sorted(self.idx2token.keys()) - idf = np.tile(np.array([self.inv_doc_freq[w] for w in words]), (D, 1)) - tfidf = tf * idf - - if ignore_special_chars: - idxs = [ - self.token2idx[""], - self.token2idx[""], - self.token2idx[""], - ] - tfidf = np.delete(tfidf, idxs, 1) - - return tfidf - - -class Vocabulary: - def __init__( - self, lowercase=True, min_count=None, max_tokens=None, filter_stopwords=True, - ): - """ - An object for compiling and encoding the unique tokens in a text corpus. - - Parameters - ---------- - lowercase : bool - Whether to convert each string to lowercase before tokenization. - Default is True. - min_count : int - Minimum number of times a token must occur in order to be included - in vocab. If `None`, include all tokens from `corpus_fp` in vocab. - Default is None. - max_tokens : int - Only add the `max_tokens` most frequent tokens that occur more - than `min_count` to the vocabulary. If None, add all tokens - greater that occur more than than `min_count`. Default is None. - filter_stopwords : bool - Whether to remove stopwords before encoding the words in the - corpus. Default is True. - """ - self.hyperparameters = { - "id": "Vocabulary", - "encoding": None, - "corpus_fps": None, - "lowercase": lowercase, - "min_count": min_count, - "max_tokens": max_tokens, - "filter_stopwords": filter_stopwords, - } - - def __len__(self): - """Return the number of tokens in the vocabulary""" - return len(self._tokens) - - def __iter__(self): - """Return an iterator over the tokens in the vocabulary""" - return iter(self._tokens) - - def __contains__(self, word): - """Assert whether `word` is a token in the vocabulary""" - return word in self.token2idx - - def __getitem__(self, key): - """ - Return the token (if key is an integer) or the index (if key is a string) - for the key in the vocabulary, if it exists. - """ - if isinstance(key, str): - return self._tokens[self.token2idx[key]] - if isinstance(key, int): - return self._tokens[key] - - @property - def n_tokens(self): - """The number of unique word tokens in the vocabulary""" - return len(self.token2idx) - - @property - def n_words(self): - """The total number of words in the corpus""" - return sum(self.counts.values()) - - @property - def shape(self): - """The number of unique word tokens in the vocabulary""" - return self._tokens.shape - - def most_common(self, n=5): - """Return the top `n` most common tokens in the corpus""" - return self.counts.most_common()[:n] - - def words_with_count(self, k): - """Return all tokens that occur `k` times in the corpus""" - return [w for w, c in self.counts.items() if c == k] - - def filter(self, words, unk=True): # noqa: A003 - """ - Filter or replace any word in `words` that does not occur in - `Vocabulary` - - Parameters - ---------- - words : list of strs - A list of words to filter - unk : bool - Whether to replace any out of vocabulary words in `words` with the - token (unk = True) or skip them entirely (unk = False). - Default is True. - - Returns - ------- - filtered : list of strs - The list of words filtered against the vocabulary. - """ - if unk: - return [w if w in self else "" for w in words] - return [w for w in words if w in self] - - def words_to_indices(self, words): - """ - Convert the words in `words` to their token indices. If a word is not - in the vocabulary, return the index for the token - - Parameters - ---------- - words : list of strs - A list of words to filter - - Returns - ------- - indices : list of ints - The token indices for each word in `words` - """ - unk_ix = self.token2idx[""] - lowercase = self.hyperparameters["lowercase"] - words = [w.lower() for w in words] if lowercase else words - return [self.token2idx[w] if w in self else unk_ix for w in words] - - def indices_to_words(self, indices): - """ - Convert the indices in `indices` to their word values. If an index is - not in the vocabulary, return the the token. - - Parameters - ---------- - indices : list of ints - The token indices for each word in `words` - - Returns - ------- - words : list of strs - The word strings corresponding to each token index in `indices` - """ - unk = "" - return [self.idx2token[i] if i in self.idx2token else unk for i in indices] - - def fit(self, corpus_fps, encoding="utf-8-sig"): - """ - Compute the vocabulary across a collection of documents. - - Parameters - ---------- - corpus_fps : str or list of strs - The filepath / list of filepaths for the document(s) to be encoded. - Each document is expected to be encoded as newline-separated - string of text, with adjacent tokens separated by a whitespace - character. - encoding : str - Specifies the text encoding for corpus. Common entries are either - 'utf-8' (no header byte), or 'utf-8-sig' (header byte). Default is - 'utf-8-sig'. - """ - if isinstance(corpus_fps, str): - corpus_fps = [corpus_fps] - - for corpus_fp in corpus_fps: - assert op.isfile(corpus_fp), "{} does not exist".format(corpus_fp) - - tokens = [] - H = self.hyperparameters - idx2word, word2idx = {}, {} - - min_count = H["min_count"] - lowercase = H["lowercase"] - max_tokens = H["max_tokens"] - filter_stop = H["filter_stopwords"] - - H["encoding"] = encoding - H["corpus_fps"] = corpus_fps - - # encode special tokens - for tt in ["", "", ""]: - word2idx[tt] = len(tokens) - idx2word[len(tokens)] = tt - tokens.append(Token(tt)) - - bol_ix = word2idx[""] - eol_ix = word2idx[""] - - for d_ix, doc_fp in enumerate(corpus_fps): - with open(doc_fp, "r", encoding=H["encoding"]) as doc: - for line in doc: - words = tokenize_words(line, lowercase, filter_stop) - - for ww in words: - if ww not in word2idx: - word2idx[ww] = len(tokens) - idx2word[len(tokens)] = ww - tokens.append(Token(ww)) - - t_idx = word2idx[ww] - tokens[t_idx].count += 1 - - # wrap line in and tags - tokens[bol_ix].count += 1 - tokens[eol_ix].count += 1 - - self._tokens = tokens - self.token2idx = word2idx - self.idx2token = idx2word - - # replace all words occurring less than `min_count` by - if min_count is not None: - self._drop_low_freq_tokens() - - # retain only the top `max_tokens` most frequent tokens, coding - # everything else as - if max_tokens is not None and len(tokens) > max_tokens: - self._keep_top_n_tokens() - - counts = {w: self._tokens[ix].count for w, ix in self.token2idx.items()} - self.counts = Counter(counts) - self._tokens = np.array(self._tokens) - - def _keep_top_n_tokens(self): - word2idx, idx2word = {}, {} - N = self.hyperparameters["max_tokens"] - tokens = sorted(self._tokens, key=lambda x: x.count, reverse=True) - - # reindex the top-N tokens... - unk_ix = None - for idx, tt in enumerate(tokens[:N]): - word2idx[tt.word] = idx - idx2word[idx] = tt.word - - if tt.word == "": - unk_ix = idx - - # ... if isn't in the top-N, add it, replacing the Nth - # most-frequent word and adjusting the count accordingly ... - if unk_ix is None: - unk_ix = self.token2idx[""] - old_count = tokens[N - 1].count - tokens[N - 1] = self._tokens[unk_ix] - tokens[N - 1].count += old_count - word2idx[""] = N - 1 - idx2word[N - 1] = "" - - # ... and recode all dropped tokens as "" - for tt in tokens[N:]: - tokens[unk_ix].count += tt.count - - self._tokens = tokens[:N] - self.token2idx = word2idx - self.idx2token = idx2word - - assert len(self._tokens) <= N - - def _drop_low_freq_tokens(self): - """ - Replace all tokens that occur less than `min_count` with the `` - token. - """ - unk_idx = 0 - unk_token = self._tokens[self.token2idx[""]] - eol_token = self._tokens[self.token2idx[""]] - bol_token = self._tokens[self.token2idx[""]] - - H = self.hyperparameters - tokens = [unk_token, eol_token, bol_token] - word2idx = {"": 0, "": 1, "": 2} - idx2word = {0: "", 1: "", 2: ""} - special = {"", "", ""} - - for tt in self._tokens: - if tt.word not in special: - if tt.count < H["min_count"]: - tokens[unk_idx].count += tt.count - else: - word2idx[tt.word] = len(tokens) - idx2word[len(tokens)] = tt.word - tokens.append(tt) - - self._tokens = tokens - self.token2idx = word2idx - self.idx2token = idx2word diff --git a/aitk/keras/schedulers/README.md b/aitk/keras/schedulers/README.md deleted file mode 100644 index 8c69927..0000000 --- a/aitk/keras/schedulers/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Learning Rate Schedulers -The `schedulers` module implements several common strategies for learning rate -decay: - -- Constant -- Exponential decay -- Noam/Transformer decay ([Vaswani et al., 2017](https://arxiv.org/pdf/1706.03762.pdf)) -- Davis King/Dlib decay ([King, 2018](http://blog.dlib.net/2018/02/automatic-learning-rate-scheduling-that.html)) - -## Plots -

- -

diff --git a/aitk/keras/schedulers/__init__.py b/aitk/keras/schedulers/__init__.py deleted file mode 100644 index 99bcd9d..0000000 --- a/aitk/keras/schedulers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .schedulers import * diff --git a/aitk/keras/schedulers/img/plot.png b/aitk/keras/schedulers/img/plot.png deleted file mode 100644 index 43a54fa35216a22f1905e3cf4210d7c74a77ae12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 383571 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 z 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 mb)#<+6BZ;4nu7k0 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
Eq)rIJcx z4I>uBqYR1f3!@bqU)%Nps_=e?(P;o!3r)RbPIMR%rVPh|=H(k8kI)c411=j#qNov1 zBX!n6-#7AhGjY-1?|L5}U)Rbpv9xrsw8_nqahFhy>0(w=3LcQXs8?)ypwqO}8_C4% zw&Y#&p>C9XFG8s?Zp_1yL?ENz979x0H4L`awB);l_~xMq+Z+B~7#hiVR|i{DKIalw zwzB@rTH&wI_0J9ISGi2C&m&B8ax(^4+&Y!cJFli3dTPpMz?joU;-<`flo&A z{PjD#a2S3K0o13`hbCMmZ~|n!nc3NmFsZu(jXg4Vfq7F4FhDkeTOgii?(r?B85ZtB zCM;ai`l{h&jwFRtW%2j#v6_Z#;r`UTf^Po9e{!bQs9?xbXPS#%Jfgbw#^hMiR&#{r zi=jtN*w`2U{Pf$A3~4WzAuVKdD`3V!moV+ed8s+|$ewyjNwcA^>!6+&^_YA)`Q}f0 z6J?xf>=)w8P1waDA(?L5aU0G+`A7M~BZlE)#m6+d^9{W${IT;z-SOP*pG(g6Nj9Jc z%*@UG26~a_AeNni&iF1upagK37LsYS>wIfQOcyUzm@{orxj1Al?!1W0S3+UY zKyEZf7&%A9$JI?XG+`WJHIla)Aoh584w{EHFD_o z1;w-qGY+6UMg(>szPT!P{3mc^zDS!L=uKeV!ipF)W!u4A!5#V)N3d=y@0^Hn=#-xo zSSH%UbTBobw$)^N&cRX1p`;LaJ*vUnSy;AYsqg0Z-rfSwh}vBjQ!@!p>uV2xzcHX2 zHXJf;JUY5mI9X-(-TM9Vis5)Lv!>kqS?2W`&gBJy=rsOnteXst z#FRPfPm=;+*Bp5~MCWH`$0NUO_*}i6_VB!%8f`P@N?BhiFcEg6DKp>r?ZdFYFaBN> zb+6d`-k%kzHaTXfaPZPqVV}4oUQzVf+1ZGi6MD3(e4`GM)q57^=F^}cwRO2ACPoX6 z{q?uXM+0D7R{jAc3ep^;kOPVqo@Wih!>j-yK@lkJzngt=U5JrS-=B?Ak{WS(`VV?XFy+TvF+q0AwGg5328-u3Igu{2VyWVpM2fK+Fc)e1;|hJJIva6s73AUZoy1F z2zk)*=_Ei0-1tes7y*ZUw+a^4y-1wYIlXH(HdVrCc&N~@lvG?}y{C7>7P{`;vDJ?0 z9vZ<7@E2fslw!xlQ2s@wq}$^mMy;S`z=%w9Wrbu^HUCkSKj#IR(OCcNX{=eA0&%s# z$}z9yoHz$tFXG0TJ~5n$B9-4uSf!LBj*q`8* zElVPp%FrgYRj7~WYGZjB+najcl#^hhP2ry6glnX5XAlHsoflHJM=S|-Fw z4ta3E{CQHm0o{}dC&eEiVhF6#8-6jvg=`bh-v6GW0Mk^&%-~1t9SrERKnGM0vpGRw z0)c7R3&rtkTXvaQt{vV1Qs~V;=~C@lzUpgJese2SJtFf}9d)zgLciP#?q13n$qdUNtln}jBd|q=;|?RL?K$wpnohz(OJY4-XFy z!5XaT=;&wwxkAFioHq?nZu*)ukQaOnv?wmYE%A2GW2Lr9OL`+W9 z_h9qn&H8(-SULDFa@jLm%v?QfQ(nh#5UB4zIxW5LZlgr=SCPbiBEA-qCuJ3O^qHxI)@#o;uVO~{p!epWUni_Wfj?P77Z%B})6N$_*}8@F&&vj$hw;a1v8 z#B?y9Pp3__D^r+A{P^)>5r{1jlQ+p3oPhefAI^`@VIppuE6aau1R$JhouHK|;5DJy z5(a$ny(SEw!|8r$oaO|y!C4rCl`^R#30617A zPjTf6jlwJp7xKrA%<;LiI85=45bxgjOZh&1Tfc?cA+v@c)5|OZT7jQZOhV*wpHDU_ zK7v)VKF@<|oTqtdYA|E}c}L=W@g_Dua*d~QFzp}E?MDb1-~DJpEU;3@{5HGae&}H3 z0VtKblH5*mXWw&mAUu1^BVbD+nJL0s-x+pEx>1GTrw&Z95`kTC_F{?)>-zCSN?Dl* z@`5f#%Lf5;BJ|g4Xo$V6BUbPQas+M>;y)c8P*w|1&Rpb3QX|4-d10PxGRP6k`_vIP zE+>1EbY7Ul+DIak#LfK1WLjWQVW{^(iT1PC1Xrd{aoeB=@e~zbzI-e>OTjBZQ#u#^ zUBnRAAUtKv3-8c8UPwJ6wU`G05=p64znqOdIOasc2O0fMA3drP!aV;`MWz4j1SvT< zxCQsHAvQNqxDX5iX$Ud!LSQGbVJh9Ar-ndPgh&V8_rK`SY)f^Ez<(EvcBg8O)|-H& z%n3dTJs#WJ+eNVdL3K6*XY5nU?)ED6Wg$AcDeH+xNyrAUxOt3meP=tU+050lS3|3c z>3^O1C`&;{^i#FZ&TG$o{#DNCi`@?0W;w+|*1;MzF_5u3phDGu90%O^;=Dts2H3`L(ty552Y0d0im8_dFV z!%J$uqGnlH)HG@!rnIBoHNt32;fZzRR634D&r7+wvO8xo^-Ud<)mH>7KcXxG7g_e+L{`PDY}xfpy0On6NmW+;&C=R`6i6P zDN4K07!8!SzsCSNMLw0CYvmIWU}*9c9Fcy*$O$}XVZ7})Xy9ft)ffc+v`$v>z?ky} zqahO)($uB_oXX6}k%a9H>`g~tBO)`GKQ2?kPjdQ0A+H1pCxjdw8A(YD$k9S# z>>y;~67UTdo$q&n8fnyXB@aMeBxV+@h2|8)CD#{dDZ%qNrHqm{r{7McZ@B@tv>XwvFO)gSU6O zTo;a{?b&s*_^ifeqh*uQ(lZb3oSrmSFS^D@{cs*7V?z{$h$lD5y?pw&fptFxxKq`#<9C54M#bRSbLH%K zLCmD+`2A0=4-Ya63S@x;i^#(u`~#7UuKc9}@cL}y>_iNH-I{yc+`-8t#r9%NNhk}`blA~M&QYrrIBGfPL>JZqy$Sp1$_rA->r4!sTdo^{Mkp!*$ zb!cFZZG*?dK+B|y3B(&G>i$Q}j7?VUok5s1J#)uoqH@(YF5+THsyI|#1O$phBtk`F z*njyKmJ}n_^_vWM?I{}!T(4IqBNw^1sBNJ?$? zE8oqyfJ8|MB+ga{_IwTXCJL3ElY{(}Amys<-G;$vU*d6iSlBmk;6X_A_=pGzMqbz` zDBsz^^5c0+i%icQ$miaKl4~fc?*9Gz?#o#jf53ris;HGl5>hBp;BJNAh6Y=ru-w#c&Gl zqk^9w@AjFisj2bFoVOX!Xv6`m%|nTA=rMO0<*%F90_u-Yq&zMkb-Dx@ov*;uWa_rx z!6g*h2~)46|1)^bp2A+`xH<+^}a!?R7BXy%pE6d^Z@$!4f(ow5#r;AqFvM#AvdT=8vuUwHH<M|hZpyHj~Kj>M| zAgR0_xO4ytBB6zJkQwLXaWrA8m&p4chP%uuv1sLW_5y0ZKLDa&3L}3MqOC3J?^=!sdkQ`X)5YDQ#jkq@Dt|!ENejrw z2vNu;hbZ5l^S}Fw_k%+>P~gtOxx5p3(_kcJ@M`TF;y>XppH=4mNXc8;cg#Fnsk<(x zKjK9ve#~y_M3(ud`53oNyPBC$P_o!EX<3F8Gllr32`dhIy)9>D;B_1Ty6E`jUm{&| zQnrL?P0`qqE=j}Ofv!|K=5CoCwfLdKP3eXIr1QvF~4S4_jvQQpKY||=frLubPAMne{|$c(lNohNBa&v z=QYMm(51+X^h%f0TGj`*nodaHZVs#qbAPzCyHeIcvZR48gM)h8q#l#ch5Nvs9)7#v+zhG*bCiyWxo5W<(UJ+%1 z7i53rXKYi(U&2Ez)Ph-fO&aU_9X|<@fwhj{26o>8x5t6R0VqZ^qFRs9v`v)>22=

h7&)yz33Z|hb6xl`T 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

%L4U66F6rH=TH1zEFN`fCFe>*yUG9s%o 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 z
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�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 ZR;;gLIVr=)F--0cRYdY)!uhNB{x8ooS-=1Q diff --git a/aitk/keras/schedulers/schedulers.py b/aitk/keras/schedulers/schedulers.py deleted file mode 100644 index 756f343..0000000 --- a/aitk/keras/schedulers/schedulers.py +++ /dev/null @@ -1,362 +0,0 @@ -from copy import deepcopy -from abc import ABC, abstractmethod - -import numpy as np - -from math import erf - - -def gaussian_cdf(x, mean, var): - """ - Compute the probability that a random draw from a 1D Gaussian with mean - `mean` and variance `var` is less than or equal to `x`. - """ - eps = np.finfo(float).eps - x_scaled = (x - mean) / np.sqrt(var + eps) - return (1 + erf(x_scaled / np.sqrt(2))) / 2 - - -class SchedulerBase(ABC): - def __init__(self): - """Abstract base class for all Scheduler objects.""" - self.hyperparameters = {} - - def __call__(self, step=None, cur_loss=None): - return self.learning_rate(step=step, cur_loss=cur_loss) - - def copy(self): - """Return a copy of the current object.""" - return deepcopy(self) - - def set_params(self, hparam_dict): - """Set the scheduler hyperparameters from a dictionary.""" - if hparam_dict is not None: - for k, v in hparam_dict.items(): - if k in self.hyperparameters: - self.hyperparameters[k] = v - - @abstractmethod - def learning_rate(self, step=None): - raise NotImplementedError - - -class ConstantScheduler(SchedulerBase): - def __init__(self, lr=0.01, **kwargs): - """ - Returns a fixed learning rate, regardless of the current step. - - Parameters - ---------- - initial_lr : float - The learning rate. Default is 0.01 - """ - super().__init__() - self.lr = lr - self.hyperparameters = {"id": "ConstantScheduler", "lr": self.lr} - - def __str__(self): - return "ConstantScheduler(lr={})".format(self.lr) - - def learning_rate(self, **kwargs): - """ - Return the current learning rate. - - Returns - ------- - lr : float - The learning rate - """ - return self.lr - - -class ExponentialScheduler(SchedulerBase): - def __init__( - self, initial_lr=0.01, stage_length=500, staircase=False, decay=0.1, **kwargs - ): - """ - An exponential learning rate scheduler. - - Notes - ----- - The exponential scheduler decays the learning rate by `decay` every - `stage_length` steps, starting from `initial_lr`:: - - learning_rate = initial_lr * decay ** curr_stage - - where:: - - curr_stage = step / stage_length if staircase = False - curr_stage = floor(step / stage_length) if staircase = True - - Parameters - ---------- - initial_lr : float - The learning rate at the first step. Default is 0.01. - stage_length : int - The length of each stage, in steps. Default is 500. - staircase : bool - If True, only adjusts the learning rate at the stage transitions, - producing a step-like decay schedule. If False, adjusts the - learning rate after each step, creating a smooth decay schedule. - Default is False. - decay : float - The amount to decay the learning rate at each new stage. Default is - 0.1. - """ - super().__init__() - self.decay = decay - self.staircase = staircase - self.initial_lr = initial_lr - self.stage_length = stage_length - self.hyperparameters = { - "id": "StepScheduler", - "decay": self.decay, - "staircase": self.staircase, - "initial_lr": self.initial_lr, - "stage_length": self.stage_length, - } - - def __str__(self): - return "ExponentialScheduler(initial_lr={}, stage_length={}, staircase={}, decay={})".format( - self.initial_lr, self.stage_length, self.staircase, self.decay - ) - - def learning_rate(self, step, **kwargs): - """ - Return the current learning rate as a function of `step`. - - Parameters - ---------- - step : int - The current step number. - - Returns - ------- - lr : float - The learning rate for the current step. - """ - cur_stage = step / self.stage_length - if self.staircase: - cur_stage = np.floor(cur_stage) - return self.initial_lr * self.decay ** cur_stage - - -class NoamScheduler(SchedulerBase): - def __init__(self, model_dim=512, scale_factor=1, warmup_steps=4000, **kwargs): - """ - The Noam learning rate scheduler, originally used in conjunction with - the Adam optimizer in [1]. - - Notes - ----- - The Noam scheduler increases the learning rate linearly for the first - `warmup_steps` steps, and decreases it thereafter proportionally to the - inverse square root of the step number:: - - lr = scale_factor * ( (model_dim ** (-0.5)) * adj_step ) - adj_step = min(step_num ** (-0.5), step_num * warmup_steps ** (-1.5)) - - References - ---------- - .. [1] Vaswani et al. (2017) "Attention is all you need". *31st - Conference on Neural Information Processing Systems*, - https://arxiv.org/pdf/1706.03762.pdf - - Parameters - ---------- - model_dim : int - The number of units in the layer output. Default is 512. - scale_factor : float - A fixed coefficient for rescaling the final learning rate. Default - is 1. - warmup_steps : int - The number of steps in the warmup stage of training. Default is - 4000. - """ - super().__init__() - self.model_dim = model_dim - self.scale_factor = scale_factor - self.warmup_steps = warmup_steps - self.hyperparameters = { - "id": "NoamScheduler", - "model_dim": self.model_dim, - "scale_factor": self.scale_factor, - "warmup_steps": self.warmup_steps, - } - - def __str__(self): - return "NoamScheduler(model_dim={}, scale_factor={}, warmup_steps={})".format( - self.model_dim, self.scale_factor, self.warmup_steps - ) - - def learning_rate(self, step, **kwargs): - warmup, d_model = self.warmup_steps, self.model_dim - new_lr = d_model ** (-0.5) * min(step ** (-0.5), step * warmup ** (-1.5)) - return self.scale_factor * new_lr - - -class KingScheduler(SchedulerBase): - def __init__(self, initial_lr=0.01, patience=1000, decay=0.99, **kwargs): - """ - The Davis King / DLib learning rate scheduler. - - Notes - ----- - The KingScheduler computes the probability that the slope of the OLS - fit to the loss history is negative. If the probability that it is - negative is less than 51% over the last `patience` steps, the scheduler - exponentially decreases the current learning rate by `decay`. - - References - ---------- - .. [1] King, D. (2018). "Automatic learning rate scheduling that really - works". http://blog.dlib.net/2018/02/automatic-learning-rate-scheduling-that.html - - Parameters - ---------- - initial_lr : float - The learning rate to begin at. Default is 0.01. - patience : int - Amount of time to maintain the current learning rate without a - decrease in loss before adjustment. Default is 1000. - decay : float - The amount to decay the learning rate at each new stage. Default is - 0.99. - """ - super().__init__() - self.decay = decay - self.patience = patience - self.initial_lr = initial_lr - self.current_lr = initial_lr - self.max_history = np.ceil(1.1 * (patience + 1)).astype(int) - - self.loss_history = [] - self.hyperparameters = { - "id": "KingScheduler", - "decay": self.decay, - "patience": self.patience, - "initial_lr": self.initial_lr, - } - - def __str__(self): - return "KingScheduler(initial_lr={}, patience={}, decay={})".format( - self.initial_lr, self.patience, self.decay - ) - - def _steps_without_decrease(self, robust=False, check_all=False): - """ - Returns the maximum number of timesteps for which `P(loss is decreasing) - < 0.51`. - - Parameters - ---------- - robust : bool - If `robust=True`, first filter out the largest 10% of the loss - values to remove transient spikes in the loss due to, e.g., a few - bad minibatches. Default is False. - check_all : bool - If False, returns the maximum number of timesteps for which P(loss - is decreasing) < 0.51. If True, only checks whether the number of - timesteps for which P(loss is decreasing) < 0.51 is equal to - ``self.patience``. The former provides more information but is - significantly more computationally expensive. Default is False. - - Returns - ------- - steps_without_decrease: int - The maximum number of steps back in loss_history for which P(loss - is decreasing) < 0.51. - """ - lh = np.array(self.loss_history) - - # drop top 10% of loss values to filter out large loss spikes - if robust: - thresh = np.quantile(lh, 0.9) - lh = np.array([i for i in lh if i <= thresh]) - - N = len(lh) - steps_without_decrease = 0 - if check_all: - for i in reversed(range(N - 2)): - if self._p_decreasing(lh, i) < 0.51: - steps_without_decrease = N - i - else: - i = max(0, N - self.patience - 1) - if self._p_decreasing(lh, i) < 0.51: - steps_without_decrease = N - i - return steps_without_decrease - - def _p_decreasing(self, loss_history, i): - """ - Compute the probability that the slope of the OLS fit to the loss - history is negative. - - Parameters - ---------- - loss_history : numpy array of shape (N,) - The sequence of loss values for the previous `N` minibatches. - i : int - Compute P(Slope < 0) beginning at index i in `history`. - - Returns - ------ - p_decreasing : float - The probability that the slope of the OLS fit to loss_history is - less than or equal to 0. - """ - loss = loss_history[i:] - N = len(loss) - - # perform OLS on the loss entries to calc the slope mean - X = np.c_[np.ones(N), np.arange(i, len(loss_history))] - intercept, s_mean = np.linalg.inv(X.T @ X) @ X.T @ loss - loss_pred = s_mean * X[:, 1] + intercept - - # compute the variance of our loss predictions and use this to compute - # the (unbiased) estimate of the slope variance - loss_var = 1 / (N - 2) * np.sum((loss - loss_pred) ** 2) - s_var = (12 * loss_var) / (N ** 3 - N) - - # compute the probability that a random sample from a Gaussian - # parameterized by s_mean and s_var is less than or equal to 0 - p_decreasing = gaussian_cdf(0, s_mean, s_var) - return p_decreasing - - def learning_rate(self, step, cur_loss): - """ - Compute the updated learning rate for the current step and loss. - - Parameters - ---------- - step : int - The current step number. Unused. - cur_loss : float - The loss at the current step. - - Returns - ------- - lr : float - The learning rate for the current step. - """ - if cur_loss is None: - raise ValueError("cur_loss must be a float, but got {}".format(cur_loss)) - - # this happens if we initialize the scheduler from a string / dict - if not hasattr(self, "max_history"): - self.max_history = np.ceil(1.1 * (self.patience + 1)).astype(int) - patience, max_history = self.patience, self.max_history - - self.loss_history.append(cur_loss) - if len(self.loss_history) < patience: - return self.current_lr - self.loss_history = self.loss_history[-max_history:] - - # if the loss has not decreased for `patience` timesteps, drop the - # learning rate - if ( - self._steps_without_decrease() > patience - and self._steps_without_decrease(robust=True) > patience - ): - self.current_lr *= self.decay - - return self.current_lr diff --git a/aitk/keras/utils/README.md b/aitk/keras/utils/README.md deleted file mode 100644 index e4231b3..0000000 --- a/aitk/keras/utils/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Utilities - -The `utils.py` module implements common, neural network-specific helper -functions, primarily for dealing with CNNs. It includes: - -- `im2col` -- `col2im` -- `conv1D` -- `conv2D` -- `dilate` -- `deconv2D` -- `minibatch` -- Various weight initialization utilities -- Various padding and convolution arithmetic utilities diff --git a/aitk/keras/utils/__init__.py b/aitk/keras/utils/__init__.py deleted file mode 100644 index 1a100c6..0000000 --- a/aitk/keras/utils/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Common neural network-specific helper functions. - -The ``neural_nets.utils` module contains neural network-specific helper -functions, primarily for dealing with CNNs. -""" - -from .utils import * diff --git a/aitk/keras/utils/utils.py b/aitk/keras/utils/utils.py deleted file mode 100644 index f435cfc..0000000 --- a/aitk/keras/utils/utils.py +++ /dev/null @@ -1,1052 +0,0 @@ -import numpy as np - -def topological_sort(layers): - """ - Given a list of layers, produce a topological - sorted list, from input(s) to output(s). - """ - stack = [] - visited = set() - for layer in reversed(layers): - if layer not in visited: - visit_node(layer, stack, visited) - return reversed(stack) - -def visit_node(layer, stack, visited): - """ - Utility function for topological_sort. - """ - visited.add(layer) - for out_layer in layer.output_layers: - if out_layer not in visited: - visit_node(out_layer, stack, visited) - stack.append(layer) - -####################################################################### -# Training Utils # -####################################################################### - - -def minibatch(X, batchsize=256, shuffle=True): - """ - Compute the minibatch indices for a training dataset. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(N, \*)` - The dataset to divide into minibatches. Assumes the first dimension - represents the number of training examples. - batchsize : int - The desired size of each minibatch. Note, however, that if ``X.shape[0] % - batchsize > 0`` then the final batch will contain fewer than batchsize - entries. Default is 256. - shuffle : bool - Whether to shuffle the entries in the dataset before dividing into - minibatches. Default is True. - - Returns - ------- - mb_generator : generator - A generator which yields the indices into X for each batch - n_batches: int - The number of batches - """ - N = X.shape[0] - ix = np.arange(N) - n_batches = int(np.ceil(N / batchsize)) - - if shuffle: - np.random.shuffle(ix) - - def mb_generator(): - for i in range(n_batches): - yield ix[i * batchsize : (i + 1) * batchsize] - - return mb_generator(), n_batches - - -####################################################################### -# Padding Utils # -####################################################################### - - -def calc_pad_dims_2D(X_shape, out_dim, kernel_shape, stride, dilation=0): - """ - Compute the padding necessary to ensure that convolving `X` with a 2D kernel - of shape `kernel_shape` and stride `stride` produces outputs with dimension - `out_dim`. - - Parameters - ---------- - X_shape : tuple of `(n_ex, in_rows, in_cols, in_ch)` - Dimensions of the input volume. Padding is applied to `in_rows` and - `in_cols`. - out_dim : tuple of `(out_rows, out_cols)` - The desired dimension of an output example after applying the - convolution. - kernel_shape : 2-tuple - The dimension of the 2D convolution kernel. - stride : int - The stride for the convolution kernel. - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - - Returns - ------- - padding_dims : 4-tuple - Padding dims for `X`. Organized as (left, right, up, down) - """ - if not isinstance(X_shape, tuple): - raise ValueError("`X_shape` must be of type tuple") - - if not isinstance(out_dim, tuple): - raise ValueError("`out_dim` must be of type tuple") - - if not isinstance(kernel_shape, tuple): - raise ValueError("`kernel_shape` must be of type tuple") - - if not isinstance(stride, int): - raise ValueError("`stride` must be of type int") - - d = dilation - fr, fc = kernel_shape - out_rows, out_cols = out_dim - n_ex, in_rows, in_cols, in_ch = X_shape - - # update effective filter shape based on dilation factor - _fr, _fc = fr * (d + 1) - d, fc * (d + 1) - d - - pr = int((stride * (out_rows - 1) + _fr - in_rows) / 2) - pc = int((stride * (out_cols - 1) + _fc - in_cols) / 2) - - out_rows1 = int(1 + (in_rows + 2 * pr - _fr) / stride) - out_cols1 = int(1 + (in_cols + 2 * pc - _fc) / stride) - - # add asymmetric padding pixels to right / bottom - pr1, pr2 = pr, pr - if out_rows1 == out_rows - 1: - pr1, pr2 = pr, pr + 1 - elif out_rows1 != out_rows: - raise AssertionError - - pc1, pc2 = pc, pc - if out_cols1 == out_cols - 1: - pc1, pc2 = pc, pc + 1 - elif out_cols1 != out_cols: - raise AssertionError - - if any(np.array([pr1, pr2, pc1, pc2]) < 0): - raise ValueError( - "Padding cannot be less than 0. Got: {}".format((pr1, pr2, pc1, pc2)) - ) - return (pr1, pr2, pc1, pc2) - - -def calc_pad_dims_1D(X_shape, l_out, kernel_width, stride, dilation=0, causal=False): - """ - Compute the padding necessary to ensure that convolving `X` with a 1D kernel - of shape `kernel_shape` and stride `stride` produces outputs with length - `l_out`. - - Parameters - ---------- - X_shape : tuple of `(n_ex, l_in, in_ch)` - Dimensions of the input volume. Padding is applied on either side of - `l_in`. - l_out : int - The desired length an output example after applying the convolution. - kernel_width : int - The width of the 1D convolution kernel. - stride : int - The stride for the convolution kernel. - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - causal : bool - Whether to compute the padding dims for a regular or causal - convolution. If causal, padding is added only to the left side of the - sequence. Default is False. - - Returns - ------- - padding_dims : 2-tuple - Padding dims for X. Organized as (left, right) - """ - if not isinstance(X_shape, tuple): - raise ValueError("`X_shape` must be of type tuple") - - if not isinstance(l_out, int): - raise ValueError("`l_out` must be of type int") - - if not isinstance(kernel_width, int): - raise ValueError("`kernel_width` must be of type int") - - if not isinstance(stride, int): - raise ValueError("`stride` must be of type int") - - d = dilation - fw = kernel_width - n_ex, l_in, in_ch = X_shape - - # update effective filter shape based on dilation factor - _fw = fw * (d + 1) - d - total_pad = int((stride * (l_out - 1) + _fw - l_in)) - - if not causal: - pw = total_pad // 2 - l_out1 = int(1 + (l_in + 2 * pw - _fw) / stride) - - # add asymmetric padding pixels to right / bottom - pw1, pw2 = pw, pw - if l_out1 == l_out - 1: - pw1, pw2 = pw, pw + 1 - elif l_out1 != l_out: - raise AssertionError - - if causal: - # if this is a causal convolution, only pad the left side of the - # sequence - pw1, pw2 = total_pad, 0 - l_out1 = int(1 + (l_in + total_pad - _fw) / stride) - assert l_out1 == l_out - - if any(np.array([pw1, pw2]) < 0): - raise ValueError("Padding cannot be less than 0. Got: {}".format((pw1, pw2))) - return (pw1, pw2) - - -def pad1D(X, pad, kernel_width=None, stride=None, dilation=0): - """ - Zero-pad a 3D input volume `X` along the second dimension. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, l_in, in_ch)` - Input volume. Padding is applied to `l_in`. - pad : tuple, int, or {'same', 'causal'} - The padding amount. If 'same', add padding to ensure that the output - length of a 1D convolution with a kernel of `kernel_shape` and stride - `stride` is the same as the input length. If 'causal' compute padding - such that the output both has the same length as the input AND - ``output[t]`` does not depend on ``input[t + 1:]``. If 2-tuple, - specifies the number of padding columns to add on each side of the - sequence. - kernel_width : int - The dimension of the 2D convolution kernel. Only relevant if p='same' - or 'causal'. Default is None. - stride : int - The stride for the convolution kernel. Only relevant if p='same' or - 'causal'. Default is None. - dilation : int - The dilation of the convolution kernel. Only relevant if p='same' or - 'causal'. Default is None. - - Returns - ------- - X_pad : :py:class:`ndarray ` of shape `(n_ex, padded_seq, in_channels)` - The padded output volume - p : 2-tuple - The number of 0-padded columns added to the (left, right) of the sequences - in `X`. - """ - p = pad - if isinstance(p, int): - p = (p, p) - - if isinstance(p, tuple): - X_pad = np.pad( - X, - pad_width=((0, 0), (p[0], p[1]), (0, 0)), - mode="constant", - constant_values=0, - ) - - # compute the correct padding dims for a 'same' or 'causal' convolution - if p in ["same", "causal"] and kernel_width and stride: - causal = p == "causal" - p = calc_pad_dims_1D( - X.shape, X.shape[1], kernel_width, stride, causal=causal, dilation=dilation - ) - X_pad, p = pad1D(X, p) - - return X_pad, p - - -def pad2D(X, pad, kernel_shape=None, stride=None, dilation=0): - """ - Zero-pad a 4D input volume `X` along the second and third dimensions. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume. Padding is applied to `in_rows` and `in_cols`. - pad : tuple, int, or 'same' - The padding amount. If 'same', add padding to ensure that the output of - a 2D convolution with a kernel of `kernel_shape` and stride `stride` - has the same dimensions as the input. If 2-tuple, specifies the number - of padding rows and colums to add *on both sides* of the rows/columns - in `X`. If 4-tuple, specifies the number of rows/columns to add to the - top, bottom, left, and right of the input volume. - kernel_shape : 2-tuple - The dimension of the 2D convolution kernel. Only relevant if p='same'. - Default is None. - stride : int - The stride for the convolution kernel. Only relevant if p='same'. - Default is None. - dilation : int - The dilation of the convolution kernel. Only relevant if p='same'. - Default is 0. - - Returns - ------- - X_pad : :py:class:`ndarray ` of shape `(n_ex, padded_in_rows, padded_in_cols, in_channels)` - The padded output volume. - p : 4-tuple - The number of 0-padded rows added to the (top, bottom, left, right) of - `X`. - """ - p = pad - if isinstance(p, int): - p = (p, p, p, p) - - if isinstance(p, tuple): - if len(p) == 2: - p = (p[0], p[0], p[1], p[1]) - - X_pad = np.pad( - X, - pad_width=((0, 0), (p[0], p[1]), (p[2], p[3]), (0, 0)), - mode="constant", - constant_values=0, - ) - - # compute the correct padding dims for a 'same' convolution - if p == "same" and kernel_shape and stride is not None: - p = calc_pad_dims_2D( - X.shape, X.shape[1:3], kernel_shape, stride, dilation=dilation - ) - X_pad, p = pad2D(X, p) - return X_pad, p - - -def dilate(X, d): - """ - Dilate the 4D volume `X` by `d`. - - Notes - ----- - For a visual depiction of a dilated convolution, see [1]. - - References - ---------- - .. [1] Dumoulin & Visin (2016). "A guide to convolution arithmetic for deep - learning." https://arxiv.org/pdf/1603.07285v1.pdf - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume. - d : int - The number of 0-rows to insert between each adjacent row + column in `X`. - - Returns - ------- - Xd : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The dilated array where - - .. math:: - - \\text{out_rows} &= \\text{in_rows} + d(\\text{in_rows} - 1) \\\\ - \\text{out_cols} &= \\text{in_cols} + d (\\text{in_cols} - 1) - """ - n_ex, in_rows, in_cols, n_in = X.shape - r_ix = np.repeat(np.arange(1, in_rows), d) - c_ix = np.repeat(np.arange(1, in_cols), d) - Xd = np.insert(X, r_ix, 0, axis=1) - Xd = np.insert(Xd, c_ix, 0, axis=2) - return Xd - - -####################################################################### -# Convolution Arithmetic # -####################################################################### - - -def calc_fan(weight_shape): - """ - Compute the fan-in and fan-out for a weight matrix/volume. - - Parameters - ---------- - weight_shape : tuple - The dimensions of the weight matrix/volume. The final 2 entries must be - `in_ch`, `out_ch`. - - Returns - ------- - fan_in : int - The number of input units in the weight tensor - fan_out : int - The number of output units in the weight tensor - """ - if len(weight_shape) == 2: - fan_in, fan_out = weight_shape - elif len(weight_shape) in [3, 4]: - in_ch, out_ch = weight_shape[-2:] - kernel_size = np.prod(weight_shape[:-2]) - fan_in, fan_out = in_ch * kernel_size, out_ch * kernel_size - else: - raise ValueError("Unrecognized weight dimension: {}".format(weight_shape)) - return fan_in, fan_out - - -def calc_conv_out_dims(X_shape, W_shape, stride=1, pad=0, dilation=0): - """ - Compute the dimension of the output volume for the specified convolution. - - Parameters - ---------- - X_shape : 3-tuple or 4-tuple - The dimensions of the input volume to the convolution. If 3-tuple, - entries are expected to be (`n_ex`, `in_length`, `in_ch`). If 4-tuple, - entries are expected to be (`n_ex`, `in_rows`, `in_cols`, `in_ch`). - weight_shape : 3-tuple or 4-tuple - The dimensions of the weight volume for the convolution. If 3-tuple, - entries are expected to be (`f_len`, `in_ch`, `out_ch`). If 4-tuple, - entries are expected to be (`fr`, `fc`, `in_ch`, `out_ch`). - pad : tuple, int, or {'same', 'causal'} - The padding amount. If 'same', add padding to ensure that the output - length of a 1D convolution with a kernel of `kernel_shape` and stride - `stride` is the same as the input length. If 'causal' compute padding - such that the output both has the same length as the input AND - ``output[t]`` does not depend on ``input[t + 1:]``. If 2-tuple, specifies the - number of padding columns to add on each side of the sequence. Default - is 0. - stride : int - The stride for the convolution kernel. Default is 1. - dilation : int - The dilation of the convolution kernel. Default is 0. - - Returns - ------- - out_dims : 3-tuple or 4-tuple - The dimensions of the output volume. If 3-tuple, entries are (`n_ex`, - `out_length`, `out_ch`). If 4-tuple, entries are (`n_ex`, `out_rows`, - `out_cols`, `out_ch`). - """ - dummy = np.zeros(X_shape) - s, p, d = stride, pad, dilation - if len(X_shape) == 3: - _, p = pad1D(dummy, p) - pw1, pw2 = p - fw, in_ch, out_ch = W_shape - n_ex, in_length, in_ch = X_shape - - _fw = fw * (d + 1) - d - out_length = (in_length + pw1 + pw2 - _fw) // s + 1 - out_dims = (n_ex, out_length, out_ch) - - elif len(X_shape) == 4: - _, p = pad2D(dummy, p) - pr1, pr2, pc1, pc2 = p - fr, fc, in_ch, out_ch = W_shape - n_ex, in_rows, in_cols, in_ch = X_shape - - # adjust effective filter size to account for dilation - _fr, _fc = fr * (d + 1) - d, fc * (d + 1) - d - out_rows = (in_rows + pr1 + pr2 - _fr) // s + 1 - out_cols = (in_cols + pc1 + pc2 - _fc) // s + 1 - out_dims = (n_ex, out_rows, out_cols, out_ch) - else: - raise ValueError("Unrecognized number of input dims: {}".format(len(X_shape))) - return out_dims - - -####################################################################### -# Convolution Vectorization Utils # -####################################################################### - - -def _im2col_indices(X_shape, fr, fc, p, s, d=0): - """ - Helper function that computes indices into X in prep for columnization in - :func:`im2col`. - - Code extended from Andrej Karpathy's `im2col.py` - """ - pr1, pr2, pc1, pc2 = p - n_ex, n_in, in_rows, in_cols = X_shape - - # adjust effective filter size to account for dilation - _fr, _fc = fr * (d + 1) - d, fc * (d + 1) - d - - out_rows = (in_rows + pr1 + pr2 - _fr) // s + 1 - out_cols = (in_cols + pc1 + pc2 - _fc) // s + 1 - - if any([out_rows <= 0, out_cols <= 0]): - raise ValueError( - "Dimension mismatch during convolution: " - "out_rows = {}, out_cols = {}".format(out_rows, out_cols) - ) - - # i1/j1 : row/col templates - # i0/j0 : n. copies (len) and offsets (values) for row/col templates - i0 = np.repeat(np.arange(fr), fc) - i0 = np.tile(i0, n_in) * (d + 1) - i1 = s * np.repeat(np.arange(out_rows), out_cols) - j0 = np.tile(np.arange(fc), fr * n_in) * (d + 1) - j1 = s * np.tile(np.arange(out_cols), out_rows) - - # i.shape = (fr * fc * n_in, out_height * out_width) - # j.shape = (fr * fc * n_in, out_height * out_width) - # k.shape = (fr * fc * n_in, 1) - i = i0.reshape(-1, 1) + i1.reshape(1, -1) - j = j0.reshape(-1, 1) + j1.reshape(1, -1) - k = np.repeat(np.arange(n_in), fr * fc).reshape(-1, 1) - return k, i, j - - -def im2col(X, W_shape, pad, stride, dilation=0): - """ - Pads and rearrange overlapping windows of the input volume into column - vectors, returning the concatenated padded vectors in a matrix `X_col`. - - Notes - ----- - A NumPy reimagining of MATLAB's ``im2col`` 'sliding' function. - - Code extended from Andrej Karpathy's ``im2col.py``. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume (not padded). - W_shape: 4-tuple containing `(kernel_rows, kernel_cols, in_ch, out_ch)` - The dimensions of the weights/kernels in the present convolutional - layer. - pad : tuple, int, or 'same' - The padding amount. If 'same', add padding to ensure that the output of - a 2D convolution with a kernel of `kernel_shape` and stride `stride` - produces an output volume of the same dimensions as the input. If - 2-tuple, specifies the number of padding rows and colums to add *on both - sides* of the rows/columns in X. If 4-tuple, specifies the number of - rows/columns to add to the top, bottom, left, and right of the input - volume. - stride : int - The stride of each convolution kernel - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - - Returns - ------- - X_col : :py:class:`ndarray ` of shape (Q, Z) - The reshaped input volume where where: - - .. math:: - - Q &= \\text{kernel_rows} \\times \\text{kernel_cols} \\times \\text{n_in} \\\\ - Z &= \\text{n_ex} \\times \\text{out_rows} \\times \\text{out_cols} - """ - fr, fc, n_in, n_out = W_shape - s, p, d = stride, pad, dilation - n_ex, in_rows, in_cols, n_in = X.shape - - # zero-pad the input - X_pad, p = pad2D(X, p, W_shape[:2], stride=s, dilation=d) - pr1, pr2, pc1, pc2 = p - - # shuffle to have channels as the first dim - X_pad = X_pad.transpose(0, 3, 1, 2) - - # get the indices for im2col - k, i, j = _im2col_indices((n_ex, n_in, in_rows, in_cols), fr, fc, p, s, d) - - X_col = X_pad[:, k, i, j] - X_col = X_col.transpose(1, 2, 0).reshape(fr * fc * n_in, -1) - return X_col, p - - -def col2im(X_col, X_shape, W_shape, pad, stride, dilation=0): - """ - Take columns of a 2D matrix and rearrange them into the blocks/windows of - a 4D image volume. - - Notes - ----- - A NumPy reimagining of MATLAB's ``col2im`` 'sliding' function. - - Code extended from Andrej Karpathy's ``im2col.py``. - - Parameters - ---------- - X_col : :py:class:`ndarray ` of shape `(Q, Z)` - The columnized version of `X` (assumed to include padding) - X_shape : 4-tuple containing `(n_ex, in_rows, in_cols, in_ch)` - The original dimensions of `X` (not including padding) - W_shape: 4-tuple containing `(kernel_rows, kernel_cols, in_ch, out_ch)` - The dimensions of the weights in the present convolutional layer - pad : 4-tuple of `(left, right, up, down)` - Number of zero-padding rows/cols to add to `X` - stride : int - The stride of each convolution kernel - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - - Returns - ------- - img : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - The reshaped `X_col` input matrix - """ - if not (isinstance(pad, tuple) and len(pad) == 4): - raise TypeError("pad must be a 4-tuple, but got: {}".format(pad)) - - s, d = stride, dilation - pr1, pr2, pc1, pc2 = pad - fr, fc, n_in, n_out = W_shape - n_ex, in_rows, in_cols, n_in = X_shape - - X_pad = np.zeros((n_ex, n_in, in_rows + pr1 + pr2, in_cols + pc1 + pc2)) - k, i, j = _im2col_indices((n_ex, n_in, in_rows, in_cols), fr, fc, pad, s, d) - - X_col_reshaped = X_col.reshape(n_in * fr * fc, -1, n_ex) - X_col_reshaped = X_col_reshaped.transpose(2, 0, 1) - - np.add.at(X_pad, (slice(None), k, i, j), X_col_reshaped) - - pr2 = None if pr2 == 0 else -pr2 - pc2 = None if pc2 == 0 else -pc2 - return X_pad[:, :, pr1:pr2, pc1:pc2] - - -####################################################################### -# Convolution # -####################################################################### - - -def conv2D(X, W, stride, pad, dilation=0): - """ - A faster (but more memory intensive) implementation of the 2D "convolution" - (technically, cross-correlation) of input `X` with a collection of kernels in - `W`. - - Notes - ----- - Relies on the :func:`im2col` function to perform the convolution as a single - matrix multiplication. - - For a helpful diagram, see Pete Warden's 2015 blogpost [1]. - - References - ---------- - .. [1] Warden (2015). "Why GEMM is at the heart of deep learning," - https://petewarden.com/2015/04/20/why-gemm-is-at-the-heart-of-deep-learning/ - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume (unpadded). - W: :py:class:`ndarray ` of shape `(kernel_rows, kernel_cols, in_ch, out_ch)` - A volume of convolution weights/kernels for a given layer. - stride : int - The stride of each convolution kernel. - pad : tuple, int, or 'same' - The padding amount. If 'same', add padding to ensure that the output of - a 2D convolution with a kernel of `kernel_shape` and stride `stride` - produces an output volume of the same dimensions as the input. If - 2-tuple, specifies the number of padding rows and colums to add *on both - sides* of the rows/columns in `X`. If 4-tuple, specifies the number of - rows/columns to add to the top, bottom, left, and right of the input - volume. - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - - Returns - ------- - Z : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The covolution of `X` with `W`. - """ - s, d = stride, dilation - _, p = pad2D(X, pad, W.shape[:2], s, dilation=dilation) - - pr1, pr2, pc1, pc2 = p - fr, fc, in_ch, out_ch = W.shape - n_ex, in_rows, in_cols, in_ch = X.shape - - # update effective filter shape based on dilation factor - _fr, _fc = fr * (d + 1) - d, fc * (d + 1) - d - - # compute the dimensions of the convolution output - out_rows = int((in_rows + pr1 + pr2 - _fr) / s + 1) - out_cols = int((in_cols + pc1 + pc2 - _fc) / s + 1) - - # convert X and W into the appropriate 2D matrices and take their product - X_col, _ = im2col(X, W.shape, p, s, d) - W_col = W.transpose(3, 2, 0, 1).reshape(out_ch, -1) - - Z = (W_col @ X_col).reshape(out_ch, out_rows, out_cols, n_ex).transpose(3, 1, 2, 0) - - return Z - - -def conv1D(X, W, stride, pad, dilation=0): - """ - A faster (but more memory intensive) implementation of a 1D "convolution" - (technically, cross-correlation) of input `X` with a collection of kernels in - `W`. - - Notes - ----- - Relies on the :func:`im2col` function to perform the convolution as a single - matrix multiplication. - - For a helpful diagram, see Pete Warden's 2015 blogpost [1]. - - References - ---------- - .. [1] Warden (2015). "Why GEMM is at the heart of deep learning," - https://petewarden.com/2015/04/20/why-gemm-is-at-the-heart-of-deep-learning/ - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, l_in, in_ch)` - Input volume (unpadded) - W: :py:class:`ndarray ` of shape `(kernel_width, in_ch, out_ch)` - A volume of convolution weights/kernels for a given layer - stride : int - The stride of each convolution kernel - pad : tuple, int, or 'same' - The padding amount. If 'same', add padding to ensure that the output of - a 1D convolution with a kernel of `kernel_shape` and stride `stride` - produces an output volume of the same dimensions as the input. If - 2-tuple, specifies the number of padding colums to add *on both sides* - of the columns in X. - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - - Returns - ------- - Z : :py:class:`ndarray ` of shape `(n_ex, l_out, out_ch)` - The convolution of X with W. - """ - _, p = pad1D(X, pad, W.shape[0], stride, dilation=dilation) - - # add a row dimension to X to permit us to use im2col/col2im - X2D = np.expand_dims(X, axis=1) - W2D = np.expand_dims(W, axis=0) - p2D = (0, 0, p[0], p[1]) - Z2D = conv2D(X2D, W2D, stride, p2D, dilation) - - # drop the row dimension - return np.squeeze(Z2D, axis=1) - - -def deconv2D_naive(X, W, stride, pad, dilation=0): - """ - Perform a "deconvolution" (more accurately, a transposed convolution) of an - input volume `X` with a weight kernel `W`, incorporating stride, pad, and - dilation. - - Notes - ----- - Rather than using the transpose of the convolution matrix, this approach - uses a direct convolution with zero padding, which, while conceptually - straightforward, is computationally inefficient. - - For further explanation, see [1]. - - References - ---------- - .. [1] Dumoulin & Visin (2016). "A guide to convolution arithmetic for deep - learning." https://arxiv.org/pdf/1603.07285v1.pdf - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume (not padded) - W: :py:class:`ndarray ` of shape `(kernel_rows, kernel_cols, in_ch, out_ch)` - A volume of convolution weights/kernels for a given layer - stride : int - The stride of each convolution kernel - pad : tuple, int, or 'same' - The padding amount. If 'same', add padding to ensure that the output of - a 2D convolution with a kernel of `kernel_shape` and stride `stride` - produces an output volume of the same dimensions as the input. If - 2-tuple, specifies the number of padding rows and colums to add *on both - sides* of the rows/columns in `X`. If 4-tuple, specifies the number of - rows/columns to add to the top, bottom, left, and right of the input - volume. - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, n_out)` - The decovolution of (padded) input volume `X` with `W` using stride `s` and - dilation `d`. - """ - if stride > 1: - X = dilate(X, stride - 1) - stride = 1 - - # pad the input - X_pad, p = pad2D(X, pad, W.shape[:2], stride=stride, dilation=dilation) - - n_ex, in_rows, in_cols, n_in = X_pad.shape - fr, fc, n_in, n_out = W.shape - s, d = stride, dilation - pr1, pr2, pc1, pc2 = p - - # update effective filter shape based on dilation factor - _fr, _fc = fr * (d + 1) - d, fc * (d + 1) - d - - # compute deconvolution output dims - out_rows = s * (in_rows - 1) - pr1 - pr2 + _fr - out_cols = s * (in_cols - 1) - pc1 - pc2 + _fc - out_dim = (out_rows, out_cols) - - # add additional padding to achieve the target output dim - _p = calc_pad_dims_2D(X_pad.shape, out_dim, W.shape[:2], s, d) - X_pad, pad = pad2D(X_pad, _p, W.shape[:2], stride=s, dilation=dilation) - - # perform the forward convolution using the flipped weight matrix (note - # we set pad to 0, since we've already added padding) - Z = conv2D(X_pad, np.rot90(W, 2), s, 0, d) - - pr2 = None if pr2 == 0 else -pr2 - pc2 = None if pc2 == 0 else -pc2 - return Z[:, pr1:pr2, pc1:pc2, :] - - -def conv2D_naive(X, W, stride, pad, dilation=0): - """ - A slow but more straightforward implementation of a 2D "convolution" - (technically, cross-correlation) of input `X` with a collection of kernels `W`. - - Notes - ----- - This implementation uses ``for`` loops and direct indexing to perform the - convolution. As a result, it is slower than the vectorized :func:`conv2D` - function that relies on the :func:`col2im` and :func:`im2col` - transformations. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, in_rows, in_cols, in_ch)` - Input volume. - W: :py:class:`ndarray ` of shape `(kernel_rows, kernel_cols, in_ch, out_ch)` - The volume of convolution weights/kernels. - stride : int - The stride of each convolution kernel. - pad : tuple, int, or 'same' - The padding amount. If 'same', add padding to ensure that the output of - a 2D convolution with a kernel of `kernel_shape` and stride `stride` - produces an output volume of the same dimensions as the input. If - 2-tuple, specifies the number of padding rows and colums to add *on both - sides* of the rows/columns in `X`. If 4-tuple, specifies the number of - rows/columns to add to the top, bottom, left, and right of the input - volume. - dilation : int - Number of pixels inserted between kernel elements. Default is 0. - - Returns - ------- - Z : :py:class:`ndarray ` of shape `(n_ex, out_rows, out_cols, out_ch)` - The covolution of `X` with `W`. - """ - s, d = stride, dilation - X_pad, p = pad2D(X, pad, W.shape[:2], stride=s, dilation=d) - - pr1, pr2, pc1, pc2 = p - fr, fc, in_ch, out_ch = W.shape - n_ex, in_rows, in_cols, in_ch = X.shape - - # update effective filter shape based on dilation factor - fr, fc = fr * (d + 1) - d, fc * (d + 1) - d - - out_rows = int((in_rows + pr1 + pr2 - fr) / s + 1) - out_cols = int((in_cols + pc1 + pc2 - fc) / s + 1) - - Z = np.zeros((n_ex, out_rows, out_cols, out_ch)) - for m in range(n_ex): - for c in range(out_ch): - for i in range(out_rows): - for j in range(out_cols): - i0, i1 = i * s, (i * s) + fr - j0, j1 = j * s, (j * s) + fc - - window = X_pad[m, i0 : i1 : (d + 1), j0 : j1 : (d + 1), :] - Z[m, i, j, c] = np.sum(window * W[:, :, :, c]) - return Z - - -####################################################################### -# Weight Initialization # -####################################################################### - - -def he_uniform(weight_shape): - """ - Initializes network weights `W` with using the He uniform initialization - strategy. - - Notes - ----- - The He uniform initializations trategy initializes thew eights in `W` using - draws from Uniform(-b, b) where - - .. math:: - - b = \sqrt{\\frac{6}{\\text{fan_in}}} - - Developed for deep networks with ReLU nonlinearities. - - Parameters - ---------- - weight_shape : tuple - The dimensions of the weight matrix/volume. - - Returns - ------- - W : :py:class:`ndarray ` of shape `weight_shape` - The initialized weights. - """ - fan_in, fan_out = calc_fan(weight_shape) - b = np.sqrt(6 / fan_in) - return np.random.uniform(-b, b, size=weight_shape) - - -def he_normal(weight_shape): - """ - Initialize network weights `W` using the He normal initialization strategy. - - Notes - ----- - The He normal initialization strategy initializes the weights in `W` using - draws from TruncatedNormal(0, b) where the variance `b` is - - .. math:: - - b = \\frac{2}{\\text{fan_in}} - - He normal initialization was originally developed for deep networks with - :class:`~numpy_ml.neural_nets.activations.ReLU` nonlinearities. - - Parameters - ---------- - weight_shape : tuple - The dimensions of the weight matrix/volume. - - Returns - ------- - W : :py:class:`ndarray ` of shape `weight_shape` - The initialized weights. - """ - fan_in, fan_out = calc_fan(weight_shape) - std = np.sqrt(2 / fan_in) - return truncated_normal(0, std, weight_shape) - - -def glorot_uniform(weight_shape, gain=1.0): - """ - Initialize network weights `W` using the Glorot uniform initialization - strategy. - - Notes - ----- - The Glorot uniform initialization strategy initializes weights using draws - from ``Uniform(-b, b)`` where: - - .. math:: - - b = \\text{gain} \sqrt{\\frac{6}{\\text{fan_in} + \\text{fan_out}}} - - The motivation for Glorot uniform initialization is to choose weights to - ensure that the variance of the layer outputs are approximately equal to - the variance of its inputs. - - This initialization strategy was primarily developed for deep networks with - tanh and logistic sigmoid nonlinearities. - - Parameters - ---------- - weight_shape : tuple - The dimensions of the weight matrix/volume. - - Returns - ------- - W : :py:class:`ndarray ` of shape `weight_shape` - The initialized weights. - """ - fan_in, fan_out = calc_fan(weight_shape) - b = gain * np.sqrt(6 / (fan_in + fan_out)) - return np.random.uniform(-b, b, size=weight_shape) - - -def glorot_normal(weight_shape, gain=1.0): - """ - Initialize network weights `W` using the Glorot normal initialization strategy. - - Notes - ----- - The Glorot normal initializaiton initializes weights with draws from - TruncatedNormal(0, b) where the variance `b` is - - .. math:: - - b = \\frac{2 \\text{gain}^2}{\\text{fan_in} + \\text{fan_out}} - - The motivation for Glorot normal initialization is to choose weights to - ensure that the variance of the layer outputs are approximately equal to - the variance of its inputs. - - This initialization strategy was primarily developed for deep networks with - :class:`~numpy_ml.neural_nets.activations.Tanh` and - :class:`~numpy_ml.neural_nets.activations.Sigmoid` nonlinearities. - - Parameters - ---------- - weight_shape : tuple - The dimensions of the weight matrix/volume. - - Returns - ------- - W : :py:class:`ndarray ` of shape `weight_shape` - The initialized weights. - """ - fan_in, fan_out = calc_fan(weight_shape) - std = gain * np.sqrt(2 / (fan_in + fan_out)) - return truncated_normal(0, std, weight_shape) - - -def truncated_normal(mean, std, out_shape): - """ - Generate draws from a truncated normal distribution via rejection sampling. - - Notes - ----- - The rejection sampling regimen draws samples from a normal distribution - with mean `mean` and standard deviation `std`, and resamples any values - more than two standard deviations from `mean`. - - Parameters - ---------- - mean : float or array_like of floats - The mean/center of the distribution - std : float or array_like of floats - Standard deviation (spread or "width") of the distribution. - out_shape : int or tuple of ints - Output shape. If the given shape is, e.g., ``(m, n, k)``, then - ``m * n * k`` samples are drawn. - - Returns - ------- - samples : :py:class:`ndarray ` of shape `out_shape` - Samples from the truncated normal distribution parameterized by `mean` - and `std`. - """ - samples = np.random.normal(loc=mean, scale=std, size=out_shape) - reject = np.logical_or(samples >= mean + 2 * std, samples <= mean - 2 * std) - while any(reject.flatten()): - resamples = np.random.normal(loc=mean, scale=std, size=reject.sum()) - samples[reject] = resamples - reject = np.logical_or(samples >= mean + 2 * std, samples <= mean - 2 * std) - return samples diff --git a/aitk/keras/wrappers/README.md b/aitk/keras/wrappers/README.md deleted file mode 100644 index 36794a1..0000000 --- a/aitk/keras/wrappers/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Wrappers - -The `wrappers.py` module implements wrappers for the layers in `layers.py`. It -includes -- Dropout ([Srivastava, et al., 2014](http://www.jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf)) diff --git a/aitk/keras/wrappers/__init__.py b/aitk/keras/wrappers/__init__.py deleted file mode 100644 index 4d07b0a..0000000 --- a/aitk/keras/wrappers/__init__.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -A collection of objects thats can wrap / otherwise modify arbitrary neural -network layers. -""" - -from abc import ABC, abstractmethod - -import numpy as np - - -class WrapperBase(ABC): - def __init__(self, wrapped_layer): - """An abstract base class for all Wrapper instances""" - self._base_layer = wrapped_layer - if hasattr(wrapped_layer, "_base_layer"): - self._base_layer = wrapped_layer._base_layer - super().__init__() - - @abstractmethod - def _init_wrapper_params(self): - raise NotImplementedError - - @abstractmethod - def forward(self, z, **kwargs): - """Overwritten by inherited class""" - raise NotImplementedError - - @abstractmethod - def backward(self, out, **kwargs): - """Overwritten by inherited class""" - raise NotImplementedError - - @property - def trainable(self): - """Whether the base layer is frozen""" - return self._base_layer.trainable - - @property - def parameters(self): - """A dictionary of the base layer parameters""" - return self._base_layer.parameters - - @property - def hyperparameters(self): - """A dictionary of the base layer's hyperparameters""" - hp = self._base_layer.hyperparameters - hpw = self._wrapper_hyperparameters - if "wrappers" in hp: - hp["wrappers"].append(hpw) - else: - hp["wrappers"] = [hpw] - return hp - - @property - def derived_variables(self): - """ - A dictionary of the intermediate values computed during layer - training. - """ - dv = self._base_layer.derived_variables.copy() - if "wrappers" in dv: - dv["wrappers"].append(self._wrapper_derived_variables) - else: - dv["wrappers"] = [self._wrapper_derived_variables] - return dv - - @property - def gradients(self): - """A dictionary of the current layer parameter gradients.""" - return self._base_layer.gradients - - @property - def act_fn(self): - """The activation function for the base layer.""" - return self._base_layer.act_fn - - @property - def X(self): - """The collection of layer inputs.""" - return self._base_layer.X - - def _init_params(self): - hp = self._wrapper_hyperparameters - if "wrappers" in self._base_layer.hyperparameters: - self._base_layer.hyperparameters["wrappers"].append(hp) - else: - self._base_layer.hyperparameters["wrappers"] = [hp] - - def freeze(self): - """ - Freeze the base layer's parameters at their current values so they can - no longer be updated. - """ - self._base_layer.freeze() - - def unfreeze(self): - """Unfreeze the base layer's parameters so they can be updated.""" - self._base_layer.freeze() - - def flush_gradients(self): - """Erase all the wrapper and base layer's derived variables and gradients.""" - assert self.trainable, "Layer is frozen" - self._base_layer.flush_gradients() - - for k, v in self._wrapper_derived_variables.items(): - self._wrapper_derived_variables[k] = [] - - def update(self, lr): - """ - Update the base layer's parameters using the accrued gradients and - layer optimizer. Flush all gradients once the update is complete. - """ - assert self.trainable, "Layer is frozen" - self._base_layer.update(lr) - self.flush_gradients() - - def _set_wrapper_params(self, pdict): - for k, v in pdict.items(): - if k in self._wrapper_hyperparameters: - self._wrapper_hyperparameters[k] = v - return self - - def set_params(self, summary_dict): - """ - Set the base layer parameters from a dictionary of values. - - Parameters - ---------- - summary_dict : dict - A dictionary of layer parameters and hyperparameters. If a required - parameter or hyperparameter is not included within `summary_dict`, - this method will use the value in the current layer's - :meth:`summary` method. - - Returns - ------- - layer : :doc:`Layer ` object - The newly-initialized layer. - """ - return self._base_layer.set_params(summary_dict) - - def summary(self): - """Return a dict of the layer parameters, hyperparameters, and ID.""" - return { - "layer": self.hyperparameters["layer"], - "layer_wrappers": [i["wrapper"] for i in self.hyperparameters["wrappers"]], - "parameters": self.parameters, - "hyperparameters": self.hyperparameters, - } - - -class Dropout(WrapperBase): - def __init__(self, wrapped_layer, p): - """ - A dropout regularization wrapper. - - Notes - ----- - During training, a dropout layer zeroes each element of the layer input - with probability `p` and scales the activation by `1 / (1 - p)` (to reflect - the fact that on average only `(1 - p) * N` units are active on any - training pass). At test time, does not adjust elements of the input at - all (ie., simply computes the identity function). - - Parameters - ---------- - wrapped_layer : :doc:`Layer ` instance - The layer to apply dropout to. - p : float in [0, 1) - The dropout propbability during training - """ - super().__init__(wrapped_layer) - self.p = p - self._init_wrapper_params() - self._init_params() - - def _init_wrapper_params(self): - self._wrapper_derived_variables = {"dropout_mask": []} - self._wrapper_hyperparameters = {"wrapper": "Dropout", "p": self.p} - - def forward(self, X, retain_derived=True): - """ - Compute the layer output with dropout for a single minibatch. - - Parameters - ---------- - X : :py:class:`ndarray ` of shape `(n_ex, n_in)` - Layer input, representing the `n_in`-dimensional features for a - minibatch of `n_ex` examples. - retain_derived : bool - Whether to retain the variables calculated during the forward pass - for use later during backprop. If False, this suggests the layer - will not be expected to backprop through wrt. this input. Default - is True. - - Returns - ------- - Y : :py:class:`ndarray ` of shape `(n_ex, n_out)` - Layer output for each of the `n_ex` examples. - """ - scaler, mask = 1.0, np.ones(X.shape).astype(bool) - if self.trainable: - scaler = 1.0 / (1.0 - self.p) - mask = np.random.rand(*X.shape) >= self.p - X = mask * X - - if retain_derived: - self._wrapper_derived_variables["dropout_mask"].append(mask) - - return scaler * self._base_layer.forward(X, retain_derived) - - def backward(self, dLdy, retain_grads=True): - """ - Backprop from the base layer's outputs to inputs. - - Parameters - ---------- - dLdy : :py:class:`ndarray ` of shape `(n_ex, n_out)` or list of arrays - The gradient(s) of the loss wrt. the layer output(s). - retain_grads : bool - Whether to include the intermediate parameter gradients computed - during the backward pass in the final parameter update. Default is - True. - - Returns - ------- - dLdX : :py:class:`ndarray ` of shape `(n_ex, n_in)` or list of arrays - The gradient of the loss wrt. the layer input(s) `X`. - """ # noqa: E501 - assert self.trainable, "Layer is frozen" - dLdy *= 1.0 / (1.0 - self.p) - return self._base_layer.backward(dLdy, retain_grads) - - -def init_wrappers(layer, wrappers_list): - """ - Initialize the layer wrappers in `wrapper_list` and return a wrapped - `layer` object. - - Parameters - ---------- - layer : :doc:`Layer ` instance - The base layer object to apply the wrappers to. - wrappers : list of dicts - A list of parameter dictionaries for a the wrapper objects. The - wrappers are initialized and applied to the the layer sequentially. - - Returns - ------- - wrapped_layer : :class:`WrapperBase` instance - The wrapped layer object - """ - for wr in wrappers_list: - if wr["wrapper"] == "Dropout": - layer = Dropout(layer, 1)._set_wrapper_params(wr) - else: - raise NotImplementedError("{}".format(wr["wrapper"])) - return layer From b4c017aa3cc41e834306a7cc4d507666ff47b4b1 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 12:25:48 -0400 Subject: [PATCH 29/37] Fixed build_model_from, get_layer_input_tensor, cleaned up imports --- aitk/networks/network.py | 44 ++-- aitk/networks/utils.py | 10 + tests/test_networks/test_network_methods.py | 225 +++++++++----------- 3 files changed, 131 insertions(+), 148 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index c2d23e4..1ddfca0 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -20,8 +20,12 @@ import matplotlib.pyplot as plt import numpy as np +import tensorflow as tf +import tensorflow.keras.backend as K from matplotlib import cm from PIL import Image +from tensorflow.keras.layers import Concatenate, Dense, InputLayer, Layer +from tensorflow.keras.models import Model from aitk.utils import array_to_image @@ -30,6 +34,7 @@ get_array_shape, get_connections, get_error_colormap, + get_layer_input_tensor, get_templates, image_to_uri, is_keras_tensor, @@ -140,8 +145,6 @@ def add(self, layer): """ Add a layer to the network. """ - from tensorflow.keras.layers import Layer - if isinstance(layer, FunctionType): raise Exception("Don't use Input; use InputLayer") @@ -530,8 +533,6 @@ def _prepare_input(self, inputs, input_names): ] def build_model(self): - from tensorflow.keras.models import Model - if len(self._connections) == 0: raise Exception("Need to connect layers before building model") @@ -585,9 +586,11 @@ def topological_sort(self, layers, input_layers): return sorted_layers def _build_graph_to(self, layer_name): + """ + Given the name of a layer, build all of the models + to that layer by calling the Keras layer as a function. + """ # recursive - from tensorflow.keras.layers import Concatenate, InputLayer - layers = self._get_layers_to(layer_name) if len(layers) == 0: # An input layer: @@ -601,7 +604,7 @@ def _build_graph_to(self, layer_name): incoming_layer = incoming_layers[0] else: # more than one incoming_layer = Concatenate()( - [layer._input_tensor for layer in incoming_layers] + [get_layer_input_tensor(layer) for layer in incoming_layers] ) if isinstance(incoming_layer, InputLayer): @@ -912,8 +915,6 @@ def predict_from(self, inputs, from_layer_name, to_layer_name): """ Propagate patterns from one bank to another bank in the network. """ - from tensorflow.keras.models import Model - if self._model is None: raise Exception("Model has not yet been compiled") @@ -1037,7 +1038,7 @@ def display( except Exception: return_type = "image" - input_vectors = self._prepare_input(inputs, self.input_bank_order) + # input_vectors = self._prepare_input(inputs, self.input_bank_order) if return_type == "html": svg = self.get_image( @@ -1182,8 +1183,6 @@ def propagate_each( count += 1 def _build_predict_models(self): - from tensorflow.keras.models import Model - # for all layers, inputs to here: for layer in self._layers: if self._get_layer_type(layer.name) != "input": @@ -1272,18 +1271,20 @@ def _make_color(self, item): return tuple(item) def _get_input_layers(self): - layers = set() + layers = [] for layer_from, layer_to in self._connections: - layers.add(layer_from) + if layer_from not in layers: + layers.append(layer_from) for layer_from, layer_to in self._connections: if layer_to in layers: layers.remove(layer_to) return [self._layers_map[name] for name in layers] def _get_output_layers(self): - layers = set() + layers = [] for layer_from, layer_to in self._connections: - layers.add(layer_to) + if layer_to not in layers: + layers.append(layer_to) for layer_from, layer_to in self._connections: if layer_from in layers: layers.remove(layer_from) @@ -2693,9 +2694,6 @@ def get_learning_rate(self): print("WARNING: you need to use an optimizer with lr") def get_metric(self, name): - import tensorflow as tf - import tensorflow.keras.backend as K - if name == "tolerance_accuracy": self._state["tolerance_accuracy_used"] = True @@ -2807,8 +2805,6 @@ def __init__( * (int, int, ...): (input layers only) the shape of the input patterns * keras layer instance: an instance of a keras layer, like Flatten() """ - import tensorflow.keras.layers - from tensorflow.keras.layers import Dense, InputLayer, Layer def make_name(index, total): if index == 0: @@ -2824,9 +2820,9 @@ def make_layer(index, layers, activation): if isinstance(layers[index], Layer) or is_keras_tensor(layers[index]): return layers[index] elif isinstance(layers[index], str) and hasattr( - tensorflow.keras.layers, layers[index] + tf.keras.layers, layers[index] ): - layer_class = getattr(tensorflow.keras.layers, layers[index]) + layer_class = getattr(tf.keras.layers, layers[index]) return layer_class() else: name = make_name(index, len(layers)) @@ -2859,8 +2855,6 @@ def make_layer(index, layers, activation): ) def _make_optimizer(self, optimizer): - import tensorflow as tf - # Get optimizer with some defaults if optimizer == "sgd": return tf.keras.optimizers.SGD( diff --git a/aitk/networks/utils.py b/aitk/networks/utils.py index 45a40e1..273b62c 100644 --- a/aitk/networks/utils.py +++ b/aitk/networks/utils.py @@ -388,3 +388,13 @@ def get_connections(model): for parent_node in node.parent_nodes: connections.append((parent_node.operation.name, layer.name)) return connections + + +def get_layer_input_tensor(layer): + """ + Get the layer, or layer._input_tensor + """ + if hasattr(layer, "_input_tensor"): + return layer._input_tensor + else: + return layer diff --git a/tests/test_networks/test_network_methods.py b/tests/test_networks/test_network_methods.py index 774609c..849cea0 100644 --- a/tests/test_networks/test_network_methods.py +++ b/tests/test_networks/test_network_methods.py @@ -8,23 +8,9 @@ # # ****************************************************** - -"""Questions: - -In the file test_network.py, all test cases call connect -and compile before using the networks. However, the -constructors for both the Network and SimpleNetwork classes -call self.compile. So is the compile call necessary? - -Should the constructors also call self.connect by default. When would -there ever be a case when you wouldn't want the layers to be -connected? - -""" - - import numpy as np from tensorflow.keras.layers import Dense, InputLayer + from aitk.networks import Network, SimpleNetwork from aitk.utils import get_dataset @@ -33,17 +19,12 @@ def test_set_weights(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) inputs = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]] - expected_outputs = [ - [0.53426534], - [0.5517651], - [0.5280447], - [0.44220227] - ] + expected_outputs = [[0.53426534], [0.5517651], [0.5280447], [0.44220227]] for i in range(len(inputs)): output = network.propagate(inputs[i]) assert np.allclose(output, expected_outputs[i]) - + def test_get_weights(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) @@ -58,15 +39,15 @@ def test_get_weights(): assert len(weights[2]) == 2 assert len(weights[3]) == 1 - + def test_get_weights_flat(): network = SimpleNetwork(3, 2, 1) original = [1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0] network.set_weights(original) weights = network.get_weights(flat=True) assert np.allclose(weights, original) - - + + def test_propagate_to(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) @@ -81,22 +62,17 @@ def test_propagate_to(): actual_activations = list(network.propagate_to(inputs[i], "hidden")) assert np.allclose(actual_activations, expected_activations[i]) - + def test_predict(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) inputs = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]] - expected_activations = [ - 0.53426534, - 0.5517651, - 0.5280447, - 0.44220227 - ] + expected_activations = [0.53426534, 0.5517651, 0.5280447, 0.44220227] results = network.predict(np.array(inputs)) actual_activations = list(np.array(results).flatten()) assert np.allclose(actual_activations, expected_activations) - + def test_predict_to(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) @@ -109,24 +85,18 @@ def test_predict_to(): [0.62245935, 0.8175745], ] for i in range(len(result)): - actual_activations = list(result[i]) assert np.allclose(list(result[i]), expected_activations[i]) - -def test_predict_from(): + +def test_predict_from_simple_network(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) - expected_activations = [ - 0.53426534, - 0.5517651, - 0.5280447, - 0.44220227 - ] + expected_activations = [0.53426534, 0.5517651, 0.5280447, 0.44220227] hiddens = [ - [0.07585818, 0.18242551], - [0.18242551, 0.37754068], - [0.37754068, 0.62245935], - [0.62245935, 0.8175745 ] + [0.07585818, 0.18242551], + [0.18242551, 0.37754068], + [0.37754068, 0.62245935], + [0.62245935, 0.8175745], ] results = network.predict_from(np.array(hiddens), "hidden", "output") actual_activations = list(np.array(results).flatten()) @@ -137,61 +107,12 @@ def test_propagate(): network = SimpleNetwork(3, 2, 1) network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) inputs = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]] - expected_activations = [ - [0.53426534], - [0.5517651], - [0.5280447], - [0.44220227] - ] + expected_activations = [[0.53426534], [0.5517651], [0.5280447], [0.44220227]] for i in range(len(inputs)): result = network.propagate(np.array(inputs[i])) assert np.allclose(result, expected_activations[i]) - -def test_train_from_set_weights(): - network = SimpleNetwork(3, 2, 1) - network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) - train_inputs = [ - [0, 0, 0], - [1, 0, 0], - [0, 1, 0], - [0, 0, 1], - [1, 1, 0], - [1, 0, 1], - [0, 1, 1], - [1, 1, 1], - ] - train_targets = [[0], [0], [0], [0], [1], [1], [1], [0]] - history = network.fit( - train_inputs, - train_targets, - batch_size=8, - report_rate=100, - epochs=1000, - accuracy=1.0, - tolerance=0.2, - ) - assert len(history.history["tolerance_accuracy"]) == 874 - expected_weights = [ - 2.348937, - 4.2549586, - 2.348937, - 4.2549586, - 2.348937, - 4.2549586, - -5.95034, - -5.579458, - -6.8648214, - 7.5803447, - -3.8168766, - ] - weights = network.get_weights() - actual_weights = [] - for array in weights: - actual_weights += list(array.flatten()) - assert np.allclose(expected_weights, actual_weights) - def test_topological_sort(): # output # / \ @@ -206,22 +127,40 @@ def test_topological_sort(): network.add(Dense(1, name="output")) network.connect("inputA", "hiddenA") network.connect("inputB", "hiddenB") - network.connect("hiddenA","output") - network.connect("hiddenB","output") + network.connect("hiddenA", "output") + network.connect("hiddenB", "output") network.compile() - result = network.topological_sort(network._layers, - network._get_input_layers()) + result = network.topological_sort(network._layers, network._get_input_layers()) names = [layer.name for layer in result] assert names[0][:-1] == names[1][:-1] == "input" assert names[2][:-1] == names[3][:-1] == "hidden" assert names[4] == "output" + +def test_predict_from_network(): + network = Network() + network.add(InputLayer([2], name="inputA")) + network.add(InputLayer([3], name="inputB")) + network.add(Dense(2, name="hiddenA")) + network.add(Dense(3, name="hiddenB")) + network.add(Dense(1, name="output")) + network.connect("inputA", "hiddenA") + network.connect("inputB", "hiddenB") + network.connect("hiddenA", "output") + network.connect("hiddenB", "output") + network.compile() + + output = network.propagate([[1, 1], [0, 0, 0]]) + hidden_a_activations = network.propagate_to([[1, 1], [0, 0, 0]], "hiddenA") + predict_from_outputs = network.predict_from( + np.array([hidden_a_activations.tolist() + [0, 0, 0]]), "hiddenA", "output" + ) + + assert np.allclose(output, predict_from_outputs[0]) + + def test_get_input_from_dataset(): - network = SimpleNetwork( - (6,6), - "Flatten", - 10, - (10, "softmax")) + network = SimpleNetwork((6, 6), "Flatten", 10, (10, "softmax")) test_inputs, test_targets = get_dataset("validate_6x6") result = network.get_input_from_dataset(0, test_inputs) diff = result - test_inputs[0] @@ -229,17 +168,13 @@ def test_get_input_from_dataset(): def test_get_target_from_dataset(): - network = SimpleNetwork( - (6,6), - "Flatten", - 10, - (10, "softmax")) + network = SimpleNetwork((6, 6), "Flatten", 10, (10, "softmax")) test_inputs, test_targets = get_dataset("validate_6x6") result = network.get_target_from_dataset(0, test_targets) diff = result - test_targets[0] assert np.count_nonzero(diff) == 0 - + def test_get_input_from_banked_dataset(): # outputA outputB # \ / @@ -254,13 +189,13 @@ def test_get_input_from_banked_dataset(): network.add(Dense(2, name="outputB")) network.connect("inputA", "hidden") network.connect("inputB", "hidden") - network.connect("hidden","outputA") - network.connect("hidden","outputB") + network.connect("hidden", "outputA") + network.connect("hidden", "outputB") network.compile() - inputs = [np.array([[0,0],[1,0],[1,1]]), - np.array([[0,0,0],[1,0,1],[1,1,1]])] - targets = [np.array([[0],[0],[1]]), - np.array([[0,0],[1,0],[1,1]])] + inputs = [ + np.array([[0, 0], [1, 0], [1, 1]]), + np.array([[0, 0, 0], [1, 0, 1], [1, 1, 1]]), + ] result = network.get_input_from_dataset(2, inputs) diff = inputs[0][2] - result[0] assert np.count_nonzero(diff) == 0 @@ -282,15 +217,59 @@ def test_get_target_from_banked_dataset(): network.add(Dense(2, name="outputB")) network.connect("inputA", "hidden") network.connect("inputB", "hidden") - network.connect("hidden","outputA") - network.connect("hidden","outputB") + network.connect("hidden", "outputA") + network.connect("hidden", "outputB") network.compile() - inputs = [np.array([[0,0],[1,0],[1,1]]), - np.array([[0,0,0],[1,0,1],[1,1,1]])] - targets = [np.array([[0],[0],[1]]), - np.array([[0,0],[1,0],[1,1]])] + inputs = [ + np.array([[0, 0], [1, 0], [1, 1]]), + np.array([[0, 0, 0], [1, 0, 1], [1, 1, 1]]), + ] result = network.get_target_from_dataset(2, inputs) diff = inputs[0][2] - result[0] assert np.count_nonzero(diff) == 0 diff = inputs[1][2] - result[1] assert np.count_nonzero(diff) == 0 + + +def test_train_from_set_weights(): + network = SimpleNetwork(3, 2, 1) + network.set_weights([1, 1, 1, 1, 1, 1, -2.5, -1.5, -3, 2, 0]) + train_inputs = [ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [1, 1, 0], + [1, 0, 1], + [0, 1, 1], + [1, 1, 1], + ] + train_targets = [[0], [0], [0], [0], [1], [1], [1], [0]] + history = network.fit( + train_inputs, + train_targets, + batch_size=8, + report_rate=100, + epochs=1000, + accuracy=1.0, + tolerance=0.2, + ) + assert len(history.history["tolerance_accuracy"]) == 874 + expected_weights = [ + 2.348937, + 4.2549586, + 2.348937, + 4.2549586, + 2.348937, + 4.2549586, + -5.95034, + -5.579458, + -6.8648214, + 7.5803447, + -3.8168766, + ] + weights = network.get_weights() + actual_weights = [] + for array in weights: + actual_weights += list(array.flatten()) + assert np.allclose(expected_weights, actual_weights) From 9b3017f5fee90d50ef91e8c07164b395a6b86230 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 14:07:21 -0400 Subject: [PATCH 30/37] Typo --- aitk/networks/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 1ddfca0..e216f26 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -1042,7 +1042,7 @@ def display( if return_type == "html": svg = self.get_image( - input_vectors, + inputs, targets, show_error, show_targets, From 5aa73199e5aeb6c14aa6f2b0b3fe453f54c83bae Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 14:33:22 -0400 Subject: [PATCH 31/37] Fixed net.display() --- aitk/networks/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index e216f26..e6ed80e 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -2460,7 +2460,7 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): else: # activations of a dataset try: image = self.make_image( - layer_name, self.predict_to(inputs, layer_name) + layer_name, self.predict_to([inputs], layer_name)[0] ) except Exception: # Error: make a red image From 7bc6177ff14bfd704b64f7df0f54127a4f024f6c Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 14:35:40 -0400 Subject: [PATCH 32/37] Removed unused vars --- aitk/networks/network.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/aitk/networks/network.py b/aitk/networks/network.py index e6ed80e..f5c29cd 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -2450,13 +2450,10 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): continue hiding[column] = False # The rest of this for loop is handling image of bank - keep_aspect_ratio = None if mode == "pca": image = self.predict_pca_to(inputs, layer_name, colors, sizes) - keep_aspect_ratio = True elif mode == "histogram": image = self.predict_histogram_to(inputs, layer_name) - keep_aspect_ratio = True else: # activations of a dataset try: image = self.make_image( From d07de3829e7f916ca776dbaa2a76d0db69f89ed1 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 14:53:17 -0400 Subject: [PATCH 33/37] Checked out master's version --- .../NeuralNetworks/DataManipulation.ipynb | 5095 +++++++++-------- 1 file changed, 2571 insertions(+), 2524 deletions(-) diff --git a/notebooks/NeuralNetworks/DataManipulation.ipynb b/notebooks/NeuralNetworks/DataManipulation.ipynb index 66d43ce..a4a221b 100644 --- a/notebooks/NeuralNetworks/DataManipulation.ipynb +++ b/notebooks/NeuralNetworks/DataManipulation.ipynb @@ -1,2543 +1,2590 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "CIWuXT1rbAAu" - }, - "source": [ - "\"Open" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "oKKbI7kMvSok" - }, - "source": [ - "# Data Manipulation\n", - "In this notebook, we explore how the composition of the training set for neural networks can lead to biased outcomes. We explore this in a categorization task with two classes by manipulating the balance of training examples across these classes." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zhwI5C5VzKDa" - }, - "source": [ - "Here we will install the relevant library in order for this notebook to run." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "id": "79TEzrOUtBpl" - }, - "outputs": [], - "source": [ - "%pip install aitk --quiet" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8RdbSwNB1SIC" - }, - "source": [ - "Running this next code block will allow us to import the additional libraries for this notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "id": "ssblrxpV9eVu" - }, - "outputs": [], - "source": [ - "from aitk.utils import gallery, array_to_image\n", - "from aitk.networks import SimpleNetwork\n", - "import tensorflow\n", - "from tensorflow.keras.datasets import mnist\n", - "import numpy as np\n", - "from random import shuffle" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eGocrfH8L4OD" - }, - "source": [ - "## Manipulating the Data\n", - "In this section, we will perform some manipulations on a dataset in order to show how the composition of a dataset can affect the efficacy of a neural network." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "53F-RO-vLY4T" - }, - "source": [ - "To demonstrate dataset composition's relevance to network efficacy, we will use a part of the MNIST dataset. MNIST is a data set composed of hand-written digits. we will focus on two digits from this dataset in order to demonstrate how over or under representing one of these two digits will affect the network's ability to accurately distinguish between them. Initially we will try categorizing 3's vs 5's. The reason we chose 3 and 5 is that they have some similarities, such as a similar curve in their bottom halves.\n", - "So distinguishing a 3 vs a 5 is more difficult than say a 3 vs a 1.\n", - "\n", - "We will start with an equal number of each digit, and then we will change the percentages of them in the dataset to explore how this imbalance impacts a network." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "uJEQC2GJO_d7" - }, - "source": [ - "This code block loads in the MNIST dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "id": "G9D1Spe3zPzH" - }, - "outputs": [], - "source": [ - "(train_x, train_y), (test_x, test_y) = mnist.load_data()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "6e1bDkc9PGuS" - }, - "source": [ - "### Set the two digits to explore\n", - "\n", - "We will begin by focusing on categorizing 3's vs 5's. But later you may change the two digits you wish to categorize to something else, such as 3 vs 8 or 1 vs 7.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "id": "8-hb0GRHebdh" - }, - "outputs": [], - "source": [ - "digit1 = 3\n", - "digit2 = 5" - ] - }, - { - "cell_type": "markdown", - "source": [ - "We begin by extracting just the two digits we want from the MNIST **training** data. We want to ensure that we start with an equal number of samples, and we will gather 5000 of each." - ], - "metadata": { - "id": "czlzg1hKdUxe" - } - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "cpAk1htBuA_J", - "outputId": "40724d1d-2bf3-4799-94a4-36486de2a1ab" - }, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "number of 3's: 5000\n", - "number of 5's: 5000\n" - ] - } - ], - "source": [ - "digit1_train_x = []\n", - "digit1_train_y = []\n", - "digit2_train_x = []\n", - "digit2_train_y = []\n", - "num_train_digit1 = 0\n", - "num_train_digit2 = 0\n", - "\n", - "for i in range(len(train_x)):\n", - " if train_y[i] == digit1 and num_train_digit1 < 5000:\n", - " digit1_train_x.append(train_x[i])\n", - " digit1_train_y.append([1,0])\n", - " num_train_digit1 += 1\n", - " elif train_y[i] == digit2 and num_train_digit2 < 5000:\n", - " digit2_train_x.append(train_x[i])\n", - " digit2_train_y.append([0,1])\n", - " num_train_digit2 += 1\n", - "\n", - "print(\"number of %d's: %d\" % (digit1, len(digit1_train_x)))\n", - "print(\"number of %d's: %d\" % (digit2, len(digit2_train_x)))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "QgZnzunCfg2P" - }, - "source": [ - "We also need to extract just the two digits of interest from the **testing** data." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "gR8upW9NC4kP", - "outputId": "6619e49c-b3be-4d4a-80ef-24b93a501b5b" - }, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "number of 3's: 750\n", - "number of 5's: 750\n" - ] - } - ], - "source": [ - "new_test_x = []\n", - "new_test_y = []\n", - "num_digit1 = 0\n", - "num_digit2 = 0\n", - "\n", - "for i in range(len(test_x)):\n", - " if test_y[i] == digit1 and num_digit1 < 750:\n", - " new_test_x.append(test_x[i])\n", - " new_test_y.append([1,0])\n", - " num_digit1 += 1\n", - " elif test_y[i] == digit2 and num_digit2 < 750:\n", - " new_test_x.append(test_x[i])\n", - " new_test_y.append([0,1])\n", - " num_digit2 += 1\n", - "\n", - "new_test_x = np.array(new_test_x)\n", - "new_test_y = np.array(new_test_y)\n", - "\n", - "new_test_x_normalized = new_test_x/255\n", - "\n", - "print(\"number of %d's: %d\" % (digit1, num_digit1))\n", - "print(\"number of %d's: %d\" % (digit2, num_digit2))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_R8h4DaG5CZG" - }, - "source": [ - "Now that we have all of the data, we can begin to manipulate the balance within it. The next function allows us to specify how to split up the data." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "id": "BzAeR_EMwl_A" - }, - "outputs": [], - "source": [ - "def split_data(pct_digit1, pct_digit2):\n", - " assert pct_digit1+pct_digit2 == 1, \"percentages must sum to 1\"\n", - " num_digit1 = int(pct_digit1*(len(digit1_train_x)))\n", - " num_digit2 = int(pct_digit2*(len(digit2_train_x)))\n", - " shuffle(digit1_train_x)\n", - " shuffle(digit2_train_x)\n", - " print(\"%d train length: %d\" % (digit1, num_digit1))\n", - " print(\"%d train length: %d\" % (digit2, num_digit2))\n", - " inputs = digit1_train_x[:num_digit1] + digit2_train_x[:num_digit2]\n", - " targets = ([[1,0]]*num_digit1) + ([[0,1]]*num_digit2)\n", - " mix = list(zip(inputs, targets))\n", - " shuffle(mix)\n", - " inputs, targets = zip(*mix)\n", - "\n", - " inputs = np.array(inputs)/255\n", - " targets = np.array(targets)\n", - "\n", - " return inputs, targets\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nSSabDe77w55" - }, - "source": [ - "### Set percentages of each digit in the training set\n", - "\n", - "Now, **enter the percentages for both digits** that you want in the dataset as a decimal. To begin, we will look at a balanced dataset that is split 50/50 (which should be entered as 0.5 for both digits below).\n", - "\n", - "After seeing how that works, change the percentages and rerun all the code blocks below to see how the network changes. For example you might want to try a 70/30 split (which should be entered as .7 and .3 below)." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "id": "h3JoM5BJ4O77" - }, - "outputs": [], - "source": [ - "pct_digit1 = 0.5\n", - "pct_digit2 = 0.5" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GMsOZm_P8V2a" - }, - "source": [ - "Run the next code block to split up the data as you specified. Additionally, the quantities of each digit will be printed; verify that the numbers look correct given the percentages that you entered." - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "11CCyt921x9f", - "outputId": "2fb06bbe-188f-4579-b36a-235c54896bbe" - }, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "3 train length: 2500\n", - "5 train length: 2500\n" - ] - } - ], - "source": [ - "inputs, targets = split_data(pct_digit1, pct_digit2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "MBYsp30e8clA" - }, - "source": [ - "Now, we can see what a sample of 10 images from the new dataset looks like. The number of examples of each digit will vary based on the percentages you inputted." - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 232 - }, - "id": "MTUkPmHPTrkc", - "outputId": "d30e6073-4e01-4506-cec4-02396cbe1876" - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
" - ] - }, - "metadata": {} - } - ], - "source": [ - "images = [array_to_image(inputs[i]) for i in range(20)]\n", - "gallery(images)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eBcWmdw-ZzHF" - }, - "source": [ - "Here we are creating the neural network. We will utilize a simple network which first flattens the two-dimensional input, passes it through two hidden layers, and then on to the output layer." - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "id": "ZYhRl_wAPRQr" - }, - "outputs": [], - "source": [ - "net = SimpleNetwork((28, 28), \"Flatten\", 25, 10, (2, \"softmax\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EhaHl4jNaptW" - }, - "source": [ - "Summarizing the network allows us to make sure it looks as it is expected. The total number of parameters gives you a good sense of the size of the network. Ours is less than 20 thousand, which is quite small by modern standard." - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "0T6j_nRrP6Xd", - "outputId": "af86be59-2b1e-4527-e117-0e2cfb686548" - }, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Model: \"SimpleNetwork\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input (InputLayer) [(None, 28, 28)] 0 \n", - " \n", - " flatten_1 (Flatten) (None, 784) 0 \n", - " \n", - " hidden_2 (Dense) (None, 25) 19625 \n", - " \n", - " hidden_3 (Dense) (None, 10) 260 \n", - " \n", - " output (Dense) (None, 2) 22 \n", - " \n", - "=================================================================\n", - "Total params: 19907 (77.76 KB)\n", - "Trainable params: 19907 (77.76 KB)\n", - "Non-trainable params: 0 (0.00 Byte)\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "net.summary()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "PHHfikpM4jAv" - }, - "source": [ - "This is a fairly simple task for the network so it only needs 10 epochs of training to achieve high accuracy (each epoch is one pass through the training data)." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 423 - }, - "id": "djrFqGdI0DG9", - "outputId": "d2ab048f-75a6-450c-d8e6-212f93bcca11" - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " 2024-09-25T19:18:56.011642\n", - " image/svg+xml\n", - " \n", - " \n", - " Matplotlib v3.7.1, https://matplotlib.org/\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ] - }, - "metadata": {} - }, - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Epoch 10/10 loss: 0.015097171068191528 - tolerance_accuracy: 0.9150079488754272 - val_loss: 0.01628131978213787 - val_tolerance_accuracy: 0.9147986769676208\n" - ] - } - ], - "source": [ - "history = net.fit(inputs, # new training examples\n", - " targets, # new training labels\n", - " verbose=1, # verbose output\n", - " validation_data=(new_test_x_normalized, # validation examples\n", - " new_test_y), # validation labels\n", - " epochs=10) # number of times to loop through the training set" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7vrCM2yO6hiH" - }, - "source": [ - "After training the network, **take note of the tolerance_accuracy and val_tolerance_accuracy for every time you retrain the network with manipulated percentages**.\n", - "\n", - "The tolerance accuracy reports the accuracy of the network on the **training** data\n", - "\n", - "The validation tolerance accuracy reports the accuracy of the network on the **testing** data." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_-kae00F6mqw" - }, - "source": [ - "**tolerance_accuracy**: *enter here*\n", - "\n", - "**val_tolerance_accuracy**: *enter here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "q1amP4wiszlm" - }, - "source": [ - "###Testing the Network" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "M7d30W-9a3zL" - }, - "source": [ - "Let's create a function to allow us to easily visualize how the trained network is doing on some sample inputs from the testing data." - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": { - "id": "NASP8hqUQv_w" - }, - "outputs": [], - "source": [ - "from time import sleep\n", - "def test(net, n):\n", - " for i in range(n):\n", - " net.display(new_test_x_normalized[i])\n", - " outputs = net.propagate(new_test_x_normalized[i])\n", - " print(\", \".join([str(round(v,2)) for v in outputs]))\n", - " sleep(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TkhGLdInbZ2Z" - }, - "source": [ - "In the visualization below, focus your attention on the two boxes in the final output layer.\n", - "\n", - "* When the network recognizes digit1 (which is initially 3), there should be a white block on the left and a black block on the right for the output.\n", - "\n", - "* When the network recognizes digit2 (which is initially 5), there should be a black block on the left and a white block on the right for the output.\n", - "\n", - "* For inputs that the network is having trouble recognizing, their output will not be clearly black or white in either of the two output blocks.\n", - "\n", - "Additionally, below the visualization of the network, when the test function is run, you can see percentages of certainty. The first number is the certainty that the digit is a digit1, and the second number is the certainty that the digit is a digit2." - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oKKbI7kMvSok" + }, + "source": [ + "# Data Manipulation\n", + "In this notebook, we explore how the composition of the training set for neural networks can lead to biased outcomes. We explore this in a categorization task with two classes by manipulating the balance of training examples across these classes." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zhwI5C5VzKDa" + }, + "source": [ + "Here we will install the relevant library in order for this notebook to run." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "id": "79TEzrOUtBpl" + }, + "outputs": [], + "source": [ + "%pip install aitk --quiet" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8RdbSwNB1SIC" + }, + "source": [ + "Running this next code block will allow us to import the additional libraries for this notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "id": "ssblrxpV9eVu" + }, + "outputs": [], + "source": [ + "from aitk.utils import gallery, array_to_image\n", + "from aitk.networks import SimpleNetwork\n", + "\n", + "import tensorflow\n", + "from tensorflow.keras.datasets import mnist\n", + "\n", + "import numpy as np\n", + "\n", + "from random import shuffle" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eGocrfH8L4OD" + }, + "source": [ + "## Manipulating the Data\n", + "In this section, we will perform some manipulations on a dataset in order to show how the composition of a dataset can affect the efficacy of a network." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "53F-RO-vLY4T" + }, + "source": [ + "To demonstrate dataset composition's relevance to network efficacy, we will use a part of the MNIST dataset. MNIST is a data set composed of hand-written digits. Specifically, we will use the 4's and 5's in this dataset in order to demonstrate how over or underrepresenting one of these two digits will affect the network's ability to accurately recognize both digits. The reason we chose 4 and 5 is that they have some similarities, such as the horizontal bar through the middle, and some differences, such as the pointed top for the 4 and the flat top for the 5.\n", + "\n", + "We will start with an equal number of 4's and 5's, and then we will change the percentages of 4's and 5's in the overall dataset to explore how dataset manipulation impacts a network." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "uJEQC2GJO_d7" + }, + "source": [ + "This code block loads in the MNIST dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "id": "G9D1Spe3zPzH" + }, + "outputs": [], + "source": [ + "(train_x, train_y), (test_x, test_y) = mnist.load_data()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6e1bDkc9PGuS" + }, + "source": [ + "This next code block will create an array of the 4's in the dataset and an array of the 5's, as well as corresponding arrays for the labels for these datasets. Then we will print the number of 4's and 5's to make sure there is an equal number." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cpAk1htBuA_J", + "outputId": "e47d614f-1581-42d3-946d-863b2c372f2c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of fours: 5000\n", + "number of fives: 5000\n" + ] + } + ], + "source": [ + "four_train_x = []\n", + "four_train_y = []\n", + "five_train_x = []\n", + "five_train_y = []\n", + "num_train_four = 0\n", + "num_train_five = 0\n", + "\n", + "for i in range(len(train_x)):\n", + " if train_y[i] == 4 and num_train_four < 5000:\n", + " four_train_x.append(train_x[i])\n", + " four_train_y.append([1,0])\n", + " num_train_four += 1\n", + " elif train_y[i] == 5 and num_train_five < 5000:\n", + " five_train_x.append(train_x[i])\n", + " five_train_y.append([0,1])\n", + " num_train_five += 1\n", + "\n", + "print(\"number of fours: \", len(four_train_x))\n", + "print(\"number of fives: \", len(five_train_x))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QgZnzunCfg2P" + }, + "source": [ + "Now, we must create arrays that contain only 4's and 5's to test the network. We additionally need this set to have an equal number of 4's and 5's, so we will print out the number of 4's and 5's to check that they are equal." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gR8upW9NC4kP", + "outputId": "6f39728a-6202-4368-f2cb-aaf49ad3b434" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of fours: 750\n", + "number of fives: 750\n" + ] + } + ], + "source": [ + "new_test_x = []\n", + "new_test_y = []\n", + "num_four = 0\n", + "num_five = 0\n", + "\n", + "for i in range(len(test_x)):\n", + " if test_y[i] == 4 and num_four < 750:\n", + " new_test_x.append(test_x[i])\n", + " new_test_y.append([1,0])\n", + " num_four += 1\n", + " elif test_y[i] == 5 and num_five < 750:\n", + " new_test_x.append(test_x[i])\n", + " new_test_y.append([0,1])\n", + " num_five += 1\n", + "\n", + "new_test_x = np.array(new_test_x)\n", + "new_test_y = np.array(new_test_y)\n", + "\n", + "new_test_x_normalized = new_test_x/255\n", + "\n", + "print(\"number of fours: \", num_four)\n", + "print(\"number of fives: \", num_five)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_R8h4DaG5CZG" + }, + "source": [ + "Here, we define a function that will allow us to split the data by percentages of 4's and 5's. This will then create an array that contains the entered percentages of 4's and 5's." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "id": "BzAeR_EMwl_A" + }, + "outputs": [], + "source": [ + "def split_data(pct4, pct5):\n", + " assert pct4+pct5 == 1, \"percentages must sum to 1\"\n", + " num4 = int(pct4*(len(four_train_x)))\n", + " num5 = int(pct5*(len(five_train_x)))\n", + " shuffle(four_train_x)\n", + " shuffle(five_train_x)\n", + " print(\"four train length:\", num4)\n", + " print(\"five train length:\", num5)\n", + " inputs = four_train_x[:num4] + five_train_x[:num5]\n", + " targets = ([[1,0]]*num4) + ([[0,1]]*num5)\n", + " mix = list(zip(inputs, targets))\n", + " shuffle(mix)\n", + " inputs, targets = zip(*mix)\n", + "\n", + " inputs = np.array(inputs)/255\n", + " targets = np.array(targets)\n", + "\n", + " return inputs, targets\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nSSabDe77w55" + }, + "source": [ + "Now, **enter the percentage of 4's** you want in the dataset as a decimal. For example, if you want 25% 4's, enter 0.25. To start, split the data evenly. After seeing how the network looks with an even split of data, change the percentages and rerun all the code blocks below to see how the network changes." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "id": "h3JoM5BJ4O77" + }, + "outputs": [], + "source": [ + "pct4 = 0.5" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kh0Fromb8QDS" + }, + "source": [ + "**Enter the percentage of 5's** you want to be in the dataset. Make sure that the decimal percentage of 4's and 5's adds to one." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "id": "MSISoLoN4SDT" + }, + "outputs": [], + "source": [ + "pct5 = 0.5" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GMsOZm_P8V2a" + }, + "source": [ + "Run this code block to split up the data and put the composition of 4's and 5's into input and target arrays. Additionally, the number of 4's and 5's will be outputted; verify that the numbers look correct given the percentages of 4's and 5's you entered." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "11CCyt921x9f", + "outputId": "6dec476a-1d17-4f40-ed24-b7d34951c7e5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "four train length: 2500\n", + "five train length: 2500\n" + ] + } + ], + "source": [ + "inputs, targets = split_data(pct4, pct5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MBYsp30e8clA" + }, + "source": [ + "Now, we can see what the new dataset we will use looks like. Notice how the number of 4's and 5's varies based on the percentages you inputted." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 233 }, + "id": "MTUkPmHPTrkc", + "outputId": "9aa677f8-e143-4ca5-fc18-29d58860f6f1" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 36, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 439 - }, - "id": "bXjwxNHPVsvD", - "outputId": "84db7c3c-890b-4a4a-f6a0-6a27b69e2f3f" - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " Layer: output 'Dense'\n", - "Act function: softmax\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 2)outputLayer: hidden_3 'Dense'\n", - "Act function: sigmoid\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)hidden_3Layer: hidden_2 'Dense'\n", - "Act function: sigmoid\n", - "Act output range: (0.0, 1.0)\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 25)hidden_2Layer: flatten_1 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 784)flatten_1Layer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 28, 28)]inputActivations for SimpleNetwork" - ] - }, - "metadata": {} - }, - { - "output_type": "stream", - "name": "stdout", - "text": [ - "0.01, 0.99\n" - ] - } + "data": { + "text/html": [ + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
" ], - "source": [ - "test(net, 10)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "5jVbLWuL9X7r" - }, - "source": [ - "As you can see from these examples, when the network's dataset is split evenly between the two digits, it typically performs very well. It has an accuracy of around 91% on the test dataset (which it was not trained on). This shows that this network is effective at predicting whether a hand drawn digit is a digit1 vs digit2." + "text/plain": [ + "" ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DJlbuEzjTZ_j" - }, - "source": [ - "Now, let's see how many total errors this network made and which specific digits it classified incorrectly.\n", - "\n", - "Run this next code block to see a summary of the errors the network made. **Note how many errors the network made and which digit it classified incorrectly most often.**" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "3xY3TF98O1re", - "outputId": "9b2ce47e-dbf9-4069-fb8f-9cd4ecb5dc18" - }, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "number of digits classified incorrectly: 34\n", - "percentage of errors on 3's: 0.38\n", - "percentage of errors on 5's: 0.62\n" - ] - } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "images = [array_to_image(inputs[i]) for i in range(20)]\n", + "gallery(images)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eBcWmdw-ZzHF" + }, + "source": [ + "Here we are creating the neural network. We will utilize a very simple network which first flattens the two-dimensional input, passes it through a single hidden layer, and then on to the output layer." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "id": "ZYhRl_wAPRQr" + }, + "outputs": [], + "source": [ + "net = SimpleNetwork((28, 28), \"Flatten\", 25, (2, \"softmax\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EhaHl4jNaptW" + }, + "source": [ + "Summarizing the network allows us to make sure it looks as it is expected." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0T6j_nRrP6Xd", + "outputId": "3bcd97ee-69cd-4cd8-a085-f9727edfe378" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: \"SimpleNetwork\"\n", + "_________________________________________________________________\n", + " Layer (type) Output Shape Param # \n", + "=================================================================\n", + " input (InputLayer) [(None, 28, 28)] 0 \n", + " \n", + " flatten_1 (Flatten) (None, 784) 0 \n", + " \n", + " hidden_2 (Dense) (None, 25) 19625 \n", + " \n", + " output (Dense) (None, 2) 52 \n", + " \n", + "=================================================================\n", + "Total params: 19677 (76.86 KB)\n", + "Trainable params: 19677 (76.86 KB)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "_________________________________________________________________\n" + ] + } + ], + "source": [ + "net.summary()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PHHfikpM4jAv" + }, + "source": [ + "This is a fairly simple task for the network so it only needs 5 epochs of training to achieve high accuracy (each epoch is one pass through the training data)." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 423 + }, + "id": "djrFqGdI0DG9", + "outputId": "9e1a8384-0f3d-41e9-b9d2-47975bf5fc3a" + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2024-06-24T19:27:35.262604\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.7.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" ], - "source": [ - "from numpy import argmax\n", - "outputs = net.predict(new_test_x_normalized)\n", - "answers = [argmax(output) for output in outputs]\n", - "newtargets = [argmax(target) for target in new_test_y]\n", - "incorrect = [i for i in range(len(answers)) if answers[i] != newtargets[i]]\n", - "print(\"number of digits classified incorrectly:\", len(incorrect))\n", - "missed_target = [targets[i] for i in incorrect]\n", - "wrong_answer = [answers[i] for i in incorrect]\n", - "per_digit1 = 0\n", - "per_digit2 = 0\n", - "for i in range(len(incorrect)):\n", - " if wrong_answer[i] == 1:\n", - " per_digit1 += 1\n", - " else:\n", - " per_digit2 += 1\n", - "print(\"percentage of errors on %d's: %.2f\" % (digit1, per_digit1/len(incorrect)))\n", - "print(\"percentage of errors on %d's: %.2f\" % (digit2, per_digit2/len(incorrect)))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8KapHOuj-hNj" - }, - "source": [ - "*write observations here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "prn5CWDRT7Aw" - }, - "source": [ - "We can see a gallery of all of the digits that were classified incorrectly." + "text/plain": [ + "" ] + }, + "metadata": {}, + "output_type": "display_data" }, { - "cell_type": "code", - "execution_count": 38, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 338 - }, - "id": "5cCoTBS4Qf6a", - "outputId": "8c1a2503-2785-4277-bdff-7f18404f4452" - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
" - ] - }, - "metadata": {} - } + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 5/5 loss: 0.007666457910090685 - tolerance_accuracy: 0.9516321420669556 - val_loss: 0.00848546251654625 - val_tolerance_accuracy: 0.948803186416626\n" + ] + } + ], + "source": [ + "history = net.fit(inputs, # new training examples\n", + " targets, # new training labels\n", + " verbose=1, # verbose output\n", + " validation_data=(new_test_x_normalized, # validation examples\n", + " new_test_y), # validation labels\n", + " epochs=5) # number of times to loop through the training set" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7vrCM2yO6hiH" + }, + "source": [ + "After training the network, **take note of the tolerance and value tolerance accuracy for every time you retrain the network with manipulated percentages**. The tolerance accuracy tells us how accurately the network has learned the data in the dataset it is trained on, where the value tolerance accuracy reports the accuracy of the network on the test dataset." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_-kae00F6mqw" + }, + "source": [ + "**val_tolerance_accuracy**: *enter here*\n", + "\n", + "**tolerance_accuracy**: *enter here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "q1amP4wiszlm" + }, + "source": [ + "###Testing the Network" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "M7d30W-9a3zL" + }, + "source": [ + "Now, we can look at inputs in the network to see if we get the expected output. For 4's, there should be a white block on the left and a black block on the right for the output. For 5's, there should be a black block on the left and a white block on the right for the output. For inputs that the network is having trouble recognizing, their output will not be clearly black or white in either of the two output blocks. Additionally, below the visualization of the network, when the test function is run, you can see percentages of certainty. The first number is the certainty that the digit is a four, and the second number is the certainty that the digit is a five." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "id": "NASP8hqUQv_w" + }, + "outputs": [], + "source": [ + "from time import sleep\n", + "def test(net, n):\n", + " for i in range(n):\n", + " net.display(new_test_x_normalized[i])\n", + " outputs = net.propagate(new_test_x_normalized[i])\n", + " print(\", \".join([str(round(v,2)) for v in outputs]))\n", + " sleep(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TkhGLdInbZ2Z" + }, + "source": [ + "We are looking at the first ten inputs for the test dataset. You may notice some strange looking examples. The third one is a 5, but with a much smaller bottom half. The ninth one appears to be a quickly drawn 4 where the bottom half is missing." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 439 + }, + "id": "bXjwxNHPVsvD", + "outputId": "b0c2b50a-a624-43fa-8424-9b7fdd3fde7a" + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Layer: output 'Dense'\n", + "Act function: softmax\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 2)outputLayer: hidden_2 'Dense'\n", + "Act function: sigmoid\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 25)hidden_2Layer: flatten_1 'Flatten'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 784)flatten_1Layer: input 'InputLayer'\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = [(None, 28, 28)]inputActivations for SimpleNetwork" ], - "source": [ - "images = [array_to_image(new_test_x[index]) for index in incorrect]\n", - "gallery(images)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Zkv9AeCqUBN8" - }, - "source": [ - "After seeing which digits were classified incorrectly, consider **why** this may have occurred and **how** the percentages that you inputted would have this effect." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "m5yUzT88wvbn" - }, - "source": [ - "*write observations here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "VClpaPuxhFCV" - }, - "source": [ - "###Changing Dataset Composition" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TrQSwWuKpJLp" - }, - "source": [ - "Now that we have shown how this network performs with an equal number of each digit in the dataset, we want to show how the performance and accuracy of the network changes when we manipulate the percentages in the dataset.\n", - "\n", - "**Return to where you entered the percentages of each digit in the training dataset and change the percentages from 50/50 to 70/30.**\n", - "\n", - "**Rerun all the code blocks from there (including the testing summary).**\n", - "\n", - "Describe the differences you saw in the results between the 50/50 split and the 70/30 split." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jkOV70V92MG7" - }, - "source": [ - "*write observations here*" + "text/plain": [ + "" ] + }, + "metadata": {}, + "output_type": "display_data" }, { - "cell_type": "markdown", - "metadata": { - "id": "JdMZcsGhrrR1" - }, - "source": [ - "After you finish training your network with the first manipulated percentages, experiment with the percentages some more.\n", - "\n", - "How imbalanced does the training set have to be before the network is unable to distinguish between the two digits?" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vLFyonP0xUwf" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "source": [ - "Also feel free to explore other digits besides 3 vs 5. To do this go back to the section where you set the digits to explore, and then rerun all of the code cells below that." - ], - "metadata": { - "id": "2baQZImGkoxc" - } + "name": "stdout", + "output_type": "stream", + "text": [ + "1.0, 0.0\n" + ] + } + ], + "source": [ + "test(net, 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5jVbLWuL9X7r" + }, + "source": [ + "As you can see from these examples, when the network's dataset is split evenly between 4's and 5's, it typically performs very well. It has an accuracy of around 95% on the test dataset (which it was not trained on) which is very significant. This shows that this network is very strong and effective at predicting whether a hand drawn digit is a 4 or a 5." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Pa6PBYpagOAh" + }, + "source": [ + "So far, we have created and shown the effectiveness of the neural network that is trained to assess whether a given digit in the test dataset is a 4 or 5. The network should perform with a high accuracy and confidence in its guesses for any given input digit. For some less clear examples, it may have a hard time distinguishing the digit, however, overall the network should perform with a good deal of accuracy when the dataset it is trained on has an equal number of 4's and 5's." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DJlbuEzjTZ_j" + }, + "source": [ + "Now, we can see how many total errors this network had and which specific digits it classified incorrectly. Run this next code block to have a summary of the errors the network made, as well as the percentage of errors that were 4's and percentage of errors that were 5's. **Note how many errors the network made and which digit it classified incorrectly most often.**" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3xY3TF98O1re", + "outputId": "c687e623-e6fd-48c2-e687-5f22719764e5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "number of digits classified incorrectly: 16\n", + "\n", + "\n", + "percentage of errors on 4's: 0.125\n", + "percentage of errors on 5's: 0.875\n" + ] + } + ], + "source": [ + "from numpy import argmax\n", + "outputs = net.predict(new_test_x_normalized)\n", + "answers = [argmax(output) for output in outputs]\n", + "newtargets = [argmax(target) for target in new_test_y]\n", + "incorrect = [i for i in range(len(answers)) if answers[i] != newtargets[i]]\n", + "print(\"number of digits classified incorrectly:\", len(incorrect))\n", + "missed_target = [targets[i] for i in incorrect]\n", + "wrong_answer = [answers[i] for i in incorrect]\n", + "per4 = 0\n", + "per5 = 0\n", + "for i in range(len(incorrect)):\n", + " if wrong_answer[i] == 1:\n", + " per4 += 1\n", + " else:\n", + " per5 += 1\n", + "\n", + "print(\"\\n\")\n", + "print(\"percentage of errors on 4's:\", per4/len(incorrect))\n", + "print(\"percentage of errors on 5's:\", per5/len(incorrect))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8KapHOuj-hNj" + }, + "source": [ + "*write observations here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "prn5CWDRT7Aw" + }, + "source": [ + "Prints gallery of digits that were classified incorrectly." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 233 }, + "id": "5cCoTBS4Qf6a", + "outputId": "d7bb537e-9c1c-455c-bbbc-ee229c7e7ff0" + }, + "outputs": [ { - "cell_type": "markdown", - "source": [ - "*write any additional observations here*" + "data": { + "text/html": [ + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
" ], - "metadata": { - "id": "_py-OICBkobR" - } - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ud4JdXQg3qTU" - }, - "source": [ - "After finishing testing the different percentages that you can use to show varying levels of efficacy in the network, consider the potential broader implications of dataset composition before moving on to the next section.\n", - "\n", - "* How could having a specific subsection of data that an AI is trained on being underrepresented have very real world consequences?\n", - "* What possible issues and biases might arise with the human decision making that goes into the creation of datasets that are used to train these networks?" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4Vy_cB1Z3r1a" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rZLQldMkMpaB" - }, - "source": [ - "## Implications of Dataset Composition\n", - "We have explored how manipulating a dataset can change a network's efficacy in a categorization task. Datasets, and thus their composition, is an essential component of neural networks. Below, we will explore how bias in a dataset's composition can lead to negative impacts on marginalized communities." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "j2dLUABn99b6" - }, - "source": [ - "Within the past couple of years, bias within algorithms and AI has begun to receive attention. Many computer scientists and researchers have begun to recognize inherent bias, known also as \"algorithmic prejudices,\" present in algorithms, software, machine learning, artificial intelligence, and nearly every facet of computer science (see reference [3]). Within the context of datasets that are used for the training of neural networks, bias is pervasive. This bias becomes particularly concerning as algorithms are beginning to take over human responsibilities (see reference [2]). For example, algorithms are now being used by US law enforcement for \"predictive justice\"(see reference [3]). These tools \"calculate the probability that a person will not show up for trial as scheduled or commit future crimes\"(see reference [3]). As these algorithms become increasingly present in our society, we must evaluate and consider their inherent biases." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "j7rFF1WhBCue" - }, - "source": [ - "A major contributor to the current movement exploring and combatting biases in algorithms is Joy Buolamwini. As a graduate student at MIT, Buolamwini co-wrote the paper \"Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification\" along with Timmit Gebru which explores the ways in which machine learning algorithms can discriminate based on classes like race and gender (see reference [1]). As Buolamwini describes in her Ted Talk, she was inspired to address bias in machine learning algorithms when as an undergraduate student at Georgia Tech a robot that was supposed to recognize faces could not detect her's, as a black woman. Among other findings, this paper revealed that while lighter-skinned males had an extremely low error rate of 0.8% while darker-skinned females had a significantly higher error rate of up to 34.7% (see reference [1])." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BFsKqbWBBG5l" - }, - "source": [ - "### Buolamwini's Work" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "D8B-Nj2iJeuD" - }, - "source": [ - "In 2017, Buolamwini gave a Ted Talk demonstrating the discriminatory tendencies of widely used and accepted training sets and algorithms (see reference [2]). She refers to the concept of the \"coded gaze\" as algorithmic bias in the field of computer science. Within her talk, she dives deeper into the harms and discriminatory practices perpetrated by these training sets which are often severely lacking diversity. These practices include predictive policing. A study from the Georgetown Law Center showed that these police systems contain 1 in 2 adults in the US in a criminal facial recognition network (see reference [4]). These networks used by law enforcement have not been audited for accuracy and can result in misidentification of criminals, having a potentially serious consequence on the victim of this misidentification. With such serious stakes, it is essential to consider and address the biases of these algorithms and networks." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "FS2tQbRTmuDk" - }, - "source": [ - "To see the Georgetown Law Center's full report on law enforcement's use of facial recognition and recommendations, please access this link: [Perpetual Line Up](https://www.perpetuallineup.org/)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "t89MgWox8_Cw" - }, - "source": [ - "Click here to watch Buolamwini's Ted Talk!\n", - "\n", - "[![IMAGE ALT TEXT](http://img.youtube.com/vi/UG_X_7g63rY/0.jpg)](https://www.youtube.com/watch?v=UG_X_7g63rY \"How I'm fighting bias in algorithms\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "KE5-cmlYOJax" - }, - "source": [ - "#### Gender Shades" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "0KTL9vzSOP3w" - }, - "source": [ - "\"Gender Shades\" tested 3 commercial gender classification systems (Microsoft, IBM, Face++) using a dataset specifically designed to determine the potential biases present in these systems (see reference [1]). The dataset (Pilot Parliaments Benchmark), specifically created for this study, was composed of faces of 1270 individuals from three African countries and three European countries. The individuals were each given skin type labels per the Fitzpatrick six-point labeling system and given gender labels, either female or male given the binary nature of the evaluation systems." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Q2rG1Jkrpkdq" - }, - "source": [ - "In evaluation of these classifiers, there were several main takeaways. Firstly, \"male subjects were more accurately classified than female subjects\"(reference [1] pg. 8). Additionally, lighter-skinned subjects were more accurately classified than those with darker skin. Further, all classifiers performed worst on darker female subjects (reference [1] pg. 8). Here is the complete summarized key findings as outlined in the study:\n", - "\n", - "* All classifiers perform better on male faces\n", - "than female faces (8.1% − 20.6% difference\n", - "in error rate)\n", - "\n", - "* All classifiers perform better on lighter faces\n", - "than darker faces (11.8% − 19.2% difference\n", - "in error rate)\n", - "\n", - "* All classifiers perform worst on darker female\n", - "faces (20.8% − 34.7% error rate)\n", - "\n", - "* Microsoft and IBM classifiers perform best on lighter male faces (error rates of 0.0% and 0.3% respectively)\n", - "\n", - "* Face++ classifiers perform best on darker\n", - "male faces (0.7% error rate)\n", - "\n", - "* The maximum difference in error rate between the best and worst classified groups is 34.4%\n", - "\n", - "(reference [1] pg. 8)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XtKuFsp3Wcz-" - }, - "source": [ - "Further, this paper emphasizes the complete inability of these commercial systems to recognize gender minorities as they are completely excluded from datasets and classification options. Buolamwini notes, \"The companies provide no documentation to clarify if their gender classification systems which provide sex labels are classifying gender identity or biological sex\"(reference [1] pg. 6). As she emphasizes, \"This reductionist view of gender does not adequately capture the complexities of gender or address trangender identities\"(reference [1] pg. 6). When using these systems it is important to consider the erasure they create of people of non binary gender identities." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ynHyOYb3tO2H" - }, - "source": [ - "Buolamwini and Gerbru's study \"Gender Shades\" brought to the forefront the inherent biases present in well-established commercial classifiers and marginalization of those with intersectional identities, particularly darker skinned women, in these algorithms. The consequences of these prejudices have the potential to only further harm people of intersectional minority identities who are already marginalized in our society. As companies continue to develop these tools, Buolamwini calls for \"inclusive benchmark datasets and subgroup accuracy reports\" which will be \"necessary to increase transparency and accountability in artificial intelligence\"(reference [1] pg. 12). Continuing into the development of these tools, there will need to be increased \"demographic and phenotypic transparency and accountability in artificial intelligence\"(reference [1] pg. 12)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UNEIAPryL3Ir" - }, - "source": [ - "To have a more comprehensive understanding of Buolamwini and her co-collabrator Timmit Gebru's research \"Gender Shades,\" you can read the full paper here: [Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification](https://proceedings.mlr.press/v81/buolamwini18a/buolamwini18a.pdf)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XUxhDr-tyhsd" - }, - "source": [ - "###Reflect" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eRu4xvL_ymAO" - }, - "source": [ - "After reading more about the biases present in machine learning algorithms, **how do you see your role as a member of a modern society in which the presence of these algorithms is only increasing? What are ways in which we can combat these biases?**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XmCFNjW111hN" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "upbZADR41UtC" - }, - "source": [ - "Go back to the data manipulation section of this notebook and take note of the accuracy percentage you recorded when either digit1s or digit2s were overrepresented (particularly for when the minority digit represented 7% or less of the dataset). **Why is accuracy alone not always a reliable parameter? What are the fallacies underlying reporting the \"accuracy\" of a system? How could this number impact systems' usage and our trust in them?** Think about Buolamwini's findings. Contextualize your answer accordingly." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4-MDz0om1z8Q" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ons3DJxzM2m6" - }, - "source": [ - "## Navigating Biases\n", - "Considering ways in which we can work towards a more inclusive computing community." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vqlxGkqDNGp_" - }, - "source": [ - "As we work towards a more inclusive and less prejudiced computer science sphere, we must consider these issues, recognize them in our processes, and change our practices. A major component of changing the presence of these biases and their impact is focusing on inclusive coding practices. As Buolamwini outlines in her Ted Talk, we must consider who codes, how we code, and why we code (see reference [2]). Having a more diverse community of coders that consider and prioritize the needs and experiences of marginalized communities is an important step in creating a more inclusive field and algorithms." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sT0MZd41gOUQ" - }, - "source": [ - "Within the Georgetown Law Center report, the writers emphasize a need for significant legislative and regulatory change (see reference [4]). Law enforcement's use of facial recognition has the potential to do real damage, if it has not already impacted countless individuals. The report suggests legislation should be passed to regulate these technologies including requiring reasonable suspicion to use facial recognition, only use mug shot databases, court approval to use ID photos and license photos, requiring probable cause to use surveillance footage, completely ban tracking individuals for free speech issues, and increase accuracy testing. Further, they suggest a complete reform to the FBI facial recognition systems. They argue that these systems must be transparent and held publicly accountable, releasing statistics relating to arrest numbers. Importantly, they call for testing of racial bias within these systems and datasets that reflect the diversity of the American population. All of these reforms are important to implement if we want to mitigate the potential harm that these law enforcement agencies can perpetuate against already vulnerable communities." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zas1mHMJfhkL" - }, - "source": [ - "As Buolamwini emphasizes at the end of her Ted Talk, we must create \"a world where technology works for all of us, not just some of us, a world where we value inclusion and center social change.\" To finish her talk, she poses a question: \"Will you join me in the fight?\" **After reading through this computational essay, consider why it is important to join this \"fight\"? What are your personal motivations behind creating a more inclusive computing space and why is it important?**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XNWsYkB7gJSo" - }, - "source": [ - "*write answer here*" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "k7baTbHH5lIl" - }, - "source": [ - "## References\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vW9gND485pXx" - }, - "source": [ - "[1] J. Buolamwini, “Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification,” MIT Media Lab. Accessed: Jul. 23, 2024. [Online]. Available: https://www.media.mit.edu/publications/gender-shades-intersectional-accuracy-disparities-in-commercial-gender-classification/\n", - "\n", - "[2] J. Buolamwini, How I’m fighting bias in algorithms, (1489075733). Accessed: Jul. 23, 2024. [Online Video]. Available: https://www.ted.com/talks/joy_buolamwini_how_i_m_fighting_bias_in_algorithms\n", - "\n", - "[3] M. S. Cataleta, “Humane Artificial Intelligence: The Fragility of Human Rights Facing AI,” East-West Center, 2020. Accessed: Jul. 23, 2024. [Online]. Available: https://www.jstor.org/stable/resrep25514\n", - "\n", - "[4] “The Perpetual Line-Up,” Perpetual Line Up. Accessed: Jul. 23, 2024. [Online]. Available: https://www.perpetuallineup.org/" + "text/plain": [ + "" ] + }, + "metadata": {}, + "output_type": "display_data" } - ], - "metadata": { - "colab": { - "collapsed_sections": [ - "rZLQldMkMpaB", - "ons3DJxzM2m6", - "k7baTbHH5lIl" - ], - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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" - } + ], + "source": [ + "images = [array_to_image(new_test_x[index]) for index in incorrect]\n", + "gallery(images)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Zkv9AeCqUBN8" + }, + "source": [ + "After seeing which digits were classified incorrectly, consider **why** this may have occurred and **how** the percentage of 4's and 5's you inputted would have this effect." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "m5yUzT88wvbn" + }, + "source": [ + "*write observations here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VClpaPuxhFCV" + }, + "source": [ + "###Changing Dataset Composition" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TrQSwWuKpJLp" + }, + "source": [ + "Now that we have shown how this network performs with an equal number of 4's and 5's in the dataset, we want to show how the performance and accuracy of the network changes when we manipulate the percentage of 4's and 5's in the dataset. **Return to where you entered the percentages of 4's and 5's in the training dataset and change the percentages, rerunning all the code blocks. To start, change the percentage of 4's to 25% and percentage of 5's to 75%.**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HsXkC5tvwhgO" + }, + "source": [ + "After rebuilding and retraining the network on a new percentage distribution of 4's and 5's, we must retest the network. **Go back to the testing the network section and note how the digits are classified, what percentage of 4's and 5's you used, and any errors you notice.**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jkOV70V92MG7" + }, + "source": [ + "*write observations here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JdMZcsGhrrR1" + }, + "source": [ + "After you finish training your network with the first manipulated percentage of 4's and 5's, change the values. You can increase or decrease the percentages, retraining the network, and observe how well the network is able to recognize 4's and 5's in the test dataset. First, see if you can find a balance of 4's and 5's where the majority of errors occur for only one of the digits. Then, see if you can find a percentage of 5's where all the 5's start being categorized as 4's or vice versa. **What are the tipping points?**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vLFyonP0xUwf" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "S1bgrSlUgPr1" + }, + "source": [ + "Ensure that you are overrepresenting 4's as well as 5's as you go back to rerun this network on manipulated percentages. **How does the network's accuracy change when you overrepresent one digit versus when you overrepresent the other?** *Note which digit was overrepresented and what percentages you used in your observations.*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Yeep3DTUgmuO" + }, + "source": [ + "*write observations here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ud4JdXQg3qTU" + }, + "source": [ + "After finishing testing the different percentages that you can use to show varying levels of efficacy in the network, consider the potential broader implications of dataset composition before moving on to the next section. **How could having a specific subsection of data that an AI is trained on being underrepresented have very real world consequences? What possible issues and biases can you see with human decision making that goes into creation of datasets that are used to train these networks?**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4Vy_cB1Z3r1a" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rZLQldMkMpaB" + }, + "source": [ + "## Implications of Dataset Composition\n", + "We have explored how manipulating a dataset can change a network's efficacy in recognition of specific numbers when they are under or overrepresented in a dataset. Datasets, and thus their composition, is an essential component of neural networks. Below, we will explore how bias in a dataset's composition can lead to negative impacts on marginalized communities." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "j2dLUABn99b6" + }, + "source": [ + "Within the past couple of years, bias within algorithms and AI has begun to receive attention. Many computer scientists and researchers have begun to recognize inherent bias, known also as \"algorithmic prejudices,\" present in algorithms, software, machine learning, artificial intelligence, and nearly every facet of computer science (see reference [3]). Within the context of datasets that are used for the training of neural networks, bias is pervasive. This bias becomes particularly concerning as algorithms are beginning to take over human responsibilities (see reference [2]). For example, algorithms are now being used by US law enforcement for \"predictive justice\"(see reference [3]). These tools \"calculate the probability that a person will not show up for trial as scheduled or commit future crimes\"(see reference [3]). As these algorithms become increasingly present in our society, we must evaluate and consider their inherent biases." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "j7rFF1WhBCue" + }, + "source": [ + "A major contributor to the current movement exploring and combatting biases in algorithms is Joy Buolamwini. As a graduate student at MIT, Buolamwini co-wrote the paper \"Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification\" along with Timmit Gebru which explores the ways in which machine learning algorithms can discriminate based on classes like race and gender (see reference [1]). As Buolamwini describes in her Ted Talk, she was inspired to address bias in machine learning algorithms when as an undergraduate student at Georgia Tech a robot that was supposed to recognize faces could not detect her's, as a black woman. Among other findings, this paper revealed that while lighter-skinned males had an extremely low error rate of 0.8% while darker-skinned females had a significantly higher error rate of up to 34.7% (see reference [1])." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BFsKqbWBBG5l" + }, + "source": [ + "### Buolamwini's Work" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D8B-Nj2iJeuD" + }, + "source": [ + "In 2017, Buolamwini gave a Ted Talk demonstrating the discriminatory tendencies of widely used and accepted training sets and algorithms (see reference [2]). She refers to the concept of the \"coded gaze\" as algorithmic bias in the field of computer science. Within her talk, she dives deeper into the harms and discriminatory practices perpetrated by these training sets which are often severely lacking diversity. These practices include predictive policing. A study from the Georgetown Law Center showed that these police systems contain 1 in 2 adults in the US in a criminal facial recognition network (see reference [4]). These networks used by law enforcement have not been audited for accuracy and can result in misidentification of criminals, having a potentially serious consequence on the victim of this misidentification. With such serious stakes, it is essential to consider and address the biases of these algorithms and networks." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FS2tQbRTmuDk" + }, + "source": [ + "To see the Georgetown Law Center's full report on law enforcement's use of facial recognition and recommendations, please access this link: [Perpetual Line Up](https://www.perpetuallineup.org/)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "t89MgWox8_Cw" + }, + "source": [ + "Click here to watch Buolamwini's Ted Talk!\n", + "\n", + "[![IMAGE ALT TEXT](http://img.youtube.com/vi/UG_X_7g63rY/0.jpg)](https://www.youtube.com/watch?v=UG_X_7g63rY \"How I'm fighting bias in algorithms\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KE5-cmlYOJax" + }, + "source": [ + "#### Gender Shades" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0KTL9vzSOP3w" + }, + "source": [ + "\"Gender Shades\" tested 3 commercial gender classification systems (Microsoft, IBM, Face++) using a dataset specifically designed to determine the potential biases present in these systems (see reference [1]). The dataset (Pilot Parliaments Benchmark), specifically created for this study, was composed of faces of 1270 individuals from three African countries and three European countries. The individuals were each given skin type labels per the Fitzpatrick six-point labeling system and given gender labels, either female or male given the binary nature of the evaluation systems." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Q2rG1Jkrpkdq" + }, + "source": [ + "In evaluation of these classifiers, there were several main takeaways. Firstly, \"male subjects were more accurately classified than female subjects\"(reference [1] pg. 8). Additionally, lighter-skinned subjects were more accurately classified than those with darker skin. Further, all classifiers performed worst on darker female subjects (reference [1] pg. 8). Here is the complete summarized key findings as outlined in the study:\n", + "\n", + "\"All classifiers perform better on male faces\n", + "than female faces (8.1% − 20.6% difference\n", + "in error rate)\n", + "\n", + "• All classifiers perform better on lighter faces\n", + "than darker faces (11.8% − 19.2% difference\n", + "in error rate)\n", + "\n", + "• All classifiers perform worst on darker female\n", + "faces (20.8% − 34.7% error rate)\n", + "\n", + "• Microsoft and IBM classifiers perform best\n", + "on lighter male faces (error rates of 0.0% and\n", + "0.3% respectively)\n", + "\n", + "• Face++ classifiers perform best on darker\n", + "male faces (0.7% error rate)\n", + "\n", + "• The maximum difference in error rate between the best and worst classified groups is\n", + "34.4%\" (reference [1] pg. 8)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XtKuFsp3Wcz-" + }, + "source": [ + "Further, this paper emphasizes the complete inability of these commercial systems to recognize gender minorities as they are completely excluded from datasets and classification options. Buolamwini notes, \"The companies provide no documentation to clarify if their gender classification systems which provide sex labels are classifying gender identity or biological sex\"(reference [1] pg. 6). As she emphasizes, \"This reductionist view of gender does not adequately capture the complexities of gender or address trangender identities\"(reference [1] pg. 6). When using these systems it is important to consider the erasure they create of people of non binary gender identities." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ynHyOYb3tO2H" + }, + "source": [ + "Buolamwini and Gerbru's study \"Gender Shades\" brought to the forefront the inherent biases present in well-established commercial classifiers and marginalization of those with intersectional identities, particularly darker skinned women, in these algorithms. The consequences of these prejudices have the potential to only further harm people of intersectional minority identities who are already marginalized in our society. As companies continue to develop these tools, Buolamwini calls for \"inclusive benchmark datasets and subgroup accuracy reports\" which will be \"necessary to increase transparency and accountability in artificial intelligence\"(reference [1] pg. 12). Continuing into the development of these tools, there will need to be increased \"demographic and phenotypic transparency and accountability in artificial intelligence\"(reference [1] pg. 12)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UNEIAPryL3Ir" + }, + "source": [ + "To have a more comprehensive understanding of Buolamwini and her co-collabrator Timmit Gebru's research \"Gender Shades,\" you can read the full paper here: [Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification](https://proceedings.mlr.press/v81/buolamwini18a/buolamwini18a.pdf)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XUxhDr-tyhsd" + }, + "source": [ + "###Reflect" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eRu4xvL_ymAO" + }, + "source": [ + "After reading more about the biases present in machine learning algorithms, **how do you see your role as a member of a modern society in which the presence of these algorithms is only increasing? What are ways in which we can combat these biases?**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XmCFNjW111hN" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "upbZADR41UtC" + }, + "source": [ + "Go back to the data manipulation section of this notebook and take note of the accuracy percentage you recorded when either fours or fives were overrepresented (particularly for when the minority digit represented 7% or less of the dataset). **Why is accuracy alone not always a reliable parameter? What are the fallacies underlying reporting the \"accuracy\" of a system? How could this number impact systems' usage and our trust in them?** Think about Buolamwini's findings. Contextualize your answer accordingly." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4-MDz0om1z8Q" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ons3DJxzM2m6" + }, + "source": [ + "## Navigating Biases\n", + "Considering ways in which we can work towards a more inclusive computing community." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vqlxGkqDNGp_" + }, + "source": [ + "As we work towards a more inclusive and less prejudiced computer science sphere, we must consider these issues, recognize them in our processes, and change our practices. A major component of changing the presence of these biases and their impact is focusing on inclusive coding practices. As Buolamwini outlines in her Ted Talk, we must consider who codes, how we code, and why we code (see reference [2]). Having a more diverse community of coders that consider and prioritize the needs and experiences of marginalized communities is an important step in creating a more inclusive field and algorithms." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sT0MZd41gOUQ" + }, + "source": [ + "Within the Georgetown Law Center report, the writers emphasize a need for significant legislative and regulatory change (see reference [4]). Law enforcement's use of facial recognition has the potential to do real damage, if it has not already impacted countless individuals. The report suggests legislation should be passed to regulate these technologies including requiring reasonable suspicion to use facial recognition, only use mug shot databases, court approval to use ID photos and license photos, requiring probable cause to use surveillance footage, completely ban tracking individuals for free speech issues, and increase accuracy testing. Further, they suggest a complete reform to the FBI facial recognition systems. They argue that these systems must be transparent and held publicly accountable, releasing statistics relating to arrest numbers. Importantly, they call for testing of racial bias within these systems and datasets that reflect the diversity of the American population. All of these reforms are important to implement if we want to mitigate the potential harm that these law enforcement agencies can perpetuate against already vulnerable communities." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zas1mHMJfhkL" + }, + "source": [ + "As Buolamwini emphasizes at the end of her Ted Talk, we must create \"a world where technology works for all of us, not just some of us, a world where we value inclusion and center social change.\" To finish her talk, she poses a question: \"Will you join me in the fight?\" **After reading through this computational essay, consider why it is important to join this \"fight\"? What are your personal motivations behind creating a more inclusive computing space and why is it important?**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XNWsYkB7gJSo" + }, + "source": [ + "*write answer here*" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "k7baTbHH5lIl" + }, + "source": [ + "## References\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vW9gND485pXx" + }, + "source": [ + "[1] J. Buolamwini, “Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification,” MIT Media Lab. Accessed: Jul. 23, 2024. [Online]. Available: https://www.media.mit.edu/publications/gender-shades-intersectional-accuracy-disparities-in-commercial-gender-classification/\n", + "\n", + "[2] J. Buolamwini, How I’m fighting bias in algorithms, (1489075733). Accessed: Jul. 23, 2024. [Online Video]. Available: https://www.ted.com/talks/joy_buolamwini_how_i_m_fighting_bias_in_algorithms\n", + "\n", + "[3] M. S. Cataleta, “Humane Artificial Intelligence: The Fragility of Human Rights Facing AI,” East-West Center, 2020. Accessed: Jul. 23, 2024. [Online]. Available: https://www.jstor.org/stable/resrep25514\n", + "\n", + "[4] “The Perpetual Line-Up,” Perpetual Line Up. Accessed: Jul. 23, 2024. [Online]. Available: https://www.perpetuallineup.org/" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [ + "rZLQldMkMpaB", + "ons3DJxzM2m6", + "k7baTbHH5lIl" + ], + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file + "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" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 0cd795943c04945f8998686b9d2349246249306e Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sat, 19 Oct 2024 17:49:42 -0400 Subject: [PATCH 34/37] Removed old colormap; now dynamic std-based --- aitk/_version.py | 2 +- aitk/networks/__init__.py | 2 +- aitk/networks/network.py | 100 +++++--------------------------------- aitk/utils/utils.py | 63 +++++++++++++++++------- 4 files changed, 60 insertions(+), 107 deletions(-) diff --git a/aitk/_version.py b/aitk/_version.py index e1edbb7..316fa60 100644 --- a/aitk/_version.py +++ b/aitk/_version.py @@ -9,4 +9,4 @@ # ************************************************************** version_info = (3, 0, 0) -__version__ = ".".join(map(str, version_info)) + "b1" +__version__ = ".".join(map(str, version_info)) + "b2" diff --git a/aitk/networks/__init__.py b/aitk/networks/__init__.py index eb89512..4c47dee 100644 --- a/aitk/networks/__init__.py +++ b/aitk/networks/__init__.py @@ -8,4 +8,4 @@ # # ****************************************************** -from .network import Network, SimpleNetwork # noqa: F401 +from .network import Dense, InputLayer, Network, SimpleNetwork # noqa: F401 diff --git a/aitk/networks/network.py b/aitk/networks/network.py index f5c29cd..170db00 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -62,7 +62,6 @@ def __init__(self, model=None, layers=None, name="Network", **config): "tolerance_accuracy_used": False, "pca": {}, } - self._initialized = False self._watchers = [] self._fit_inputs = None self._fit_targets = None @@ -171,85 +170,8 @@ def model(self): def initialize_model(self): # Build intermediary models: self._build_predict_models() - # Set the colormap, etc for each layer: + # Config for various layer settings (like 'vshape'): self.config["layers"] = {layer.name: {} for layer in self._layers} - self.initialize() - - def initialize(self, inputs=None, reset=True): - """ - Set colormap for each layer based on inputs or - activation functions per layer. - - Args: - inputs: inputs in single pattern format (not a dataset) - reset: if True, reset the colormap ranges - - If inputs is None, just make best guess for all layers. - - If inputs is not None, use these for input layer - colormap, and all other layers get best guess. - - If reset is True, don't use previous colormap - for input layers, but sample from inputs again. - If reset is False, consider previous input - layer colormap's with new input values. - """ - if not self._layers: - raise Exception("Layers must be set before initialization") - - if inputs is None or len(inputs) == 0: - # We don't have direct values, so we base colormap - # on activation output ranges - for layer in self._layers: - if layer.name not in self.config["layers"]: - self.config["layers"][layer.name] = {} - if self._get_layer_type(layer.name) == "input": - self.config["layers"][layer.name]["colormap"] = ("gray", -2, 2) - else: - minmax = self._get_act_minmax(layer.name) - self.config["layers"][layer.name]["colormap"] = ( - "gray", - minmax[0], - minmax[1], - ) - else: - self._initialized = True - # If reset is true, we set to extremes so any value will adjust - # Only do this on input layers: - if reset: - for layer in self._layers: - if self._get_layer_type(layer.name) == "input": - if layer.name not in self.config["layers"]: - self.config["layers"][layer.name] = {} - # FIXME: set color at some point if image - self.config["layers"][layer.name]["colormap"] = ( - "gray", - float("+inf"), # extreme too big - float("-inf"), # extreme too small - ) - # Now we set the minmax for input layer, based on past values - # or extremes: - for layer in self._layers: - # FIXME? - outputs = self.propagate_to(inputs, layer.name, return_type="numpy") - # FIXME: multiple output banks are lists of numpys - color_orig, min_orig, max_orig = self.config["layers"][layer.name][ - "colormap" - ] - min_new, max_new = math.floor(outputs.min()), math.ceil(outputs.max()) - if min_new != max_new: - self.config["layers"][layer.name]["colormap"] = ( - color_orig, - min_new, - max_new, - ) - else: - # Don't let them be equal: - self.config["layers"][layer.name]["colormap"] = ( - color_orig, - min_new - 1, - max_new + 1, - ) def connect(self, from_layer_name=None, to_layer_name=None): """ """ @@ -995,9 +917,6 @@ def get_image( # Everything else is sticky: self.config.update(config) - if not self._initialized and inputs is not None: - self.initialize(inputs) - try: svg = self.to_svg( inputs=inputs, @@ -1257,7 +1176,15 @@ def _layer_array_to_image(self, layer_name, vector, channel=None): vector = vector.reshape(vshape) try: - image = array_to_image(vector, minmax=self._layer_minmax(layer_name)) + if self[layer_name].__class__.__name__ != "Dense": + avg = vector.mean() + std = vector.std() + minimum = vector.min() + maximum = vector.max() + minmax = [max(avg - std, minimum), min(avg + std, maximum)] + else: + minmax = self._get_act_minmax(layer_name) + image = array_to_image(vector, minmax=minmax) except Exception: # Error: make a red image image = array_to_image([[[255, 0, 0]], [[255, 0, 0]]]) @@ -2456,9 +2383,8 @@ def _pre_process_struct(self, inputs, ordering, targets, mode, colors, sizes): image = self.predict_histogram_to(inputs, layer_name) else: # activations of a dataset try: - image = self.make_image( - layer_name, self.predict_to([inputs], layer_name)[0] - ) + outputs = self.propagate_to(inputs, layer_name) + image = self.make_image(layer_name, outputs) except Exception: # Error: make a red image image = array_to_image( @@ -2841,7 +2767,7 @@ def make_layer(index, layers, activation): return Dense(size, activation=activation_function, name=name) layers = [make_layer(index, layers, activation) for index in range(len(layers))] - super().__init__(layers=layers) + super().__init__(layers=layers, name=name) for i in range(len(layers) - 1): self.connect(layers[i].name, layers[i + 1].name) if metrics is None: diff --git a/aitk/utils/utils.py b/aitk/utils/utils.py index 75dd76a..177aa59 100644 --- a/aitk/utils/utils.py +++ b/aitk/utils/utils.py @@ -8,10 +8,10 @@ # # *********************************************************** -import math import base64 import html import io +import math import os import sys @@ -65,9 +65,13 @@ def array_to_image(array, colormap=None, channels="last", minmax=None): ) from exc ## Need to be in range (0,1) for colormapping: + if minmax[0] != minmax[1]: + array.clip(*minmax) + else: + minmax = [minmax[0] - 1, minmax[1] + 1] array = rescale_array(array, minmax, (0, 1), "float") try: - cm_hot = cm.get_cmap(image_colormap) + cm_hot = cm.get_cmap(colormap) array = cm_hot(array) except Exception: print("WARNING: invalid colormap; ignored") @@ -81,6 +85,7 @@ def array_to_image(array, colormap=None, channels="last", minmax=None): image = PIL.Image.fromarray(array, mode) return image + def rescale_array(array, old_range, new_range, dtype): """ Given a numpy array in an old_range, rescale it @@ -105,6 +110,7 @@ def rescale_array(array, old_range, new_range, dtype): else: return (new_min + (array - old_min) * new_delta / old_delta).astype(dtype) + def image_to_data(img_src, format="PNG"): # Convert to binary data: b = io.BytesIO() @@ -115,8 +121,16 @@ def image_to_data(img_src, format="PNG"): data = data.decode("latin1") return "data:image/%s;base64,%s" % (format, html.escape(data)) -def gallery(images, labels="{index}", border_width=1, background_color=(255, 255, 255), - return_type="display", clear=True, gallery_shape=None): + +def gallery( + images, + labels="{index}", + border_width=1, + background_color=(255, 255, 255), + return_type="display", + clear=True, + gallery_shape=None, +): """ Construct a gallery (grid) of images. Can return an HTML table of images or a single Image. @@ -155,10 +169,11 @@ def gallery(images, labels="{index}", border_width=1, background_color=(255, 255 if len(images) == 0: return None - if ((gallery_shape is None) or - (len(gallery_shape) == 2 and - (gallery_shape[0] is None) and - (gallery_shape[1] is None))): + if (gallery_shape is None) or ( + len(gallery_shape) == 2 + and (gallery_shape[0] is None) + and (gallery_shape[1] is None) + ): gallery_cols = math.ceil(math.sqrt(len(images))) gallery_rows = math.ceil(len(images) / gallery_cols) else: @@ -201,16 +216,24 @@ def gallery(images, labels="{index}", border_width=1, background_color=(255, 255 label_pattern = labels labels = [label_pattern for i in range(len(images))] - table = '' + table = "
" index = 0 for row in range(gallery_rows): table += '' % border_width for col in range(gallery_cols): if index < len(labels): - label = str(labels[index]).format(**{ - "count": index + 1, "index": index, "row": row, "col": col}) - table += '" else: table += "" @@ -228,6 +251,7 @@ def gallery(images, labels="{index}", border_width=1, background_color=(255, 255 else: return output + def progress_bar(range, show_progress=True, progress_type="tqdm"): """ Wrap a range/iter in a progress bar (or not). @@ -242,14 +266,15 @@ def progress_bar(range, show_progress=True, progress_type="tqdm"): return range elif progress_type == "tqdm": return tqdm.tqdm(range) - elif ((progress_type == "notebook") and - (sys.platform != "emscripten")): + elif (progress_type == "notebook") and (sys.platform != "emscripten"): return tqdm.notebook.tqdm(range) else: return range -def images_to_movie(*frames, movie_name="aitk_movie", start=0, stop=None, - loop=0, duration=100, mp4=True): + +def images_to_movie( + *frames, movie_name="aitk_movie", start=0, stop=None, loop=0, duration=100, mp4=True +): """ Save as animated gif and optionally mp4; show with controls. loop - 0 means continually @@ -270,7 +295,9 @@ def images_to_movie(*frames, movie_name="aitk_movie", start=0, stop=None, ) if mp4: retval = os.system( - """ffmpeg -y -v quiet -nostats -hide_banner -loglevel error -i {0}.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" {0}.mp4""".format(movie_name) + """ffmpeg -y -v quiet -nostats -hide_banner -loglevel error -i {0}.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" {0}.mp4""".format( + movie_name + ) ) if retval != 0: print( From 28c50a82f00f96c33ca1be12062604ec59f47945 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sun, 20 Oct 2024 11:55:09 -0400 Subject: [PATCH 35/37] Fixed dynamic minmax; alias SequentialNetwork for SimpleNetwork --- aitk/networks/__init__.py | 4 ++- aitk/networks/network.py | 73 ++++++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/aitk/networks/__init__.py b/aitk/networks/__init__.py index 4c47dee..a8d22f3 100644 --- a/aitk/networks/__init__.py +++ b/aitk/networks/__init__.py @@ -8,4 +8,6 @@ # # ****************************************************** -from .network import Dense, InputLayer, Network, SimpleNetwork # noqa: F401 +from tensorflow.keras.layers import * + +from .network import Network, SequentialNetwork, SimpleNetwork # noqa: F401 diff --git a/aitk/networks/network.py b/aitk/networks/network.py index 170db00..295a242 100644 --- a/aitk/networks/network.py +++ b/aitk/networks/network.py @@ -1176,14 +1176,7 @@ def _layer_array_to_image(self, layer_name, vector, channel=None): vector = vector.reshape(vshape) try: - if self[layer_name].__class__.__name__ != "Dense": - avg = vector.mean() - std = vector.std() - minimum = vector.min() - maximum = vector.max() - minmax = [max(avg - std, minimum), min(avg + std, maximum)] - else: - minmax = self._get_act_minmax(layer_name) + minmax = self._get_dynamic_minmax(layer_name, vector) image = array_to_image(vector, minmax=minmax) except Exception: # Error: make a red image @@ -1191,6 +1184,34 @@ def _layer_array_to_image(self, layer_name, vector, channel=None): return image + def _get_dynamic_minmax(self, layer_name, vector): + if self[layer_name].__class__.__name__ == "Dense": + # Get minmax based on activation function + minmax = self._get_act_minmax(layer_name) + elif self[layer_name].__class__.__name__ == "Flatten": + # Get minmax from previous layer + inputs_to_layer_name = self._get_layers_to(layer_name) + minmax = self._get_dynamic_minmax(inputs_to_layer_name[0].name, vector) + elif self[layer_name].__class__.__name__ == "InputLayer": + # Hardcoded to typical ranges + minimum = vector.min() + maximum = vector.max() + if minimum < 0: + minmax = [-1, 1] + elif maximum > 100 and maximum <= 255: + # Assuming image + minmax = [0, 255] + else: + minmax = [0, 1] + else: + # Compute minmax based on mean +/- std + avg = vector.mean() + std = vector.std() + minimum = vector.min() + maximum = vector.max() + minmax = [max(avg - std, minimum), min(avg + std, maximum)] + return minmax + def _make_color(self, item): if isinstance(item, numbers.Number): return (item, item, item) @@ -1359,24 +1380,17 @@ def _get_act_minmax(self, layer_name): Note: +/- 2 represents infinity """ layer = self[layer_name] - if layer.__class__.__name__ == "Flatten": - in_layer = self._get_layers_to(layer_name)[0] - return self._get_act_minmax(in_layer.name) - elif self._get_layer_type(layer_name) == "input": - color, mini, maxi = self._get_colormap(layer) - return (mini, maxi) - else: # try to get from activation function - activation = self._get_activation_name(layer) - if activation in ["tanh", "softsign"]: - return (-1, +1) - elif activation in ["sigmoid", "softmax", "hard_sigmoid"]: - return (0, +1) - elif activation in ["relu", "elu", "softplus"]: - return (0, +2) - elif activation in ["selu", "linear"]: - return (-2, +2) - else: # default, or unknown activation function - return (-2, +2) + activation = self._get_activation_name(layer) + if activation in ["tanh", "softsign"]: + return (-1, +1) + elif activation in ["sigmoid", "softmax", "hard_sigmoid"]: + return (0, +1) + elif activation in ["relu", "elu", "softplus"]: + return (0, +2) + elif activation in ["selu", "linear"]: + return (-2, +2) + else: # default, or unknown activation function + return (0, +2) def _get_border_color(self, layer_name): if ( @@ -2700,7 +2714,7 @@ class SimpleNetwork(Network): def __init__( self, *layers, - name="SimpleNetwork", + name="SequentialNetwork", activation="sigmoid", loss="mse", optimizer="sgd", @@ -2762,7 +2776,7 @@ def make_layer(index, layers, activation): size, activation_function = size else: raise Exception( - "Invalid SimpleNetwork layer representation: %r" % size + "Invalid SquentialNetwork layer representation: %r" % size ) return Dense(size, activation=activation_function, name=name) @@ -2792,3 +2806,6 @@ def clear_watchers(self): weights, etc. """ self._watchers[:] = [] + + +SequentialNetwork = SimpleNetwork From 19ffd205565e1ed6231e9c6c9dd1d182d9a492b9 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sun, 20 Oct 2024 11:55:50 -0400 Subject: [PATCH 36/37] Updated notebooks --- notebooks/Advanced/DogsVsCats.ipynb | 2543 +++---- .../NeuralNetworks/BasicNeuralNets.ipynb | 6207 +++++++++-------- 2 files changed, 4634 insertions(+), 4116 deletions(-) diff --git a/notebooks/Advanced/DogsVsCats.ipynb b/notebooks/Advanced/DogsVsCats.ipynb index 685d691..021cf19 100644 --- a/notebooks/Advanced/DogsVsCats.ipynb +++ b/notebooks/Advanced/DogsVsCats.ipynb @@ -20,19 +20,20 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "from aitk.utils.datasets import get_dataset\n", - "import aitk.networks as nets\n", + "from aitk.utils import array_to_image\n", + "from aitk.networks import SequentialNetwork, Conv2D\n", "from PIL import Image\n", "import numpy as np" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -41,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -50,7 +51,7 @@ "(24478, 128, 128, 3)" ] }, - "execution_count": 3, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -61,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -70,7 +71,7 @@ "(24478, 2)" ] }, - "execution_count": 4, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -81,121 +82,212 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAABiY0lEQVR4nO392Y+d2bIfiEXEGr5xzzknkyySNdepOlV1Bt1JV0a3riQD6oaMdttGCzDgF8OADb8afvU/0Ab8aKMFwxD8YrQNud2ypLZu3/neM1TVqSKrimRxSOY87Pkb1xR++JK8R63/oMFAIrGZ3Htn7rVixfCLX8QCeCNv5I28kTfyRt7IG3kjb+SNvJE38kbeyBt5I2/kjbyRN/JG3sh/3wX/9/+7/0NZlv1BfuvgIM1i6711xhgzm84FohLatq4pm0Qned4HH0LM3Su95xBCCAERiUQcx0QkhEAQIQTvvbXWe9+PUyKaTqdCySiKjDG9Xi9N09q0dV1nWcbMIQRmNtYOBoM2tOy91hoAynXhnENEY4wQoqqqra2tNM/Oz88RcTAaNU3zO//h39NaK4HBeQgskRACOC+QIpIEIVhHDJKED65pGr/0ZVkWRVHXdVvVxWpdrApTN23ThNb71kBgBSRQEgD7MLDOAQRJrFQgwY5964R1MgTi4BWXWDUZ9u9syq3+yjcy3vDea637/aFWsbXOWksknXPM3jnnnLWudc5470MI8vVWMDMzAwARdUtJrx6/FgRkcIjIzAQIgACIgMiAiIh4s6uI3XdEZEkoJUVKSElKYvBM6IGbpnHOIZEUottI5z0RudqGEAgFh2CMFUhZmg2z/mw2Q8ehdUF7CYKQZEDFJG2IJCYqFhGzDxTYGeuNtXVVBa5XxfT6cjGdFet1U1Z1XaeYWWu73y4AlZTd78qEQqVQxoQoAJEJAzNzXNcmBIvsEQUgCxKaJAnhPQaPMkiUSqCUUgghAgKAUiqOY621IOF9eL2eIbAQIgQvhGAWzIyIUimllLp5vRBAJFEIIVZijcxEJKWUUt5oNzKDR0BADASeGRH5ZqUBGQi6hyCImAiYmRAEoRQgCAR1n8t5r7RGoiiKAMBa2/01iBhHEQBESltrRQD2jpUDCf00kwFiklHAjBQASBuc8blFdG01XS9n8+nV9XJ6vZrN61VRV1UshCYBPgjgOIoGOhrrXlOYnHTQ0nKLDJGKFEnnHDIAAYHoFJEASCoiUojahya4hjkgEQiBKEQg58A7QEtIQhARAYD3nkKIoihN0yiKOCCRl1IiCgBAZABglgyeWQBACEES0X9HeUkQIgoh2HtEvNF9xJvD0ak8IiIw3ZgjBBSASNQ9v/sM3WPLgRAccAgeg/fAjBCASQoKobM/3jlglkLEUVSvC4EkpYiVoixA4FhpDsG64CyGdWvrgFXrnCuxatv2v/pn/3clJCJ6YykEKYQCigLt9zYFIAVG7wlAAElL6HAU9XpZLqU0jWXvCdBaW5laSsnMzoXWGuMdAAiNiijWkWWG4LxzgRFRCA/ggyLhORBStxrdanrvTdOkaaq1llI6G14tr2BmAGLmTpW7DUBE2dnfTrz3AQCQurX23r+2J927Ewm82S8kQoE3tgsBu615vZ0hBCEEAHgEJgQA5714taOI2FS1d46tk0IIoZjZsVNA47TnWiOMD8Fy1drWtD6YqmbnTd0QgxKSvffWQWDn3DjOFDEieAcYUEsRCYEsEyuBvWSKVRxpLUkwewhhulwFFiKK08AEKhIyoDZBAiADOOkbocu2sd6BY2CHSighWVIg4RiAKQQXnEcpBBGj0CgdASISIhFVbcvM3WfvHrzSxhvVJCLqvr02QVrrKIq01kopzywkAUB3Al6bcu+9914AgrzRdGCkAJ4ZmBmQ6dXWdG9NhMzILAAVCSmEB5BIDCAAyYVEqBAwEUoJyezbtm1W5dqGFHSzXltjbNOW6yI4j8xtUfWzXLkgEGMJkYgQNRKIREStjeNYK00KiUBKKRDZB+es9yAAhefQ2sY3zjlm3tjYCM671lBgQGIBkVK9vF+WFRGBihLtE6mLtq6bxhkDqAUJTcITECCRdB5aRGQQJEiRAqtEEEQkZaT0uuUbYw7QLSMAOBdeGwYiEnBzAphZCiGklK/dAIfQLaNSKjhHSN0WBA4hBCYOCEDdZmIABkZgYAZGAEKgm/OIgoADAgnPEVBMkgMqJu881MZbr5Wqi7r22DAE50zTruaLNorOrlfWGKWUEpJC6Ok4z1KI+hRYaRIMsVSJisAHYM7ihNelUoqQrHfeerbGheCCRyGISCglpPTsHASKkziOG+LFalUuVyKAFjIh2Ut6w7yntep0SQGhkgiRZLRkW9MqIQGJACUJpbQKRMbZqlRKSCQBBBA6dVZKaQ2dNnvPRCylCiE4F4QQfGOwGXwAkF0sI40x1lrnnHMOia33JLDbOiJ6vQGvg6UgEAiYABhD9xX4VSgEDMBdZORvbJdonI458+R9UAxt67ENrmlXTbuYzuZI7IMkkiRM06os6weBKu1luVKqbVsBGAcBAWKpIqHAeckYefSN5xAU+khoBHI+eBeYQUWaIsWCamuCAC9w2dSX8+liNScpe72eFeHq8rItqkHSG2W9XpQI6fNMRzr2rTF1G6xHxDSKEx154JOLUwgBGTkEIaQWEiIhrJstFhKBNTJwcL4LghExjqMoipRSAD4IEEIgIrN5fQKYuxPgb5ywlLJT+SiK4kS31pJAIkKgcr1GoiRKSyidc51jEJEEAB+C884ZE0KQJIjIOZckCRHZ1iBiHMeooGmakVJmuvJlWa2Lpq5DayGwBITAPRCKhJJCkZAksJcJxCxSAIAOBXMuEyKCwAEgYooCCqnBerQ+lRoBoHUOQpZl7J0SlKV62VRFsUrHgxLC+ez6h+PDH46eX82nlkPW7+X9Xmnr+XR2/85bVM3zdfzenbfnq8s6ET2TbPSHJJEbE5Eqi8LUTb/f355s1sYs66pxFoVFpkwnaa/fU3q+mC5Wy0Y2apKkaRqIbNWy0L1erwsuENFa21mXbrlvwhwiIuqCb+n/PWFAZrbWhhAChM6TdBsIAI1plVJaKa1FUNq2xjnHzrvArLTWUkiFDMpx27amLM9fXLIP3nv2IWIWgQhQAhKiQurSJYkkUXR+bCB1QGDmAAyBlSCZaqVUtS6koogkKikCSBIU2DmHAkJEtvbLppQipjxmDxft6vvDZ04jb2S3Nj7ckejQz9er2Wwm9Wg06fGgf3x0kjvXg7JZlsflfHc42TdbOqAOsJEPojRhZmOMAMzTDJQwi2XVtC0Ilk74kEU6kiqJ48a1TWtca9BJ7z0IeB0EIgIA/zt577+XKt2E/53cBPsEXfivtUYmItJae3PjkJMoxsCuNd6G4FyXfEqSsVQREzTW18a0rTe2qeqiKLLLsnu3SCollFZCIAkGYpBIEkkJKZEEkUACAOW8kFJGigktB8/BOROC01lESIZDcE4ixYqUkMRiXVcSbLw12FDjypnCm/m6ejm7UFv9nYOdW+/cS0b9i/n0ycsXan419rdePn+xvbm5OZ7Em6N+lqdpfrR6eDKfUT+tV+dh2SQeP7p97/bWbmA/vbjqR0kcJyypaNrWefYhsGPnLLMQQmsNDl6bh26JOvV/vcqd4v720r+K4/FmA/6dXPfV/0dRFEWRaaz3vntr51wIQQXwPoBxbDyGIBm1QEWEtbVF2xbVerFuqxp88N5760bxQCBJKbtYSAASAwEIou4EdD+XXQzFABgIAIGRSAmJBMzBI7OmQMTM3guPxBJr8MH5ZG84Xcw1NlJGs6aog9Ob/Vtb+dbt/SfHz798/GBzfzcd9TcOtid3toej0eYX34zHY8/BEQyH4ySK99nmOs5AD0XsZ2tztZq31Q6E3mhIRM3VwjQtSsrTTEjtPROQBmzLQkoSgBBYAGqtZRSJikDeeNf/ju98HU++3oDOqMgOkejCfO/Je48MzNxZKmste0BGZu7COLuuFIlYKopjdMG2rV1WdWurVQHOu6qxjSXPkdKR1krHA53c/G4G8iy7AA4wElIAdksvUXT5BSDEeWq9M8GFECDSMtagCJBn6yUzeuDAgZAoULDOGCNhtSzXcUhy6lGuRuONoHA9vfqv/n//34btYDLubY6gEvPldLFcEpFe+2xDrapyuVg4Drt7t4Z723mctMsqH27mOzuzpy+vTqbq+PDt7Vu94UAUbdE01kGa53GSVVXjjIuFZKm6zBYRlZKR0igEM2utO339ba1/fThuAnUkfpUKyH9P/W82iJCUUt1bRFFklDHGEVFPKeecL5u2tqaq26IyReVqIzwrRg2YgYqkTKSOhBZC3CTLzDcLLYQWUhAhgwQkIklCINFrfSEvlZIqdhJr8Nf18vp6tWwrkUSrtp6vl+uylFJ2iJ6U8ur5RRRFOfcmubi7f2+yufHdk0d/9td/cXR6cuutWxzct7/56vjkZFUWWS8dDocbJr492kwIkkAScDwZRlleVZX3fmFrBB0Ne1w0zvGsXJVFcSvLrQ8uWMIbzCFYV7UmjiLnDHlUSrEUzOydY+YkSaSUXQ772/Daa6wFEX/7MMjXKNCNhIAEiChIKKWEEATUpelVVUVS6Rqrqirmy3Jd+daQCzKAcNCLU40UkUxEpFFIJOkJA9iEBGDnXZSQWkgphEDq0jrxagMQkQABwEKrdEyxqr25WEyfnZ8czS7ndTHc3Spcu66rxhodRXUMqfXkqURnyxrKxYHisdkpr86eH78s66IoV4+/+z47ORoOh6kQgUR5Pa+u53//7/7H/SAXbT1Je6xiCDwejzc2N48Y1uezGHFnczIUMc1L9MJZj4hZloFtS2MgcKS0V7acL6J+3sU5kVIO2RgDTnQwnJQdvgCvHcBvwwqvN+BvTdBvCTrnOicMfONMQmBLtizL2Wxmm/bs4jJ4zz4okInSiY4jECpghEJ4Vh4kgiZUSBqlEKJ6hSx10SoihhAYWUnZbYDoorJXJ0CoCARVTX02u3p2dnS8uC7AURY/PXkZD3qDrcn+eJTmGTMvl8vr+fzy5KSqqvF4/PHWp/2N8ePHj58fPouS+PPPP3/x7CkR7W/tEOKL6llvOHrnnXeoMc+/fdRKGN/eXYP7+suvvnv6NM0yJaVbl+PxrtR6tj5bH5/uZOO9jS2zbtMs8wJXi4UPPOiPMEA5X7jWWG+ZOtTBWWsVJEkcd1r775n+f+efzH9rnSQQR4lWSdw6i1IIoSIZBesmSX+5CGY9uz45W02XUJmN1oKb6jhXHbgshDQoiVSn70KQJEVCSolCeClaIkRM5Y1BpBAUohQoSUgkgdR5f/CBrQMAKaWWcmF90u+9OH3xf/ov/29NT3/0ez87vjwfDJL+cPv2zt7extbe3s755YWMo/T+3X/xX/9/dkejC2MGWXrnrYPpanaxvG41/fQPfr/f79MgW87mIs/G/cHzFy/zpO/a8Dfu/Hp+2cvy+80gOF8enl1Ov2m9+9Gnn+g4Pgkn6pbg3TREm7968fKT0XB0TU64Yb/Pni4uLwHLrX4/2dq4vr50tkk3B0VwrWAx7FdpvA6wBQpql4vUeEMohv1ebdpVuZKRBqIgwLG3IZjgHfkALAVR0TRNUcrhKBISAMgFX5u//OWfmlVdzZdhXZMLEQslJZKQ+Lc+Q0AXPgqBJLoI5/V/IcErcBT5pq7QZbxdgn0TCTB0r0bEzh1t7u/9yS/+6v/5x//65Ori7p1PVsU6SmKSYmNjUyl1+86d66uLOwe33//RR/+P/9d/2VZ1uSrrut7d3c2ybHp60rbtO++8c+vWrX/+z//5Tz//yf/2f/2/+b/+F/+sKIqf/vSnV2fn0+n0++Nj9p43t46Pj+fT2en5mQeOsrQLQ6bTKbkwTPJESimlc84F4evaSySCPM+dc1VVJUmysbHx8ux4tVz62EslitW68WUy6es0QSUZKRA654I1DBAniXGWkZGZGASgBARAZpDI4K1TQm4Ox9662eX04vh0dnk1O7/ENmDrFGNMShAIBgggtFBCKikVCYGkkCQJhdRtzG97kxsnQ0LgjQ8QSPKVuRdExNBtlbrJ15l9+OL7h7958v3p/DpICgIvZ9P3Pv4oiaP1bOGgfvLkycMHX3/++ecqiZ88ejwcDC7mKyDa3d1NkqSzv3meP378mJlfvnz5/Pnzqqomg+H04urBt99CCKAUIlZVdVxU5+fnzPz+xx+9/6OPluVaKhWcc85JIZqirqqKQyBSjkNd1zpN+v3+fDYrV+tkOBjkvclwNG1WrqlFHEFrjXdZr+e1aIkFMCsRAJrgiEEoGUwLEAA7EA4ooGBiZtlF6BpFhOLl0fHzR0+On71YT+d7423hiUhpFrrzkwBIfKPCRJKEJJJAikRnygWSINF9ERIRMYJ6tTGqeyHcGD8tZAeeKCGklMjgnPMh/Ms//beFNx989ok9/sEGL1SUJMlqtfzm6683+sNnT34YD/vM/Bd/9mchBO+sEGJra6vX611fX3vv26apqurZs2fIcHh4+H/8z//zjz748P79+6eHR3mer5dLY9tytTZlFWldNxUzW2uEJGvN9fXVdDrdHk02e4MQvAAQglSsJEDtHDuf5HGjo0U5LdciSaK9nd3MDcrT54FBkrYhYG0cgfFWESgtSQhbG+sc+g6HCIQdeg8QUMGrDUBE0zSLq/l3X359fXwKtdnNxrJxMoAEFMBKdeg3dc5b0Ct9R1J4k0Z1Nv23M+puqRUQAQm42SEAIAZkkCS6YtpNFMTsA4MPXtKLw6O3hu+rSA+HQ5En3z389tnzp5KxXq6X09knH35wcXV5eHS0vbvz5MmTxWp1cOeOc+7hw4cqiVer1db29tbWVpZlv/vzv3N8dPTxRz8yVb23t1euiwcPHvyD//g/+vbbb6vlWik16PXrtmmaZj6fH+zfAoD5dHZ5eVkcFMMo1Vqfnp4CvhPFkalKY0wO+aCf27qqywrY94a9ZJgtXTUNVRUgcpB7SrLUNK33jN4DICPY4MGHACyYgUEA3JRyAzIDGWcDAqFEgPV84comQT2OszhgFFA7VAHQBQiMIASpDj+QJDQJSUIKoUgoIbVSWsjXX11+q6SUggShBBDMxCwBBKEkFAiSKBJCCRIICCwIlRTv3Lt/sTj77psHwvhhnLl19eCLr84Oj6qynM/no/H45enJX/3N35xfXlRVZZyt65qkuJpeX15egg+DwSDLsmCdADTGfPTRR/ffulsUxd3793b39/7JP/kn3nvbmrZtiajX68VxvJzNH3373ePHj+M4/uyzzzY2Nlar1Xq9ds69ePGi8cZxEEJwcNaYPEkng5GWkp2fX8+D8wfbuzvDiXYct6EfdCp1FsWahLPW1i2/qgnDa6gYABm69AgBZBvYAwYEpRQGTnUi2tCsip5OBCEyAHUFR8GEnkALlJKUQClQEWkkKUgS6VeYxmtso0MWIiFex78SEAEFEBJSYCG60j8CABNFSoOCt2/d+ezgw5PZVeIxzAtfF4kDR+ri8Hhra+ujjz6qmzJN0+liPl0tGmfjNHHONU0TRVEIoZ/34ig6Pj7+/PPPz8/ORv3B8+fPv/rqqzu3DqIo+vnPf/7P/vk/V0rdvn37YG9fKVVV1boq1031L/7Fv7j/ztu//7u/l0RxVVV+VcnAxNBaAw1GKoqVtlXDMurnuWvaslyvlsukn6lB0lfxdj4USLI0q6PzNE0zqcmjsU6gECS6YiYjMrDveAwMzAABCGNlgl+U68VyWdRNBzARYLCefUBEQklKkpKkpVBSkVBAim4w5C6T0iQiIRUJ/Vvf1esfopCMkhEZBJIkUkJ0vIrXSYBA6rKYD3YO/mf/8D/69Nb9fgt8udxR2bsbe7CsNnqDPEkvLi4CwtbBvkfASEV5qpQCwjRNkyQ5PDycTqdJksRR9OTxY611kiS//OUvv/zyyzzP/8M/+vu3bt3q9bK8l5bF6je/+fKLL361Wi22dzbfeef+hx+9X6yXJ6dHgCFLIh9s8JYEePDGGCEoiiJrmrauBVIWJyFAV8RtihJMuD3ZuTPc9NNy+u2zcLHoWxyyyoJIAskA7DwAMIJHcAQWuUW2yIaY1nVFiRZxwkolvdxyqNtG6UhoRVIwUAAOzF1mrNNECRlHUSQVBAbnCVAhCUAtlZZKk4yljoQSSMF507ToHQQnkCMlIiUEcnDWW4McjGls2yAHIgAIzF4IfGuw+dN77/+v/pP/7OPtO+bk2p3NyqOL+1v7O70R+rBcLp8evvj64YPtg/3pcgFSlHUVQnAcVqtVh5qU66KqKmYu18Xjx4+/+eYbKeXv/P7v9Xq9JEs/+/TTNIqttfP5/OriwhjzOz//O6PhMFI6jmPbmuFwWBRFURTMHMcxSaET7Zxr6koA+rYxVR3H8XA4ZOb1chkprYGKy2nK8t3tW/Jy/ezPf/3sr76kabEpE1Hb6moeoVAkhBAsyQloMRhiqwkSLT2SRwrAxjsXPArk7nnWM0olCASBFEiSkVyASJAilISIJIFiLROpBJIgEABSkpQEAMEHQu7K9kSklNJSvcakOhvlnINXjADvfYe2xt6qyt7KRv/4d//e3njju8Nnh8s6tOhjMdrbbAn7/YEHTrIUlJgtl6Tk9Xy2d3BrsVxqrYvlynu/s7VdVdV8Pr86v8iybLVa/fEf//Hm5ub+/v7du3dPXh4t54vs9u3xcPjBRx8ppcqyTNO0MS0iKhKTyaQKFBpjfaiqAhmEhi5+h8AhBA6hC3mNc6Y2SBChkh6t5Z/s3D06P714dvbN4enw1u7OO/cPNrYbwVflKkgGiSglSLC2NaZxzskOrzDBNaatrYkZQAkWZE0gBBAkpCYliQQidaQEJaQSggIoITIdR0oLIgxMgFoqpZT33hkLgFIp1a3+K4wQX7EiuugrhHCzG1J0iAX1B+MAIOjj9z66e3D70enLu0/uPzh7/nx1ffudd0/LhR5lpW3zQX9cja+vrzXi+fn522+/Xdf1eDy21hbLFTu/MRobY548ebK1tWWM+eM//uMPPvhgOp0uFrPxePi7v/PzUX+wWCwIwqNH302nV1rLwaDH7Kuq2B1txGOuF6uIpGdm9hiYvXMmoFCgPIBKsjSK49I0VVVpLbUSyiF5iAr33mD7zmB7ZquFtdXFVFizBBsPshqscc46x8RCya72J9EH8hysraumaVqNCpACEkkBSPgqwCchFEkSnenvKtSopYqUjqNIAHb0BUmkpbTMyIwAWqpIyY5vEULodLwjwtwARPJGA7otIaVguQCtuG2DN3ma/+QnP9u4vb/+4395tJrmabaZKsp0jyBP0lvbu0+/f0xCrFar07Oz1Wq1vb29tbWFiHmer1YrREySpN/vb21tee8PX778/tGjYNrhcIiBF+ns/PQsSmKQQqVxVRR5v++MOT8/T0kNdNLv98e9Qb/IExlLRluH4JwIyM57FYhIZ0kdbG1aYC9DhBIiVL5oUck4S7NhFhbXy1U13NpI+8OZrZQgj8I6a9rGhpvSr5SOOQC7YKraNi3HEkiEEHSH6aCURApIo9SklIwEO+oK8AgSqYOXCZABnbHsAwFSV6YQFGutJGothSBrPUBABObgnAPiKIqU1s45awwzMwYt4Lpcbm7v4LBPi9lqtcwjoZWSSLPZ7PDwMN4e16YcbUwWV9PhYBCTtKFVSh0dHRVFAQBbW1txFL19//6XX35ZlWW3E2VZdia73+//7LNP67K6vLiIlR4NhoPR8PD4aLFeFUWxd+uWt3Z5PVssFirnTOooijLOYqm0I+/IhhYCGGO892kkozjV3pq2scYb32qt0jiNkqSsymU1UxuDno5B4no6/8Vf/TkOs2xrONrZGPdynyTGtyF4AJBRIGLhvAPj2HmCVzCpkgKkJqlQRCQj1AlFWmjgQAgCQCB2kbsUJABJiMIY9o6AlSAiFESRUoThdYGiYzm64D2HsixvtJ6IlAQAkpIJh3f3l8u1DqY21bJa21It67VO4t3d3aurK8XmZHo+3txIdPTJxx/vjjcePf2h1+sZYxDx/Pz85eFhU9d7e3tE1KGk09msLEuttRDi4ODAOae1vn37dl2UVVFWVbVYLC6n171eb2NjI1JqkWQRE//WeYWO7plJELFt2qJpirqGRIssjjhxzoEPzjkHhJpDU2e9nuLASeLQseAfXvzwX/yf/y8h1dnWcGN/uz8exImWkpSSkdYyAiFJtmBFAGKQpCSJrpLcFWwToWIRxUJFKEVA0AoRMbBCklJqIaWUAlAJWZdl9+cKIZQQSqkoitg2nZsNIQglpVbgEAC8962zaNrX21PXddu2cyd//de/iEG4phaI999557otWms2t7efP3mIkuuqOnl51E+zq9HG1nB8OhykaVoXZZZlQoirq6vOtUynU0Tc3d3d2Nzc2dlBxIvrq5Pzs+nFeaT0zvb20bMXTdPs7u7evXv3zr2759MrKWWWZYM449YWl9MQghbSmFYwCOCESUspk8QyNyHUbZP3U6kUSiGFkNZ565zxpqrzNAnsbdMUtsJBvr+7d//2W08vT5ZX0+vrqwAeMBCBkkIIITUJT8wkOtPRRUvogZmRUJKIpNIdv4mRAqCUAhCQRVfNkVJKKQJESnWfvAt7ujpPpJTxBjpmIwdi7p7jnIvSpCt5dlpmjJkvF7PZ7Df11fcPHt7d2a+XS7ZO5enKtVVTn12cn5ydbmdv3bp1a7VapSpaTmejNH/33Xfn8/lqvojj+M6dO+vVajweZ1m2u7vb/Rl100wmk+3t7YP6ztnZmS2K68srQVQUxe3bt9999902uG8ffd8ZKwhhd7xJKBbOEZBSKgC3bcu+DUGiSrWM0jS1Uizb6m8TTCFE4GCtt7bX74P3jWni/kijrrxPk+Rgf/+qWlWhbbwJSEKgQHbWmLqV58JIBHJkFkWKkqqmFwkNlAlKFEWxEIoQPXNwSApIm5toUkqhUZAAxmAhBO8bblGw4VqwSHKVJImH1kusTaOlyvq9pmmY+MsHX02Xi+vl/L1PfxT1sl89+M2zs5fbB/s6S55fvDip1tFBfph72hgcn51+8fhP00Hv2fPD4/OzjbsHzgf2cjLZHY0m89XywcnZIII0Td+9d7eoq8vp5d3793bv7EdRBBIl4CBJyvNLvyhHg82Bk8No+ODlcouG4bw8yLdk6f783/zbVvjd+29Nm+XB3Xvz6+n1w4vP777349vvhuvVrku3417TNNbbVpIBD6aUUka9RPu2uZ4ppXaSvnOuDM4iGmfOemKc99fni/xyfms4tAbOV+tPRrt/cfmnyb3ty7I05KUgMGZjlPu1lR00IaBjw/NN+RBFHMdKqQ7NB2ZA7DAf8QqVuzkreBMaKyFkRz595aJDCNbaLMkjkiGEi9OzdVVONsfb+3v7b9/9q1//8q9+/ctZsarYWRGeHh+u6+r04lyPh+8f3BoMBqcXp/PljAkd8nw1V0olSSKVTrM4MBrTtG0rhIj6uXPOBR9FUQfrV1U1GAw++uijJ999//z585NnL2xRL6ezREdZnEgpBXLS73lnX7x4MV3P996+vbW1per04uQ0j5K9/VuL+VxwvJcN2qISatjF0N3B9famPaJDPl4RIG6OQgjBW2ubNooicGCMEWmstd7d3c3StKgqicQSnbOKwVrb1alCFwi+bneRUmqpkjiOlJZIr6uaHVTQxY6RVEopKQQBsvPBuo46GZEUiARwA/B7X17NoLE6SgRSnuc2eEt8vpyND3aOZpd//fCrH86PC7Y1hpCo4d727Xt3esPexezy+eGL+WpZNXVZVcwsFalIR0kURRFCsNYGaxC5aRohRJqm/X4/hPDkyZPvvvuubdvlfD6ZTMbjsXEuH/Q///zzg9u3Hz/9Ie/3fv8P/+4/+kf/aHN7Cwjv3Lmzs7Nz9OKwXK1v7+4Psvz85fHqaqGBItKpjoQQURQlSdI17XSdP9baLnwIr/i2nZNnZmds2zRJFLP3dVl1ld6dza3NyYYp61jpVEXkmYicsUIIGbznbq2sC94jgBIyIiXwppguAQmpW3cpJQff6X6HgwoGH0IwViaJRgEIEBgQpBJSSmttppPgnFuuZtNpNum3PlTBPb04vigWV6YsJRdmVa1oGG9kg16C2WhnUpjqxcnhbD1nwUVTYqS2dravZzNmrusaBDVNS1IU5SplV0GUJEnn54UQdVkdHR0dPn+xs7V1cXY+v7iaTCb3bt3uDQfFat2VvQaDQWmak9NTFCIf9C8uLr58+M1wYxL/RGUqgdpoqRNQqVDjpC/tDauQmSOlO5ZfVVVRFHnvu18qpYyIAnNgJgDbtCLKauesN6pNqqaUPXVn/9aT2UlEErSoqRQI3to40jKEAAIgsPe+C0O1VJHU4AOKG5RfAkm60Wl8Vd7qKmLEwJ7BBQqsSTBztwFdFd6xBQDwYblez9fzSoU124Vvvj958fDls4Wp490RJZFHWLqmKtl6V7sqiiIDjmIdKtcYO1A0nEzKtjGuXS7Wuiq992mamqYRgiCNGtMWVZmafP/g1t27dy8uLh4/fgwhXJyfnx+doPW2bsrV2lT1qizKpvzyq68ePXr06Pvvtne3Tk5Ojs9PI5Kz88v/9l/+67/7s9/76Yc/ro6n7bLIx/uJUOiQmW8KGHHcVdOapum25HW40TEhvPcevTe26/npDFewDizd2bvV/+FB5Rk8S0YKwVvHWknozAUiMENgRSLWOlExee5gS4EkO8TYB49eK0l0UxWTRJI6FEJ467oODYYQAADABV81dTAVSnG9nl+tl5dXx+fVcg328cXxyrfrUCfpoLcxtuzLpjSuFkKcXp8nSXK9mM1msxDCeLQx3txwzus4stau1os4pHEcp1kcJ3owGKwX86yXk5JVVV1dXXWs9Pl8fn5+vjGZbAxGwdhIyNFookkUdXV5fcXMR6fHs9UyG+bQctM0/VFfIU0GE+24upr3ZTQAzbVLsggCcwivlT2OYxe8cdZ6h4ghsHFWOKm1JimkVtJzIDIdriwVAMRxbBH2t3f2N7cfXx15oAiFcbYj4MpuD0k4A0hEWqlMx5mKkVmSkCQ6dgv44MGL36r6ShRKyEhQUIgu+NYgglbKgWcOnkNdm/lyGaFwEL589ujZxcnj86M1uWhjGDJdV1UDKAXUwZZ1URRFV9kHtNfn84uLC2u8EKI/njCJZbHMB/2iKJiwU73ONPf7+cnLw42Njazfu5pef//991GS9Hq9NI4PDw+/+eKrLEn/4Hd+N9XRNw8fxDqKo2h9NWMEncaDyWixXgmJzHxxfL493vj4/rtuWf/w5be//6PPb23uUuPIcNem2cGFiEhSJEnSnYPOJTRN44ETYEQEQZoFaGqrJotTksIYk6RxQ3Z/c/vu/sGT4+eMmKWxM42UipllByd0pFyFpIXUSkVKKRQCbzKm4LvzxiEEFASd/iMRkRKSGUGpqmikVkAkEByA5VDZdl6uHx8+i7L0h+kZDFMNo1RwI6E0TY1htLO5ubXlvFkVy9Daum5Wq5Xsq/l8TiS2djYXi9XZxXmS5t7z7YMtQSqOrgK7uigX02siEsAhhFVZMGFRFIvFYkPKfp5HUTQejo6tPzs5/eI3X20MRuz9YDAw1t67d+/hd99Fsb5z787L46PFbJolCcVQL9df//UX29no/mQvF3FPRnGalvOlkvFNr5X3AKAEdf1f3nvHwTa+ddbVoQtSGEEgKS2X67LbtrKp0zxGhnFvsLexJQMEz5mOSxJCkfdWSimbqs6Bzs7OVovl7o+32TjrWqm0SpI4jpm5Kau2btqmsUQoME+zVEdaavahcU0kZJxmpqmttS17nUUyjkpnanbJeNBTBw8efXvJtTNmAfZqvaJelG2O2rWorfnh+Q9XFxdsnESq16skSUywKtLseVVWQkUhwNXVdDQauxDa1mxsbDjn1st5Xdd5nh8dHXngy8vL8/Nzz0EIsVqtLi4u3nrrrfFoPOoPBr1+2zS9QT9Ser1aKaUGo5EP7ux8ZoIdjEZFXZyenu6ONwXT+mr64737v/vZTzaifoSq3xtw1TTOvQZxO4Js18+ik1iGIITo1Lxq6hg4juNgXF2XiFhUVZIkea9XW2O9qRbmJx//+L/58z+e2sI7J0kEDkwoq6rSAZmiJIreOrjdyzIqjUC0TRuRlEkaxVGqokpXVVm61gRg611rTERSC6lQIAM4F6eJqUoQxErMyvWjw2cvjo+Ktj6L7PVy3gRjDCx9U5Jti+ZsPQdBRbGSROgCBK8kiSjpp72X7bUQUmkFQM5CCBwCm9bVVatk1LazPM/bqi6rdRc3C62EEOv1mhGUUpubm0VRPHn0+MuiSKO43+87Yxfr1Qfvvnf7rTubk41Hj55GcYxVOV3OE5PkvV7/fnZnc0dXoc/6/t7tgc5TEUcgubXeeqUkA3Q+IIQAgB2IIjh0Rj8CdiE454xz3La7g2HbtmVR1E1bmkYRey2IyLdN7VyepBerqY5VFEUt2BBY9no92XpoPPuwt7sLgU1VRzKKlAZmdl7EFCUaAYKxRWsaY5BBo3BSU6wFIVtnndNJrLy1EoMSs2X5+OTw++c/NMZcbyiUwmtsvW1lQFJt2c6WCyFwvV4PspwgQOBIKimjOI5TyrIsk1JVpSmaCjy3jeVQLGbrTz75Udvati6999vb22kav3jxYt1UnkPV1F00ggzlupg2jfd+jSSlXC2WL1++vL6+3tzczPM8oljFUd7vzYuFCX57Mu6peKM32b893o+Gd8c746yXYyI9gmNEwQD4imbLzPCK8WmtVYRKKam19j4Au+CDZWZWSsVJYpmtcx4YBEVSSGvBtXf2bh1eHAukKIqM80BC7m7vmGWxWJ6tl6t4tNNWNRsrdZrHSWd8Xmd93UlsmoYAjI48B8/BewjeW+fQESoJCgvXzqp1ESykkcqjWjVCYm3axtYBuHXWGCMJvHORUr00aZvGOK+TWCIZ74b9fi/vM6OplgKFlFrKmB308v7+3u3vvnvkHSdJur21u7k1cc59//TJYrHoeqM3xpOyLJk5z3PbtF3+ePutO7PrKRLt7O4KIc4Oz0IIg+EwGff6/f7OxqZyPKKoJ9PdydY4GVKgjifsQ0CSzrcAELzvwkqSwnsvWRpnQwjWe0RUkWaExhrr/Xq9juNYRjoKwdvWI3hrGxd8sM7Zg9099bVkZqEke5RKyV6vV9tw1bbOOUkCA2il8izz1nUNAc45Tn0X0jJz14smtSIlGcGH4DkwYWkaSqNAfLVcni9mNQXuxS74ZX1tC1sURRe3LFbLslxnWeYAN4fDwWBwcXEhdIj7ubVtVdTb/R2t4rpu0YEilSb9KIqDhx998Ml6sT4+PBoM+v3+cLVab2xsvPfeB4b94eFhV3i5e/fu119/LYTopRnlva4Nf3t7u6qqh999m2RpFEWr6+VstYwH6WBzMhgN8+FANK4vk/J8ZTITlGePDohZsGMmD694tX9bSeXQNde5EELTUAdqRlrjTamgw+dICgzEwNa7xpnClBZsnmaJjpZt65VywRNJmSSJHkAURaP+QCIRIPtg6sZ1GiRl169xk3cAykjrV42AhBRCYARGCAiMUJrmajm/XM3nTTktVqu6XJiFMaZpmvFwlKdZVawt0KQ/NMbs7G4rpaaLuc4iPUhtDUksb9868J6n10stCydAS51GPSAxHI4PD59XZVOW5ebmGGQw3mmhP/zww7Zt27btWsOVUm1VL9wihLC1tQUAWuudnZ2j0xMQdPft+xd0WpnWGFNV1aoqoyiKLfhUl0VRrFaUbSQ6gdaHEEhKDjfUfiLqoNzO+LRtm2RpcM44i951fgiJhJTokAkBIQiUcaS1EsG5quS2IEIMnGXZ9bJ0hmvTMJMkojhNlVKDwaBtW5BpCKGua4WkhFRav64a3qSCCUkpA7APgaUkRO99Y0xQcH0+O5pfvZifn82ur5bzs8V0VZdGGnhVLh71+ty2iVC7m1sBw+b2VtHUST/PB7lMYwC3v7u7t7HfNIYdleuaoIl00uv1sqxHQEVRjUaTF0fPkjzN81jrqK6r0dbwnXfeefbsWcej6vf713WzWq2EELPZTCn15NnTnZ2dvb29s7Oz999//+OPP46y+Gq9KG1blqUExMp4WopFfQWJmdyOenHTFMEDCWFdK9j9rf4RBWbnnDFGatXhAjZ423h8RYWy1hpjkEUIgQUG4LKurqbX09XMiPBydg7M1lpAyYjGWVlVFaI0xhBRXdeyn8s4fo1DCSWBKDgPhJ1+ORkAwFrrpGOhGME4VzV129gfnj/9/uTFlVnPQzsvV2VdmeC9dUopZHDGBOdjrUMcJ1E8mgyzYd9cX2V5PtycNM6CkTu3b4kKFYk0Sgf5gINK4nxna3dja2swGMxniyhKxoPxZDIpioX3fjQa1XV19+7d5XI5nU4PDw8//fTTpqy67Gm1WimllsX6zp0777333l/81V+21tR13SGX6/V6tl5OSdZX86Ry7w/3zxssb63D0DeNccbHjGVdaRk6Lj/+VpsRIzjngEgIYYNv2zYwd/3ZxhhhFIFsnDPsq7Z5cXL0w4vnq7b0Gn84f7lwRdu2uepnKqPQynEve/TF181s1kfcThIoy4R0nkYoBSopIq3jSJNUXWuj5wkzMvjGCldJIQyEtWjXfXhwfvIIL0/GZsVwPF+pcTw7nW5ubqZ1uLi4gFi99eF7xhjLkUqiwf4uIy6rtjVhe7wzSAZN06SRNserM0is9bv7u9EkVN+fDPv7HN6/u/M/LmZnae9Pw+pvNg/ksD8a9Q9OTl/evi/W82xva1uSWC2WOzs74/H4+vraeEdEOon39vbyPPfer9frzz/97Pz07DJaOmuZcSBTU1TDOB3vb2Jlrg/Pbg+2r8w6X55rQcwtVHWmI2dv+APE6BtjjKHgB1HatK1QEpSIhQoqtMZUTV23zfPRet2c1pW7WM6/e/7D4fmp9Q4AmqpWSCpSIhVZf2yMUYRKpLK8nrt1xa0VXkhJaaQHaW8yGhvv4IbdKQWS8EzMENgHn6eZTFMACAiNMVVTr9GeXZw33ub9/uHLJ8vlEmva3ty6/dadL/7qiw6gL4qiAxF7vd50Or1161Zd11EUZVnW6/Xatu1cxbDXb1s7nV4nOY7HG4NslMAwsKubEgBGo9Fivi7LcjIetV6s1+vz8/V4PB4MBkmSpGnakfeJKE3T5XJZVdXu7u7e3l7HnQaAumpNVcdRNJxstnGFJqRpOp5scWVH25tRL2vA2+ClIs9QtWU8HFVVVVUrY22n+x7Yc7DeAyN6AqKAXKNZrpZFUfzX/+aPDYSgRAlu1VSOgBHKskyiWJJQUgKRQgAIHgEI5fXT43a6ji3lIs50mlKUJ3ma5ip4FoSChBAigECmwAihXM46YjoKkUYyEK7b+rSYLeryfH1VKTg5P8tGA5Xq0WjUlFVVVZ0bFEJkWTYYDHq93rNnz7a3t7vaQ+dgsizrChRRFBVFcXx8dvvuKM8GcZRt9LaEAGanlNjZ2Wlq61u/sbFRtlVrFt7D6elpBw4bYy4vL7vy8nA47BBKY0xXVW+aZrFYDCnTQveSnACNXbvGVFSR8RRrr8XSNdW6VUH0klQylrYoVnUIwYvg2K3W68PT4ydPnx6fnrz77rtREsvohu/Utu2iE7dkQexE6U3FLhn0dBKDFMF7BrQdsAdsgrfB2+Dl4ukxls2Ioo2kN1RpBIKZm6YBQYGDYNEhsZJIAaKGKIqstau6VpGmTLXsT+fXD4+eLrk5X0yv2xKlGG9MlBJVUV5dXYUQJpPJZDIBgK44Xpblcrk8Pj6Oosg5570fDoeTySSKorIsu26x6+vrrZ2MSJmWs51MSFYRA/okzuI4dazH46GqF/O1vnfv1mw2W6/X3fSzrqduf3+/aZo8z6Momk6nFxcX3QirsiwT47a3twcyXq1W5MLGeJJnWblav/3xh4PBqI3EuqzrssHVtWnaqigOzaps6sVqOV8sameEUlGeRD86+Jvz5x252hsbnEfE4HxLjdLSA4tYKZShLlZ1KZ3x3mNgidTRnUEQaQ3BsQUp53UEYpQkA5mkIlKMiGR9QABm5G4uGRMCEQkJuLW11VqDS/IEhW1P59cvL8+OppdLslYisLxz+6C1djVdzK6nxCCESJKkI2tmWTafz4+OjsqyfPHiRQghiqJ+vy+l7KpO3TMRGYmljDgoZwkRfWiEdADBGKtVHIs8ijSLvPW5wvTq6mq9Xo9Go+l02pUP33rrrUePHnVjeDogk4gmk4kQYqdUWZahEE40LUkEaI1ZNdWjw2ffhdDF3G1t6qI0xiDDmV/v7O1u3tvLxf6yKlZlUdrWepe9vae1ds77lgZpvr+9k0RxWzer0+PTqwuLPJ4MM54sirX1DhlM3ciukCVllCY6Sxxw3TZyG7NIqp5IoiAooJBKRbGKNBB1s2ckCYGCuu9IQYU0ykQStcFd18uTxfXSNtG4b9ZTkSTUhGzQX52czufzpqwG/X6SiI4kIqU8Pz9frVaz2QwAZrPZarXa3Nzc3t4WQlxeXiZJkuf5elUSYZrGcZQHF0k1BIDWrEia8WRQtotebxjLiZCYRVltek3psyzrUKCOa9TNBdzf31+v10mSDAaD0WhUVVUcxzs7O3trdXl5WdZVbc1yuXx6eOiQAVHGSWuNECLPcym1jUlkWZKmH0/ujicTmURXy3m5bkpuvQKR6P5oZFtj1m2c6Pvv3v87n34+7PVX88XF0fG//bM/+e7pE+WMTmLjPDMQYp73Jd7EUVrHWsWBnW9auaHSSGmttCKBiEBoOVhnUAoIgIiS0ZPwJANJgdT6lpREJTlWdeWvq/XKt7KX2nYeFHuLi9WSEbx1vTwXAZRSRVForfM8f/78udY6juO2bZk5TdPxeJymadetb4yZTqfIAtADQNt4RclwuCulsrbxobh9Z+fkbCnJJnoQgkuzmEhGUbS1tdWFyO+//z4Ree/btt3f37++vu71epPJZDQaNU2zXq/rul4+XxyfnrTWRkm8qstlXao0znu9xnkn0BGbtsS2CSEQ0dI2eHF01stJq3Vb1+yifj4a9qXWTVVRYA0YMSRIfakHMgJS8d23Dg6fX6+XhljFUSwyIYRgMHUjAL11znvbGhBUt816tZQxUyq1jmIg9AgtsTO1qZ3ngIjIoElEJGOptFSKhBpFjamCQ4lxCW7RlBfrhQt6ul7qUZb081VRoA9lWU529ti6yrrVarW7uzsajb755pudnZ1+vz+fz6WUe3t7BwcHXYl1MplorY+OjsbDSVUXTVPNZ6t+tpHtbkRRBNgsl9ODvcFskZm2IaKmLeNMNI1ZL2YdWlXX9f3799M0bZrmu+++Gw6Hw+Gw3+/HcZxl2Wg0WiwWZ2dnSWmdIhCqYtciZ+NBNuiTkvOraxVFRGScReQ4S4QQ3jodOApMAJQkw1iJJGqNXS0WTVUPsjzywayLo+8efd2GYd6rV8UPy+uXpydSiDiNWZDn4J1vmlYEEEjAgIyxUFpFANDoWEopp4s5ljKfDBtnbfAi1tPlfDwegw9KSpay8n5VVN57JWW9bPvDgU5iaNzcVD+cvCy9efLkWbQ1rK0pinp6eeFbM0rSqqrYOpaqW4jpdHrv3j3vfaeziLizsxPH8cnJiff+4cOHe3t73vurq6vp/CRN0+WyHPf6kR6sV2U+9M9+883Vwp+cHvXz7YP33kMK19NDBGlt03WPtm17fHx8586djqE1m806yuJkMjHG3L179/vvv2/bFiWFVK9Xq7KpZRzleVo5s1rO0jRvrdFSvP/ue1XVFEWxs7l1eXnZzqbL+UJnSTLosQvFfGm8Q8JUalvWKmA/ydi47x88tFXjjZ1HON7cICUL0yDhar5g59+9e98by9at1+uqrdD6RCgialUty9Cm455IY9CSGaUSOo6HuSIS4Ly1rnWVtdaDF1pEEVqhvj78QUW6tqb2rcwTs1jdvnfXZ2per1zptNZANyUkLaXMsm5IZxzHm5ub3WSXjkhb13XTNJ39ub6+7libbdMwWAC+vprf2pRZMpEqzIuHq+JiVRUX57MwyQC5btbWtd5zFEX37t0bjUZdU+qTJ0/29vbG47GUcjgc1nV9cnLStf0kSeKcG+wfaK3Hpr2+vl6uVy14UjIfDpSKpFHD/uBnP/lJnvb+zb/6175u/+l/+j/98pd/fnx8fHx+tpq2g8ko7+WNt3Vde+e0lJpk8LyqS+FCnqSDjbFtaw5grcMA5EELtbWx8/mPP5v0h+v54sWz5y+Pj1rvXG0a01SrUopJrvq9JriL5aywrYNQm5adX80WqY4SHXWDRUWshVDIYbqYf/3koZTy9Pw8UNh/+27R1KPN4WBnozxpOgwAiUzTEgWVpIN+v9uA4XA4Ho+bpunAgA5AXi6Xnef03hdFEcexdwawZWbThjzdVKIXxe3q/Gq2OLGuaFtprVdKXU/XQohBfzTsx9vb2zs7O2maMvP19XXbtnVd371794cffjDGdIMuHjx4kCTJzs7O6ew6TdMAXLh23dYAqLVWQl5eXu/v7H7+6ac//viTVCUvHv/wl3/25//tv/5vPvmDH0dbI3HYv57PPELLPhCCFFEcuaatnEl1lGYjwRB8WIFTUWScbduWCSEEJeTO1vb777zLrd3qDVMVmbp59OJpU67zQf/OwYF8PD12U54Vq6Pri5Z92dTL5XJjMLJlnSgtAL33ItbpsC/jyEJI0rRNpBfCJdKYJh8O8lWfiPI8z5I0ksrUlTcGvYul6uCRjsbSPegA247jtbGxUZbler0WQgyHww6MGg376/JSShqNNvb37jkrfbVcri6upycMLov3OZBzbja/HgyT0WgiKDLGzOdzRNze3u7ISCGEs7OzEEK/37+8vOyYW8PhcLFYrOpy3VTdoN60lyupIYS2bXd2dgaDwe3bt9fL1Q8njzfHk08/+fH15dXXL54hIuWZNO2yWIeqIiJiUCRNaAhR5nk6HAbvV/NFURTjtA8QSEnD3jnrgi+q8ujoaHMwGvcG+/v708X8YjE9m13XphVOy79++mBVlUFRFRwoYcDG4+yjn3324b13fNUcHb789vvvrlaLypKMgwnercosj0nKj25/vlzMVk0lpXTWVutCMGipvHWmaRMl814vHw26sc/dEJemabqIaDAYCCE6sl+XQHV745zTWvYoy/Ikgu1BbwtB13V9fnFUVjNmdM0aw3w6nc7nU6S+kpEQbV3Xy+VSCFHXdQghy7LhcPjixYvOuBFRFEVCCP+qqNL9XEkZRRECNVVVl5UEeXZ29qtf/cpUzeNvHxFDGieT0fjhk8dCCASw1iohN0aTrY3NQa93fX1d9SprLQqxLMuiKq21Iolqb1GRzBJvjfWOOZxdX/63f/5nP3r3/TxOkKFum+3d3WjQW7VV2TaySOGyqgbDiVJp651gPRlvfPTzT+9u7UUoBtvjha/mz0yBzoeqbBshosZxHORnb9/tzfp//id/aoONe9np4dGyLdqq7rr9gyCUIorjal1JKdM07VqXQghJknQltvl83jTN/fv3m6ZZLpchhC6GmWwO8zyXPnMWExVba88vTkbj3DlYXNXBLi4vLwFC2zaz2awb+pokyXA4TNM0y7IOtU/TdDabnZ2ddSWBxWKxXC6jKFrPFp1RheDLdm2tc8Z2BnBrsvH8+XPTtFvb26Ztry8uGcHERklJgKE2NrQqH35w595PPv2srutvH33/3aPvr+azDoZzHATIat1KpUSsMUk0QAwQGnM5m//Nr79QSOy844BKql4aZbnMc7nz3t0zs240NKEt20oJGWaXv/7mK/9u++5b93bv3LozvX9hi5mpWgnO6khmvjXWhlmxCsGvyiJJomDdslrPy2Vt21hHyGBsW9e14xsF7FraOmJT95iZ1+u1tbZrjFZK9fv95XJZFosoViGEfm/gPbaNX69KY6o7b+1HuvfsUVMscT6f64TiOM7Snvd+tVpVVaWU6sxIXdeLxaIsy/v37z958uTx48eTyeSjjz6qqmo2m2kgiaS7pIcZScapllKenV2YXn++nm1OJp//nZ9tbWy+ePrs4cOHojUCMJbKQSgWi6vm+HK8t9o8+Pjjj6cvzx83gWqfylgkqrHGcuBIVdZqT1kaCyEgMCqlpfJ1KwhVEgnEIMkT1KZprZXv/vhHT8+OK2/a1kOk+v3hejr/8sHXGqiXZMN+v2iq6/ns2lainzpFSqDl0LbNL778dUaiqEpEni3m/cnQO0eA/eHQGDObXnXkL611V8C7of1q3SWrzNzZnKZpXucBL1++FBSYfdM0o4ORFHq5XM3n8yhWk8l4b/duaFdHL5ar1WqkqXOqXXo1n88vLi66MVr9fj/Lsrquu/bHPM/H43Ecx3Vdv/XWW9OLuXPOtxYEKSl0pAIAIsZxXBTF7t7uP/yjf/De2+91FLRf/PpXo16/bRoRYDQYbcS5Kaqz5y9/YQPX7cnTF75sYpKOMDBEeS/OUkdwcXXpgUWkrbV1XWmmVCnwIY3TvZ3de2/fn+xuz8v1r775zdffPpC//stfkAmhqFNBgeTicoqIS3D/9vuv5hFMhqMfTl+svBlEWcx6PV0HamIhRCzbtj28njcR12atY91gIKG2h0Nmrur6zu69q6srYF00xXA4ZCFRy1VZNG3DzHkvLVZFJKiX6PXsajwYoGmcbe/u7Rjzh1Gsvv324XA8adVfmHR59OTXSg0iuvObX7+Yza8tVDJEu7s/fv/9D4nIx6SqpbRRbZt1udJajgfDw8NDLWQklfdc1e31YulevpxOp+PNDa3E+dnJaDIOHKqi7A1HwXtBYjDqEdAHH7734Ufv72/tXJxdnhwdsjVBKIseJZVknbCci1Moj188POHi9PRUCJHm+Xy5KMsy6+XOm6UMm7d22rJaXc96Oh7rzNdtFPj+2+/dv3tvf2d3MB5JKR98+1BdLnadlOfn503TdIBtAHDkGmtcWRpjvvjiizSKm6pm75WQVdM451QSddTE14alKsu2bfM877xflwFkWXZ9fb1areJeLBUxeG9dx9XuaqpSSlJSOo0kETEwB+/ZgBJisVgMBoPJZALgzy9ezmazwOb09PTq6qo1dZLEg0GfmU9PT9fr9a13b9++fXt3d5edH/UHcZwS0dbGprcu6+UHe7sd/NeYVmv98OtvttL+crlsrdFJnGZZlmXO+xv6gXGnp6e/+MUv8ihZLdZHh4cu+GK16vV6/bznjPHWpVkmpVwvlxcXF12hpmmazuVorZl5PZ3bVTnIe3ub275ui+m8FyV3Dm7/zu/8zsHBQRRF8+Xih5cvnh4dGuTtg31ZFQUzM2JbNyxICJGqFBE3Jxtt25Zl2TVltG3bDdlfz2bdbnVzLTc2Nkyv1/VidMe2I9MNen1r7WQyuZhddvcKJEkSQsDAzrm2sZHWAOA4OOeMvZnj64O7Xp8dnxzdu/eW8cba5vzqsmzKvBeToqzf64t+f9BL0xilqE3LhEqpXq/njSWGREdd8SuJ4r29vZOTk08//fT6ejabzdi46XK1mM7sfG2tnWQb23u7w9EoyXuz2awsy63NzflscX5+vpwv2DgCkUTRaDRq3Lqr4EuibgYuATBzVVVd1Ltcr7sQw3pnjHnv1lumaSKlNaNj3N3ajkienJz84le/PD4/y3r5bLk4Oj15fvyyapreoC8jpYWSANA4ywhCKqmVB16tVswcSSWURAYB0Bv0R4NhlqVFUZSv+vG89965LqTBwFEUbW9vZ1kWnAcA51xTFcEZgiAJkiSRcapVHEXR5eVlWdbrddE2vmpc5ySUUj64KFL37981pl0XCxKwv7+f9+IolsNhv5uu1dXO8jzv0Avn3GK+jJQGD8iFbdqN8STSGpiJ4eOPPhyNBi9evBiP9t+6dXD07MXu7u7+wa3RxkRKWTcGEZWUVVURke4mmCtwxldNg4hZLytWaydVbzBo66Ysy2G///bbb3eVpbOLi1VRIBEKAg9S69XJRVEUG+NJ1OslKO+/dW9zMnn58uUvfvWr7OmTtJdjrFkg5YnOtCGSACBJkBQoRHevQmCAEDbHky5iISII3AH3w37fGdNxhNq27RoigxAAcHJyAj50lE0p5fXlVUejTOMohNDWVUlCIMXDuIOGjHHMKFWEeEN31VprFVeFuHv3zlt3bx8ePru8OvXe9Qe5tU3bekTvPQf2Hfte66LXG1RF1ev1mqbRUpVl6a1rqzpPs6auD/b2v/766+Fw+PGHH52fnP7sxz8eDofVT39urZ3OZ6vVamlaY32sdLS5iYjsgrUWGaRUrAAAlJBr6QaDwWgwiKLIWxf19Ntvv/32228nSbJcLs8uLlzwBOwa75l7vd5Q5U3a+/jjj3e3d7766qtHD78tDm7Fabp7+1ZRV9frJTeCEq2yREhtvZPe2MLajngttZYkdByRlN0VPavVqivpaa2vrq689wqxgxa01q8Xjoi2trbK1Xq9Xl9dXdV1XazWUsokSQjTqmyKqqqqqisLBw8dhtMfjqIkBSBA0THOlVJ23o4nO8a0V9cXi+UMMSit1+u1kCwEOm+70mYIYbFYtG1rwNy5cyeKosFgcHV2jgzGGGtMrLS1djQYPvjmwfb29tt37h49e7H7859/9vs/vbq6+su//MvVauWBgUmnuqOTpGkaQnDGIqKpTdM0rDjppQTgvV/OF2mcvPPOOwf7+9bax48fLxaLq+k0iiKpVdu21pi6rt3lent7+73777z99ttJFE/ns42tzUD49OjQQgBJbXDFvMBKqTS2zsnRaBRCUHHUHw6iKGqMMc4ywnK99t674DvLAACeQwBG/Nu4vmtL67LNTz75ZL1YHh8fr9frxWKBDF0A2uv1ur6+jj51dXVVlHWWXXkGreM4TbSKg+fGtN4FRNzYvjcY9U4vjoxr0zxltmkWex74YJMkqqrSOae0DiHUTWPXa0+hLmrVV1GUeMcqV8G6dVEUPiRR3Ov1gjNVsXrr9p1/9a/+VT9NQOvOTwBAoqO6taZp27YdTcY//vGP33nnnTzN6rr+/rvHjx8/btvWCG+trcuqbdtYRwRwcXHx4sWLFy9eWGuTLOsN+gEYiISSVVP3gIGwauoA/N5HHxDRbLH48sHXs8W8DS7u51KJdu09A/jgrJUHBwcdfePg4MBz+P7x42+//fbi6jLNs+B9h+FY78qyvFE93/W/3FxqwsxaKSHE2dlZGsVxHHeow2gw7CJ9QW2v13PjYIxZFuVqVZRlXZYNoxiOR/3BqDcYBs9QlXXdhhAmGwMkfvbkB2PrNI3LasngSMBgOBwMBovF/OrqqqqqbjJqHMdCoXOuqWvnHBMOBqMSqanqB9988w//g7//w+Pvf/r5T+I4vjy/+PD9D548evz84nJ3d/f6+rpji5SrQkppg59eXddl1VR127bX19eHRy+vZ9M4jqu2liQ6lvVqtfriiy+klFVVbWxsVE2jtfbezxZzoVTWy4ko0/nJ7OqrR99Obu2Ox+PT4+MHDx48ePgwzlJbV93ENgkoARUI5xj/s//kn96MeCNsmqYsy6az8sDMHKcJSdk0TQAWQlhrcx11bL2OCjgejw9u3epIgM9/eHpyctIxG5ChQyCCq7oXzlfr1aqom6au26KqtY4//8nPPvzRx96Fw8PDq9m8e74tOYpUYF9VRQhOafLeVnWRpvHt2wd37twBgBcvXjx79rxjtZR1efvWQZZlw0Gv1+th4Nl0+u1vfmPa9nd//rMsTYN1G+PJYj59/N33zrm5524QEDO/ePFi/9Zt5xwRoRRN004mExXppmm0iltnV6tV1kubpiGiLEn51dHJsswYI5RSkQZC45z1zgYPAG5ZOmM2NjZ2d3dta66vrzuEKgAjIgqBgrqXtNYYY/Af/dE/hhA6O4OI3YAERBRKISIKqpp6vV6TlOPxOEkS1zTGmG7q13vvvTeZTNqmmc/ncRxfnJ5dXFwYY0IItjU3fBCNACClllJaF9q2ZaQoSj786GNmbEy7Wq6rqrHBIyKCSFQ8HPbH43Gvl/UHmdayKFfT6dVvfvNVWZZKqYODg729/bZtnzx58vz54WjU/8lPfpIkyXq1QsRBnq1Xq1/81V9eX169decgjeJhnm1vbZ2dnB69OBwMBpO79+fzuVLq+++/DyEA0GAw0FrnvZ7xPo7jANxaK6WGm3Yor6XqnIS39vWs8zRNgYgRPAfjnOcQEIjItaabg2SMCc6lcZKnGQA0VfXbHcXuZlq6x//Jf/pPO6faxfvI/FvNCGC9N8YwQpTEXaKRxrFzrgOQx+OxUqpYr5fLZdu2GG5G6mutkaFpmqZpgNi50BXHmaGs6yhKJpPNu/fuHR+fHh0dVXUTRUkHUXQMhjiOkyTK8nQw6GVZ0pp6uVw8fPiwS5e6wXREpFUcxzHxjUnc2d3WWl9fX3pjm7p88JuvJ+OhbY1AHvcG11eX15dX4/H4Z/+Dv98BId99912SJOW6Iikmk4n3PoqT7pYtFMIFZgCtNXh3wy9qmi5GIiJGSNPUem+cNc623jFhR2Irq6obxg2Bg3XB+S7TSeMEuk5sBkQkfnXPw//8f/G/bNs2OCeQkCE4102wt9Z2FKC6bXQcx3HsOVhr1W/N/+k+SRxFHd6rhewK4ogYqRvMpzKmK78kSWatbUwbR+loNKqqpiuHdb1qzjlCmSRJaxvvfWsaa1spKU40AFtr67oMISAKIgJA725GDCngDudJ83xza6PX6xHB2dnJ7Pp6Z2tzOZ+eHB4JhOB8XZZKyHsffXrr1q2qqqqiLIoiBHj27Nm9e/esd2mWL4s1SZH2e6t1WZtWSgnGSCmzJImiiODmei4dR7PZzHrfWtM46zmAoG4D8l5vsVg0dZ2mqRJyNV84Y8fDYVs37EMIgQAIsJuaGEKQp1cXxCCl1CQgcHBeIUmlBnlvMBis1+uL6ysJKBAFChXLOI27xgchRK/XAwDvnPd+sVhIvKm3bG5uvvfee++8885oNPr1g+86HzCdTq8uLkPdrlbFel123eUdo6Qsq7qupZRxpHxwUpHSqffKB8vcTXfifr9fFIUxTgiRphmhdM55z6FuQN0Qh0MIZVUpLRghzfM0z5C4aRrTNMFYwOC97+pxXZIopUx0srO59dZbb403Jq2xv3nwTWeam6bxHKx3A6Xffvvte/fu5XkuSTDzarW6ml6fnZ25EDx0V7UREyKi8942LTE4Y0tf5EnamXEUwgXPwAAcAAiAgbsDIau2EUQaGIghBLYeSSgSP//pz7Isu7q62p3vTDY3pJTPDl+cn5/fNAg69zoKMm3btu1gMMDAiNi2bdM05+fnAJDnuY4zKaXnygVwgbvLx2zTdqe+Ksqu57+XZSEE09SOLZFU3fVTvrsLDYVIO1i7aUxZlpeXl8A0HI4nk0mGZL0HgO29raKqfnj2g1CU91IX/Pn1FSEPNkaubk9Pjq4X8xAC6cuuHvD4+0cff/zxztbOZ5999uGHH8ZZ+uLw5dMXz5vFvKqqqm16g75zrt/vf/zxx5988okztutCePToUccqQ0QppBKCJQXgDuqC2rx7525ZlkdHR2xdGifd9DopZeBuaC54YAb2EAIEmfZydp5fjZPppGvCurW/f/vgYLVaTTY26rouy3K9WC5WKyml+q3hNDeEqvW6G6fb0TSPjo6Ojo6YuRUZEncMl86CZ2kejSJnjXNOIA0Gg9u3bmktT05OXrx4kYx7IQQbbHCBISAygCRi451xlgnzQb83GDIjkbTB37pze3t7e75cnl2ertfr3d1dHauLy7N+v29MQ8hJksgst64ty3I6nR4dHXXZSVmWSoidnZ3d3V2lVJe+NE2jlKpt22nPYrWqqurw8JB9qOt6tVrVdf3y5ctHTx5vbW0BESpBUrIkzyG0rXNuezT55N0P6rZxjVkul6gEpGnHdjHeeedc8ADdJS0IICQzN9a4phVIsVAaqLNNv/rVr7I0zbJsvV4fHx9fXl2dn593IGj3PYqiLr1ar1ZlWcZxbKx5PRarGwgqpRQiLcsSPSdJggBtW3dlyLapkyTZnGx8+P77H374PiL+zV/99fOnzzotAwhCEiI6Z5um6jwzkVRKA6MQYjzeuH//nTt37pjp/PLy8unTp8a3KIT1ToMaTSadqWlNa5qmn+eTzc2qqqbzWVmW3Z+9vb1dliUAfPXVV957odXl1fV0Oh1tbnhkKY1SyhgDcfLNN988efRYKdXtAREd7N9ywfOrriUm8v7mXszTwyP/o0/efutuL8vPLy+ePH92fHrCUspIews2+ODZA3d91oiI/+Af/tMurwIM3Uw3JEbkKJaDQS+KVGtqa9so0lLKwO5DsfHOO+/sH9xCko5DYLxazI7Ozn/z8IFx3oTgETyDD0FrHWdp2V0jEFgT5kmaaW2aer2Ys/Pvf/Duj3704Z07t5VST188/eqrr84uzist8zQzxpTrdXCefQCAWGsppfW+bVvPYe/W/o8/+6zX6x2fnjx9+tQ5h1J0IUPV1EkUT4Yj07TlclWvi1HeJxds3fTznm3Nnzx7cHBwsLOz043NnU6nIYSOSt0xNvxvXVpkrZ1k/S529Bwa03ZJkop0N+YHoFNY7uZvAkBSQppnw8l4tLmR9XIHvKrLdVUeHh9VdV21TXgVgHZImiTh4GZuIQAgEhMhAAbPbWvDzR2emqhr3PUf/PjHg/GIhW6Mk5EejUf97Z3Jzv6vv35gmZFkmsRCahe88c5aHwKkWZboKJh2vV4XzkVKZr38448+/PSTT7a2Nk4vzh49evTsxdPleiW1IoAQQnCuA6AEEnvfVfAFolKKnV0sFt9/+61QajabNaaVUkrmyntETKK4axsWDGmaBmPLdaGQNkajft47PT75wz/4u03TWGOdsRxCP+91+E+kdC/L8zTrKnTOuUjpPM2aoupasqy1KKiLO1prAABeXYSBiEyAAQAgGeSB8GI+fXr8snYm6/du3bm9s793dnXpQ+jgdwwcguumo+D/8B//j153Af62dNTlrku/oxmHEJqm6Vdw+/btu3fvjjc30jxrrf/h2dPvvv9+tlwFQhQUGKy1ASFOkyzLIp2FEBACefbOgPWSUCvx9/7e393d3lkVy28efP3y6Ki2NTOTEKUPSZIE5+qyQgbvPXvfjY7qmnJvlsO71lpr7ebmZhRFAW4GB+V5zszluhgNBrf3bg2yPNOxJjHI8kjp50+f/b+/+Iu7d+9+/vnniPg3f/M3T5486TgyXV7d4Vfd9LrOzZILSimSwjlXt01jTJfTRlHUOTaBHeW8GwfNpnYob34WgJlQSIlSxHHcJUbBOmtt2zShuzcE0L7qw7yx2wiEKLpQgbC7PFVxkN654DBE4vBy/uTsqiO7pXlmvFutK5QakAKzZyYptFZZmiVxwsHZurLWaiU0Cg+ham1d+0dPHj968vhyejmdT5MkyYcD46wxRgR2xkDgzc3N/d09ZL44O5/P56+nVcVxrOOoKApjTJYkSggACM5764LzDdWICCEs5wsz3th86+4Hb78rAeuy6qYMRt/8YnZ9ffj8uVJqMZux9yDEcj5nZmeMj+MsyzbGY2vtarUqiiJL89VqFYD7w8E4S8u69hziOF4ulwCAXRcr4Q2RHzEa9RtrnHMoSEoZOBRNU6/q8XBkjPHWdTfnRFJ5RkSUDK8vDcbOoCEKIqlVolQkSDGTd2wch6AQxNI2UkokFaSoA5qybZ1tTBgO+957RMiiqBtgWNf1YraU4IUQaaQR0ZrWtgaRVaQfPXkiNHlmlcQyjUvbeu+jLFEa1suVc25rY/MP/+APNicbh4eHL5+/ODk5WRbrq6urxWyW5nljWtO0QgjbGrAWEWOtmdk0TUepb+tmOp2en5xuDsfj/uB1Z12e5ycnJ7PZLE3T9XrdYenr9brf73dtwF2zQjexLU3TzdHk4uLi7OL88vIyzfM4jo2x5+fnaZoiIgIy/jvXtTkBghRIqtu2KOqOltAfDqumdsZCYCAhGAAJhUBECSw7EAaBECRhNxlacVDeESN2G3Bz1TNqSjUKQd2NJgCImMWc9wZt3cRJLKU0dXN5dt4xIbRSsRIA4I1lCFLKZNgjIu9tsAwCBGGUxiRlva5b24CWKeooitiH2fX1999/b966u72x+c69+1dXV9fX17/84tc/PHvaHREikiS898TU3cweQmh9YBdYoBKiqerDw8Mkit9/+52N8aQoih+ePb26uBRIw/4gyzII3LYtAvbzHvtAQkLgcl2U66JDBkeD4TvvvPPjzz69vLz84qsvL66u0jzrx33HgQEIEBBvPEEX4wcuVmtmvjFTSgWArnfq5qSGEAA70xpCIERJkAMDAhJJgUqgIhKEMo6yEIADdmOIiLo7bKFVLRHFSistILAxjTWGvWPwxIzeB9sSh3G/v7u7OxoPONiqqi4uLq6urlCAlAMb/Gq1mGxueHZ12/q6jrNURdqyb0wL1iVJooScz+d/9id/+s1vvv7RBx9+9tlnOzs7W1tby2I9nU7rthGI2I2Ubw0IYXwwTZum6c7Wdp6mAFCs1svl8uXLl+xDU9e9NJvP57/61a96vV5RFNfX14vFosNUutaduq4BoLvc13vflcGTJPmTP/vTt956K0rijli2WCziLNVaO+e6PejUv1t9Bt7Oh0VVlmXpOJBSOtIohSOPiIyeMTCzDcGx726MlxiG3VEiJgJBIARKRBwNt9q29d5HUdIVnbsNnJnruq7Xxdx7z8F565CDQBhkeVOt2PntzY1PP/nkgw/e72V527Yqksz8/OXhL37xi2cvni+XcxVHUqu6rbJeD6RYV4WvK5Kim3vXFkUcx2mapnFsW7Narb788svz8/Pf+73f6xoxukI0EHUJeS/LuuVwzu3v7v7kJz+5fesAAMqy/OKXv3rw4EEI4eXR0WKxCN63zoYWX08S71C2jtjatY10dI1uxkHbtkVR9Pv9w8PDvN/b2tkZb21eXFxcz2fOuW5BuhutQzdYkhkAwrzY6ffTjV3j3Wy5WFYFEyZx5ENwRIjomRnBAQcCiSiRB8AgJGVp1utlaZoKicwhyxIcMBEhsveuy4Sbtlq0192cSmCWCJEUikgQRZKLptje2PzD3//5++++54xpyvmwP2AdA4YsS+JYS0kemKQAgNbamEOnfcaYuqq11qt1MYoz55wB9Na2bRtHEQd++PDharUKIdjgPYckSXw374IZEU9PTyeTye/8zu+89957g16/Y39ubm5++umnQognT57URdMfDpqmKeqqMaYDjlarVTf6Lc/zP/zDP1RKPXz48MWLFx3G11Fau0mMUitjzMXFRZwmO/t7Wb/XlT2stUCkSLAPSBQlUZakf/fgRx9+9FGUJuu6suwfPHn017/8xeVqrpNYkfDkXfAeGJUgJs8stzbupGmaZYkQIrBBhCgWUaSKcul807Z1a6pu2L11rXPOYdWhqQQIyBAoBEbAltskEkrx9dX5Sy0jLYkomOYX334PAPP5/OjkuLGNjiOSiCx0HCNDURSIoJQapHkcx+RZRxEERkEYBDPXbQOBPfB0Me/iDQBgxO5adaCbWu7e3t6tW7e2NjallF0LwvX19cn52fnVZW3aKIllEgkIFKkYpXNOKJlkaXeYUNBsMf+jP/qj+++8fXh4+OzZs45B3FUul8tlVyGw1kKDUZr0er07d996+PDhxniyWCyqpticbBhj4jj+vT/4/f9g66PBaDhfr1B6lSXF9t7T8WRZrJ11gX0IwXNwIXQld8dB3rl9j9l3qAuzsK5ZrVatKeu6sK4ytnLOMLiucxER0RsiIoJuiP3N7bkMxrhYy6oqfv3rX371xS+6BuDgfEizEEJjjDFGRVpFmpldCN4YIYQmklJKEsF6BoPGhQjathFOCCQXAnsvpYzj2BhDUkiSjNi5OCAkKcuq7vV6e3t7XbPf1dXV+fn5fD6fzWZXV1eX11fW+0wgtQok9Saj5XpdNQ0KQVJ67wNAa+2Tp097g0EURbPZ7Pj0dLlcdt0G8/m8u2UrADCCMaYoiv5wMBgM7ty5c3l52YXFF1eXWxubP/3pT997773Zk+u6ruu28QJ1EishFQl2niUGDl22zAgBwHHwIUgdsXPB2Ma0bWvKdTFfLKZFuUiziNkiBSFQKZBSdhi2bW8uXaWbDbjxQpFUfzvfzHlNpIR0GLy3xloASLJEKNmZEQYGz+CcFjKLkrZumnXR39jc3DlYkquqqq6qSCkXvHeOETvTL5SSSgXoSHQMzNgNaglhvlw+e/bMe39+enZ2djabzaqmdt1rlVyXxaoqs14+HA61NVAWxlmtdfAcgIP3RyfHl9dXXdNydywuri47yMtbK6XsEkDjXVEU1rs8z+/evbter71U1trisrhzcHt7e7tt21SSiJSSWJbrs8Pnj54/PTo/XdVlnGd8c4KJGGSAQBRCkMdn3/b7/SSNGru8mp5cTy8BQpxKpQPDa3QIGUI3tEjKDjMNIQRGIUiQUIi4LMpVVb/OqI3zggERvbkZJBMEMgfnHTNrIZWSzlpv7DDOev0R9UYfffDh2/fu/auHv6qqysznLoQAHIC7kTBa6+6foRsRFnwA9g6yOK6a+snTHy4uLm6Y8SF4DlJKklIoCVJUTV02lSUGLZM863nXNI1QigkDAgHUdc2E3Z0Po9EokkmH0+kkNuuyblvpvY4jAHDBh7p23gNi14l2enyyubnpvf/1l1/evnXrnpxMXb0qiqOzk5enJydXF8uq0HlqILiOVtK1XzNIRkCSx+dfZ+ssSRJrbVHPgRophVSxZ+O99d4ze6KbS0+7gnvojA4AovfdHZCI/Y3N1toQApFk5rZtve3CLAOEQAjehBC8Y0EUkGywkZD9PHn3rfu39/bzON3f2c3S1HrngQMCIZCUXbWg23DPAYOH7moCgUgEhN20ImPMqlizDzf38iEmWdaY1gVPQCqNUyUch2VVxFHUG/StdwEYBXE3Y1Yr652KdAihqEqlFApyzl3Ppj2d+G5QX/Ay0jfztBFfvny5ubHRsfPm19PVavXDDz8cHh7+2ikbvLW2aOuqri0EEgIEGWtc8OFmpiogs2BARlm3F0V1M5VUax0lgtk0plZKAYLSRKSEuEH/vffWdeAddDUgZgyBkWExm6Ho5hYBIoJUIKNASMBCCET0IQChjgSBAAYl5Xtvv3t7Z+/urTtbwzH6UMyX3/z6y5OTk+V6FUIIkohQCBlFUddFBESMSIIECQHACJ07UUqlWSZJmKYNAMa5um0DgA3OhYAehVA6iQUHz1xW1XA4BMRu/rOxVgEorevVSkrJAE3bWufiOGYAYy3opOOIdJir1Cowu7b13j999uzW/v7WxiY7v1qtnHWXl5cqydu2BUKSwkn2DJ5tUzTdG3pmZBZIAoiIGFkyVjqWRBSCdcH6ILTWSRx1DMM4SrXWiKJt26KomqZpnMNuFVACISMxQGBofchjLXTUtK13Tuu4UyLFN+guANxMQEcKrd/Z3PrZ5z+5c+tAOG5X5Xq+eP7D0z//kz+dDsEa02mxs7bLeEMI0F1GxxxCQBKM4L133idRSgDW2tpUVVF2HeHdlUhxEqOSxrvW2cBIUiqtmrLpQEZrrRCiK0B1aGM3bazjHphXpexuCB+x6JTXOdca0/Xdx3H87Nmz4PyPf/Txy5cvv33wcDQaLYEtW2YOwTWmIaI4jlOtgvfAjAEFYHepKf27N86/kTfyRt7IG3kjb+SNvJE38kbeyBt5I2/kjbyRN/JG3sgbeSNv5L+38v8HCL37MhYv754AAAAASUVORK5CYII=\n", + "image/jpeg": "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", + "image/png": "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", "text/plain": [ - "" + "" ] }, - "execution_count": 5, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "Image.fromarray((inputs[0] * 255).astype(\"uint8\"))" + "array_to_image(inputs[0])" ] }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ - "from tensorflow.keras.layers import Conv2D\n", - "network = nets.SimpleNetwork(\n", + "network = SequentialNetwork(\n", " (128, 128, 3), \n", - " Conv2D(2, 3, activation=\"relu\", input_shape=(128, 128, 3), name=\"conv2d\"),\n", + " Conv2D(2, 3, activation=\"relu\", name=\"conv2d\"),\n", " \"Flatten\", \n", - " (100, \"tanh\"), \n", - " (10, \"tanh\"), \n", + " (100, \"relu\"), \n", + " (10, \"relu\"), \n", " (2, \"softmax\"),\n", " loss=\"binary_crossentropy\",\n", + " optimizer=\"adam\",\n", ")" ] }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 20, "metadata": {}, - "outputs": [], - "source": [ - "network.set_learning_rate(.1)\n", - "network.set_momentum(.1)" - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
Model: \"SequentialNetwork\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"SequentialNetwork\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ input (InputLayer)              │ (None, 128, 128, 3)    │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ conv2d (Conv2D)                 │ (None, 126, 126, 2)    │            56 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ flatten_3 (Flatten)             │ (None, 31752)          │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ hidden_3 (Dense)                │ (None, 100)            │     3,175,300 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ hidden_4 (Dense)                │ (None, 10)             │         1,010 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ output (Dense)                  │ (None, 2)              │            22 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ input (\u001b[38;5;33mInputLayer\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m128\u001b[0m, \u001b[38;5;34m128\u001b[0m, \u001b[38;5;34m3\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ conv2d (\u001b[38;5;33mConv2D\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m126\u001b[0m, \u001b[38;5;34m126\u001b[0m, \u001b[38;5;34m2\u001b[0m) │ \u001b[38;5;34m56\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ flatten_3 (\u001b[38;5;33mFlatten\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m31752\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ hidden_3 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m100\u001b[0m) │ \u001b[38;5;34m3,175,300\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ hidden_4 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m1,010\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ output (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m2\u001b[0m) │ \u001b[38;5;34m22\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 3,176,388 (12.12 MB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m3,176,388\u001b[0m (12.12 MB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 3,176,388 (12.12 MB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m3,176,388\u001b[0m (12.12 MB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "network.config[\"layers\"][\"conv2d\"][\"feature\"] = 0" + "network.summary()" ] }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ - "ds = network.input_to_dataset(inputs[0])" + "network.config[\"layers\"][\"conv2d\"][\"feature\"] = 1" ] }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "array([[0.7334817 , 0.26651827]], dtype=float32)" + "[0.5129900574684143, 0.4870099127292633]" ] }, - "execution_count": 73, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "network.predict(ds)" + "network.propagate(inputs[0])" ] }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "\n", + "\n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " Layer: output 'Dense'\n", "Act function: softmax\n", - "Act output range: (0, 1)\n", - "Shape = (None, 2)outputLayer: hidden_4 'Dense'\n", - "Act function: tanh\n", - "Act output range: (-1, 1)\n", - "Shape = (None, 10)hidden_4Layer: hidden_3 'Dense'\n", - "Act function: tanh\n", - "Act output range: (-1, 1)\n", - "Shape = (None, 100)hidden_3Layer: hidden_2 'Flatten'\n", - "Shape = (None, 31752)hidden_2Layer: conv2d 'Conv2D'\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 2)outputLayer: hidden_4 'Dense'\n", "Act function: relu\n", - "Act output range: (0, +Infinity)\n", - "Shape = (None, 126, 126, 2)conv2dLayer: input 'InputLayer'\n", - "Shape = [(None, 128, 128, 3)]inputActivations for SimpleNetwork" + "Act output range: (0.0, +Infinity)\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 10)hidden_4Layer: hidden_3 'Dense'\n", + "Act function: relu\n", + "Act output range: (0.0, +Infinity)\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 100)hidden_3Layer: flatten_3 'Flatten'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 31752)flatten_3Layer: conv2d 'Conv2D'\n", + "Act function: relu\n", + "Act output range: (0.0, +Infinity)\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 126, 126, 2)conv2d21Layer: input 'InputLayer'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 128, 128, 3)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -211,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -222,7 +314,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -231,7 +323,7 @@ "(20, 2)" ] }, - "execution_count": 80, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -242,7 +334,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -251,24 +343,23 @@ "\n", "\n", - "\n", - "\n", + "\n", " \n", - " \n", + " \n", " \n", " \n", - " 2021-04-27T19:27:38.154813\n", + " 2024-10-20T11:42:03.268446\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.3.1, https://matplotlib.org/\n", + " Matplotlib v3.8.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -277,1005 +368,1039 @@ "L 720 0 \n", "L 0 0 \n", "z\n", - "\" style=\"fill:none;\"/>\n", + "\" style=\"fill: #ffffff\"/>\n", " \n", " \n", " \n", - " \n", + "\" style=\"fill: #ffffff\"/>\n", " \n", " \n", " \n", " \n", " \n", - " \n", + "\" style=\"stroke: #000000; stroke-width: 0.8\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + "M 2253 4666 \n", + "L 3047 4666 \n", + "L 3047 1625 \n", + "L 3713 1625 \n", + "L 3713 1100 \n", + "L 3047 1100 \n", + "L 3047 0 \n", + "L 2419 0 \n", + "L 2419 1100 \n", + "L 313 1100 \n", + "L 313 1709 \n", + "L 2253 4666 \n", + "z\n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + "\" style=\"stroke: #000000; stroke-width: 0.8\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", - " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + "\" style=\"fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", + "\" style=\"fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", + " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + " \n", + " \n", - " \n", + " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -1290,453 +1415,356 @@ "L 283.320739 55.238125 \n", "Q 283.320739 57.238125 285.320739 57.238125 \n", "z\n", - "\" style=\"fill:#ffffff;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n", + "\" style=\"fill: #ffffff; opacity: 0.8; stroke: #cccccc; stroke-linejoin: miter\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + "\" style=\"fill: #ffffff\"/>\n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", + " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + "\" style=\"fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", + "\" style=\"fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -1751,54 +1779,55 @@ "L 399.363636 55.51625 \n", "Q 399.363636 57.51625 401.363636 57.51625 \n", "z\n", - "\" style=\"fill:#ffffff;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n", + "\" style=\"fill: #ffffff; opacity: 0.8; stroke: #cccccc; stroke-linejoin: miter\"/>\n", " \n", - " \n", + " \n", " \n", + "\" style=\"fill: none; stroke: #0000ff; stroke-width: 1.5; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", - " \n", + "\" transform=\"scale(0.015625)\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "\n" @@ -1815,16 +1844,16 @@ "output_type": "stream", "text": [ "Stopped because accuracy beat goal of 1.0\n", - "Epoch 107/500 \n" + "Epoch 14/500 loss: 0.09768255800008774 - tolerance_accuracy: 1.0\n" ] }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 81, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -1841,34 +1870,40 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "\n", + "\n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " Layer: output 'Dense'\n", "Act function: softmax\n", - "Act output range: (0, 1)\n", - "Shape = (None, 2)outputLayer: hidden_4 'Dense'\n", - "Act function: tanh\n", - "Act output range: (-1, 1)\n", - "Shape = (None, 10)hidden_4Layer: hidden_3 'Dense'\n", - "Act function: tanh\n", - "Act output range: (-1, 1)\n", - "Shape = (None, 100)hidden_3Layer: hidden_2 'Flatten'\n", - "Shape = (None, 31752)hidden_2Layer: conv2d 'Conv2D'\n", + "Act output range: (0.0, 1.0)\n", + "Actual minmax: (0.0, 1.0)\n", + "Shape = (None, 2)outputLayer: hidden_4 'Dense'\n", "Act function: relu\n", - "Act output range: (0, +Infinity)\n", - "Shape = (None, 126, 126, 2)conv2dLayer: input 'InputLayer'\n", - "Shape = [(None, 128, 128, 3)]inputActivations for SimpleNetwork" + "Act output range: (0.0, +Infinity)\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 10)hidden_4Layer: hidden_3 'Dense'\n", + "Act function: relu\n", + "Act output range: (0.0, +Infinity)\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 100)hidden_3Layer: flatten_3 'Flatten'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 31752)flatten_3Layer: conv2d 'Conv2D'\n", + "Act function: relu\n", + "Act output range: (0.0, +Infinity)\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 126, 126, 2)conv2d21Layer: input 'InputLayer'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 128, 128, 3)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -1879,7 +1914,7 @@ } ], "source": [ - "network.display(inputs[-1], scale=0.75)" + "network.display(inputs[0], scale=0.75)" ] }, { @@ -1909,7 +1944,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.13" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/notebooks/NeuralNetworks/BasicNeuralNets.ipynb b/notebooks/NeuralNetworks/BasicNeuralNets.ipynb index 234d222..a680239 100644 --- a/notebooks/NeuralNetworks/BasicNeuralNets.ipynb +++ b/notebooks/NeuralNetworks/BasicNeuralNets.ipynb @@ -96,31 +96,21 @@ "id": "jndZgPkeaTwN", "outputId": "54305dbe-9044-48e1-96a9-fb57b461cc3d" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m454.2/454.2 kB\u001b[0m \u001b[31m2.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.6/1.6 MB\u001b[0m \u001b[31m16.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h" - ] - } - ], + "outputs": [], "source": [ "%pip install aitk --quiet" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "id": "il-65yhPaYRZ" }, "outputs": [], "source": [ "from aitk.utils import array_to_image, get_dataset, gallery\n", - "from aitk.networks import SimpleNetwork\n", + "from aitk.networks import SequentialNetwork\n", "import numpy as np" ] }, @@ -137,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": { "id": "wqQfLfDZoHf9" }, @@ -164,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -176,12 +166,13 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAAAAABVicqIAAAAYklEQVR4nO3VMQrAIAxAURXvf+V21lBScQvvj0F8kCW97T1hclrfB+P6yx9BIBAIpDwy0xfhBIXSM1dnXRAIBAL5KN6T/H4cV2ddEAgEAoFAIBAIBAKBQCAQCAQCgUAgkLUXA4UCzaT1sj8AAAAASUVORK5CYII=\n", + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCABkAGQBAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/APn+ivt/wJ/yTzw1/wBgq1/9FLXQUUUUUUV8wftHf8lD0/8A7BUf/o2WvH6KKK+3/An/ACTzw1/2CrX/ANFLXQUUUUUUV8wftHf8lD0//sFR/wDo2WvH6KKK+3/An/JPPDX/AGCrX/0UtdBXx5408aeKrXx14ht7fxLrMMEWp3KRxx38qqiiVgAAGwABxisP/hO/GH/Q165/4MZv/iqP+E78Yf8AQ165/wCDGb/4qj/hO/GH/Q165/4MZv8A4qvov4BatqWs+Bb641TULu+nXU5EWS6maVgvlRHALEnGSTj3NeqV8wftHf8AJQ9P/wCwVH/6Nlrx+iiivt/wJ/yTzw1/2CrX/wBFLXQV8QeO/wDkofiX/sK3X/o1q5+iivp/9nH/AJJ5qH/YVk/9FRV7BXzB+0d/yUPT/wDsFR/+jZa8fooorcg8aeKrW3it7fxLrMMESBI447+VVRQMAABsAAcYqT/hO/GH/Q165/4MZv8A4qsOeea6uJbi4lkmnlcvJJIxZnYnJJJ5JJ5zUdFFfT/7OP8AyTzUP+wrJ/6Kir2CvmD9o7/koen/APYKj/8ARsteP0UUUUUUUUV9P/s4/wDJPNQ/7Csn/oqKvYK+YP2jv+Sh6f8A9gqP/wBGy14/RRRRRRRRRX0/+zj/AMk81D/sKyf+ioq9gr5g/aO/5KHp/wD2Co//AEbLXj9FFFFFFFFFfT/7OP8AyTzUP+wrJ/6Kir2CvmD9o7/koen/APYKj/8ARsteP0UUUUUUUUV9P/s4/wDJPNQ/7Csn/oqKvYK+YP2jv+Sh6f8A9gqP/wBGy14/RRRRRRRRRX0/+zj/AMk81D/sKyf+ioq9gr5g/aO/5KHp/wD2Co//AEbLXj9FFFFFFFFFfT/7OP8AyTzUP+wrJ/6Kir2CvmD9o7/koen/APYKj/8ARsteP0UUUUUUUUV9P/s4/wDJPNQ/7Csn/oqKvYK+YP2jv+Sh6f8A9gqP/wBGy14/RRRRRRRRRX0/+zj/AMk81D/sKyf+ioq9gr5g/aO/5KHp/wD2Co//AEbLXj9Ff//Z", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAAAAABVicqIAAAAwUlEQVR4Ae3VMQ7DIBTA0Kbq/a/c7nwbxNDN2TCQSC9IPK/1+a7hevysO95r+Me4j1ypxhXXlcDV4k5XXFcCV4s/x9XjCho7jtdcR3iY7UJcO50xF9cg2YW4djpjLq5BsgvzPjnfH7v34Vz/BFksxmUy2ONCFotxmQz2uJDFYlwmgz0uZLEYl8lgjwtZLMZlMtjjQhaLcZkM9riQxWJcJoM9LmSxGJfJYI8LWSzGZTLY40IWi3GZDPa4kMViXCaD/QcDhQLNQYGjsgAAAABJRU5ErkJggg==", "text/plain": [ "" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -201,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": { "id": "L1--TM14RsRP" }, @@ -228,7 +219,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -240,12 +231,13 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAAAAABVicqIAAAAP0lEQVR4nO3NQQEAAAQEMBRXXYrz2gqst/Lm4ZBIJBKJRCKRSCQSiUQikUgkEolEIpFIJBKJRCKRSCQSiSTsACTeAUd6+u1NAAAAAElFTkSuQmCC\n", + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCABkAGQBAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/AEoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooor/2Q==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAAAAABVicqIAAAAf0lEQVR4Ae3SsQ0AIAwEscDirM4MV1Dh1CdFsn6deX/7/YsZT5IyLlxJIMXWhSsJpNi6cCWBFFsXriSQYuvClQRSbF24kkCKrQtXEkixdeFKAim2LlxJIMXWhSsJpNi6cCWBFFsXriSQYuvClQRSbF24kkCKrQtXEkixdX3KdQEk3gFH5//jEAAAAABJRU5ErkJggg==", "text/plain": [ "" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -271,7 +263,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -286,7 +278,7 @@ "68719476736" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -311,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -319,22 +311,7 @@ "id": "fk8wYgrdarNm", "outputId": "4650c15c-6a04-4b65-c51c-c312b09f5110" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Downloading data from https://raw.githubusercontent.com/ArtificialIntelligenceToolkit/datasets/master/digits6x6/digits6x6.zip\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "8192it [00:00, 14997703.35it/s]\n" - ] - } - ], + "outputs": [], "source": [ "inputs, targets = get_dataset(\"digits6x6\")" ] @@ -350,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -370,7 +347,7 @@ " [0, 1, 1, 1, 1, 0]])" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -390,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -405,7 +382,7 @@ "array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0])" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -425,7 +402,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -445,7 +422,7 @@ " [1, 1, 1, 1, 1, 1]])" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -456,7 +433,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -471,7 +448,7 @@ "array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0])" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -491,7 +468,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -504,7 +481,7 @@ { "data": { "text/html": [ - "
%s
' % (border_width, label) - table += '%s' % (image_to_data(images[index]), label, label) + label = str(labels[index]).format( + **{"count": index + 1, "index": index, "row": row, "col": col} + ) + table += '
%s
' % ( + border_width, + label, + ) + table += '%s' % ( + image_to_data(images[index]), + label, + label, + ) table += "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
34
\"34\"
35
\"35\"
36
\"36\"
37
\"37\"
38
\"38\"
39
\"39\"
40
\"40\"
41
\"41\"
42
\"42\"
43
\"43\"
44
\"44\"
45
\"45\"
46
\"46\"
47
\"47\"
48
\"48\"
49
\"49\"
50
\"50\"
51
\"51\"
52
\"52\"
53
\"53\"
54
\"54\"
55
\"55\"
56
\"56\"
57
\"57\"
58
\"58\"
59
\"59\"
60
\"60\"
61
\"61\"
62
\"62\"
63
\"63\"
64
\"64\"
65
\"65\"
66
\"66\"
67
\"67\"
68
\"68\"
69
\"69\"
70
\"70\"
71
\"71\"
72
\"72\"
73
\"73\"
74
\"74\"
75
\"75\"
76
\"76\"
77
\"77\"
78
\"78\"
79
\"79\"
80
\"80\"
81
\"81\"
82
\"82\"
83
\"83\"
84
\"84\"
85
\"85\"
86
\"86\"
87
\"87\"
88
\"88\"
89
\"89\"
90
\"90\"
91
\"91\"
92
\"92\"
93
\"93\"
94
\"94\"
95
\"95\"
96
\"96\"
97
\"97\"
98
\"98\"
99
\"99\"
100
\"100\"
101
\"101\"
102
\"102\"
103
\"103\"
104
\"104\"
105
\"105\"
106
\"106\"
107
\"107\"
108
\"108\"
109
\"109\"
110
\"110\"
111
\"111\"
112
\"112\"
113
\"113\"
114
\"114\"
115
\"115\"
116
\"116\"
117
\"117\"
118
\"118\"
119
\"119\"
120
\"120\"
121
\"121\"
122
\"122\"
123
\"123\"
124
\"124\"
125
\"125\"
126
\"126\"
127
\"127\"
128
\"128\"
129
\"129\"
130
\"130\"
131
\"131\"
132
\"132\"
133
\"133\"
134
\"134\"
135
\"135\"
136
\"136\"
137
\"137\"
138
\"138\"
139
\"139\"
140
\"140\"
141
\"141\"
142
\"142\"
143
\"143\"
144
\"144\"
145
\"145\"
146
\"146\"
147
\"147\"
148
\"148\"
149
\"149\"
150
\"150\"
151
\"151\"
152
\"152\"
153
\"153\"
154
\"154\"
155
\"155\"
156
\"156\"
157
\"157\"
158
\"158\"
159
\"159\"
160
\"160\"
161
\"161\"
162
\"162\"
163
\"163\"
164
\"164\"
165
\"165\"
166
\"166\"
167
\"167\"
168
\"168\"
169
\"169\"
170
\"170\"
171
\"171\"
172
\"172\"
173
\"173\"
174
\"174\"
175
\"175\"
176
\"176\"
177
\"177\"
178
\"178\"
179
\"179\"
180
\"180\"
181
\"181\"
182
\"182\"
183
\"183\"
184
\"184\"
185
\"185\"
186
\"186\"
187
\"187\"
188
\"188\"
189
\"189\"
190
\"190\"
191
\"191\"
192
\"192\"
193
\"193\"
194
\"194\"
195
\"195\"
196
\"196\"
197
\"197\"
198
\"198\"
199
\"199\"
200
\"200\"
201
\"201\"
202
\"202\"
203
\"203\"
204
\"204\"
205
\"205\"
206
\"206\"
207
\"207\"
208
\"208\"
209
\"209\"
210
\"210\"
211
\"211\"
212
\"212\"
213
\"213\"
214
\"214\"
215
\"215\"
216
\"216\"
217
\"217\"
218
\"218\"
219
\"219\"
220
\"220\"
221
\"221\"
222
\"222\"
223
\"223\"
224
\"224\"
225
\"225\"
226
\"226\"
227
\"227\"
228
\"228\"
229
\"229\"
230
\"230\"
231
\"231\"
232
\"232\"
233
\"233\"
234
\"234\"
235
\"235\"
236
\"236\"
237
\"237\"
238
\"238\"
239
\"239\"
" + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
34
\"34\"
35
\"35\"
36
\"36\"
37
\"37\"
38
\"38\"
39
\"39\"
40
\"40\"
41
\"41\"
42
\"42\"
43
\"43\"
44
\"44\"
45
\"45\"
46
\"46\"
47
\"47\"
48
\"48\"
49
\"49\"
50
\"50\"
51
\"51\"
52
\"52\"
53
\"53\"
54
\"54\"
55
\"55\"
56
\"56\"
57
\"57\"
58
\"58\"
59
\"59\"
60
\"60\"
61
\"61\"
62
\"62\"
63
\"63\"
64
\"64\"
65
\"65\"
66
\"66\"
67
\"67\"
68
\"68\"
69
\"69\"
70
\"70\"
71
\"71\"
72
\"72\"
73
\"73\"
74
\"74\"
75
\"75\"
76
\"76\"
77
\"77\"
78
\"78\"
79
\"79\"
80
\"80\"
81
\"81\"
82
\"82\"
83
\"83\"
84
\"84\"
85
\"85\"
86
\"86\"
87
\"87\"
88
\"88\"
89
\"89\"
90
\"90\"
91
\"91\"
92
\"92\"
93
\"93\"
94
\"94\"
95
\"95\"
96
\"96\"
97
\"97\"
98
\"98\"
99
\"99\"
100
\"100\"
101
\"101\"
102
\"102\"
103
\"103\"
104
\"104\"
105
\"105\"
106
\"106\"
107
\"107\"
108
\"108\"
109
\"109\"
110
\"110\"
111
\"111\"
112
\"112\"
113
\"113\"
114
\"114\"
115
\"115\"
116
\"116\"
117
\"117\"
118
\"118\"
119
\"119\"
120
\"120\"
121
\"121\"
122
\"122\"
123
\"123\"
124
\"124\"
125
\"125\"
126
\"126\"
127
\"127\"
128
\"128\"
129
\"129\"
130
\"130\"
131
\"131\"
132
\"132\"
133
\"133\"
134
\"134\"
135
\"135\"
136
\"136\"
137
\"137\"
138
\"138\"
139
\"139\"
140
\"140\"
141
\"141\"
142
\"142\"
143
\"143\"
144
\"144\"
145
\"145\"
146
\"146\"
147
\"147\"
148
\"148\"
149
\"149\"
150
\"150\"
151
\"151\"
152
\"152\"
153
\"153\"
154
\"154\"
155
\"155\"
156
\"156\"
157
\"157\"
158
\"158\"
159
\"159\"
160
\"160\"
161
\"161\"
162
\"162\"
163
\"163\"
164
\"164\"
165
\"165\"
166
\"166\"
167
\"167\"
168
\"168\"
169
\"169\"
170
\"170\"
171
\"171\"
172
\"172\"
173
\"173\"
174
\"174\"
175
\"175\"
176
\"176\"
177
\"177\"
178
\"178\"
179
\"179\"
180
\"180\"
181
\"181\"
182
\"182\"
183
\"183\"
184
\"184\"
185
\"185\"
186
\"186\"
187
\"187\"
188
\"188\"
189
\"189\"
190
\"190\"
191
\"191\"
192
\"192\"
193
\"193\"
194
\"194\"
195
\"195\"
196
\"196\"
197
\"197\"
198
\"198\"
199
\"199\"
200
\"200\"
201
\"201\"
202
\"202\"
203
\"203\"
204
\"204\"
205
\"205\"
206
\"206\"
207
\"207\"
208
\"208\"
209
\"209\"
210
\"210\"
211
\"211\"
212
\"212\"
213
\"213\"
214
\"214\"
215
\"215\"
216
\"216\"
217
\"217\"
218
\"218\"
219
\"219\"
220
\"220\"
221
\"221\"
222
\"222\"
223
\"223\"
224
\"224\"
225
\"225\"
226
\"226\"
227
\"227\"
228
\"228\"
229
\"229\"
230
\"230\"
231
\"231\"
232
\"232\"
233
\"233\"
234
\"234\"
235
\"235\"
236
\"236\"
237
\"237\"
238
\"238\"
239
\"239\"
" ], "text/plain": [ "" @@ -517,7 +494,7 @@ "source": [ "images = [array_to_image(inputs[i]) for i in range(len(inputs))]\n", "bigger = [image.resize((36,36), resample=0) for image in images]\n", - "gallery(bigger)" + "gallery(bigger, gallery_shape=(10, None))" ] }, { @@ -537,7 +514,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -545,29 +522,14 @@ "id": "qM8BYwXiWpCJ", "outputId": "3bd58799-663a-4349-eaf6-c2883f50de0a" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Downloading data from https://raw.githubusercontent.com/ArtificialIntelligenceToolkit/datasets/master/validate_6x6/validate_6x6.data\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "8192it [00:00, 13727422.44it/s]\n" - ] - } - ], + "outputs": [], "source": [ "test_inputs, test_targets = get_dataset(\"validate_6x6\")" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -616,14 +578,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": { "id": "teYgZ4_md74G" }, "outputs": [], "source": [ "def build_network(hidden_layer_size):\n", - " return SimpleNetwork(\n", + " return SequentialNetwork(\n", " (6,6),\n", " \"Flatten\",\n", " hidden_layer_size,\n", @@ -644,7 +606,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": { "id": "KxQLML_ZevzQ" }, @@ -670,7 +632,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -680,27 +642,89 @@ }, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model: \"SimpleNetwork\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input (InputLayer) [(None, 6, 6)] 0 \n", - " \n", - " flatten (Flatten) (None, 36) 0 \n", - " \n", - " hidden_2 (Dense) (None, 10) 370 \n", - " \n", - " output (Dense) (None, 10) 110 \n", - " \n", - "=================================================================\n", - "Total params: 480 (1.88 KB)\n", - "Trainable params: 480 (1.88 KB)\n", - "Non-trainable params: 0 (0.00 Byte)\n", - "_________________________________________________________________\n" - ] + "data": { + "text/html": [ + "
Model: \"SequentialNetwork\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"SequentialNetwork\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ input (InputLayer)              │ (None, 6, 6)           │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ flatten (Flatten)               │ (None, 36)             │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ hidden_2 (Dense)                │ (None, 10)             │           370 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ output (Dense)                  │ (None, 10)             │           110 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ input (\u001b[38;5;33mInputLayer\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m6\u001b[0m, \u001b[38;5;34m6\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ flatten (\u001b[38;5;33mFlatten\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m36\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ hidden_2 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m370\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ output (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m110\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 480 (1.88 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m480\u001b[0m (1.88 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 480 (1.88 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m480\u001b[0m (1.88 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -720,7 +744,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -740,19 +764,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIIAAAsLCw8PDxISEhUVFRkZGR8fHycnJzs7OywAAAAACgABAAAIDgAFBABg4MCAAAUICAgIADs=\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -763,7 +787,7 @@ } ], "source": [ - "net.display(inputs[0])" + "net.display(my_digit)" ] }, { @@ -783,7 +807,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -804,11 +828,11 @@ " \n", " \n", " \n", - " 2024-06-27T19:28:10.202516\n", + " 2024-10-20T11:49:21.287464\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.7.1, https://matplotlib.org/\n", + " Matplotlib v3.8.1, https://matplotlib.org/\n", " \n", " \n", " \n", @@ -839,12 +863,12 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -880,7 +904,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -921,7 +945,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -957,7 +981,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1004,7 +1028,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1060,7 +1084,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1208,12 +1232,12 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1238,12 +1262,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1254,12 +1278,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1270,12 +1294,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1286,12 +1310,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1352,677 +1376,778 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", @@ -2260,58 +2455,6 @@ "L -19 4666 \n", "z\n", "\" transform=\"scale(0.015625)\"/>\n", - " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -2395,11 +2542,26 @@ " \n", + "\" style=\"fill: none; stroke: #ff0000; stroke-width: 1.5; stroke-linecap: square\"/>\n", " \n", " \n", - " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", " \n", " \n", @@ -2459,7 +2606,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2472,7 +2619,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2487,7 +2634,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2502,7 +2649,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2517,7 +2664,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2532,7 +2679,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2560,7 +2707,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2575,7 +2722,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2590,7 +2737,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2605,7 +2752,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2620,7 +2767,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2635,7 +2782,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2667,28 +2814,6 @@ "L 1831 4666 \n", "z\n", "\" transform=\"scale(0.015625)\"/>\n", - " \n", " \n", " \n", " \n", + "L 443.514481 216.28 \n", + "L 443.745291 216.28 \n", + "L 443.9761 217.82 \n", + "L 444.206909 216.28 \n", + "L 447.899859 216.28 \n", + "L 448.130668 215.510001 \n", + "L 448.361477 216.28 \n", + "L 448.823096 216.28 \n", + "L 449.053905 215.510001 \n", + "L 449.284714 215.510001 \n", + "L 449.515524 214.74 \n", + "L 449.746333 214.74 \n", + "L 449.977142 215.510001 \n", + "L 450.207952 214.74 \n", + "L 450.438761 214.74 \n", + "L 450.66957 215.510001 \n", + "L 450.90038 214.74 \n", + "L 451.131189 215.510001 \n", + "L 451.361998 215.510001 \n", + "L 451.823617 213.970001 \n", + "L 452.285236 213.970001 \n", + "L 452.516045 213.199999 \n", + "L 452.746854 213.199999 \n", + "L 452.977664 211.66 \n", + "L 454.593329 211.66 \n", + "L 454.824138 210.119999 \n", + "L 455.054948 211.66 \n", + "L 455.285757 211.66 \n", + "L 455.516566 210.119999 \n", + "L 455.978185 205.500001 \n", + "L 456.439803 205.500001 \n", + "L 456.670613 204.730001 \n", + "L 456.901422 204.730001 \n", + "L 457.132231 197.03 \n", + "L 457.363041 196.260001 \n", + "L 457.59385 197.03 \n", + "L 457.824659 192.409999 \n", + "L 458.055469 197.03 \n", + "L 458.286278 190.870001 \n", + "L 458.747897 190.870001 \n", + "L 458.978706 189.330002 \n", + "L 459.209515 188.559997 \n", + "L 459.671134 188.559997 \n", + "L 459.901943 187.789998 \n", + "L 460.363562 187.789998 \n", + "L 460.594371 187.019999 \n", + "L 460.825181 187.019999 \n", + "L 461.05599 183.940002 \n", + "L 462.671655 183.940002 \n", + "L 462.902464 183.170003 \n", + "L 463.133274 183.940002 \n", + "L 463.364083 181.629999 \n", + "L 465.210558 181.629999 \n", + "L 465.441367 180.859999 \n", + "L 466.133795 180.859999 \n", + "L 466.364604 180.09 \n", + "L 466.595414 180.09 \n", + "L 466.826223 178.550001 \n", + "L 467.057032 177.780002 \n", + "L 467.287842 177.780002 \n", + "L 467.74946 176.239998 \n", + "L 468.672697 176.239998 \n", + "L 468.903507 175.469999 \n", + "L 470.288363 175.469999 \n", + "L 470.519172 174.7 \n", + "L 470.980791 174.7 \n", + "L 471.2116 173.93 \n", + "L 472.134837 173.93 \n", + "L 472.365647 173.160001 \n", + "L 472.596456 171.620003 \n", + "L 473.288884 169.309999 \n", + "L 473.519693 169.309999 \n", + "L 473.750503 168.54 \n", + "L 474.904549 168.54 \n", + "L 475.135358 164.689998 \n", + "L 475.366168 164.689998 \n", + "L 475.596977 163.919999 \n", + "L 475.827786 163.919999 \n", + "L 476.058596 163.15 \n", + "L 476.289405 163.919999 \n", + "L 476.751024 163.919999 \n", + "L 476.981833 163.15 \n", + "L 477.212642 163.919999 \n", + "L 477.443452 163.919999 \n", + "L 477.674261 163.15 \n", + "L 478.13588 163.15 \n", + "L 478.366689 163.919999 \n", + "L 478.828308 162.38 \n", + "L 479.289926 158.529999 \n", + "L 479.751545 156.99 \n", + "L 479.982354 156.99 \n", + "L 480.213164 156.220001 \n", + "L 480.443973 153.909997 \n", + "L 480.674782 149.290002 \n", + "L 480.905592 148.520003 \n", + "L 481.136401 149.290002 \n", + "L 481.36721 148.520003 \n", + "L 481.59802 148.520003 \n", + "L 481.828829 146.979999 \n", + "L 482.059638 143.900001 \n", + "L 483.675303 143.900001 \n", + "L 483.906113 142.360003 \n", + "L 484.136922 142.360003 \n", + "L 484.367731 143.900001 \n", + "L 484.82935 142.360003 \n", + "L 485.752587 142.360003 \n", + "L 485.983397 139.279995 \n", + "L 486.445015 137.739996 \n", + "L 486.675825 138.509996 \n", + "L 486.906634 137.739996 \n", + "L 487.137443 138.509996 \n", + "L 487.368253 137.739996 \n", + "L 487.599062 138.509996 \n", + "L 487.829871 136.969997 \n", + "L 488.060681 136.969997 \n", + "L 488.29149 135.429999 \n", + "L 488.522299 134.659999 \n", + "L 488.753108 136.199998 \n", + "L 488.983918 133.120001 \n", + "L 489.214727 133.89 \n", + "L 489.445536 132.350001 \n", + "L 489.676346 131.580002 \n", + "L 490.137964 131.580002 \n", + "L 490.368774 130.810003 \n", + "L 490.830392 130.810003 \n", + "L 491.061202 130.040004 \n", + "L 491.52282 130.040004 \n", + "L 491.75363 129.270004 \n", + "L 491.984439 129.270004 \n", + "L 492.215248 130.040004 \n", + "L 492.907676 130.040004 \n", + "L 493.138486 129.270004 \n", + "L 493.369295 130.040004 \n", + "L 493.600104 130.040004 \n", + "L 493.830914 129.270004 \n", + "L 494.754151 129.270004 \n", + "L 494.98496 128.500005 \n", + "L 495.446579 128.500005 \n", + "L 496.139007 126.189996 \n", + "L 497.293053 126.189996 \n", + "L 497.523863 125.419997 \n", + "L 497.754672 123.109999 \n", + "L 498.4471 123.109999 \n", + "L 498.677909 122.34 \n", + "L 499.139528 122.34 \n", + "L 499.370337 121.570001 \n", + "L 499.601147 121.570001 \n", + "L 499.831956 120.800001 \n", + "L 500.062765 121.570001 \n", + "L 500.293575 120.800001 \n", + "L 500.986003 120.800001 \n", + "L 501.216812 120.030002 \n", + "L 501.67843 120.030002 \n", + "L 501.90924 118.490004 \n", + "L 502.140049 120.030002 \n", + "L 502.370858 120.030002 \n", + "L 502.601668 116.950005 \n", + "L 502.832477 119.260003 \n", + "L 503.063286 119.260003 \n", + "L 503.294096 118.490004 \n", + "L 503.524905 118.490004 \n", + "L 503.755714 116.950005 \n", + "L 503.986524 116.950005 \n", + "L 504.217333 116.179995 \n", + "L 504.909761 116.179995 \n", + "L 505.14057 114.639996 \n", + "L 505.37138 113.869997 \n", + "L 505.602189 114.639996 \n", + "L 505.832998 113.869997 \n", + "L 506.756236 113.869997 \n", + "L 506.987045 114.639996 \n", + "L 507.217854 113.869997 \n", + "L 507.448664 113.869997 \n", + "L 507.679473 113.099998 \n", + "L 507.910282 113.099998 \n", + "L 508.141092 112.329999 \n", + "L 508.371901 112.329999 \n", + "L 508.60271 110.79 \n", + "L 508.833519 111.559999 \n", + "L 509.064329 110.79 \n", + "L 509.987566 110.79 \n", + "L 510.449185 109.250001 \n", + "L 510.679994 109.250001 \n", + "L 510.910803 110.79 \n", + "L 511.141613 110.020001 \n", + "L 511.372422 110.020001 \n", + "L 511.603231 109.250001 \n", + "L 512.295659 109.250001 \n", + "L 512.757278 107.710003 \n", + "L 512.988087 109.250001 \n", + "L 513.218897 109.250001 \n", + "L 513.911325 106.940004 \n", + "L 514.603753 106.940004 \n", + "L 514.834562 108.480002 \n", + "L 515.29618 106.940004 \n", + "L 516.219418 106.940004 \n", + "L 516.450227 106.170004 \n", + "L 516.681036 106.940004 \n", + "L 516.911846 105.400005 \n", + "L 517.142655 106.170004 \n", + "L 517.373464 106.170004 \n", + "L 517.835083 104.629995 \n", + "L 520.604795 104.629995 \n", + "L 520.835604 103.859996 \n", + "L 521.066414 104.629995 \n", + "L 521.758841 104.629995 \n", + "L 521.989651 99.24 \n", + "L 522.22046 102.319997 \n", + "L 522.451269 102.319997 \n", + "L 522.682079 103.859996 \n", + "L 523.143697 102.319997 \n", + "L 523.605316 102.319997 \n", + "L 523.836125 99.24 \n", + "L 524.066935 94.620004 \n", + "L 524.297744 102.319997 \n", + "L 524.528553 102.319997 \n", + "L 524.759363 100.779999 \n", + "L 524.990172 94.620004 \n", + "L 525.6826 94.620004 \n", + "L 526.144219 102.319997 \n", + "L 526.375028 94.620004 \n", + "L 527.990693 94.620004 \n", + "L 528.221502 99.24 \n", + "L 528.452312 95.390004 \n", + "L 528.683121 94.620004 \n", + "L 530.760405 94.620004 \n", + "L 530.991214 93.850005 \n", + "L 531.222024 91.539996 \n", + "L 531.452833 93.850005 \n", + "L 531.683642 90.769997 \n", + "L 531.914452 91.539996 \n", + "L 532.60688 91.539996 \n", + "L 532.837689 90.769997 \n", + "L 533.530117 90.769997 \n", + "L 533.760926 89.999998 \n", + "L 533.991736 89.999998 \n", + "L 534.222545 89.229999 \n", + "L 534.453354 90.769997 \n", + "L 535.145782 88.459999 \n", + "L 535.376591 88.459999 \n", + "L 535.607401 87.69 \n", + "L 535.83821 87.69 \n", + "L 536.069019 86.920001 \n", + "L 536.299829 87.69 \n", + "L 536.761447 87.69 \n", + "L 536.992257 86.920001 \n", + "L 537.223066 85.380002 \n", + "L 537.684685 86.920001 \n", + "L 538.377113 84.610003 \n", + "L 538.838731 86.150001 \n", + "L 539.069541 86.150001 \n", + "L 539.30035 84.610003 \n", + "L 539.531159 84.610003 \n", + "L 539.761969 85.380002 \n", + "L 539.992778 84.610003 \n", + "L 540.223587 84.610003 \n", + "L 540.454397 85.380002 \n", + "L 540.685206 85.380002 \n", + "L 540.916015 84.610003 \n", + "L 542.070062 84.610003 \n", + "L 542.300871 83.840004 \n", + "L 542.53168 83.840004 \n", + "L 542.76249 83.070004 \n", + "L 544.839774 83.070004 \n", + "L 545.070583 82.300005 \n", + "L 545.301392 82.300005 \n", + "L 545.532202 81.529995 \n", + "L 545.763011 82.300005 \n", + "L 545.99382 81.529995 \n", + "L 546.22463 82.300005 \n", + "L 546.686248 80.759996 \n", + "L 547.147867 80.759996 \n", + "L 547.840295 83.070004 \n", + "L 548.071104 81.529995 \n", + "L 548.301913 80.759996 \n", + "L 548.532723 80.759996 \n", + "L 548.763532 79.989996 \n", + "L 548.994341 79.989996 \n", + "L 549.45596 81.529995 \n", + "L 549.917579 79.989996 \n", + "L 553.148909 79.989996 \n", + "L 553.379719 78.449998 \n", + "L 553.610528 79.989996 \n", + "L 555.226193 79.989996 \n", + "L 555.457002 79.219997 \n", + "L 556.38024 79.219997 \n", + "L 556.611049 78.449998 \n", + "L 556.841858 79.989996 \n", + "L 557.072668 79.219997 \n", + "L 557.303477 79.989996 \n", + "L 557.534286 78.449998 \n", + "L 559.149952 78.449998 \n", + "L 559.380761 79.219997 \n", + "L 559.61157 78.449998 \n", + "L 561.227235 78.449998 \n", + "L 561.458045 77.679999 \n", + "L 561.688854 77.679999 \n", + "L 561.919663 78.449998 \n", + "L 562.150473 78.449998 \n", + "L 562.381282 77.679999 \n", + "L 562.612091 78.449998 \n", + "L 562.842901 77.679999 \n", + "L 564.689375 77.679999 \n", + "L 564.920185 76.909999 \n", + "L 565.150994 77.679999 \n", + "L 568.382324 77.679999 \n", + "L 568.613134 76.14 \n", + "L 568.843943 77.679999 \n", + "L 569.305562 77.679999 \n", + "L 569.536371 76.14 \n", + "L 569.99799 77.679999 \n", + "L 570.228799 76.909999 \n", + "L 570.459608 76.909999 \n", + "L 570.690418 77.679999 \n", + "L 570.921227 74.600001 \n", + "L 571.152036 74.600001 \n", + "L 571.382846 75.370001 \n", + "L 571.613655 74.600001 \n", + "L 571.844464 74.600001 \n", + "L 572.075274 76.14 \n", + "L 572.306083 75.370001 \n", + "L 572.536892 73.830002 \n", + "L 572.767702 73.830002 \n", + "L 572.998511 74.600001 \n", + "L 573.22932 73.830002 \n", + "L 574.152558 73.830002 \n", + "L 574.383367 72.290004 \n", + "L 574.844985 72.290004 \n", + "L 575.075795 73.060003 \n", + "L 575.537413 73.060003 \n", + "L 575.768223 72.290004 \n", + "L 576.69146 72.290004 \n", + "L 576.922269 71.520004 \n", + "L 577.153079 71.520004 \n", + "L 577.383888 70.750005 \n", + "L 577.614697 72.290004 \n", + "L 577.845507 69.979995 \n", + "L 578.076316 69.979995 \n", + "L 578.537935 71.520004 \n", + "L 578.768744 69.979995 \n", + "L 578.999553 70.750005 \n", + "L 579.230363 69.979995 \n", + "L 579.922791 69.979995 \n", + "L 580.1536 68.439996 \n", + "L 580.384409 69.979995 \n", + "L 580.615219 69.209996 \n", + "L 580.846028 69.979995 \n", + "L 581.076837 69.979995 \n", + "L 581.307646 70.750005 \n", + "L 581.538456 68.439996 \n", + "L 581.769265 69.209996 \n", + "L 582.461693 69.209996 \n", + "L 582.692502 69.979995 \n", + "L 582.923312 69.209996 \n", + "L 583.61574 69.209996 \n", + "L 583.846549 69.979995 \n", + "L 584.077358 69.209996 \n", + "L 584.538977 69.209996 \n", + "L 584.769786 68.439996 \n", + "L 585.000596 68.439996 \n", + "L 585.231405 69.209996 \n", + "L 585.462214 69.209996 \n", + "L 585.693024 68.439996 \n", + "L 585.923833 69.209996 \n", + "L 586.385452 69.209996 \n", + "L 586.616261 68.439996 \n", + "L 586.84707 69.209996 \n", + "L 587.07788 68.439996 \n", + "L 587.308689 69.209996 \n", + "L 587.539498 68.439996 \n", + "L 587.770307 68.439996 \n", + "L 588.001117 69.209996 \n", + "L 588.231926 68.439996 \n", + "L 589.616782 68.439996 \n", + "L 589.847591 69.209996 \n", + "L 590.078401 69.209996 \n", + "L 590.30921 68.439996 \n", + "L 593.540541 68.439996 \n", + "L 593.77135 69.209996 \n", + "L 594.002159 68.439996 \n", + "L 595.387015 68.439996 \n", + "L 595.617824 67.669997 \n", + "L 595.848634 68.439996 \n", + "L 598.156727 68.439996 \n", + "L 598.618346 66.899998 \n", + "L 598.849155 67.669997 \n", + "L 599.310774 67.669997 \n", + "L 599.541583 66.899998 \n", + "L 599.772392 66.899998 \n", + "L 600.003202 68.439996 \n", + "L 600.234011 66.899998 \n", + "L 600.69563 66.899998 \n", + "L 600.926439 68.439996 \n", + "L 601.157248 68.439996 \n", + "L 601.388057 66.899998 \n", + "L 622.391706 66.899998 \n", + "L 622.622515 66.129999 \n", + "L 623.314943 66.129999 \n", + "L 623.545752 66.899998 \n", + "L 623.776562 66.129999 \n", + "L 624.007371 66.899998 \n", + "L 624.23818 66.899998 \n", + "L 624.46899 66.129999 \n", + "L 624.699799 64.59 \n", + "L 625.161418 66.129999 \n", + "L 625.392227 66.129999 \n", + "L 625.623036 64.59 \n", + "L 625.853846 64.59 \n", + "L 626.084655 63.820001 \n", + "L 626.315464 64.59 \n", + "L 626.546274 66.129999 \n", + "L 626.777083 66.129999 \n", + "L 627.007892 64.59 \n", + "L 627.469511 66.129999 \n", + "L 628.161939 66.129999 \n", + "L 628.392748 63.820001 \n", + "L 628.623557 65.359999 \n", + "L 628.854367 66.129999 \n", + "L 629.085176 64.59 \n", + "L 629.315985 63.820001 \n", + "L 629.777604 63.820001 \n", + "L 630.239223 65.359999 \n", + "L 630.470032 63.820001 \n", + "L 630.700841 63.820001 \n", + "L 630.931651 66.129999 \n", + "L 631.16246 64.59 \n", + "L 631.393269 63.820001 \n", + "L 631.854888 63.820001 \n", + "L 632.085697 65.359999 \n", + "L 632.316507 63.820001 \n", + "L 634.6246 63.820001 \n", + "L 634.855409 65.359999 \n", + "L 635.086218 63.820001 \n", + "L 636.471074 63.820001 \n", + "L 636.471074 63.820001 \n", + "\" clip-path=\"url(#p3f403f150e)\" style=\"fill: none; stroke: #0000ff; stroke-width: 1.5; stroke-linecap: square\"/>\n", " \n", " \n", " \n", + "\" clip-path=\"url(#p3f403f150e)\" style=\"fill: none; stroke: #00bfbf; stroke-width: 1.5; stroke-linecap: square\"/>\n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -3339,10 +3471,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3359,16 +3491,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "Epoch 1000/1000 loss: 0.002076978562399745 - tolerance_accuracy: 0.9666666388511658 - val_loss: 0.02979998290538788 - val_tolerance_accuracy: 0.6000000238418579\n" + "Epoch 1000/1000 loss: 0.0023138849064707756 - tolerance_accuracy: 0.9416666626930237 - val_loss: 0.040301691740751266 - val_tolerance_accuracy: 0.6000000238418579\n" ] }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 21, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -3398,7 +3530,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": { "id": "w4bVfKh7ZNoz" }, @@ -3415,7 +3547,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3435,19 +3567,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIIAAAAAAAEBAQICAggICGNjY4uLiwAAAAAAACwAAAAACgABAAAIDQABCCQgYACAAAIKBAQAOw==\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", + "Actual minmax: (0.0, +Infinity)\n", "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -3460,7 +3592,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.0, 0.0, 0.0, 0.29, 0.01, 0.01, 0.0, 0.01, 0.02, 0.65\n" + "0.0, 0.0, 0.0, 0.39, 0.01, 0.03, 0.0, 0.01, 0.01, 0.55\n" ] } ], @@ -3485,7 +3617,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "metadata": { "id": "hoonJ4gWiYXn" }, @@ -3499,7 +3631,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3519,19 +3651,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIIAAAAAAAEBAQcHBwkJCRISEikpKa6urgAAACwAAAAACgABAAAIDgAHABBgAEAAAAAIFAgIADs=\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", + "Actual minmax: (0.0, +Infinity)\n", "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -3544,7 +3676,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.06, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0, 0.0, 0.45, 0.48\n" + "0.04, 0.0, 0.03, 0.69, 0.0, 0.01, 0.0, 0.0, 0.07, 0.16\n" ] } ], @@ -3565,7 +3697,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3585,19 +3717,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIMAAAAAAAQEBAYGBggICAoKChoaGioqKi0tLWRkZAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAACgABAAAIDgAJIBhwoAABAQYABAgIADs=\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", + "Actual minmax: (0.0, +Infinity)\n", "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -3610,7 +3742,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.06, 0.38, 0.03, 0.06, 0.05, 0.01, 0.01, 0.38, 0.0, 0.01\n" + "0.04, 0.39, 0.03, 0.18, 0.1, 0.04, 0.03, 0.17, 0.0, 0.02\n" ] } ], @@ -3635,7 +3767,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "metadata": { "id": "qN3gIVuCoUA5" }, @@ -3653,7 +3785,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3673,19 +3805,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIEAAAAAAAEBAQwMDO7u7iwAAAAACgABAAAIDAABCAQwAEAAgQICAgA7\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", + "Actual minmax: (0.0, +Infinity)\n", "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -3698,7 +3830,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.0, 0.0, 0.0, 0.0, 0.98, 0.0, 0.01, 0.0, 0.0, 0.0\n" + "0.0, 0.0, 0.0, 0.0, 0.93, 0.0, 0.01, 0.0, 0.0, 0.05\n" ] } ], @@ -3730,7 +3862,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "metadata": { "id": "oIVYN7qbCzaj" }, @@ -3741,7 +3873,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "metadata": { "id": "k7FKqroNZl8l" }, @@ -3767,7 +3899,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3779,12 +3911,13 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAAAAABVicqIAAAAe0lEQVR4nO3ZQQqAMAwEQOv//1zvhhKiYiHMnhMHclnUMY9qRnnjLG88CAQCgUDaI7GB0hYLK/eNMNDnXBAIBAJZZLyvjzARBvqcCwKBQCA7kfwlKC+t9Jl9zgWBQCCQncgXX+7S9DkXBAKBQBapl0OMP0EQCAQC+QW5ABUfCsKeZOCqAAAAAElFTkSuQmCC\n", + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCABkAGQBAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/APn+iiivt/wJ/wAk88Nf9gq1/wDRS10FfIHxt/5K9rv/AG7/APpPHXn9fT/7OP8AyTzUP+wrJ/6Kir2CiiivgCiiivt/wJ/yTzw1/wBgq1/9FLXQV8gfG3/kr2u/9u//AKTx15/X0/8As4/8k81D/sKyf+ioq9gooorn/wDhBPB//QqaH/4Lof8A4mj/AIQTwf8A9Cpof/guh/8Aia+PPGkENr468Q29vFHDBFqdykccahVRRKwAAHAAHGKw6+3/AAJ/yTzw1/2CrX/0UtdBWPfeE/Dep3kl5f8Ah/Sru6kxvmnso5HbAAGWIycAAfhVf/hBPB//AEKmh/8Aguh/+JrU03SdN0a3a30vT7SxgZy7R2sKxKWwBkhQBnAAz7CrlFFFFFfEHjv/AJKH4l/7Ct1/6Naufr7f8Cf8k88Nf9gq1/8ARS10FFFFFFFFFFfEHjv/AJKH4l/7Ct1/6Naufrcg8aeKrW3it7fxLrMMESBI447+VVRQMAABsAAcYqT/AITvxh/0Neuf+DGb/wCKr6n+EF/ean8LdGvL+7nu7qTz9808hkdsTyAZY8nAAH4V3FFFfOnx98S69o3jqxt9L1vUrGBtMjdo7W6eJS3myjJCkDOABn2FeV/8J34w/wChr1z/AMGM3/xVH/Cd+MP+hr1z/wAGM3/xVfb9FfEHjv8A5KH4l/7Ct1/6Naufoor6/wDgl/ySHQv+3j/0okr0CiivmD9o7/koen/9gqP/ANGy14/RX3/RXD3/AMIPAmp6jc395oXmXV1K80z/AGucbnYkscB8DJJ6VX/4Ul8PP+he/wDJ24/+OV8seLLG30zxlrlhZx+Xa2uoXEMKbidqLIwUZPJwAOtY9dhonxS8ZeHNHg0nSdZ+z2MG7y4vssL7dzFjyyEnkk8mtD/hdvxD/wChh/8AJK3/APjdH/C7fiH/ANDD/wCSVv8A/G6P+F2/EP8A6GH/AMkrf/43Xp/w40TTvi34euNf8cW/9q6nb3bWUU+9oNsKojhdsRVT80jnJGeevArsP+FJfDz/AKF7/wAnbj/45R/wpL4ef9C9/wCTtx/8cr0CiiiviDx3/wAlD8S/9hW6/wDRrVz9FFFFfT/7OP8AyTzUP+wrJ/6Kir2CivmD/ho7xh/0DdD/AO/E3/x2j/ho7xh/0DdD/wC/E3/x2j/ho7xh/wBA3Q/+/E3/AMdo/wCGjvGH/QN0P/vxN/8AHa8r1bUptZ1m+1S4WNZ724kuJFjBChnYsQMknGT6mqde8fDv4KeG/F3gTTdcv73VY7q683ekEsYQbZXQYBjJ6KO9dP8A8M4+D/8AoJa5/wB/4f8A41XjnxZ8Fab4D8VWul6XPdzQS2SXDNdOrMGLuuBtVRjCDt61wdd54K+LOveA9Gm0vS7TTZoJbhrhmuo3ZgxVVwNrqMYQdvWuk/4aO8Yf9A3Q/wDvxN/8do/4aO8Yf9A3Q/8AvxN/8drx+iiiiivr/wCCX/JIdC/7eP8A0okr0CvmD9o7/koen/8AYKj/APRsteP0UUV9P/8ADOPg/wD6CWuf9/4f/jVH/DOPg/8A6CWuf9/4f/jVfOniXTYdG8Vavpdu0jQWV7NbxtIQWKo5UE4AGcD0FZdfRfhr4BeFdZ8K6RqlxqGsrPe2UNxIsc0QUM6BiBmMnGT6mtT/AIZx8H/9BLXP+/8AD/8AGq5DW/iPrHwk1ifwPoFtY3OmaZt8mW/R3mbzFErbijKp+aRgMKOAOvWs/wD4aO8Yf9A3Q/8AvxN/8drp/Dfhuz+O+nSeKPFEk9nfWsp09I9MYRxmNQJASJA53ZlbnOMAceux/wAM4+D/APoJa5/3/h/+NUf8M4+D/wDoJa5/3/h/+NUf8M4+D/8AoJa5/wB/4f8A41R/wzj4P/6CWuf9/wCH/wCNV7BRXxB47/5KH4l/7Ct1/wCjWrn6+3/An/JPPDX/AGCrX/0UtdBXyB8bf+Sva7/27/8ApPHXn9fT/wCzj/yTzUP+wrJ/6Kir2CiiiiiviDx3/wAlD8S/9hW6/wDRrVz9fb/gT/knnhr/ALBVr/6KWugr5A+Nv/JXtd/7d/8A0njrz+vp/wDZx/5J5qH/AGFZP/RUVewUUUV//9k=", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAAAAABVicqIAAAA6klEQVR4Ae3XQQ7CMBBD0QZx/yuD6GpkSx55QVefVZMxDbxEGuVc2+ejgaMTltDASyf+MWaRShUuuCqBKszpgqsSqMLHOo72JAtU77/DHOHKDC64KoEqzOmCqxKowu81rf3lWhuMfYMjvCrPAFxTY32GayWaAbimxvoM10o0A49w+SVo/oTfs7UgDdjYutoj/4RFbCPSBFxJx2pwGUmagCvpWA0uI0kTfUvym9b6DvYkbYHV4DKSNAFX0rEaXEaSJuBKOlbzS5B2B7vSaMAbjK7CnqhIHMMVebQIl4rEMVyRR4twqUgcP8L1BQaQC8NeWuBoAAAAAElFTkSuQmCC", "text/plain": [ "" ] }, - "execution_count": 31, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -3805,7 +3938,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3825,19 +3958,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIEAAAAAAAEBAQICAvf39ywAAAAACgABAAAIDAAHAAAgYCDBAAACAgA7\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Shape = (None, 10)hidden_2Layer: flatten 'Flatten'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 36)flattenLayer: input 'InputLayer'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -3850,7 +3983,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.01, 0.0, 0.0, 0.0, 0.02, 0.0, 0.96, 0.0, 0.01, 0.0\n" + "0.97, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0, 0.01, 0.0, 0.0\n" ] } ], @@ -3878,7 +4011,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "metadata": { "id": "WS0lLtdtanY-" }, @@ -3899,7 +4032,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 41, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3912,7 +4045,7 @@ { "data": { "text/html": [ - "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
34
\"34\"
35
\"35\"
36
\"36\"
37
\"37\"
38
\"38\"
39
\"39\"
40
\"40\"
41
\"41\"
42
\"42\"
43
\"43\"
44
\"44\"
45
\"45\"
46
\"46\"
47
\"47\"
48
\"48\"
49
\"49\"
50
\"50\"
51
\"51\"
52
\"52\"
53
\"53\"
54
\"54\"
55
\"55\"
56
\"56\"
57
\"57\"
58
\"58\"
59
\"59\"
60
\"60\"
61
\"61\"
62
\"62\"
63
\"63\"
64
\"64\"
65
\"65\"
66
\"66\"
67
\"67\"
68
\"68\"
69
\"69\"
70
\"70\"
71
\"71\"
72
\"72\"
73
\"73\"
74
\"74\"
75
\"75\"
76
\"76\"
77
\"77\"
78
\"78\"
79
\"79\"
80
\"80\"
81
\"81\"
82
\"82\"
83
\"83\"
84
\"84\"
85
\"85\"
86
\"86\"
87
\"87\"
88
\"88\"
89
\"89\"
90
\"90\"
91
\"91\"
92
\"92\"
93
\"93\"
94
\"94\"
95
\"95\"
96
\"96\"
97
\"97\"
98
\"98\"
99
\"99\"
100
\"100\"
101
\"101\"
102
\"102\"
103
\"103\"
104
\"104\"
105
\"105\"
106
\"106\"
107
\"107\"
108
\"108\"
109
\"109\"
110
\"110\"
111
\"111\"
112
\"112\"
113
\"113\"
114
\"114\"
115
\"115\"
116
\"116\"
117
\"117\"
118
\"118\"
119
\"119\"
120
\"120\"
121
\"121\"
122
\"122\"
123
\"123\"
124
\"124\"
125
\"125\"
126
\"126\"
127
\"127\"
128
\"128\"
129
\"129\"
130
\"130\"
131
\"131\"
132
\"132\"
133
\"133\"
134
\"134\"
135
\"135\"
136
\"136\"
137
\"137\"
138
\"138\"
139
\"139\"
140
\"140\"
141
\"141\"
142
\"142\"
143
\"143\"
144
\"144\"
145
\"145\"
146
\"146\"
147
\"147\"
148
\"148\"
149
\"149\"
150
\"150\"
151
\"151\"
152
\"152\"
153
\"153\"
154
\"154\"
155
\"155\"
156
\"156\"
157
\"157\"
158
\"158\"
159
\"159\"
160
\"160\"
161
\"161\"
162
\"162\"
163
\"163\"
164
\"164\"
165
\"165\"
166
\"166\"
167
\"167\"
168
\"168\"
169
\"169\"
170
\"170\"
171
\"171\"
172
\"172\"
173
\"173\"
174
\"174\"
175
\"175\"
176
\"176\"
177
\"177\"
178
\"178\"
179
\"179\"
180
\"180\"
181
\"181\"
182
\"182\"
183
\"183\"
184
\"184\"
185
\"185\"
186
\"186\"
187
\"187\"
188
\"188\"
189
\"189\"
190
\"190\"
191
\"191\"
192
\"192\"
193
\"193\"
194
\"194\"
195
\"195\"
196
\"196\"
197
\"197\"
198
\"198\"
199
\"199\"
200
\"200\"
201
\"201\"
202
\"202\"
203
\"203\"
204
\"204\"
205
\"205\"
206
\"206\"
207
\"207\"
208
\"208\"
209
\"209\"
210
\"210\"
211
\"211\"
212
\"212\"
213
\"213\"
214
\"214\"
215
\"215\"
216
\"216\"
217
\"217\"
218
\"218\"
219
\"219\"
220
\"220\"
221
\"221\"
222
\"222\"
223
\"223\"
224
\"224\"
225
\"225\"
226
\"226\"
227
\"227\"
228
\"228\"
229
\"229\"
230
\"230\"
231
\"231\"
232
\"232\"
233
\"233\"
234
\"234\"
235
\"235\"
236
\"236\"
237
\"237\"
238
\"238\"
239
\"239\"
" + "
0
\"0\"
1
\"1\"
2
\"2\"
3
\"3\"
4
\"4\"
5
\"5\"
6
\"6\"
7
\"7\"
8
\"8\"
9
\"9\"
10
\"10\"
11
\"11\"
12
\"12\"
13
\"13\"
14
\"14\"
15
\"15\"
16
\"16\"
17
\"17\"
18
\"18\"
19
\"19\"
20
\"20\"
21
\"21\"
22
\"22\"
23
\"23\"
24
\"24\"
25
\"25\"
26
\"26\"
27
\"27\"
28
\"28\"
29
\"29\"
30
\"30\"
31
\"31\"
32
\"32\"
33
\"33\"
34
\"34\"
35
\"35\"
36
\"36\"
37
\"37\"
38
\"38\"
39
\"39\"
40
\"40\"
41
\"41\"
42
\"42\"
43
\"43\"
44
\"44\"
45
\"45\"
46
\"46\"
47
\"47\"
48
\"48\"
49
\"49\"
50
\"50\"
51
\"51\"
52
\"52\"
53
\"53\"
54
\"54\"
55
\"55\"
56
\"56\"
57
\"57\"
58
\"58\"
59
\"59\"
60
\"60\"
61
\"61\"
62
\"62\"
63
\"63\"
64
\"64\"
65
\"65\"
66
\"66\"
67
\"67\"
68
\"68\"
69
\"69\"
70
\"70\"
71
\"71\"
72
\"72\"
73
\"73\"
74
\"74\"
75
\"75\"
76
\"76\"
77
\"77\"
78
\"78\"
79
\"79\"
80
\"80\"
81
\"81\"
82
\"82\"
83
\"83\"
84
\"84\"
85
\"85\"
86
\"86\"
87
\"87\"
88
\"88\"
89
\"89\"
90
\"90\"
91
\"91\"
92
\"92\"
93
\"93\"
94
\"94\"
95
\"95\"
96
\"96\"
97
\"97\"
98
\"98\"
99
\"99\"
100
\"100\"
101
\"101\"
102
\"102\"
103
\"103\"
104
\"104\"
105
\"105\"
106
\"106\"
107
\"107\"
108
\"108\"
109
\"109\"
110
\"110\"
111
\"111\"
112
\"112\"
113
\"113\"
114
\"114\"
115
\"115\"
116
\"116\"
117
\"117\"
118
\"118\"
119
\"119\"
120
\"120\"
121
\"121\"
122
\"122\"
123
\"123\"
124
\"124\"
125
\"125\"
126
\"126\"
127
\"127\"
128
\"128\"
129
\"129\"
130
\"130\"
131
\"131\"
132
\"132\"
133
\"133\"
134
\"134\"
135
\"135\"
136
\"136\"
137
\"137\"
138
\"138\"
139
\"139\"
140
\"140\"
141
\"141\"
142
\"142\"
143
\"143\"
144
\"144\"
145
\"145\"
146
\"146\"
147
\"147\"
148
\"148\"
149
\"149\"
150
\"150\"
151
\"151\"
152
\"152\"
153
\"153\"
154
\"154\"
155
\"155\"
156
\"156\"
157
\"157\"
158
\"158\"
159
\"159\"
160
\"160\"
161
\"161\"
162
\"162\"
163
\"163\"
164
\"164\"
165
\"165\"
166
\"166\"
167
\"167\"
168
\"168\"
169
\"169\"
170
\"170\"
171
\"171\"
172
\"172\"
173
\"173\"
174
\"174\"
175
\"175\"
176
\"176\"
177
\"177\"
178
\"178\"
179
\"179\"
180
\"180\"
181
\"181\"
182
\"182\"
183
\"183\"
184
\"184\"
185
\"185\"
186
\"186\"
187
\"187\"
188
\"188\"
189
\"189\"
190
\"190\"
191
\"191\"
192
\"192\"
193
\"193\"
194
\"194\"
195
\"195\"
196
\"196\"
197
\"197\"
198
\"198\"
199
\"199\"
200
\"200\"
201
\"201\"
202
\"202\"
203
\"203\"
204
\"204\"
205
\"205\"
206
\"206\"
207
\"207\"
208
\"208\"
209
\"209\"
210
\"210\"
211
\"211\"
212
\"212\"
213
\"213\"
214
\"214\"
215
\"215\"
216
\"216\"
217
\"217\"
218
\"218\"
219
\"219\"
220
\"220\"
221
\"221\"
222
\"222\"
223
\"223\"
224
\"224\"
225
\"225\"
226
\"226\"
227
\"227\"
228
\"228\"
229
\"229\"
230
\"230\"
231
\"231\"
232
\"232\"
233
\"233\"
234
\"234\"
235
\"235\"
236
\"236\"
237
\"237\"
238
\"238\"
239
\"239\"
" ], "text/plain": [ "" @@ -3925,7 +4058,7 @@ "source": [ "images = [array_to_image(inputs[i]) for i in range(len(inputs))]\n", "bigger = [image.resize((36,36), resample=0) for image in images]\n", - "gallery(bigger)" + "gallery(bigger, gallery_shape=(10, None))" ] }, { @@ -3941,7 +4074,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "metadata": { "id": "_k4uKVTTburh" }, @@ -3952,7 +4085,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -3962,36 +4095,98 @@ }, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model: \"SimpleNetwork\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input (InputLayer) [(None, 6, 6)] 0 \n", - " \n", - " flatten_1 (Flatten) (None, 36) 0 \n", - " \n", - " hidden_2 (Dense) (None, 10) 370 \n", - " \n", - " output (Dense) (None, 10) 110 \n", - " \n", - "=================================================================\n", - "Total params: 480 (1.88 KB)\n", - "Trainable params: 480 (1.88 KB)\n", - "Non-trainable params: 0 (0.00 Byte)\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "net2.summary()" - ] - }, - { + "data": { + "text/html": [ + "
Model: \"SequentialNetwork\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"SequentialNetwork\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ input (InputLayer)              │ (None, 6, 6)           │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ flatten_1 (Flatten)             │ (None, 36)             │             0 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ hidden_2 (Dense)                │ (None, 10)             │           370 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ output (Dense)                  │ (None, 10)             │           110 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ input (\u001b[38;5;33mInputLayer\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m6\u001b[0m, \u001b[38;5;34m6\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ flatten_1 (\u001b[38;5;33mFlatten\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m36\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ hidden_2 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m370\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ output (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m110\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 480 (1.88 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m480\u001b[0m (1.88 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 480 (1.88 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m480\u001b[0m (1.88 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "net2.summary()" + ] + }, + { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -4012,11 +4207,11 @@ " \n", " \n", " \n", - " 2024-06-27T19:29:42.383101\n", + " 2024-10-20T11:50:40.070139\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.7.1, https://matplotlib.org/\n", + " Matplotlib v3.8.1, https://matplotlib.org/\n", " \n", " \n", " \n", @@ -4047,12 +4242,12 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4088,7 +4283,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4129,7 +4324,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4165,7 +4360,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4212,7 +4407,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4268,7 +4463,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4416,12 +4611,12 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4446,12 +4641,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4462,12 +4657,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4478,12 +4673,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4494,12 +4689,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4560,548 +4755,795 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", " \n", - " \n", + " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -5474,11 +5938,26 @@ " \n", + "\" style=\"fill: none; stroke: #ff0000; stroke-width: 1.5; stroke-linecap: square\"/>\n", " \n", " \n", - " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", " \n", " \n", @@ -5538,7 +6002,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5551,7 +6015,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5566,7 +6030,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5581,7 +6045,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5596,7 +6060,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5611,7 +6075,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5639,7 +6103,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5654,7 +6118,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5669,7 +6133,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5684,7 +6148,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5699,7 +6163,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5714,7 +6178,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5746,28 +6210,6 @@ "L 1831 4666 \n", "z\n", "\" transform=\"scale(0.015625)\"/>\n", - " \n", " \n", " \n", " \n", + "L 571.382846 67.669997 \n", + "L 587.539498 67.669997 \n", + "L 588.001117 66.129999 \n", + "L 588.231926 67.669997 \n", + "L 589.155163 67.669997 \n", + "L 589.385973 66.129999 \n", + "L 589.616782 66.899998 \n", + "L 589.847591 66.899998 \n", + "L 590.078401 67.669997 \n", + "L 590.30921 66.129999 \n", + "L 590.770829 66.129999 \n", + "L 591.001638 65.359999 \n", + "L 591.232447 66.129999 \n", + "L 591.463257 65.359999 \n", + "L 591.694066 65.359999 \n", + "L 591.924875 66.129999 \n", + "L 592.155685 65.359999 \n", + "L 594.002159 65.359999 \n", + "L 594.232969 66.129999 \n", + "L 594.463778 65.359999 \n", + "L 597.695108 65.359999 \n", + "L 597.925918 64.59 \n", + "L 598.156727 65.359999 \n", + "L 598.387536 65.359999 \n", + "L 598.618346 63.820001 \n", + "L 598.849155 63.820001 \n", + "L 599.310774 65.359999 \n", + "L 599.541583 63.820001 \n", + "L 599.772392 63.820001 \n", + "L 600.003202 65.359999 \n", + "L 600.234011 64.59 \n", + "L 600.46482 64.59 \n", + "L 600.69563 63.820001 \n", + "L 601.849676 63.820001 \n", + "L 602.080485 64.59 \n", + "L 602.311295 63.820001 \n", + "L 615.698235 63.820001 \n", + "L 616.159854 62.280002 \n", + "L 616.390663 63.820001 \n", + "L 617.083091 63.820001 \n", + "L 617.54471 62.280002 \n", + "L 632.316507 62.280002 \n", + "L 632.778125 60.740004 \n", + "L 633.008935 62.280002 \n", + "L 633.239744 62.280002 \n", + "L 633.470553 60.740004 \n", + "L 633.701363 59.970004 \n", + "L 634.162981 59.970004 \n", + "L 634.39379 61.510003 \n", + "L 634.6246 62.280002 \n", + "L 635.086218 62.280002 \n", + "L 635.317028 60.740004 \n", + "L 635.547837 60.740004 \n", + "L 635.778646 59.970004 \n", + "L 636.009456 59.970004 \n", + "L 636.240265 59.200005 \n", + "L 636.471074 59.200005 \n", + "L 636.471074 59.200005 \n", + "\" clip-path=\"url(#p1e00e9049d)\" style=\"fill: none; stroke: #0000ff; stroke-width: 1.5; stroke-linecap: square\"/>\n", " \n", " \n", " \n", + "L 467.057032 237.84 \n", + "L 467.287842 219.36 \n", + "L 473.288884 219.36 \n", + "L 473.519693 200.879999 \n", + "L 473.750503 219.36 \n", + "L 473.981312 200.879999 \n", + "L 558.688333 200.879999 \n", + "L 558.919142 182.399998 \n", + "L 559.149952 182.399998 \n", + "L 559.380761 200.879999 \n", + "L 559.84238 200.879999 \n", + "L 560.073189 182.399998 \n", + "L 576.69146 182.399998 \n", + "L 576.922269 163.919999 \n", + "L 577.153079 182.399998 \n", + "L 578.537935 182.399998 \n", + "L 578.768744 163.919999 \n", + "L 578.999553 182.399998 \n", + "L 579.461172 182.399998 \n", + "L 579.691981 163.919999 \n", + "L 579.922791 163.919999 \n", + "L 580.1536 182.399998 \n", + "L 580.384409 163.919999 \n", + "L 581.538456 163.919999 \n", + "L 581.769265 182.399998 \n", + "L 582.000074 163.919999 \n", + "L 583.38493 163.919999 \n", + "L 583.61574 182.399998 \n", + "L 583.846549 163.919999 \n", + "L 589.616782 163.919999 \n", + "L 589.847591 145.44 \n", + "L 590.078401 163.919999 \n", + "L 591.001638 163.919999 \n", + "L 591.232447 145.44 \n", + "L 591.694066 145.44 \n", + "L 591.924875 163.919999 \n", + "L 592.155685 145.44 \n", + "L 592.386494 145.44 \n", + "L 592.617303 163.919999 \n", + "L 592.848113 145.44 \n", + "L 636.471074 145.44 \n", + "L 636.471074 145.44 \n", + "\" clip-path=\"url(#p1e00e9049d)\" style=\"fill: none; stroke: #00bfbf; stroke-width: 1.5; stroke-linecap: square\"/>\n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -6390,10 +6866,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6410,16 +6886,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "Epoch 1000/1000 loss: 0.0026288372464478016 - tolerance_accuracy: 0.949999988079071 - val_loss: 0.03410140797495842 - val_tolerance_accuracy: 0.6000000238418579\n" + "Epoch 1000/1000 loss: 0.003038567490875721 - tolerance_accuracy: 0.9666666388511658 - val_loss: 0.0379040353000164 - val_tolerance_accuracy: 0.5\n" ] }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 37, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -6447,7 +6923,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -6467,19 +6943,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIIAAAAAAAoKCjMzM0ZGRnp6egAAAAAAAAAAACwAAAAACgABAAAIDQAFEAAAYACAAAQBBAQAOw==\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)hidden_2Layer: flatten_1 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 36)flatten_1Layer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Shape = (None, 10)hidden_2Layer: flatten_1 'Flatten'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 36)flatten_1Layer: input 'InputLayer'\n", + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -6492,7 +6968,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.01, 0.0, 0.0, 0.0, 0.01, 0.0, 0.85, 0.0, 0.12, 0.0\n" + "0.2, 0.48, 0.0, 0.0, 0.28, 0.0, 0.04, 0.0, 0.0, 0.0\n" ] } ], @@ -6544,7 +7020,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -6564,19 +7040,19 @@ " \n", " \n", " \n", - " Layer: output 'Dense'\n", + " </defs><rect x=\"99.0\" y=\"24\" width=\"202\" height=\"52\" style=\"fill:none;stroke:black;stroke-width:2\"/><image id=\"keras-network_output\" class=\"keras-network\" x=\"100.0\" y=\"25\" height=\"50\" width=\"200\" preserveAspectRatio=\"none\" image-rendering=\"optimizeSpeed\" xlink:href=\"data:image/gif;base64,R0lGODdhCgABAIIAAAAAAAEBAQUFBXFxcYODgwAAAAAAAAAAACwAAAAACgABAAAIDQAHCAAAgACAAAQBBAQAOw==\"><title>Layer: output 'Dense'\n", "Act function: softmax\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", - "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", + "Shape = (None, 10)outputLayer: hidden_2 'Dense'\n", "Act function: sigmoid\n", "Act output range: (0.0, 1.0)\n", "Actual minmax: (0.0, 1.0)\n", "Shape = (None, 10)hidden_2Layer: flatten_1 'Flatten'\n", - "Actual minmax: (0.0, 1.0)\n", + "Actual minmax: (0.0, +Infinity)\n", "Shape = (None, 36)flatten_1Layer: input 'InputLayer'\n", - "Actual minmax: (0.0, 1.0)\n", - "Shape = [(None, 6, 6)]inputActivations for SimpleNetwork" + "Actual minmax: (0.0, +Infinity)\n", + "Shape = (None, 6, 6)inputActivations for SequentialNetwork" ], "text/plain": [ "" @@ -6589,7 +7065,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.7, 0.0, 0.0, 0.0, 0.28, 0.0, 0.0, 0.0, 0.0, 0.01\n" + "0.45, 0.02, 0.0, 0.0, 0.52, 0.0, 0.01, 0.0, 0.0, 0.0\n" ] } ], @@ -6654,7 +7130,14 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.13" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } } }, "nbformat": 4, From 6d7c562802cea36e7214384fc2b66489210b93a6 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Sun, 20 Oct 2024 11:56:52 -0400 Subject: [PATCH 37/37] Version 3.0.0 --- aitk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aitk/_version.py b/aitk/_version.py index 316fa60..9e2806c 100644 --- a/aitk/_version.py +++ b/aitk/_version.py @@ -9,4 +9,4 @@ # ************************************************************** version_info = (3, 0, 0) -__version__ = ".".join(map(str, version_info)) + "b2" +__version__ = ".".join(map(str, version_info))