Keras
TensorFlow
numpy
machine learning
deep learning

Using pure numpy metric as metric in Keras/TensorFlow

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A Keras metric runs during model execution, which means it normally receives TensorFlow tensors rather than ordinary NumPy arrays. That is why a pure NumPy metric cannot be dropped into model.compile unchanged; you either have to wrap it with tf.numpy_function or rewrite the metric with TensorFlow ops.

Why a Pure NumPy Metric Does Not Plug In Directly

Keras metrics are executed inside TensorFlow's runtime. During training, y_true and y_pred are tensors that may live on a device and participate in graph execution. A plain NumPy function expects concrete in-memory arrays on the Python side.

Those worlds are different.

If you call NumPy operations directly on TensorFlow tensors inside a metric, you usually get one of three outcomes:

  • eager-mode code that works in simple tests but is fragile or slow in training
  • graph-mode failures because NumPy cannot execute on symbolic tensors
  • silent performance loss because data must cross the TensorFlow-Python boundary

That is why the preferred answer is usually: if possible, implement the metric in TensorFlow ops instead of NumPy.

The Preferred Solution: Rewrite the Metric with TensorFlow Ops

Suppose you want mean absolute percentage error with a small denominator guard. A TensorFlow-native metric is straightforward.

python
1import tensorflow as tf
2
3
4def safe_mape_tf(y_true, y_pred):
5    epsilon = tf.constant(1e-7, dtype=y_pred.dtype)
6    denom = tf.maximum(tf.abs(y_true), epsilon)
7    return tf.reduce_mean(tf.abs((y_true - y_pred) / denom)) * 100.0

Because this uses TensorFlow operations only, it integrates cleanly with model.compile.

python
1from tensorflow import keras
2
3model = keras.Sequential([
4    keras.layers.Input(shape=(4,)),
5    keras.layers.Dense(8, activation="relu"),
6    keras.layers.Dense(1)
7])
8
9model.compile(optimizer="adam", loss="mse", metrics=[safe_mape_tf])

This version is portable, traceable, and much easier to optimize.

If You Must Use NumPy, Wrap It Explicitly

Sometimes you already have a tested NumPy metric and want to reuse it for reporting. In that case, tf.numpy_function is the bridge.

python
1import numpy as np
2import tensorflow as tf
3
4
5def rmse_numpy(y_true, y_pred):
6    y_true = np.asarray(y_true)
7    y_pred = np.asarray(y_pred)
8    return np.sqrt(np.mean((y_true - y_pred) ** 2)).astype(np.float32)
9
10
11def rmse_metric(y_true, y_pred):
12    value = tf.numpy_function(rmse_numpy, [y_true, y_pred], tf.float32)
13    value.set_shape(())
14    return value

This lets Keras call a NumPy implementation during metric computation.

python
1from tensorflow import keras
2
3model = keras.Sequential([
4    keras.layers.Input(shape=(2,)),
5    keras.layers.Dense(1)
6])
7model.compile(optimizer="adam", loss="mse", metrics=[rmse_metric])

The set_shape(()) call is useful because TensorFlow otherwise loses static shape information about the returned scalar.

Important Limits of tf.numpy_function

This wrapper is a compatibility tool, not a best-practice default.

It has several costs:

  • execution crosses from TensorFlow into Python
  • the metric is harder to serialize and export cleanly
  • it does not become a differentiable TensorFlow op
  • distributed or accelerated execution may be less efficient

That last point matters. Metrics do not need gradients, but they still run during training or evaluation. A slow Python callback path can become noticeable if the metric is computed often.

For that reason, a NumPy metric is usually acceptable for occasional evaluation or experimentation, but less attractive as a permanent training-time metric in a production pipeline.

When a Callback Is Better Than a Metric

If the metric is expensive, highly custom, or only meaningful at epoch boundaries, consider computing it in a callback instead of as a live training metric. That keeps the training step simpler while still letting you log the number you care about.

In other words, ask whether the value truly needs to be computed on every batch or whether periodic evaluation is enough.

Common Pitfalls

Trying to use a raw NumPy function directly in model.compile is the most common mistake. Keras expects TensorFlow-compatible callables there.

Forgetting to set the returned tensor shape after tf.numpy_function can also cause shape-related confusion later.

Assuming that tf.numpy_function makes a metric fast is another mistake. It is convenient, not free.

Finally, do not confuse metrics with losses. A NumPy-wrapped function is unsuitable for backpropagation as a training loss because TensorFlow cannot differentiate through ordinary NumPy code.

Summary

  • Keras metrics normally need TensorFlow tensors, not pure NumPy arrays
  • the best long-term fix is to rewrite the metric with TensorFlow ops
  • 'tf.numpy_function can wrap an existing NumPy metric when reuse matters more than portability or speed'
  • wrapped NumPy metrics are fine for reporting but are less ideal for production-grade training loops
  • never use a pure NumPy implementation as a differentiable training loss

Course illustration
Course illustration

All Rights Reserved.