majianjia / nnom

A higher-level Neural Network library for microcontrollers.
Apache License 2.0
909 stars 245 forks source link

Probability of C model does not match python output #155

Open ConciseVerbosity18 opened 2 years ago

ConciseVerbosity18 commented 2 years ago

Hello, I have followed the examples and converted the keras model to an nnom model. For my application, I need the probability of each class as an output. When I run the model, the outputs have a very small magnitude of probability regardless of the inputs. Below is my setup. Any help or guidance you can give would be very appreciated.

python setup:

from tensorflow.keras.layers import Dense, Input
from tqdm.keras import TqdmCallback
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.optimizers import SGD
from tensorflow.math import tanh
from tensorflow.keras.losses import MeanSquaredError as MSE
from tensorflow.keras import Model
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
#
# define functions and train model

def train_model(xtrain,ytrain, learnRate = .01):
    numFeatures = xtrain.shape[1]
    inputsize = [numFeatures]
    numClasses = ytrain.shape[1]

    inp = Input(shape=(inputsize))
    h1 = Dense(numFeatures*2)(inp)
    h1 = tanh(h1)
    h2 = Dense(numFeatures*3)(h1)
    h2 = tanh(h2)
    h3 = Dense(numFeatures*3)(h2)
    h3 = tanh(h3)
    h4 = Dense(numFeatures*2)(h3)
    h4 = tanh(h4)
    outs = Dense(numClasses)(h4)
    opti = SGD(learning_rate=learnRate, momentum=0.9)
    model = Model(inputs = inp, outputs = outs)
    model.compile(optimizer = opti, loss = MSE())
    history = model.fit(xtrain,ytrain,epochs = 5,batch_size=128, shuffle=True,callbacks=[TqdmCallback(verbose=1)])
    plt.semilogy(history.history['loss'],label='loss')
    plt.title('Loss History')
    plt.legend()
    plt.show()
    return model

def scale( x, out_min,  out_max): # scale a single column of data
    return (x - np.min(x)) * (out_max - out_min) / (np.max(x) - np.min(x)) + out_min

def scale_data(data1,minout=-1,maxout=1): #scale a full matrix/dataframe
    data = data1.copy()
    for i in range(data.shape[1]):
        data[:,i] = scale(data[:,i],minout,maxout)
    return data

def quantize_data(data1): # quantize scaled data
    data = data1.copy()
    data = (data*128).clip(-128,127)
    return data.astype('byte')

traindata = np.load('Features.npy')
labels = np.array(pd.read_csv('Kinematics.csv',header=None))[1:,:]

traindata = scale_data(traindata)
traindata = quantize_data(traindata)
labels = scale_data(labels)

model = train_model(traindata,labels)

# convert model and weights
import sys
sys.path.append(r'nnom\scripts')
from nnom import *

head = 'weights_mockup.h'
generate_model(model,traindata[::10],format='hwc', name=head)
# I manually fix the tanh layers in the weights_mockup.h

# generate binary testing data
def quantize_data(data1): # quantize scaled data
    data = data1.copy()
    data = (data*128).clip(-128,127)
    return data.astype('byte')
testdata = quantize_data(scale_data(np.load('FeaturesTest.npy')))
testdata.tofile('testdata_q.bin')

#main.c is built and ran in Visual Studio. Output is saved to results.csv

#read C results
output_file = r"result.csv"
cpredin = pd.read_csv(output_file, delimiter=' ')
# format results
cpred = pd.DataFrame()
cpred[0] = cpredin[cpredin['DOF'] == 0]['Value'].values
cpred[1] = cpredin[cpredin['DOF'] == 1]['Value'].values
cpred = cpred.values
predictions_python = model.predict(testdata[::10]) # downsample for speed
numDOF = 2
testlabels = np.array(pd.read_csv('Kinematicstest.csv'))
fig,axes = plt.subplots(numDOF,1,figsize=(15,5))

for i in range(numDOF):
    ax = axes[i]
    ax.plot(predictions_python[:,i], label='Python', linewidth=3)
    ax.plot(cpred[:,i],label='cpred', linewidth=5)
    ax.legend()
plt.show()

image

main.c:


#ifdef __cpluplus
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "nnom.h"
#include "weights_mockup.h"
#define num_DOF  2
float instantaneous_output[num_DOF] = {0};

    int8_t* load(const char* file, size_t* size) //loads binary file
    {
        size_t sz;
        FILE* fp;// = fopen_s(file, "rb");
        fopen_s(&fp, file, "rb");
        int8_t* input;
        assert(fp);
        fseek(fp, 0, SEEK_END);
        sz = ftell(fp);
        fseek(fp, 0, SEEK_SET);
        input = malloc(sz);
        fread(input, 1, sz, fp);
        fclose(fp);
        *size = sz;
        return input;
    }

#ifdef NNOM_USING_STATIC_MEMORY
    uint8_t static_buf[1024 * 500];
#endif

    int main(int argc, char* argv[]) {

        nnom_model_t* model;
        //nnom_predict_t* pre;
        int8_t* input;
        float prob;
        uint32_t label;
        int numfeat;
        size_t size = 0;
        input = load("testdata.bin", &size); // load binary testing data
        float true_estimate;
        FILE *op;
        fopen_s(&op,"result.csv", "w"); //open csv file for output
        fprintf(op, "DOF Value\n"); // header for csv output file
        printf("Validation Size: %d\n", (uint32_t)size);

#ifdef NNOM_USING_STATIC_MEMORY
        // when use static memory buffer, we need to set it before create
        nnom_set_static_buf(static_buf, sizeof(static_buf));
#endif
        model = nnom_model_create(); //creates model using data from weights_mockup.h

        for (size_t seek = 0; seek < size;) {
            //labels

                if (seek >= size)
                    break;
                memcpy(nnom_input_data, input + seek, sizeof(nnom_input_data));
                seek += sizeof(nnom_input_data);
                numfeat = sizeof(nnom_input_data) / sizeof(*nnom_input_data);

                // run model
                model_run(model);

                // save results
                for (int ii = 0; ii < num_DOF; ii++) {
                    fprintf(op, "%d %f\n", ii, (float)nnom_output_data[ii] / (1 << DENSE_16_OUTPUT_DEC));
                }
            //}

            printf("Processing %d%%\n", (uint32_t)(seek * 100 / size));

        }
        fclose(op);

        // model
        model_stat(model);
        model_delete(model);

        free(input);
        return 0;

    }
#ifdef __cpluplus
}
#endif

weights_mockup.h:


#include "nnom.h"

/* Weights, bias and Q format */
#define TENSOR_DENSE_11_KERNEL_0 {-19, 73, -23, -15, -62, 43, 1, 22, -9, 9, -47, 1, 43, 21, -1, -39, -6, -15, 16, -27, -24, 22, 35, -12, -13}

#define TENSOR_DENSE_11_KERNEL_0_DEC_BITS {5}

#define TENSOR_DENSE_11_BIAS_0 {0, -1, 0, -1, -1}

#define TENSOR_DENSE_11_BIAS_0_DEC_BITS {5}

#define DENSE_11_BIAS_LSHIFT {0}

#define DENSE_11_OUTPUT_RSHIFT {7}

#define TENSOR_DENSE_12_KERNEL_0 {79, -25, -70, 31, -41, -102, 75, -53, 25, -70, -32, -84, 47, 4, -12, -56, -25, -64, 49, 42, 85, 30, -114, -6, -65, -56, 104, -17, -39, 83, 70, 7, 48, 63, 17, -28, -89, -11, -49, -25, -69, -58, -38, 76, -29, 15, 69, -52, -2, -122}

#define TENSOR_DENSE_12_KERNEL_0_DEC_BITS {7}

#define TENSOR_DENSE_12_BIAS_0 {-41, -14, 22, -5, 4, 19, 22, -18, 53, -102}

#define TENSOR_DENSE_12_BIAS_0_DEC_BITS {7}

#define DENSE_12_BIAS_LSHIFT {7}

#define DENSE_12_OUTPUT_RSHIFT {9}

#define TENSOR_DENSE_13_KERNEL_0 {-4, 60, 24, 44, 12, 43, 25, -72, -26, 77, -46, 14, -5, 66, -62, -27, -59, 68, -44, 60, 73, 81, 7, -33, -10, -10, -33, -37, 55, -37, 67, 19, -40, -52, -40, -34, 84, -47, 94, 53, 31, -38, -17, -22, 49, 83, 42, -73, 87, -15, 54, 27, 65, -31, 40, -71, 15, 52, -58, -89, -57, 24, -56, -67, -72, -79, -23, 48, 54, 8, -55, -22, -76, 44, 45, 27, -17, 86, 66, 13, -11, -48, -19, 80, -45, -19, -39, 0, 60, -52, 27, -18, -41, -61, -78, -61, -14, 42, 57, 85}

#define TENSOR_DENSE_13_KERNEL_0_DEC_BITS {7}

#define TENSOR_DENSE_13_BIAS_0 {-36, -36, -15, -43, -85, -63, 4, -74, 54, 15}

#define TENSOR_DENSE_13_BIAS_0_DEC_BITS {8}

#define DENSE_13_BIAS_LSHIFT {6}

#define DENSE_13_OUTPUT_RSHIFT {9}

#define TENSOR_DENSE_14_KERNEL_0 {-42, -27, -26, -23, 37, -8, 20, -44, -19, 2, 16, -2, -43, -36, -37, -43, -13, -25, -4, -42, -31, -18, -28, 4, 19, -14, 45, -31, 1, 43, -23, -49, 43, -14, -27, 48, -31, -42, -31, 41, -15, 9, -30, 6, 70, 69, 15, 3, 46, -12}

#define TENSOR_DENSE_14_KERNEL_0_DEC_BITS {6}

#define TENSOR_DENSE_14_BIAS_0 {103, 8, 19, -10, -51}

#define TENSOR_DENSE_14_BIAS_0_DEC_BITS {7}

#define DENSE_14_BIAS_LSHIFT {6}

#define DENSE_14_OUTPUT_RSHIFT {8}

#define TENSOR_DENSE_15_KERNEL_0 {32, -70, 15, 60, -38, -68, 4, 65, -37, 93}

#define TENSOR_DENSE_15_KERNEL_0_DEC_BITS {6}

#define TENSOR_DENSE_15_BIAS_0 {-104, -50}

#define TENSOR_DENSE_15_BIAS_0_DEC_BITS {8}

#define DENSE_15_BIAS_LSHIFT {5}

#define DENSE_15_OUTPUT_RSHIFT {8}

#define TENSOR_DENSE_16_KERNEL_0 {66, -6, 0, 70}

#define TENSOR_DENSE_16_KERNEL_0_DEC_BITS {6}

#define TENSOR_DENSE_16_BIAS_0 {-96, 70}

#define TENSOR_DENSE_16_BIAS_0_DEC_BITS {11}

#define DENSE_16_BIAS_LSHIFT {2}

#define DENSE_16_OUTPUT_RSHIFT {7}

/* output q format for each layer */
#define INPUT_3_OUTPUT_DEC 0
#define INPUT_3_OUTPUT_OFFSET 0
#define DENSE_11_OUTPUT_DEC -2
#define DENSE_11_OUTPUT_OFFSET 0
#define TF_MATH_TANH_9_OUTPUT_DEC 7
#define TF_MATH_TANH_9_OUTPUT_OFFSET 0
#define DENSE_12_OUTPUT_DEC 5
#define DENSE_12_OUTPUT_OFFSET 0
#define TF_MATH_TANH_10_OUTPUT_DEC 7
#define TF_MATH_TANH_10_OUTPUT_OFFSET 0
#define DENSE_13_OUTPUT_DEC 5
#define DENSE_13_OUTPUT_OFFSET 0
#define TF_MATH_TANH_11_OUTPUT_DEC 7
#define TF_MATH_TANH_11_OUTPUT_OFFSET 0
#define DENSE_14_OUTPUT_DEC 5
#define DENSE_14_OUTPUT_OFFSET 0
#define TF_MATH_TANH_12_OUTPUT_DEC 7
#define TF_MATH_TANH_12_OUTPUT_OFFSET 0
#define DENSE_15_OUTPUT_DEC 5
#define DENSE_15_OUTPUT_OFFSET 0
#define TF_MATH_TANH_13_OUTPUT_DEC 7
#define TF_MATH_TANH_13_OUTPUT_OFFSET 0
#define DENSE_16_OUTPUT_DEC 6
#define DENSE_16_OUTPUT_OFFSET 0

/* bias shift and output shift for none-weighted layer */

/* tensors and configurations for each layer */
static int8_t nnom_input_data[5] = {0};

const nnom_shape_data_t tensor_input_3_dim[] = {5};
const nnom_qformat_param_t tensor_input_3_dec[] = {0};
const nnom_qformat_param_t tensor_input_3_offset[] = {0};
const nnom_tensor_t tensor_input_3 = {
    .p_data = (void*)nnom_input_data,
    .dim = (nnom_shape_data_t*)tensor_input_3_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_input_3_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_input_3_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_io_config_t input_3_config = {
    .super = {.name = "input_3"},
    .tensor = (nnom_tensor_t*)&tensor_input_3
};
const int8_t tensor_dense_11_kernel_0_data[] = TENSOR_DENSE_11_KERNEL_0;

const nnom_shape_data_t tensor_dense_11_kernel_0_dim[] = {5, 5};
const nnom_qformat_param_t tensor_dense_11_kernel_0_dec[] = TENSOR_DENSE_11_KERNEL_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_11_kernel_0_offset[] = {0};
const nnom_tensor_t tensor_dense_11_kernel_0 = {
    .p_data = (void*)tensor_dense_11_kernel_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_11_kernel_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_11_kernel_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_11_kernel_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 2,
    .bitwidth = 8
};
const int8_t tensor_dense_11_bias_0_data[] = TENSOR_DENSE_11_BIAS_0;

const nnom_shape_data_t tensor_dense_11_bias_0_dim[] = {5};
const nnom_qformat_param_t tensor_dense_11_bias_0_dec[] = TENSOR_DENSE_11_BIAS_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_11_bias_0_offset[] = {0};
const nnom_tensor_t tensor_dense_11_bias_0 = {
    .p_data = (void*)tensor_dense_11_bias_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_11_bias_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_11_bias_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_11_bias_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_qformat_param_t dense_11_output_shift[] = DENSE_11_OUTPUT_RSHIFT;
const nnom_qformat_param_t dense_11_bias_shift[] = DENSE_11_BIAS_LSHIFT;
const nnom_dense_config_t dense_11_config = {
    .super = {.name = "dense_11"},
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .weight = (nnom_tensor_t*)&tensor_dense_11_kernel_0,
    .bias = (nnom_tensor_t*)&tensor_dense_11_bias_0,
    .output_shift = (nnom_qformat_param_t *)&dense_11_output_shift,
    .bias_shift = (nnom_qformat_param_t *)&dense_11_bias_shift
};
const int8_t tensor_dense_12_kernel_0_data[] = TENSOR_DENSE_12_KERNEL_0;

const nnom_shape_data_t tensor_dense_12_kernel_0_dim[] = {5, 10};
const nnom_qformat_param_t tensor_dense_12_kernel_0_dec[] = TENSOR_DENSE_12_KERNEL_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_12_kernel_0_offset[] = {0};
const nnom_tensor_t tensor_dense_12_kernel_0 = {
    .p_data = (void*)tensor_dense_12_kernel_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_12_kernel_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_12_kernel_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_12_kernel_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 2,
    .bitwidth = 8
};
const int8_t tensor_dense_12_bias_0_data[] = TENSOR_DENSE_12_BIAS_0;

const nnom_shape_data_t tensor_dense_12_bias_0_dim[] = {10};
const nnom_qformat_param_t tensor_dense_12_bias_0_dec[] = TENSOR_DENSE_12_BIAS_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_12_bias_0_offset[] = {0};
const nnom_tensor_t tensor_dense_12_bias_0 = {
    .p_data = (void*)tensor_dense_12_bias_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_12_bias_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_12_bias_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_12_bias_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_qformat_param_t dense_12_output_shift[] = DENSE_12_OUTPUT_RSHIFT;
const nnom_qformat_param_t dense_12_bias_shift[] = DENSE_12_BIAS_LSHIFT;
const nnom_dense_config_t dense_12_config = {
    .super = {.name = "dense_12"},
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .weight = (nnom_tensor_t*)&tensor_dense_12_kernel_0,
    .bias = (nnom_tensor_t*)&tensor_dense_12_bias_0,
    .output_shift = (nnom_qformat_param_t *)&dense_12_output_shift,
    .bias_shift = (nnom_qformat_param_t *)&dense_12_bias_shift
};
const int8_t tensor_dense_13_kernel_0_data[] = TENSOR_DENSE_13_KERNEL_0;

const nnom_shape_data_t tensor_dense_13_kernel_0_dim[] = {10, 10};
const nnom_qformat_param_t tensor_dense_13_kernel_0_dec[] = TENSOR_DENSE_13_KERNEL_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_13_kernel_0_offset[] = {0};
const nnom_tensor_t tensor_dense_13_kernel_0 = {
    .p_data = (void*)tensor_dense_13_kernel_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_13_kernel_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_13_kernel_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_13_kernel_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 2,
    .bitwidth = 8
};
const int8_t tensor_dense_13_bias_0_data[] = TENSOR_DENSE_13_BIAS_0;

const nnom_shape_data_t tensor_dense_13_bias_0_dim[] = {10};
const nnom_qformat_param_t tensor_dense_13_bias_0_dec[] = TENSOR_DENSE_13_BIAS_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_13_bias_0_offset[] = {0};
const nnom_tensor_t tensor_dense_13_bias_0 = {
    .p_data = (void*)tensor_dense_13_bias_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_13_bias_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_13_bias_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_13_bias_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_qformat_param_t dense_13_output_shift[] = DENSE_13_OUTPUT_RSHIFT;
const nnom_qformat_param_t dense_13_bias_shift[] = DENSE_13_BIAS_LSHIFT;
const nnom_dense_config_t dense_13_config = {
    .super = {.name = "dense_13"},
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .weight = (nnom_tensor_t*)&tensor_dense_13_kernel_0,
    .bias = (nnom_tensor_t*)&tensor_dense_13_bias_0,
    .output_shift = (nnom_qformat_param_t *)&dense_13_output_shift,
    .bias_shift = (nnom_qformat_param_t *)&dense_13_bias_shift
};
const int8_t tensor_dense_14_kernel_0_data[] = TENSOR_DENSE_14_KERNEL_0;

const nnom_shape_data_t tensor_dense_14_kernel_0_dim[] = {10, 5};
const nnom_qformat_param_t tensor_dense_14_kernel_0_dec[] = TENSOR_DENSE_14_KERNEL_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_14_kernel_0_offset[] = {0};
const nnom_tensor_t tensor_dense_14_kernel_0 = {
    .p_data = (void*)tensor_dense_14_kernel_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_14_kernel_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_14_kernel_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_14_kernel_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 2,
    .bitwidth = 8
};
const int8_t tensor_dense_14_bias_0_data[] = TENSOR_DENSE_14_BIAS_0;

const nnom_shape_data_t tensor_dense_14_bias_0_dim[] = {5};
const nnom_qformat_param_t tensor_dense_14_bias_0_dec[] = TENSOR_DENSE_14_BIAS_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_14_bias_0_offset[] = {0};
const nnom_tensor_t tensor_dense_14_bias_0 = {
    .p_data = (void*)tensor_dense_14_bias_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_14_bias_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_14_bias_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_14_bias_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_qformat_param_t dense_14_output_shift[] = DENSE_14_OUTPUT_RSHIFT;
const nnom_qformat_param_t dense_14_bias_shift[] = DENSE_14_BIAS_LSHIFT;
const nnom_dense_config_t dense_14_config = {
    .super = {.name = "dense_14"},
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .weight = (nnom_tensor_t*)&tensor_dense_14_kernel_0,
    .bias = (nnom_tensor_t*)&tensor_dense_14_bias_0,
    .output_shift = (nnom_qformat_param_t *)&dense_14_output_shift,
    .bias_shift = (nnom_qformat_param_t *)&dense_14_bias_shift
};
const int8_t tensor_dense_15_kernel_0_data[] = TENSOR_DENSE_15_KERNEL_0;

const nnom_shape_data_t tensor_dense_15_kernel_0_dim[] = {5, 2};
const nnom_qformat_param_t tensor_dense_15_kernel_0_dec[] = TENSOR_DENSE_15_KERNEL_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_15_kernel_0_offset[] = {0};
const nnom_tensor_t tensor_dense_15_kernel_0 = {
    .p_data = (void*)tensor_dense_15_kernel_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_15_kernel_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_15_kernel_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_15_kernel_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 2,
    .bitwidth = 8
};
const int8_t tensor_dense_15_bias_0_data[] = TENSOR_DENSE_15_BIAS_0;

const nnom_shape_data_t tensor_dense_15_bias_0_dim[] = {2};
const nnom_qformat_param_t tensor_dense_15_bias_0_dec[] = TENSOR_DENSE_15_BIAS_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_15_bias_0_offset[] = {0};
const nnom_tensor_t tensor_dense_15_bias_0 = {
    .p_data = (void*)tensor_dense_15_bias_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_15_bias_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_15_bias_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_15_bias_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_qformat_param_t dense_15_output_shift[] = DENSE_15_OUTPUT_RSHIFT;
const nnom_qformat_param_t dense_15_bias_shift[] = DENSE_15_BIAS_LSHIFT;
const nnom_dense_config_t dense_15_config = {
    .super = {.name = "dense_15"},
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .weight = (nnom_tensor_t*)&tensor_dense_15_kernel_0,
    .bias = (nnom_tensor_t*)&tensor_dense_15_bias_0,
    .output_shift = (nnom_qformat_param_t *)&dense_15_output_shift,
    .bias_shift = (nnom_qformat_param_t *)&dense_15_bias_shift
};
const int8_t tensor_dense_16_kernel_0_data[] = TENSOR_DENSE_16_KERNEL_0;

const nnom_shape_data_t tensor_dense_16_kernel_0_dim[] = {2, 2};
const nnom_qformat_param_t tensor_dense_16_kernel_0_dec[] = TENSOR_DENSE_16_KERNEL_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_16_kernel_0_offset[] = {0};
const nnom_tensor_t tensor_dense_16_kernel_0 = {
    .p_data = (void*)tensor_dense_16_kernel_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_16_kernel_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_16_kernel_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_16_kernel_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 2,
    .bitwidth = 8
};
const int8_t tensor_dense_16_bias_0_data[] = TENSOR_DENSE_16_BIAS_0;

const nnom_shape_data_t tensor_dense_16_bias_0_dim[] = {2};
const nnom_qformat_param_t tensor_dense_16_bias_0_dec[] = TENSOR_DENSE_16_BIAS_0_DEC_BITS;
const nnom_qformat_param_t tensor_dense_16_bias_0_offset[] = {0};
const nnom_tensor_t tensor_dense_16_bias_0 = {
    .p_data = (void*)tensor_dense_16_bias_0_data,
    .dim = (nnom_shape_data_t*)tensor_dense_16_bias_0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_dense_16_bias_0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_dense_16_bias_0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_qformat_param_t dense_16_output_shift[] = DENSE_16_OUTPUT_RSHIFT;
const nnom_qformat_param_t dense_16_bias_shift[] = DENSE_16_BIAS_LSHIFT;
const nnom_dense_config_t dense_16_config = {
    .super = {.name = "dense_16"},
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .weight = (nnom_tensor_t*)&tensor_dense_16_kernel_0,
    .bias = (nnom_tensor_t*)&tensor_dense_16_bias_0,
    .output_shift = (nnom_qformat_param_t *)&dense_16_output_shift,
    .bias_shift = (nnom_qformat_param_t *)&dense_16_bias_shift
};
static int8_t nnom_output_data[2] = {0};

const nnom_shape_data_t tensor_output0_dim[] = {2};
const nnom_qformat_param_t tensor_output0_dec[] = {DENSE_16_OUTPUT_DEC};
const nnom_qformat_param_t tensor_output0_offset[] = {0};
const nnom_tensor_t tensor_output0 = {
    .p_data = (void*)nnom_output_data,
    .dim = (nnom_shape_data_t*)tensor_output0_dim,
    .q_dec = (nnom_qformat_param_t*)tensor_output0_dec,
    .q_offset = (nnom_qformat_param_t*)tensor_output0_offset,
    .qtype = NNOM_QTYPE_PER_TENSOR,
    .num_dim = 1,
    .bitwidth = 8
};

const nnom_io_config_t output0_config = {
    .super = {.name = "output0"},
    .tensor = (nnom_tensor_t*)&tensor_output0
};
/* model version */
#define NNOM_MODEL_VERSION (10000*0 + 100*4 + 3)

/* nnom model */
static nnom_model_t* nnom_model_create(void)
{
    static nnom_model_t model;
    nnom_layer_t* layer[13];

    check_model_version(NNOM_MODEL_VERSION);
    new_model(&model);

    layer[0] = input_s(&input_3_config);
    layer[1] = model.hook(dense_s(&dense_11_config), layer[0]);
    layer[2] = model.active(act_hard_tanh(INPUT_3_OUTPUT_DEC), layer[1]);
    layer[3] = model.hook(dense_s(&dense_12_config), layer[2]);
    layer[4] = model.active(act_hard_tanh(TF_MATH_TANH_9_OUTPUT_DEC), layer[3]);
    layer[5] = model.hook(dense_s(&dense_13_config), layer[4]);
    layer[6] = model.active(act_hard_tanh(TF_MATH_TANH_10_OUTPUT_DEC), layer[5]);
    layer[7] = model.hook(dense_s(&dense_14_config), layer[6]);
    layer[8] = model.active(act_hard_tanh(TF_MATH_TANH_11_OUTPUT_DEC), layer[7]);
    layer[9] = model.hook(dense_s(&dense_15_config), layer[8]);
    layer[10] = model.active(act_hard_tanh(TF_MATH_TANH_12_OUTPUT_DEC), layer[9]);
    layer[11] = model.hook(dense_s(&dense_16_config), layer[10]);
    layer[12] = model.hook(output_s(&output0_config), layer[11]);
    model_compile(&model, layer[0], layer[12]);
    return &model;
}