Feature request: an additional config context for forcing conversion toward a specific Array API-compatible array library.

### Describe the workflow you want to enable

Per https://github.com/scikit-learn/scikit-learn/pull/22554 such workflow is possible:

```python
from sklearn.datasets import make_classification
from sklearn import config_context
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
import cupy.array_api as xp

X_np, y_np = make_classification(random_state=0)
X_cu = xp.asarray(X_np)
y_cu = xp.asarray(y_np)
X_cu.device


with config_context(array_api_dispatch=True):
    lda = LinearDiscriminantAnalysis()
    X_trans = lda.fit_transform(X_cu, y_cu)
```

(see https://scikit-learn.org/dev/modules/array_api.html )

I would like to additonally enable following workflow:

```python
X_np, y_np = make_classification(random_state=0)

with config_context(array_api_dispatch=True, array_api_namespace=xp):
    lda = LinearDiscriminantAnalysis()
    X_trans = lda.fit_transform(X_np, y_np)
```

where `X_trans` are the same in both examples, and in both examples compute has been done with `cupy`.  The difference is that we pass numpy inputs and rely on the estimator to convert using the appropriate array namespace.

The added value here for the user is that the `check_array` function could be used to load arbitrary inputs to the array library relying on its `xp.asarray` method, in a way that is consistent to `sklearn.utils.validation.check_array` requirements accross all array libraries.

Currently, a user that would have data formated as, let's say, a list of lists, has two choices:

- either pass the list of list directly to `lda.fit`, but then since the list of list does not have a `__array_namespace__` attribute it will fallback to numpy array namespace
- either convert first the input to `cupy`, but then, unless the user re-implements methodically `check_array` with `cupy` conversion, the behavior of the conversion might differ from what `check_array` would do when converting to `numpy`, and anyway reimplementing `check_array` is not very efficient to begin with.

**Edit:** following discussions, adding examples that highlights the added value I can see to memory management with such an interface:

```python
X_np, y_np = my_data_loader()  # let's assume that X_np, y_np are typed `np.int64`...

# as a consequence, here the cupy array are also typed `int64`
X_cu = xp.asarray(X_np)
y_cu = xp.asarray(y_np)
X_cu.device

with config_context(array_api_dispatch=True):
    lda = LinearDiscriminantAnalysis()
    # But `lda` requires `floats` to work
    # (see https://github.com/scikit-learn/scikit-learn/blob/f3f51f9b6/sklearn/discriminant_analysis.py#L551)
    # so implicitly, an additional copy of X_cu and y_cu in `float` is going to be created
    # resulting on twice the memory allocation for data.
    X_trans = lda.fit_transform(X_cu, y_cu)
```

for the unwary user, this workflow is safer:

```python
X_np, y_np = my_data_loader()  # let's assume that X_np, y_np are typed `np.int64`...

with config_context(array_api_dispatch=True, array_api_namespace=xp):
    lda = LinearDiscriminantAnalysis()
    # The X_np, y_np are yet again implicitly transformed as cupy float arrays. But
    # the intermediate copy that the users created is skipped (or at worst,
    # deleted right after creation), resulting in better memory management.
    X_trans = lda.fit_transform(X_np, y_np)
``` 

### Describe your proposed solution

The `get_namespace` function can query the `config_context` and force the use of the `__array_namespace__` that is set by the config rather than the `__array_namespace__` that attached to the input (if any). (rather simple solution)

COMMENT:
In general I would also agree with that (implicit conversions are against the zen of python :pray: ), but I understand that sklearn tries to just "make it work" so that beginner users can go faster to the point.

About memory efficiency, here are examples of what I mean (I'll also add the examples to the OP):

```python
X_np, y_np = my_data_loader()  # let's assume that X_np, y_np are typed `np.int64`...

# as a consequence, here the cupy array are also typed `int64`
X_cu = xp.asarray(X_np)
y_cu = xp.asarray(y_np)
X_cu.device

with config_context(array_api_dispatch=True):
    lda = LinearDiscriminantAnalysis()
    # But `lda` requires `floats` to work
    # (see https://github.com/scikit-learn/scikit-learn/blob/f3f51f9b6/sklearn/discriminant_analysis.py#L551)
    # so implicitly, an additional copy of X_cu and y_cu in `float` is going to be created
    # resulting on twice the memory allocation for data.
    X_trans = lda.fit_transform(X_cu, y_cu)
```

for the unwary user, this workflow is safer:

```python
X_np, y_np = my_data_loader()  # let's assume that X_np, y_np are typed `np.int64`...

with config_context(array_api_dispatch=True, array_api_namespace=xp):
    lda = LinearDiscriminantAnalysis()
    # The X_np, y_np are yet again implicitly transformed as cupy float arrays. But
    # the intermediate copy that the users created is skipped (or at worst,
    # deleted right after creation), resulting in better memory management.
    X_trans = lda.fit_transform(X_np, y_np)
```

On a side note, implicit copies in sklearn estimators in the data validation pipeline seems to almost always be avoidable if the user adapts its data loader to load directly with the adequate type the first time the data are read from disk. Has it ever been considered to issue a warning when those implicit copies are triggered ?

Labels: New Feature, Needs Decision - Include Feature, Array API