forked from xuos/xiuos
APP_Framework/Framework/:add NNoM(v0.4.3) source code
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
fully_connected_opt_weight_generation.py - is from https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/NN/Scripts/NNFunctions witch is not a part of NNoM
|
||||
|
||||
Please refer to NNoM documents for its usages.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# package
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
This file is apart of CMSIS-NN release
|
||||
https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/NN/Scripts/NNFunctions
|
||||
'''
|
||||
|
||||
import numpy as np
|
||||
|
||||
def convert_to_x4_q7_weights(weights):
|
||||
[r, h, w, c] = weights.shape
|
||||
weights = np.reshape(weights, (r, h*w*c))
|
||||
num_of_rows = r
|
||||
num_of_cols = h*w*c
|
||||
new_weights = np.copy(weights)
|
||||
new_weights = np.reshape(new_weights, (r*h*w*c))
|
||||
counter = 0
|
||||
for i in range(int(num_of_rows/4)):
|
||||
# we only need to do the re-ordering for every 4 rows
|
||||
row_base = 4*i
|
||||
for j in range(int(num_of_cols/4)):
|
||||
# for each 4 entries
|
||||
column_base = 4*j
|
||||
new_weights[counter] = weights[row_base ][column_base ]
|
||||
new_weights[counter+1] = weights[row_base+1][column_base ]
|
||||
new_weights[counter+2] = weights[row_base ][column_base+2]
|
||||
new_weights[counter+3] = weights[row_base+1][column_base+2]
|
||||
new_weights[counter+4] = weights[row_base+2][column_base ]
|
||||
new_weights[counter+5] = weights[row_base+3][column_base ]
|
||||
new_weights[counter+6] = weights[row_base+2][column_base+2]
|
||||
new_weights[counter+7] = weights[row_base+3][column_base+2]
|
||||
|
||||
new_weights[counter+8] = weights[row_base ][column_base+1]
|
||||
new_weights[counter+9] = weights[row_base+1][column_base+1]
|
||||
new_weights[counter+10] = weights[row_base ][column_base+3]
|
||||
new_weights[counter+11] = weights[row_base+1][column_base+3]
|
||||
new_weights[counter+12] = weights[row_base+2][column_base+1]
|
||||
new_weights[counter+13] = weights[row_base+3][column_base+1]
|
||||
new_weights[counter+14] = weights[row_base+2][column_base+3]
|
||||
new_weights[counter+15] = weights[row_base+3][column_base+3]
|
||||
counter = counter + 16
|
||||
# the remaining ones are in order
|
||||
for j in range((int)(num_of_cols-num_of_cols%4), int(num_of_cols)):
|
||||
new_weights[counter] = weights[row_base][j]
|
||||
new_weights[counter+1] = weights[row_base+1][j]
|
||||
new_weights[counter+2] = weights[row_base+2][j]
|
||||
new_weights[counter+3] = weights[row_base+3][j]
|
||||
counter = counter + 4
|
||||
return new_weights
|
||||
|
||||
def convert_to_x4_q15_weights(weights):
|
||||
[r, h, w, c] = weights.shape
|
||||
weights = np.reshape(weights, (r, h*w*c))
|
||||
num_of_rows = r
|
||||
num_of_cols = h*w*c
|
||||
new_weights = np.copy(weights)
|
||||
new_weights = np.reshape(new_weights, (r*h*w*c))
|
||||
counter = 0
|
||||
for i in range(int(num_of_rows/4)):
|
||||
# we only need to do the re-ordering for every 4 rows
|
||||
row_base = 4*i
|
||||
for j in range(int(num_of_cols/2)):
|
||||
# for each 2 entries
|
||||
column_base = 2*j
|
||||
new_weights[counter] = weights[row_base ][column_base ]
|
||||
new_weights[counter+1] = weights[row_base ][column_base+1]
|
||||
new_weights[counter+2] = weights[row_base+1][column_base ]
|
||||
new_weights[counter+3] = weights[row_base+1][column_base+1]
|
||||
new_weights[counter+4] = weights[row_base+2][column_base ]
|
||||
new_weights[counter+5] = weights[row_base+2][column_base+1]
|
||||
new_weights[counter+6] = weights[row_base+3][column_base ]
|
||||
new_weights[counter+7] = weights[row_base+3][column_base+1]
|
||||
|
||||
counter = counter + 8
|
||||
# the remaining ones are in order
|
||||
for j in range((int)(num_of_cols-num_of_cols%2), int(num_of_cols)):
|
||||
new_weights[counter] = weights[row_base][j]
|
||||
new_weights[counter+1] = weights[row_base+1][j]
|
||||
new_weights[counter+2] = weights[row_base+2][j]
|
||||
new_weights[counter+3] = weights[row_base+3][j]
|
||||
counter = counter + 4
|
||||
return new_weights
|
||||
|
||||
def convert_q7_q15_weights(weights):
|
||||
[r, h, w, c] = weights.shape
|
||||
weights = np.reshape(weights, (r, h*w*c))
|
||||
num_of_rows = r
|
||||
num_of_cols = h*w*c
|
||||
new_weights = np.copy(weights)
|
||||
new_weights = np.reshape(new_weights, (r*h*w*c))
|
||||
counter = 0
|
||||
for i in range(int(num_of_rows/4)):
|
||||
# we only need to do the re-ordering for every 4 rows
|
||||
row_base = 4*i
|
||||
for j in range(int(num_of_cols/2)):
|
||||
# for each 2 entries
|
||||
column_base = 2*j
|
||||
new_weights[counter] = weights[row_base ][column_base ]
|
||||
new_weights[counter+1] = weights[row_base+1][column_base ]
|
||||
new_weights[counter+2] = weights[row_base ][column_base+1]
|
||||
new_weights[counter+3] = weights[row_base+1][column_base+1]
|
||||
new_weights[counter+4] = weights[row_base+2][column_base ]
|
||||
new_weights[counter+5] = weights[row_base+3][column_base ]
|
||||
new_weights[counter+6] = weights[row_base+2][column_base+1]
|
||||
new_weights[counter+7] = weights[row_base+3][column_base+1]
|
||||
|
||||
counter = counter + 8
|
||||
# the remaining ones are in order
|
||||
for j in range((int)(num_of_cols-num_of_cols%2), int(num_of_cols)):
|
||||
new_weights[counter] = weights[row_base][j]
|
||||
new_weights[counter+1] = weights[row_base+1][j]
|
||||
new_weights[counter+2] = weights[row_base+2][j]
|
||||
new_weights[counter+3] = weights[row_base+3][j]
|
||||
counter = counter + 4
|
||||
return new_weights
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# input dimensions
|
||||
vec_dim = 127
|
||||
row_dim = 127
|
||||
|
||||
weight = np.zeros((row_dim,vec_dim), dtype=int)
|
||||
|
||||
# generate random inputs
|
||||
for i in range(row_dim):
|
||||
for j in range(vec_dim):
|
||||
weight[i][j] = np.random.randint(256)-128
|
||||
|
||||
weight = np.reshape(weight, (row_dim, vec_dim, 1, 1))
|
||||
|
||||
outfile = open("../Ref_Implementations/fully_connected_testing_weights.h", "w")
|
||||
outfile.write("#define IP2_WEIGHT {")
|
||||
weight.tofile(outfile,sep=",",format="%d")
|
||||
outfile.write("}\n\n")
|
||||
|
||||
new_weight = convert_to_x4_q7_weights(weight)
|
||||
outfile.write("#define IP4_WEIGHT {")
|
||||
new_weight.tofile(outfile,sep=",",format="%d")
|
||||
outfile.write("}\n\n")
|
||||
|
||||
new_weight = convert_q7_q15_weights(weight)
|
||||
outfile.write("#define IP4_q7_q15_WEIGHT {")
|
||||
new_weight.tofile(outfile,sep=",",format="%d")
|
||||
outfile.write("}\n\n")
|
||||
|
||||
new_weight = convert_to_x4_q15_weights(weight)
|
||||
outfile.write("#define IP4_WEIGHT_Q15 {")
|
||||
new_weight.tofile(outfile,sep=",",format="%d")
|
||||
outfile.write("}\n\n")
|
||||
|
||||
|
||||
outfile.close()
|
||||
@@ -0,0 +1,561 @@
|
||||
'''
|
||||
Copyright (c) 2018-2020
|
||||
Jianjia Ma
|
||||
majianjia@live.com
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
Change Logs:
|
||||
Date Author Notes
|
||||
2020-05-22 Jianjia Ma The first version
|
||||
'''
|
||||
from tensorflow.keras.layers import *
|
||||
import numpy as np
|
||||
|
||||
def convert_tensor_name(t):
|
||||
return 'tensor_'+t.name.replace('/', '_').replace(':', '_')
|
||||
|
||||
def to_cstyle(data, integer=True):
|
||||
#Convert an array to C style basket, not to be used for very large array. size > options['threshold'] will lead to ...
|
||||
if(integer):
|
||||
data = np.array(data, dtype=np.int).flatten()
|
||||
else:
|
||||
data = np.array(data).flatten()
|
||||
s = np.array2string(data, separator=',')
|
||||
s = s.replace("\n","").replace("\r","").replace(' ','')
|
||||
s = s.replace(',', ', ')
|
||||
s = s.replace('(', '[').replace(')', ']')
|
||||
return s.replace('[', '{').replace(']', '}')
|
||||
|
||||
def tensor_shape(tensor, is_io_tensor=False):
|
||||
# inconsistance of TF1 and TF2
|
||||
# get tensor shape without None or ?
|
||||
try:
|
||||
shape = tensor.shape.as_list() # tf1
|
||||
except:
|
||||
shape = tensor.get_shape().as_list() # tf2
|
||||
if(shape[0] == None or is_io_tensor):
|
||||
shape = shape[1:]
|
||||
else:
|
||||
shape = shape
|
||||
# for rnn input with timestamp = None, need a better implementation
|
||||
for i in range(len(shape)):
|
||||
shape[i] = shape[i] if shape[i] is not None else 1
|
||||
return shape
|
||||
|
||||
def gen_base_config(layer):
|
||||
config = '{.name = "%s"}' % (layer.name)
|
||||
return config
|
||||
|
||||
def gen_values(var_name, var, size='', dtype='const int8_t'):
|
||||
s = '<dtype> <var_name>[<size>] = <var>;\n'
|
||||
s = s.replace('<var_name>', var_name).replace('<var>', var).replace('<size>', size).replace('<dtype>', dtype)
|
||||
return s
|
||||
|
||||
# generate tensor by the tensor config
|
||||
def gen_tensor(tensor, dec_bits, tensor_value='NULL', per_axis=False, is_io_tensor=False):
|
||||
config = '''
|
||||
const nnom_shape_data_t <tensor_name>_dim[] = <dim>;
|
||||
const nnom_qformat_param_t <tensor_name>_dec[] = <q_dec>;
|
||||
const nnom_qformat_param_t <tensor_name>_offset[] = <q_offset>;
|
||||
const nnom_tensor_t <tensor_name> = {
|
||||
.p_data = (void*)<value>,
|
||||
.dim = (nnom_shape_data_t*)<tensor_name>_dim,
|
||||
.q_dec = (nnom_qformat_param_t*)<tensor_name>_dec,
|
||||
.q_offset = (nnom_qformat_param_t*)<tensor_name>_offset,
|
||||
.qtype = <qtype>,
|
||||
.num_dim = <num_dim>,
|
||||
.bitwidth = <bitwidth>
|
||||
};
|
||||
'''
|
||||
# inconsistance of TF1 and TF2
|
||||
shape = tensor_shape(tensor, is_io_tensor)
|
||||
config = config.replace('<tensor_name>', convert_tensor_name(tensor))#.name.replace('/','_').split(':')[0]) #conv2d/kernel:0
|
||||
config = config.replace('<bitwidth>', '8')
|
||||
config = config.replace('<value>', tensor_value)
|
||||
config = config.replace('<dim>', to_cstyle(shape))
|
||||
config = config.replace('<num_dim>', str(len(shape)))
|
||||
if(type(dec_bits) == str):
|
||||
config = config.replace('<q_dec>', dec_bits)
|
||||
config = config.replace('<q_offset>', to_cstyle([0]))
|
||||
else:
|
||||
config = config.replace('<q_dec>', to_cstyle(dec_bits))
|
||||
config = config.replace('<q_offset>', to_cstyle([0]))
|
||||
if(per_axis):
|
||||
config = config.replace('<qtype>', 'NNOM_QTYPE_PER_AXIS')
|
||||
else:
|
||||
config = config.replace('<qtype>', 'NNOM_QTYPE_PER_TENSOR')
|
||||
return config
|
||||
|
||||
# create tensor by directly setting up the value
|
||||
def gen_create_tensor(tensor_name, shape, dec_bits, tensor_value='NULL', per_axis=False):
|
||||
config = '''
|
||||
const nnom_shape_data_t <tensor_name>_dim[] = <dim>;
|
||||
const nnom_qformat_param_t <tensor_name>_dec[] = <q_dec>;
|
||||
const nnom_qformat_param_t <tensor_name>_offset[] = <q_offset>;
|
||||
const nnom_tensor_t <tensor_name> = {
|
||||
.p_data = (void*)<value>,
|
||||
.dim = (nnom_shape_data_t*)<tensor_name>_dim,
|
||||
.q_dec = (nnom_qformat_param_t*)<tensor_name>_dec,
|
||||
.q_offset = (nnom_qformat_param_t*)<tensor_name>_offset,
|
||||
.qtype = <qtype>,
|
||||
.num_dim = <num_dim>,
|
||||
.bitwidth = <bitwidth>
|
||||
};
|
||||
'''
|
||||
config = config.replace('<tensor_name>', tensor_name)
|
||||
config = config.replace('<bitwidth>', '8')
|
||||
config = config.replace('<value>', tensor_value)
|
||||
config = config.replace('<dim>', to_cstyle(shape))
|
||||
config = config.replace('<num_dim>', str(len(shape)))
|
||||
if(type(dec_bits) == str):
|
||||
config = config.replace('<q_dec>', dec_bits)
|
||||
config = config.replace('<q_offset>', to_cstyle([0]))
|
||||
else:
|
||||
config = config.replace('<q_dec>', to_cstyle(dec_bits))
|
||||
config = config.replace('<q_offset>', to_cstyle([0]))
|
||||
if(per_axis):
|
||||
config = config.replace('<qtype>', 'NNOM_QTYPE_PER_AXIS')
|
||||
else:
|
||||
config = config.replace('<qtype>', 'NNOM_QTYPE_PER_TENSOR')
|
||||
return config
|
||||
|
||||
def gen_conv2d_config(layer, output_shifts, bias_shifts):
|
||||
c = '''
|
||||
const nnom_qformat_param_t <layer_name>_output_shift[] = <output_shift_values>;
|
||||
const nnom_qformat_param_t <layer_name>_bias_shift[] = <bias_shift_values>;
|
||||
const nnom_conv2d_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.qtype = <qtype>,
|
||||
.weight = (nnom_tensor_t*)&<weight>,
|
||||
.bias = (nnom_tensor_t*)&<bias>,
|
||||
.output_shift = (nnom_qformat_param_t *)&<layer_name>_output_shift,
|
||||
.bias_shift = (nnom_qformat_param_t *)&<layer_name>_bias_shift,
|
||||
.filter_size = <filter_size>,
|
||||
.kernel_size = <kernel_size>,
|
||||
.stride_size = <stride_size>,
|
||||
.padding_size = <padding_size>,
|
||||
.dilation_size = <dilation_size>,
|
||||
.padding_type = <padding_type>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<qtype>', "NNOM_QTYPE_PER_TENSOR")
|
||||
c = c.replace('<weight>',convert_tensor_name(layer.weights[0]))
|
||||
c = c.replace('<bias>',convert_tensor_name(layer.weights[1]))
|
||||
c = c.replace('<output_shift_values>', output_shifts)
|
||||
c = c.replace('<bias_shift_values>', bias_shifts)
|
||||
c = c.replace('<filter_size>', str(layer.filters) if layer.filters is not None else str(layer.depth_multiplier)) # output channel
|
||||
c = c.replace('<kernel_size>', to_cstyle(layer.kernel_size))
|
||||
c = c.replace('<stride_size>', to_cstyle(layer.strides))
|
||||
c = c.replace('<padding_size>', '{0, 0}') # not using it with keras, defined by padding type instead
|
||||
c = c.replace('<dilation_size>', to_cstyle(layer.dilation_rate))
|
||||
c = c.replace('<padding_type>', 'PADDING_'+layer.padding.upper())
|
||||
return c
|
||||
|
||||
def gen_conv2d_trans_config(layer, output_shifts, bias_shifts):
|
||||
c = '''
|
||||
const nnom_qformat_param_t <layer_name>_output_shift[] = <output_shift_values>;
|
||||
const nnom_qformat_param_t <layer_name>_bias_shift[] = <bias_shift_values>;
|
||||
const nnom_conv2d_trans_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.qtype = <qtype>,
|
||||
.weight = (nnom_tensor_t*)&<weight>,
|
||||
.bias = (nnom_tensor_t*)&<bias>,
|
||||
.output_shift = (nnom_qformat_param_t *)&<layer_name>_output_shift,
|
||||
.bias_shift = (nnom_qformat_param_t *)&<layer_name>_bias_shift,
|
||||
.filter_size = <filter_size>,
|
||||
.kernel_size = <kernel_size>,
|
||||
.stride_size = <stride_size>,
|
||||
.padding_size = <padding_size>,
|
||||
.dilation_size = <dilation_size>,
|
||||
.padding_type = <padding_type>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<qtype>', "NNOM_QTYPE_PER_TENSOR")
|
||||
c = c.replace('<weight>',convert_tensor_name(layer.weights[0]))
|
||||
c = c.replace('<bias>',convert_tensor_name(layer.weights[1]))
|
||||
c = c.replace('<output_shift_values>', output_shifts)
|
||||
c = c.replace('<bias_shift_values>', bias_shifts)
|
||||
c = c.replace('<filter_size>', str(layer.filters)) # output channel
|
||||
c = c.replace('<kernel_size>', to_cstyle(layer.kernel_size))
|
||||
c = c.replace('<stride_size>', to_cstyle(layer.strides))
|
||||
c = c.replace('<padding_size>', '{0, 0}') # not using it with keras, defined by padding type instead
|
||||
c = c.replace('<dilation_size>', to_cstyle(layer.dilation_rate))
|
||||
c = c.replace('<padding_type>', 'PADDING_'+layer.padding.upper())
|
||||
return c
|
||||
|
||||
def gen_dense_config(layer, output_shifts, bias_shift):
|
||||
c = '''
|
||||
const nnom_qformat_param_t <layer_name>_output_shift[] = <output_shift_values>;
|
||||
const nnom_qformat_param_t <layer_name>_bias_shift[] = <bias_shift_values>;
|
||||
const nnom_dense_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.qtype = <qtype>,
|
||||
.weight = (nnom_tensor_t*)&<weight>,
|
||||
.bias = (nnom_tensor_t*)&<bias>,
|
||||
.output_shift = (nnom_qformat_param_t *)&<layer_name>_output_shift,
|
||||
.bias_shift = (nnom_qformat_param_t *)&<layer_name>_bias_shift
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<qtype>', "NNOM_QTYPE_PER_TENSOR")
|
||||
c = c.replace('<weight>', convert_tensor_name(layer.weights[0]))
|
||||
c = c.replace('<bias>', convert_tensor_name(layer.weights[1]))
|
||||
c = c.replace('<output_shift_values>', output_shifts)
|
||||
c = c.replace('<bias_shift_values>', bias_shift)
|
||||
return c
|
||||
|
||||
def gen_io_config(layer, tensor_name):
|
||||
c = '''
|
||||
const nnom_io_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.tensor = (nnom_tensor_t*)&<tensor>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<tensor>', tensor_name)
|
||||
return c
|
||||
|
||||
def gen_output_config(previous_layer, dec_bits, output_num, value_name='nnom_output_data'): #cheat at the moments
|
||||
c = '''
|
||||
const nnom_shape_data_t <tensor_name>_dim[] = <dim>;
|
||||
const nnom_qformat_param_t <tensor_name>_dec[] = <q_dec>;
|
||||
const nnom_qformat_param_t <tensor_name>_offset[] = <q_offset>;
|
||||
const nnom_tensor_t <tensor_name> = {
|
||||
.p_data = (void*)<value>,
|
||||
.dim = (nnom_shape_data_t*)<tensor_name>_dim,
|
||||
.q_dec = (nnom_qformat_param_t*)<tensor_name>_dec,
|
||||
.q_offset = (nnom_qformat_param_t*)<tensor_name>_offset,
|
||||
.qtype = <qtype>,
|
||||
.num_dim = <num_dim>,
|
||||
.bitwidth = 8
|
||||
};
|
||||
|
||||
const nnom_io_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.tensor = (nnom_tensor_t*)&<tensor_name>
|
||||
};
|
||||
'''
|
||||
shape = tensor_shape(previous_layer.output, is_io_tensor=True)
|
||||
|
||||
c = c.replace('<tensor_name>', 'tensor_output'+str(output_num))
|
||||
c = c.replace('<layer_name>', 'output'+str(output_num))
|
||||
c = c.replace('<base_config>', '{.name = "output'+str(output_num)+'"}') # cheating at the moment.
|
||||
c = c.replace('<value>', value_name)
|
||||
c = c.replace('<qtype>', 'NNOM_QTYPE_PER_TENSOR')
|
||||
c = c.replace('<num_dim>', str(len(shape)))
|
||||
c = c.replace('<dim>', to_cstyle(shape))
|
||||
c = c.replace('<q_dec>', '{'+dec_bits+'}')
|
||||
c = c.replace('<q_offset>', to_cstyle([0]))
|
||||
return c
|
||||
|
||||
|
||||
def gen_pooling_config(layer, output_shifts='0'):
|
||||
c = '''
|
||||
const nnom_pool_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.padding_type = <padding_type>,
|
||||
.output_shift = <output_shift>,
|
||||
.kernel_size = <kernel_size>,
|
||||
.stride_size = <stride_size>,
|
||||
.num_dim = <num_dim>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<padding_type>', 'PADDING_'+layer.padding.upper())
|
||||
c = c.replace('<kernel_size>', to_cstyle(layer.pool_size))
|
||||
c = c.replace('<stride_size>', to_cstyle(layer.strides))
|
||||
c = c.replace('<num_dim>', str(len(layer.pool_size)))
|
||||
c = c.replace('<output_shift>', output_shifts) # not used at the moment
|
||||
return c
|
||||
|
||||
def gen_gl_pooling_config(layer, output_shifts='0'):
|
||||
c = '''
|
||||
const nnom_global_pool_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.output_shift = <output_shift>,
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<output_shift>', output_shifts)
|
||||
return c
|
||||
|
||||
|
||||
|
||||
def gen_matrix_config(layer, output_shift_name='0'):
|
||||
c = '''
|
||||
const nnom_matrix_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.output_shift = <output_shift>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<output_shift>', output_shift_name) # not used at the moment
|
||||
return c
|
||||
|
||||
def gen_zero_padding_config(layer):
|
||||
c = '''
|
||||
const nnom_zero_padding_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.pad = <padding>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
try:
|
||||
c = c.replace('<padding>', to_cstyle(sum(layer.padding, ())))
|
||||
except:
|
||||
pad = ((0, 0), layer.padding)
|
||||
c = c.replace('<padding>', to_cstyle(sum(pad, ())))
|
||||
return c
|
||||
|
||||
def gen_cropping_config(layer):
|
||||
c = '''
|
||||
const nnom_cropping_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.pad = <padding>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
try:
|
||||
c = c.replace('<padding>', to_cstyle(sum(layer.cropping, ()))) #((top_crop, bottom_crop), (left_crop, right_crop))
|
||||
except:
|
||||
pad = ((0, 0), layer.cropping)
|
||||
c = c.replace('<padding>', to_cstyle(sum(pad, ())))
|
||||
return c
|
||||
|
||||
def gen_upsampling_config(layer):
|
||||
c = '''
|
||||
const nnom_upsample_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.kernel = <kernel>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<kernel>', to_cstyle(layer.size))
|
||||
return c
|
||||
|
||||
def gen_softmax_config(layer):
|
||||
c = '''
|
||||
const nnom_softmax_config_t <layer_name>_config = {
|
||||
.super = <base_config>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
return c
|
||||
|
||||
def gen_flatten_config(layer):
|
||||
c = '''
|
||||
const nnom_flatten_config_t <layer_name>_config = {
|
||||
.super = <base_config>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
return c
|
||||
|
||||
def gen_reshape_config(layer):
|
||||
c = '''
|
||||
const nnom_shape_data_t <layer_name>_targeted_shape[] = <shape>;
|
||||
const nnom_reshape_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.dim = (nnom_shape_data_t*)<layer_name>_targeted_shape,
|
||||
.num_dim = <num_dim>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<shape>', to_cstyle(layer.output_shape[1:]))
|
||||
c = c.replace('<num_dim>', str(len(layer.output_shape[1:])))
|
||||
return c
|
||||
|
||||
def gen_concat_config(layer):
|
||||
c = '''
|
||||
const nnom_concat_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.axis = <axis>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<axis>', str(layer.axis))
|
||||
return c
|
||||
|
||||
def gen_lambda_config(layer, run_func_name='NULL', build_func_name='NULL', free_func_name='NULL', parameters_name='NULL'):
|
||||
c = '''
|
||||
const nnom_lambda_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.run_func_name = <run_func_name>,
|
||||
.build_func_name = <build_func_name>,
|
||||
.free_func_name = <free_func_name>,
|
||||
.parameters = <parameters_name>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<run_func_name>', run_func_name)
|
||||
c = c.replace('<build_func_name>', build_func_name)
|
||||
c = c.replace('<free_func_name>', free_func_name)
|
||||
c = c.replace('<parameters_name>', parameters_name)
|
||||
return c
|
||||
|
||||
def gen_rnn_config(layer):
|
||||
c = '''
|
||||
const nnom_rnn_config_t <layer_name>_config = {
|
||||
.super = <base_config>,
|
||||
.return_sequence = <return_sequence>,
|
||||
.stateful = <stateful>,
|
||||
.go_backwards = <go_backwards>
|
||||
};
|
||||
'''
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<stateful>', 'true' if layer.stateful else 'false')
|
||||
c = c.replace('<go_backwards>', 'true' if layer.go_backwards else 'false')
|
||||
c = c.replace('<return_sequence>', 'true' if layer.return_sequences else 'false')
|
||||
return c
|
||||
|
||||
def gen_simple_cell_config(layer, q_list):
|
||||
c = '''
|
||||
const nnom_simple_cell_config_t <layer_name>_simple_cell_config = {
|
||||
.super = <base_config>,
|
||||
.weights = (nnom_tensor_t*)&<weights>,
|
||||
.recurrent_weights = (nnom_tensor_t*)&<recurrent_weights>,
|
||||
.bias = (nnom_tensor_t*)&<bias>,
|
||||
.q_dec_iw = <q_dec_iw>,
|
||||
.q_dec_hw = <q_dec_hw>,
|
||||
.q_dec_h = <q_dec_h>,
|
||||
.act_type = <act_type>,
|
||||
.units = <units>
|
||||
};
|
||||
'''
|
||||
try:
|
||||
cell_cfg = layer.get_config()['cell']['config']
|
||||
except:
|
||||
cell_cfg = layer.get_config()
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<weights>', convert_tensor_name(layer.weights[0]))
|
||||
c = c.replace('<recurrent_weights>', convert_tensor_name(layer.weights[1]))
|
||||
c = c.replace('<bias>', convert_tensor_name(layer.weights[2]))
|
||||
c = c.replace('<q_dec_iw>', str(q_list[1])) # the qfmt of input x weight
|
||||
c = c.replace('<q_dec_hw>', str(q_list[2])) # q of hidden x recurrent weight
|
||||
c = c.replace('<q_dec_h>', str(q_list[0])) # output, if act != relu, should be 7 (consider delete it.)
|
||||
c = c.replace('<act_type>', 'ACT_' + cell_cfg['activation'].upper())
|
||||
c = c.replace('<units>', str(cell_cfg['units']))
|
||||
return c
|
||||
|
||||
def gen_lstm_cell_config(layer, q_list):
|
||||
c = '''
|
||||
const nnom_lstm_cell_config_t <layer_name>_lstm_cell_config = {
|
||||
.super = <base_config>,
|
||||
.weights = (nnom_tensor_t*)&<weights>,
|
||||
.recurrent_weights = (nnom_tensor_t*)&<recurrent_weights>,
|
||||
.bias = (nnom_tensor_t*)&<bias>,
|
||||
.q_dec_z = <q_dec_z>,
|
||||
.q_dec_h = <q_dec_h>,
|
||||
.q_dec_c = <q_dec_c>,
|
||||
.units = <units>
|
||||
};
|
||||
'''
|
||||
try:
|
||||
cell_cfg = layer.get_config()['cell']['config']
|
||||
except:
|
||||
cell_cfg = layer.get_config()
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<weights>', convert_tensor_name(layer.weights[0]))
|
||||
c = c.replace('<recurrent_weights>', convert_tensor_name(layer.weights[1]))
|
||||
c = c.replace('<bias>', convert_tensor_name(layer.weights[2]))
|
||||
c = c.replace('<q_dec_h>', str(q_list[0])) # output and memory state, (should be q0.7. consider delete it)
|
||||
c = c.replace('<q_dec_c>', str(q_list[1])) # cell state
|
||||
c = c.replace('<q_dec_z>', str(q_list[2])) # input*weight + hidden*weight + bias
|
||||
c = c.replace('<units>', str(cell_cfg['units']))
|
||||
return c
|
||||
|
||||
|
||||
|
||||
def gen_gru_cell_config(layer, q_list):
|
||||
c = '''
|
||||
const nnom_gru_cell_config_t <layer_name>_gru_cell_config = {
|
||||
.super = <base_config>,
|
||||
.weights = (nnom_tensor_t*)&<weights>,
|
||||
.recurrent_weights = (nnom_tensor_t*)&<recurrent_weights>,
|
||||
.bias = (nnom_tensor_t*)&<bias>,
|
||||
.q_dec_z = <q_dec_z>,
|
||||
.q_dec_h = <q_dec_h>,
|
||||
.units = <units>
|
||||
};
|
||||
'''
|
||||
try:
|
||||
cell_cfg = layer.get_config()['cell']['config']
|
||||
except:
|
||||
cell_cfg = layer.get_config()
|
||||
c = c.replace('<layer_name>', layer.name)
|
||||
c = c.replace('<base_config>', gen_base_config(layer))
|
||||
c = c.replace('<weights>', convert_tensor_name(layer.weights[0]))
|
||||
c = c.replace('<recurrent_weights>', convert_tensor_name(layer.weights[1]))
|
||||
c = c.replace('<bias>', convert_tensor_name(layer.weights[2]))
|
||||
c = c.replace('<q_dec_h>', str(q_list[0])) #
|
||||
c = c.replace('<q_dec_z>', str(q_list[1])) #
|
||||
c = c.replace('<units>', str(cell_cfg['units']))
|
||||
return c
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test only
|
||||
from tensorflow.keras.models import load_model
|
||||
model = load_model("../model.h5")
|
||||
print(gen_tensor(model.layers[1].weights[0], dec_bits=(1, 2, 3, 4, 5)))
|
||||
print(gen_tensor(model.layers[1].weights[1], dec_bits=(1, 2, 3, 4, 5)))
|
||||
print(gen_conv2d_config(model.layers[1], (1,2,3), 3))
|
||||
|
||||
with open("test.h", 'w') as fp:
|
||||
# fp.write(gen_tensor(model.layers[1].weights[0], dec_bits=(1, 2, 3, 4, 5)))
|
||||
# fp.write(gen_tensor(model.layers[1].weights[1], dec_bits=(1, 2, 3, 4, 5)))
|
||||
# fp.write(gen_conv2d_config(model.layers[1], (1,2,3,)))
|
||||
|
||||
fp.write('#include "nnom.h"\n')
|
||||
|
||||
# test all
|
||||
for layer in model.layers:
|
||||
if(type(layer) in [Conv2D, Conv1D]):
|
||||
for w in layer.weights:
|
||||
fp.write(gen_tensor(w, [3]))
|
||||
fp.write(gen_conv2d_config(layer, {0}, 2))
|
||||
elif(type(layer) in [Dense]):
|
||||
for w in layer.weights:
|
||||
fp.write(gen_tensor(w, [3]))
|
||||
fp.write(gen_dense_config(layer, 2, 2))
|
||||
elif(type(layer) in [Input]):
|
||||
fp.write(gen_io_config(layer, [9,1,1]))
|
||||
elif(type(layer) in [MaxPooling2D, GlobalMaxPooling2D, AveragePooling2D, GlobalAveragePooling2D]):
|
||||
fp.write(gen_pooling_config(layer))
|
||||
elif(type(layer) in [Multiply, Add, Subtract]):
|
||||
fp.write(gen_matrix_config(layer))
|
||||
elif(type(layer) in [ZeroPadding2D, ZeroPadding1D]):
|
||||
fp.write(gen_zero_padding_config(layer))
|
||||
elif(type(layer) in [Cropping2D, Cropping1D]):
|
||||
fp.write(gen_cropping_config(layer))
|
||||
elif(type(layer) in [Softmax]):
|
||||
fp.write(gen_softmax_config(layer))
|
||||
elif(type(layer) in [Flatten]):
|
||||
fp.write(gen_flatten_config(layer))
|
||||
elif(type(layer) in [Concatenate]):
|
||||
fp.write(gen_concat_config(layer))
|
||||
elif(type(layer) in [Lambda]):
|
||||
fp.write(gen_lambda_config(layer))
|
||||
elif(type(layer) in [UpSampling2D, UpSampling1D]):
|
||||
fp.write(gen_upsampling_config(layer))
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,845 @@
|
||||
'''
|
||||
Copyright (c) 2018-2020
|
||||
Jianjia Ma
|
||||
majianjia@live.com
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Change Logs:
|
||||
Date Author Notes
|
||||
2019-02-05 Jianjia Ma The first version
|
||||
|
||||
|
||||
This file provides:
|
||||
-> fake_quantisation layers which simulate the output quantisation on fixed-point NN models.
|
||||
-> weights/bias quantisation of Convolution and Dense Layer. "weight.h" file generations
|
||||
-> export "testing set" binary data file.
|
||||
-> print output ranges of each layers.
|
||||
|
||||
Currently, this script does not support RNN (type) layers.
|
||||
'''
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import tensorflow as tf
|
||||
from tensorflow.keras.layers import InputLayer
|
||||
from tensorflow.keras.models import Model
|
||||
|
||||
from sklearn import metrics
|
||||
from .fully_connected_opt_weight_generation import *
|
||||
import time
|
||||
import warnings
|
||||
|
||||
"""
|
||||
this is the generate the test set data to a bin file
|
||||
bin file can be used to validate the implementation in MCU
|
||||
|
||||
"""
|
||||
def generate_test_bin(x, y, name='test_data_with_label.bin'):
|
||||
'''
|
||||
this method generate the
|
||||
:param x: input x data size
|
||||
:param y: input label (one hot label)
|
||||
:return:
|
||||
'''
|
||||
# quantize input x
|
||||
min_value = np.min(x)
|
||||
max_value = np.max(x)
|
||||
|
||||
int_bits = int(np.ceil(np.log2(max(abs(min_value), abs(max_value)))))
|
||||
dec_bits = 7 - int_bits
|
||||
x = np.round(x*2**dec_bits).astype(np.int8)
|
||||
# get label
|
||||
if(len(y.shape) >1):
|
||||
test_label = np.argwhere(y == 1).astype(np.int8) # test data
|
||||
test_label = test_label[:, 1]
|
||||
else:
|
||||
test_label = y
|
||||
|
||||
# get data
|
||||
dat = x.astype(dtype="byte") # test data
|
||||
batch_size = dat.shape[0] # total pices of data
|
||||
dat = dat.flatten() # flatten to get the total size.
|
||||
block_size = int(dat.size / batch_size) # this must be integer but... just to confirm
|
||||
|
||||
# write (label x 128) (data_block x 128)
|
||||
label_batch = 128 # the Y-modem example uses 128 batch
|
||||
with open(name, 'wb') as f:
|
||||
start = 0
|
||||
while start <= (test_label.size - label_batch):
|
||||
test_label[start: start + label_batch].tofile(f)
|
||||
dat[block_size * start: block_size * (start + label_batch)].tofile(f)
|
||||
start += label_batch
|
||||
|
||||
# the rest data
|
||||
if (start < test_label.size):
|
||||
rest_len = test_label.size - start
|
||||
new_labls = test_label[start:]
|
||||
new_labls = np.pad(new_labls, (0, label_batch - rest_len), mode='constant')
|
||||
new_labls.tofile(f)
|
||||
dat[block_size * start:].tofile(f)
|
||||
|
||||
print("binary test file generated:", name)
|
||||
print("test data length:", test_label.size)
|
||||
return
|
||||
|
||||
def is_shift_layer(layer):
|
||||
''' layer which can change the output encoding'''
|
||||
#FIXME: add more which will change the output shift
|
||||
if('input' in layer.name or
|
||||
'conv2d' in layer.name or
|
||||
'conv1d' in layer.name or
|
||||
'dense' in layer.name or
|
||||
'softmax' in layer.name or
|
||||
'sigmoid' in layer.name or
|
||||
'tanh' in layer.name or
|
||||
('add' in layer.name and 'zero' not in layer.name) or # the name, zero_padding contains 'add'
|
||||
'subtract' in layer.name or
|
||||
'multiply' in layer.name or
|
||||
('activation' in layer.name and layer.get_config()['activation'] == 'softmax')or
|
||||
('activation' in layer.name and layer.get_config()['activation'] == 'sigmoid') or
|
||||
('activation' in layer.name and layer.get_config()['activation'] == 'tanh')
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_shift_fixed(layer):
|
||||
''' layer which shift to a fixed value'''
|
||||
#FIXME: add more which will change the output shift
|
||||
if('softmax' in layer.name or
|
||||
'sigmoid' in layer.name or
|
||||
'tanh' in layer.name or
|
||||
('activation' in layer.name and layer.get_config()['activation'] == 'softmax') or
|
||||
('activation' in layer.name and layer.get_config()['activation'] == 'sigmoid') or
|
||||
('activation' in layer.name and layer.get_config()['activation'] == 'tanh')
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def fuse_bn_to_conv(layer):
|
||||
# try to fuse BN layer to convolutional
|
||||
if ('conv' in layer.name) and \
|
||||
('batch_normalization' in layer._outbound_nodes[0].outbound_layer.name):
|
||||
|
||||
print("fusing batch normalization to", layer.name)
|
||||
bn_layer = layer._outbound_nodes[0].outbound_layer
|
||||
c_w = layer.get_weights()[0]
|
||||
c_b = layer.get_weights()[1]
|
||||
print('original weight max', c_w.max(), 'min', c_w.min())
|
||||
print('original bias max', c_b.max(), 'min', c_b.min())
|
||||
bn_gamma = bn_layer.get_weights()[0]
|
||||
bn_beta = bn_layer.get_weights()[1]
|
||||
bn_mean = bn_layer.get_weights()[2]
|
||||
bn_variance = bn_layer.get_weights()[3]
|
||||
|
||||
if ('conv2d' in layer.name):
|
||||
epsilon = 1e-3 # default epsilon for tf.slim.batch_norm
|
||||
for l in range(c_w.shape[3]):
|
||||
for k in range(c_w.shape[2]):
|
||||
for j in range(c_w.shape[1]):
|
||||
for i in range(c_w.shape[0]):
|
||||
if "depthwise" in layer.name: # depthwise batchnorm params are ordered differently
|
||||
c_w[i][j][k][l] *= bn_gamma[k] / np.sqrt(bn_variance[k] + epsilon)
|
||||
else:
|
||||
c_w[i][j][k][l] *= bn_gamma[l] / np.sqrt(bn_variance[l] + epsilon)
|
||||
|
||||
if "depthwise" in layer.name:
|
||||
depth_dim = c_w.shape[2]
|
||||
else:
|
||||
depth_dim = c_w.shape[3]
|
||||
for l in range(depth_dim):
|
||||
c_b[l] = (bn_gamma[l] * (c_b[l] - bn_mean[l]) / np.sqrt(bn_variance[l] + epsilon)) + bn_beta[l]
|
||||
# conv1d
|
||||
else:
|
||||
epsilon = 1e-3 # default epsilon for tf.slim.batch_norm
|
||||
for k in range(c_w.shape[2]):
|
||||
for j in range(c_w.shape[1]):
|
||||
for i in range(c_w.shape[0]):
|
||||
if "depthwise" in layer.name: # depthwise batchnorm params are ordered differently
|
||||
c_w[i][j][k] *= bn_gamma[j] / np.sqrt(bn_variance[j] + epsilon)
|
||||
else:
|
||||
c_w[i][j][k] *= bn_gamma[k] / np.sqrt(bn_variance[k] + epsilon)
|
||||
|
||||
if "depthwise" in layer.name:
|
||||
depth_dim = c_w.shape[1]
|
||||
else:
|
||||
depth_dim = c_w.shape[2]
|
||||
for l in range(depth_dim):
|
||||
c_b[l] = (bn_gamma[l] * (c_b[l] - bn_mean[l]) / np.sqrt(bn_variance[l] + epsilon)) + bn_beta[l]
|
||||
|
||||
print('fused weight max', c_w.max(), 'min', c_w.min())
|
||||
print('fused bias max', c_b.max(), 'min', c_b.min())
|
||||
# write the weights back to the layer
|
||||
# after that, the model will be destroyed.. need a better way to pass the new weight
|
||||
layer.set_weights([c_w, c_b])
|
||||
|
||||
def generate_weights(model, name='weights.h', format='hwc', shift_list=None):
|
||||
# Quantize weights to 8-bits using (min,max) and write to file
|
||||
f = open(name, 'w')
|
||||
f.write('#include "nnom.h"\n\n')
|
||||
f.close()
|
||||
|
||||
for curr_idx, layer in enumerate(model.layers):
|
||||
if (not layer.weights):
|
||||
continue
|
||||
|
||||
# before merging bn layer, check if the bn is "legally" after Conv
|
||||
if('batch_normalization' in layer.name) and \
|
||||
('conv' not in layer.inbound_nodes[0].inbound_layers.name):
|
||||
raise Exception('Currently only support batch_normalization after conv', layer.name,
|
||||
layer._inbound_nodes[0].inbound_layers[0].name)
|
||||
|
||||
# try to fuse BN layer to convolutional
|
||||
if ('conv' in layer.name) and \
|
||||
('batch_normalization' in layer.outbound_nodes[0].outbound_layer.name):
|
||||
fuse_bn_to_conv(layer)
|
||||
|
||||
# generate weights and bias now
|
||||
weight_dec_shift = 0
|
||||
print('weights for layer', layer.name)
|
||||
for var in layer.weights:
|
||||
var_name = str(var.name)
|
||||
if("kernel" in var_name ):
|
||||
var_values = layer.get_weights()[0] # weight
|
||||
print(" weight:", var_name)
|
||||
elif("bias" in var_name):
|
||||
var_values = layer.get_weights()[1] # bias
|
||||
print(" bias: ",var_name)
|
||||
else:
|
||||
continue
|
||||
|
||||
print(" original shape: ", var_values.shape)
|
||||
min_value = np.min(var_values)
|
||||
max_value = np.max(var_values)
|
||||
|
||||
int_bits = int(np.ceil(np.log2(max(abs(min_value), abs(max_value)))))
|
||||
dec_bits = 7 - int_bits
|
||||
print(" dec bit", dec_bits)
|
||||
bSameAsKernel = False
|
||||
if(is_shift_layer(layer)):
|
||||
bSameAsKernel = False
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
input_encoding = shift_list[inp]
|
||||
if ("kernel" in var_name):
|
||||
weight_dec_shift = dec_bits
|
||||
else:
|
||||
shift = input_encoding+weight_dec_shift-dec_bits
|
||||
if(shift < 0):
|
||||
bSameAsKernel = True
|
||||
if(shift_list is None or bSameAsKernel):
|
||||
# check if bias shift > weight shift, then reduce bias shift to weight shift
|
||||
if ("kernel" in var_name):
|
||||
weight_dec_shift = dec_bits
|
||||
else:
|
||||
if(dec_bits > weight_dec_shift):
|
||||
dec_bits = weight_dec_shift
|
||||
print(" new dec bit", dec_bits)
|
||||
|
||||
# convert to [-128,128) or int8
|
||||
var_values = np.round(var_values * 2 ** dec_bits)
|
||||
var_name = var_name.replace('/', '_')
|
||||
var_name = var_name.replace(':', '_')
|
||||
with open(name, 'a') as f:
|
||||
f.write('#define ' + var_name.upper() + ' {')
|
||||
# CHW format
|
||||
if ('chw' in format):
|
||||
if "dense" in var_name and "kernel" in var_name:
|
||||
transposed_wts = np.transpose(var_values)
|
||||
transposed_wts = convert_to_x4_q7_weights(
|
||||
np.reshape(transposed_wts, (transposed_wts.shape[0], transposed_wts.shape[1], 1, 1)))
|
||||
# all other kernels, bias stay the same
|
||||
else:
|
||||
transposed_wts = var_values
|
||||
# HWC format
|
||||
else:
|
||||
if (len(var_values.shape) == 3): # 1D convolution layer weights
|
||||
transposed_wts = np.transpose(var_values, (2, 0, 1))
|
||||
elif (len(var_values.shape) == 4): # 2D convolution layer weights
|
||||
transposed_wts = np.transpose(var_values, (3, 0, 1, 2))
|
||||
else: # fully connected layer weights or biases of any layer
|
||||
# test, use opt weight reorder
|
||||
if "dense" in var_name and "kernel" in var_name:
|
||||
transposed_wts = np.transpose(var_values)
|
||||
transposed_wts = convert_to_x4_q7_weights(np.reshape(transposed_wts ,(transposed_wts.shape[0], transposed_wts.shape[1], 1, 1)))
|
||||
else:
|
||||
transposed_wts = np.transpose(var_values)
|
||||
|
||||
print(" reshape to:",transposed_wts.shape)
|
||||
|
||||
with open(name, 'a') as f:
|
||||
transposed_wts.tofile(f, sep=", ", format="%d")
|
||||
f.write('}\n\n')
|
||||
if ("bias" in var_name):
|
||||
f.write('#define ' + var_name.upper() + '_SHIFT ' + '(' + str(dec_bits) + ')\n\n\n')
|
||||
if ("kernel" in var_name ):
|
||||
f.write('#define ' + var_name.upper() + '_SHIFT ' + '(' + str(dec_bits) + ')\n\n')
|
||||
"""
|
||||
# for checking the quantised and dequantised range.
|
||||
with K.tf.Session() as session:
|
||||
# convert back original range but quantized to 8-bits or 256 levels
|
||||
var_values = var_values / (2 ** dec_bits)
|
||||
var_values = session.run(K.tf.assign(var, var_values))
|
||||
print(' '+var_name + ' number of wts/bias: ' + str(var_values.shape) + \
|
||||
' dec bits: ' + str(dec_bits) + \
|
||||
' max: (' + str(np.max(var_values)) + ',' + str(max_value) + ')' + \
|
||||
' min: (' + str(np.min(var_values)) + ',' + str(min_value) + ')')
|
||||
"""
|
||||
|
||||
def layers_output_ranges(model, x_test, quantize_method='max_min', calibrate_size=1000):
|
||||
# limit the test data size
|
||||
np.random.shuffle(x_test)
|
||||
if(x_test.shape[0] > calibrate_size):
|
||||
x_test = x_test[:1000]
|
||||
# test, show the output ranges
|
||||
shift_list = {}
|
||||
# FIXME: only support one input
|
||||
if(type(model.layers[0]) != InputLayer):
|
||||
L = [model.input] + model.layers
|
||||
else:
|
||||
L = model.layers
|
||||
last_layer = None
|
||||
|
||||
for layer in L: # layer loop
|
||||
if("input" in layer.name):
|
||||
features = x_test
|
||||
else:
|
||||
# batch_normalization will need to be handled differently, since we are fusing the weight to its predecessor.
|
||||
# sigmoid and tanh are different, their shift is fixed to 7
|
||||
if(is_shift_layer(layer) or
|
||||
('batch_normalization' in layer.name)):
|
||||
layer_model = Model(inputs=model.input, outputs=layer.output)
|
||||
features = layer_model.predict(x_test)
|
||||
else:
|
||||
# leave the features not changed, so this layer shift will be the same
|
||||
# as its inputs
|
||||
pass
|
||||
# calculate no saturation shift
|
||||
max_val = features.max()
|
||||
min_val = features.min()
|
||||
int_bits = int(np.ceil(np.log2(max(abs(max_val), abs(min_val)))))
|
||||
dec_bits = 7 - int_bits
|
||||
|
||||
# saturation shift, using KLD method
|
||||
# Ref: http://on-demand.gputechconf.com/gtc/2017/presentation/s7310-8-bit-inference-with-tensorrt.pdf
|
||||
if('kld' in quantize_method and not is_shift_fixed(layer) and "input" not in layer.name and "dense" not in layer.name): # test, also do not use kld in input layer
|
||||
import scipy.stats
|
||||
abs_max = max(abs(max_val), abs(min_val))
|
||||
small_var = 1e-5
|
||||
bins = np.arange(-abs_max, abs_max, abs_max/2048*2)
|
||||
q_bins = np.arange(-abs_max, abs_max, abs_max/256*2)
|
||||
flat_hist = np.histogram(features.flatten(), bins=bins)[0]
|
||||
kl_loss = []
|
||||
kl_shifts = []
|
||||
for shift in range(4):
|
||||
t = 2 ** (dec_bits + shift) # 2-based threshold
|
||||
act = np.round(features.flatten() * t)
|
||||
act = act / t
|
||||
act = np.clip(act, -128/t, 127/t)
|
||||
act = np.histogram(act, bins=q_bins)[0]
|
||||
act_hist = np.zeros(2047)
|
||||
chunk = int(2048/256)
|
||||
for i in range(int(255)):
|
||||
none_zero = np.count_nonzero(flat_hist[i*chunk:(i+1)*chunk])
|
||||
if none_zero == 0:
|
||||
continue
|
||||
for j in range(chunk):
|
||||
act_hist[i*chunk+j] = act[i]/none_zero if flat_hist[i*chunk+j] != 0 else 0
|
||||
flat_hist[flat_hist==0] = small_var
|
||||
act_hist[act_hist==0] = small_var
|
||||
kl = scipy.stats.entropy(flat_hist, act_hist)
|
||||
kl_loss.append(kl)
|
||||
kl_shifts.append(dec_bits + shift)
|
||||
"""
|
||||
ax = plt.subplot(8, 1, shift+1)
|
||||
ax.plot(flat_hist)
|
||||
ax.plot(act_hist)
|
||||
"""
|
||||
new_dec = kl_shifts[np.argmin(kl_loss)] # set the dec_bit to the KLD results
|
||||
#plt.show()
|
||||
print("KLD loss", kl_loss)
|
||||
print("KLD shift", kl_shifts)
|
||||
if(new_dec != dec_bits):
|
||||
print(layer.name,"is using KLD method, original shift",dec_bits, "KLD results", new_dec)
|
||||
dec_bits = new_dec
|
||||
|
||||
print( layer.name, "max value:", max_val, "min value:", min_val,"dec bit", dec_bits)
|
||||
# record the shift
|
||||
if(type(model.input) == tf.Tensor and type(model.layers[0]) != InputLayer):
|
||||
shift_list[layer.name.split(':')[0]] = dec_bits
|
||||
else:
|
||||
shift_list[layer.name] = dec_bits
|
||||
if ('batch_normalization' in layer.name):
|
||||
shift_list[last_layer.name] = dec_bits # use the bn layer shift to update the last layer.
|
||||
last_layer = layer
|
||||
|
||||
LM = {}
|
||||
for layer in model.layers:
|
||||
LM[layer.name] = layer
|
||||
L = [l for l in model.layers[1:]]
|
||||
L.reverse()
|
||||
|
||||
def update_previous_layer_shift(layer, Q):
|
||||
if(type(layer.input) == list):
|
||||
for inp in layer.input:
|
||||
iname = inp.name.split('/')[0]
|
||||
if('input' in iname):
|
||||
continue
|
||||
shift_list[iname] = Qmin
|
||||
if(not is_shift_layer(LM[iname])):
|
||||
update_previous_layer_shift(LM[iname], Q)
|
||||
else:
|
||||
iname = layer.input.name.split('/')[0]
|
||||
if('input' in iname):
|
||||
return
|
||||
shift_list[iname] = Qmin
|
||||
if(not is_shift_layer(LM[iname])):
|
||||
update_previous_layer_shift(LM[iname], Q)
|
||||
for layer in L:
|
||||
if(type(layer.input) == list):
|
||||
iname = layer.input[0].name.split('/')[0]
|
||||
Qmin = shift_list[iname]
|
||||
for inp in layer.input:
|
||||
iname = inp.name.split('/')[0]
|
||||
if(shift_list[iname] < Qmin):
|
||||
Qmin = shift_list[iname]
|
||||
if(shift_list[iname] != Qmin):
|
||||
bFlag = True
|
||||
for inp in layer.input:
|
||||
iname = inp.name.split('/')[0]
|
||||
shift_list[iname] = Qmin
|
||||
if(not is_shift_layer(LM[iname])):
|
||||
update_previous_layer_shift(LM[iname], Qmin)
|
||||
print('set shift', Qmin, 'for the input of', layer.name, ':', [inp.name.split('/')[0] for inp in layer.input])
|
||||
if(not is_shift_layer(layer) or Qmin < shift_list[layer.name]): # update current layer's shift only when we cannot change the shift
|
||||
shift_list[layer.name] = Qmin
|
||||
print("shift list", shift_list)
|
||||
return shift_list
|
||||
|
||||
def generate_model(model, x_test, name='weights.h', format='hwc', quantize_method='max_min'):
|
||||
shift_list = layers_output_ranges(model, x_test, quantize_method=quantize_method)
|
||||
generate_weights(model, name=name, format=format, shift_list=shift_list)
|
||||
if(type(model.layers[0]) != InputLayer):
|
||||
L = [model.input] + model.layers
|
||||
else:
|
||||
L = model.layers
|
||||
with open(name,'a') as fp:
|
||||
fp.write('\n/* output enconding for each layer */\n')
|
||||
for layer in L:
|
||||
if(type(model.input) == tf.Tensor and type(model.layers[0]) != InputLayer):
|
||||
iname = layer.name.split(':')[0]
|
||||
else:
|
||||
iname = layer.name
|
||||
fp.write('#define %s_OUTPUT_SHIFT %s\n'%(iname.upper(), shift_list[iname]))
|
||||
fp.write('\n/* bias shift and output shift for each layer */\n')
|
||||
for layer in model.layers:
|
||||
if(is_shift_layer(layer)):
|
||||
iname = layer.name.upper()
|
||||
if(len(layer.weights) == 2 and
|
||||
'kernel' in layer.weights[0].name and
|
||||
'bias' in layer.weights[1].name):
|
||||
kname = layer.weights[0].name.upper().replace('/', '_').replace(':', '_')
|
||||
bname = layer.weights[1].name.upper().replace('/', '_').replace(':', '_')
|
||||
inp = layer.input.name.replace(':','/').split('/')[0].upper()
|
||||
fp.write('#define {0}_OUTPUT_RSHIFT ({1}_OUTPUT_SHIFT+{2}_SHIFT-{0}_OUTPUT_SHIFT)\n'.format(
|
||||
iname, inp, kname))
|
||||
fp.write('#define {0}_BIAS_LSHIFT ({1}_OUTPUT_SHIFT+{2}_SHIFT-{3}_SHIFT)\n'.format(
|
||||
iname, inp, kname, bname))
|
||||
fp.write('#if {0}_OUTPUT_RSHIFT < 0\n#error {0}_OUTPUT_RSHIFT must be bigger than 0\n#endif\n'.format(iname))
|
||||
fp.write('#if {0}_BIAS_LSHIFT < 0\n#error {0}_BIAS_RSHIFT must be bigger than 0\n#endif\n'.format(iname))
|
||||
# add, sub
|
||||
elif ('add' in layer.name or
|
||||
'subtract' in layer.name):
|
||||
# only consider the first, they have been set to same in out_put_range()
|
||||
inp = layer.input[0].name.replace(':','/').split('/')[0].upper()
|
||||
fp.write('#define {0}_OUTPUT_RSHIFT ({1}_OUTPUT_SHIFT-{0}_OUTPUT_SHIFT)\n'.format(
|
||||
iname, inp))
|
||||
fp.write('#if {0}_OUTPUT_RSHIFT < 0\n#error {0}_OUTPUT_RSHIFT must be bigger than 0\n#endif\n'.format(iname))
|
||||
# mult is different, Q3.4 * Q3.4 = Q6.8. if mult out is Q4.3, then shift (Q.4+q.4)-Q.3=5. Am I right?
|
||||
elif ('multiply' in layer.name ):
|
||||
inp = layer.input[0].name.replace(':','/').split('/')[0].upper()
|
||||
fp.write('#define {0}_OUTPUT_RSHIFT ({1}_OUTPUT_SHIFT*2-{0}_OUTPUT_SHIFT)\n'.format(
|
||||
iname, inp))
|
||||
fp.write('#if {0}_OUTPUT_RSHIFT < 0\n#error {0}_OUTPUT_RSHIFT must be bigger than 0\n#endif\n'.format(iname))
|
||||
|
||||
fp.write('\n/* weights for each layer */\n')
|
||||
LI = {}
|
||||
ID = 0
|
||||
def is_skipable_layer(layer):
|
||||
# FIXME: add more that could be skiped
|
||||
if('lambda' in layer.name or
|
||||
'dropout' in layer.name or
|
||||
'batch_normalization' in layer.name or
|
||||
('flatten' in layer.name and 'chw' not in format)): # flatten layer can be skipped in HWC but have to present in CHW
|
||||
return True
|
||||
return False
|
||||
for id,layer in enumerate(L):
|
||||
if(is_skipable_layer(layer)):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
LI[layer.name] = (LI[inp][0], layer)
|
||||
else:
|
||||
if(type(model.input) == tf.Tensor and type(model.layers[0]) != InputLayer):
|
||||
LI[layer.name.split(':')[0]] = (ID, layer)
|
||||
else:
|
||||
LI[layer.name] = (ID, layer)
|
||||
ID += 1
|
||||
|
||||
if ('input' in layer.name or not layer.weights):
|
||||
continue
|
||||
for var in layer.weights:
|
||||
var_name = str(var.name).replace('/', '_').replace(':', '_')
|
||||
if("kernel" in var_name):
|
||||
fp.write('static const int8_t %s_weights[] = %s;\n'%(layer.name, var_name.upper()))
|
||||
fp.write('static const nnom_weight_t %s_w = { (const void*)%s_weights, %s_OUTPUT_RSHIFT};\n'%(layer.name,layer.name, layer.name.upper()))
|
||||
elif("bias" in var_name):
|
||||
fp.write('static const int8_t %s_bias[] = %s;\n'%(layer.name, var_name.upper()))
|
||||
fp.write('static const nnom_bias_t %s_b = { (const void*)%s_bias, %s_BIAS_LSHIFT};\n'%(layer.name,layer.name, layer.name.upper()))
|
||||
fp.write('\n/* nnom model */\n')
|
||||
# FIXME: now only support one input and one output
|
||||
sz = 1
|
||||
for d in model.input.shape[1:]:
|
||||
sz = sz*d
|
||||
fp.write('static int8_t nnom_input_data[%d];\n'%(sz))
|
||||
sz = 1
|
||||
for d in model.output.shape[1:]:
|
||||
sz = sz*d
|
||||
fp.write('static int8_t nnom_output_data[%d];\n'%(sz))
|
||||
fp.write('static nnom_model_t* nnom_model_create(void)\n{\n')
|
||||
fp.write('\tstatic nnom_model_t model;\n')
|
||||
if(ID>32):
|
||||
fp.write('\tnnom_layer_t ** layer = malloc(sizeof(nnom_layer_t *)*%d);\n'%(ID+1))
|
||||
fp.write('\tif(NULL == layer) return NULL;\n')
|
||||
else:
|
||||
fp.write('\tnnom_layer_t* layer[%d];\n'%(ID+1))
|
||||
fp.write('\n\tnew_model(&model);\n\n')
|
||||
for layer in L:
|
||||
if(is_skipable_layer(layer)):
|
||||
continue
|
||||
#FIXME: need a better solution to seperate the input 'tensor' from other layers
|
||||
if (type(model.input) == tf.Tensor and type(model.layers[0]) != InputLayer):
|
||||
id,_ = LI[layer.name.split(':')[0]]
|
||||
else:
|
||||
id,_ = LI[layer.name]
|
||||
|
||||
if('input' in layer.name):
|
||||
try:
|
||||
inshape = layer.input_shape[0][1:] # new changes in tf2?
|
||||
except:
|
||||
inshape = layer.shape[1:]
|
||||
if (len(inshape) == 1): # 1-D input
|
||||
fp.write('\tlayer[%d] = Input(shape(%d,1,1), nnom_input_data);\n' % (id, inshape[0]))
|
||||
elif (len(inshape) == 2): # 1-D input
|
||||
fp.write('\tlayer[%d] = Input(shape(1,%d,%d), nnom_input_data);\n' % (id, inshape[0], inshape[1]))
|
||||
else:
|
||||
fp.write('\tlayer[%d] = Input(shape%s, nnom_input_data);\n' % (id, inshape))
|
||||
|
||||
# convlutional
|
||||
elif('conv1d' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if('depthwise' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(DW_Conv2D({1}, kernel(1,{2}), stride(1,{3}), dilation(1,{4}), PADDING_{5}, &{6}_w, &{6}_b), layer[{7}]);\n'.format(
|
||||
id, 1, cfg['kernel_size'][0], cfg['strides'][0], cfg['dilation_rate'][0], cfg['padding'].upper(),
|
||||
layer.name, LI[inp][0]))
|
||||
else:
|
||||
fp.write('\tlayer[{0}] = model.hook(Conv2D({1}, kernel(1,{2}), stride(1,{3}), dilation(1,{4}), PADDING_{5}, &{6}_w, &{6}_b), layer[{7}]);\n'.format(
|
||||
id, cfg['filters'], cfg['kernel_size'][0], cfg['strides'][0], cfg['dilation_rate'][0], cfg['padding'].upper(),
|
||||
layer.name, LI[inp][0]))
|
||||
elif('conv2d' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if ('depthwise' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(DW_Conv2D({1}, kernel{2}, stride{3}, dilation{4}, PADDING_{5}, &{6}_w, &{6}_b), layer[{7}]);\n'.format(
|
||||
id, 1, cfg['kernel_size'], cfg['strides'], cfg['dilation_rate'], cfg['padding'].upper(),
|
||||
layer.name, LI[inp][0]))
|
||||
else:
|
||||
fp.write('\tlayer[{0}] = model.hook(Conv2D({1}, kernel{2}, stride{3}, dilation{4}, PADDING_{5}, &{6}_w, &{6}_b), layer[{7}]);\n'.format(
|
||||
id, cfg['filters'], cfg['kernel_size'], cfg['strides'], cfg['dilation_rate'], cfg['padding'].upper(),
|
||||
layer.name, LI[inp][0]))
|
||||
# activations
|
||||
elif('activation' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if(cfg['activation'] == 'relu'):
|
||||
fp.write('\tlayer[%s] = model.active(act_relu(), layer[%s]);\n'%(id, LI[inp][0]))
|
||||
if(cfg['activation'] == 'tanh'):
|
||||
fp.write('\tlayer[%s] = model.active(act_tanh(%s_OUTPUT_SHIFT), layer[%s]);\n'%(id, inp.upper(), LI[inp][0]))
|
||||
if(cfg['activation'] == 'sigmoid'):
|
||||
fp.write('\tlayer[%s] = model.active(act_sigmoid(%s_OUTPUT_SHIFT), layer[%s]);\n'%(id, inp.upper(), LI[inp][0]))
|
||||
elif(cfg['activation'] == 'softmax'):
|
||||
fp.write('\tlayer[%s] = model.hook(Softmax(), layer[%s]);\n'%(id, LI[inp][0]))
|
||||
elif('re_lu' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
fp.write('\tlayer[%s] = model.active(act_relu(), layer[%s]);\n'%(id, LI[inp][0]))
|
||||
# pooling
|
||||
elif('max_pooling' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if ('global' in layer.name):
|
||||
fp.write('\tlayer[%s] = model.hook(GlobalMaxPool(), layer[%s]);\n' % (id, LI[inp][0]))
|
||||
elif('2d' in layer.name):
|
||||
fp.write('\tlayer[%s] = model.hook(MaxPool(kernel%s, stride%s, PADDING_%s), layer[%d]);\n'%(
|
||||
id, cfg['pool_size'], cfg['strides'], cfg['padding'].upper(), LI[inp][0]))
|
||||
elif('1d' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(MaxPool(kernel(1,{1}), stride(1,{2}), PADDING_{3}), layer[{4}]);\n'.format(
|
||||
id, cfg['pool_size'][0], cfg['strides'][0], cfg['padding'].upper(), LI[inp][0]))
|
||||
elif('average_pooling' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if ('global' in layer.name):
|
||||
# a global avg pool before softmax can be replace by sumpool in MCU (recommend)
|
||||
if(layer == model.layers[-2] and 'Softmax' in model.layers[-1].output.name):
|
||||
print(layer.name, 'has been replaced by GlobalSumPool()')
|
||||
fp.write('\tlayer[%s] = model.hook(GlobalSumPool(), layer[%s]);\n' % (id, LI[inp][0]))
|
||||
else:
|
||||
fp.write('\tlayer[%s] = model.hook(GlobalAvgPool(), layer[%s]);\n' % (id, LI[inp][0]))
|
||||
elif('2d' in layer.name):
|
||||
fp.write('\tlayer[%s] = model.hook(AvgPool(kernel%s, stride%s, PADDING_%s), layer[%d]);\n'%(
|
||||
id, cfg['pool_size'], cfg['strides'], cfg['padding'].upper(), LI[inp][0]))
|
||||
elif('1d' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(AvgPool(kernel(1,{1}), stride(1,{2}), PADDING_{3}), layer[{4}]);\n'.format(
|
||||
id, cfg['pool_size'][0], cfg['strides'][0], cfg['padding'].upper(), LI[inp][0]))
|
||||
elif ('up_sampling' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if('2d' in layer.name):
|
||||
fp.write('\tlayer[%s] = model.hook(UpSample(kernel%s), layer[%d]);\n'%(id, cfg['size'], LI[inp][0]))
|
||||
elif('1d' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(UpSample(kernel(1,{1})), layer[{2}]);\n'.format(
|
||||
id, cfg['size'][0], LI[inp][0]))
|
||||
# zero padding
|
||||
elif ('zero_padding' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if('2d' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(ZeroPadding(border({1},{2},{3},{4})), layer[{5}]);\n'.format(
|
||||
id, cfg['padding'][0][0], cfg['padding'][0][1], cfg['padding'][1][0],cfg['padding'][1][1], LI[inp][0]))
|
||||
elif('1d' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(ZeroPadding(border(0,0,{1},{2})), layer[{3}]);\n'.format(
|
||||
id, cfg['padding'][0], cfg['padding'][1], LI[inp][0]))
|
||||
# Cropping
|
||||
elif ('cropping' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if('2d' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(Cropping(border({1},{2},{3},{4})), layer[{5}]);\n'.format(
|
||||
id, cfg['cropping'][0][0], cfg['cropping'][0][1], cfg['cropping'][1][0],cfg['cropping'][1][1], LI[inp][0]))
|
||||
elif('1d' in layer.name):
|
||||
fp.write('\tlayer[{0}] = model.hook(Cropping(border(0,0,{1},{2})), layer[{3}]);\n'.format(
|
||||
id, cfg['cropping'][0], cfg['cropping'][1], LI[inp][0]))
|
||||
|
||||
# others
|
||||
elif('flatten' in layer.name): # flatten is needed in CHW backend but not needed in HWC
|
||||
inp = layer.input.name.replace(':', '/').split('/')[0]
|
||||
fp.write('\tlayer[%s] = model.hook(Flatten(), layer[%s]);\n'%(id, LI[inp][0]))
|
||||
elif('concatenate' in layer.name):
|
||||
inps = [input.name.replace(':','/').split('/')[0] for input in layer.input]
|
||||
inX = ''
|
||||
for inp in inps:
|
||||
inX += ' ,layer[%d]'%(LI[inp][0])
|
||||
cfg = layer.get_config()
|
||||
fp.write('\tlayer[%s] = model.mergex(Concat(%s), %s%s);\n'%(
|
||||
id, cfg['axis'], len(inps), inX))
|
||||
elif('add' in layer.name):
|
||||
inps = [input.name.replace(':','/').split('/')[0] for input in layer.input]
|
||||
inX = ''
|
||||
for inp in inps:
|
||||
inX += ' ,layer[%d]'%(LI[inp][0])
|
||||
fp.write('\tlayer[%s] = model.mergex(Add(%s_OUTPUT_RSHIFT), %s%s);\n'%(
|
||||
id, layer.name.upper(), len(inps), inX))
|
||||
elif('subtract' in layer.name):
|
||||
inps = [input.name.replace(':','/').split('/')[0] for input in layer.input]
|
||||
inX = ''
|
||||
for inp in inps:
|
||||
inX += ' ,layer[%d]'%(LI[inp][0])
|
||||
fp.write('\tlayer[%s] = model.mergex(Sub(%s_OUTPUT_RSHIFT), %s%s);\n'%(
|
||||
id, layer.name.upper(), len(inps), inX))
|
||||
elif('multiply' in layer.name):
|
||||
warnings.warn("Warning mutiply is under testing")
|
||||
inps = [input.name.replace(':','/').split('/')[0] for input in layer.input]
|
||||
inX = ''
|
||||
for inp in inps:
|
||||
inX += ' ,layer[%d]'%(LI[inp][0])
|
||||
fp.write('\tlayer[%s] = model.mergex(Mult(%s_OUTPUT_RSHIFT), %s%s);\n'%(
|
||||
id, layer.name.upper(), len(inps), inX))
|
||||
elif('dense' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
fp.write('\tlayer[{0}] = model.hook(Dense({1}, &{2}_w, &{2}_b), layer[{3}]);\n'.format(
|
||||
id, cfg['units'], layer.name, LI[inp][0]))
|
||||
elif('softmax' in layer.name):
|
||||
inp = layer.input.name.replace(':','/').split('/')[0]
|
||||
fp.write('\tlayer[%s] = model.hook(Softmax(), layer[%s]);\n'%(id, LI[inp][0]))
|
||||
else:
|
||||
raise Exception('unsupported layer', layer.name, layer)
|
||||
|
||||
"""
|
||||
# temporary fixed for activations attached into layers in construction
|
||||
def is_activation_attached(layer):
|
||||
if(("Softmax" in layer.output.name and "softmax" not in layer.name)or
|
||||
("Relu" in layer.output.name and "re_lu" not in layer.name) or
|
||||
("Sigmoid" in layer.output.name and "sigmoid" not in layer.name) or
|
||||
("Tanh" in layer.output.name and "tanh" not in layer.name)):
|
||||
return True
|
||||
return False
|
||||
if "input" not in layer.name and is_activation_attached(layer):
|
||||
inp = layer.output.name.replace(':', '/').split('/')[0]
|
||||
cfg = layer.get_config()
|
||||
if(cfg['activation'] == 'relu'):
|
||||
fp.write('\tlayer[%s] = model.active(act_relu(), layer[%s]);\n'%(id, LI[inp][0]))
|
||||
if(cfg['activation'] == 'tanh'):
|
||||
fp.write('\tlayer[%s] = model.active(act_tanh(%s_OUTPUT_SHIFT), layer[%s]);\n'%(id, inp.upper(), LI[inp][0]))
|
||||
if(cfg['activation'] == 'sigmoid'):
|
||||
fp.write('\tlayer[%s] = model.active(act_sigmoid(%s_OUTPUT_SHIFT), layer[%s]);\n'%(id, inp.upper(), LI[inp][0]))
|
||||
elif(cfg['activation'] == 'softmax'):
|
||||
fp.write('\tlayer[%s] = model.hook(Softmax(), layer[%s]);\n'%(id, LI[inp][0]))
|
||||
"""
|
||||
|
||||
# FIXME, test later.
|
||||
if('softmax' in layer.name
|
||||
or ('activation' in layer.name and layer.get_config()['activation'] == 'softmax')):
|
||||
fp.write('\tlayer[%s] = model.hook(Output(shape(%s,1,1), nnom_output_data), layer[%s]);\n'%(id+1, layer.output.shape[1], id))
|
||||
elif len(layer.output.shape) == 4:
|
||||
fp.write('\tlayer[%s] = model.hook(Output(shape%s, nnom_output_data), layer[%s]);\n'%(id+1, layer.output.shape[1:], id))
|
||||
elif len(layer.output.shape) == 3:
|
||||
fp.write('\tlayer[%s] = model.hook(Output(shape(1,%s,%s), nnom_output_data), layer[%s]);\n'%(id+1, layer.output.shape[1], layer.output.shape[2], id))
|
||||
elif len(layer.output.shape) == 2:
|
||||
fp.write('\tlayer[%s] = model.hook(Output(shape(%s,1,1), nnom_output_data), layer[%s]);\n'%(id+1, layer.output.shape[1], id))
|
||||
else:
|
||||
raise Exception('unsupported output shape of the last layer', layer.name, layer)
|
||||
fp.write('\tmodel_compile(&model, layer[0], layer[%s]);\n'%(id+1))
|
||||
if(ID>32):
|
||||
fp.write('\tfree(layer);\n')
|
||||
fp.write('\treturn &model;\n}\n')
|
||||
with open('.shift_list','w') as fp:
|
||||
fp.write(str(shift_list))
|
||||
|
||||
def evaluate_model(model, x_test, y_test, running_time=False, to_file='evaluation.txt'):
|
||||
# Score trained model.
|
||||
scores = model.evaluate(x_test, y_test, verbose=2)
|
||||
print('Test loss:', scores[0])
|
||||
print('Top 1:', scores[1])
|
||||
|
||||
if(len(y_test.shape)>1):
|
||||
# predictions = model.predict(x_test)
|
||||
# output = tf.keras.metrics.top_k_categorical_accuracy(y_test, predictions, k=2)
|
||||
# # with tf.Session() as sess:
|
||||
# # result = sess.run(output)
|
||||
# result =
|
||||
# print("Top 2:",result)
|
||||
|
||||
predictions = model.predict(x_test)
|
||||
matrix = metrics.confusion_matrix(y_test.argmax(axis=1), predictions.argmax(axis=1))
|
||||
print(matrix)
|
||||
|
||||
run_time = 0
|
||||
if running_time:
|
||||
# try to calculate the time
|
||||
T = time.time()
|
||||
for i in range(10):
|
||||
model.predict(x_test)
|
||||
T = time.time() - T
|
||||
run_time = round((T / 10 / x_test.shape[0] * 1000 * 1000), 2)
|
||||
print("Runing time:",run_time , "us" )
|
||||
#
|
||||
with open(to_file, 'w') as f:
|
||||
f.write("Runing time: "+ str(run_time) + "us" + "\n")
|
||||
f.write('Test loss:'+ str(scores[0]) + "\n")
|
||||
f.write('Top 1:'+ str(scores[1])+ "\n")
|
||||
if (len(y_test.shape) > 1):
|
||||
#f.write("Top 2:"+ str(result)+ "\n")
|
||||
#f.write(str(matrix))
|
||||
for row in matrix:
|
||||
row.tofile(f, sep=',')
|
||||
f.write("\n")
|
||||
|
||||
# try to check the weight and bias dec ranges
|
||||
for layer in model.layers:
|
||||
if (not layer.weights):
|
||||
continue
|
||||
for var in layer.weights:
|
||||
var_name = str(var.name)
|
||||
if ("kernel" in var_name):
|
||||
var_values = layer.get_weights()[0] # weight
|
||||
else:
|
||||
var_values = layer.get_weights()[1] # bias
|
||||
min_value = np.min(var_values)
|
||||
max_value = np.max(var_values)
|
||||
intt = int(np.ceil(np.log2(max(abs(min_value), abs(max_value)))))
|
||||
dec = 7 - intt
|
||||
print(var_name, "Dec num:", dec)
|
||||
return scores
|
||||
|
||||
def f2q(d, Q):
|
||||
'''To convert a number from floating point to Qm.n format:
|
||||
1. Multiply the floating point number by 2n
|
||||
2. Round to the nearest integer
|
||||
'''
|
||||
return np.round(d*2**Q)
|
||||
|
||||
|
||||
def q2f(d, Q):
|
||||
'''To convert a number from Qm.n format to floating point:
|
||||
1. Convert the number to floating point as if it were an integer, in other words remove the binary point
|
||||
2. Multiply by 2-n
|
||||
'''
|
||||
return d*2**-Q
|
||||
|
||||
def show_weights(w, name):
|
||||
sz = 1
|
||||
for s in w.shape:
|
||||
sz = sz*s
|
||||
aL = w.reshape(sz,)
|
||||
MIN,MAX=min(aL),max(aL)
|
||||
Q = int(np.ceil(np.log2(max(abs(MIN),abs(MAX)))))
|
||||
Q = 7-Q
|
||||
qL = f2q(aL,Q)
|
||||
qL = q2f(qL,Q)
|
||||
plt.figure(figsize=(18, 3))
|
||||
plt.subplot(131)
|
||||
plt.title(name)
|
||||
plt.plot(aL)
|
||||
plt.grid()
|
||||
aL.sort()
|
||||
plt.plot(aL,'r')
|
||||
plt.grid()
|
||||
plt.subplot(132)
|
||||
plt.title('Q%s'%(Q))
|
||||
qL.sort()
|
||||
plt.plot(aL,'r')
|
||||
plt.plot(qL,'g')
|
||||
plt.grid()
|
||||
plt.subplot(133)
|
||||
plt.hist(aL,100)
|
||||
plt.title('hist')
|
||||
plt.grid()
|
||||
plt.show()
|
||||
|
||||
def compare(a,b,name):
|
||||
sz = 1
|
||||
for s in a.shape:
|
||||
sz = sz*s
|
||||
aL = a.reshape(sz,)
|
||||
bL = b.reshape(sz,)
|
||||
assert(len(aL) == len(bL))
|
||||
Z = list(zip(aL,bL))
|
||||
Z.sort(key=lambda x: x[0])
|
||||
aL1,bL1=zip(*Z)
|
||||
plt.figure(figsize=(18, 3))
|
||||
plt.subplot(131)
|
||||
plt.plot(aL)
|
||||
plt.plot(aL1,'r')
|
||||
plt.grid()
|
||||
plt.title('tf-%s'%(name))
|
||||
plt.subplot(133)
|
||||
plt.plot(bL1,'g')
|
||||
plt.plot(aL1,'r')
|
||||
plt.grid()
|
||||
plt.title('compare')
|
||||
plt.subplot(132)
|
||||
bL1=list(bL1)
|
||||
bL1.sort()
|
||||
plt.plot(bL)
|
||||
plt.plot(bL1,'g')
|
||||
plt.grid()
|
||||
plt.title('nn-%s'%(name))
|
||||
plt.show()
|
||||
|
||||
Reference in New Issue
Block a user