0
$\begingroup$

I trained a Keras Network. During training, I would first initialize a normalizer from the values in the entire dataset, then partition into train, test and validation datasets. After partitioning, I applied the normalizer to each set.

Now I wish to use the network in production.

As the new data arrive, and I wish to execute the network, I obviously need to normalize the input. Must I pre-initialize the normalizer with the values from the original training set, in order for the normalization step to be valid for the network as trained?

I can't envision how it would work otherwise. Thank you.

$\endgroup$

2 Answers 2

2
$\begingroup$

Yes that is indeed the case. You must store the used normalization weights, and use them also with the new data. Otherwise their distribution would be different, and your model's performance will be very poor.

Actually I got a crazy(?) idea just now, could you add a BatchNormalization layer right after the network's input layer? This way it would be automatically be stored as part of the model. I haven't ever tested this idea, or seen it being used anywhere.

$\endgroup$
3
  • 2
    $\begingroup$ Your idea is already here: tensorflow.org/api_docs/python/tf/keras/layers/Normalization :) This performs standardization, so if one requires a different normalization (like min-max scaling) is possible to create a custom pre-processing layer with weights that would be learned before training, then fixed, and finally saved along the model's weights $\endgroup$ Commented Sep 18, 2023 at 12:04
  • 2
    $\begingroup$ Ahaa I didn't know about that one, it seems like a better fit for this purpose rather than the BatchNormalization. $\endgroup$ Commented Sep 19, 2023 at 8:16
  • $\begingroup$ Thank you both very much for these helpful thoughts and alternative approaches. $\endgroup$ Commented Sep 20, 2023 at 18:52
1
$\begingroup$

Given the answer provided by @NikoNyrh is correct, I just want to add how you can make a custom pre-processing layer with tensorflow that you can integrate in your model.

Assume you want to build a min-max scaler:

import tensorflow as tf

from tensorflow.keras.layers.experimental.preprocessing import PreprocessingLayer
from tensorflow.keras.layers import Input

class MinMaxScaler(PreprocessingLayer):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # assume normalization should occur on last axis
        self.axis = 1

        # weights: to be set in build()
        self.min_value: tf.Variable = None
        self.max_value: tf.Variable = None

    def build(self, input_shape):
        super().build(input_shape)

        input_shape = tf.TensorShape(input_shape).as_list()
        shape = (input_shape[self.axis],)

        self.min_value = self.add_weight(
            name="min_value", shape=shape, dtype=self.compute_dtype,
            initializer='zeros', trainable=False)

        self.max_value = self.add_weight(
            name="max_value", shape=shape, dtype=self.compute_dtype,
            initializer='zeros', trainable=False)

    def call(self, inputs):
        # Here the normalization occurs on the model' inputs
        inputs = tf.convert_to_tensor(inputs, dtype=self.compute_dtype)
        _range = self.max_value - self.min_value + tf.keras.backend.epsilon()
        return (inputs - self.min_value) / _range

    def update_state(self, data):
        # Normalization logic here
        data = tf.convert_to_tensor(data, dtype=self.compute_dtype)

        # find min-max boundaries
        batch_min = tf.reduce_min(data, axis=0)
        batch_max = tf.reduce_max(data, axis=0)

        # update the weights
        self.min_value.assign(tf.minimum(self.min_value, batch_min))
        self.max_value.assign(tf.maximum(self.max_value, batch_max))

    def reset_state(self):
        self.min_value.assign(tf.zeros_like(self.min_value))
        self.max_value.assign(tf.zeros_like(self.max_value))

You can use the MinMaxScaler layer within a keras model easily:

# build some model
inp = Input(input_shape=your_input_shape)
scaler = MinMaxScaler()
norm_inp = scaler(inp)
# ... hidden layers
out = ...

model = tf.keras.Model(inp, out)
model.compile(...)

# before training the model, you should call adapt() to find the normalization weights
scaler.adapt(some_data)
print(scaler.min_value, scaler.max_value)

# finally you can train the model
model.fit(...)

# save everything
model.save_weights()  # for example

Basically, the adapt() method computes the weights/parameters that are used to normalize the data when calling model(x), for example during inference. The custom layer can be included in a tf.keras.Model having all the saving and loading benefits, i.e., you don't have to keep track of the normalization statistics by yourself. Last advantage, is that the normalization/scaling logic can run on CPU/GPU and TPU as well.

Indeed, this is a minimum working example so in real code you want to add more functionalities and checks.

$\endgroup$
2
  • 1
    $\begingroup$ This is terrific. Thank you for working up this helpful example. Now this makes it difficult to determine which answer to accept! $\endgroup$ Commented Sep 20, 2023 at 18:58
  • $\begingroup$ @PittsburghDBA Thanks. Well, you can upvote both and accept what you find the most useful :) $\endgroup$ Commented Sep 21, 2023 at 13:27

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.