Source code for dragon.search_space.dragon_variables

import numpy as np
from dragon.search_space.zellij_variables import Variable, DynamicBlock
from dragon.search_space.cells import AdjMatrix, Node, fill_adj_matrix
    
[docs] class HpVar(Variable): """HpVar(Variable) The class `HpVar` defines :ref:`var` which represent a node operation. The operation can be a `Constant` or a `CatVar`, where the values inherit from the `Brick` class. If the operation is represented by a `Constant`, the multiple operations should share the same hyperparameters. Parameters ---------- label : str Name of the variable. operation : `Constant` or `CatVar` One or several candidate operations encoded as `Brick` variable. If operation is a `CatVar`, the multiple operations should share the same hyperparameters. hyperparameters : dict Dictionary of hyperparameters which inherit from `Variables` (for example `IntVar` for a number of channels or `FloatVar` for a dropout rate). Examples ---------- >>> from dragon.search_space.bricks import MLP >>> from dragon.search_space.zellij_variables import Constant, IntVar >>> from dragon.search_space.dragon_variables import HpVar >>> mlp = Constant("Mlp operation", MLP) >>> hp = {"out_channels": IntVar("out_channels", 1, 10)} >>> mlp_var = HpVar("MLP var", mlp, hyperparameters=hp) >>> mlp_var.random() [<class 'dragon.search_space.bricks.basics.MLP'>, {'out_channels': 9}] >>> from dragon.search_space.bricks import LayerNorm1d, BatchNorm1d >>> from dragon.search_space.zellij_variables import CatVar >>> norm = CatVar("1d norm layers", features=[LayerNorm1d, BatchNorm1d]) >>> norm_var = HpVar("Norm var", norm, hyperparameters={}) >>> norm_var.random() [<class 'dragon.search_space.bricks.normalization.BatchNorm1d'>, {}] """ def __init__(self, label, operation, hyperparameters, **kwargs): super().__init__(label, **kwargs) for h in hyperparameters: assert isinstance(hyperparameters[h], Variable), f"The hyperparameters should be instances of Variable but got {h} instead." self.name = operation self.label = label self.hyperparameters = hyperparameters
[docs] def random(self, size = 1): """random(size=1) Create random operation. First, if the operation is a `CatVar`, an operation is randomly selected among the different possibilities. Then, one random value per hyperparameter is drawn. Parameters ---------- size : int, default=1 Number of draws. Returns ------- matrices: list or `AdjMatrix` List containing the randomly created operations, or a single operation if size=1. """ if size == 1: if isinstance(self.name, Variable): name = self.name.random() else: name = self.name hp = {} for h in self.hyperparameters: hp[h] = self.hyperparameters[h].random() return [name, hp] else: res = [] for _ in range(size): if isinstance(self.name, Variable): name = self.name.random() else: name = self.name hp = {} for h in self.hyperparameters: hp[h] = h.random(1) res.append([name, hp])
[docs] def isconstant(self): """isconstant() Returns ------- out: False """ isconstant = self.name.isconstant() for h in self.hyperparameters: if not self.hyperparameters[h].isconstant(): isconstant = False return isconstant
[docs] class NodeVariable(Variable): """NodeVariable(Variable) The class `NodeVariable` defines :ref:`var` which represent DAGs nodes by creating objects from the `Node` class. Parameters ---------- label : str Name of the variable. operations : `DynamicBlock` `DynamicBlock` containing :ref:`var` corresponding to the candidate operations. init_complexity : int Maximum number of nodes that the randomly created DAGs should have. Examples ---------- >>> from dragon.search_space.dragon_variables import NodeVariable, HpVar >>> from dragon.search_space.bricks import MLP >>> from dragon.search_space.zellij_variables import Constant, IntVar, CatVar >>> from dragon.search_space.bricks_variables import activation_var >>> combiner = CatVar("Combiner", features = ['add', 'mul']) >>> operation = HpVar("Operation", Constant("Mlp operation", MLP), hyperparameters={"out_channels": IntVar("out_channels", 1, 10)}) >>> node = NodeVariable(label="Node variable", ... combiner=combiner, ... operation=operation, ... activation_function=activation_var("Activation")) >>> node.random() (combiner) mul -- (name) <class 'dragon.search_space.bricks.basics.MLP'> -- (hp) {'out_channels': 2} -- (activation) SiLU() -- """ def __init__(self, label, combiner, operation, activation_function, **kwargs): super().__init__(label, **kwargs) assert isinstance(combiner, Variable), f"The combiner should be of type Variable but got {combiner} instead." assert isinstance(operation, Variable), f"The operation should be of type Variable but got {operation} instead." assert isinstance(activation_function, Variable), f"The activation function should be of type Variable but got {activation_function} instead." self.combiner = combiner self.operation = operation self.activation_function = activation_function
[docs] def random(self, size=1): """random(size=1) Create random nodes. The combiner, the operation and the activation function are sequentally randomly selected. Parameters ---------- size : int, default=1 Number of draws. Returns ------- matrices: list or `Node` List containing the randomly created nodes, or a single node if size=1. """ if size == 1: c = self.combiner.random() op = self.operation.random() name, hp = op[0], op[1] f = self.activation_function.random() return Node(c, name, hp, f) else: res = [] for _ in range(size): c = self.combiner.random() op = self.operation.random() name, hp = op[0], op[1] f = self.activation_function.random() res.append(Node(c, name, hp, f)) return res
[docs] def isconstant(self): """isconstant() Returns ------- out: False Return False, a dynamic block cannot be constant. (It is a binary) """ return self.combiner.isconstant() and self.operation.isconstant() and self.activation_function.isconstant()
def __repr__(self): return f"Combiner: {self.combiner.__repr__()} - Operation: {self.operation.__repr__()} - Act. Function: {self.activation_function.__repr__()}"
[docs] class EvoDagVariable(Variable): """EvoDagVariable(Variable) The class `EvoDagVariable` defines :ref:`var` which represent Directed Acyclic Graph by creating objects from the `AdjMatrix` class. The candidate operations should be gathered within a `DynamicBlock`. The maximum size of this `DynamicBlock` will set the graph maximum number of nodes. Parameters ---------- label : str Name of the variable. operations : `DynamicBlock` `DynamicBlock` containing :ref:`var` corresponding to the candidate operations. init_complexity : int Maximum number of nodes that the randomly created DAGs should have. Examples ---------- >>> from dragon.search_space.dragon_variables import HpVar, NodeVariable, EvoDagVariable >>> from dragon.search_space.bricks import MLP, MaxPooling1D, AVGPooling1D >>> from dragon.search_space.zellij_variables import Constant, IntVar, CatVar, DynamicBlock >>> from dragon.search_space.bricks_variables import activation_var >>> mlp = HpVar("Operation", Constant("MLP operation", MLP), hyperparameters={"out_channels": IntVar("out_channels", 1, 10)}) >>> pooling = HpVar("Operation", CatVar("Pooling operation", [MaxPooling1D, AVGPooling1D]), hyperparameters={"pool_size": IntVar("pool_size", 1, 5)}) >>> candidates = NodeVariable(label = "Candidates", ... combiner=CatVar("Combiner", features=['add', 'concat']), ... operation=CatVar("Candidates", [mlp, pooling]), ... activation_function=activation_var("Activation")) >>> operations = DynamicBlock("Operations", candidates, repeat=5) >>> dag = EvoDagVariable(label="DAG", operations=operations) >>> dag.random() NODES: [ (combiner) add -- (name) <class 'dragon.search_space.bricks.basics.Identity'> -- (hp) {} -- (activation) Identity() -- , (combiner) add -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 2} -- (activation) Sigmoid() -- , (combiner) concat -- (name) <class 'dragon.search_space.bricks.pooling.MaxPooling1D'> -- (hp) {'pool_size': 3} -- (activation) ELU(alpha=1.0) -- , (combiner) add -- (name) <class 'dragon.search_space.bricks.pooling.AVGPooling1D'> -- (hp) {'pool_size': 4} -- (activation) ReLU() -- ] | MATRIX:[[0, 1, 1, 1], [0, 0, 1, 1], [0, 0, 0, 1], [0, 0, 0, 0]] """ def __init__(self, label, operations, init_complexity=None, **kwargs): assert isinstance(operations, DynamicBlock), f""" Operations must inherit from `DynamicBlock`, got {operations} """ self.operations = operations self.max_size = operations.repeat self.complexity = init_complexity super(EvoDagVariable, self).__init__(label, **kwargs)
[docs] def random(self, size=1): """random(size=1) Create random DAGs. First, a list of random nodes is creating, with a size lower than the :code: complexity attribute. The first element of this list will always be an Identity layer. Then, the adjacency matrix is created as an upper-triangle matrix, with the same size as the list. This adjacency matrix is corrected using the :code: fill_adj_matrix function to prevent nodes from having no incoming or outgoing connections. Parameters ---------- size : int, default=1 Number of draws. Returns ------- matrices: list or AdjMatrix List containing the randomly created DAGs, or a single DAG if size=1. """ matrices = [] for _ in range(size): operations = self.operations.random() if self.complexity is not None: operations = operations[: min(self.complexity, len(operations))] from dragon.search_space.bricks import Identity operations = [Node("add", Identity, {})] + operations matrix = np.random.randint(0, 2, (len(operations), len(operations))) matrix = np.triu(matrix, k=1) matrix = fill_adj_matrix(matrix) adj_matrix = AdjMatrix(operations, matrix) matrices.append(adj_matrix) if size == 1: return matrices[0] return matrices
[docs] def isconstant(self): """isconstant() Returns ------- out: False Return False, a dynamic block cannot be constant. (It is a binary) """ return False
def __repr__(self): return ( super(EvoDagVariable, self).__repr__() + f"\ \t- Operations:\n" + self.operations.__repr__() )