{"record":{"id":"eae8eeed7fff6b67","repo":"keras-team/keras","slug":"when-class-id-is-provided-y-pred-must-be-a-2d-arr","errorCode":null,"errorMessage":"When class_id is provided, y_pred must be a 2D array with shape (num_samples, num_classes), found shape: {y_pred.shape}","messagePattern":"When class_id is provided, y_pred must be a 2D array with shape \\(num_samples, num_classes\\), found shape: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"keras/src/metrics/metrics_utils.py","lineNumber":474,"sourceCode":"            f'Invalid keys: \"{invalid_keys}\". '\n            f'Valid variable key options are: \"{list(ConfusionMatrix)}\"'\n        )\n\n    y_pred, y_true = squeeze_or_expand_to_same_rank(y_pred, y_true)\n    if sample_weight is not None:\n        sample_weight = ops.expand_dims(\n            ops.cast(sample_weight, dtype=variable_dtype), axis=-1\n        )\n        _, sample_weight = squeeze_or_expand_to_same_rank(\n            y_true, sample_weight, expand_rank_1=False\n        )\n\n    if top_k is not None:\n        y_pred = _filter_top_k(y_pred, top_k)\n\n    if class_id is not None:\n        if len(y_pred.shape) == 1:\n            raise ValueError(\n                \"When class_id is provided, y_pred must be a 2D array \"\n                \"with shape (num_samples, num_classes), found shape: \"\n                f\"{y_pred.shape}\"\n            )\n\n        # Preserve dimension to match with sample_weight\n        y_true = y_true[..., class_id, None]\n        y_pred = y_pred[..., class_id, None]\n\n    if thresholds_distributed_evenly:\n        return _update_confusion_matrix_variables_optimized(\n            variables_to_update,\n            y_true,\n            y_pred,\n            thresholds,\n            multi_label=multi_label,\n            sample_weights=sample_weight,\n            label_weights=label_weights,","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/keras-team/keras/blob/7a34a03db60bf60042242d6a556fc3be119046a5/keras/src/metrics/metrics_utils.py#L456-L492","documentation":"When update_confusion_matrix_variables() is called with class_id, it slices one class column out of y_pred, which requires a 2D prediction tensor of shape (num_samples, num_classes). If y_pred is rank 1 (for example after top_k filtering or with a single-output model), this ValueError is raised.","triggerScenarios":"Calling with class_id=k while y_pred has rank 1, or combining top_k (which can reduce the prediction rank) with class_id on single-output models.","commonSituations":"Using Precision(class_id=1) or Recall(class_id=0) on a model whose output shape is (batch,) instead of (batch, num_classes) - typical for single-sigmoid-output binary classifiers.","solutions":["Reshape y_pred to (batch, 1) with keras.ops.expand_dims(y_pred, -1) before update_state, or make the model output 2D.","For binary classification, drop class_id and use the default thresholded metric on the single output.","If using top_k with class_id, verify the post-filter y_pred still has rank 2."],"exampleFix":"# before\nmetric = keras.metrics.Precision(class_id=0)\nmetric.update_state(y_true, y_pred)  # y_pred shape (batch,)\n\n# after\nmetric.update_state(y_true, keras.ops.expand_dims(y_pred, -1))  # (batch, 1)","handlingStrategy":"type-guard","validationCode":"import keras.ops as ops\ny_pred2 = ops.expand_dims(y_pred, -1) if len(y_pred.shape) == 1 else y_pred","typeGuard":"def is_rank2(x) -> bool:\n    return len(getattr(x, 'shape', ())) == 2","tryCatchPattern":null,"preventionTips":["Always feed (batch, num_classes) predictions when using class_id.","Check model.output_shape before compiling with class_id metrics."],"tags":["keras","metrics","shape-mismatch","class-id","rank"],"backgroundTag":"tensor-rank-mismatch","analyzedSha":"7a34a03db60bf60042242d6a556fc3be119046a5","analyzedAt":"2026-08-25T21:25:25.994Z","schemaVersion":2},"datasetVersion":"2026-08-26T02:17:13.382Z"}