import numpy as np from keras.src import backend from keras.src.api_export import keras_export from keras.src.utils import tf_utils @keras_export("keras.utils.normalize") def normalize(x, axis=-1, order=2): """Normalizes an array. If the input is a NumPy array, a NumPy array will be returned. If it's a backend tensor, a backend tensor will be returned. Args: x: Array to normalize. axis: axis along which to normalize. order: Normalization order (e.g. `order=2` for L2 norm). Returns: A normalized copy of the array. """ from keras.src import ops if isinstance(x, np.ndarray): # NumPy input norm = np.atleast_1d(np.linalg.norm(x, order, axis)) norm[norm == 0] = 1 # axis cannot be `None` axis = axis or -1 return x / np.expand_dims(norm, axis) # Backend tensor input return ops.nn.normalize(x, axis=axis, order=order) @keras_export("keras.utils.to_categorical") def to_categorical(x, num_classes=None): """Converts a class vector (integers) to binary class matrix. E.g. for use with `categorical_crossentropy`. Args: x: Array-like with class values to be converted into a matrix (integers from 0 to `num_classes - 1`). num_classes: Total number of classes. If `None`, this would be inferred as `max(x) + 1`. Defaults to `None`. Returns: A binary matrix representation of the input as a NumPy array. The class axis is placed last. Example: >>> a = keras.utils.to_categorical([0, 1, 2, 3], num_classes=4) >>> print(a) [[1. 0. 0. 0.] [0. 1. 0. 0.] [0. 0. 1. 0.] [0. 0. 0. 1.]] >>> b = np.array([.9, .04, .03, .03, ... .3, .45, .15, .13, ... .04, .01, .94, .05, ... .12, .21, .5, .17]).reshape(4,4) >>> loss = keras.ops.categorical_crossentropy(a, b) >>> print(np.around(loss, 5)) [0.10536 0.82807 0.1011 1.77196] >>> loss = keras.ops.categorical_crossentropy(a, a) >>> print(np.around(loss, 5)) [0. 0. 0. 0.] """ if backend.is_tensor(x): input_shape = backend.core.shape(x) # Shrink the last dimension if the shape is (..., 1). if ( input_shape is not None and len(input_shape) > 1 and input_shape[-1] == 1 ): newshape = tuple(input_shape[:-1]) x = backend.numpy.reshape(x, newshape) return backend.nn.one_hot(x, num_classes) x = np.array(x, dtype="int64") input_shape = x.shape # Shrink the last dimension if the shape is (..., 1). if input_shape and input_shape[-1] == 1 and len(input_shape) > 1: input_shape = tuple(input_shape[:-1]) x = x.reshape(-1) if not num_classes: num_classes = np.max(x) + 1 batch_size = x.shape[0] categorical = np.zeros((batch_size, num_classes)) categorical[np.arange(batch_size), x] = 1 output_shape = input_shape + (num_classes,) categorical = np.reshape(categorical, output_shape) return categorical def encode_categorical_inputs( inputs, output_mode, depth, dtype, sparse=False, count_weights=None, backend_module=None, ): """Encodes categorical inputs according to output_mode. Args: inputs: the inputs to encode. output_mode: one of `"int"`, `"one_hot"`, `"multi_hot"`, or `"count"`. depth: number of classes, this will be the last dimension of the output. dtype: the dtype of the output, unless `count_weights` is not `None`. sparse: whether the output should be sparse for backends supporting it. count_weights: weights to apply if `output_mode` is `"count"`. backend_module: the backend to use instead of the current one. Returns: the encoded inputs. """ backend_module = backend_module or backend if output_mode == "int": return backend_module.cast(inputs, dtype=dtype) rank_of_inputs = len(backend_module.shape(inputs)) # In all cases, we should uprank scalar input to a single sample. if rank_of_inputs == 0: inputs = backend_module.numpy.expand_dims(inputs, -1) rank_of_inputs = 1 if ( backend_module.__name__.endswith("tensorflow") and rank_of_inputs <= 2 and output_mode in ("multi_hot", "count") ): # TF only fastpath. Uses bincount; faster. Doesn't work for rank 3+. try: return tf_utils.tf_encode_categorical_inputs( inputs, output_mode, depth, dtype=dtype, sparse=sparse, count_weights=count_weights, ) except ValueError: pass if output_mode == "multi_hot": return backend_module.nn.multi_hot( inputs, depth, dtype=dtype, sparse=sparse ) elif output_mode == "one_hot": input_shape = backend_module.core.shape(inputs) # Shrink the last dimension if the shape is (..., 1). if ( input_shape is not None and len(input_shape) > 1 and input_shape[-1] == 1 ): newshape = tuple(input_shape[:-1]) inputs = backend_module.numpy.reshape(inputs, newshape) return backend_module.nn.one_hot( inputs, depth, dtype=dtype, sparse=sparse ) elif output_mode == "count": # We don't use `ops.bincount` because its output has a dynamic shape # (last dimension is the highest value of `inputs`). We implement a # narrower use case where `minlength` and `maxlength` (not supported by # `ops.bincount`) are the same and static value: `depth`. We also don't # need to support indices that are negative or greater than `depth`. reduction_axis = 1 if len(inputs.shape) > 1 else 0 if count_weights is not None: dtype = count_weights.dtype one_hot_encoding = backend_module.nn.one_hot( inputs, depth, dtype=dtype, sparse=sparse ) if count_weights is not None: count_weights = backend_module.numpy.expand_dims(count_weights, -1) one_hot_encoding = one_hot_encoding * count_weights outputs = backend_module.numpy.sum( one_hot_encoding, axis=reduction_axis, ) return outputs def build_pos_neg_masks( query_labels, key_labels, remove_diagonal=True, ): from keras.src import ops if ops.ndim(query_labels) == 1: query_labels = ops.reshape(query_labels, (-1, 1)) if ops.ndim(key_labels) == 1: key_labels = ops.reshape(key_labels, (-1, 1)) positive_mask = ops.equal(query_labels, ops.transpose(key_labels)) negative_mask = ops.logical_not(positive_mask) if remove_diagonal: positive_mask = ops.logical_and( positive_mask, ~ops.eye( ops.size(query_labels), ops.size(key_labels), k=0, dtype="bool", ), ) return positive_mask, negative_mask