# Graph¶

## Description¶

Container that represents the topology of the network in the form of a graph.

## Initializing¶

def __init__(self, inputs, outputs, unsafe=False, nodesOnly=False, name=None):


Parameters

Parameter Allowed types Description Default
inputs Union[Node, list] Input nodes of the graph -
outputs Union[Node, list] Output nodes of the graph -
unsafe bool Enables/disables secure placement of tensors in the GPU False
nodesOnly bool If True, then information about the modules used will not be stored in the attributes of the graph False
name str Container name None

Explanations

unsafe - for some modules, it is possible to overwrite their tensors in the GPU to save memory. If the unsafe flag is not set, the graph will be validated for the absence of sections leading to corruption of the data tensors in the GPU caused by inplace flags;

nodesOnly - the data on the nodes included in the graph is stored in its nodes attribute, and when the nodesOnly flag is not set, information about the modules corresponding to these nodes is stored in a separate modules attribute.

## Methods¶

### gatherTopology¶

def gatherTopology(self, node, nodesOnly):

Functionality
Collects information about the graph topology, stores it in the class attributes, and, under certain conditions, validates the graph for the presence of sections that can lead to corruption of the data tensors in the GPU.

Parameters

Parameter Allowed types Description Default
node Node Node descendant class object that is the input node of the graph. -
nodesOnly bool If True, then information about the modules used will not be stored in the attributes of the graph -

Explanations

-

### getBlueprint¶

def getBlueprint(self):

Functionality
Generates a dictionary containing all the necessary information about the structure of the graph for its recovery in the absence of the code.

Parameters

-

Explanations

-

### getNodeByName¶

def getNodeByName(self, name):

Functionality
Returns the node of the graph (an object of the Node descendant class) by its name.

Parameters

Parameter Allowed types Description Default
name str Node of interest name -

Explanations

-

### optimizeForShape¶

def optimizeForShape(self, shape, memlimit=None):

Functionality
Performs graph optimization for a given input size

Parameters

Parameter Allowed types Description Default
shape tuple The input shape to optimize for -
memlimit int The input shape to optimize for None

Explanations

-

### updateData¶

def updateData(self, data):

Functionality
Starting from the input nodes, it makes a sequential passage through the graph, applying data operations assigned to nodes.

Parameters

Parameter Allowed types Description Default
data tensor Input data tensor -

Explanations

-

### dataShapeFrom¶

def dataShapeFrom(self, shape):

Functionality
Calculates the shape of the data after it passes through the graph.

Parameters

Parameter Allowed types Description Default
shape Union[list, tuple] Input data shape -

Explanations
shape - tuple, if there is one input node and List[tuple], if there are several input nodes

### graphDataShape¶

def graphDataShape(self, shape, onmodule):

Functionality
The basic method to calculate the shape of the data after it passes through the graph. It takes up an auxiliary function as an argument, providing the ability to perform an additional operation when calculating shapes.

Parameters

Parameter Allowed types Description Default
shape Union[list, tuple] Input data shape -

Explanations
shape - see dataShapeFrom;

onmmodule - an additional function that will be used in the calculation of shapes; as a rule, this is a function for optimizing a node for its output shape

### backward¶

def backward(self, grad, updParamGrads=True, updGrad=True, scale=1.0, momentum=0.0):

Functionality
Starting from the output nodes, it goes through the graph in the opposite direction, implementing the algorithm for the back propagation of the error — calculating the gradients on the parameters of the nodes and their input data.

Parameters

Parameter Allowed types Description Default
updParamGrads bool Flag that regulates the calculation of the gradient on the parameters of the node True
updGrad bool Flag that regulates the calculation of the gradient on the input data of the node True
scale float Scale: determines the scale the gradient 1.0
momentum float Momentum: determines how much gradient should be kept in this iteration 0.0

Explanations

-

### gradShapeFrom¶

def gradShapeFrom(self, shape):

Functionality
Calculates the gradient shape on the input nodes of the graph.

Parameters

Parameter Allowed types Description Default
shape Union[list, tuple] Gradient shape of the output nodes of the graph -

Explanations

-

### updateGrad¶

def updateGrad(self, grad):

Functionality
A stub

Parameters

Parameter Allowed types Description Default

Explanations

-

### reset¶

def reset(self):

Functionality
Resets the internal state of the graph and its nodes (for details, see Node.reset)

Parameters

-

Explanations

-

### clearTraverse¶

def clearTraverse(self):

Functionality
Unsets the traverse flagsf wdVisited and bwdVisited for all the graph nodes.

Parameters

-

Explanations

-

## Examples¶

Let us compose a simple graph that performs the following operations:

1. Two fully connected input layers 'linear0' and 'linear1'
2. Splitting the data tensor after the first input layer into three blocks (‘split’)
3. Concatenation of two blocks from the first tensor with the data tensor of the second fully connected layer (‘concat0’)
4. Activation 'act' on the concatenation 'concat0'
5. Final concatenation of the remaining block from 'split' and the data after the ‘act’ activation (‘concat1’)

Necessary imports:

import numpy as np
from PuzzleLib.Backend import gpuarray
from PuzzleLib.Containers import Graph
from PuzzleLib.Modules import Linear, Split, Concat, Activation, relu


Info

gpuarray is necessary for the correct placement of the tensor in the GPU

Creating nodes of the future graph:

v1 = Linear(10, 5, name="linear0").node()
h1 = Split(axis=1, sections=(2, 2, 1), name="split").node(v1)
v2 = Linear(10, 5, name="linear1").node()
h2 = Concat(axis=1, name="concat0").node((h1, [1, 2]), v2)
h3 = Activation(relu, name="act").node(h2)
h4 = Concat(axis=1, name="concat1").node((h1, 0), h3)


Initialization of the graph itself:

graph = Graph(inputs=[v1, v2], outputs=h4)


np.random.seed(123)
v1data = gpuarray.to_gpu(np.random.randint(0, 255, (5, 10)).astype(np.float32))
v2data = gpuarray.to_gpu(np.random.randint(0, 255, (5, 10)).astype(np.float32))


Graph data processing:

graph([v1data, v2data])


Data preparation (loading from the GPU into RAM using the get method) from the 'split' layer, and its output to the screen. The round method hereinafter is used only to simplify the visual perception of the tensors:

splitData = [d.get() for d in graph["split"].data]
for i, data in enumerate(splitData):
... print("split block {} results: \n{}".format(i, data.round(1)))

split block 0 results:
[[-306.6 -436.2]
[-210.  -386.2]
[-476.1 -364.3]
[-185.  -217.1]
[-334.  -413. ]]
split block 1 results:
[[-272.2   52.7]
[-169.8   13.8]
[-242.3   57.7]
[ -50.7   59. ]
[-245.6   69.7]]
split block 2 results:
[[296.1]
[180.9]
[330.7]
[124.1]
[201.5]]


Results from the other layers:

# 'linear1' layer results
print(graph["linear1"].data.get().round(1))

[[ -92.3  241.8  -80.2   18.4   40.3]
[ -50.2  178.3  -65.8    9.8  -97. ]
[ -15.3  122.3 -177.9  -26.6 -187.2]
[ -84.4  293.8 -132.5  -78.5  -58.7]
[  24.9   34.2 -126.4  -15.8    8. ]]

# 'concat0' layer results
print(graph["concat0"].data.get().round(1))

[[  17.1 -348.5 -222.5  -92.3  241.8  -80.2   18.4   40.3]
[  82.6 -332.7 -235.6  -50.2  178.3  -65.8    9.8  -97. ]
[  28.1 -298.3 -102.7  -15.3  122.3 -177.9  -26.6 -187.2]
[  33.8 -176.1  -96.6  -84.4  293.8 -132.5  -78.5  -58.7]
[  13.3 -285.  -190.7   24.9   34.2 -126.4  -15.8    8. ]]

# 'act' layer results
print(graph["act"].data.get().round(1))

[[ 17.1   0.    0.    0.  241.8   0.   18.4  40.3]
[ 82.6   0.    0.    0.  178.3   0.    9.8   0. ]
[ 28.1   0.    0.    0.  122.3   0.    0.    0. ]
[ 33.8   0.    0.    0.  293.8   0.    0.    0. ]
[ 13.3   0.    0.   24.9  34.2   0.    0.    8. ]]

# 'concat1' layer results
print(graph["concat1"].data.get().round(1))

[[205.1  40.4  17.1   0.    0.    0.  241.8   0.   18.4  40.3]
[145.4  55.7  82.6   0.    0.    0.  178.3   0.    9.8   0. ]
[112.4 175.7  28.1   0.    0.    0.  122.3   0.    0.    0. ]
[ 50.4 145.5  33.8   0.    0.    0.  293.8   0.    0.    0. ]
[216.4 137.9  13.3   0.    0.   24.9  34.2   0.    0.    8. ]]
`