scikit-learn / scikit-learn/scikit-learn

`OneVsOneClassifier` does not accept custom input types

Open
#23,779 6 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Bug module:multiclass
Dominant language
Python
Stars
67.3k
Forks
27.4k
Avg merge
1d 15h
Merged PRs (30d)
58

Description

Describe the bug

Due to the additional validation in #6626, OneVsOneClassifier cannot be used with custom types that work like arrays but cannot be converted to arrays. In my case I am the maintainer of scikit-fda, a functional data library that attempts to be compatible with scikit-learn. The metaestimators OneVsRestClassifier and OneVsOneClassifier should be trivially compatible with our data. Currently OneVsRestClassifier works fine, while OneVsOneClassifier doesn't.

I think that scikit-learn has sometimes very aggressive validation that makes it difficult to extend it for custom objects, as in this case.

Steps/Code to Reproduce

I have no example using only scikit-learn, as you need custom types and classifiers / transformers to trigger it
The following code is adapted from https://fda.readthedocs.io/en/latest/auto_tutorial/plot_skfda_sklearn.html#multiclass-and-multioutput-classification-utilities
Note that the code works if you replace OneVsOneClassifier with OneVsRestClassifier, as the later does not have that aggressive validation.

import skfda
import skfda.preprocessing.dim_reduction.variable_selection as vs
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
from sklearn.multiclass import OneVsOneClassifier

X, y = skfda.datasets.fetch_phoneme(return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

pipeline = Pipeline([
    ("dim_reduction", vs.RKHSVariableSelection(n_features_to_select=3)),
    ("classifier", SVC()),
])

multiclass = OneVsOneClassifier(pipeline)

multiclass.fit(X_train, y_train)
multiclass.score(X_test, y_test)
Expected Results

The result of the classification.

Actual Results
TypeError                                 Traceback (most recent call last)
TypeError: float() argument must be a string or a number, not 'FDataGrid'

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
<ipython-input-2-66e12b97fe3e> in <module>
     17 multiclass = OneVsOneClassifier(pipeline)
     18 
---> 19 multiclass.fit(X_train, y_train)
     20 multiclass.score(X_test, y_test)

~/Programas/Utilidades/Lenguajes/miniconda3/envs/fda38/lib/python3.8/site-packages/sklearn/multiclass.py in fit(self, X, y)
    726         """
    727         # We need to validate the data because we do a safe_indexing later.
--> 728         X, y = self._validate_data(
    729             X, y, accept_sparse=["csr", "csc"], force_all_finite=False
    730         )

~/Programas/Utilidades/Lenguajes/miniconda3/envs/fda38/lib/python3.8/site-packages/sklearn/base.py in _validate_data(self, X, y, reset, validate_separately, **check_params)
    579                 y = check_array(y, **check_y_params)
    580             else:
--> 581                 X, y = check_X_y(X, y, **check_params)
    582             out = X, y
    583 

~/Programas/Utilidades/Lenguajes/miniconda3/envs/fda38/lib/python3.8/site-packages/sklearn/utils/validation.py in check_X_y(X, y, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, multi_output, ensure_min_samples, ensure_min_features, y_numeric, estimator)
    962         raise ValueError("y cannot be None")
    963 
--> 964     X = check_array(
    965         X,
    966         accept_sparse=accept_sparse,

~/Programas/Utilidades/Lenguajes/miniconda3/envs/fda38/lib/python3.8/site-packages/sklearn/utils/validation.py in check_array(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator)
    744                     array = array.astype(dtype, casting="unsafe", copy=False)
    745                 else:
--> 746                     array = np.asarray(array, order=order, dtype=dtype)
    747             except ComplexWarning as complex_warning:
    748                 raise ValueError(

ValueError: setting an array element with a sequence.

Versions
System:
    python: 3.8.0 | packaged by conda-forge | (default, Nov 22 2019, 19:11:38)  [GCC 7.3.0]
executable: /home/carlos/Programas/Utilidades/Lenguajes/miniconda3/envs/fda38/bin/python3.8
   machine: Linux-5.4.0-92-generic-x86_64-with-glibc2.10

Python dependencies:
          pip: 21.3.1
   setuptools: 60.5.0
      sklearn: 1.0.2
        numpy: 1.22.0
        scipy: 1.7.3
       Cython: 0.29.26
       pandas: 1.4.0
   matplotlib: 3.5.1
       joblib: 1.1.0
threadpoolctl: 3.0.0

Built with OpenMP: True

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the supplied scikit-fda example and read sklearn/multiclass.py around OneVsOneClassifier.fit and its _validate_data call. Compare its handling with OneVsRestClassifier, then verify that the same FDataGrid pipeline can fit and score with OneVsOneClassifier without the reported conversion error.

Written by the indexing model from the issue text.

Assessment

Tech stack
numpy, python
Domain
machine-learning
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.