diff --git a/README.rst b/README.rst index 565bbbc..6d65800 100644 --- a/README.rst +++ b/README.rst @@ -42,7 +42,7 @@ It can also be used with a python environment in the following manner: from keras2c import k2c k2c(model, function_name, malloc=False, num_tests=10, verbose=True) -For more information, see `Installation `_ and `Usage `_ +For more information, see `Installation `_ and `Usage `_ Supported Layers @@ -73,9 +73,9 @@ ToDo Contribute ********** -- Documentation: ``_ -- Issue Tracker: ``_ -- Source Code: ``_ +- Documentation: ``_ +- Issue Tracker: ``_ +- Source Code: ``_ License ******* @@ -83,14 +83,14 @@ License The project is licensed under the LGPLv3 license. -.. |Build-Status| image:: https://travis-ci.org/f0uriest/keras2c.svg?branch=master - :target: https://travis-ci.org/f0uriest/keras2c +.. |Build-Status| image:: https://travis-ci.org/plasmacontrol/keras2c.svg?branch=master + :target: https://travis-ci.org/plasmacontrol/keras2c :alt: Build Status -.. |Codecov| image:: https://codecov.io/gh/f0uriest/keras2c/branch/master/graph/badge.svg - :target: https://codecov.io/gh/f0uriest/keras2c +.. |Codecov| image:: https://codecov.io/gh/plasmacontrol/keras2c/branch/master/graph/badge.svg + :target: https://codecov.io/gh/plasmacontrol/keras2c :alt: Code Coverage -.. |License| image:: https://img.shields.io/github/license/f0uriest/keras2c - :target: https://github.com/f0uriest/keras2c/blob/master/LICENSE +.. |License| image:: https://img.shields.io/github/license/plasmacontrol/keras2c + :target: https://github.com/plasmacontrol/keras2c/blob/master/LICENSE :alt: License: LGPLv3 .. |DOI| image:: https://zenodo.org/badge/193152058.svg :target: https://zenodo.org/badge/latestdoi/193152058 diff --git a/include/k2c_activations.c b/include/k2c_activations.c index 909256e..8b71118 100644 --- a/include/k2c_activations.c +++ b/include/k2c_activations.c @@ -116,6 +116,24 @@ void k2c_sigmoid_func(float * x, const size_t size) { } k2c_activationType * k2c_sigmoid = k2c_sigmoid_func; +/** + * swish activation function. + * y = x * (1/(1+exp(-x))) + * + * :param x: array of input values. Gets overwritten by output. + * :param size: length of input array. + */ +void k2c_swish_func(float * x, const size_t size) { + + for (size_t i = 0; i < size; ++i) { + float xv = x[i]; + float v = xv; + if (v < -30.0f) v = -30.0f; // Clamp to avoid overflow + x[i] = xv / (1.0f + expf(-v)); + } +} +k2c_activationType * k2c_swish = k2c_swish_func; + /** * Soft max activation function. diff --git a/include/k2c_conv_transpose_layer.c b/include/k2c_conv_transpose_layer.c new file mode 100644 index 0000000..f206889 --- /dev/null +++ b/include/k2c_conv_transpose_layer.c @@ -0,0 +1,73 @@ +#include +#include "k2c_include.h" + +/** + * 1D (temporal) Convolution. + * Assumes a "channels last" structure. + * + * :param output: output tensor. + * :param input: input tensor. + * :param kernel: kernel tensor. + * :param bias: bias tensor. + * :param stride: stride length of the convolution. + * :param dilation: dilation rate to use for dilated convolution. + * :param activation: activation function to apply to output. + */ +void k2c_conv1d_transpose(k2c_tensor *output, const k2c_tensor *input, + const k2c_tensor *kernel, const k2c_tensor *bias, + const size_t stride, const size_t start_crop, + k2c_activationType *activation) +{ + memset(output->array, 0, output->numel * sizeof(output->array[0])); + + const size_t n_height = input->shape[0]; + const size_t n_channels = input->shape[1]; + const size_t k_size = kernel->shape[0]; + const size_t n_filters = kernel->shape[1]; + const size_t out_height = output->shape[0]; + + const size_t ker_dim12 = n_channels * n_filters; + + size_t cs = 0; + size_t ce = 0; + size_t ts = 0; + size_t ks = 0; + + for (size_t f = 0; f < n_filters; ++f) + { + for (size_t ch = 0; ch < n_channels; ++ch) + { + for (size_t t = 0; t < n_height; ++t) + { + ts = t * stride; + if (ts > start_crop) + { + cs = ts - start_crop; + } + else + { + cs = 0; + } + if (ts + k_size - start_crop > out_height) + { + ce = out_height; + } + else + { + ce = ts + k_size - start_crop; + } + ks = cs - (ts - start_crop); + for (size_t i = 0; i < ce - cs; ++i) + { + output->array[(i + cs) * n_filters + f] += + kernel->array[(i + ks) * ker_dim12 + f * n_channels + ch] * + input->array[t * n_channels + ch]; + } + } + } + } + // } + + k2c_bias_add(output, bias); + activation(output->array, output->numel); +} diff --git a/include/k2c_convolution_layers.c b/include/k2c_convolution_layers.c index d44cc8d..5b76ce3 100644 --- a/include/k2c_convolution_layers.c +++ b/include/k2c_convolution_layers.c @@ -6,13 +6,11 @@ Licensed under MIT License https://github.com/f0uriest/keras2c */ - #include #include #include #include "k2c_include.h" - /** * 1D (temporal) Padding. * @@ -21,30 +19,33 @@ Licensed under MIT License * :param fill: value to fill in padded areas. * :param pad: array[2] of how many rows to pad. Order is {before dim 1, after dim 1}. */ -void k2c_pad1d(k2c_tensor* output, const k2c_tensor* input, const float fill, - const size_t * pad) { +void k2c_pad1d(k2c_tensor *output, const k2c_tensor *input, const float fill, + const size_t *pad) +{ const size_t in_width = input->shape[1]; const size_t pad_top = pad[0]; // set output array to fill value - if (fabs(fill) < 1e-6) { + if (fabs(fill) < 1e-6) + { // fill is ~zero, use memset - memset(output->array,0,output->numel*sizeof(output->array[0])); + memset(output->array, 0, output->numel * sizeof(output->array[0])); } - else { - for(size_t i=0; inumel; ++i) { + else + { + for (size_t i = 0; i < output->numel; ++i) + { output->array[i] = fill; } } // memcpy the old array in the right place - const size_t offset = pad_top*in_width; - memcpy(&output->array[offset],&input->array[0], - input->numel*sizeof(input->array[0])); + const size_t offset = pad_top * in_width; + memcpy(&output->array[offset], &input->array[0], + input->numel * sizeof(input->array[0])); } - /** * 2D (spatial) Padding. * @@ -53,8 +54,9 @@ void k2c_pad1d(k2c_tensor* output, const k2c_tensor* input, const float fill, * :param fill: value to fill in padded areas. * :param pad: array[4] of how many rows/cols to pad. Order is {before dim 1, after dim 1, before dim 2, after dim 2}. */ -void k2c_pad2d(k2c_tensor* output, const k2c_tensor* input, const float fill, - const size_t * pad) { +void k2c_pad2d(k2c_tensor *output, const k2c_tensor *input, const float fill, + const size_t *pad) +{ const size_t in_height = input->shape[0]; const size_t in_width = input->shape[1]; @@ -64,29 +66,32 @@ void k2c_pad2d(k2c_tensor* output, const k2c_tensor* input, const float fill, const size_t pad_right = pad[3]; // set output array to fill value - if (fabs(fill) < 1e-6) { + if (fabs(fill) < 1e-6) + { // fill is ~zero, use memset - memset(output->array,0,output->numel*sizeof(output->array[0])); + memset(output->array, 0, output->numel * sizeof(output->array[0])); } - else { - for(size_t i=0; inumel; ++i) { + else + { + for (size_t i = 0; i < output->numel; ++i) + { output->array[i] = fill; } } // memcpy the old array in the middle - size_t offset = in_channels*(pad_left+pad_right+in_width)*pad_top + - in_channels*pad_left; - const size_t num = in_channels*in_width; - const size_t step = num+in_channels*(pad_left+pad_right); - for (size_t i=0; iarray[offset], - &input->array[i*num], - num*sizeof(input->array[0])); + &input->array[i * num], + num * sizeof(input->array[0])); offset += step; } } - /** * 3D (spatial or spatio-temporal) Padding. * @@ -95,8 +100,9 @@ void k2c_pad2d(k2c_tensor* output, const k2c_tensor* input, const float fill, * :param fill: value to fill in padded areas. * :param pad: array[6] of how many rows/cols to pad. Order is {before dim 1, after dim 1, before dim 2, after dim 2, before dim 3, after dim 3}. */ -void k2c_pad3d(k2c_tensor* output, const k2c_tensor* input, const float fill, - const size_t * pad) { +void k2c_pad3d(k2c_tensor *output, const k2c_tensor *input, const float fill, + const size_t *pad) +{ const size_t dim1 = input->shape[0]; const size_t dim2 = input->shape[1]; @@ -107,33 +113,37 @@ void k2c_pad3d(k2c_tensor* output, const k2c_tensor* input, const float fill, const size_t in_channels = input->shape[3]; // set output array to fill value - if (fabs(fill) < 1e-6) { + if (fabs(fill) < 1e-6) + { // fill is ~zero, use memset - memset(output->array,0,output->numel*sizeof(output->array[0])); + memset(output->array, 0, output->numel * sizeof(output->array[0])); } - else { - for(size_t i=0; inumel; ++i) { + else + { + for (size_t i = 0; i < output->numel; ++i) + { output->array[i] = fill; } } // memcpy the old array in the middle - const size_t offset1 = in_channels*(outdim2*outdim3)*pad[0] + in_channels*outdim3*pad[2] + in_channels*pad[4]; - const size_t num = in_channels*dim3; - const size_t outstep2 = num+in_channels*(pad[4]+pad[5]); - const size_t outstep1 = outdim2*outdim3*in_channels; - const size_t instep1 = dim2*dim3*in_channels; - const size_t instep2 = dim3*in_channels; - - for (size_t i=0; iarray[offset1+i*outstep1 + j*outstep2], - &input->array[i*instep1+j*instep2], - num*sizeof(input->array[0])); + const size_t offset1 = in_channels * (outdim2 * outdim3) * pad[0] + in_channels * outdim3 * pad[2] + in_channels * pad[4]; + const size_t num = in_channels * dim3; + const size_t outstep2 = num + in_channels * (pad[4] + pad[5]); + const size_t outstep1 = outdim2 * outdim3 * in_channels; + const size_t instep1 = dim2 * dim3 * in_channels; + const size_t instep2 = dim3 * in_channels; + + for (size_t i = 0; i < dim1; ++i) + { + for (size_t j = 0; j < dim2; ++j) + { + memcpy(&output->array[offset1 + i * outstep1 + j * outstep2], + &input->array[i * instep1 + j * instep2], + num * sizeof(input->array[0])); } } } - /** * 1D (temporal) Convolution. * Assumes a "channels last" structure. @@ -146,33 +156,37 @@ void k2c_pad3d(k2c_tensor* output, const k2c_tensor* input, const float fill, * :param dilation: dilation rate to use for dilated convolution. * :param activation: activation function to apply to output. */ -void k2c_conv1d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* kernel, - const k2c_tensor* bias, const size_t stride, const size_t dilation, - k2c_activationType *activation) { +void k2c_conv1d(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, const size_t stride, const size_t dilation, + k2c_activationType *activation) +{ - memset(output->array,0,output->numel*sizeof(output->array[0])); + memset(output->array, 0, output->numel * sizeof(output->array[0])); const size_t out_times = output->shape[0]; const size_t out_channels = output->shape[1]; const size_t in_channels = input->shape[1]; - for (size_t x0=0; x0 < out_times; ++x0) { - for (size_t z=0; z < kernel->shape[0]; ++z) { - for (size_t q=0; q < in_channels; ++q) { - for (size_t k=0; k < out_channels; ++k) { - output->array[x0*out_channels + k] += - kernel->array[z*(kernel->shape[2]*kernel->shape[1]) + - q*(kernel->shape[2]) + k]* - input->array[(x0*stride + dilation*z)*in_channels + q]; + for (size_t x0 = 0; x0 < out_times; ++x0) + { + for (size_t z = 0; z < kernel->shape[0]; ++z) + { + for (size_t q = 0; q < in_channels; ++q) + { + for (size_t k = 0; k < out_channels; ++k) + { + output->array[x0 * out_channels + k] += + kernel->array[z * (kernel->shape[2] * kernel->shape[1]) + + q * (kernel->shape[2]) + k] * + input->array[(x0 * stride + dilation * z) * in_channels + q]; } } } } - k2c_bias_add(output,bias); - activation(output->array,output->numel); + k2c_bias_add(output, bias); + activation(output->array, output->numel); } - /** * 2D (spatial) Convolution. * Assumes a "channels last" structure. @@ -185,42 +199,43 @@ void k2c_conv1d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* k * :param dilation: array[2] dilation rate to use for dilated convolution. Order is {dilation dim 1, dilation dim 2}. * :param activation: activation function to apply to output. */ -void k2c_conv2d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* kernel, - const k2c_tensor* bias, const size_t * stride, const size_t * dilation, - k2c_activationType *activation) { +void k2c_conv2d(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, const size_t *stride, const size_t *dilation, + k2c_activationType *activation) +{ - memset(output->array,0,output->numel*sizeof(output->array[0])); + memset(output->array, 0, output->numel * sizeof(output->array[0])); const size_t out_rows = output->shape[0]; const size_t out_cols = output->shape[1]; const size_t out_channels = output->shape[2]; const size_t in_channels = input->shape[2]; - for (size_t x0=0; x0 < out_rows; ++x0) { - for (size_t x1=0; x1 < out_cols; ++x1) { - for (size_t z0=0; z0 < kernel->shape[0]; ++z0) { - for (size_t z1=0; z1 < kernel->shape[1]; ++z1) { - for (size_t q=0; q < in_channels; ++q) { - for (size_t k=0; k < out_channels; ++k) { - output->array[x0*(output->shape[2]*output->shape[1]) - + x1*(output->shape[2]) + k] += - kernel->array[z0*(kernel->shape[3]*kernel->shape[2]*kernel->shape[1]) - + z1*(kernel->shape[3]*kernel->shape[2]) - + q*(kernel->shape[3]) + k]* - input->array[(x0*stride[0] - + dilation[0]*z0)*(input->shape[2]*input->shape[1]) - + (x1*stride[1] + dilation[1]*z1)*(input->shape[2]) + q]; + for (size_t x0 = 0; x0 < out_rows; ++x0) + { + for (size_t x1 = 0; x1 < out_cols; ++x1) + { + for (size_t z0 = 0; z0 < kernel->shape[0]; ++z0) + { + for (size_t z1 = 0; z1 < kernel->shape[1]; ++z1) + { + for (size_t q = 0; q < in_channels; ++q) + { + for (size_t k = 0; k < out_channels; ++k) + { + output->array[x0 * (output->shape[2] * output->shape[1]) + x1 * (output->shape[2]) + k] += + kernel->array[z0 * (kernel->shape[3] * kernel->shape[2] * kernel->shape[1]) + z1 * (kernel->shape[3] * kernel->shape[2]) + q * (kernel->shape[3]) + k] * + input->array[(x0 * stride[0] + dilation[0] * z0) * (input->shape[2] * input->shape[1]) + (x1 * stride[1] + dilation[1] * z1) * (input->shape[2]) + q]; } } } } } } - k2c_bias_add(output,bias); - activation(output->array,output->numel); + k2c_bias_add(output, bias); + activation(output->array, output->numel); } - /** * 3D (spatial or spatio-temporal) Convolution. * Assumes a "channels last" structure. @@ -233,41 +248,36 @@ void k2c_conv2d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* k * :param dilation: array[3] dilation rate to use for dilated convolution. Order is {dilation dim 1, dilation dim 2, dilation dim 3}. * :param activation: activation function to apply to output. */ -void k2c_conv3d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* kernel, - const k2c_tensor* bias, const size_t * stride, const size_t * dilation, - k2c_activationType *activation) { +void k2c_conv3d(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, const size_t *stride, const size_t *dilation, + k2c_activationType *activation) +{ - memset(output->array,0,output->numel*sizeof(output->array[0])); + memset(output->array, 0, output->numel * sizeof(output->array[0])); const size_t dim1 = output->shape[0]; const size_t dim2 = output->shape[1]; const size_t dim3 = output->shape[2]; const size_t out_channels = output->shape[3]; const size_t in_channels = input->shape[3]; - for (size_t x0=0; x0 < dim1; ++x0) { - for (size_t x1=0; x1 < dim2; ++x1) { - for (size_t x2=0; x2shape[0]; ++z0) { - for (size_t z1=0; z1 < kernel->shape[1]; ++z1) { - for (size_t z2=0; z2 < kernel->shape[2]; ++z2) { - for (size_t q=0; q < in_channels; ++q) { - for (size_t k=0; k < out_channels; ++k) { - output->array[x0*(output->shape[3]*output->shape[2] - *output->shape[1]) - + x1*(output->shape[3]*output->shape[2]) - + x2*(output->shape[3]) + k] += - kernel->array[z0*(kernel->shape[4]*kernel->shape[3] - *kernel->shape[2]*kernel->shape[1]) - + z1*(kernel->shape[4]*kernel->shape[3] - *kernel->shape[2]) - + z2*(kernel->shape[4]*kernel->shape[3]) - + q*(kernel->shape[4]) + k] - *input->array[(x0*stride[0] + dilation[0]*z0) - *(input->shape[3]*input->shape[2]*input->shape[1]) - + (x1*stride[1] + dilation[1]*z1) - *(input->shape[3]*input->shape[2]) - + (x2*stride[2] + dilation[2]*z2) - *(input->shape[3]) + q]; + for (size_t x0 = 0; x0 < dim1; ++x0) + { + for (size_t x1 = 0; x1 < dim2; ++x1) + { + for (size_t x2 = 0; x2 < dim3; ++x2) + { + for (size_t z0 = 0; z0 < kernel->shape[0]; ++z0) + { + for (size_t z1 = 0; z1 < kernel->shape[1]; ++z1) + { + for (size_t z2 = 0; z2 < kernel->shape[2]; ++z2) + { + for (size_t q = 0; q < in_channels; ++q) + { + for (size_t k = 0; k < out_channels; ++k) + { + output->array[x0 * (output->shape[3] * output->shape[2] * output->shape[1]) + x1 * (output->shape[3] * output->shape[2]) + x2 * (output->shape[3]) + k] += + kernel->array[z0 * (kernel->shape[4] * kernel->shape[3] * kernel->shape[2] * kernel->shape[1]) + z1 * (kernel->shape[4] * kernel->shape[3] * kernel->shape[2]) + z2 * (kernel->shape[4] * kernel->shape[3]) + q * (kernel->shape[4]) + k] * input->array[(x0 * stride[0] + dilation[0] * z0) * (input->shape[3] * input->shape[2] * input->shape[1]) + (x1 * stride[1] + dilation[1] * z1) * (input->shape[3] * input->shape[2]) + (x2 * stride[2] + dilation[2] * z2) * (input->shape[3]) + q]; } } } @@ -276,11 +286,10 @@ void k2c_conv3d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* k } } } - k2c_bias_add(output,bias); - activation(output->array,output->numel); + k2c_bias_add(output, bias); + activation(output->array, output->numel); } - /** * 1D (temporal) Cropping. * @@ -288,14 +297,14 @@ void k2c_conv3d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* k * :param input: tensor to crop. * :param pad: array[2] of how many rows to crop. Order is {before dim 1, after dim 1}. */ -void k2c_crop1d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop) { +void k2c_crop1d(k2c_tensor *output, const k2c_tensor *input, const size_t *crop) +{ - const size_t offset = crop[0]*input->shape[1]; - memcpy(&output->array[0],&input->array[offset], - output->numel*sizeof(output->array[0])); + const size_t offset = crop[0] * input->shape[1]; + memcpy(&output->array[0], &input->array[offset], + output->numel * sizeof(output->array[0])); } - /** * 2D (spatial) Cropping. * @@ -303,7 +312,8 @@ void k2c_crop1d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop * :param input: tensor to crop. * :param pad: array[4] of how many rows/cols to crop. Order is {before dim 1, after dim 1, before dim 2, after dim 2}. */ -void k2c_crop2d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop) { +void k2c_crop2d(k2c_tensor *output, const k2c_tensor *input, const size_t *crop) +{ const size_t out_height = output->shape[0]; const size_t in_width = input->shape[1]; @@ -312,15 +322,15 @@ void k2c_crop2d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop const size_t crop_left = crop[2]; const size_t crop_right = crop[3]; - size_t offset = in_channels*in_width*crop_top + in_channels*crop_left; - const size_t num = in_channels*(in_width-crop_left-crop_right); - for (size_t i=0; iarray[i*num],&input->array[offset],num*sizeof(input->array[0])); - offset += in_width*in_channels; + size_t offset = in_channels * in_width * crop_top + in_channels * crop_left; + const size_t num = in_channels * (in_width - crop_left - crop_right); + for (size_t i = 0; i < out_height; ++i) + { + memcpy(&output->array[i * num], &input->array[offset], num * sizeof(input->array[0])); + offset += in_width * in_channels; } } - /** * 3D (spatial or spatio-temporal) Cropping. * @@ -328,7 +338,8 @@ void k2c_crop2d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop * :param input: tensor to crop. * :param pad: array[6] of how many rows/cols to crop. Order is {before dim 1, after dim 1, before dim 2, after dim 2, before dim 3, after dim 3}. */ -void k2c_crop3d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop) { +void k2c_crop3d(k2c_tensor *output, const k2c_tensor *input, const size_t *crop) +{ const size_t dim1 = input->shape[0]; const size_t dim2 = input->shape[1]; @@ -338,24 +349,25 @@ void k2c_crop3d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop const size_t outdim3 = dim3 - crop[4] - crop[5]; const size_t in_channels = input->shape[3]; - const size_t offset1 = in_channels*(dim2*dim3)*crop[0] + - in_channels*dim3*crop[2] + in_channels*crop[4]; - const size_t num = in_channels*outdim3; - const size_t instep2 = num+in_channels*(crop[4]+crop[5]); - const size_t instep1 = dim2*dim3*in_channels; - const size_t outstep1 = outdim2*outdim3*in_channels; - const size_t outstep2 = outdim3*in_channels; - - for (size_t i=0; iarray[i*outstep1 + j*outstep2], - &input->array[offset1+i*instep1+j*instep2], - num*sizeof(input->array[0])); + const size_t offset1 = in_channels * (dim2 * dim3) * crop[0] + + in_channels * dim3 * crop[2] + in_channels * crop[4]; + const size_t num = in_channels * outdim3; + const size_t instep2 = num + in_channels * (crop[4] + crop[5]); + const size_t instep1 = dim2 * dim3 * in_channels; + const size_t outstep1 = outdim2 * outdim3 * in_channels; + const size_t outstep2 = outdim3 * in_channels; + + for (size_t i = 0; i < outdim1; ++i) + { + for (size_t j = 0; j < outdim2; ++j) + { + memcpy(&output->array[i * outstep1 + j * outstep2], + &input->array[offset1 + i * instep1 + j * instep2], + num * sizeof(input->array[0])); } } } - /** * 1D (temporal) Upsampling. * Repeats each temporal step size times along the time axis. @@ -364,21 +376,24 @@ void k2c_crop3d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop * :param input: input tensor. * :param size: Upsampling factor. */ -void k2c_upsampling1d(k2c_tensor* output, const k2c_tensor* input, const size_t size) { +void k2c_upsampling1d(k2c_tensor *output, const k2c_tensor *input, const size_t size) +{ const size_t in_height = input->shape[0]; const size_t in_width = input->shape[1]; - for (size_t i=0; iarray[(size*i+j)*in_width + k] = input->array[i*in_width+k]; + for (size_t i = 0; i < in_height; ++i) + { + for (size_t j = 0; j < size; ++j) + { + for (size_t k = 0; k < in_width; ++k) + { + output->array[(size * i + j) * in_width + k] = input->array[i * in_width + k]; } } } } - /** * 2D (spatial) Upsampling. * Repeats the rows and columns of the data by size[0] and size[1] respectively. @@ -387,24 +402,26 @@ void k2c_upsampling1d(k2c_tensor* output, const k2c_tensor* input, const size_t * :param input: input tensor. * :param size: array[2] of upsampling factors. Order is {upsampling dim 1, upsampling dim 2}. */ -void k2c_upsampling2d(k2c_tensor* output, const k2c_tensor* input, const size_t * size) { +void k2c_upsampling2d(k2c_tensor *output, const k2c_tensor *input, const size_t *size) +{ const size_t out_height = output->shape[0]; const size_t out_width = output->shape[1]; const size_t channels = input->shape[2]; - for (size_t i=0; iarray[k2c_sub2idx(outsub,output->shape,output->ndim)], - &input->array[k2c_sub2idx(insub,input->shape,input->ndim)], - channels*sizeof(input->array[0])); + for (size_t i = 0; i < out_height; ++i) + { + for (size_t j = 0; j < out_width; ++j) + { + const size_t insub[K2C_MAX_NDIM] = {i / size[0], j / size[1], 0}; + const size_t outsub[K2C_MAX_NDIM] = {i, j, 0}; + memcpy(&output->array[k2c_sub2idx(outsub, output->shape, output->ndim)], + &input->array[k2c_sub2idx(insub, input->shape, input->ndim)], + channels * sizeof(input->array[0])); } } } - /** * 2D (spatial) Upsampling. * Repeats the 1st, 2nd and 3rd dimensions of the data by size[0], size[1] and size[2] respectively. @@ -413,21 +430,25 @@ void k2c_upsampling2d(k2c_tensor* output, const k2c_tensor* input, const size_t * :param input: input tensor. * :param size: array[3] of upsampling factors. Order is {upsampling dim 1, upsampling dim 2, upsampling dim 3}. */ -void k2c_upsampling3d(k2c_tensor* output, const k2c_tensor* input, const size_t * size) { +void k2c_upsampling3d(k2c_tensor *output, const k2c_tensor *input, const size_t *size) +{ const size_t dim1 = output->shape[0]; const size_t dim2 = output->shape[1]; const size_t dim3 = output->shape[2]; const size_t channels = input->shape[3]; - for (size_t i=0; iarray[k2c_sub2idx(outsub,output->shape,output->ndim)], - &input->array[k2c_sub2idx(insub,input->shape,input->ndim)], - channels*sizeof(input->array[0])); + for (size_t i = 0; i < dim1; ++i) + { + for (size_t j = 0; j < dim2; ++j) + { + for (size_t k = 0; k < dim3; ++k) + { + const size_t insub[K2C_MAX_NDIM] = {i / size[0], j / size[1], k / size[2], 0}; + const size_t outsub[K2C_MAX_NDIM] = {i, j, k, 0}; + memcpy(&output->array[k2c_sub2idx(outsub, output->shape, output->ndim)], + &input->array[k2c_sub2idx(insub, input->shape, input->ndim)], + channels * sizeof(input->array[0])); } } } diff --git a/include/k2c_include.h b/include/k2c_include.h index 24347af..c30ce0d 100644 --- a/include/k2c_include.h +++ b/include/k2c_include.h @@ -2,8 +2,10 @@ k2c_include.h This file is part of keras2c Copyright 2020 Rory Conlin -Licensed under MIT License +Licensed under LGPLv3 License https://github.com/f0uriest/keras2c + +Modified by Anchal Gupta */ #pragma once @@ -11,137 +13,142 @@ Licensed under MIT License #include #include "k2c_tensor_include.h" - // Activations -void k2c_linear_func(float * x, const size_t size); -void k2c_exponential_func(float * x, const size_t size); -void k2c_relu_func(float * x, const size_t size); -void k2c_hard_sigmoid_func(float * x, const size_t size); -void k2c_tanh_func(float * x, const size_t size); -void k2c_sigmoid_func(float * x, const size_t size); -void k2c_softmax_func(float * x, const size_t size); -void k2c_softplus_func(float * x, const size_t size); -void k2c_softsign_func(float * x, const size_t size); -typedef void k2c_activationType(float * x, const size_t size); -extern k2c_activationType * k2c_linear; -extern k2c_activationType * k2c_exponential; -extern k2c_activationType * k2c_relu; -extern k2c_activationType * k2c_hard_sigmoid; -extern k2c_activationType * k2c_tanh; -extern k2c_activationType * k2c_sigmoid; -extern k2c_activationType * k2c_softmax; -extern k2c_activationType * k2c_softplus; -extern k2c_activationType * k2c_softsign; +void k2c_linear_func(float *x, const size_t size); +void k2c_exponential_func(float *x, const size_t size); +void k2c_relu_func(float *x, const size_t size); +void k2c_hard_sigmoid_func(float *x, const size_t size); +void k2c_tanh_func(float *x, const size_t size); +void k2c_sigmoid_func(float *x, const size_t size); +void k2c_softmax_func(float *x, const size_t size); +void k2c_softplus_func(float *x, const size_t size); +void k2c_softsign_func(float *x, const size_t size); +typedef void k2c_activationType(float *x, const size_t size); +extern k2c_activationType *k2c_linear; +extern k2c_activationType *k2c_exponential; +extern k2c_activationType *k2c_relu; +extern k2c_activationType *k2c_swish; +extern k2c_activationType *k2c_hard_sigmoid; +extern k2c_activationType *k2c_tanh; +extern k2c_activationType *k2c_sigmoid; +extern k2c_activationType *k2c_softmax; +extern k2c_activationType *k2c_softplus; +extern k2c_activationType *k2c_softsign; // Advanced Activations -void k2c_LeakyReLU(float * x, const size_t size, const float alpha); -void k2c_PReLU(float * x, const size_t size, const float * alpha); -void k2c_ELU(float * x, const size_t size, const float alpha); -void k2c_ThresholdedReLU(float * x, const size_t size, const float theta); -void k2c_ReLU(float * x, const size_t size, const float max_value, const float negative_slope, +void k2c_LeakyReLU(float *x, const size_t size, const float alpha); +void k2c_PReLU(float *x, const size_t size, const float *alpha); +void k2c_ELU(float *x, const size_t size, const float alpha); +void k2c_ThresholdedReLU(float *x, const size_t size, const float theta); +void k2c_ReLU(float *x, const size_t size, const float max_value, const float negative_slope, const float threshold); // Convolutions -void k2c_pad1d(k2c_tensor* output, const k2c_tensor* input, const float fill, - const size_t * pad); -void k2c_pad2d(k2c_tensor* output, const k2c_tensor* input, const float fill, - const size_t * pad); -void k2c_pad3d(k2c_tensor* output, const k2c_tensor* input, const float fill, - const size_t * pad); -void k2c_conv1d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* kernel, - const k2c_tensor* bias, const size_t stride, const size_t dilation, +void k2c_pad1d(k2c_tensor *output, const k2c_tensor *input, const float fill, + const size_t *pad); +void k2c_pad2d(k2c_tensor *output, const k2c_tensor *input, const float fill, + const size_t *pad); +void k2c_pad3d(k2c_tensor *output, const k2c_tensor *input, const float fill, + const size_t *pad); +void k2c_conv1d(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, const size_t stride, const size_t dilation, k2c_activationType *activation); -void k2c_conv2d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* kernel, - const k2c_tensor* bias, const size_t * stride, const size_t * dilation, +void k2c_conv2d(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, const size_t *stride, const size_t *dilation, k2c_activationType *activation); -void k2c_conv3d(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* kernel, - const k2c_tensor* bias, const size_t * stride, const size_t * dilation, +void k2c_conv3d(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, const size_t *stride, const size_t *dilation, k2c_activationType *activation); -void k2c_crop1d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop); -void k2c_crop2d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop); -void k2c_crop3d(k2c_tensor* output, const k2c_tensor* input, const size_t * crop); -void k2c_upsampling1d(k2c_tensor* output, const k2c_tensor* input, const size_t size); -void k2c_upsampling2d(k2c_tensor* output, const k2c_tensor* input, const size_t * size); -void k2c_upsampling3d(k2c_tensor* output, const k2c_tensor* input, const size_t * size); +void k2c_crop1d(k2c_tensor *output, const k2c_tensor *input, const size_t *crop); +void k2c_crop2d(k2c_tensor *output, const k2c_tensor *input, const size_t *crop); +void k2c_crop3d(k2c_tensor *output, const k2c_tensor *input, const size_t *crop); +void k2c_upsampling1d(k2c_tensor *output, const k2c_tensor *input, const size_t size); +void k2c_upsampling2d(k2c_tensor *output, const k2c_tensor *input, const size_t *size); +void k2c_upsampling3d(k2c_tensor *output, const k2c_tensor *input, const size_t *size); +void k2c_conv1d_transpose(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, const size_t stride, const size_t start_crop, + k2c_activationType *activation); // Core Layers -void k2c_dense(k2c_tensor* output, const k2c_tensor* input, const k2c_tensor* kernel, - const k2c_tensor* bias, k2c_activationType *activation, float * fwork); -void k2c_flatten(k2c_tensor *output, const k2c_tensor* input); -void k2c_reshape(k2c_tensor *output, const k2c_tensor* input, const size_t * newshp, +void k2c_dense(k2c_tensor *output, const k2c_tensor *input, const k2c_tensor *kernel, + const k2c_tensor *bias, k2c_activationType *activation, float *fwork); +void k2c_flatten(k2c_tensor *output, const k2c_tensor *input); +void k2c_reshape(k2c_tensor *output, const k2c_tensor *input, const size_t *newshp, const size_t newndim); -void k2c_permute_dims(k2c_tensor* output, const k2c_tensor* input, - const size_t * permute); -void k2c_repeat_vector(k2c_tensor* output, const k2c_tensor* input, const size_t n); +void k2c_permute_dims(k2c_tensor *output, const k2c_tensor *input, + const size_t *permute); +void k2c_repeat_vector(k2c_tensor *output, const k2c_tensor *input, const size_t n); // Embedding -void k2c_embedding(k2c_tensor* outputs, const k2c_tensor* inputs, const k2c_tensor* kernel); +void k2c_embedding(k2c_tensor *outputs, const k2c_tensor *inputs, const k2c_tensor *kernel); // Helper functions -void k2c_matmul(float * C, const float * A, const float * B, const size_t outrows, +void k2c_matmul(float *C, const float *A, const float *B, const size_t outrows, const size_t outcols, const size_t innerdim); -void k2c_affine_matmul(float * C, const float * A, const float * B, const float * d, - const size_t outrows,const size_t outcols, const size_t innerdim); -size_t k2c_sub2idx(const size_t * sub, const size_t * shape, const size_t ndim); -void k2c_idx2sub(const size_t idx, size_t * sub, const size_t * shape, const size_t ndim); -void k2c_dot(k2c_tensor* C, const k2c_tensor* A, const k2c_tensor* B, const size_t * axesA, - const size_t * axesB, const size_t naxes, const int normalize, float * fwork); -void k2c_bias_add(k2c_tensor* A, const k2c_tensor* b); +void k2c_affine_matmul(float *C, const float *A, const float *B, const float *d, + const size_t outrows, const size_t outcols, const size_t innerdim); +size_t k2c_sub2idx(const size_t *sub, const size_t *shape, const size_t ndim); +void k2c_idx2sub(const size_t idx, size_t *sub, const size_t *shape, const size_t ndim); +void k2c_dot(k2c_tensor *C, const k2c_tensor *A, const k2c_tensor *B, const size_t *axesA, + const size_t *axesB, const size_t naxes, const int normalize, float *fwork); +void k2c_bias_add(k2c_tensor *A, const k2c_tensor *b); void k2c_flip(k2c_tensor *A, const size_t axis); -float* k2c_read_array(const char* filename, const size_t array_size); +float *k2c_read_array(const char *filename, const size_t array_size); // Merge layers -void k2c_add(k2c_tensor* output, const size_t num_tensors,...); -void k2c_subtract(k2c_tensor* output, const size_t num_tensors, - const k2c_tensor* tensor1, const k2c_tensor* tensor2); -void k2c_multiply(k2c_tensor* output, const size_t num_tensors,...); -void k2c_average(k2c_tensor* output, const size_t num_tensors,...); -void k2c_max(k2c_tensor* output, const size_t num_tensors,...); -void k2c_min(k2c_tensor* output, const size_t num_tensors,...); -void k2c_concatenate(k2c_tensor* output, const size_t axis, const size_t num_tensors,...); +void k2c_add(k2c_tensor *output, const size_t num_tensors, ...); +void k2c_subtract(k2c_tensor *output, const size_t num_tensors, + const k2c_tensor *tensor1, const k2c_tensor *tensor2); +void k2c_multiply(k2c_tensor *output, const size_t num_tensors, ...); +void k2c_average(k2c_tensor *output, const size_t num_tensors, ...); +void k2c_max(k2c_tensor *output, const size_t num_tensors, ...); +void k2c_min(k2c_tensor *output, const size_t num_tensors, ...); +void k2c_concatenate(k2c_tensor *output, const size_t axis, const size_t num_tensors, ...); + +// Split layers +void k2c_split(k2c_tensor *output, k2c_tensor *input, size_t offset); // Normalization layers -void k2c_batch_norm(k2c_tensor* outputs, const k2c_tensor* inputs, const k2c_tensor* mean, - const k2c_tensor* stdev, const k2c_tensor* gamma, const k2c_tensor* beta, +void k2c_batch_norm(k2c_tensor *outputs, const k2c_tensor *inputs, const k2c_tensor *mean, + const k2c_tensor *stdev, const k2c_tensor *gamma, const k2c_tensor *beta, const size_t axis); // Pooling layers -void k2c_global_max_pooling(k2c_tensor* output, const k2c_tensor* input); -void k2c_global_avg_pooling(k2c_tensor* output, const k2c_tensor* input); -void k2c_maxpool1d(k2c_tensor* output, const k2c_tensor* input, const size_t pool_size, +void k2c_global_max_pooling(k2c_tensor *output, const k2c_tensor *input); +void k2c_global_avg_pooling(k2c_tensor *output, const k2c_tensor *input); +void k2c_maxpool1d(k2c_tensor *output, const k2c_tensor *input, const size_t pool_size, const size_t stride); -void k2c_maxpool2d(k2c_tensor* output, const k2c_tensor* input, const size_t * pool_size, - const size_t * stride); -void k2c_avgpool1d(k2c_tensor* output, const k2c_tensor* input, const size_t pool_size, +void k2c_maxpool2d(k2c_tensor *output, const k2c_tensor *input, const size_t *pool_size, + const size_t *stride); +void k2c_avgpool1d(k2c_tensor *output, const k2c_tensor *input, const size_t pool_size, const size_t stride); -void k2c_avgpool2d(k2c_tensor* output, const k2c_tensor* input, const size_t * pool_size, - const size_t * stride); +void k2c_avgpool2d(k2c_tensor *output, const k2c_tensor *input, const size_t *pool_size, + const size_t *stride); // Recurrent layers -void k2c_lstmcell(float * state, const float * input, const k2c_tensor* kernel, - const k2c_tensor* recurrent_kernel, const k2c_tensor* bias, float * fwork, +void k2c_lstmcell(float *state, const float *input, const k2c_tensor *kernel, + const k2c_tensor *recurrent_kernel, const k2c_tensor *bias, float *fwork, k2c_activationType *recurrent_activation, k2c_activationType *output_activation); -void k2c_lstm(k2c_tensor* output, const k2c_tensor* input, float * state, - const k2c_tensor* kernel, const k2c_tensor* recurrent_kernel, - const k2c_tensor* bias, float * fwork, const int go_backwards, +void k2c_lstm(k2c_tensor *output, const k2c_tensor *input, float *state, + const k2c_tensor *kernel, const k2c_tensor *recurrent_kernel, + const k2c_tensor *bias, float *fwork, const int go_backwards, const int return_sequences, k2c_activationType *recurrent_activation, k2c_activationType *output_activation); -void k2c_simpleRNNcell(float * state, const float * input, const k2c_tensor* kernel, - const k2c_tensor* recurrent_kernel, const k2c_tensor* bias, - float * fwork, k2c_activationType *output_activation); -void k2c_simpleRNN(k2c_tensor* output, const k2c_tensor* input, float * state, - const k2c_tensor* kernel, const k2c_tensor* recurrent_kernel, - const k2c_tensor* bias, float * fwork, const int go_backwards, +void k2c_simpleRNNcell(float *state, const float *input, const k2c_tensor *kernel, + const k2c_tensor *recurrent_kernel, const k2c_tensor *bias, + float *fwork, k2c_activationType *output_activation); +void k2c_simpleRNN(k2c_tensor *output, const k2c_tensor *input, float *state, + const k2c_tensor *kernel, const k2c_tensor *recurrent_kernel, + const k2c_tensor *bias, float *fwork, const int go_backwards, const int return_sequences, k2c_activationType *output_activation); -void k2c_grucell(float * state, const float * input, const k2c_tensor* kernel, - const k2c_tensor* recurrent_kernel, const k2c_tensor* bias, float * fwork, +void k2c_grucell(float *state, const float *input, const k2c_tensor *kernel, + const k2c_tensor *recurrent_kernel, const k2c_tensor *bias, float *fwork, const int reset_after, k2c_activationType *recurrent_activation, k2c_activationType *output_activation); -void k2c_gru(k2c_tensor* output, const k2c_tensor* input, float * state, - const k2c_tensor* kernel, const k2c_tensor* recurrent_kernel, - const k2c_tensor* bias, float * fwork, const int reset_after, +void k2c_gru(k2c_tensor *output, const k2c_tensor *input, float *state, + const k2c_tensor *kernel, const k2c_tensor *recurrent_kernel, + const k2c_tensor *bias, float *fwork, const int reset_after, const int go_backwards, const int return_sequences, k2c_activationType *recurrent_activation, k2c_activationType *output_activation); - diff --git a/include/k2c_split_layers.c b/include/k2c_split_layers.c new file mode 100644 index 0000000..de98380 --- /dev/null +++ b/include/k2c_split_layers.c @@ -0,0 +1,11 @@ + #include +#include "k2c_include.h" + +/* +Split input tensor into one output tensor +*/ + +void k2c_split(k2c_tensor *output, k2c_tensor *input, size_t offset) +{ + memcpy(&output->array[0], &input->array[offset], output->numel * sizeof(output->array[0])); +} diff --git a/include/makefile b/include/makefile index 82e9cd5..d285ff0 100644 --- a/include/makefile +++ b/include/makefile @@ -19,13 +19,15 @@ endif OBJ = \ k2c_activations.o \ k2c_convolution_layers.o \ + k2c_conv_transpose_layer.o \ k2c_core_layers.o \ k2c_embedding_layers.o \ k2c_helper_functions.o \ k2c_merge_layers.o \ k2c_normalization_layers.o \ k2c_pooling_layers.o \ - k2c_recurrent_layers.o + k2c_recurrent_layers.o \ + k2c_split_layers.o \ DEPS = \ k2c_include.h \ diff --git a/keras2c/check_model.py b/keras2c/check_model.py index 3845bc8..16452e8 100644 --- a/keras2c/check_model.py +++ b/keras2c/check_model.py @@ -1,7 +1,7 @@ """check_model.py This file is part of keras2c Copyright 2020 Rory Conlin -Licensed under MIT License +Licensed under LGPLv3 License https://github.com/f0uriest/keras2c Checks a model before conversion to flag unsupported features @@ -12,8 +12,7 @@ from keras2c.io_parsing import layer_type, flatten from keras2c.weights2c import Weights2C from keras2c.layer2c import Layers2C -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -54,7 +53,7 @@ def name_check(model): valid = True log = '' for layer in model.layers: - if not is_valid_c_name(layer.name): + if not is_valid_c_name(layer.name.replace('.', '_')): valid = False log += "layer name '" + layer.name + "' is not a valid C name. \n" return valid, log @@ -105,7 +104,7 @@ def activation_supported_check(model): """ supported_activations = ['linear', 'relu', 'softmax', 'softplus', - 'softsign', 'relu', 'tanh', 'sigmoid', + 'softsign', 'relu', 'tanh', 'sigmoid', 'swish', 'hard_sigmoid', 'exponential'] def check_layer(layer): @@ -176,11 +175,19 @@ def check_layer(layer): "' is not supported at this time. \n" if config.get('shared_axes'): valid = False - log += "shared axes option for layer '" + layer.name + \ - "' is not supported at this time. \n" - if layer_type(layer) in ['Add', 'Subtract', 'Multiply', 'Average', - 'Maximum', 'Minimum']: - inshps = layer.input_shape + log += ( + "shared axes option for layer '" + + layer.name + + "' is not supported at this time. \n" + ) + if layer_type(layer) in ["Add", "Subtract", "Multiply", "Average", "Maximum", + "Minimum",]: + inshps = [] + # Collect shapes from all inbound nodes + for node in getattr(layer, "_inbound_nodes", []): + for tensor in node.input_tensors: + inshps.append(tensor.shape) + insize = [np.prod(inp[1:]) for inp in inshps] if len(set(insize)) > 1: valid = False diff --git a/keras2c/io_parsing.py b/keras2c/io_parsing.py index c7defb2..dfd6451 100644 --- a/keras2c/io_parsing.py +++ b/keras2c/io_parsing.py @@ -1,18 +1,22 @@ """io_parsing.py This file is part of keras2c Copyright 2020 Rory Conlin -Licensed under MIT License +Licensed under LGPLv3 License https://github.com/f0uriest/keras2c Helper functions to get input and output names for each layer etc. """ -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" def layer_type(layer): """Gets the type of a layer @@ -40,6 +44,17 @@ def get_all_io_names(model): a = [get_layer_io_names(layer) for layer in model.layers] return list(set(flatten(a))) +def parse_io_name(name): + name = name.replace('.', '_') + skip_start = name.find('/') + skip_end = name.rfind(':') + out_str = name + if skip_start != -1: + out_str = name[:skip_start] + if skip_end != -1: + out_str += '_' + name[skip_end+1:] + out_str = out_str.replace(':', '_').replace('/', '_') + return out_str def get_layer_num_io(layer): """Gets the number of inputs and outputs for a layer @@ -52,23 +67,25 @@ def get_layer_num_io(layer): num_outputs (int): number of output nodes from the layer """ - num_inputs = 0 - error = False - while not error: - try: - layer.get_input_at(num_inputs) - num_inputs += 1 - except ValueError: - error = True - - num_outputs = 0 - error = False - while not error: - try: - layer.get_output_at(num_outputs) - num_outputs += 1 - except ValueError: - error = True + if hasattr(layer, "inputs"): + if isinstance(layer.inputs, list): + num_inputs = len(layer.inputs) + else: + num_inputs = 1 + else: + # fallback: count inbound nodes + num_inputs = len(getattr(layer, "_inbound_nodes", [])) + + # If outputs attribute is present, count actual tensor outputs + if hasattr(layer, "outputs"): + outs = layer.outputs + if isinstance(outs, list): + num_outputs = len(outs) + else: + num_outputs = 1 + else: + # Fallback: count graph nodes that produce outputs + num_outputs = len(getattr(layer, "_inbound_nodes", [])) return num_inputs, num_outputs @@ -83,41 +100,38 @@ def get_layer_io_names(layer): outputs (list): names of all the output nodes from the layer """ - num_inputs, num_outputs = get_layer_num_io(layer) + num_nodes = len(getattr(layer, "_inbound_nodes", [])) + inputs = [] - # num_inputs>1 -> shared layer - for i in range(num_inputs): + outputs = [] + + for node_index in range(num_nodes): + node = layer._inbound_nodes[node_index] # is the input a list? - if isinstance(layer.get_input_at(i), list): - temp_list = [] - list_length = len(layer.get_input_at(i)) - for j in range(list_length): - name = layer.get_input_at(i)[j].name.split(':')[ - 0].split('/')[0] - temp_list.append(name) - inputs.insert(i, temp_list) + node_inputs = node.input_tensors + if node_inputs is None: + inputs.append([]) else: - name = layer.get_input_at(i).name.split(':')[0].split('/')[0] - inputs.insert(i, name) + if isinstance(node_inputs, (list, tuple)): + if len(node_inputs) == 1: + inputs.append(parse_io_name(node_inputs[0].name)) + else: + inputs.append([parse_io_name(t.name) for t in node_inputs]) + else: + # single tensor + inputs.append(parse_io_name(node_inputs.name)) - outputs = [] - for i in range(num_outputs): - # is the output a list? - if isinstance(layer.get_output_at(i), list): - temp_list = [] - list_length = len(layer.get_output_at(i)) - for j in range(list_length): - name = layer.get_output_at(i)[j].name.split(':')[ - 0].split('/')[0] - temp_list.append(name) - outputs.insert(i, temp_list) + node_outputs = getattr(node, "output_tensors", None) + if node_outputs is None: + outputs.append([]) else: - name = layer.get_output_at(i).name - if 'bidirectional' in name.lower(): - name = name.split('/')[-2] + if isinstance(node_outputs, (list, tuple)): + if len(node_outputs) == 1: + outputs.append(parse_io_name(node_outputs[0].name)) + else: + outputs.append([parse_io_name(t.name) for t in node_outputs]) else: - name = name.split('/')[0] - outputs.insert(i, name) + outputs.append(parse_io_name(node_outputs.name)) return inputs, outputs @@ -138,10 +152,10 @@ def get_model_io_names(model): inputs = [] outputs = [] for i in range(num_inputs): - nm = model.inputs[i].name.split(':')[0].split('/')[0] + nm = parse_io_name(model.inputs[i].name) inputs.append(nm) for i in range(num_outputs): - nm = model.outputs[i].name.split(':')[0].split('/')[0] + nm = parse_io_name(model.outputs[i].name) outputs.append(nm) return inputs, outputs diff --git a/keras2c/keras2c_main.py b/keras2c/keras2c_main.py index ace0245..f4d25aa 100644 --- a/keras2c/keras2c_main.py +++ b/keras2c/keras2c_main.py @@ -1,7 +1,7 @@ """keras2c_main.py This file is part of keras2c Copyright 2020 Rory Conlin -Licensed under MIT License +Licensed under LGPLv3 License https://github.com/f0uriest/keras2c Converts keras model to C code @@ -16,22 +16,25 @@ from keras2c.make_test_suite import make_test_suite import numpy as np import subprocess -import tensorflow.keras as keras -import tensorflow as tf -tf.compat.v1.disable_eager_execution() +import keras -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" + +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" def model2c(model, function_name, malloc=False, verbose=True): """Generates C code for model - Writes main function definition to "function_name.c" and a public header + Writes main function definition to "function_name.c" and a public header with declarations to "function_name.h" Args: @@ -196,7 +199,7 @@ def k2c(model, function_name, malloc=False, num_tests=10, verbose=True): verbose (bool): whether to print progress Raises: - ValueError: if model is not instance of keras.models.Model + ValueError: if model is not instance of keras.models.Model Returns: None diff --git a/keras2c/layer2c.py b/keras2c/layer2c.py index 79e1985..390cae8 100644 --- a/keras2c/layer2c.py +++ b/keras2c/layer2c.py @@ -1,7 +1,7 @@ """layer2c.py This file is part of keras2c Copyright 2020 Rory Conlin -Licensed under MIT License +Licensed under LGPLv3 License https://github.com/f0uriest/keras2c Writes individual layers to C code @@ -9,15 +9,18 @@ # imports from keras2c.io_parsing import layer_type, get_model_io_names, get_all_io_names, get_layer_io_names, flatten -import tensorflow as tf -tf.compat.v1.disable_eager_execution() -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" + +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" class Layers2C(): @@ -31,72 +34,132 @@ class Layers2C(): def __init__(self, model, malloc): self.model = model self.model_inputs, self.model_outputs = get_model_io_names(self.model) - self.layers = '' + self.layers = "" self.malloc = malloc def write_layers(self, verbose=True): - """Writes layers in the correct graph order. - - Args: - verbose (bool): whether to print progress - - Returns: - layers (str): C code for calling layer functions in correct order - - """ + """Writes layers in the correct graph order.""" written_io = set(self.model_inputs) - unwritten_io = set(get_all_io_names(self.model)) - written_io + all_io = set(flatten(get_all_io_names(self.model))) + unwritten_io = all_io - written_io + while len(unwritten_io) > 0: + progress = False + for layer in self.model.layers: layer_inputs, layer_outputs = get_layer_io_names(layer) - for i, (inp, outp) in enumerate(zip(layer_inputs, layer_outputs)): - if (set(flatten(inp)).issubset(written_io) and - set(flatten(outp)).issubset(unwritten_io))or \ - layer_type(layer) == 'InputLayer': - if verbose: - print('Writing layer ', outp) - method = getattr( - self, '_write_layer_' + layer_type(layer)) - method(layer, inp, outp, i) - written_io |= set(flatten(inp)) - written_io |= set(flatten(outp)) - unwritten_io -= set(flatten(inp)) - unwritten_io -= set(flatten(outp)) + + # Check if this layer has multiple nodes (shared layer) + if isinstance(layer_inputs, list) and len(layer_inputs) > 0: + # Process each node separately + for i, (inp, outp) in enumerate(zip(layer_inputs, layer_outputs)): + # Handle nested lists from merge layers + if isinstance(inp, list): + flat_inputs = set(flatten([inp])) + else: + flat_inputs = set([inp]) if inp else set() + + if isinstance(outp, list): + flat_outputs = set(flatten([outp])) + else: + flat_outputs = set([outp]) if outp else set() + + if flat_inputs.issubset(written_io) and ( + flat_outputs & unwritten_io + ): + if verbose: + print( + f"Writing layer {layer.name} node {i}: {inp} -> {outp}" + ) + + # dispatch to the proper layer writer + method = getattr(self, "_write_layer_" + layer_type(layer)) + method( + layer, inp, outp, i + ) # Pass node index as last parameter + + # mark inputs and outputs as written + written_io |= flat_inputs + written_io |= flat_outputs + unwritten_io -= flat_outputs + + progress = True + else: + # Handle layers with no nodes (shouldn't happen but just in case) + if verbose: + print(f"Warning: Layer {layer.name} has no nodes") + + if not progress: + raise RuntimeError( + "Could not write any layer in this iteration. " + "Check for cycles or disconnected layers in the model." + ) + return self.layers def _format_io_names(self, layer, inp, outp, model_io=False): - nm = layer.name - pnm = '&' + nm + """ + Normalize and format input/output tensor names for code generation. + + Works with Keras 3 (where inp/outp can be strings, lists, or nested lists). + + Returns: + nm, pnm, inp_nm, outp_nm + (+ is_model_input, is_model_output if model_io=True) + """ + + def ensure_list(x): + """Wrap strings or non-iterables into a list.""" + if isinstance(x, str): + return [x] + if isinstance(x, (list, tuple)): + return list(x) + return [x] + + def flatten_any(x): + """Recursively flatten lists/tuples.""" + if isinstance(x, (list, tuple)): + res = [] + for xi in x: + res.extend(flatten_any(xi)) + return res + else: + return [x] + + nm = layer.name.replace(".", "_") + pnm = "&" + nm + + # Normalize inputs / outputs to flat lists of strings + in_list = flatten_any(ensure_list(inp)) + out_list = flatten_any(ensure_list(outp)) + + inp_nm = [] + outp_nm = [] is_model_input = False is_model_output = False - if isinstance(inp, list): - inp_nm = [] - for j in inp: - if j in self.model_inputs or 'timeslice' in j: - inp_nm.append(j + '_input') - is_model_input = True - else: - inp_nm.append('&' + j + '_output') - else: - if inp in self.model_inputs or 'timeslice' in inp: - inp_nm = inp + '_input' + + # --- Format inputs --- + for j in in_list: + if j in self.model_inputs or "timeslice" in j: + inp_nm.append(j + "_input") is_model_input = True else: - inp_nm = '&' + inp + '_output' - if isinstance(outp, list): - outp_nm = [] - for o in outp: - if o in self.model_outputs or 'timeslice' in o: - outp_nm.append(o + '_output') - is_model_output = True - else: - outp_nm.append('&' + outp + '_output') - else: - if outp in self.model_outputs or 'timeslice' in outp: - outp_nm = outp + '_output' + inp_nm.append("&" + j + "_output") + + # --- Format outputs --- + for o in out_list: + if o in self.model_outputs or "timeslice" in o: + outp_nm.append(o + "_output") is_model_output = True else: - outp_nm = '&' + outp + '_output' + outp_nm.append("&" + o + "_output") + + # Collapse back to string if only one element (for consistency with old behavior) + if len(inp_nm) == 1: + inp_nm = inp_nm[0] + if len(outp_nm) == 1: + outp_nm = outp_nm[0] + if model_io: return nm, pnm, inp_nm, outp_nm, is_model_input, is_model_output else: @@ -126,25 +189,24 @@ def _write_layer_TimeDistributed(self, layer, inputs, outputs, i): self.layers += '\n } \n' def _write_layer_Bidirectional(self, layer, inputs, outputs, i): - subname = layer.layer.name - method = getattr(self, '_write_layer_' + layer_type(layer.layer)) - method(layer.forward_layer, inputs, - 'forward_' + subname, i) - method(layer.backward_layer, inputs, - 'backward_' + subname, i) + subname = layer.forward_layer.name + method = getattr(self, '_write_layer_' + layer_type(layer.forward_layer)) + method(layer.forward_layer, inputs, subname, i) + subname = layer.backward_layer.name + method = getattr(self, '_write_layer_' + layer_type(layer.backward_layer)) + method(layer.backward_layer, inputs, subname, i) mode = layer.merge_mode - inputs = ['forward_' + subname, - 'backward_' + subname] - if layer.layer.return_sequences: - self.layers += 'k2c_flip(&backward_' + subname + '_output,0); \n' + inputs = [layer.forward_layer.name, layer.backward_layer.name] + if layer.return_sequences: + self.layers += 'k2c_flip(&' + subname + '_output,0); \n' if mode == 'sum': - self._write_layer_Merge(layer, inputs, outputs, i, 'Add') + self._write_layer_Merge(layer, inputs, outputs, 0, 'Add') elif mode == 'mul': - self._write_layer_Merge(layer, inputs, outputs, i, 'Multiply') + self._write_layer_Merge(layer, inputs, outputs, 0, 'Multiply') elif mode == 'ave': - self._write_layer_Merge(layer, inputs, outputs, i, 'Average') + self._write_layer_Merge(layer, inputs, outputs, 0, 'Average') elif mode == 'concat': - self._write_layer_Concatenate(layer, inputs, outputs, i) + self._write_layer_Concatenate(layer, inputs, outputs, 0) def _write_layer_LSTM(self, layer, inputs, outputs, i): nm, pnm, inputs, outputs = self._format_io_names( @@ -186,8 +248,7 @@ def _write_layer_Conv(self, layer, inputs, outputs, i): '_padded_input', i) self.layers += fname + outputs + ',' + pnm + \ '_padded_input,' + pnm + '_kernel, \n\t' + \ - pnm + '_bias,' + nm + '_stride,' + nm + \ - '_dilation,' + activation + '); \n' + pnm + '_bias,' + nm + '_stride,' + nm + '_dilation,' + activation + '); \n' def _write_layer_Conv1D(self, layer, inputs, outputs, i): self._write_layer_Conv(layer, inputs, outputs, i) @@ -198,6 +259,16 @@ def _write_layer_Conv2D(self, layer, inputs, outputs, i): def _write_layer_Conv3D(self, layer, inputs, outputs, i): self._write_layer_Conv(layer, inputs, outputs, i) + def _write_layer_Conv1DTranspose(self, layer, inputs, outputs, i): + nm, pnm, inputs, outputs = self._format_io_names( + layer, inputs, outputs) + activation = 'k2c_' + layer.get_config()['activation'] + + # Write the conv1d_transpose layer + self.layers += 'k2c_conv1d_transpose(' + outputs + ',' + inputs + ',' + \ + pnm + '_kernel, \n\t' + pnm + '_bias,' + nm + '_stride,' + \ + nm + '_start_crop,' + activation + '); \n' + def _write_layer_MaxPooling1D(self, layer, inputs, outputs, i): self._write_layer_Pooling(layer, inputs, outputs, i) @@ -573,3 +644,18 @@ def _write_layer_Input(self, layer, inputs, outputs, i): def _write_layer_InputLayer(self, layer, inputs, outputs, i): self.layers += '' + + def _write_layer_TFOpLambda(self, layer, inputs, outputs, i): + self._write_layer_TensorFlowOpLayer(layer, inputs, outputs, i) + + def _write_layer_TensorFlowOpLayer(self, layer, inputs, outputs, i): + if 'split' in layer.name: + _, _, inputs, outputs = self._format_io_names( + layer, inputs, outputs) + offset = 0 + for j, outp in enumerate(outputs): + self.layers += 'k2c_split(' + outp + ',' + inputs + ',' + str(offset) + '); \n' + offset += layer.get_output_at(i)[j].shape[-1] + else: + raise AssertionError('Unsupported TensorFlowOpLayer: ' + layer.name + '\n' + + 'Currently only split operation is supported.') diff --git a/keras2c/make_test_suite.py b/keras2c/make_test_suite.py index 9b6904e..627dd92 100644 --- a/keras2c/make_test_suite.py +++ b/keras2c/make_test_suite.py @@ -1,7 +1,7 @@ """make_test_suite.py This file is part of keras2c Copyright 2020 Rory Conlin -Licensed under MIT License +Licensed under LGPLv3 License https://github.com/f0uriest/keras2c Generates automatic test suite for converted code @@ -11,15 +11,19 @@ import numpy as np from keras2c.io_parsing import get_model_io_names from keras2c.weights2c import Weights2C -import tensorflow as tf import subprocess -tf.compat.v1.disable_eager_execution() -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" + +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" + +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" def make_test_suite(model, function_name, malloc_vars, num_tests=10, stateful=False, verbose=True, tol=1e-5): @@ -74,8 +78,10 @@ def make_test_suite(model, function_name, malloc_vars, num_tests=10, stateful=Fa s += 'struct timeval GetTimeStamp(); \n \n' file.write(s) for i in range(num_tests): - if i == num_tests//2 and stateful: - model.reset_states() + if i == num_tests // 2 and stateful: + for layer in model.layers: + if hasattr(layer, 'reset_states'): + layer.reset_states() # generate random input and write to file ct = 0 while True: @@ -87,7 +93,14 @@ def make_test_suite(model, function_name, malloc_vars, num_tests=10, stateful=Fa rand_inputs.insert(j, rand_input) # make predictions outputs = model.predict(rand_inputs) - if np.isfinite(outputs).all(): + if isinstance(outputs, list): + good = True + for outp in outputs: + if not np.isfinite(outp).all(): + good = False + if good: + break + elif np.isfinite(outputs).all(): break else: ct += 1 @@ -109,7 +122,7 @@ def make_test_suite(model, function_name, malloc_vars, num_tests=10, stateful=Fa model_outputs[j] + '_test' + str(i+1))) s = 'int main(){\n' file.write(s) - + s = ' float errors[' + str(num_tests*num_outputs) + '];\n' s += ' size_t num_tests = ' + str(num_tests) + '; \n' s += 'size_t num_outputs = ' + str(num_outputs) + '; \n' diff --git a/keras2c/weights2c.py b/keras2c/weights2c.py index a4b3e47..1152cbe 100644 --- a/keras2c/weights2c.py +++ b/keras2c/weights2c.py @@ -1,7 +1,7 @@ """weights2c.py This file is part of keras2c Copyright 2020 Rory Conlin -Licensed under MIT License +Licensed under LGPLv3 License https://github.com/f0uriest/keras2c Gets weights and other parameters from each layer and writes to C file @@ -10,17 +10,21 @@ # imports import numpy as np from keras2c.io_parsing import layer_type, get_layer_io_names, get_model_io_names -from tensorflow.keras import backend as K import tensorflow as tf -tf.compat.v1.disable_eager_execution() + + maxndim = 5 +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" class Weights2C(): @@ -99,9 +103,9 @@ def _write_weights_array2c(self, array, name): else: self.stack_vars += temp - def _write_weights_layer(self, layer): + def _write_weights_layer(self, layer, **kwargs): method = getattr(self, '_write_weights_' + layer_type(layer)) - return method(layer) + return method(layer, **kwargs) def write_weights(self, verbose=True): """Parses and generates code for model weights and other parameters @@ -113,7 +117,7 @@ def write_weights(self, verbose=True): (tuple): tuple containing - **stack_vars** (*str*): code for variables allocated on the stack - - **malloc_vars** (*dict*): dictionary of name,value pairs for arrays to be + - **malloc_vars** (*dict*): dictionary of name,value pairs for arrays to be allocated on the heap - **static_vars** (*str*): code fora C struct containing static variables (eg, states of a stateful RNN) @@ -136,12 +140,19 @@ def _write_static_vars(self): def _write_outputs(self, layer): _, outputs = get_layer_io_names(layer) - if len(outputs) > 1: + if isinstance(outputs, list): for i, outp in enumerate(outputs): - outshp = layer.get_output_at(i).shape[1:] - if outp not in self.model_io[1]: - self._write_weights_array2c( - np.zeros(outshp), outp + '_output') + if isinstance(outp, list): + for j, outpp in enumerate(outp): + outshp = layer.get_output_at(i)[j].shape[1:] + if outpp not in self.model_io[1]: + self._write_weights_array2c( + np.zeros(outshp), outpp + '_output') + else: + outshp = layer.output.shape[1:] + if outp not in self.model_io[1]: + self._write_weights_array2c( + np.zeros(outshp), outp + '_output') else: outshp = layer.output_shape[1:] if outputs[0] not in self.model_io[1]: @@ -152,18 +163,40 @@ def _write_outputs(self, layer): def _write_weights_Bidirectional(self, layer): try: - foo = layer.forward_layer.input_shape - foo = layer.backward_layer.input_shape + forward_shapes = [ + t.shape + for node in getattr(layer.forward_layer, "_inbound_nodes", []) + for t in node.input_tensors + ] + backward_shapes = [ + t.shape + for node in getattr(layer.backward_layer, "_inbound_nodes", []) + for t in node.input_tensors + ] + + # If there are shapes, just return them + if forward_shapes: + forward_output = layer.forward_layer.output + else: + raise AttributeError + + if backward_shapes: + backward_output = layer.backward_layer.output + else: + raise AttributeError except: - temp_input = tf.keras.layers.Input( - layer.input_shape[2:]) - foo = layer.layer.__call__(temp_input) - foo = layer.forward_layer.__call__(temp_input) - foo = layer.backward_layer.__call__(temp_input) - self._write_weights_layer(layer.backward_layer) - self._write_weights_layer(layer.forward_layer) - if layer.merge_mode: + temp_input = tf.keras.layers.Input(shape=layer.input.shape[1:]) + forward_output = layer.forward_layer(temp_input) + backward_output = layer.backward_layer(temp_input) + + self._write_weights_layer(layer.backward_layer, skip_outputs=True) + outshp = layer.backward_layer.output.shape[1:] + self._write_weights_array2c(np.zeros(outshp), layer.backward_layer.name + '_output') + self._write_weights_layer(layer.forward_layer, skip_outputs=True) + outshp = layer.forward_layer.output.shape[1:] + self._write_weights_array2c(np.zeros(outshp), layer.forward_layer.name + '_output') + if layer.merge_mode: self._write_outputs(layer) self.stack_vars += 'size_t ' + layer.name + '_num_tensors' + str(0) + \ ' = ' + str(2) + '; \n' @@ -177,35 +210,37 @@ def _write_weights_Bidirectional(self, layer): else: output_names = get_layer_io_names(layer)[1][0] - subname = layer.layer.name + subname = layer.forward_layer.name self.stack_vars += 'k2c_tensor * ' + \ - output_names[0] + ' = forward_' + subname + '_output; \n' + output_names[0] + ' = ' + subname + '_output; \n' + subname = layer.backward_layer.name self.stack_vars += 'k2c_tensor * ' + \ - output_names[1] + ' = backward_' + subname + '_output; \n' + output_names[1] + ' = ' + subname + '_output; \n' - def _write_weights_TimeDistributed(self, layer): - self._write_outputs(layer) + def _write_weights_TimeDistributed(self, layer, skip_outputs=False): + if not skip_outputs: + self._write_outputs(layer) try: - foo = layer.layer.input_shape + foo = layer.layer.input.shape except: temp_input = tf.keras.layers.Input( - layer.input_shape[2:], batch_size=1) + layer.input.shape[2:], batch_size=1) foo = layer.layer.__call__(temp_input) self._write_weights_layer(layer.layer) - timeslice_input = np.squeeze(np.zeros(layer.layer.input_shape[1:])) - timeslice_output = np.squeeze(np.zeros(layer.layer.output_shape[1:])) + timeslice_input = np.squeeze(np.zeros(layer.layer.input.shape[1:])) + timeslice_output = np.squeeze(np.zeros(layer.layer.output.shape[1:])) self._write_weights_array2c( timeslice_input, layer.layer.name + '_timeslice_input') self._write_weights_array2c( timeslice_output, layer.layer.name + '_timeslice_output') self.stack_vars += 'const size_t ' + layer.name +\ - '_timesteps = ' + str(layer.input_shape[1]) + '; \n' + '_timesteps = ' + str(layer.input.shape[1]) + '; \n' self.stack_vars += 'const size_t ' + layer.name +\ '_in_offset = ' + \ - str(np.prod(layer.input_shape[2:])) + '; \n' + str(np.prod(layer.input.shape[2:])) + '; \n' self.stack_vars += 'const size_t ' + layer.name +\ '_out_offset = ' + \ - str(np.prod(layer.output_shape[2:])) + '; \n' + str(np.prod(layer.output.shape[2:])) + '; \n' def _write_weights_Input(self, layer): self.stack_vars += '' @@ -213,15 +248,16 @@ def _write_weights_Input(self, layer): def _write_weights_InputLayer(self, layer): self.stack_vars += '' - def _write_weights_BatchNormalization(self, layer): - center = layer.get_config()['center'] - scale = layer.get_config()['scale'] - if isinstance(layer.get_config()['axis'], (list, tuple, np.ndarray)): - axis = layer.get_config()['axis'][0]-1 + def _write_weights_BatchNormalization(self, layer, skip_outputs=False): + cfg = layer.get_config() + center = cfg['center'] + scale = cfg['scale'] + if isinstance(cfg['axis'], (list, tuple, np.ndarray)): + axis = cfg['axis'][0]-1 else: - axis = layer.get_config()['axis']-1 + axis = cfg['axis']-1 - epsilon = layer.get_config()['epsilon'] + epsilon = cfg['epsilon'] if center and scale: gamma = layer.get_weights()[0] @@ -245,7 +281,8 @@ def _write_weights_BatchNormalization(self, layer): gamma = np.ones(mean.shape) stdev = np.sqrt(variance + epsilon) - self._write_outputs(layer) + if not skip_outputs: + self._write_outputs(layer) self.stack_vars += 'size_t ' + layer.name + \ '_axis = ' + str(axis) + '; \n' self._write_weights_array2c(mean, layer.name + '_mean') @@ -254,16 +291,18 @@ def _write_weights_BatchNormalization(self, layer): self._write_weights_array2c(beta, layer.name + '_beta') self.stack_vars += '\n\n' - def _write_weights_LSTM(self, layer): - units = layer.get_config()['units'] - self._write_outputs(layer) + def _write_weights_LSTM(self, layer, skip_outputs=False): + cfg = layer.get_config() + units = cfg['units'] + if not skip_outputs: + self._write_outputs(layer) self.stack_vars += 'float ' + layer.name + \ '_fwork[' + str(8*units) + '] = {0}; \n' self.stack_vars += 'int ' + layer.name + '_go_backwards = ' + \ - str(int(layer.get_config()['go_backwards'])) + ';\n' + str(int(cfg['go_backwards'])) + ';\n' self.stack_vars += 'int ' + layer.name + '_return_sequences = ' + \ - str(int(layer.get_config()['return_sequences'])) + ';\n' - if layer.get_config()['stateful']: + str(int(cfg['return_sequences'])) + ';\n' + if cfg['stateful']: self.static_vars.update({layer.name + '_state': 2*units}) self.stack_vars += 'float * ' + layer.name + '_state = ' + \ self.function_name + '_states.' + \ @@ -275,7 +314,7 @@ def _write_weights_LSTM(self, layer): weights = layer.get_weights() kernel = weights[0] recurrent_kernel = weights[1] - if layer.get_config()['use_bias']: + if cfg['use_bias']: bias = weights[2] else: bias = np.zeros(4*units) @@ -288,18 +327,20 @@ def _write_weights_LSTM(self, layer): self._write_weights_array2c(bias, layer.name + '_bias') self.stack_vars += '\n \n' - def _write_weights_GRU(self, layer): - units = layer.get_config()['units'] - self._write_outputs(layer) + def _write_weights_GRU(self, layer, skip_outputs=False): + cfg = layer.get_config() + units = cfg['units'] + if not skip_outputs: + self._write_outputs(layer) self.stack_vars += 'float ' + layer.name + \ '_fwork[' + str(6*units) + '] = {0}; \n' self.stack_vars += 'int ' + layer.name + '_reset_after = ' + \ - str(int(layer.get_config()['reset_after'])) + ';\n' + str(int(cfg['reset_after'])) + ';\n' self.stack_vars += 'int ' + layer.name + '_go_backwards = ' + \ - str(int(layer.get_config()['go_backwards'])) + ';\n' + str(int(cfg['go_backwards'])) + ';\n' self.stack_vars += 'int ' + layer.name + '_return_sequences = ' + \ - str(int(layer.get_config()['return_sequences'])) + ';\n' - if layer.get_config()['stateful']: + str(int(cfg['return_sequences'])) + ';\n' + if cfg['stateful']: self.static_vars.update({layer.name + '_state': units}) self.stack_vars += 'float * ' + layer.name + '_state = ' + \ self.function_name + '_states.' + \ @@ -311,9 +352,9 @@ def _write_weights_GRU(self, layer): weights = layer.get_weights() kernel = weights[0] recurrent_kernel = weights[1] - if layer.get_config()['use_bias']: + if cfg['use_bias']: bias = weights[2] - if layer.get_config()['reset_after']: + if cfg['reset_after']: rbias = bias[1] bias = bias[0] else: @@ -332,16 +373,18 @@ def _write_weights_GRU(self, layer): self._write_weights_array2c(cbias, layer.name + '_bias') self.stack_vars += '\n \n' - def _write_weights_SimpleRNN(self, layer): - units = layer.get_config()['units'] - self._write_outputs(layer) + def _write_weights_SimpleRNN(self, layer, skip_outputs=False): + cfg = layer.get_config() + units = cfg['units'] + if not skip_outputs: + self._write_outputs(layer) self.stack_vars += 'int ' + layer.name + '_go_backwards = ' + \ - str(int(layer.get_config()['go_backwards'])) + ';\n' + str(int(cfg['go_backwards'])) + ';\n' self.stack_vars += 'int ' + layer.name + '_return_sequences = ' + \ - str(int(layer.get_config()['return_sequences'])) + ';\n' + str(int(cfg['return_sequences'])) + ';\n' self.stack_vars += 'float ' + layer.name + \ '_fwork[' + str(2*units) + '] = {0}; \n' - if layer.get_config()['stateful']: + if cfg['stateful']: self.static_vars.update({layer.name + '_state': units}) self.stack_vars += 'float * ' + layer.name + '_state = ' + \ self.function_name + '_states.' + \ @@ -353,7 +396,7 @@ def _write_weights_SimpleRNN(self, layer): weights = layer.get_weights() kernel = weights[0] recurrent_kernel = weights[1] - if layer.get_config()['use_bias']: + if cfg['use_bias']: bias = weights[2] else: bias = np.zeros(units) @@ -363,11 +406,13 @@ def _write_weights_SimpleRNN(self, layer): self._write_weights_array2c(bias, layer.name + '_bias') self.stack_vars += '\n \n' - def _write_weights_Dense(self, layer): - self._write_outputs(layer) + def _write_weights_Dense(self, layer, skip_outputs=False): + cfg = layer.get_config() + if not skip_outputs: + self._write_outputs(layer) weights = layer.get_weights() A = weights[0] - if layer.get_config()['use_bias']: + if cfg['use_bias']: b = weights[1] else: b = np.zeros(A.shape[1]) @@ -375,21 +420,23 @@ def _write_weights_Dense(self, layer): self._write_weights_array2c(A, layer.name + '_kernel') self._write_weights_array2c(b, layer.name + '_bias') self.stack_vars += 'float ' + layer.name + \ - '_fwork[' + str(np.prod(layer.input_shape[1:]) + + '_fwork[' + str(np.prod(layer.input.shape[1:]) + np.prod(A.shape)) + '] = {0}; \n' self.stack_vars += '\n \n' - def _write_weights_Conv1D(self, layer): - padding = layer.get_config()['padding'] - stride = layer.get_config()['strides'][0] - dilation = layer.get_config()['dilation_rate'][0] - kernel_size = layer.get_config()['kernel_size'][0] + def _write_weights_Conv1D(self, layer, skip_outputs=False): + cfg = layer.get_config() + padding = cfg['padding'] + stride = cfg['strides'][0] + dilation = cfg['dilation_rate'][0] + kernel_size = cfg['kernel_size'][0] self.stack_vars += 'size_t ' + layer.name + \ '_stride = ' + str(stride) + '; \n' self.stack_vars += 'size_t ' + layer.name + \ '_dilation = ' + str(dilation) + '; \n' - self._write_outputs(layer) - inshp = layer.get_input_at(0).shape[1:] + if not skip_outputs: + self._write_outputs(layer) + inshp = layer.input.shape[1:] if padding == 'causal': pad_along_height = dilation*(kernel_size-1) pad_top = pad_along_height @@ -407,11 +454,11 @@ def _write_weights_Conv1D(self, layer): layer.name + '_padded_input') self.stack_vars += 'size_t ' + layer.name + '_pad[2] = {' + str(pad_top) + ','\ + str(pad_bottom) + '}; \n' - self.stack_vars += 'float ' + layer.name + '_fill = 0.0f; \n' + self.stack_vars += "float " + layer.name + "_fill = 0.0f; \n" weights = layer.get_weights() kernel = weights[0] - if layer.get_config()['use_bias']: + if cfg['use_bias']: bias = weights[1] else: bias = np.zeros(kernel.shape[2]) @@ -419,19 +466,21 @@ def _write_weights_Conv1D(self, layer): self._write_weights_array2c(bias, layer.name + '_bias') self.stack_vars += '\n \n' - def _write_weights_Conv2D(self, layer): - padding = layer.get_config()['padding'] - stride = layer.get_config()['strides'] - dilation = layer.get_config()['dilation_rate'] - kernel_size = layer.get_config()['kernel_size'] + def _write_weights_Conv2D(self, layer, skip_outputs=False): + cfg = layer.get_config() + padding = cfg['padding'] + stride = cfg['strides'] + dilation = cfg['dilation_rate'] + kernel_size = cfg['kernel_size'] self.stack_vars += 'size_t ' + layer.name + \ '_stride[2] = {' + ','.join([str(i) for i in stride]) + '}; \n' self.stack_vars += 'size_t ' + layer.name + \ '_dilation[2] = {' + ','.join([str(i) for i in dilation]) + '}; \n' - self._write_outputs(layer) + if not skip_outputs: + self._write_outputs(layer) if padding == 'same': - inshp = layer.get_input_at(0).shape[1:] + inshp = layer.input.shape[1:] pad_along_height = dilation[0]*(kernel_size[0]-1) pad_top = int(pad_along_height // 2) pad_bottom = int(pad_along_height - pad_top) @@ -449,7 +498,7 @@ def _write_weights_Conv2D(self, layer): weights = layer.get_weights() kernel = weights[0] - if layer.get_config()['use_bias']: + if cfg['use_bias']: bias = weights[1] else: bias = np.zeros(kernel.shape[3]) @@ -457,19 +506,21 @@ def _write_weights_Conv2D(self, layer): self._write_weights_array2c(bias, layer.name + '_bias') self.stack_vars += '\n \n' - def _write_weights_Conv3D(self, layer): - padding = layer.get_config()['padding'] - stride = layer.get_config()['strides'] - dilation = layer.get_config()['dilation_rate'] - kernel_size = layer.get_config()['kernel_size'] + def _write_weights_Conv3D(self, layer, skip_outputs=False): + cfg = layer.get_config() + padding = cfg['padding'] + stride = cfg['strides'] + dilation = cfg['dilation_rate'] + kernel_size = cfg['kernel_size'] self.stack_vars += 'size_t ' + layer.name + \ '_stride[3] = {' + ','.join([str(i) for i in stride]) + '}; \n' self.stack_vars += 'size_t ' + layer.name + \ '_dilation[3] = {' + ','.join([str(i) for i in dilation]) + '}; \n' - self._write_outputs(layer) + if not skip_outputs: + self._write_outputs(layer) if padding == 'same': - inshp = layer.get_input_at(0).shape[1:] + inshp = layer.input.shape[1:] pad_along_height = dilation[0]*(kernel_size[0]-1) pad_top = int(pad_along_height // 2) pad_bottom = int(pad_along_height - pad_top) @@ -493,7 +544,7 @@ def _write_weights_Conv3D(self, layer): weights = layer.get_weights() kernel = weights[0] - if layer.get_config()['use_bias']: + if cfg['use_bias']: bias = weights[1] else: bias = np.zeros(kernel.shape[3]) @@ -501,23 +552,63 @@ def _write_weights_Conv3D(self, layer): self._write_weights_array2c(bias, layer.name + '_bias') self.stack_vars += '\n \n' - def _write_weights_MaxPooling1D(self, layer): - return self._write_weights_Pooling1D(layer) + def _write_weights_Conv1DTranspose(self, layer, skip_outputs=False): + cfg = layer.get_config() + padding = cfg['padding'] + stride = cfg['strides'][0] + dilation = cfg['dilation_rate'][0] + if dilation != 1: + raise ValueError('Dilation not supported for Conv1DTranspose') + kernel_size = cfg['kernel_size'][0] + + # Write stride to C + self.stack_vars += 'size_t ' + layer.name + \ + '_stride = ' + str(stride) + '; \n' + + if padding == 'valid': + start_crop = 0 + elif padding == 'same': + start_crop = (kernel_size - stride) // 2 + else: + raise ValueError('Only same and valid padding supported for Conv1DTranspose') + # Write start_crop to C + self.stack_vars += 'size_t ' + layer.name + \ + '_start_crop = ' + str(start_crop) + '; \n' + + # Initialize layer.name + '_output' + if not skip_outputs: + self._write_outputs(layer) + + # Write kernel and bias to C + weights = layer.get_weights() + kernel = weights[0] + if cfg['use_bias']: + bias = weights[1] + else: + bias = np.zeros(kernel.shape[1]) + self._write_weights_array2c(kernel, layer.name + '_kernel') + self._write_weights_array2c(bias, layer.name + '_bias') + self.stack_vars += '\n \n' + + def _write_weights_MaxPooling1D(self, layer, **kwargs): + return self._write_weights_Pooling1D(layer, **kwargs) - def _write_weights_AveragePooling1D(self, layer): - return self._write_weights_Pooling1D(layer) + def _write_weights_AveragePooling1D(self, layer, **kwargs): + return self._write_weights_Pooling1D(layer, **kwargs) - def _write_weights_Pooling1D(self, layer): - pad = layer.get_config()['padding'] - stride = layer.get_config()['strides'][0] - pool_size = layer.get_config()['pool_size'][0] + def _write_weights_Pooling1D(self, layer, skip_outputs=False): + cfg = layer.get_config() + pad = cfg['padding'] + stride = cfg['strides'][0] + pool_size = cfg['pool_size'][0] self.stack_vars += 'size_t ' + layer.name + \ '_stride = ' + str(stride) + '; \n' self.stack_vars += 'size_t ' + layer.name + \ '_pool_size = ' + str(pool_size) + '; \n' - self._write_outputs(layer) - inshp = layer.get_input_at(0).shape[1:] - outshp = layer.get_output_at(0).shape[1:] + if not skip_outputs: + self._write_outputs(layer) + inshp = layer.input.shape[1:] + outshp = layer.output.shape[1:] if pad == 'same': pad_along_height = max((outshp[0] - 1) * stride + pool_size - inshp[0], 0) @@ -530,25 +621,27 @@ def _write_weights_Pooling1D(self, layer): self.stack_vars += 'float ' + layer.name + '_fill = -HUGE_VALF; \n' self.stack_vars += '\n\n' - def _write_weights_MaxPooling2D(self, layer): - return self._write_weights_Pooling2D(layer) + def _write_weights_MaxPooling2D(self, layer, **kwargs): + return self._write_weights_Pooling2D(layer, **kwargs) - def _write_weights_AveragePooling2D(self, layer): - return self._write_weights_Pooling2D(layer) + def _write_weights_AveragePooling2D(self, layer, **kwargs): + return self._write_weights_Pooling2D(layer, **kwargs) - def _write_weights_Pooling2D(self, layer): - padding = layer.get_config()['padding'] - stride = layer.get_config()['strides'] - pool_size = layer.get_config()['pool_size'] + def _write_weights_Pooling2D(self, layer, skip_outputs=False): + cfg = layer.get_config() + padding = cfg['padding'] + stride = cfg['strides'] + pool_size = cfg['pool_size'] self.stack_vars += 'size_t ' + layer.name + \ '_stride[2] = {' + ','.join([str(i) for i in stride]) + '}; \n' self.stack_vars += 'size_t ' + layer.name + \ '_pool_size[2] = {' + ','.join([str(i) for i in pool_size]) + '}; \n' - self._write_outputs(layer) + if not skip_outputs: + self._write_outputs(layer) if padding == 'same': - inshp = layer.get_input_at(0).shape[1:] - outshp = layer.get_output_at(0).shape[1:] + inshp = layer.input.shape[1:] + outshp = layer.output.shape[1:] pad_along_height = max((outshp[0] - 1) * stride[0] + pool_size[0] - inshp[0], 0) pad_top = int(pad_along_height // 2) @@ -567,48 +660,50 @@ def _write_weights_Pooling2D(self, layer): self.stack_vars += 'float ' + layer.name + '_fill = -HUGE_VALF; \n' self.stack_vars += '\n\n' - def _write_weights_GlobalMaxPooling1D(self, layer): - return self._write_weights_GlobalPooling(layer) + def _write_weights_GlobalMaxPooling1D(self, layer, **kwargs): + return self._write_weights_GlobalPooling(layer, **kwargs) - def _write_weights_GlobalMaxPooling2D(self, layer): - return self._write_weights_GlobalPooling(layer) + def _write_weights_GlobalMaxPooling2D(self, layer, **kwargs): + return self._write_weights_GlobalPooling(layer, **kwargs) - def _write_weights_GlobalMaxPooling3D(self, layer): - return self._write_weights_GlobalPooling(layer) + def _write_weights_GlobalMaxPooling3D(self, layer, **kwargs): + return self._write_weights_GlobalPooling(layer, **kwargs) - def _write_weights_GlobalAveragePooling1D(self, layer): - return self._write_weights_GlobalPooling(layer) + def _write_weights_GlobalAveragePooling1D(self, layer, **kwargs): + return self._write_weights_GlobalPooling(layer, **kwargs) - def _write_weights_GlobalAveragePooling2D(self, layer): - return self._write_weights_GlobalPooling(layer) + def _write_weights_GlobalAveragePooling2D(self, layer, **kwargs): + return self._write_weights_GlobalPooling(layer, **kwargs) - def _write_weights_GlobalAveragePooling3D(self, layer): - return self._write_weights_GlobalPooling(layer) + def _write_weights_GlobalAveragePooling3D(self, layer, **kwargs): + return self._write_weights_GlobalPooling(layer, **kwargs) - def _write_weights_GlobalPooling(self, layer): - self._write_outputs(layer) + def _write_weights_GlobalPooling(self, layer, skip_outputs=False): + if not skip_outputs: + self._write_outputs(layer) self.stack_vars += '\n\n' - def _write_weights_Add(self, layer): - return self._write_weights_Merge(layer) + def _write_weights_Add(self, layer, **kwargs): + return self._write_weights_Merge(layer, **kwargs) - def _write_weights_Subtract(self, layer): - return self._write_weights_Merge(layer) + def _write_weights_Subtract(self, layer, **kwargs): + return self._write_weights_Merge(layer, **kwargs) - def _write_weights_Multiply(self, layer): - return self._write_weights_Merge(layer) + def _write_weights_Multiply(self, layer, **kwargs): + return self._write_weights_Merge(layer, **kwargs) - def _write_weights_Average(self, layer): - return self._write_weights_Merge(layer) + def _write_weights_Average(self, layer, **kwargs): + return self._write_weights_Merge(layer, **kwargs) - def _write_weights_Maximum(self, layer): - return self._write_weights_Merge(layer) + def _write_weights_Maximum(self, layer, **kwargs): + return self._write_weights_Merge(layer, **kwargs) - def _write_weights_Minimum(self, layer): - return self._write_weights_Merge(layer) + def _write_weights_Minimum(self, layer, **kwargs): + return self._write_weights_Merge(layer, **kwargs) - def _write_weights_Merge(self, layer): - self._write_outputs(layer) + def _write_weights_Merge(self, layer, skip_outputs=False): + if not skip_outputs: + self._write_outputs(layer) inputs, outputs = get_layer_io_names(layer) for i, (inp, outp) in enumerate(zip(inputs, outputs)): num_tensors = len(inp) @@ -616,16 +711,17 @@ def _write_weights_Merge(self, layer): ' = ' + str(num_tensors) + '; \n' self.stack_vars += '\n\n' - def _write_weights_Concatenate(self, layer): + def _write_weights_Concatenate(self, layer, ): + cfg = layer.get_config() inputs, outputs = get_layer_io_names(layer) for i, (inp, outp) in enumerate(zip(inputs, outputs)): - outshp = layer.get_output_at(i).shape[1:] + outshp = layer.output.shape[1:] num_tensors = len(inp) self.stack_vars += 'size_t ' + layer.name + '_num_tensors' + str(i) + \ ' = ' + str(num_tensors) + '; \n' - ax = layer.get_config()['axis'] + ax = cfg['axis'] if ax < 0: - ax += len(layer.get_input_at(i)[0].shape) + ax += len(layer.input[0].shape) self.stack_vars += 'size_t ' + layer.name + '_axis = ' +\ str(ax-1) + '; \n' if outp not in self.model_io[1]: @@ -634,27 +730,34 @@ def _write_weights_Concatenate(self, layer): self.stack_vars += '\n\n' def _write_weights_ELU(self, layer): - alpha = layer.get_config()['alpha'] + cfg = layer.get_config() + alpha = cfg['alpha'] self.stack_vars += 'float ' + layer.name + \ '_alpha = ' + str(alpha) + '; \n' self.stack_vars += '\n\n' def _write_weights_LeakyReLU(self, layer): - alpha = layer.get_config()['alpha'] + cfg = layer.get_config() + try: + alpha = cfg['alpha'] + except KeyError: + alpha = cfg['negative_slope'] self.stack_vars += 'float ' + layer.name + \ '_alpha = ' + str(alpha) + '; \n' self.stack_vars += '\n\n' def _write_weights_ThresholdedReLU(self, layer): - theta = layer.get_config()['theta'] + cfg = layer.get_config() + theta = cfg['theta'] self.stack_vars = 'float ' + layer.name + \ '_theta = ' + str(theta) + '; \n' self.stack_vars += '\n\n' def _write_weights_ReLU(self, layer): - max_value = layer.get_config()['max_value'] - negative_slope = layer.get_config()['negative_slope'] - threshold = layer.get_config()['threshold'] + cfg = layer.get_config() + max_value = cfg['max_value'] + negative_slope = cfg['negative_slope'] + threshold = cfg['threshold'] if max_value is None: max_value = 'HUGE_VALF' self.stack_vars += 'float ' + layer.name + \ @@ -670,10 +773,12 @@ def _write_weights_PReLU(self, layer): layer.get_weights()[0], layer.name + '_alpha') self.stack_vars += '\n\n' - def _write_weights_Reshape(self, layer): + def _write_weights_Reshape(self, layer, skip_outputs=False): + cfg = layer.get_config() nm = layer.name - self._write_outputs(layer) - newshp = layer.get_config()['target_shape'] + if not skip_outputs: + self._write_outputs(layer) + newshp = cfg['target_shape'] newndim = len(newshp) newshp = np.concatenate((newshp, np.ones(maxndim-newndim))) self.stack_vars += 'size_t ' + nm + \ @@ -683,26 +788,32 @@ def _write_weights_Reshape(self, layer): separator=',')[1:-1]) + '}; \n' self.stack_vars += '\n\n' - def _write_weights_Permute(self, layer): - self._write_outputs(layer) - permute = np.array(layer.get_config()['dims']).astype(int) - 1 + def _write_weights_Permute(self, layer, skip_outputs=False): + cfg = layer.get_config() + if not skip_outputs: + self._write_outputs(layer) + permute = np.array(cfg['dims']).astype(int) - 1 self.stack_vars += 'size_t ' + layer.name + '_permute[' + str(permute.size) + '] = {' +\ str(np.array2string(permute.astype(int), separator=',')[1:-1]) + '}; \n' self.stack_vars += '\n\n' - def _write_weights_RepeatVector(self, layer): - self._write_outputs(layer) - n = layer.get_config()['n'] + def _write_weights_RepeatVector(self, layer, skip_outputs=False): + cfg = layer.get_config() + if not skip_outputs: + self._write_outputs(layer) + n = cfg['n'] self.stack_vars += 'size_t ' + layer.name + '_n = ' + str(n) + '; \n' self.stack_vars += '\n\n' - def _write_weights_Dot(self, layer): + def _write_weights_Dot(self, layer, skip_outputs=False): + cfg = layer.get_config() nm = layer.name - self._write_outputs(layer) + if not skip_outputs: + self._write_outputs(layer) work_size = np.prod(layer.input[0].shape[1:]) + \ np.prod(layer.input[1].shape[1:]) - axes = np.array(layer.get_config()['axes']) - 1 + axes = np.array(cfg['axes']) - 1 self.stack_vars += 'size_t ' + nm + \ '_axesA[1] = {' + str(axes[0]) + '}; \n' self.stack_vars += 'size_t ' + nm + \ @@ -711,106 +822,125 @@ def _write_weights_Dot(self, layer): self.stack_vars += 'float ' + nm + \ '_fwork[' + str(work_size) + '] = {0}; \n' self.stack_vars += 'int ' + nm + '_normalize = ' + \ - str(int(layer.get_config()['normalize'])) + '; \n' + str(int(cfg['normalize'])) + '; \n' self.stack_vars += '\n\n' - def _write_weights_Embedding(self, layer): + def _write_weights_Embedding(self, layer, skip_outputs=False): nm = layer.name - self._write_outputs(layer) + if not skip_outputs: + self._write_outputs(layer) kernel = layer.get_weights()[0] self._write_weights_array2c(kernel, nm+'_kernel') self.stack_vars += '\n\n' - def _write_weights_UpSampling1D(self, layer): + def _write_weights_UpSampling1D(self, layer, skip_outputs=False): + cfg = layer.get_config() nm = layer.name - self._write_outputs(layer) - size = layer.get_config()['size'] + if not skip_outputs: + self._write_outputs(layer) + size = cfg['size'] self.stack_vars += 'size_t ' + nm + '_size = ' + str(size) + '; \n' self.stack_vars += '\n\n' - def _write_weights_UpSampling2D(self, layer): + def _write_weights_UpSampling2D(self, layer, skip_outputs=False): + cfg = layer.get_config() nm = layer.name - self._write_outputs(layer) - size = layer.get_config()['size'] + if not skip_outputs: + self._write_outputs(layer) + size = cfg['size'] self.stack_vars += 'size_t ' + nm + '_size[2] = {' + str(size[0]) + \ ',' + str(size[1]) + '}; \n' self.stack_vars += '\n\n' - def _write_weights_UpSampling3D(self, layer): + def _write_weights_UpSampling3D(self, layer, skip_outputs=False): + cfg = layer.get_config() nm = layer.name - self._write_outputs(layer) - size = layer.get_config()['size'] + if not skip_outputs: + self._write_outputs(layer) + size = cfg['size'] self.stack_vars += 'size_t ' + nm + '_size[3] = {' + str(size[0]) + \ ',' + str(size[1]) + ',' + str(size[2]) + '}; \n' self.stack_vars += '\n\n' - def _write_weights_Cropping1D(self, layer): + def _write_weights_Cropping1D(self, layer, skip_outputs=False): nm = layer.name - self._write_outputs(layer) - crop_top = layer.get_config()['cropping'][0] - crop_bottom = layer.get_config()['cropping'][1] + if not skip_outputs: + self._write_outputs(layer) + cfg = layer.get_config() + crop_top = cfg['cropping'][0] + crop_bottom = cfg['cropping'][1] self.stack_vars += 'size_t ' + nm + '_crop[2] = {' + str(crop_top) + ','\ + str(crop_bottom) + '}; \n' self.stack_vars += '\n\n' - def _write_weights_Cropping2D(self, layer): + def _write_weights_Cropping2D(self, layer, skip_outputs=False): nm = layer.name - self._write_outputs(layer) - crop_top = layer.get_config()['cropping'][0][0] - crop_bottom = layer.get_config()['cropping'][0][1] - crop_left = layer.get_config()['cropping'][1][0] - crop_right = layer.get_config()['cropping'][1][1] + if not skip_outputs: + self._write_outputs(layer) + cfg = layer.get_config() + crop_top = cfg['cropping'][0][0] + crop_bottom = cfg['cropping'][0][1] + crop_left = cfg['cropping'][1][0] + crop_right = cfg['cropping'][1][1] self.stack_vars += 'size_t ' + nm + '_crop[4] = {' + str(crop_top) + ','\ + str(crop_bottom) + ',' + str(crop_left) + \ ',' + str(crop_right) + '}; \n' self.stack_vars += '\n\n' - def _write_weights_Cropping3D(self, layer): + def _write_weights_Cropping3D(self, layer, skip_outputs=False): nm = layer.name - self._write_outputs(layer) - crop0 = layer.get_config()['cropping'][0][0] - crop1 = layer.get_config()['cropping'][0][1] - crop2 = layer.get_config()['cropping'][1][0] - crop3 = layer.get_config()['cropping'][1][1] - crop4 = layer.get_config()['cropping'][2][0] - crop5 = layer.get_config()['cropping'][2][1] + if not skip_outputs: + self._write_outputs(layer) + cfg = layer.get_config() + crop0 = cfg['cropping'][0][0] + crop1 = cfg['cropping'][0][1] + crop2 = cfg['cropping'][1][0] + crop3 = cfg['cropping'][1][1] + crop4 = cfg['cropping'][2][0] + crop5 = cfg['cropping'][2][1] self.stack_vars += 'size_t ' + nm + '_crop[6] = {' + str(crop0) + ','\ + str(crop1) + ',' + str(crop2) + ',' + str(crop3) + \ ',' + str(crop4) + ',' + str(crop5) + '}; \n' self.stack_vars += '\n\n' - def _write_weights_ZeroPadding1D(self, layer): + def _write_weights_ZeroPadding1D(self, layer, skip_outputs=False): nm = layer.name - self._write_outputs(layer) - pad_top = layer.get_config()['padding'][0] - pad_bottom = layer.get_config()['padding'][1] + if not skip_outputs: + self._write_outputs(layer) + cfg = layer.get_config() + pad_top = cfg['padding'][0] + pad_bottom = cfg['padding'][1] self.stack_vars += 'size_t ' + nm + '_pad[2] = {' + str(pad_top) + ','\ + str(pad_bottom) + '}; \n' self.stack_vars += 'float ' + nm + '_fill = 0.0f; \n' self.stack_vars += '\n\n' - def _write_weights_ZeroPadding2D(self, layer): + def _write_weights_ZeroPadding2D(self, layer, skip_outputs=False): nm = layer.name - self._write_outputs(layer) - pad_top = layer.get_config()['padding'][0][0] - pad_bottom = layer.get_config()['padding'][0][1] - pad_left = layer.get_config()['padding'][1][0] - pad_right = layer.get_config()['padding'][1][1] + if not skip_outputs: + self._write_outputs(layer) + cfg = layer.get_config() + pad_top = cfg['padding'][0][0] + pad_bottom = cfg['padding'][0][1] + pad_left = cfg['padding'][1][0] + pad_right = cfg['padding'][1][1] self.stack_vars += 'size_t ' + nm + '_pad[4] = {' + str(pad_top) + ','\ + str(pad_bottom) + ',' + str(pad_left) + \ ',' + str(pad_right) + '}; \n' self.stack_vars += 'float ' + nm + '_fill = 0.0f; \n' self.stack_vars += '\n\n' - def _write_weights_ZeroPadding3D(self, layer): + def _write_weights_ZeroPadding3D(self, layer, skip_outputs=False): nm = layer.name - self._write_outputs(layer) - pad0 = layer.get_config()['padding'][0][0] - pad1 = layer.get_config()['padding'][0][1] - pad2 = layer.get_config()['padding'][1][0] - pad3 = layer.get_config()['padding'][1][1] - pad4 = layer.get_config()['padding'][2][0] - pad5 = layer.get_config()['padding'][2][1] + if not skip_outputs: + self._write_outputs(layer) + cfg = layer.get_config() + pad0 = cfg['padding'][0][0] + pad1 = cfg['padding'][0][1] + pad2 = cfg['padding'][1][0] + pad3 = cfg['padding'][1][1] + pad4 = cfg['padding'][2][0] + pad5 = cfg['padding'][2][1] self.stack_vars += 'size_t ' + nm + '_pad[6] = {' + str(pad0) + ','\ + str(pad1) + ',' + str(pad2) + ',' + str(pad3) + \ ',' + str(pad4) + ',' + str(pad5) + '}; \n' @@ -836,7 +966,7 @@ def _write_weights_SpatialDropout3D(self, layer): def _write_weights_Flatten(self, layer): _, outputs = get_layer_io_names(layer) for i, outp in enumerate(outputs): - inshp = layer.get_input_at(i).shape[1:] + inshp = layer.input.shape[1:] if outp not in self.model_io[1]: self._write_weights_array2c( np.zeros(inshp).flatten(), outp + '_output') @@ -848,3 +978,16 @@ def _write_weights_Activation(self, layer): def _write_weights_Dropout(self, layer): # no weights needed pass + + def _write_weights_TFOpLambda(self, layer): + self._write_weights_TensorFlowOpLayer(layer) + + def _write_weights_TensorFlowOpLayer(self, layer, skip_outputs=False): + # Special case when tf.split is used + # no weights needed + if 'split' in layer.name: + if not skip_outputs: + self._write_outputs(layer) + else: + raise AssertionError('Unsupported TensorFlowOpLayer: ' + layer.name + '\n' + + 'Currently only split operation is supported.') diff --git a/requirements.txt b/requirements.txt index 07d79fa..54e4728 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,2 @@ -# This is an implicit value, here for clarity ---index-url https://pypi.python.org/simple/ - -numpy >= 1.13.0 -tensorflow >= 2.0 - +tensorflow +keras >= 3.0.0 diff --git a/tests/numpy_conv1d.py b/tests/numpy_conv1d.py new file mode 100644 index 0000000..0a2e8a9 --- /dev/null +++ b/tests/numpy_conv1d.py @@ -0,0 +1,587 @@ +""" +Numpy implementation of 1D convolution and transposed convolution of keras.Conv1D +and keras.Conv1DTranspose + +The idea is to obtain the same output as keras.Conv1D and keras.Conv1DTranspose in +simple numpy code so that it can be implemented in C code. + +Inspired by the following code: +https://github.com/rmwkwok/transposed_convolution_in_numpy/tree/main +""" + +import numpy as np +import keras +import traceback +import time + + +def out_shape(input_shape: tuple[int, int], + kernel: np.ndarray, + strides: int = 1, + padding: str = "valid", + mode: str = "normal" + ) -> tuple[int, int]: + """ + Compute the output shape of the convolution operation. + + Parameters + ---------- + input_shape : tuple[int, int] + Shape of the input tensor without batch size. (height, channels) + kernel: numpy.ndarray + Kernel of the convolution operation. + Returned by model.layer[i].get_weights()[0] for a keras model. + strides : int + Strides of the convolution operation + padding : str + Padding of the convolution operation. 'valid' or 'same' are supported. + mode : str + Mode of the convolution operation. + 'normal' means normal convolution and 'transposed' means transposed convolution. + + Returns + ------- + Tuple[int, int] + Shape of the output tensor without batch size. + (no of convolutions, no of filters) + """ + ih, ic = input_shape + if mode == "normal": + ksize, kc, n_filters = kernel.shape + assert ic == kc + if padding == "valid": + p_2 = 0 + elif padding == "same": + p_2 = ksize - 1 + nconv = (ih + p_2 - ksize) // strides + 1 + elif mode == "transposed": + ksize, n_filters, kc = kernel.shape + assert ic == kc + if padding == "valid": + p_2 = 0 + elif padding == "same": + p_2 = ksize - strides + nconv = ih - p_2 + ksize - 1 + ((ih - 1) * (strides - 1)) + # if padding == 'valid': + # nconv = ih + ksize - 1 + ((ih - 1) * (strides - 1)) + # elif padding == 'same': + # nconv = ih + (ih * (strides - 1)) + return nconv, n_filters + + +def padding1d(inputs : np.ndarray, + kernel: np.ndarray, + strides: int = 1, + padding: str = "valid", + mode: str = "normal" + ) -> np.ndarray: + """ + Compute the padded input tensor for the convolution operation and crops the output + tensor for the transposed convolution operation. + + Parameters + ---------- + inputs : numpy.ndarray + Input tensor with batch size. (batch size, height, channels) + kernel : numpy.ndarray + Kernel of the convolution operation. + Returned by model.layer[i].get_weights()[0] for a keras model. + strides : int + Strides of the convolution operation + padding : str + Padding of the convolution operation. 'valid' or 'same' are supported. + mode : str + Mode of the convolution operation. + 'normal' means normal convolution and 'transposed' means transposed convolution. + + Returns + ------- + numpy.ndarray + Padded input tensor with batch size. + (batch size, height, channels) or cropped output tensor with batch size. + (batch size, convolutions, filters) + """ + ksize = kernel.shape[0] + if padding == "valid": + return inputs + elif padding == "same": + if mode == "normal": + p_2 = ksize - 1 + elif mode == "transposed": + p_2 = ksize - strides + if p_2 % 2 == 0: + before = after = p_2 // 2 + else: + before = (p_2 - 1) // 2 + after = before + 1 + if mode == "normal": + outputs = np.zeros((inputs.shape[0], inputs.shape[1] + p_2, inputs.shape[2])) + outputs[:, before : before + inputs.shape[1], :] = inputs + elif mode == "transposed": + outputs = inputs[:, before : inputs.shape[1] - after, :] + return outputs + + +def stride1d(inputs: np.ndarray, + kernel: np.ndarray, + strides: int = 1, + padding: str = "valid", + mode: str = "normal" + ) -> np.ndarray: + """ + Compute the strided output tensor for the convolution operation + or zeros inserted (unstrided) input tensor for the transposed convolution operation. + + Parameters + ---------- + inputs : numpy.ndarray + Input tensor with batch size. (batch size, height, channels) + kernel : numpy.ndarray + Kernel of the convolution operation. + Returned by model.layer[i].get_weights()[0] for a keras model. + strides : int + Strides of the convolution operation + padding : str + Padding of the convolution operation. 'valid' or 'same' are supported. + mode : str + Mode of the convolution operation. + 'normal' means normal convolution and 'transposed' means transposed convolution. + + Returns + ------- + numpy.ndarray + Strided output tensor with batch size. (batch size, convolutions, filters) + or unstrided input tensor with batch size. (batch size, height, channels). + """ + # if strides == 1: + # return inputs + ksize = kernel.shape[0] + if mode == "normal": + if padding == "valid": + outputs = inputs[:, ::strides, :] + elif padding == "same": + total_skip = (inputs.shape[1] - 1) % strides + if total_skip % 2 == 0: + start_ind = total_skip // 2 + else: + start_ind = total_skip // 2 + (ksize % 2) + outputs = inputs[:, start_ind::strides, :] + elif mode == "transposed": + outputs = np.zeros( + ( + inputs.shape[0], + inputs.shape[1] + (inputs.shape[1] - 1) * (strides - 1), + inputs.shape[2], + ) + ) + outputs[:, ::strides, :] = inputs + return outputs + + +def unroll_kernel1d(input_shape: tuple[int, int], + kernel: np.ndarray, + strides: int = 1, + padding: str = "valid", + mode: str = "normal" + ) -> np.ndarray: + """ + Get the unrolled kernel for the convolution or transposed convolution operations. + + Parameters + ---------- + input_shape : tuple[int, int] + Shape of the input tensor without batch size. (height, channels) + kernel : numpy.ndarray + Kernel of the convolution operation. + Returned by model.layer[i].get_weights()[0] for a keras model. + strides : int + Strides of the convolution operation + padding : str + Padding of the convolution operation. 'valid' or 'same' are supported. + mode : str + Mode of the convolution operation. 'normal' means normal convolution and + 'transposed' means transposed convolution. + + Returns + ------- + numpy.npdarray + In normal mode: unrolled kernel for the convolution operation. + (channels, no of convolutions, filters, padded input size) + In transposed mode: unrolled kernel for the transposed convolution operation. + (channels, zero inserted input size + kernel size - 1, filters, + zero inserted input size) + + Note + ---- + In current implementation, the unrolled kernel in normal mode would only work for + strides=1 case and it is assumed that the striding is done after the convolution + operation. + + In current implementation, the unrolled kernel in transposed mode would only work + for padding='valid' case and it is assumed that any cropping for padding='same' + is done after the transposed convolution operation. + + """ + nconv, nfilters = out_shape( + input_shape, kernel, strides=strides, padding=padding, mode=mode + ) + nh, nc = input_shape + ksize = kernel.shape[0] + if mode == "normal": + if padding == "valid": + p_2 = 0 + elif padding == "same": + p_2 = ksize - 1 + total_inp_size = nh + p_2 + elif mode == "transposed": + total_inp_size = nh + (nh - 1) * (strides - 1) + unrolled_kernel = np.zeros((nc, nconv, nfilters, total_inp_size), dtype=np.float32) + if mode == "normal": + if padding == "valid": + start_ind = 0 + elif padding == "same": + # nconv_stride1 = ih + p_2 - ksize + 1 = total_inp_size - ksize + 1 + # total_skip = (nconv_stride1 - 1) % strides + total_skip = (total_inp_size - ksize) % strides + if total_skip % 2 == 0: + start_ind = total_skip // 2 + else: + start_ind = total_skip // 2 + (ksize % 2) + for ch in range(nc): + for i in range(nconv): + # Go through only those convs which are not removed in stride1d later + i_s = start_ind + i * strides + for f in range(nfilters): + if mode == "normal": + unrolled_kernel[ch, i, f, i_s : i_s + ksize] += kernel[:, ch, f] + return unrolled_kernel + elif mode == "transposed": + # if cropping was done later, start_ind is the start point in cropping + if padding == "valid": + start_ind = 0 + elif padding == "same": + out_crop = ksize - strides + start_ind = out_crop // 2 + for ch in range(nc): + for f in range(nfilters): + for i in range(total_inp_size): + cropped_start = max(0, i - start_ind) + cropped_end = min(nconv, i + ksize - start_ind) + ker_start = cropped_start - (i - start_ind) + ker_end = ker_start + cropped_end - cropped_start + unrolled_kernel[ch, cropped_start:cropped_end, f, i] += kernel[ + ker_start:ker_end, f, ch + ] + return unrolled_kernel + + +def conv1d(inputs: np.ndarray, + kernel: np.ndarray, + strides: int = 1, + padding: str = "valid" + ) -> np.ndarray: + """ + Compute the output tensor of the convolution operation. + + Parameters + ---------- + inputs : numpy.ndarray + Input tensor with batch size. (batch size, height, channels) + kernel : numpy.ndarray + Kernel of the convolution operation. + Returned by model.layer[i].get_weights()[0] for a keras model. + strides : int + Strides of the convolution operation + padding : str + Padding of the convolution operation. 'valid' or 'same' are supported. + + Returns + ------- + numpy.ndarray + Output tensor with batch size. (batch size, convolutions, filters) + """ + n_batches, n_height, n_channels = inputs.shape + outputs = np.zeros( + (n_batches, *out_shape( + (n_height, n_channels), + kernel, + strides=strides, + padding=padding, + mode="normal"), + ) + ) + padded_input = padding1d(inputs, kernel, strides=strides, padding=padding, + mode="normal") + unrolled_kernel = unroll_kernel1d((n_height, n_channels), kernel, + strides=strides, padding=padding, mode="normal") + _, n_convs, _, n_padded_inp = unrolled_kernel.shape + assert n_padded_inp == padded_input.shape[1] + for batch in range(n_batches): + for ch in range(n_channels): + flat_inp = np.zeros((n_padded_inp, 1)) + flat_inp[:, 0] = padded_input[batch, :, ch] + for i in range(n_convs): + outputs[batch, i, :] += np.matmul( + unrolled_kernel[ch, i, :, :], flat_inp + ).flatten() + return outputs + # return stride1d(outputs, kernel, strides=strides, padding=padding, mode='normal') + # Needed when unrolled kernel is first calculated with strides=1 + + +def conv1d_transpose(inputs: np.ndarray, + kernel: np.ndarray, + strides: int = 1, + padding: str = "valid" + ) -> np.ndarray: + """ + Compute the output tensor of the transposed convolution operation. + + Parameters + ---------- + inputs : numpy.ndarray + Input tensor with batch size. (batch size, height, channels) + kernel : numpy.ndarray + Kernel of the transposed convolution operation. + Returned by model.layer[i].get_weights()[0] for a keras model. + strides : int + Strides of the transposed convolution operation + padding : str + Padding of the transposed convolution operation. + 'valid' or 'same' are supported. + + Returns + ------- + numpy.ndarray: + Output tensor with batch size. (batch size, height, channels) + """ + n_batches, n_height, n_channels = inputs.shape + outputs = np.zeros( + ( + n_batches, + *out_shape( + (n_height, n_channels), + kernel, + strides=strides, + padding=padding, + mode="transposed", + ), + ) + ) + strided_input = stride1d( + inputs, kernel, strides=strides, padding=padding, mode="transposed" + ) + unrolled_kernel = unroll_kernel1d( + (n_height, n_channels), + kernel, + strides=strides, + padding=padding, + mode="transposed", + ) + _, _, n_filters, n_strided_inp = unrolled_kernel.shape + assert n_strided_inp == strided_input.shape[1] + for batch in range(n_batches): + for ch in range(n_channels): + flat_inp = np.zeros((n_strided_inp, 1)) + flat_inp[:, 0] = strided_input[batch, :, ch] + for f in range(n_filters): + outputs[batch, :, f] += np.matmul( + unrolled_kernel[ch, :, f, :], flat_inp + ).flatten() + return outputs + # padding1d(outputs, kernel, strides=strides, padding=padding, mode='transposed') + # Needed when unrolled kernel is first calculated with padding='valid' + + +def conv1d_transpose_direct(inputs: np.ndarray, + kernel: np.ndarray, + strides: int = 1, + padding: str = "valid" + ) -> np.ndarray: + """ + True flat array based direct implementation of transposed convolution operation. + This function can be converted to C code directly. + + Parameters + ---------- + inputs :numpy.ndarray + Input tensor with batch size. (batch size, height, channels) + kernel : numpy.ndarray + Kernel of the transposed convolution operation. + Returned by model.layer[i].get_weights()[0] for a keras model. + strides : int + Strides of the transposed convolution operation + padding : str + Padding of the transposed convolution operation. + 'valid' or 'same' are supported. + + Returns + ------- + numpy.ndarray + Output tensor with batch size. + (batch size, number of convolutions, number of filters) + """ + n_batches, n_height, n_channels = inputs.shape + ksize, n_filters, kc = kernel.shape + outputs = np.zeros( + ( + n_batches, + *out_shape( + (n_height, n_channels), + kernel, + strides=strides, + padding=padding, + mode="transposed", + ), + ) + ) + _, n_conv, _ = outputs.shape + if padding == "valid": + p_2 = 0 + elif padding == "same": + p_2 = ksize - strides + si = p_2 // 2 + output_flat = np.zeros((n_batches * n_conv * n_filters)) + k_flat = kernel.flatten(order="C") + i_flat = inputs.flatten(order="C") + for b in range(n_batches): + for f in range(n_filters): + for ch in range(n_channels): + for t in range(n_height): + if t * strides > si: + cs = t * strides - si + else: + cs = 0 + if t * strides + ksize - si > n_conv: + ce = n_conv + else: + ce = t * strides + ksize - si + ks = cs - (t * strides - si) + for i in range(0, ce - cs): + output_flat[ + b * n_conv * n_filters + (cs + i) * n_filters + f + ] += ( + k_flat[(i + ks) * n_filters * kc + f * kc + ch] + * i_flat[b * n_height * n_channels + t * n_channels + ch] + ) + return output_flat.reshape((n_batches, n_conv, n_filters)) + # return outputs + + +if __name__ == "__main__": + mn = 0 + mt = 0 + pv = 0 + ps = 0 + for _ in range(50): + nb = np.random.randint(1, 10) + nh = np.random.randint(2, 50) + nc = np.random.randint(1, 50) + nf = np.random.randint(1, 50) + nk = np.random.randint(1, nh) + strides = np.random.randint(1, max(nk, 2)) + if np.random.randint(2): + padding = "valid" + pv += 1 + else: + padding = "same" + ps += 1 + + if np.random.randint(2): + mode = "normal" + mn += 1 + else: + mode = "transposed" + mt += 1 + + t_a = keras.layers.Input((nh, nc)) + if mode == "normal": + t_b = keras.layers.Conv1D( + filters=nf, kernel_size=nk, padding=padding, strides=strides + )(t_a) + elif mode == "transposed": + t_b = keras.layers.Conv1DTranspose( + filters=nf, kernel_size=nk, padding=padding, strides=strides + )(t_a) + mod = keras.models.Model(inputs=t_a, outputs=t_b) + ker = mod.layers[1].get_weights()[0] + inp = np.random.random((nb, nh, nc)) + out = mod.predict(inp) + try: + calc_out_shape = out_shape( + (nh, nc), ker, padding=padding, strides=strides, mode=mode + ) + except BaseException: + print("-" * 50) + print("Python Error in calculating output shape") + traceback.print_exc() + error = True + + error = False + if calc_out_shape != out.shape[1:]: + print("-" * 50) + print("Error in calculating output shape") + error = True + + try: + t_start = time.time() + if mode == "normal": + out2 = conv1d(inp, ker, strides=strides, padding=padding) + elif mode == "transposed": + out2 = conv1d_transpose(inp, ker, strides=strides, padding=padding) + t_end = time.time() + print( + "Numpy version time taken: {:.0f} ms".format( + (t_end - t_start) * 1e3 + ) + ) + if mode == "transposed": + t_d_start = time.time() + out3 = conv1d_transpose_direct( + inp, ker, strides=strides, padding=padding + ) + t_d_end = time.time() + print( + "Direct version time taken: {:.0f} ms".format( + (t_d_end - t_d_start) * 1e3 + ) + ) + + except BaseException: + print("-" * 50) + print("Python Error in calculating output") + traceback.print_exc() + error = True + + if out.shape != out2.shape: + print("-" * 50) + print("Error in returned output shape") + error = True + elif np.abs(out - out2).max() > 3e-6: + print("-" * 50) + print("Error in output values") + error = True + elif mode == "transposed": + if np.abs(out - out3).max() > 3e-6: + print("-" * 50) + print("Error in direct output values") + error = True + + if error: + print("Batch size:", nb) + print("Input shape:", (nh, nc)) + print("Number of filters:", nf) + print("Kernel size:", nk) + print("Strides:", strides) + print("Padding:", padding) + print("Mode:", mode) + print("Expected output shape:", out.shape[1:]) + print("Calculated output shape:", calc_out_shape) + print("Returned output shape:", out2.shape[1:]) + print('Output mismatch error:', np.abs(out - out2).max()) + if mode == 'transposed': + print('Direct output mismatch error:', np.abs(out - out3).max(), np.abs(out2 - out3).max()) + break + if not error: + print("Tested with {} normal, {} transposed, {} valid and {} same padding cases".format(mn, mt, pv, ps)) + print('All tests passed!') diff --git a/tests/test_advanced_activation_layers.py b/tests/test_advanced_activation_layers.py index c9e81aa..1edf897 100644 --- a/tests/test_advanced_activation_layers.py +++ b/tests/test_advanced_activation_layers.py @@ -6,14 +6,11 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras +import keras from keras2c import keras2c_main -import subprocess import time -import os from test_core_layers import build_and_run -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -25,6 +22,16 @@ class TestAdvancedActivation(unittest.TestCase): """tests for advanced activation layers""" + def test_swish(self): + inshp = (9, 7, 6, 3) + a = keras.layers.Input(inshp) + b = keras.layers.Activation('swish')(a) + model = keras.models.Model(inputs=a, outputs=b) + name = 'test___swish' + str(int(time.time())) + keras2c_main.k2c(model, name) + rcode = build_and_run(name) + self.assertEqual(rcode, 0) + def test_LeakyReLU(self): inshp = (9, 7, 6, 3) alpha = 0.5 @@ -61,7 +68,7 @@ def test_ThresholdedReLU(self): inshp = (3, 6, 19, 11) theta = 0.3 a = keras.layers.Input(inshp) - b = keras.layers.ThresholdedReLU(theta=theta)(a) + b = keras.layers.ReLU(threshold=theta)(a) model = keras.models.Model(inputs=a, outputs=b) name = 'test___ThresholdedReLU' + str(int(time.time())) keras2c_main.k2c(model, name) diff --git a/tests/test_checks.py b/tests/test_checks.py index a5f9108..6af8424 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -6,13 +6,10 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras +import keras from keras2c import keras2c_main -import subprocess -import time import numpy as np -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -32,7 +29,7 @@ def test_is_model(self): def test_is_valid_cname(self): inshp = (10, 8) name = 'foobar' - a = keras.layers.Input(inshp, name='f/oo') + a = keras.layers.Input(inshp, name='1foo') b = keras.layers.Dense(10)(a) model = keras.models.Model(inputs=a, outputs=b) self.assertRaises(AssertionError, keras2c_main.k2c, model, name) diff --git a/tests/test_convolution_layers.py b/tests/test_convolution_layers.py index 2952125..e5b8c18 100644 --- a/tests/test_convolution_layers.py +++ b/tests/test_convolution_layers.py @@ -6,21 +6,22 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras +import keras from keras2c import keras2c_main -import subprocess import time -import os from test_core_layers import build_and_run -import tensorflow as tf -tf.compat.v1.disable_eager_execution() -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" + +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" class TestConvolutionLayers(unittest.TestCase): """tests for convolution layers""" @@ -222,7 +223,10 @@ def test_ZeroPad3D(self): pad_back = 4 a = keras.layers.Input(inshp) b = keras.layers.ZeroPadding3D( - padding=((pad_top, pad_bottom), (pad_left, pad_right), (pad_front, pad_back)))(a) + padding=((pad_top, pad_bottom), + (pad_left, pad_right), + (pad_front, pad_back)) + )(a) model = keras.models.Model(inputs=a, outputs=b) name = 'test___ZeroPad3D' + str(int(time.time())) keras2c_main.k2c(model, name) @@ -269,7 +273,10 @@ def test_Cropping3D(self): crop_back = 0 a = keras.layers.Input(inshp) b = keras.layers.Cropping3D( - cropping=((crop_top, crop_bottom), (crop_left, crop_right), (crop_front, crop_back)))(a) + cropping=((crop_top, crop_bottom), + (crop_left, crop_right), + (crop_front, crop_back)) + )(a) model = keras.models.Model(inputs=a, outputs=b) name = 'test___Cropping3D' + str(int(time.time())) keras2c_main.k2c(model, name) diff --git a/tests/test_convtranspose_layers.py b/tests/test_convtranspose_layers.py new file mode 100644 index 0000000..197a098 --- /dev/null +++ b/tests/test_convtranspose_layers.py @@ -0,0 +1,52 @@ +"""test_convolution_layers.py +This file is part of the test suite for keras2c +Implements tests for convolution layers +""" + +#!/usr/bin/env python3 + +import unittest +import keras +from keras2c import keras2c_main +import time +from test_core_layers import build_and_run + +import numpy as np + +__author__ = "Anchal Gupta" +__copyright__ = "Copyright 2024, Anchal Gupta" +__license__ = "MIT" +__maintainer__ = "Anchal Gupta, https://github.com/anchal-physics/keras2c" +__email__ = "guptaa@fusion.gat.com" + + +class TestConvolutionTransposeLayers(unittest.TestCase): + """tests for convolution layers""" + + def test_Conv1DTranspose1(self): + for tno in range (10): + nh = np.random.randint(2, 50) + nc = np.random.randint(1, 50) + nf = np.random.randint(1, 50) + nk = np.random.randint(1, nh) + strides = np.random.randint(1, max(nk, 2)) + inshp = (nh, nc) + if tno % 2 == 0: + padding = 'valid' + else: + padding = 'same' + dilation_rate = 1 + activation = None # 'relu' + a = keras.layers.Input(inshp) + b = keras.layers.Conv1DTranspose(filters=nf, + kernel_size=nk, + strides=strides, + padding=padding, + dilation_rate=dilation_rate, + activation=activation, + use_bias=False)(a) + model = keras.models.Model(inputs=a, outputs=b) + name = 'test___Conv1DTranspose1' + str(int(time.time())) + keras2c_main.k2c(model, name) + rcode = build_and_run(name) + self.assertEqual(rcode, 0) diff --git a/tests/test_core_layers.py b/tests/test_core_layers.py index 0bfb930..7ca1995 100644 --- a/tests/test_core_layers.py +++ b/tests/test_core_layers.py @@ -6,49 +6,82 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras +import keras from keras2c import keras2c_main import subprocess import time import os -import tensorflow as tf -tf.compat.v1.disable_eager_execution() +import shutil +import platform -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" -CC = 'gcc' - +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" def build_and_run(name, return_output=False): - cwd = os.getcwd() - os.chdir(os.path.abspath('./include/')) - lib_code = subprocess.run(['make']).returncode - os.chdir(os.path.abspath(cwd)) - if lib_code != 0: - return 'lib build failed' + CC = 'gcc' + + repo_path = os.path.abspath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), '../') + ) + include_path = os.path.join(repo_path, './include/') if os.environ.get('CI'): - ccflags = '-g -Og -std=c99 --coverage -I./include/' + ccflags = '-g -Og -std=c99 --coverage ' else: - ccflags = '-Ofast -std=c99 -I./include/' + ccflags = '-Ofast -std=c99 ' - cc = CC + ' ' + ccflags + ' -o ' + name + ' ' + name + '.c ' + \ + cwd = os.getcwd() + in_repo_root = cwd == repo_path + if not in_repo_root: + shutil.copytree(include_path, './include') + include_path = os.path.abspath('./include/') + + if platform.system() == 'Linux': + cwd = os.getcwd() + os.chdir('./include') + lib_code = subprocess.run(['make']).returncode + os.chdir(os.path.abspath(cwd)) + if lib_code != 0: + return 'lib build failed' + + cc = CC + ' ' + ccflags + ' -o ' + name + ' ' + name + '.c ' + \ name + '_test_suite.c -L./include/ -l:libkeras2c.a -lm' + elif platform.system() == 'Darwin': + inc_files = ' '.join( + [os.path.join(include_path, f) + for f in os.listdir(include_path) if f.endswith('.c')]) + cc = CC + ' ' + ccflags + ' -o ' + name + ' ' + name + '.c ' + \ + name + '_test_suite.c ' + inc_files + + elif platform.system() == 'Windows': + if shutil.which('gcc'): + exe_name = f"{name}.exe" + inc_files = ' '.join( + os.path.join(include_path, f) + for f in os.listdir(include_path) if f.endswith('.c')) + cc = f"{CC} {ccflags} -o {exe_name} {name}.c {name}_test_suite.c {inc_files}" + else: + return 'gcc not found' build_code = subprocess.run(cc.split()).returncode if build_code != 0: return 'build failed' proc_output = subprocess.run(['./' + name]) rcode = proc_output.returncode if rcode == 0: - if not os.environ.get('CI'): - subprocess.run('rm ' + name + '*', shell=True) - return (rcode, proc_output.stdout) if return_output else rcode + subprocess.run('rm ' + name + '*', shell=True) + if not in_repo_root: + shutil.rmtree('./include') + return (rcode, proc_output.stdout) if return_output else rcode return rcode @@ -237,7 +270,3 @@ def test_SharedLayer1(self): keras2c_main.k2c(model, name) rcode = build_and_run(name) self.assertEqual(rcode, 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_malloc.py b/tests/test_malloc.py index 9813555..338a765 100644 --- a/tests/test_malloc.py +++ b/tests/test_malloc.py @@ -6,14 +6,11 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras +import keras from keras2c import keras2c_main -import subprocess import time -import os from test_core_layers import build_and_run -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -61,7 +58,3 @@ def test_Malloc2(self): keras2c_main.k2c(model, name, malloc=True) rcode = build_and_run(name) self.assertEqual(rcode, 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_merge_layers.py b/tests/test_merge_layers.py index 7ab2e8b..4c34ba0 100644 --- a/tests/test_merge_layers.py +++ b/tests/test_merge_layers.py @@ -8,12 +8,9 @@ import unittest import tensorflow.keras as keras from keras2c import keras2c_main -import subprocess import time -import os from test_core_layers import build_and_run -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -184,7 +181,3 @@ def test_Concatenate3(self): keras2c_main.k2c(model, name) rcode = build_and_run(name) self.assertEqual(rcode, 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_models.py b/tests/test_models.py index 6b68ff8..66e6f4d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -6,19 +6,14 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras -from tensorflow.keras import models -from tensorflow.keras import layers -from tensorflow.keras.layers import Input, Dense, LSTM, Conv1D, Conv2D, ConvLSTM2D, Dot, Add, Multiply, Concatenate, Reshape, Permute, ZeroPadding1D, Cropping1D -from tensorflow.keras.models import Model -import numpy as np +import keras +from keras import layers +from keras.layers import Input, Dense, LSTM, Conv2D, Add, Concatenate, Reshape, Permute +from keras.models import Model from keras2c import keras2c_main -import subprocess import time -import os from test_core_layers import build_and_run -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -151,30 +146,39 @@ def test_ProfilePredictorConv1D(self): prof_act = [] for i in range(num_targets): - current_profiles_processed_1 = layers.Conv1D(filters=8, kernel_size=2, - padding='same', - activation='relu')(current_profiles_processed_0) - current_profiles_processed_2 = layers.Conv1D(filters=8, kernel_size=4, - padding='same', - activation='relu')(current_profiles_processed_1) - current_profiles_processed_3 = layers.Conv1D(filters=8, kernel_size=8, - padding='same', - activation='relu')(current_profiles_processed_2) + current_profiles_processed_1 = ( + layers.Conv1D( + filters=8, kernel_size=2, padding='same', activation='relu' + )(current_profiles_processed_0) + ) + current_profiles_processed_2 = layers.Conv1D( + filters=8, kernel_size=4, padding='same', activation='relu' + )(current_profiles_processed_1) + current_profiles_processed_3 = layers.Conv1D( + filters=8, kernel_size=8, padding='same', activation='relu' + )(current_profiles_processed_2) final_output = layers.Concatenate()( - [current_profiles_processed_1, current_profiles_processed_2, current_profiles_processed_3]) - final_output = layers.Conv1D(filters=10, kernel_size=4, - padding='same', activation='tanh')(final_output) - final_output = layers.Conv1D(filters=1, kernel_size=4, - padding='same', activation='linear')(final_output) - final_output = layers.Reshape(target_shape=( - profile_length,), name="target_"+target_profile_names[i])(final_output) + [current_profiles_processed_1, + current_profiles_processed_2, + current_profiles_processed_3] + ) + final_output = layers.Conv1D( + filters=10, kernel_size=4, padding='same', activation='tanh' + )(final_output) + final_output = layers.Conv1D( + filters=1, kernel_size=4, padding='same', activation='linear' + )(final_output) + final_output = layers.Reshape( + target_shape=(profile_length,), name="target_"+target_profile_names[i] + )(final_output) prof_act.append(final_output) print(len(prof_act)) - model = Model(inputs=profile_inputs + actuator_past_inputs + - actuator_future_inputs, outputs=prof_act) + model = Model( + inputs=profile_inputs + actuator_past_inputs + actuator_future_inputs, + outputs=prof_act) name = 'test___ProfilePredictorConv1D' + str(int(time.time())) keras2c_main.k2c(model, name) @@ -216,22 +220,27 @@ def test_ProfilePredictorConv2D(self): # shape = (lookback, length, channels=num_profiles) profiles = Conv2D(filters=int(num_profiles*max_channels/8), kernel_size=(1, int(profile_length/12)), - strides=(1, 1), padding='same', activation=std_activation)(profiles) + strides=(1, 1), padding='same', activation=std_activation + )(profiles) profiles = Conv2D(filters=int(num_profiles*max_channels/4), kernel_size=(1, int(profile_length/8)), - strides=(1, 1), padding='same', activation=std_activation)(profiles) + strides=(1, 1), padding='same', activation=std_activation + )(profiles) profiles = Conv2D(filters=int(num_profiles*max_channels/2), kernel_size=(1, int(profile_length/6)), - strides=(1, 1), padding='same', activation=std_activation)(profiles) + strides=(1, 1), padding='same', activation=std_activation + )(profiles) profiles = Conv2D(filters=int(num_profiles*max_channels), kernel_size=(1, int(profile_length/4)), - strides=(1, 1), padding='same', activation=std_activation)(profiles) + strides=(1, 1), padding='same', activation=std_activation + )(profiles) # shape = (lookback, length, channels) if profile_lookback > 1: - profiles = Conv2D(filters=int(num_profiles*max_channels), kernel_size=(profile_lookback, 1), - strides=(1, 1), padding='valid', activation=std_activation)(profiles) - profiles = Reshape((profile_length, int( - num_profiles*max_channels)))(profiles) + profiles = Conv2D(filters=int(num_profiles*max_channels), + kernel_size=(profile_lookback, 1), + strides=(1, 1), padding='valid', activation=std_activation + )(profiles) + profiles = Reshape((profile_length, int(num_profiles*max_channels)))(profiles) # shape = (length, channels) actuator_future_inputs = [] @@ -239,9 +248,11 @@ def test_ProfilePredictorConv2D(self): actuators = [] for i in range(num_actuators): actuator_future_inputs.append( - Input(future_actuator_inshape, name='input_future_' + actuator_names[i])) + Input(future_actuator_inshape, + name='input_future_' + actuator_names[i])) actuator_past_inputs.append( - Input(past_actuator_inshape, name='input_past_' + actuator_names[i])) + Input(past_actuator_inshape, + name='input_past_' + actuator_names[i])) actuators.append(Concatenate( axis=-1)([actuator_past_inputs[i], actuator_future_inputs[i]])) actuators[i] = Reshape( @@ -250,16 +261,22 @@ def test_ProfilePredictorConv2D(self): # shaoe = (time, num_actuators) actuators = Dense(units=int(num_profiles*max_channels/8), activation=std_activation)(actuators) - # actuators = Conv1D(filters=int(num_profiles*max_channels/8), kernel_size=3, strides=1, - # padding='causal', activation=std_activation)(actuators) + # actuators = Conv1D(filters=int(num_profiles*max_channels/8), + # kernel_size=3, strides=1,padding='causal', + # activation=std_activation + # )(actuators) actuators = Dense(units=int(num_profiles*max_channels/4), activation=std_activation)(actuators) - # actuators = Conv1D(filters=int(num_profiles*max_channels/4), kernel_size=3, strides=1, - # padding='causal', activation=std_activation)(actuators) + # actuators = Conv1D(filters=int(num_profiles*max_channels/4), + # kernel_size=3, strides=1, padding='causal', + # activation=std_activation + # )(actuators) actuators = Dense(units=int(num_profiles*max_channels/2), activation=std_activation)(actuators) - actuators = LSTM(units=int(num_profiles*max_channels), activation=std_activation, - recurrent_activation='hard_sigmoid')(actuators) + actuators = LSTM(units=int(num_profiles*max_channels), + activation=std_activation, + recurrent_activation='hard_sigmoid' + )(actuators) actuators = Reshape((int(num_profiles*max_channels), 1))(actuators) # shape = (channels, 1) actuators = Dense(units=int(profile_length/4), @@ -278,22 +295,35 @@ def test_ProfilePredictorConv2D(self): prof_act = [] for i in range(num_targets): - prof_act.append(Conv2D(filters=max_channels, kernel_size=(1, int(profile_length/4)), strides=(1, 1), - padding='same', activation=std_activation)(merged)) + prof_act.append( + Conv2D(filters=max_channels, kernel_size=(1, int(profile_length/4)), + strides=(1, 1), padding='same', activation=std_activation + )(merged)) # shape = (1,length,max_channels) - prof_act[i] = Conv2D(filters=int(max_channels/2), kernel_size=(1, int(profile_length/8)), - strides=(1, 1), padding='same', activation=std_activation)(prof_act[i]) - prof_act[i] = Conv2D(filters=int(max_channels/4), kernel_size=(1, int(profile_length/6)), - strides=(1, 1), padding='same', activation=std_activation)(prof_act[i]) - prof_act[i] = Conv2D(filters=int(max_channels/8), kernel_size=(1, int(profile_length/4)), - strides=(1, 1), padding='same', activation=std_activation)(prof_act[i]) - prof_act[i] = Conv2D(filters=1, kernel_size=(1, int(profile_length/4)), strides=(1, 1), - padding='same', activation=None)(prof_act[i]) + prof_act[i] = Conv2D( + filters=int(max_channels/2), kernel_size=(1, int(profile_length/8)), + strides=(1, 1), padding='same', activation=std_activation + )(prof_act[i]) + prof_act[i] = Conv2D( + filters=int(max_channels/4), kernel_size=(1, int(profile_length/6)), + strides=(1, 1), padding='same', activation=std_activation + )(prof_act[i]) + prof_act[i] = Conv2D( + filters=int(max_channels/8), kernel_size=(1, int(profile_length/4)), + strides=(1, 1), padding='same', activation=std_activation + )(prof_act[i]) + prof_act[i] = Conv2D( + filters=1, kernel_size=(1, int(profile_length/4)), strides=(1, 1), + padding='same', activation=None + )(prof_act[i]) # shape = (1,length,1) - prof_act[i] = Reshape((profile_length,), name='target_' + - target_profile_names[i])(prof_act[i]) - model = Model(inputs=profile_inputs + actuator_past_inputs + - actuator_future_inputs, outputs=prof_act) + prof_act[i] = Reshape( + (profile_length,), name='target_' + target_profile_names[i] + )(prof_act[i]) + model = Model( + inputs=profile_inputs + actuator_past_inputs + actuator_future_inputs, + outputs=prof_act + ) name = 'test___ProfilePredictorConv2D' + str(int(time.time())) keras2c_main.k2c(model, name) rcode = build_and_run(name) @@ -337,5 +367,3 @@ def test_ProfilePredictorConv2D(self): # keras2c_main.k2c(model, name) # rcode = build_and_run(name) # self.assertEqual(rcode, 0) -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_pooling_layers.py b/tests/test_pooling_layers.py index fbe7776..e4ffda0 100644 --- a/tests/test_pooling_layers.py +++ b/tests/test_pooling_layers.py @@ -6,14 +6,11 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras +import keras from keras2c import keras2c_main -import subprocess import time -import os from test_core_layers import build_and_run -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -82,7 +79,7 @@ def test_AveragePooling1D2(self): model = keras.models.Model(inputs=a, outputs=b) name = 'test___AveragePooling1D2' + str(int(time.time())) keras2c_main.k2c(model, name) - rcode = build_and_run(name) + rcode = build_and_run(name) # TODO gives an average error of infinity self.assertEqual(rcode, 0) def test_MaxPooling2D1(self): @@ -142,7 +139,7 @@ def test_AveragePooling2D2(self): model = keras.models.Model(inputs=a, outputs=b) name = 'test___AveragePooling2D2' + str(int(time.time())) keras2c_main.k2c(model, name) - rcode = build_and_run(name) + rcode = build_and_run(name) # TODO gives an average error of infinity self.assertEqual(rcode, 0) def test_GlobalAveragePooling1D(self): diff --git a/tests/test_recurrent_layers.py b/tests/test_recurrent_layers.py index c07cab2..0a11370 100644 --- a/tests/test_recurrent_layers.py +++ b/tests/test_recurrent_layers.py @@ -6,14 +6,11 @@ #!/usr/bin/env python3 import unittest -import tensorflow.keras as keras +import keras from keras2c import keras2c_main -import subprocess import time -import os from test_core_layers import build_and_run -import tensorflow as tf -tf.compat.v1.disable_eager_execution() + __author__ = "Rory Conlin" __copyright__ = "Copyright 2020, Rory Conlin" @@ -152,7 +149,3 @@ def test_GRU3(self): keras2c_main.k2c(model, name) rcode = build_and_run(name) self.assertEqual(rcode, 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_split_layers.py b/tests/test_split_layers.py new file mode 100644 index 0000000..a1fd00c --- /dev/null +++ b/tests/test_split_layers.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +import unittest +import keras +from keras2c import keras2c_main +import time +import numpy as np +from test_core_layers import build_and_run +import tensorflow as tf + + +class TestSplitLayers(unittest.TestCase): + """tests for split layers""" + + def test_split_layers(self): + for tno in range(10): + n_splits = np.random.randint(2, 10) + in_dim = np.random.randint(1 * n_splits, 10 * n_splits) + + # Method 1: Random distribution that guarantees validity + split_sizes = [1] * n_splits # Start with minimum size of 1 for each split + remaining = in_dim - n_splits # Distribute the remaining dimensions + + # Randomly distribute the remaining dimensions + for _ in range(remaining): + idx = np.random.randint(0, n_splits) + split_sizes[idx] += 1 + + # Convert to list of integers (sometimes numpy types cause issues) + split_sizes = [int(s) for s in split_sizes] + + # Create model with these pre-computed split sizes + a = keras.layers.Input((in_dim,)) + + cumsum = np.cumsum(split_sizes[:-1]).tolist() # Don't include the last one + b = keras.ops.split(a, indices_or_sections=cumsum, axis=1) + + model = keras.models.Model(inputs=a, outputs=b) + name = 'test___SplitLayer' + str(int(time.time())) + keras2c_main.k2c(model, name) + rcode = build_and_run(name) + self.assertEqual(rcode, 0) diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index 289518e..2565d71 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -6,32 +6,35 @@ #!/usr/bin/env python3 from test_core_layers import build_and_run -import numpy as np -import os import time -import subprocess from keras2c import keras2c_main import unittest -import tensorflow.keras as keras -from tensorflow.python.framework.ops import disable_eager_execution -disable_eager_execution() +import keras -__author__ = "Rory Conlin" -__copyright__ = "Copyright 2020, Rory Conlin" -__license__ = "MIT" -__maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" -__email__ = "wconlin@princeton.edu" +# Original author +# __author__ = "Rory Conlin" +# __copyright__ = "Copyright 2020, Rory Conlin" +# __license__ = "MIT" +# __maintainer__ = "Rory Conlin, https://github.com/f0uriest/keras2c" +# __email__ = "wconlin@princeton.edu" + +# Modified by +__author__ = "Anchal Gupta" +__email__ = "guptaa@fusion.gat.com" class TestWrappers(unittest.TestCase): """tests for layer wrappers""" def test_Bidirectional1(self): model = keras.models.Sequential() - model.add(keras.layers.Bidirectional(keras.layers.LSTM(10, return_sequences=True), - input_shape=(5, 10), merge_mode='concat')) - model.add(keras.layers.Bidirectional(keras.layers.LSTM(10, return_sequences=True), - merge_mode='mul')) + model.add(keras.layers.Bidirectional( + keras.layers.LSTM(10, return_sequences=True), + input_shape=(5, 10), merge_mode='concat') + ) + model.add(keras.layers.Bidirectional( + keras.layers.LSTM(10, return_sequences=True), merge_mode='mul') + ) model.add(keras.layers.Dense(5)) model.build() name = 'test___Bidirectional1' + str(int(time.time())) @@ -42,10 +45,12 @@ def test_Bidirectional1(self): def test_Bidirectional2(self): model = keras.models.Sequential() model.add(keras.layers.Dense(5, input_shape=(5, 10))) - model.add(keras.layers.Bidirectional(keras.layers.LSTM(10, return_sequences=True), - merge_mode='ave')) - model.add(keras.layers.Bidirectional(keras.layers.LSTM(10, return_sequences=True), - merge_mode='concat')) + model.add(keras.layers.Bidirectional( + keras.layers.LSTM(10, return_sequences=True), merge_mode='ave') + ) + model.add(keras.layers.Bidirectional( + keras.layers.LSTM(10, return_sequences=True), merge_mode='concat') + ) model.add(keras.layers.Dense(5)) model.build() name = 'test___Bidirectional2' + str(int(time.time())) @@ -54,7 +59,6 @@ def test_Bidirectional2(self): self.assertEqual(rcode, 0) def test_TimeDistributed1(self): - inputs = keras.layers.Input(shape=(8, 5)) outputs = keras.layers.TimeDistributed( keras.layers.Dense(7, use_bias=True,))(inputs)