Toeplitz matrices are ones where the values along all diagonals are constant. Doubly-blocked Toeplitz matrices are matrices made out of Toeplitz sub-matrices in a structure that is, itself Toeplitz so the sub-matrices are repeated along diagonals. Doubly-blocked Toeplitz matrices can be used to represent common machine learning operations including convolutions and, in GeNN, Toeplitz connectivity initialisation snippets can be used to generate such connectivity on the fly with SynapseMatrixType::TOEPLITZ_KERNELG (see Synaptic matrix types).
There are a number of predefined Toeplitz connectivity initialisation snippets:
In Python, these models can be selected by their unqualified name e.g. "Conv2D".
For example, to initialise convolutional synaptic connectivity with a 3x3 kernel between two populations of 1024 neurons, each representing a 32x32 layer with a single channel:
InitTopelitzConnectivitySnippet::Conv2D::ParamValues conv(
3, 3,
32, 32, 1,
32, 32, 1);
...
initToeplitzConnectivity<InitTopelitzConnectivitySnippet::Conv2D>(conv));
conv = {"conv_kh": 3, "conv_kw": 3,
"conv_ih": 32, "conv_iw": 32, "conv_ic": 1,
"conv_oh": 32, "conv_ow": 32, "conv_oc": 1}
model.add_synapse_population(
...
genn_model.init_toeplitz_connectivity("Conv2D", conv))
Here,
conv_kh
denotes the kernel height,
conv_kw
the kernel width,
conv_ih
the input layer height,
conv_iw
, the input layer width,
conv_ic
the number of input channels,
conv_oh
the output layer height,
conv_ow
the output layer width, and
conv_oc
the number of output channels/filters.
Defining a new Toeplitz connectivity snippet
Similarly to sparse connectivity initialisation snippets, Toeplitz connectivity initialisation snippets can be created by simply defining a class in the model description.
For example, the following Toeplitz connectivity initialisation snippet could be used to convolve a kern_dim x kern_dim
square kernel with the output of populations of pop_dim x pop_dim
neurons.
{
public:
{"kernCol", "int", "$(id_diag) % (int)$(kern_dim)"}});
"const int preRow = $(id_pre) / (int)$(pop_dim);\n"
"const int preCol = $(id_pre) % (int)$(pop_dim);\n"
"// If we haven't gone off edge of output\n"
"const int postRow = preRow + $(kernRow) - 1;\n"
"const int postCol = preCol + $(kernCol) - 1;\n"
"if(postRow >= 0 && postCol >= 0 && postRow < (int)$(pop_dim) && postCol < (int)$(pop_dim)) {\n"
" // Calculate postsynaptic index\n"
" const int postInd = ((postRow * (int)$(pop_dim)) + postCol;\n"
" $(addSynapse, postInd, $(kernRow), $(kernCol));\n"
"}\n");
[](unsigned int, unsigned int, const std::vector<double> &pars)
{
return ((unsigned int)pars[0] * (unsigned int)pars[0]);
});
[](const std::vector<double> &pars)->std::vector<unsigned int>
{
return {(unsigned int)pars[0], (unsigned int)pars[0]};
});
};
simple_conv2d_model = genn_model.create_custom_toeplitz_connect_init_snippet_class(
"simple_conv2d",
param_names=["kern_size", "pop_dim"],
diagonal_build_state_vars=[
("kernRow", "int", "$(id_diag) / (int)$(kern_dim)"),
("kernCol", "int", "$(id_diag) % (int)$(kern_dim)")],
diagonal_build_code=
"""
const int preRow = $(id_pre) / (int)$(pop_dim);
const int preCol = $(id_pre) % (int)$(pop_dim);
// If we haven't gone off edge of output
const int postRow = preRow + $(kernRow) - 1;
const int postCol = preCol + $(kernCol) - 1;
if(postRow >= 0 && postCol >= 0 && postRow < (int)$(pop_dim) && postCol < (int)$(pop_dim)) {
// Calculate postsynaptic index
const int postInd = (postRow * (int)$(pop_dim)) + postCol;
$(addSynapse, postInd, $(kernRow), $(kernCol));
}
""",
calc_max_row_len_func=genn_model.create_cmlf_class(
lambda num_pre, num_post, pars: int(pars[0]) * int(pars[0]))(),
calc_kernel_size_func=genn_model.create_cmlf_class(
lambda pars: UnsignedIntVector([int(pars[0]), int(pars[0])]))())
Each
diagonal of Toeplitz connectivity is initialised independently by running the snippet of code specified using the
SET_DIAGONAL_BUILD_CODE()
macro diagonal_build_code
keyword argument within a loop. The $(id_diag) variable can be used to access the index of the diagonal being generated and the $(id_pre) variable can be used to access the index of the presynaptic neuron currently being processed. The
SET_DIAGONAL_BUILD_STATE_VARS()
macro diagonal_build_state_vars
keyword argument can be used to initialise state variables outside of the loop - in this case $(id_diag) is split into
kernRow
and
kernCol
which are subsequently used to access the kernel element repeated along each diagonal. Similarly to sparse connectivity initialisation snippets when a kernel is used, synapses are added to using the $(addSynapse, target, kernRow, kernCol) function where the size of the kernel is specified using the
SET_CALC_KERNEL_SIZE_FUNC() macro calc_kernel_size_func
keyword argument` .
Previous | Top | Next