Source code for mltk.core.tflite_model.tflite_types

#pylint: disable=redefined-builtin
from __future__ import annotations
from typing import List, Tuple
from enum import IntEnum, EnumMeta
from dataclasses import dataclass, field
import numpy as np

from .tflite_tensor import TfliteTensor
from . import tflite_schema as _tflite_schema_fb



[docs]class TfliteActivation(IntEnum): """Activation types This may be instantiated with either an integer OR string, e.g.: .. highlight:: python .. code-block:: python activation = TfliteActivation(0) activation = TfliteActivation('relu') """ NONE = _tflite_schema_fb.ActivationFunctionType.NONE """No activation, linear pass through""" RELU = _tflite_schema_fb.ActivationFunctionType.RELU """Rectified Linear Unit, f(x) = max(0, x)""" RELU_N1_TO_1 = _tflite_schema_fb.ActivationFunctionType.RELU_N1_TO_1 """Rectified Linear Unit, -1 to 1, f(x) = min(max(-1, x), 1)""" RELU6 = _tflite_schema_fb.ActivationFunctionType.RELU6 """Rectified Linear Unit, 0 to 6, f(x) = min(max(0, x), 6)""" TANH = _tflite_schema_fb.ActivationFunctionType.TANH """Hyperbolic Tangent, f(x) = tanh(x)""" SIGN_BIT = _tflite_schema_fb.ActivationFunctionType.SIGN_BIT """Sigmoid, f(x) = e^x / (e^x + 1)"""
[docs] def to_string(self) -> str: """Return the activation as a string""" return self.name.title()
def __str__(self) -> str: return self.to_string()
# This allows for providing a string or int to the constructor TfliteActivation.__new__ = \ lambda cls, value: super(TfliteActivation, cls).__new__(cls, _convert_object_value_to_int(_tflite_schema_fb.ActivationFunctionType(), value)) # This allows for providing a string or int to the constructor # as well as providing the width and height class _TflitePaddingEnumMeta(EnumMeta): def __call__(cls, value, *args, width:int=0, height:int=0): self = super(EnumMeta, cls).__call__( _convert_object_value_to_int(_tflite_schema_fb.Padding(), value), ) if len(args) == 2: self.width = args[0] self.height = args[1] else: self.width = width self.height = height return self
[docs]class TflitePadding(IntEnum, metaclass=_TflitePaddingEnumMeta): """Padding types This may be instantiated with either an integer OR string, e.g.: .. highlight:: python .. code-block:: python padding = TflitePadding(0) padding = TflitePadding('same') """ SAME = _tflite_schema_fb.Padding.SAME """Add zeros to the borders of the input so that the output has the same dimensions when applying the kernel .. highlight:: python .. code-block:: python out_height = ceil(float(in_height) / float(stride_height)) out_width = ceil(float(in_width) / float(stride_width)) """ VALID = _tflite_schema_fb.Padding.VALID """Apply the kernel only the valid regions of the input, the output dimensions will be reduced .. highlight:: python .. code-block:: python out_height = ceil(float(in_height - filter_height + 1) / float(stride_height)) out_width = ceil(float(in_width - filter_width + 1) / float(stride_width)) """ @property def width(self) -> int: """The calculated width of the padding for the current layer""" return getattr(self, '_width', 0) @width.setter def width(self, v:int): setattr(self, '_width', v) @property def height(self) -> int: """The calculated height of the padding for the current layer""" return getattr(self, '_height', 0) @height.setter def height(self, v:int): setattr(self, '_height', v)
[docs] def to_string(self) -> str: """Return the padding as a string""" return self.name.title()
def __str__(self) -> str: return self.to_string()
[docs]@dataclass class TfliteConvParams: """Calculated Convolution Parameters .. seealso:: - `ConvParamsQuantize <https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/kernels/conv_common.cc#L54>`_ - `Quantization Specification <https://www.tensorflow.org/lite/performance/quantization_spec>`_ """ padding:TflitePadding = field(default_factory=lambda: TflitePadding.SAME) """Kernel padding""" stride_width:int = 0 """Kernel width""" stride_height:int = 0 """Kernel height""" dilation_width_factor:int = 1 """Kernel dilation width factor""" dilation_height_factor:int = 1 """Kernel dilation height factor""" input_offset:int = 0 """Input quantization offset (i.e. zero point)""" weights_offset:int = 0 """Weight (aka filters) quantization offset (i.e. zero point)""" output_offset:int = 0 """Output quantization offset (i.e. zero point)""" per_channel_output_multiplier:List[int] = field(default_factory=list) """Per layer multipliers for output scalers""" per_channel_output_shift:List[int] = field(default_factory=list) """Per layer shifts for output scalers""" quantized_activation_min:int = 0 """Fused activation min value""" quantized_activation_max:int = 0 """Fused activation max value"""
[docs] @staticmethod def calculate(layer) -> TfliteConvParams: """Calculate the parameters for the given layer""" from .tflite_layer import TfliteConv2dLayer layer:TfliteConv2dLayer = layer options = layer.options input = layer.input_tensor filters = layer.filters_tensor output = layer.output_tensor input_height = input.shape[1] input_width = input.shape[2] filter_height = filters.shape[1] filter_width = filters.shape[2] params = TfliteConvParams() params.padding = options.padding params.stride_width = options.stride_width params.stride_height = options.stride_height params.dilation_width_factor = options.dilationWFactor params.dilation_height_factor = options.dilationHFactor params.input_offset = int(-input.quantization.zeropoint[0]) params.weights_offset = int(-filters.quantization.zeropoint[0]) params.output_offset = int(output.quantization.zeropoint[0]) _compute_padding_height_width( padding = params.padding, stride_height = options.stride_height, stride_width = options.stride_width, dilation_height_factor = options.dilationHFactor, dilation_width_factor = options.dilationWFactor, height = input_height, width = input_width, filter_height = filter_height, filter_width = filter_width ) _populate_convolution_quantization_params( input=input, filters=filters, output=output, per_channel_multiplier=params.per_channel_output_multiplier, per_channel_shift=params.per_channel_output_shift, ) params.quantized_activation_min, params.quantized_activation_max = \ _calculate_activation_range_quantized( activation=options.activation, output=output, ) return params
[docs]@dataclass class TfliteDepthwiseConvParams: """Calculated Depthwise Convolution Parameters .. seealso:: - `DepthwiseConvParamsQuantized <https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/kernels/depthwise_conv_common.cc#L59>`_ - `Quantization Specification <https://www.tensorflow.org/lite/performance/quantization_spec>`_ """ padding:TflitePadding = field(default_factory=lambda: TflitePadding.SAME) """Kernel padding""" stride_width:int = 0 """Kernel width""" stride_height:int = 0 """Kernel height""" dilation_width_factor:int = 1 """Kernel dilation width factor""" dilation_height_factor:int = 1 """Kernel dilation height factor""" depth_multiplier:int = 0 """Depth multiplier""" input_offset:int = 0 """Input quantization offset (i.e. zero point)""" weights_offset:int = 0 """Weight (aka filters) quantization offset (i.e. zero point)""" output_offset:int = 0 """Output quantization offset (i.e. zero point)""" per_channel_output_multiplier:List[int] = field(default_factory=list) """Per layer multipliers for output scalers""" per_channel_output_shift:List[int] = field(default_factory=list) """Per layer shifts for output scalers""" quantized_activation_min:int = 0 """Fused activation min value""" quantized_activation_max:int = 0 """Fused activation max value"""
[docs] @staticmethod def calculate(layer) -> TfliteDepthwiseConvParams: """Calculate the parameters for the given layer""" from .tflite_layer import TfliteDepthwiseConv2dLayer layer:TfliteDepthwiseConv2dLayer = layer options = layer.options input = layer.input_tensor filters = layer.filters_tensor output = layer.output_tensor input_height = input.shape[1] input_width = input.shape[2] filter_height = filters.shape[1] filter_width = filters.shape[2] params = TfliteDepthwiseConvParams() params.padding = options.padding params.stride_width = options.stride_width params.stride_height = options.stride_height params.dilation_width_factor = options.dilationWFactor params.dilation_height_factor = options.dilationHFactor params.depth_multiplier = options.depthMultiplier params.input_offset = int(-input.quantization.zeropoint[0]) params.weights_offset = int(-filters.quantization.zeropoint[0]) params.output_offset = int(output.quantization.zeropoint[0]) _compute_padding_height_width( padding = params.padding, stride_height = options.stride_height, stride_width = options.stride_width, dilation_height_factor = options.dilationHFactor, dilation_width_factor = options.dilationWFactor, height = input_height, width = input_width, filter_height = filter_height, filter_width = filter_width ) _populate_convolution_quantization_params( input=input, filters=filters, output=output, per_channel_multiplier=params.per_channel_output_multiplier, per_channel_shift=params.per_channel_output_shift, ) params.quantized_activation_min, params.quantized_activation_max = \ _calculate_activation_range_quantized( activation=options.activation, output=output, ) return params
[docs]@dataclass class TfliteTransposeConvParams: """Calculated Transpose Convolution Parameters .. seealso:: - `CalculateOpData <https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/kernels/transpose_conv.cc#L72>`_ - `Quantization Specification <https://www.tensorflow.org/lite/performance/quantization_spec>`_ """ padding:TflitePadding = field(default_factory=lambda: TflitePadding.SAME) """Kernel padding""" stride_width:int = 0 """Kernel width""" stride_height:int = 0 """Kernel height""" input_offset:int = 0 """Input quantization offset (i.e. zero point)""" weights_offset:int = 0 """Weight (aka filters) quantization offset (i.e. zero point)""" output_offset:int = 0 """Output quantization offset (i.e. zero point)""" per_channel_output_multiplier:List[int] = field(default_factory=list) """Per layer multipliers for output scalers""" per_channel_output_shift:List[int] = field(default_factory=list) """Per layer shifts for output scalers""" quantized_activation_min:int = 0 """Fused activation min value""" quantized_activation_max:int = 0 """Fused activation max value"""
[docs] @staticmethod def calculate(layer) -> TfliteTransposeConvParams: """Calculate the parameters for the given layer""" from .tflite_layer import TfliteTransposeConvLayer layer:TfliteTransposeConvLayer = layer options = layer.options input = layer.input_tensor filters = layer.filters_tensor output = layer.output_tensor input_height = input.shape[1] input_width = input.shape[2] filter_height = filters.shape[1] filter_width = filters.shape[2] params = TfliteTransposeConvParams() params.padding = options.padding params.stride_width = options.stride_width params.stride_height = options.stride_height params.input_offset = int(-input.quantization.zeropoint[0]) params.weights_offset = int(-filters.quantization.zeropoint[0]) params.output_offset = int(output.quantization.zeropoint[0]) _compute_padding_height_width( padding = params.padding, stride_height = options.stride_height, stride_width = options.stride_width, dilation_height_factor = 1, dilation_width_factor = 1, height = input_height, width = input_width, filter_height = filter_height, filter_width = filter_width ) _populate_convolution_quantization_params( input=input, filters=filters, output=output, per_channel_multiplier=params.per_channel_output_multiplier, per_channel_shift=params.per_channel_output_shift, ) params.quantized_activation_min, params.quantized_activation_max = \ _calculate_activation_range_quantized( activation=TfliteActivation.NONE, output=output, ) return params
[docs]@dataclass class TfliteFullyConnectedParams: """Calculated Full Connected Parameters .. seealso:: - `FullyConnectedParamsQuantized <https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/kernels/fully_connected_common.cc#L34>`_ - `Quantization Specification <https://www.tensorflow.org/lite/performance/quantization_spec>`_ """ input_offset:int = 0 """Input quantization offset (i.e. zero point)""" weights_offset:int = 0 """Weight (aka filters) quantization offset (i.e. zero point)""" output_offset:int = 0 """Output quantization offset (i.e. zero point)""" output_multiplier:int = 0 """Output multiplier for its quantization scaler""" output_shift:int = 0 """Output shift for its quantization scaler""" quantized_activation_min:int = 0 """Fused activation min value""" quantized_activation_max:int = 0 """Fused activation max value"""
[docs] @staticmethod def calculate(layer) -> TfliteFullyConnectedParams: """Calculate the parameters for the given layer""" from .tflite_layer import TfliteFullyConnectedLayer layer:TfliteFullyConnectedLayer = layer options = layer.options input = layer.input_tensor weights = layer.weights_tensor output = layer.output_tensor params = TfliteFullyConnectedParams() params.input_offset = int(-input.quantization.zeropoint[0]) params.weights_offset = int(-weights.quantization.zeropoint[0]) params.output_offset = int(output.quantization.zeropoint[0]) input_product_scale = float(np.float32(input.quantization.scale[0]) * np.float32(weights.quantization.scale[0])) # NOTE: TFLM uses float32 for the multiplication real_multiplier = input_product_scale / output.quantization.scale[0] params.output_multiplier, params.output_shift = _quantize_multiplier(real_multiplier) params.quantized_activation_min, params.quantized_activation_max = \ _calculate_activation_range_quantized( activation=options.activation, output=output, ) return params
[docs]@dataclass class TflitePoolParams: """Calculated Pooling Parameters .. seealso:: - `AveragePoolingEvalQuantized <https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/kernels/pooling_common.cc#L105>`_ - `Quantization Specification <https://www.tensorflow.org/lite/performance/quantization_spec>`_ """ padding:TflitePadding = field(default_factory=lambda: TflitePadding.SAME) """Kernel padding""" stride_width:int = 0 """Kernel width""" stride_height:int = 0 """Kernel height""" quantized_activation_min:int = 0 """Fused activation min value""" quantized_activation_max:int = 0 """Fused activation max value"""
[docs] @staticmethod def calculate(layer) -> TflitePoolParams: """Calculate the parameters for the given layer""" from .tflite_layer import TflitePooling2dLayer layer:TflitePooling2dLayer = layer options = layer.options input = layer.input_tensor output = layer.output_tensor input_height = input.shape[1] input_width = input.shape[2] filter_height = options.filter_height filter_width = options.filter_width params = TflitePoolParams() params.padding = options.padding params.stride_width = options.stride_width params.stride_height = options.stride_height _compute_padding_height_width( padding = params.padding, stride_height = options.stride_height, stride_width = options.stride_width, dilation_height_factor = 1, dilation_width_factor = 1, height = input_height, width = input_width, filter_height = filter_height, filter_width = filter_width ) params.quantized_activation_min, params.quantized_activation_max = \ _calculate_activation_range_quantized( activation=options.activation, output=output, ) return params
def _compute_padding_height_width( padding: TflitePadding, stride_height:int, stride_width:int, dilation_height_factor:int, dilation_width_factor:int, height:int, width:int, filter_height:int, filter_width:int, ): out_width = _compute_out_size( padding_type=padding, image_size=width, filter_size=filter_width, stride=stride_width, dilation_rate=dilation_width_factor ) out_height = _compute_out_size( padding_type=padding, image_size=height, filter_size=filter_height, stride=stride_height, dilation_rate=dilation_height_factor ) padding.width, _ = _compute_padding_with_offset( stride=stride_width, dilation_rate=dilation_width_factor, in_size=width, filter_size=filter_width, out_size=out_width ) padding.height, _ = _compute_padding_with_offset( stride=stride_height, dilation_rate=dilation_height_factor, in_size=height, filter_size=filter_height, out_size=out_height ) def _populate_convolution_quantization_params( input: TfliteTensor, filters: TfliteTensor, output: TfliteTensor, per_channel_multiplier:List[int], per_channel_shift:List[int], ) -> Tuple[int, int]: num_channels = filters.shape[filters.quantization.quantization_dimension] input_scale = input.quantization.scale[0] output_scale = output.quantization.scale[0] filter_scales = filters.quantization.scale is_per_channel = len(filter_scales) > 1 for i in range(num_channels): # If per-tensor quantization parameter is specified, broadcast it along the # quantization dimension (channels_out). filter_scale = filter_scales[i] if is_per_channel else filter_scales[0] effective_output_scale = input_scale * filter_scale / output_scale significand, channel_shift = _quantize_multiplier(effective_output_scale) per_channel_multiplier.append(significand) per_channel_shift.append(channel_shift) def _calculate_activation_range_quantized( activation:TfliteActivation, output:TfliteTensor ) -> Tuple[int, int]: scale = output.quantization.scale[0] zeropoint = output.quantization.zeropoint qmin = np.iinfo(output.dtype).min qmax = np.iinfo(output.dtype).max if activation == TfliteActivation.RELU: tmp_q = _quantize(scale, zeropoint, 0.0) act_min = max(qmin, tmp_q) act_max = qmax elif activation == TfliteActivation.RELU6: tmp_q = _quantize(scale, zeropoint, 0.0) act_min = max(qmin, tmp_q) tmp_q = _quantize(scale, zeropoint, 6.0) act_max = min(qmax, tmp_q) elif activation == TfliteActivation.RELU_N1_TO_1: tmp_q = _quantize(scale, zeropoint, -1.0) act_min = max(qmin, tmp_q) tmp_q = _quantize(scale, zeropoint, 1.0) act_max = min(qmax, tmp_q) else: act_min = qmin act_max = qmax return act_min, act_max def _compute_out_size( padding_type: TflitePadding, image_size:int, filter_size:int, stride:int, dilation_rate:int = 1, ) -> int: effective_filter_size = (filter_size - 1) * dilation_rate + 1 if stride == 0: return 0 if padding_type == TflitePadding.SAME: return (image_size + stride - 1) // stride elif padding_type == TflitePadding.VALID: return (image_size + stride - effective_filter_size) // stride else: return 0 def _compute_padding_with_offset( stride:int, dilation_rate:int, in_size:int, filter_size:int, out_size:int, ) -> Tuple[int, int]: effective_filter_size = (filter_size - 1) * dilation_rate + 1 total_padding = ((out_size - 1) * stride + effective_filter_size - in_size) total_padding = max(total_padding, 0) padding = total_padding // 2 offset = total_padding % 2 return padding, offset def _quantize_multiplier(double_multiplier:float) -> Tuple[int, int]: ''' Quantize floating point multiplier: returns 'shift' and 'quantized_multiplier' such that double_multiplier = quantized_multiplier * (2**shift), where shift is a signed integer and quantized_multiplier is q(1.31) in [0.5, 1.0) ''' if double_multiplier == 0: quantized_multiplier=0 shift=0 else: shift = np.ceil(np.log2(double_multiplier)) quantized_multiplier = (2**-shift)*double_multiplier quantized_multiplier = (2**32)*quantized_multiplier quantized_multiplier += 1 quantized_multiplier /= 2 return int(quantized_multiplier), shift _int32_max = np.iinfo(np.int32).max _int32_min = np.iinfo(np.int32).min def _quantize(scale:float, zeropoint:int, f:float) -> int: tmp = round(f / scale) assert tmp >= _int32_min and tmp <= _int32_max q = zeropoint + int(tmp) return q def _convert_object_value_to_int(obj:object, needle:str, default:int=-1) -> int: if isinstance(needle, int): return needle for key in dir(obj): if key.lower() == needle.lower(): return getattr(obj, key) return default