Graph
neps.search_spaces.architecture.graph
#
EdgeData
#
EdgeData(data: dict = None)
Class that holds data for each edge. Data can be shared between instances of the graph where the edges lives in.
Also defines the default key 'op', which is Identity()
. It must
be private always.
Items can be accessed directly as attributes with .key
or
in a dict-like fashion with [key]
. To set a new item use .set()
.
PARAMETER | DESCRIPTION |
---|---|
data |
Inject some initial data. Will be always private.
TYPE:
|
Source code in neps/search_spaces/architecture/graph.py
clone
#
Return a true deep copy of EdgeData. Even shared items are not shared anymore.
RETURNS | DESCRIPTION |
---|---|
EdgeData
|
New independent instance. |
copy
#
When a graph is copied to get multiple instances (e.g. when reusing subgraphs at more than one location) then this function will be called for all edges.
It will create a deep copy for the private entries but only a shallow copy for the shared entries. E.g. architectural weights should be shared, but parameters of a 3x3 convolution not.
Therefore 'op' must be always private.
RETURNS | DESCRIPTION |
---|---|
EdgeData
|
A new EdgeData object with independent private items, but shallow shared items. |
Source code in neps/search_spaces/architecture/graph.py
delete
#
finalize
#
Sets this edge as final. This means it cannot be changed anymore and will also not appear in the update functions of the graph.
has
#
has(key: str)
Checks whether key
exists.
PARAMETER | DESCRIPTION |
---|---|
key |
The key to check.
TYPE:
|
RETURNS | DESCRIPTION |
---|---|
bool
|
True if key exists, False otherwise. |
Source code in neps/search_spaces/architecture/graph.py
is_deleted
#
is_final
#
remove
#
remove(key: str)
Removes an item from the EdgeData
PARAMETER | DESCRIPTION |
---|---|
key |
The key for the item to be removed.
TYPE:
|
Source code in neps/search_spaces/architecture/graph.py
set
#
set(key: str, value, shared=False)
Used to assign a new item to the EdgeData object.
PARAMETER | DESCRIPTION |
---|---|
key |
The key.
TYPE:
|
value |
The value to store
TYPE:
|
shared |
Default: False. Whether the item should be a shallow copy between different instances of EdgeData (and consequently between different instances of Graph).
TYPE:
|
Source code in neps/search_spaces/architecture/graph.py
update
#
Update the data in here. If the data is added as dict, then all variables will be handled as private.
PARAMETER | DESCRIPTION |
---|---|
data |
If dict, then values will be set as private. If EdgeData then all entries will be replaced. |
Source code in neps/search_spaces/architecture/graph.py
Graph
#
Bases: Module
, DiGraph
Base class for defining a search space. Add nodes and edges
as for a directed acyclic graph in networkx
. Nodes can contain
graphs as children, also edges can contain graphs as operations.
Note, if a graph is copied, the shared attributes of its edges are shallow copies whereas the private attributes are deep copies.
To differentiate copies of the same graph you can define a scope
with set_scope()
.
Graph at nodes:
graph = Graph() graph.add_node(1, subgraph=Graph())
If the node has more than one input use set_input()
to define the
routing to the input nodes of the subgraph.
Graph at edges:
graph = Graph() graph.add_nodes_from([1, 2]) graph.add_edge(1, 2, EdgeData({'op': Graph()}))
Modify the graph after definition
If you want to modify the graph e.g. in an optimizer once
it has been defined already use the function update_edges()
or update_nodes()
.
Use as pytorch module If you want to learn the weights of the operations or any other parameters of the graph you have to parse it first.
graph = getFancySearchSpace() graph.parse() logits = graph(data) optimizer.min(loss(logits, target))
To update the pytorch module representation (e.g. after removing or adding some new edges), you have to unparse. Beware that this is not fast, so it should not be done on each batch or epoch, rather once after discretizising. If you want to change the representation of the graph use rather some shared operation indexing at the edges.
graph.update(remove_random_edges) graph.unparse() graph.parse() logits = graph(data)
is set as sum.
Note
When inheriting form Graph
note that __init__()
cannot take any parameters.
This is due to the way how networkx is implemented, i.e. graphs are reconstructed
internally and no parameters for init are considered.
Our recommended solution is to create static attributes before initialization and
then load them dynamically in __init__()
.
def init(self): num_classes = self.NUM_CLASSES MyGraph.NUM_CLASSES = 42 my_graph_42_classes = MyGraph()
Source code in neps/search_spaces/architecture/graph.py
OPTIMIZER_SCOPE
class-attribute
instance-attribute
#
Whether the search space has an interface to one of the tabular benchmarks which can then be used to query architecture performances.
If this is set to true then query()
should be implemented.
__hash__
#
As it is very complicated to compare graphs (i.e. check all edge attributes, do the have shared attributes, ...) use just the name for comparison.
This is used when determining whether two instances are copies.
Source code in neps/search_spaces/architecture/graph.py
add_edges_densly
#
add_node
#
Adds a node to the graph.
Note that adding a node using an index that has been used already will override its attributes.
PARAMETER | DESCRIPTION |
---|---|
node_index |
The index for the node. Expect to be >= 1.
TYPE:
|
**attr |
The attributes which can be added in a dict like form.
DEFAULT:
|
Source code in neps/search_spaces/architecture/graph.py
clone
#
compile
#
Instanciates the ops at the edges using the arguments specified at the edges
Source code in neps/search_spaces/architecture/graph.py
copy
#
Copy as defined in networkx, i.e. a shallow copy.
Just handling recursively nested graphs seperately.
Source code in neps/search_spaces/architecture/graph.py
forward
#
Forward some data through the graph. This is done recursively in case there are graphs defined on nodes or as 'op' on edges.
PARAMETER | DESCRIPTION |
---|---|
x |
The input. If the graph sits on a node the input can be a dict with {source_idx: Tensor} to be routed to the defined input nodes. If the graph sits on an edge, x is the feature tensor.
TYPE:
|
args |
This is only required to handle cases where the graph sits on an edge and receives an EdgeData object which will be ignored
DEFAULT:
|
Source code in neps/search_spaces/architecture/graph.py
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
|
get_all_edge_data
#
Get edge attributes of this graph and all child graphs in one go.
PARAMETER | DESCRIPTION |
---|---|
key |
The key of the attribute
TYPE:
|
scope |
The scope to be applied
TYPE:
|
private_edge_data |
Whether to return data from graph copies as well.
TYPE:
|
RETURNS | DESCRIPTION |
---|---|
list
|
All data in a list.
TYPE:
|
Source code in neps/search_spaces/architecture/graph.py
get_dense_edges
#
Returns the edge indices (i, j) that would make a fully connected DAG without circles such that i < j and i != j. Assumes nodes are already created.
RETURNS | DESCRIPTION |
---|---|
list
|
list of edge indices. |
Source code in neps/search_spaces/architecture/graph.py
modules_str
#
Once the graph has been parsed, prints the modules as they appear in pytorch.
Source code in neps/search_spaces/architecture/graph.py
num_input_nodes
#
num_input_nodes() -> int
The number of input nodes, i.e. the nodes without an incoming edge.
RETURNS | DESCRIPTION |
---|---|
int
|
Number of input nodes.
TYPE:
|
parse
#
Convert the graph into a neural network which can then be optimized by pytorch.
Source code in neps/search_spaces/architecture/graph.py
prepare_discretization
#
In some cases the search space is manipulated before the final discretization is happening, e.g. DARTS. In such chases this should be defined in the search space, so all optimizers can call it.
Source code in neps/search_spaces/architecture/graph.py
prepare_evaluation
#
In some cases the evaluation architecture does not match the searched one. An example is where the makro_model is extended to increase the parameters. This is done here.
reset_weights
#
reset_weights(inplace: bool = False)
Resets the weights for the 'op' at all edges.
PARAMETER | DESCRIPTION |
---|---|
inplace |
Do the operation in place or return a modified copy.
TYPE:
|
Returns: Graph: Returns the modified version of the graph.
Source code in neps/search_spaces/architecture/graph.py
set_at_edges
#
Sets the attribute for all edges in this and any child graph
Source code in neps/search_spaces/architecture/graph.py
set_input
#
set_input(node_idxs: list)
Route the input from specific parent edges to the input nodes of this subgraph. Inputs are assigned in lexicographical order.
Example:
- Parent node (i.e. node where self
is located on) has two
incoming edges from nodes 3 and 5.
- self
has two input nodes 1 and 2 (i.e. nodes without
an incoming edge)
- node_idxs = [5, 3]
Then input of node 5 is routed to node 1 and input of node 3
is routed to node 2.
Similarly, if node_idxs = [5, 5]
then input of node 5 is routed
to both node 1 and 2. Warning: In this case the output of another
incoming edge is ignored!
Should be used in a builder-like pattern: 'subgraph'=Graph().set_input([5, 3])
PARAMETER | DESCRIPTION |
---|---|
node_idx |
The index of the nodes where the data is coming from.
TYPE:
|
RETURNS | DESCRIPTION |
---|---|
Graph
|
self with input node indices set. |
Source code in neps/search_spaces/architecture/graph.py
set_scope
#
set_scope(scope: str, recursively=True)
Sets the scope of this instance of the graph.
The function should be used in a builder-like pattern
'subgraph'=Graph().set_scope("scope")
.
PARAMETER | DESCRIPTION |
---|---|
scope |
the scope
TYPE:
|
recursively |
Also set the scope for all child graphs. default True
TYPE:
|
RETURNS | DESCRIPTION |
---|---|
Graph
|
self with the setted scope. |
Source code in neps/search_spaces/architecture/graph.py
unparse
#
Undo the pytorch parsing by reconstructing the graph uusing the networkx data structures.
This is done recursively also for child graphs.
RETURNS | DESCRIPTION |
---|---|
Graph
|
An unparsed shallow copy of the graph. |
Source code in neps/search_spaces/architecture/graph.py
update_edges
#
This updates the edge data of this graph and all child graphs.
This is the preferred way to manipulate the edges after the definition
of the graph, e.g. by optimizers who want to insert their own op.
update_func(current_edge_data)
. This way optimizers
can initialize and store necessary information at edges.
Note that edges marked as 'final' will not be updated here.
PARAMETER | DESCRIPTION |
---|---|
update_func |
Function which accepts one argument called
TYPE:
|
scope |
Can be "all" or list of scopes to be updated.
TYPE:
|
private_edge_data |
If set to true, this means update_func will be applied to all edges. THIS IS NOT RECOMMENDED FOR SHARED ATTRIBUTES. Shared attributes should be set only once, we take care it is syncronized across all copies of this graph. The only usecase for setting it to true is when actually changing
TYPE:
|
Source code in neps/search_spaces/architecture/graph.py
update_nodes
#
Update the nodes of the graph and its incoming and outgoing edges by iterating over the
graph and applying update_func
to each of it. This is the
preferred way to change the search space once it has been defined.
Note that edges marked as 'final' will not be updated here.
PARAMETER | DESCRIPTION |
---|---|
update_func |
Function that accepts three incoming parameters named
TYPE:
|
scope |
Can be "all" or list of scopes to be updated. Only graphs and child graphs with the specified scope are considered
TYPE:
|
single_instance |
If set to false, this means update_func will be applied to nodes of all copies of a graphs. THIS IS NOT RECOMMENDED FOR SHARED ATTRIBUTES, i.e. when manipulating the shared data of incoming or outgoing edges. Shared attributes should be set only once, we take care it is syncronized across all copies of this graph. The only usecase for setting it to true is when actually changing
TYPE:
|
Source code in neps/search_spaces/architecture/graph.py
iter_flatten
#
Flatten a potentially deeply nested python list
Source code in neps/search_spaces/architecture/graph.py
log_first_n
#
Log only for the first n times.
Args:
lvl (int): the logging level
msg (str):
n (int):
name (str): name of the logger to use. Will use the caller's module by default.
key (str or tuple[str]): the string(s) can be one of "caller" or
"message", which defines how to identify duplicated logs.
For example, if called with n=1, key="caller"
, this function
will only log the first call from the same caller, regardless of
the message content.
If called with n=1, key="message"
, this function will log the
same content only once, even if they are called from different places.
If called with n=1, key=("caller", "message")
, this function
will not log only if the same caller has logged the same message before.