|
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.fixes import ( |
|
COO_CONTAINERS, |
|
CSC_CONTAINERS, |
|
CSR_CONTAINERS, |
|
DOK_CONTAINERS, |
|
LIL_CONTAINERS, |
|
) |
|
from sklearn.utils.multiclass import check_classification_targets, type_of_target |
|
|
|
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(random_state=0)) |
|
|
|
|
|
with pytest.raises(NotFittedError): |
|
ovr.predict([]) |
|
|
|
|
|
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(): |
|
|
|
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(): |
|
|
|
ovr = OneVsRestClassifier(LinearSVC(random_state=0)) |
|
pred = ovr.fit(iris.data, iris.target).predict(iris.data) |
|
assert len(ovr.estimators_) == n_classes |
|
|
|
clf = LinearSVC(random_state=0) |
|
pred2 = clf.fit(iris.data, iris.target).predict(iris.data) |
|
assert np.mean(iris.target == pred) == np.mean(iris.target == pred2) |
|
|
|
|
|
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(): |
|
|
|
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 |
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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)) |
|
|
|
|
|
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(): |
|
|
|
|
|
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]) |
|
|
|
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]) |
|
|
|
assert np.mean(pred == iris.target) > 0.9 |
|
|
|
|
|
@pytest.mark.parametrize( |
|
"sparse_container", |
|
CSR_CONTAINERS + CSC_CONTAINERS + COO_CONTAINERS + DOK_CONTAINERS + LIL_CONTAINERS, |
|
) |
|
def test_ovr_fit_predict_sparse(sparse_container): |
|
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_container(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) |
|
|
|
|
|
Y_proba = clf_sprs.predict_proba(X_test) |
|
|
|
|
|
|
|
pred = Y_proba > 0.5 |
|
assert_array_equal(pred, Y_pred_sprs.toarray()) |
|
|
|
|
|
clf = svm.SVC() |
|
clf_sprs = OneVsRestClassifier(clf).fit(X_train, sparse_container(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(): |
|
|
|
|
|
X = np.ones((10, 2)) |
|
X[:5, :] = 0 |
|
|
|
|
|
|
|
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 = np.zeros((10, 2)) |
|
y[5:, 0] = 1 |
|
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(): |
|
|
|
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(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"]) |
|
|
|
|
|
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(): |
|
|
|
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) |
|
|
|
|
|
clf = OneVsRestClassifier(base_clf).fit(X, Y) |
|
y_pred = clf.predict([[3, 0, 0]])[0] |
|
assert y_pred == 1 |
|
|
|
for base_clf in ( |
|
LinearSVC(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(): |
|
|
|
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(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_only = OneVsRestClassifier(svm.SVR()).fit(X_train, Y_train) |
|
assert not hasattr(decision_only, "predict_proba") |
|
|
|
|
|
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") |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
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_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) |
|
|
|
|
|
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(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(): |
|
|
|
|
|
|
|
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(random_state=0)) |
|
with pytest.raises(NotFittedError): |
|
ovo.predict([]) |
|
|
|
|
|
def test_ovo_fit_on_list(): |
|
|
|
|
|
ovo = OneVsOneClassifier(LinearSVC(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(): |
|
|
|
ovo = OneVsOneClassifier(LinearSVC(random_state=0)) |
|
ovo.fit(iris.data, iris.target).predict(iris.data) |
|
assert len(ovo.estimators_) == n_classes * (n_classes - 1) / 2 |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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)) |
|
|
|
|
|
ovr = OneVsOneClassifier(SVC()) |
|
assert not hasattr(ovr, "partial_fit") |
|
|
|
|
|
def test_ovo_decision_function(): |
|
n_samples = iris.data.shape[0] |
|
|
|
ovo_clf = OneVsOneClassifier(LinearSVC(random_state=0)) |
|
|
|
ovo_clf.fit(iris.data, iris.target == 0) |
|
decisions = ovo_clf.decision_function(iris.data) |
|
assert decisions.shape == (n_samples,) |
|
|
|
|
|
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)) |
|
|
|
|
|
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 |
|
|
|
|
|
assert_array_equal(votes, np.round(decisions)) |
|
|
|
for class_idx in range(n_classes): |
|
|
|
|
|
|
|
|
|
|
|
assert set(votes[:, class_idx]).issubset(set([0.0, 1.0, 2.0])) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert len(np.unique(decisions[:, class_idx])) > 146 |
|
|
|
|
|
def test_ovo_gridsearch(): |
|
ovo = OneVsOneClassifier(LinearSVC(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(): |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
votes = np.round(ovo_decision) |
|
normalized_confidences = ovo_decision - votes |
|
|
|
|
|
assert_array_equal(votes[0, :], 1) |
|
|
|
assert_array_equal(np.argmax(votes[1:], axis=1), ovo_prediction[1:]) |
|
|
|
assert ovo_prediction[0] == normalized_confidences[0].argmax() |
|
|
|
|
|
def test_ovo_ties2(): |
|
|
|
X = np.array([[1, 2], [2, 1], [-2, 1], [-2, -1]]) |
|
y_ref = np.array([2, 0, 1, 2]) |
|
|
|
|
|
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(): |
|
|
|
X = np.eye(4) |
|
y = np.array(["a", "b", "c", "d"]) |
|
|
|
ovo = OneVsOneClassifier(LinearSVC()) |
|
ovo.fit(X, y) |
|
assert_array_equal(y, ovo.predict(X)) |
|
|
|
|
|
def test_ovo_one_class(): |
|
|
|
X = np.eye(4) |
|
y = np.array(["a"] * 4) |
|
|
|
ovo = OneVsOneClassifier(LinearSVC()) |
|
msg = "when only one class" |
|
with pytest.raises(ValueError, match=msg): |
|
ovo.fit(X, y) |
|
|
|
|
|
def test_ovo_float_y(): |
|
|
|
X = iris.data |
|
y = iris.data[:, 0] |
|
|
|
ovo = OneVsOneClassifier(LinearSVC()) |
|
msg = "Unknown label type" |
|
with pytest.raises(ValueError, match=msg): |
|
ovo.fit(X, y) |
|
|
|
|
|
def test_ecoc_exceptions(): |
|
ecoc = OutputCodeClassifier(LinearSVC(random_state=0)) |
|
with pytest.raises(NotFittedError): |
|
ecoc.predict([]) |
|
|
|
|
|
def test_ecoc_fit_predict(): |
|
|
|
ecoc = OutputCodeClassifier(LinearSVC(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 |
|
|
|
|
|
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(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(): |
|
|
|
X = iris.data |
|
y = iris.data[:, 0] |
|
|
|
ovo = OutputCodeClassifier(LinearSVC()) |
|
msg = "Unknown label type" |
|
with pytest.raises(ValueError, match=msg): |
|
ovo.fit(X, y) |
|
|
|
|
|
@pytest.mark.parametrize("csc_container", CSC_CONTAINERS) |
|
def test_ecoc_delegate_sparse_base_estimator(csc_container): |
|
|
|
|
|
X, y = iris.data, iris.target |
|
X_sp = csc_container(X) |
|
|
|
|
|
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="Sparse data was passed"): |
|
ecoc.fit(X_sp, y) |
|
|
|
ecoc.fit(X, y) |
|
with pytest.raises(TypeError, match="Sparse data was passed"): |
|
ecoc.predict(X_sp) |
|
|
|
|
|
ecoc = OutputCodeClassifier(LinearSVC(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 |
|
|
|
|
|
|
|
assert y[-1] == 0 |
|
X = X[:-1] |
|
y = y[:-1] |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
assert ovo_precomputed.estimators_[1].n_features_in_ == 99 |
|
assert ovo_precomputed.estimators_[2].n_features_in_ == 100 |
|
|
|
|
|
@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.__sklearn_tags__().input_tags.pairwise |
|
|
|
ovr_true = MultiClassClassifier(clf_precomputed) |
|
assert ovr_true.__sklearn_tags__().input_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] |
|
) |
|
|
|
|
|
def test_support_missing_values(MultiClassClassifier): |
|
|
|
|
|
|
|
rng = np.random.RandomState(42) |
|
X, y = iris.data, iris.target |
|
X = np.copy(X) |
|
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)) |
|
|
|
|
|
def test_multiclass_estimator_attribute_error(): |
|
"""Check that we raise the proper AttributeError when the final estimator |
|
does not implement the `partial_fit` method, which is decorated with |
|
`available_if`. |
|
|
|
Non-regression test for: |
|
https://github.com/scikit-learn/scikit-learn/issues/28108 |
|
""" |
|
iris = datasets.load_iris() |
|
|
|
|
|
|
|
clf = OneVsRestClassifier(estimator=LogisticRegression(random_state=42)) |
|
|
|
outer_msg = "This 'OneVsRestClassifier' has no attribute 'partial_fit'" |
|
inner_msg = "'LogisticRegression' object has no attribute 'partial_fit'" |
|
with pytest.raises(AttributeError, match=outer_msg) as exec_info: |
|
clf.partial_fit(iris.data, iris.target) |
|
assert isinstance(exec_info.value.__cause__, AttributeError) |
|
assert inner_msg in str(exec_info.value.__cause__) |
|
|