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
5 changes: 1 addition & 4 deletions .github/constraints/deps.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
numpy
scipy>=1.7.1
scikit-learn>=1.2.1
pandas
mealpy>=3.0.2
permetrics>=2.0.0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are transitive deps (via grafo), so being removed here (assuming we agree to remove grafo usage..)

matplotlib>=3.7.5
numpydoc
sphinx_copybutton
sphinx_design
pydata_sphinx_theme
pydata_sphinx_theme
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jobs:
python -m pip install -v ".[test]"
python -m pip install ruff==0.13.1
python -m pip install -r .github/constraints/deps.txt
python -m pip install --no-deps graforvfl
- name: lint
run: |
ruff check
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/wheel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ jobs:
shell: bash
run: |
python -m pip install dist/gfdl*.whl
# test deps need to be manually installed
# because of annoying version conflicts
# with grafo?
python -m pip install --no-deps "permetrics>=2.0.0" "mealpy>=3.0.2"
python -m pip install --no-deps graforvfl
python -m pip install pandas ucimlrepo
cd $RUNNER_TEMP
python -m pytest --pyargs gfdl --cov=gfdl --cov-report=term-missing
86 changes: 58 additions & 28 deletions src/gfdl/tests/test_model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import graforvfl
# tests/test_model.py
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per gh-10, I think we more or less agreed to stop adding these comments for reasons related to maintainability; that's easy enough to remove


import numpy as np
import pytest
from numpy.testing import assert_allclose
Expand Down Expand Up @@ -363,18 +364,59 @@ def test_invalid_alpha(Classifier):
bad_est.fit(X, y)


@pytest.mark.parametrize("hidden_layer_sizes", [(10,), (100,)])
@pytest.mark.parametrize("n_classes", [2, 5])
@pytest.mark.parametrize("activation", activations)
@pytest.mark.parametrize("weight_scheme", weights[2:])
@pytest.mark.parametrize("alpha", [None, 0.5, 1])
@pytest.mark.parametrize("""hidden_layer_sizes,
n_classes,
activation,
weight_scheme,
alpha,
exp_proba_shape,
exp_proba_median,
exp_proba_min""", [

# expected values are from graforvfl library
([10,], 2, "relu", "uniform", None, (20, 2), 0.5, 0.0444571694),
([100,], 2, "tanh", "normal", None, (20, 2), 0.5, 0.02538905725),
([10,], 5, "softmax", "lecun_uniform", None, (20, 5),
0.186506112, 0.08469873),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need help reconstituting a "reasonable" number of permutations "manually," to sample the parameter space that was previously being sampled automatically and compared against graforvfl.

It certainly isn't sustainable to restore all the of the cases from the pervious Cartesian product of cases, and that was probably overparametrizing anyway, but certainly what I've done here is not enough.

([10,], 2, "relu", "uniform", 0.5, (20, 2), 0.49999999999999994,
0.04676933232591643),
([10,], 2, "relu", "normal", 0.5, (20, 2), 0.5,
0.13832596541020634),
([10,], 2, "relu", "he_uniform", 0.5, (20, 2), 0.5,
0.09354846081377409),
([10,], 2, "relu", "lecun_uniform", 0.5, (20, 2), 0.5,
0.09387932375067173),
([10,], 2, "relu", "glorot_uniform", 0.5, (20, 2),
0.49999999999999994, 0.09474642560519067),
([10,], 2, "relu", "he_normal", 0.5, (20, 2), 0.5,
0.13756805074436051),
([10,], 2, "relu", "lecun_normal", 0.5, (20, 2), 0.5,
0.1366715193146648),
([10,], 2, "relu", "glorot_normal", 0.5, (20, 2), 0.5,
0.147434110768701),
([100,], 5, "relu", "normal", 1, (20, 5), 0.15697278777061396,
0.014480242978774488),
([100,], 5, "tanh", "normal", 1, (20, 5), 0.18173657135483476,
0.04755723146401269),
([100,], 5, "sigmoid", "normal", 1, (20, 5), 0.1831653950464296,
0.05378741996708733),
([100,], 5, "softmax", "normal", 1, (20, 5), 0.19357646668265396,
0.10898717209741866),
([100,], 5, "softmin", "normal", 1, (20, 5), 0.18746771358297387,
0.09186562406164228),
([100,], 5, "log_sigmoid", "normal", 1, (20, 5),
0.16722029352468032, 0.012690348255702557),
([100,], 5, "log_softmax", "normal", 1, (20, 5),
0.1853363666712296, 0.10846041127337658),
])
def test_classification_against_grafo(hidden_layer_sizes, n_classes, activation,
weight_scheme, alpha):
# test binary and multi-class classification against
# the open source graforvfl library on some synthetic
weight_scheme, alpha, exp_proba_shape,
exp_proba_median, exp_proba_min):
# test binary and multi-class classification against expected values
# from the open source graforvfl library on some synthetic
# datasets
X, y = make_classification(n_classes=n_classes,
n_informative=8)
n_informative=8, random_state=0)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a problem with the old code--we should pretty much always pin random seeds in test case generation--it wasn't being caught because both our library and grafo were getting the same varied data...

X_train, X_test, y_train, _ = train_test_split(X, y, test_size=0.2,
random_state=0)
model = GFDLClassifier(hidden_layer_sizes=hidden_layer_sizes,
Expand All @@ -385,26 +427,14 @@ def test_classification_against_grafo(hidden_layer_sizes, n_classes, activation,
reg_alpha=alpha)
model.fit(X_train, y_train)

grafo_act = "none" if activation == "identity" else activation
if weight_scheme == "uniform":
grafo_wts = "random_uniform"
elif weight_scheme == "normal":
grafo_wts = "random_normal"
else:
grafo_wts = weight_scheme

grafo_rvfl = graforvfl.RvflClassifier(size_hidden=hidden_layer_sizes[0],
act_name=grafo_act,
weight_initializer=grafo_wts,
reg_alpha=alpha,
seed=0)

grafo_rvfl.fit(X_train, y_train)

actual_proba = model.predict_proba(X_test)
expected_proba = grafo_rvfl.predict_proba(X_test)
actual_proba_shape = actual_proba.shape
actual_proba_median = np.median(actual_proba)
actual_proba_min = np.min(actual_proba)

np.testing.assert_allclose(actual_proba, expected_proba, rtol=2.5e-07)
np.testing.assert_allclose(actual_proba_shape, exp_proba_shape)
np.testing.assert_allclose(actual_proba_median, exp_proba_median)
np.testing.assert_allclose(actual_proba_min, exp_proba_min)


@parametrize_with_checks([GFDLClassifier(), EnsembleGFDLClassifier()])
Expand Down
110 changes: 62 additions & 48 deletions src/gfdl/tests/test_regression.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import numpy as np
import pytest
from graforvfl import RvflRegressor
from sklearn.datasets import fetch_openml, make_regression
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
Expand All @@ -9,20 +8,59 @@

from gfdl.model import GFDLRegressor

activations = ["relu", "tanh", "sigmoid", "identity", "softmax", "softmin",
"log_sigmoid", "log_softmax"]
weights = ["uniform", "normal", "he_uniform", "lecun_uniform",
"glorot_uniform", "he_normal", "lecun_normal", "glorot_normal"]


@pytest.mark.parametrize("n_samples", [100, 1000])
@pytest.mark.parametrize("n_targets", [10, 100])
@pytest.mark.parametrize("hidden_layer_sizes", [(100,), (1000,)])
@pytest.mark.parametrize("activation", activations)
@pytest.mark.parametrize("weight_scheme", weights)
@pytest.mark.parametrize("reg_alpha", [1, 10])
@pytest.mark.parametrize("""n_samples,
n_targets,
hidden_layer_sizes,
activation,
weight_scheme,
reg_alpha,
exp_preds_shape,
exp_preds_median,
exp_preds_min,
exp_preds_r2""", [
# expected values are from the graforvfl library
(100, 10, (100,), "relu", "glorot_normal", 10, (25, 10),
-29.31478018, -490.57518221, 0.97537085),
(100, 10, (100,), "tanh", "uniform", 1, (25, 10),
-43.03897314, -504.32794352, 0.98411997),
(100, 10, (100,), "log_softmax", "uniform", 1, (25, 10),
-30.56871963218171, -558.1388909597706, 0.9999532782125536),
(100, 10, (100,), "log_sigmoid", "normal", 10, (25, 10),
-19.5976250350991, -574.1699708675857, 0.9853855947182326),
(100, 10, (1000,), "softmin", "he_uniform", 1, (25, 10),
-57.91870287977487, -589.6707200160679, 0.9656730623177637),
(100, 10, (1000,), "softmax", "lecun_uniform", 10, (25, 10),
-51.938696542946786, -513.4094105001416, 0.9589931777194366),
(100, 100, (100,), "sigmoid", "glorot_uniform", 1, (25, 100),
-46.92889730988215, -1585.2331437646524, 0.6496204322668526),
(100, 100, (100,), "tanh", "he_normal", 10, (25, 100),
-5.531248709518545, -1131.5021652659007, 0.6018381457540279),
(100, 100, (1000,), "relu", "lecun_normal", 1, (25, 100),
-24.857674257413233, -1241.941403822942, 0.5954067650339964),
(100, 100, (1000,), "identity", "glorot_normal", 10, (25, 100),
-49.66037744636776, -1418.0996396366454, 0.6387637880009253),
(1000, 10, (100,), "log_softmax", "glorot_normal", 1, (250, 10),
-2.157983014856103, -821.8910528092026, 0.999999671320564),
(1000, 10, (100,), "log_sigmoid", "lecun_normal", 10, (250, 10),
-2.25281191108881, -813.3197346939389, 0.9998208055604957),
(1000, 10, (1000,), "softmin", "he_normal", 1, (250, 10),
-2.932635323616438, -819.9889270165279, 0.9999535335431835),
(1000, 10, (1000,), "softmax", "glorot_uniform", 10, (250, 10),
-3.27895924524588, -809.0526184106433, 0.9996980844468629),
(1000, 100, (100,), "sigmoid", "lecun_uniform", 1, (250, 100),
40.193814730616296, -2003.2760146757932, 0.9999864051131802),
(1000, 100, (100,), "tanh", "he_normal", 10, (250, 100),
38.349789631939906, -1968.7361166078529, 0.9984649082549426),
(1000, 100, (1000,), "relu", "normal", 1, (250, 100),
47.91240910167704, -2194.259205351918, 0.8620693547752554),
(1000, 100, (1000,), "identity", "uniform", 10, (250, 100),
39.788475103832646, -2004.3219743138504, 0.9999999882159872)
])
def test_regression_against_grafo(n_samples, n_targets, hidden_layer_sizes,
activation, weight_scheme, reg_alpha):
activation, weight_scheme, reg_alpha,
exp_preds_shape, exp_preds_median,
exp_preds_min, exp_preds_r2):
N, d = n_samples, n_targets
RNG = 42
X, y = make_regression(n_samples=N,
Expand All @@ -42,48 +80,24 @@ def test_regression_against_grafo(n_samples, n_targets, hidden_layer_sizes,
X_train_s = scaler.transform(X_train)
X_test_s = scaler.transform(X_test)

grafo_act = "none" if activation == "identity" else activation
if weight_scheme == "uniform":
grafo_wts = "random_uniform"
elif weight_scheme == "normal":
grafo_wts = "random_normal"
else:
grafo_wts = weight_scheme

# Define models
models = {
"GrafoRVFL": RvflRegressor(
size_hidden=hidden_layer_sizes[0],
act_name=grafo_act,
weight_initializer=grafo_wts,
reg_alpha=reg_alpha,
seed=RNG
),
"GFDL": GFDLRegressor(
model = GFDLRegressor(
hidden_layer_sizes=hidden_layer_sizes,
activation=activation,
weight_scheme=weight_scheme,
direct_links=1,
seed=RNG,
reg_alpha=reg_alpha
)
}

# Fit + predict
preds = {}

for name, model in models.items():
Xtr, Xte = (X_train_s, X_test_s)
model.fit(Xtr, y_train)
yhat = model.predict(Xte)
preds[name] = yhat

# Compare GrafoRVFL and GFDL results
grf_results = preds["GrafoRVFL"]
cur_results = preds["GFDL"]

# Test results
np.testing.assert_allclose(cur_results, grf_results, atol=1e-07)
model.fit(X_train_s, y_train)
actual_preds = model.predict(X_test_s)
actual_preds_shape = actual_preds.shape
actual_preds_median = np.median(actual_preds)
actual_preds_min = actual_preds.min()
actual_preds_r2 = r2_score(y_test, actual_preds)
np.testing.assert_allclose(actual_preds_shape, exp_preds_shape)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could be a bit stricter here and for classifier test above with assert_array_equal since the shapes are by definition exact integers, but that can be done later, and in practice any reasonable bug would still cause a test failure as is.

np.testing.assert_allclose(actual_preds_median, exp_preds_median)
np.testing.assert_allclose(actual_preds_min, exp_preds_min)
np.testing.assert_allclose(actual_preds_r2, exp_preds_r2)


@parametrize_with_checks([GFDLRegressor()])
Expand Down