| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923 |
- from re import escape
- import numpy as np
- import pytest
- import scipy.sparse as sp
- from numpy.testing import assert_allclose
- from sklearn import datasets, svm
- from sklearn.datasets import load_breast_cancer
- from sklearn.exceptions import NotFittedError
- from sklearn.impute import SimpleImputer
- from sklearn.linear_model import (
- ElasticNet,
- Lasso,
- LinearRegression,
- LogisticRegression,
- Perceptron,
- Ridge,
- SGDClassifier,
- )
- from sklearn.metrics import precision_score, recall_score
- from sklearn.model_selection import GridSearchCV, cross_val_score
- from sklearn.multiclass import (
- OneVsOneClassifier,
- OneVsRestClassifier,
- OutputCodeClassifier,
- )
- from sklearn.naive_bayes import MultinomialNB
- from sklearn.neighbors import KNeighborsClassifier
- from sklearn.pipeline import Pipeline, make_pipeline
- from sklearn.svm import SVC, LinearSVC
- from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
- from sklearn.utils import (
- check_array,
- shuffle,
- )
- from sklearn.utils._mocking import CheckingClassifier
- from sklearn.utils._testing import assert_almost_equal, assert_array_equal
- from sklearn.utils.multiclass import check_classification_targets, type_of_target
- msg = "The default value for `force_alpha` will change"
- pytestmark = pytest.mark.filterwarnings(f"ignore:{msg}:FutureWarning")
- iris = datasets.load_iris()
- rng = np.random.RandomState(0)
- perm = rng.permutation(iris.target.size)
- iris.data = iris.data[perm]
- iris.target = iris.target[perm]
- n_classes = 3
- def test_ovr_exceptions():
- ovr = OneVsRestClassifier(LinearSVC(dual="auto", random_state=0))
- # test predicting without fitting
- with pytest.raises(NotFittedError):
- ovr.predict([])
- # Fail on multioutput data
- msg = "Multioutput target data is not supported with label binarization"
- with pytest.raises(ValueError, match=msg):
- X = np.array([[1, 0], [0, 1]])
- y = np.array([[1, 2], [3, 1]])
- OneVsRestClassifier(MultinomialNB()).fit(X, y)
- with pytest.raises(ValueError, match=msg):
- X = np.array([[1, 0], [0, 1]])
- y = np.array([[1.5, 2.4], [3.1, 0.8]])
- OneVsRestClassifier(MultinomialNB()).fit(X, y)
- def test_check_classification_targets():
- # Test that check_classification_target return correct type. #5782
- y = np.array([0.0, 1.1, 2.0, 3.0])
- msg = type_of_target(y)
- with pytest.raises(ValueError, match=msg):
- check_classification_targets(y)
- def test_ovr_fit_predict():
- # A classifier which implements decision_function.
- ovr = OneVsRestClassifier(LinearSVC(dual="auto", random_state=0))
- pred = ovr.fit(iris.data, iris.target).predict(iris.data)
- assert len(ovr.estimators_) == n_classes
- clf = LinearSVC(dual="auto", random_state=0)
- pred2 = clf.fit(iris.data, iris.target).predict(iris.data)
- assert np.mean(iris.target == pred) == np.mean(iris.target == pred2)
- # A classifier which implements predict_proba.
- ovr = OneVsRestClassifier(MultinomialNB())
- pred = ovr.fit(iris.data, iris.target).predict(iris.data)
- assert np.mean(iris.target == pred) > 0.65
- def test_ovr_partial_fit():
- # Test if partial_fit is working as intended
- X, y = shuffle(iris.data, iris.target, random_state=0)
- ovr = OneVsRestClassifier(MultinomialNB())
- ovr.partial_fit(X[:100], y[:100], np.unique(y))
- ovr.partial_fit(X[100:], y[100:])
- pred = ovr.predict(X)
- ovr2 = OneVsRestClassifier(MultinomialNB())
- pred2 = ovr2.fit(X, y).predict(X)
- assert_almost_equal(pred, pred2)
- assert len(ovr.estimators_) == len(np.unique(y))
- assert np.mean(y == pred) > 0.65
- # Test when mini batches doesn't have all classes
- # with SGDClassifier
- X = np.abs(np.random.randn(14, 2))
- y = [1, 1, 1, 1, 2, 3, 3, 0, 0, 2, 3, 1, 2, 3]
- ovr = OneVsRestClassifier(
- SGDClassifier(max_iter=1, tol=None, shuffle=False, random_state=0)
- )
- ovr.partial_fit(X[:7], y[:7], np.unique(y))
- ovr.partial_fit(X[7:], y[7:])
- pred = ovr.predict(X)
- ovr1 = OneVsRestClassifier(
- SGDClassifier(max_iter=1, tol=None, shuffle=False, random_state=0)
- )
- pred1 = ovr1.fit(X, y).predict(X)
- assert np.mean(pred == y) == np.mean(pred1 == y)
- # test partial_fit only exists if estimator has it:
- ovr = OneVsRestClassifier(SVC())
- assert not hasattr(ovr, "partial_fit")
- def test_ovr_partial_fit_exceptions():
- ovr = OneVsRestClassifier(MultinomialNB())
- X = np.abs(np.random.randn(14, 2))
- y = [1, 1, 1, 1, 2, 3, 3, 0, 0, 2, 3, 1, 2, 3]
- ovr.partial_fit(X[:7], y[:7], np.unique(y))
- # If a new class that was not in the first call of partial fit is seen
- # it should raise ValueError
- y1 = [5] + y[7:-1]
- msg = r"Mini-batch contains \[.+\] while classes must be subset of \[.+\]"
- with pytest.raises(ValueError, match=msg):
- ovr.partial_fit(X=X[7:], y=y1)
- def test_ovr_ovo_regressor():
- # test that ovr and ovo work on regressors which don't have a decision_
- # function
- ovr = OneVsRestClassifier(DecisionTreeRegressor())
- pred = ovr.fit(iris.data, iris.target).predict(iris.data)
- assert len(ovr.estimators_) == n_classes
- assert_array_equal(np.unique(pred), [0, 1, 2])
- # we are doing something sensible
- assert np.mean(pred == iris.target) > 0.9
- ovr = OneVsOneClassifier(DecisionTreeRegressor())
- pred = ovr.fit(iris.data, iris.target).predict(iris.data)
- assert len(ovr.estimators_) == n_classes * (n_classes - 1) / 2
- assert_array_equal(np.unique(pred), [0, 1, 2])
- # we are doing something sensible
- assert np.mean(pred == iris.target) > 0.9
- def test_ovr_fit_predict_sparse():
- for sparse in [
- sp.csr_matrix,
- sp.csc_matrix,
- sp.coo_matrix,
- sp.dok_matrix,
- sp.lil_matrix,
- ]:
- base_clf = MultinomialNB(alpha=1)
- X, Y = datasets.make_multilabel_classification(
- n_samples=100,
- n_features=20,
- n_classes=5,
- n_labels=3,
- length=50,
- allow_unlabeled=True,
- random_state=0,
- )
- X_train, Y_train = X[:80], Y[:80]
- X_test = X[80:]
- clf = OneVsRestClassifier(base_clf).fit(X_train, Y_train)
- Y_pred = clf.predict(X_test)
- clf_sprs = OneVsRestClassifier(base_clf).fit(X_train, sparse(Y_train))
- Y_pred_sprs = clf_sprs.predict(X_test)
- assert clf.multilabel_
- assert sp.issparse(Y_pred_sprs)
- assert_array_equal(Y_pred_sprs.toarray(), Y_pred)
- # Test predict_proba
- Y_proba = clf_sprs.predict_proba(X_test)
- # predict assigns a label if the probability that the
- # sample has the label is greater than 0.5.
- pred = Y_proba > 0.5
- assert_array_equal(pred, Y_pred_sprs.toarray())
- # Test decision_function
- clf = svm.SVC()
- clf_sprs = OneVsRestClassifier(clf).fit(X_train, sparse(Y_train))
- dec_pred = (clf_sprs.decision_function(X_test) > 0).astype(int)
- assert_array_equal(dec_pred, clf_sprs.predict(X_test).toarray())
- def test_ovr_always_present():
- # Test that ovr works with classes that are always present or absent.
- # Note: tests is the case where _ConstantPredictor is utilised
- X = np.ones((10, 2))
- X[:5, :] = 0
- # Build an indicator matrix where two features are always on.
- # As list of lists, it would be: [[int(i >= 5), 2, 3] for i in range(10)]
- y = np.zeros((10, 3))
- y[5:, 0] = 1
- y[:, 1] = 1
- y[:, 2] = 1
- ovr = OneVsRestClassifier(LogisticRegression())
- msg = r"Label .+ is present in all training examples"
- with pytest.warns(UserWarning, match=msg):
- ovr.fit(X, y)
- y_pred = ovr.predict(X)
- assert_array_equal(np.array(y_pred), np.array(y))
- y_pred = ovr.decision_function(X)
- assert np.unique(y_pred[:, -2:]) == 1
- y_pred = ovr.predict_proba(X)
- assert_array_equal(y_pred[:, -1], np.ones(X.shape[0]))
- # y has a constantly absent label
- y = np.zeros((10, 2))
- y[5:, 0] = 1 # variable label
- ovr = OneVsRestClassifier(LogisticRegression())
- msg = r"Label not 1 is present in all training examples"
- with pytest.warns(UserWarning, match=msg):
- ovr.fit(X, y)
- y_pred = ovr.predict_proba(X)
- assert_array_equal(y_pred[:, -1], np.zeros(X.shape[0]))
- def test_ovr_multiclass():
- # Toy dataset where features correspond directly to labels.
- X = np.array([[0, 0, 5], [0, 5, 0], [3, 0, 0], [0, 0, 6], [6, 0, 0]])
- y = ["eggs", "spam", "ham", "eggs", "ham"]
- Y = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 0, 1], [1, 0, 0]])
- classes = set("ham eggs spam".split())
- for base_clf in (
- MultinomialNB(),
- LinearSVC(dual="auto", random_state=0),
- LinearRegression(),
- Ridge(),
- ElasticNet(),
- ):
- clf = OneVsRestClassifier(base_clf).fit(X, y)
- assert set(clf.classes_) == classes
- y_pred = clf.predict(np.array([[0, 0, 4]]))[0]
- assert_array_equal(y_pred, ["eggs"])
- # test input as label indicator matrix
- clf = OneVsRestClassifier(base_clf).fit(X, Y)
- y_pred = clf.predict([[0, 0, 4]])[0]
- assert_array_equal(y_pred, [0, 0, 1])
- def test_ovr_binary():
- # Toy dataset where features correspond directly to labels.
- X = np.array([[0, 0, 5], [0, 5, 0], [3, 0, 0], [0, 0, 6], [6, 0, 0]])
- y = ["eggs", "spam", "spam", "eggs", "spam"]
- Y = np.array([[0, 1, 1, 0, 1]]).T
- classes = set("eggs spam".split())
- def conduct_test(base_clf, test_predict_proba=False):
- clf = OneVsRestClassifier(base_clf).fit(X, y)
- assert set(clf.classes_) == classes
- y_pred = clf.predict(np.array([[0, 0, 4]]))[0]
- assert_array_equal(y_pred, ["eggs"])
- if hasattr(base_clf, "decision_function"):
- dec = clf.decision_function(X)
- assert dec.shape == (5,)
- if test_predict_proba:
- X_test = np.array([[0, 0, 4]])
- probabilities = clf.predict_proba(X_test)
- assert 2 == len(probabilities[0])
- assert clf.classes_[np.argmax(probabilities, axis=1)] == clf.predict(X_test)
- # test input as label indicator matrix
- clf = OneVsRestClassifier(base_clf).fit(X, Y)
- y_pred = clf.predict([[3, 0, 0]])[0]
- assert y_pred == 1
- for base_clf in (
- LinearSVC(dual="auto", random_state=0),
- LinearRegression(),
- Ridge(),
- ElasticNet(),
- ):
- conduct_test(base_clf)
- for base_clf in (MultinomialNB(), SVC(probability=True), LogisticRegression()):
- conduct_test(base_clf, test_predict_proba=True)
- def test_ovr_multilabel():
- # Toy dataset where features correspond directly to labels.
- X = np.array([[0, 4, 5], [0, 5, 0], [3, 3, 3], [4, 0, 6], [6, 0, 0]])
- y = np.array([[0, 1, 1], [0, 1, 0], [1, 1, 1], [1, 0, 1], [1, 0, 0]])
- for base_clf in (
- MultinomialNB(),
- LinearSVC(dual="auto", random_state=0),
- LinearRegression(),
- Ridge(),
- ElasticNet(),
- Lasso(alpha=0.5),
- ):
- clf = OneVsRestClassifier(base_clf).fit(X, y)
- y_pred = clf.predict([[0, 4, 4]])[0]
- assert_array_equal(y_pred, [0, 1, 1])
- assert clf.multilabel_
- def test_ovr_fit_predict_svc():
- ovr = OneVsRestClassifier(svm.SVC())
- ovr.fit(iris.data, iris.target)
- assert len(ovr.estimators_) == 3
- assert ovr.score(iris.data, iris.target) > 0.9
- def test_ovr_multilabel_dataset():
- base_clf = MultinomialNB(alpha=1)
- for au, prec, recall in zip((True, False), (0.51, 0.66), (0.51, 0.80)):
- X, Y = datasets.make_multilabel_classification(
- n_samples=100,
- n_features=20,
- n_classes=5,
- n_labels=2,
- length=50,
- allow_unlabeled=au,
- random_state=0,
- )
- X_train, Y_train = X[:80], Y[:80]
- X_test, Y_test = X[80:], Y[80:]
- clf = OneVsRestClassifier(base_clf).fit(X_train, Y_train)
- Y_pred = clf.predict(X_test)
- assert clf.multilabel_
- assert_almost_equal(
- precision_score(Y_test, Y_pred, average="micro"), prec, decimal=2
- )
- assert_almost_equal(
- recall_score(Y_test, Y_pred, average="micro"), recall, decimal=2
- )
- def test_ovr_multilabel_predict_proba():
- base_clf = MultinomialNB(alpha=1)
- for au in (False, True):
- X, Y = datasets.make_multilabel_classification(
- n_samples=100,
- n_features=20,
- n_classes=5,
- n_labels=3,
- length=50,
- allow_unlabeled=au,
- random_state=0,
- )
- X_train, Y_train = X[:80], Y[:80]
- X_test = X[80:]
- clf = OneVsRestClassifier(base_clf).fit(X_train, Y_train)
- # Decision function only estimator.
- decision_only = OneVsRestClassifier(svm.SVR()).fit(X_train, Y_train)
- assert not hasattr(decision_only, "predict_proba")
- # Estimator with predict_proba disabled, depending on parameters.
- decision_only = OneVsRestClassifier(svm.SVC(probability=False))
- assert not hasattr(decision_only, "predict_proba")
- decision_only.fit(X_train, Y_train)
- assert not hasattr(decision_only, "predict_proba")
- assert hasattr(decision_only, "decision_function")
- # Estimator which can get predict_proba enabled after fitting
- gs = GridSearchCV(
- svm.SVC(probability=False), param_grid={"probability": [True]}
- )
- proba_after_fit = OneVsRestClassifier(gs)
- assert not hasattr(proba_after_fit, "predict_proba")
- proba_after_fit.fit(X_train, Y_train)
- assert hasattr(proba_after_fit, "predict_proba")
- Y_pred = clf.predict(X_test)
- Y_proba = clf.predict_proba(X_test)
- # predict assigns a label if the probability that the
- # sample has the label is greater than 0.5.
- pred = Y_proba > 0.5
- assert_array_equal(pred, Y_pred)
- def test_ovr_single_label_predict_proba():
- base_clf = MultinomialNB(alpha=1)
- X, Y = iris.data, iris.target
- X_train, Y_train = X[:80], Y[:80]
- X_test = X[80:]
- clf = OneVsRestClassifier(base_clf).fit(X_train, Y_train)
- # Decision function only estimator.
- decision_only = OneVsRestClassifier(svm.SVR()).fit(X_train, Y_train)
- assert not hasattr(decision_only, "predict_proba")
- Y_pred = clf.predict(X_test)
- Y_proba = clf.predict_proba(X_test)
- assert_almost_equal(Y_proba.sum(axis=1), 1.0)
- # predict assigns a label if the probability that the
- # sample has the label with the greatest predictive probability.
- pred = Y_proba.argmax(axis=1)
- assert not (pred - Y_pred).any()
- def test_ovr_multilabel_decision_function():
- X, Y = datasets.make_multilabel_classification(
- n_samples=100,
- n_features=20,
- n_classes=5,
- n_labels=3,
- length=50,
- allow_unlabeled=True,
- random_state=0,
- )
- X_train, Y_train = X[:80], Y[:80]
- X_test = X[80:]
- clf = OneVsRestClassifier(svm.SVC()).fit(X_train, Y_train)
- assert_array_equal(
- (clf.decision_function(X_test) > 0).astype(int), clf.predict(X_test)
- )
- def test_ovr_single_label_decision_function():
- X, Y = datasets.make_classification(n_samples=100, n_features=20, random_state=0)
- X_train, Y_train = X[:80], Y[:80]
- X_test = X[80:]
- clf = OneVsRestClassifier(svm.SVC()).fit(X_train, Y_train)
- assert_array_equal(clf.decision_function(X_test).ravel() > 0, clf.predict(X_test))
- def test_ovr_gridsearch():
- ovr = OneVsRestClassifier(LinearSVC(dual="auto", random_state=0))
- Cs = [0.1, 0.5, 0.8]
- cv = GridSearchCV(ovr, {"estimator__C": Cs})
- cv.fit(iris.data, iris.target)
- best_C = cv.best_estimator_.estimators_[0].C
- assert best_C in Cs
- def test_ovr_pipeline():
- # Test with pipeline of length one
- # This test is needed because the multiclass estimators may fail to detect
- # the presence of predict_proba or decision_function.
- clf = Pipeline([("tree", DecisionTreeClassifier())])
- ovr_pipe = OneVsRestClassifier(clf)
- ovr_pipe.fit(iris.data, iris.target)
- ovr = OneVsRestClassifier(DecisionTreeClassifier())
- ovr.fit(iris.data, iris.target)
- assert_array_equal(ovr.predict(iris.data), ovr_pipe.predict(iris.data))
- def test_ovo_exceptions():
- ovo = OneVsOneClassifier(LinearSVC(dual="auto", random_state=0))
- with pytest.raises(NotFittedError):
- ovo.predict([])
- def test_ovo_fit_on_list():
- # Test that OneVsOne fitting works with a list of targets and yields the
- # same output as predict from an array
- ovo = OneVsOneClassifier(LinearSVC(dual="auto", random_state=0))
- prediction_from_array = ovo.fit(iris.data, iris.target).predict(iris.data)
- iris_data_list = [list(a) for a in iris.data]
- prediction_from_list = ovo.fit(iris_data_list, list(iris.target)).predict(
- iris_data_list
- )
- assert_array_equal(prediction_from_array, prediction_from_list)
- def test_ovo_fit_predict():
- # A classifier which implements decision_function.
- ovo = OneVsOneClassifier(LinearSVC(dual="auto", random_state=0))
- ovo.fit(iris.data, iris.target).predict(iris.data)
- assert len(ovo.estimators_) == n_classes * (n_classes - 1) / 2
- # A classifier which implements predict_proba.
- ovo = OneVsOneClassifier(MultinomialNB())
- ovo.fit(iris.data, iris.target).predict(iris.data)
- assert len(ovo.estimators_) == n_classes * (n_classes - 1) / 2
- def test_ovo_partial_fit_predict():
- temp = datasets.load_iris()
- X, y = temp.data, temp.target
- ovo1 = OneVsOneClassifier(MultinomialNB())
- ovo1.partial_fit(X[:100], y[:100], np.unique(y))
- ovo1.partial_fit(X[100:], y[100:])
- pred1 = ovo1.predict(X)
- ovo2 = OneVsOneClassifier(MultinomialNB())
- ovo2.fit(X, y)
- pred2 = ovo2.predict(X)
- assert len(ovo1.estimators_) == n_classes * (n_classes - 1) / 2
- assert np.mean(y == pred1) > 0.65
- assert_almost_equal(pred1, pred2)
- # Test when mini-batches have binary target classes
- ovo1 = OneVsOneClassifier(MultinomialNB())
- ovo1.partial_fit(X[:60], y[:60], np.unique(y))
- ovo1.partial_fit(X[60:], y[60:])
- pred1 = ovo1.predict(X)
- ovo2 = OneVsOneClassifier(MultinomialNB())
- pred2 = ovo2.fit(X, y).predict(X)
- assert_almost_equal(pred1, pred2)
- assert len(ovo1.estimators_) == len(np.unique(y))
- assert np.mean(y == pred1) > 0.65
- ovo = OneVsOneClassifier(MultinomialNB())
- X = np.random.rand(14, 2)
- y = [1, 1, 2, 3, 3, 0, 0, 4, 4, 4, 4, 4, 2, 2]
- ovo.partial_fit(X[:7], y[:7], [0, 1, 2, 3, 4])
- ovo.partial_fit(X[7:], y[7:])
- pred = ovo.predict(X)
- ovo2 = OneVsOneClassifier(MultinomialNB())
- pred2 = ovo2.fit(X, y).predict(X)
- assert_almost_equal(pred, pred2)
- # raises error when mini-batch does not have classes from all_classes
- ovo = OneVsOneClassifier(MultinomialNB())
- error_y = [0, 1, 2, 3, 4, 5, 2]
- message_re = escape(
- "Mini-batch contains {0} while it must be subset of {1}".format(
- np.unique(error_y), np.unique(y)
- )
- )
- with pytest.raises(ValueError, match=message_re):
- ovo.partial_fit(X[:7], error_y, np.unique(y))
- # test partial_fit only exists if estimator has it:
- ovr = OneVsOneClassifier(SVC())
- assert not hasattr(ovr, "partial_fit")
- def test_ovo_decision_function():
- n_samples = iris.data.shape[0]
- ovo_clf = OneVsOneClassifier(LinearSVC(dual="auto", random_state=0))
- # first binary
- ovo_clf.fit(iris.data, iris.target == 0)
- decisions = ovo_clf.decision_function(iris.data)
- assert decisions.shape == (n_samples,)
- # then multi-class
- ovo_clf.fit(iris.data, iris.target)
- decisions = ovo_clf.decision_function(iris.data)
- assert decisions.shape == (n_samples, n_classes)
- assert_array_equal(decisions.argmax(axis=1), ovo_clf.predict(iris.data))
- # Compute the votes
- votes = np.zeros((n_samples, n_classes))
- k = 0
- for i in range(n_classes):
- for j in range(i + 1, n_classes):
- pred = ovo_clf.estimators_[k].predict(iris.data)
- votes[pred == 0, i] += 1
- votes[pred == 1, j] += 1
- k += 1
- # Extract votes and verify
- assert_array_equal(votes, np.round(decisions))
- for class_idx in range(n_classes):
- # For each sample and each class, there only 3 possible vote levels
- # because they are only 3 distinct class pairs thus 3 distinct
- # binary classifiers.
- # Therefore, sorting predictions based on votes would yield
- # mostly tied predictions:
- assert set(votes[:, class_idx]).issubset(set([0.0, 1.0, 2.0]))
- # The OVO decision function on the other hand is able to resolve
- # most of the ties on this data as it combines both the vote counts
- # and the aggregated confidence levels of the binary classifiers
- # to compute the aggregate decision function. The iris dataset
- # has 150 samples with a couple of duplicates. The OvO decisions
- # can resolve most of the ties:
- assert len(np.unique(decisions[:, class_idx])) > 146
- def test_ovo_gridsearch():
- ovo = OneVsOneClassifier(LinearSVC(dual="auto", random_state=0))
- Cs = [0.1, 0.5, 0.8]
- cv = GridSearchCV(ovo, {"estimator__C": Cs})
- cv.fit(iris.data, iris.target)
- best_C = cv.best_estimator_.estimators_[0].C
- assert best_C in Cs
- def test_ovo_ties():
- # Test that ties are broken using the decision function,
- # not defaulting to the smallest label
- X = np.array([[1, 2], [2, 1], [-2, 1], [-2, -1]])
- y = np.array([2, 0, 1, 2])
- multi_clf = OneVsOneClassifier(Perceptron(shuffle=False, max_iter=4, tol=None))
- ovo_prediction = multi_clf.fit(X, y).predict(X)
- ovo_decision = multi_clf.decision_function(X)
- # Classifiers are in order 0-1, 0-2, 1-2
- # Use decision_function to compute the votes and the normalized
- # sum_of_confidences, which is used to disambiguate when there is a tie in
- # votes.
- votes = np.round(ovo_decision)
- normalized_confidences = ovo_decision - votes
- # For the first point, there is one vote per class
- assert_array_equal(votes[0, :], 1)
- # For the rest, there is no tie and the prediction is the argmax
- assert_array_equal(np.argmax(votes[1:], axis=1), ovo_prediction[1:])
- # For the tie, the prediction is the class with the highest score
- assert ovo_prediction[0] == normalized_confidences[0].argmax()
- def test_ovo_ties2():
- # test that ties can not only be won by the first two labels
- X = np.array([[1, 2], [2, 1], [-2, 1], [-2, -1]])
- y_ref = np.array([2, 0, 1, 2])
- # cycle through labels so that each label wins once
- for i in range(3):
- y = (y_ref + i) % 3
- multi_clf = OneVsOneClassifier(Perceptron(shuffle=False, max_iter=4, tol=None))
- ovo_prediction = multi_clf.fit(X, y).predict(X)
- assert ovo_prediction[0] == i % 3
- def test_ovo_string_y():
- # Test that the OvO doesn't mess up the encoding of string labels
- X = np.eye(4)
- y = np.array(["a", "b", "c", "d"])
- ovo = OneVsOneClassifier(LinearSVC(dual="auto"))
- ovo.fit(X, y)
- assert_array_equal(y, ovo.predict(X))
- def test_ovo_one_class():
- # Test error for OvO with one class
- X = np.eye(4)
- y = np.array(["a"] * 4)
- ovo = OneVsOneClassifier(LinearSVC(dual="auto"))
- msg = "when only one class"
- with pytest.raises(ValueError, match=msg):
- ovo.fit(X, y)
- def test_ovo_float_y():
- # Test that the OvO errors on float targets
- X = iris.data
- y = iris.data[:, 0]
- ovo = OneVsOneClassifier(LinearSVC(dual="auto"))
- msg = "Unknown label type"
- with pytest.raises(ValueError, match=msg):
- ovo.fit(X, y)
- def test_ecoc_exceptions():
- ecoc = OutputCodeClassifier(LinearSVC(dual="auto", random_state=0))
- with pytest.raises(NotFittedError):
- ecoc.predict([])
- def test_ecoc_fit_predict():
- # A classifier which implements decision_function.
- ecoc = OutputCodeClassifier(
- LinearSVC(dual="auto", random_state=0), code_size=2, random_state=0
- )
- ecoc.fit(iris.data, iris.target).predict(iris.data)
- assert len(ecoc.estimators_) == n_classes * 2
- # A classifier which implements predict_proba.
- ecoc = OutputCodeClassifier(MultinomialNB(), code_size=2, random_state=0)
- ecoc.fit(iris.data, iris.target).predict(iris.data)
- assert len(ecoc.estimators_) == n_classes * 2
- def test_ecoc_gridsearch():
- ecoc = OutputCodeClassifier(LinearSVC(dual="auto", random_state=0), random_state=0)
- Cs = [0.1, 0.5, 0.8]
- cv = GridSearchCV(ecoc, {"estimator__C": Cs})
- cv.fit(iris.data, iris.target)
- best_C = cv.best_estimator_.estimators_[0].C
- assert best_C in Cs
- def test_ecoc_float_y():
- # Test that the OCC errors on float targets
- X = iris.data
- y = iris.data[:, 0]
- ovo = OutputCodeClassifier(LinearSVC(dual="auto"))
- msg = "Unknown label type"
- with pytest.raises(ValueError, match=msg):
- ovo.fit(X, y)
- def test_ecoc_delegate_sparse_base_estimator():
- # Non-regression test for
- # https://github.com/scikit-learn/scikit-learn/issues/17218
- X, y = iris.data, iris.target
- X_sp = sp.csc_matrix(X)
- # create an estimator that does not support sparse input
- base_estimator = CheckingClassifier(
- check_X=check_array,
- check_X_params={"ensure_2d": True, "accept_sparse": False},
- )
- ecoc = OutputCodeClassifier(base_estimator, random_state=0)
- with pytest.raises(TypeError, match="A sparse matrix was passed"):
- ecoc.fit(X_sp, y)
- ecoc.fit(X, y)
- with pytest.raises(TypeError, match="A sparse matrix was passed"):
- ecoc.predict(X_sp)
- # smoke test to check when sparse input should be supported
- ecoc = OutputCodeClassifier(LinearSVC(dual="auto", random_state=0))
- ecoc.fit(X_sp, y).predict(X_sp)
- assert len(ecoc.estimators_) == 4
- def test_pairwise_indices():
- clf_precomputed = svm.SVC(kernel="precomputed")
- X, y = iris.data, iris.target
- ovr_false = OneVsOneClassifier(clf_precomputed)
- linear_kernel = np.dot(X, X.T)
- ovr_false.fit(linear_kernel, y)
- n_estimators = len(ovr_false.estimators_)
- precomputed_indices = ovr_false.pairwise_indices_
- for idx in precomputed_indices:
- assert (
- idx.shape[0] * n_estimators / (n_estimators - 1) == linear_kernel.shape[0]
- )
- def test_pairwise_n_features_in():
- """Check the n_features_in_ attributes of the meta and base estimators
- When the training data is a regular design matrix, everything is intuitive.
- However, when the training data is a precomputed kernel matrix, the
- multiclass strategy can resample the kernel matrix of the underlying base
- estimator both row-wise and column-wise and this has a non-trivial impact
- on the expected value for the n_features_in_ of both the meta and the base
- estimators.
- """
- X, y = iris.data, iris.target
- # Remove the last sample to make the classes not exactly balanced and make
- # the test more interesting.
- assert y[-1] == 0
- X = X[:-1]
- y = y[:-1]
- # Fitting directly on the design matrix:
- assert X.shape == (149, 4)
- clf_notprecomputed = svm.SVC(kernel="linear").fit(X, y)
- assert clf_notprecomputed.n_features_in_ == 4
- ovr_notprecomputed = OneVsRestClassifier(clf_notprecomputed).fit(X, y)
- assert ovr_notprecomputed.n_features_in_ == 4
- for est in ovr_notprecomputed.estimators_:
- assert est.n_features_in_ == 4
- ovo_notprecomputed = OneVsOneClassifier(clf_notprecomputed).fit(X, y)
- assert ovo_notprecomputed.n_features_in_ == 4
- assert ovo_notprecomputed.n_classes_ == 3
- assert len(ovo_notprecomputed.estimators_) == 3
- for est in ovo_notprecomputed.estimators_:
- assert est.n_features_in_ == 4
- # When working with precomputed kernels we have one "feature" per training
- # sample:
- K = X @ X.T
- assert K.shape == (149, 149)
- clf_precomputed = svm.SVC(kernel="precomputed").fit(K, y)
- assert clf_precomputed.n_features_in_ == 149
- ovr_precomputed = OneVsRestClassifier(clf_precomputed).fit(K, y)
- assert ovr_precomputed.n_features_in_ == 149
- assert ovr_precomputed.n_classes_ == 3
- assert len(ovr_precomputed.estimators_) == 3
- for est in ovr_precomputed.estimators_:
- assert est.n_features_in_ == 149
- # This becomes really interesting with OvO and precomputed kernel together:
- # internally, OvO will drop the samples of the classes not part of the pair
- # of classes under consideration for a given binary classifier. Since we
- # use a precomputed kernel, it will also drop the matching columns of the
- # kernel matrix, and therefore we have fewer "features" as result.
- #
- # Since class 0 has 49 samples, and class 1 and 2 have 50 samples each, a
- # single OvO binary classifier works with a sub-kernel matrix of shape
- # either (99, 99) or (100, 100).
- ovo_precomputed = OneVsOneClassifier(clf_precomputed).fit(K, y)
- assert ovo_precomputed.n_features_in_ == 149
- assert ovr_precomputed.n_classes_ == 3
- assert len(ovr_precomputed.estimators_) == 3
- assert ovo_precomputed.estimators_[0].n_features_in_ == 99 # class 0 vs class 1
- assert ovo_precomputed.estimators_[1].n_features_in_ == 99 # class 0 vs class 2
- assert ovo_precomputed.estimators_[2].n_features_in_ == 100 # class 1 vs class 2
- @pytest.mark.parametrize(
- "MultiClassClassifier", [OneVsRestClassifier, OneVsOneClassifier]
- )
- def test_pairwise_tag(MultiClassClassifier):
- clf_precomputed = svm.SVC(kernel="precomputed")
- clf_notprecomputed = svm.SVC()
- ovr_false = MultiClassClassifier(clf_notprecomputed)
- assert not ovr_false._get_tags()["pairwise"]
- ovr_true = MultiClassClassifier(clf_precomputed)
- assert ovr_true._get_tags()["pairwise"]
- @pytest.mark.parametrize(
- "MultiClassClassifier", [OneVsRestClassifier, OneVsOneClassifier]
- )
- def test_pairwise_cross_val_score(MultiClassClassifier):
- clf_precomputed = svm.SVC(kernel="precomputed")
- clf_notprecomputed = svm.SVC(kernel="linear")
- X, y = iris.data, iris.target
- multiclass_clf_notprecomputed = MultiClassClassifier(clf_notprecomputed)
- multiclass_clf_precomputed = MultiClassClassifier(clf_precomputed)
- linear_kernel = np.dot(X, X.T)
- score_not_precomputed = cross_val_score(
- multiclass_clf_notprecomputed, X, y, error_score="raise"
- )
- score_precomputed = cross_val_score(
- multiclass_clf_precomputed, linear_kernel, y, error_score="raise"
- )
- assert_array_equal(score_precomputed, score_not_precomputed)
- @pytest.mark.parametrize(
- "MultiClassClassifier", [OneVsRestClassifier, OneVsOneClassifier]
- )
- # FIXME: we should move this test in `estimator_checks` once we are able
- # to construct meta-estimator instances
- def test_support_missing_values(MultiClassClassifier):
- # smoke test to check that pipeline OvR and OvO classifiers are letting
- # the validation of missing values to
- # the underlying pipeline or classifiers
- rng = np.random.RandomState(42)
- X, y = iris.data, iris.target
- X = np.copy(X) # Copy to avoid that the original data is modified
- mask = rng.choice([1, 0], X.shape, p=[0.1, 0.9]).astype(bool)
- X[mask] = np.nan
- lr = make_pipeline(SimpleImputer(), LogisticRegression(random_state=rng))
- MultiClassClassifier(lr).fit(X, y).score(X, y)
- @pytest.mark.parametrize("make_y", [np.ones, np.zeros])
- def test_constant_int_target(make_y):
- """Check that constant y target does not raise.
- Non-regression test for #21869
- """
- X = np.ones((10, 2))
- y = make_y((10, 1), dtype=np.int32)
- ovr = OneVsRestClassifier(LogisticRegression())
- ovr.fit(X, y)
- y_pred = ovr.predict_proba(X)
- expected = np.zeros((X.shape[0], 2))
- expected[:, 0] = 1
- assert_allclose(y_pred, expected)
- def test_ovo_consistent_binary_classification():
- """Check that ovo is consistent with binary classifier.
- Non-regression test for #13617.
- """
- X, y = load_breast_cancer(return_X_y=True)
- clf = KNeighborsClassifier(n_neighbors=8, weights="distance")
- ovo = OneVsOneClassifier(clf)
- clf.fit(X, y)
- ovo.fit(X, y)
- assert_array_equal(clf.predict(X), ovo.predict(X))
|