Source code for deephyper.nas.node

"""This module provides the available node types to build a ``KSearchSpace``.
"""
import tensorflow as tf

import deephyper.core.exceptions
from deephyper.nas.operation import Operation


[docs]class Node: """Represents a node of a ``KSearchSpace``. Args: name (str): node name. """ # Number of 'Node' instances created num = 0 def __init__(self, name="", *args, **kwargs): Node.num += 1 self._num = Node.num self._tensor = None self.name = name def __str__(self): return f"{self.name}[id={self._num}]" @property def id(self): return self._num @property def op(self): raise NotImplementedError def create_tensor(self, *args, **kwargs): raise NotImplementedError @staticmethod def verify_operation(op): if isinstance(op, Operation): return op elif isinstance(op, tf.keras.layers.Layer): return Operation(op) else: raise RuntimeError( f"Can't add this operation '{op.__name__}'. An operation should be either of type Operation or tf.keras.layers.Layer when is of type: {type(op)}" )
[docs]class OperationNode(Node): def __init__(self, name="", *args, **kwargs): super().__init__(name=name, *args, **kwargs) def create_tensor(self, inputs=None, train=True, seed=None, **kwargs): if self._tensor is None: if inputs is None: try: self._tensor = self.op(train=train, seed=None) except TypeError: raise RuntimeError( f'Verify if node: "{self}" has incoming connexions!' ) else: self._tensor = self.op(inputs, train=train) return self._tensor
[docs]class VariableNode(OperationNode): """This class represents a node of a graph where you have a set of possible operations. It means the agent will have to act to choose one of these operations. >>> import tensorflow as tf >>> from deephyper.nas.space.node import VariableNode >>> vnode = VariableNode("VNode1") >>> from deephyper.nas.space.op.op1d import Dense >>> vnode.add_op(Dense( ... units=10, ... activation=tf.nn.relu)) >>> vnode.num_ops 1 >>> vnode.add_op(Dense( ... units=1000, ... activation=tf.nn.tanh)) >>> vnode.num_ops 2 >>> vnode.set_op(0) >>> vnode.op.units 10 Args: name (str): node name. """ def __init__(self, name=""): super().__init__(name=name) self._ops = list() self._index = None def __str__(self): if self._index is not None: return f"{super().__str__()}(Variable[{str(self.op)}])" else: return f"{super().__str__()}(Variable[?])" def add_op(self, op): self._ops.append(self.verify_operation(op)) @property def num_ops(self): return len(self._ops) def set_op(self, index): self.get_op(index).init(self) def get_op(self, index): assert "float" in str(type(index)) or "int" in str( type(index) ), f"found type is : {type(index)}" if "float" in str(type(index)): self._index = self.denormalize(index) else: assert 0 <= index and index < len( self._ops ), f"Number of possible operations is: {len(self._ops)}, but index given is: {index} (index starts from 0)!" self._index = index return self.op
[docs] def denormalize(self, index): """Denormalize a normalized index to get an absolute indexes. Useful when you want to compare the number of different search_spaces. Args: indexes (float|int): a normalized index. Returns: int: An absolute indexes corresponding to the operation choosen with the relative index of `index`. """ if type(index) is int: return index else: assert 0.0 <= index and index <= 1.0 res = int(index * len(self._ops)) if index == 1.0: res -= 1 return res
@property def op(self): if len(self._ops) == 0: raise RuntimeError("This VariableNode doesn't have any operation yet.") elif self._index is None: raise RuntimeError( 'This VariableNode doesn\'t have any set operation, please use "set_op(index)" if you want to set one' ) else: return self._ops[self._index] @property def ops(self): return self._ops
[docs]class ConstantNode(OperationNode): """A ConstantNode represents a node with a fixed operation. It means the agent will not make any new decision for this node. The common use case for this node is to add a tensor in the graph. >>> import tensorflow as tf >>> from deephyper.nas.space.node import ConstantNode >>> from deephyper.nas.space.op.op1d import Dense >>> cnode = ConstantNode(op=Dense(units=100, activation=tf.nn.relu), name='CNode1') >>> cnode.op Dense_100_relu Args: op (Operation, optional): operation to fix for this node. Defaults to None. name (str, optional): node name. Defaults to ``''``. """ def __init__(self, op=None, name="", *args, **kwargs): super().__init__(name=name) if op is not None: op = self.verify_operation(op) op.init(self) # set operation self._op = op def set_op(self, op): op = self.verify_operation(op) op.init(self) self._op = op def __str__(self): return f"{super().__str__()}(Constant[{str(self.op)}])" @property def op(self): return self._op
[docs]class MirrorNode(OperationNode): """A MirrorNode is a node which reuse an other, it enable the reuse of tf.keras layers. This node will not add operations to choose. Args: node (Node): The targeted node to mirror. >>> from deephyper.nas.space.node import VariableNode, MirrorNode >>> from deephyper.nas.space.op.op1d import Dense >>> vnode = VariableNode() >>> vnode.add_op(Dense(10)) >>> vnode.add_op(Dense(20)) >>> mnode = MirrorNode(vnode) >>> vnode.set_op(0) >>> vnode.op Dense_10 >>> mnode.op Dense_10 """ def __init__(self, node): super().__init__(name=f"Mirror[{str(node)}]") self._node = node @property def op(self): return self._node.op
[docs]class MimeNode(OperationNode): """A MimeNode is a node which reuse an the choice made for an VariableNode, it enable the definition of a Cell based search_space. This node reuse the operation from the mimed VariableNode but only the choice made. Args: node (VariableNode): the VariableNode to mime. >>> from deephyper.nas.space.node import VariableNode, MimeNode >>> from deephyper.nas.space.op.op1d import Dense >>> vnode = VariableNode() >>> vnode.add_op(Dense(10)) >>> vnode.add_op(Dense(20)) >>> mnode = MimeNode(vnode) >>> mnode.add_op(Dense(30)) >>> mnode.add_op(Dense(40)) >>> vnode.set_op(0) >>> vnode.op Dense_10 >>> mnode.op Dense_30 """ def __init__(self, node, name=""): super().__init__(name=f"Mime[{name}][src={str(node)}]") self.node = node self._ops = list() def add_op(self, op): self._ops.append(self.verify_operation(op)) @property def num_ops(self): return len(self._ops) def set_op(self): if self.node._index is None: raise deephyper.core.exceptions.DeephyperRuntimeError( f"{str(self)} cannot be initialized because its source {str(self.node)} is not initialized!" ) self._ops[self.node._index].init(self) @property def op(self): if self.num_ops != self.node.num_ops: raise deephyper.core.exceptions.DeephyperRuntimeError( f"{str(self)} and {str(self.node)} should have the same number of opertions, when {str(self)} has {self.num_ops} and {str(self.node)} has {self.node.num_ops}!" ) else: return self._ops[self.node._index] @property def ops(self): return self._ops