Source code for quantizeml.layers.activations

#!/usr/bin/env python
# ******************************************************************************
# Copyright 2022 Brainchip Holdings Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ******************************************************************************

__all__ = ["QuantizedReLU"]

import numpy as np
import tensorflow as tf
import keras

from .layers_base import (register_quantize_target, rescale_outputs,
                          tensor_inputs, apply_buffer_bitwidth)
from .quantizers import AlignedWeightQuantizer, OutputQuantizer
from ..tensors import FixedPoint, QFloat, QTensor


[docs]@register_quantize_target(keras.layers.ReLU) @tf.keras.utils.register_keras_serializable() class QuantizedReLU(keras.layers.Layer): """Quantized version of the ReLU activation layer applicable on FixedPoint tensor. Args: max_value (float, optional): ReLU maximum value. Defaults to 6. quant_config (dict, optional): the serialized quantization configuration. Defaults to None. """ unsupported_args = { 'negative_slope': 0, 'threshold': 0} def __init__(self, *args, max_value=6, quant_config=None, **kwargs): super().__init__(*args, **kwargs) self.quant_config = quant_config or dict() # Use quant_config to build quantizers out_quant_cfg = self.quant_config.get("output_quantizer", False) if out_quant_cfg: self.out_quantizer = OutputQuantizer(name="output_quantizer", **out_quant_cfg) else: self.out_quantizer = None self.buffer_bitwidth = apply_buffer_bitwidth(self.quant_config, signed=False) if max_value is not None: # Store max_value if isinstance(max_value, np.ndarray): max_value = max_value.item() max_value_quantizer_cfg = self.quant_config.get("max_value_quantizer", {}) self.max_value_quantizer = AlignedWeightQuantizer(name="max_value_quantizer", signed=False, **max_value_quantizer_cfg) self.max_value = max_value @tensor_inputs([QTensor]) @rescale_outputs def call(self, inputs): """ReLU activation function. In other terms: 1. clip the value between 0 and :attr:`max_value`. 2. quantize the output if an output_quantizer is set. Args: inputs (:obj:`QFloat`): the inputs tensor. Returns: :obj:`FixedPoint`: QuantizedReLU outputs. """ if isinstance(inputs, FixedPoint): # if inputs is FixedPoint, create an equivalent QFloat with scale # set to 1 inputs = QFloat(inputs, tf.constant(1.)) # Express zero as a QFloat aligned with the inputs because this is what the # dispatched operations expect. # The actual hardware implementation will simply use a zero integer. zero = QFloat(FixedPoint(tf.constant(0.), inputs.fp.value_bits, inputs.fp.frac_bits), inputs.scales) if self.max_value is None: # Just remove negative values return tf.math.maximum(inputs, zero) # Quantize and align max_value with the inputs max_value = self.max_value_quantizer(tf.cast(self.max_value, tf.float32), inputs) # Clip the inputs return tf.clip_by_value(inputs, zero, max_value) def get_config(self): config = super().get_config() config.update({ "max_value": self.max_value, "quant_config": self.quant_config }) return config