Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/gfdl/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,14 +352,16 @@ def __init__(
activation: str = "identity",
weight_scheme: str = "uniform",
seed: int = None,
reg_alpha: float = None
reg_alpha: float = None,
rtol: float | None = None,
):
super().__init__(hidden_layer_sizes=hidden_layer_sizes,
activation=activation,
weight_scheme=weight_scheme,
direct_links=True,
seed=seed,
reg_alpha=reg_alpha)
reg_alpha=reg_alpha,
rtol=rtol)

def fit(self, X, Y):

Expand Down Expand Up @@ -412,7 +414,7 @@ def fit(self, X, Y):
# If reg_alpha is None, use direct solve using
# MoorePenrose Pseudo-Inverse, otherwise use ridge regularized form.
if self.reg_alpha is None:
coeff = np.linalg.pinv(D) @ Y
coeff = np.linalg.pinv(D, rtol=self.rtol) @ Y
else:
ridge = Ridge(alpha=self.reg_alpha, fit_intercept=False)
ridge.fit(D, Y)
Expand Down Expand Up @@ -511,6 +513,12 @@ class EnsembleGFDLClassifier(ClassifierMixin, EnsembleGFDL):
multiplies the L2 term of `sklearn` `Ridge`, controlling the
regularization strength. `reg_alpha` must be a non-negative float.

rtol : float, default=None
Cutoff for small singular values for the Moore-Penrose
pseudo-inverse. Only applies when ``reg_alpha=None``.
When ``rtol=None``, the array API standard default for
``pinv`` is used.

voting : str, default=`"soft"`
Whether to use soft or hard voting in the ensemble.

Expand Down Expand Up @@ -543,13 +551,15 @@ def __init__(
weight_scheme: str = "uniform",
seed: int = None,
reg_alpha: float = None,
rtol: float = None,
voting: str = "soft", # "soft" or "hard"
):
super().__init__(hidden_layer_sizes=hidden_layer_sizes,
activation=activation,
weight_scheme=weight_scheme,
seed=seed,
reg_alpha=reg_alpha,
rtol=rtol
)
self.voting = voting

Expand Down
46 changes: 41 additions & 5 deletions src/gfdl/tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def test_sklearn_api_conformance(estimator, check):
def test_rtol_classifier(reg_alpha, rtol, expected_acc, expected_roc):
# For Moore-Penrose, a large singular value cutoff (rtol)
# may be required to achieve reasonable results. This test
# showcases that a default low cut of leads to almost random classification
# showcases that a default low cut off leads to almost random classification
# output for the Digits datasets which is alleviated by increasing the cut off.
# This cut off has no effect on ridge solver.
data = load_digits()
Expand All @@ -432,11 +432,9 @@ def test_rtol_classifier(reg_alpha, rtol, expected_acc, expected_roc):
X_train_s = scaler.transform(X_train)
X_test_s = scaler.transform(X_test)

activation = "softmax"
weight_scheme = "normal"
model = GFDLClassifier(hidden_layer_sizes=[800] * 10,
activation=activation,
weight_scheme=weight_scheme,
activation="softmax",
weight_scheme="normal",
seed=0,
reg_alpha=reg_alpha,
rtol=rtol)
Expand All @@ -450,3 +448,41 @@ def test_rtol_classifier(reg_alpha, rtol, expected_acc, expected_roc):

np.testing.assert_allclose(acc_cur, expected_acc)
np.testing.assert_allclose(roc_cur, expected_roc)


@pytest.mark.parametrize("reg_alpha, rtol, expected_acc, expected_roc", [
(5.0, 1e-15, 0.7222222222222222, 0.9525486362311113),
(None, 1e-15, 0.10833333333333334, 0.5062846049300238),
(None, 1e-3, 0.9555555555555556, 0.9920190654177233),
])
def test_rtol_ensemble(reg_alpha, rtol, expected_acc, expected_roc):
# For Moore-Penrose, a large singular value cutoff (rtol)
# may be required to achieve reasonable results. This test
# showcases that a default low cut off leads to almost random classification
# output for the Digits datasets which is alleviated by increasing the cut off.
# This cut off has no effect on ridge solver.
data = load_digits()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=0)

scaler = StandardScaler().fit(X_train)
X_train_s = scaler.transform(X_train)
X_test_s = scaler.transform(X_test)

model = EnsembleGFDLClassifier(hidden_layer_sizes=[2000] * 2,
activation="relu",
weight_scheme="uniform",
seed=0,
reg_alpha=reg_alpha,
rtol=rtol)
model.fit(X_train_s, y_train)

y_hat_cur = model.predict(X_test_s)
y_hat_cur_proba = model.predict_proba(X_test_s)

acc_cur = accuracy_score(y_test, y_hat_cur)
roc_cur = roc_auc_score(y_test, y_hat_cur_proba, multi_class="ovo")

np.testing.assert_allclose(acc_cur, expected_acc)
np.testing.assert_allclose(roc_cur, expected_roc, atol=1e-05)