Skip to content
Closed

Pcs #42

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://f0uriest.github.io/keras2c/installation.html>`_ and `Usage <https://f0uriest.github.io/keras2c/usage.html>`_
For more information, see `Installation <https://plasmacontrol.github.io/keras2c/installation.html>`_ and `Usage <https://plasmacontrol.github.io/keras2c/usage.html>`_


Supported Layers
Expand Down Expand Up @@ -73,24 +73,24 @@ ToDo
Contribute
**********

- Documentation: `<https://f0uriest.github.io/keras2c/>`_
- Issue Tracker: `<https://github.com/f0uriest/keras2c/issues>`_
- Source Code: `<https://github.com/f0uriest/keras2c/>`_
- Documentation: `<https://plasmacontrol.github.io/keras2c/>`_
- Issue Tracker: `<https://github.com/plasmacontrol/keras2c/issues>`_
- Source Code: `<https://github.com/plasmacontrol/keras2c/>`_

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
Expand Down
31 changes: 24 additions & 7 deletions include/k2c_activations.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,24 @@ k2c_activationType * k2c_relu = k2c_relu_func;


/**
* ReLU activation function.
* y = {1 if x> 2.5}
* {0.2*x+0.5 if -2.5<x< 2.5}
* {0 if x<-2.5}
* Hard sigmoid activation function.
* y = clip(x+3, 0, 6) / 6
*
* :param x: array of input values. Gets overwritten by output.
* :param size: length of input array.
*/
void k2c_hard_sigmoid_func(float * x, const size_t size) {

for (size_t i=0; i < size; ++i) {
if (x[i] <= -2.5f) {
float val = x[i] + 3.0f;
if (val <= 0.0f) {
x[i] = 0.0f;
}
else if (x[i]>=2.5f) {
else if (val >= 6.0f) {
x[i] = 1.0f;
}
else {
x[i] = 0.2f*x[i] + 0.5f;
x[i] = val / 6.0f;
}
}
}
Expand Down Expand Up @@ -116,6 +115,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.
Expand Down
1 change: 1 addition & 0 deletions include/k2c_include.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ 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;
Expand Down
12 changes: 8 additions & 4 deletions include/k2c_pooling_layers.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,14 @@ void k2c_avgpool1d(k2c_tensor* output, const k2c_tensor* input, const size_t poo
for (size_t j=0, k=0; j<output->numel; j+=channels, k+=stride*channels) {
int count = 0;
for (size_t l=0; l<pool_size*channels; l+=channels) {
if (input->array[k+i+l] > -HUGE_VALF) {
if (input->array[k+i+l] > -3.4e+38f) {
output->array[j+i] += input->array[k+i+l];
++count;
}
}
output->array[i+j] /= (float)count;
if (count > 0) {
output->array[i+j] /= (float)count;
}
}
}
}
Expand Down Expand Up @@ -168,13 +170,15 @@ void k2c_avgpool2d(k2c_tensor* output, const k2c_tensor* input, const size_t * p
for (size_t n=0; n<pool_size[1]*channels; n+=channels) {
for (size_t p=0; p<pool_size[0]*channels*input->shape[1];
p+=channels*input->shape[1]) {
if (-HUGE_VALF < input->array[m+k+i+n+p]) {
if (input->array[m+k+i+n+p] > -3.4e+38f) {
output->array[l+j+i] += input->array[m+k+i+n+p];
++count;
}
}
}
output->array[l+j+i] /= (float)count;
if (count > 0) {
output->array[l+j+i] /= (float)count;
}
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions keras2c/check_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# imports
import numpy as np
from keras2c.io_parsing import layer_type, flatten
from keras2c.io_parsing import layer_type, flatten, get_model_layers
from keras2c.weights2c import Weights2C
from keras2c.layer2c import Layers2C

Expand Down Expand Up @@ -52,7 +52,7 @@ def name_check(model):

valid = True
log = ''
for layer in model.layers:
for layer in get_model_layers(model):
if not is_valid_c_name(layer.name.replace('.', '_')):
valid = False
log += "layer name '" + layer.name + "' is not a valid C name. \n"
Expand Down Expand Up @@ -85,7 +85,7 @@ def check_layer(layer):

valid = True
log = ''
for layer in model.layers:
for layer in get_model_layers(model):
flag, templog = check_layer(layer)
valid = valid and flag
log += templog
Expand All @@ -104,8 +104,8 @@ def activation_supported_check(model):
"""

supported_activations = ['linear', 'relu', 'softmax', 'softplus',
'softsign', 'relu', 'tanh', 'sigmoid',
'hard_sigmoid', 'exponential']
'softsign', 'relu', 'tanh', 'sigmoid', 'swish',
'silu', 'hard_sigmoid', 'exponential']

def check_layer(layer):
valid = True
Expand All @@ -132,7 +132,7 @@ def check_layer(layer):

valid = True
log = ''
for layer in model.layers:
for layer in get_model_layers(model):
flag, templog = check_layer(layer)
valid = valid and flag
log += templog
Expand Down Expand Up @@ -203,7 +203,7 @@ def check_layer(layer):

valid = True
log = ''
for layer in model.layers:
for layer in get_model_layers(model):
flag, templog = check_layer(layer)
valid = valid and flag
log += templog
Expand Down
106 changes: 96 additions & 10 deletions keras2c/io_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,78 @@ def layer_type(layer):
return layer.__class__.__name__


def get_model_layers(model):
"""Gets all layers/operations in the model that need code generation.

In Keras 3, some operations (like Split) appear in model._operations
but not in model.layers. This function returns a combined list.

Args:
model (keras Model): model to parse

Returns:
layers (list): list of all layers/operations
"""
layers = list(model.layers)
seen_names = {l.name for l in layers}
if hasattr(model, '_operations'):
for op in model._operations:
if op.name not in seen_names:
layers.append(op)
seen_names.add(op.name)
return layers


def get_real_tensor_names(model):
"""Gets the set of tensor names that are part of the real model graph.

Traces backward from model outputs through inbound nodes, collecting all
tensor names that are reachable. This filters out internal sub-layer
tensors (e.g., from Bidirectional's internal forward/backward calls).

Args:
model (keras Model): model to parse

Returns:
real_names (set): set of tensor names in the real model graph
"""
visited = set()
queue = []
for t in model.outputs:
queue.append(t)
for t in model.inputs:
queue.append(t)

all_layers = get_model_layers(model)

while queue:
t = queue.pop(0)
tname = parse_io_name(t.name)
if tname in visited:
continue
visited.add(tname)
for layer in all_layers:
for node in getattr(layer, '_inbound_nodes', []):
out_t = getattr(node, 'output_tensors', None)
if out_t is None:
continue
if not isinstance(out_t, (list, tuple)):
out_t = [out_t]
matched = False
for ot in out_t:
if parse_io_name(ot.name) == tname:
matched = True
break
if matched:
inp_t = node.input_tensors
if inp_t is not None:
if not isinstance(inp_t, (list, tuple)):
inp_t = [inp_t]
for it in inp_t:
queue.append(it)
return visited


def get_all_io_names(model):
"""Gets names of all node names in the model

Expand All @@ -41,7 +113,8 @@ def get_all_io_names(model):
io (list): names of all the nodes in the model
"""

a = [get_layer_io_names(layer) for layer in model.layers]
valid = get_real_tensor_names(model)
a = [get_layer_io_names(layer, valid) for layer in get_model_layers(model)]
return list(set(flatten(a)))

def parse_io_name(name):
Expand Down Expand Up @@ -89,11 +162,14 @@ def get_layer_num_io(layer):
return num_inputs, num_outputs


def get_layer_io_names(layer):
def get_layer_io_names(layer, valid_tensors=None):
"""Gets the names of the inputs and outputs of a layer

Args:
layer (keras Layer): layer you want to parse
valid_tensors (set, optional): if provided, only include nodes whose
output tensors are in this set. Used to filter out internal
sub-layer nodes (e.g., from Bidirectional wrappers).

Returns:
inputs (list): names of all the input nodes to the layer
Expand All @@ -110,28 +186,38 @@ def get_layer_io_names(layer):
# is the input a list?
node_inputs = node.input_tensors
if node_inputs is None:
inputs.append([])
node_inp = []
else:
if isinstance(node_inputs, (list, tuple)):
if len(node_inputs) == 1:
inputs.append(parse_io_name(node_inputs[0].name))
node_inp = parse_io_name(node_inputs[0].name)
else:
inputs.append([parse_io_name(t.name) for t in node_inputs])
node_inp = [parse_io_name(t.name) for t in node_inputs]
else:
# single tensor
inputs.append(parse_io_name(node_inputs.name))
node_inp = parse_io_name(node_inputs.name)

node_outputs = getattr(node, "output_tensors", None)
if node_outputs is None:
outputs.append([])
node_out = []
else:
if isinstance(node_outputs, (list, tuple)):
if len(node_outputs) == 1:
outputs.append(parse_io_name(node_outputs[0].name))
node_out = parse_io_name(node_outputs[0].name)
else:
outputs.append([parse_io_name(t.name) for t in node_outputs])
node_out = [parse_io_name(t.name) for t in node_outputs]
else:
outputs.append(parse_io_name(node_outputs.name))
node_out = parse_io_name(node_outputs.name)

# Filter: if valid_tensors provided, only include nodes whose outputs
# are in the valid set (filters out internal sub-layer nodes)
if valid_tensors is not None:
flat_out = flatten([node_out]) if node_out else []
if not any(o in valid_tensors for o in flat_out):
continue

inputs.append(node_inp)
outputs.append(node_out)

return inputs, outputs

Expand Down
Loading
Loading