Clarification of output array type when metrics accept multiclass/multioutput

Clarification of how we should handle array output type when a metric outputs several values (i.e. accepts multiclass or multioutput input).

The issue was summarised succinctly in https://github.com/scikit-learn/scikit-learn/pull/30439#issuecomment-2532238196:

> Not sure what should be the output namespace / device in case we output an array, e.g. roc_auc_score with average=None on multiclass problems...

Currently all regression/classification metrics that support array API and multiclass or multioutput, all output an array in the same namespace and device as the input (checked code and manually). Summary of these metrics :

### Regression metrics

Returns array in same namespace/device:
* [explained_variance_score](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.explained_variance_score.html#sklearn.metrics.explained_variance_score)
* [r2_score](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.r2_score.html#sklearn.metrics.r2_score) 
* [mean_absolute_error](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.mean_absolute_error.html#sklearn.metrics.mean_absolute_error)
* [mean_absolute_percentage_error](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.mean_absolute_percentage_error.html#sklearn.metrics.mean_absolute_percentage_error)
* [mean_pinball_loss](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.mean_pinball_loss.html#sklearn.metrics.mean_pinball_loss)
* [mean_squared_error](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.mean_squared_error.html#sklearn.metrics.mean_squared_error)
* [mean_squared_log_error](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.mean_squared_log_error.html#sklearn.metrics.mean_squared_log_error)
* [root_mean_squared_error](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.root_mean_squared_error.html#sklearn.metrics.root_mean_squared_error)
* [root_mean_squared_log_error](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.root_mean_squared_log_error.html#sklearn.metrics.root_mean_squared_log_error)

### Classification metrics
Returns array in same namespace/device:

* [precision_recall_fscore_support](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.precision_recall_fscore_support.html#sklearn.metrics.precision_recall_fscore_support) and family ([f1_score](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.f1_score.html#sklearn.metrics.f1_score), [fbeta_score](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.fbeta_score.html#sklearn.metrics.fbeta_score), [precision_score](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.precision_score.html#sklearn.metrics.precision_score), [recall_score](https://scikit-learn.org/dev/modules/generated/sklearn.metrics.recall_score.html#sklearn.metrics.recall_score))

Looking at the metrics code, if we wanted to support a list of scalars, we'd generally have to do extra processing to convert an array (often output of an xp.** function) to a list of scalars.

Once we arrive at a consensus we should update the array API documentation and update the `check_array_api_metric` in tests such that when the output is array/list - we check that the output type etc is correct.

cc @ogrisel @betatim

COMMENT:
> Spitballing - would you consider making _convert_to_numpy public?

`_convert_to_numpy` is a hack in the sense that it is not namespace agnostic: it only supports torch and cupy (and `array_api_strict`, mostly for array API compliance testing purposes) while the rest of our API code supposedly work for any namespace that follows the spec.

EDIT: for the record, here is how it is implemented at the moment:

https://github.com/scikit-learn/scikit-learn/blob/675736ae9e3f360975e1f5b477a8eba68a376ba9/sklearn/utils/_array_api.py#L780-L789

The clean way to implement this would be to rely on `from_dlpack` which should allow for any namespace/device to any namespace/device transfers, possibly with memory copies. But this requires the array API compliant libraries to adopt the latest version of dlpack spec for their [`__dlpack__` method](https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html) and the latest version of the array API spec for the [`from_dlpack` method](https://data-apis.org/array-api/latest/API_specification/generated/array_api.from_dlpack.html) of the consuming library.

For instance, with pytorch 2.7.0 (and numpy 2.2.5), this does not work yet:

```python
>>> import array_api_compat.torch as xp
>>> import numpy as np
>>> np.from_dlpack(xp.ones(3, device="mps"), device="cpu")
Traceback (most recent call last):
  Cell In[9], line 1
    np.from_dlpack(xp.ones(3, device="mps"), device="cpu")
TypeError: Tensor.__dlpack__() got an unexpected keyword argument 'dl_device'
```

This exception is raised by PyTorch.

EDIT: the PyTorch specific problem is being tackled in this set of PRs:

- https://github.com/pytorch/pytorch/pull/145000


NumPy and array-api-strict are fine:

```python
>>> import array_api_strict as xp
>>> np.from_dlpack(xp.ones(3, device=xp.Device("device1")), device="cpu")
array([1., 1., 1.])
```

but maybe it's just array-api-strict that is too lax when simulating non-CPU devices, because based on my understanding of the spec, I would have expected any of the following to raise an error:

```python
>>> np.from_dlpack(xp.ones(3, device=xp.Device("device1")), device=None, copy=False)
array([1., 1., 1.])
>>>  np.from_dlpack(xp.ones(3, device=xp.Device("device1")), device="cpu", copy=False)
array([1., 1., 1.])
```

So to conclude, cross-device dlpack-based namespace conversion is too new to be relied upon hence we have to keep our `_convert_to_numpy` hack for the time being, as long as libraries do not implement the full dlpack/array API specs to get `np.from_dlpack` as expected.

Labels: Needs Decision, Array API