sklearn-morpho 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.SRCINFO +3 -3
- sklearn_morpho-0.2.2/.github/workflows/ci.yml +90 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.gitignore +2 -1
- sklearn_morpho-0.2.2/.qbuild/scripts/coverage +1 -0
- sklearn_morpho-0.2.2/.qbuild/scripts/main +1 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/l-DEP.ipynb +15 -15
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/r-DEP.ipynb +15 -15
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/simple_perceptron.ipynb +16 -9
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/train_multiclass.ipynb +2 -1
- sklearn_morpho-0.2.2/PKG-INFO +10 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/PKGBUILD +2 -2
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/README.md +6 -24
- sklearn_morpho-0.2.2/pyproject.toml +49 -0
- sklearn_morpho-0.2.2/src/sklearn_morpho/__init__.py +5 -0
- sklearn_morpho-0.2.2/src/sklearn_morpho/classifiers/__init__.py +3 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/classifiers/ldep.py +51 -31
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/classifiers/rdep.py +45 -27
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/classifiers/simple_perceptron.py +46 -27
- sklearn_morpho-0.2.2/src/sklearn_morpho/stopping/__init__.py +7 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_base.py +9 -2
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_cost.py +8 -5
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_early.py +8 -3
- sklearn_morpho-0.2.2/src/sklearn_morpho/stopping/stopping_epoch.py +24 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_train_stop.py +4 -2
- sklearn_morpho-0.2.2/src/sklearn_morpho/training/__init__.py +6 -0
- {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_ldep.py +81 -45
- {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_rdep.py +107 -55
- {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_simple_perceptron.py +48 -31
- {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_wrapper.py +73 -41
- sklearn_morpho-0.2.2/src/sklearn_morpho/weighting/__init__.py +3 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/weighting/weighting_base.py +15 -8
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/weighting/weighting_dist.py +11 -10
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/weighting/weighting_none.py +3 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/testing/compare_estimators.py +75 -32
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/testing/compare_estimators_show.py +13 -7
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/testing/display_boundary.py +32 -18
- sklearn_morpho-0.2.2/tests/integration/sklearn_test.py +23 -0
- sklearn_morpho-0.2.2/tests/stopping/cost_test.py +32 -0
- sklearn_morpho-0.2.2/tests/stopping/early_test.py +41 -0
- sklearn_morpho-0.2.2/tests/stopping/epoch_test.py +28 -0
- sklearn_morpho-0.2.2/tests/stopping/train_stop_test.py +20 -0
- sklearn_morpho-0.2.2/tests/training/friendly_dataset.py +31 -0
- sklearn_morpho-0.2.2/tests/training/test_dccp_wrapper.py +137 -0
- sklearn_morpho-0.2.2/tests/training/test_ldep.py +78 -0
- sklearn_morpho-0.2.2/tests/training/test_rdep.py +100 -0
- sklearn_morpho-0.2.2/tests/training/test_simple_perceptron.py +88 -0
- sklearn_morpho-0.2.2/tests/weighting/dist_test.py +36 -0
- sklearn_morpho-0.2.2/tests/weighting/none_test.py +50 -0
- sklearn_morpho-0.2.1/.github/workflows/ci.yml +0 -36
- sklearn_morpho-0.2.1/.qbuild/scripts/main +0 -1
- sklearn_morpho-0.2.1/PKG-INFO +0 -9
- sklearn_morpho-0.2.1/pyproject.toml +0 -32
- sklearn_morpho-0.2.1/src/sklearn_morpho/__init__.py +0 -5
- sklearn_morpho-0.2.1/src/sklearn_morpho/stopping/__init__.py +0 -5
- sklearn_morpho-0.2.1/src/sklearn_morpho/stopping/stopping_epoch.py +0 -21
- sklearn_morpho-0.2.1/src/sklearn_morpho/tests/moons_test.py +0 -16
- sklearn_morpho-0.2.1/src/sklearn_morpho/tests/sklearn_test.py +0 -19
- sklearn_morpho-0.2.1/src/sklearn_morpho/tests/train_test.py +0 -19
- sklearn_morpho-0.2.1/src/sklearn_morpho/weighting/__init__.py +0 -3
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.github/workflows/pypi-publish.yml +0 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.gitlab-ci.yml +0 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.qbuild/scripts/tests +0 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/LICENSE +0 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/check_todos.sh +0 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/pkgbuild_helper.sh +0 -0
- {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/pyrightconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pkgbase = python-sklearn-morpho
|
|
2
2
|
pkgdesc = Scikit-learn estimator toolbox for morphological perceptrons
|
|
3
|
-
pkgver = 0.2.
|
|
3
|
+
pkgver = 0.2.1
|
|
4
4
|
pkgrel = 1
|
|
5
5
|
url = https://github.com/d-002/sklearn-morpho
|
|
6
6
|
arch = any
|
|
@@ -13,7 +13,7 @@ pkgbase = python-sklearn-morpho
|
|
|
13
13
|
depends = python-cvxpy
|
|
14
14
|
depends = python-pytest
|
|
15
15
|
depends = python-scikit-learn
|
|
16
|
-
source = https://files.pythonhosted.org/packages/source/s/sklearn_morpho/sklearn_morpho-0.2.
|
|
17
|
-
sha256sums =
|
|
16
|
+
source = https://files.pythonhosted.org/packages/source/s/sklearn_morpho/sklearn_morpho-0.2.1.tar.gz
|
|
17
|
+
sha256sums = aaf94e85e2f546928ace464bf776c08681cd5349a48012076c3c66a9e801961f
|
|
18
18
|
|
|
19
19
|
pkgname = python-sklearn-morpho
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "master" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "**" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test on ${{ matrix.os }}
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
|
|
26
|
+
- name: Install Hatch
|
|
27
|
+
run: python -m pip install hatch
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: hatch run pytest
|
|
31
|
+
|
|
32
|
+
coverage:
|
|
33
|
+
name: Coverage test, ignore failed tests
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout repository
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Set up Python
|
|
41
|
+
uses: actions/setup-python@v5
|
|
42
|
+
with:
|
|
43
|
+
python-version: "3.13"
|
|
44
|
+
|
|
45
|
+
- name: Install Hatch
|
|
46
|
+
run: python -m pip install hatch
|
|
47
|
+
|
|
48
|
+
- name: Install coverage utilities
|
|
49
|
+
run: python -m pip install coverage
|
|
50
|
+
|
|
51
|
+
- name: Check coverage
|
|
52
|
+
run: |
|
|
53
|
+
hatch run pytest --cov --cov-fail-under=90
|
|
54
|
+
|
|
55
|
+
todo:
|
|
56
|
+
name: Check for TODOs in code
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
|
|
59
|
+
steps:
|
|
60
|
+
- name: Checkout repository
|
|
61
|
+
uses: actions/checkout@v4
|
|
62
|
+
|
|
63
|
+
- name: Scan for TODOs in the code
|
|
64
|
+
run: ./check_todos.sh
|
|
65
|
+
|
|
66
|
+
linter:
|
|
67
|
+
name: Check for ${{ matrix.name }} errors
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
strategy:
|
|
70
|
+
matrix:
|
|
71
|
+
include:
|
|
72
|
+
- name: "linting"
|
|
73
|
+
command: "python -m ruff check"
|
|
74
|
+
- name: "coding style"
|
|
75
|
+
command: "[ -z \"$(python -m ruff format | grep reformatted)\" ]"
|
|
76
|
+
|
|
77
|
+
steps:
|
|
78
|
+
- name: Checkout repository
|
|
79
|
+
uses: actions/checkout@v4
|
|
80
|
+
|
|
81
|
+
- name: Set up Python
|
|
82
|
+
uses: actions/setup-python@v5
|
|
83
|
+
with:
|
|
84
|
+
python-version: "3.13"
|
|
85
|
+
|
|
86
|
+
- name: Install linter
|
|
87
|
+
run: python -m pip install ruff
|
|
88
|
+
|
|
89
|
+
- name: Run linter for coding style errors
|
|
90
|
+
run: ${{ matrix.command }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hatch run pytest --cov --cov-report=html && xdg-open htmlcov/index.html
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
python testing/display_boundary.py
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"source": [
|
|
67
67
|
"random_state = np.random.RandomState(42)\n",
|
|
68
68
|
"\n",
|
|
69
|
-
"X, y = make_moons(n_samples=1000, noise
|
|
69
|
+
"X, y = make_moons(n_samples=1000, noise=0.2, random_state=random_state)\n",
|
|
70
70
|
"y = np.array(['red', 'blue'])[y]"
|
|
71
71
|
]
|
|
72
72
|
},
|
|
@@ -890,7 +890,8 @@
|
|
|
890
890
|
"ldep = LDEP(verbose=1, random_state=random_state)\n",
|
|
891
891
|
"\n",
|
|
892
892
|
"X_train, X_test, y_train, y_test = train_test_split(\n",
|
|
893
|
-
" X, y, test_size
|
|
893
|
+
" X, y, test_size=0.3, random_state=random_state\n",
|
|
894
|
+
")\n",
|
|
894
895
|
"\n",
|
|
895
896
|
"ldep.fit(X_train, y_train)"
|
|
896
897
|
]
|
|
@@ -926,12 +927,7 @@
|
|
|
926
927
|
],
|
|
927
928
|
"source": [
|
|
928
929
|
"ldep_dccp = LDEP(use_dccp_library=True, verbose=1, random_state=random_state)\n",
|
|
929
|
-
"
|
|
930
|
-
"# ignore warnings coming from the DCCP library\n",
|
|
931
|
-
"import warnings\n",
|
|
932
|
-
"with warnings.catch_warnings(action=\"ignore\"):\n",
|
|
933
|
-
" ldep_dccp.fit(X_train, y_train)\n",
|
|
934
|
-
"del warnings"
|
|
930
|
+
"ldep_dccp.fit(X_train, y_train)"
|
|
935
931
|
]
|
|
936
932
|
},
|
|
937
933
|
{
|
|
@@ -991,7 +987,9 @@
|
|
|
991
987
|
"outputs": [],
|
|
992
988
|
"source": [
|
|
993
989
|
"score_test = f1_score(y_test, ldep.predict(X_test), pos_label=np.unique(y)[1])\n",
|
|
994
|
-
"score_test_dccp = f1_score(
|
|
990
|
+
"score_test_dccp = f1_score(\n",
|
|
991
|
+
" y_test, ldep_dccp.predict(X_test), pos_label=np.unique(y)[1]\n",
|
|
992
|
+
")"
|
|
995
993
|
]
|
|
996
994
|
},
|
|
997
995
|
{
|
|
@@ -1031,18 +1029,20 @@
|
|
|
1031
1029
|
],
|
|
1032
1030
|
"source": [
|
|
1033
1031
|
"for name, estimator, score in (\n",
|
|
1034
|
-
"
|
|
1035
|
-
"
|
|
1032
|
+
" ('l-DEP with manual linearization', ldep, score_test),\n",
|
|
1033
|
+
" ('l-DEP with dccp library', ldep_dccp, score_test_dccp),\n",
|
|
1036
1034
|
"):\n",
|
|
1037
1035
|
" # draw the decision boundary\n",
|
|
1038
1036
|
" disp = DecisionBoundaryDisplay.from_estimator(\n",
|
|
1039
|
-
" estimator
|
|
1037
|
+
" estimator,\n",
|
|
1038
|
+
" X_test,\n",
|
|
1039
|
+
" response_method='decision_function',\n",
|
|
1040
1040
|
" grid_resolution=200,\n",
|
|
1041
1041
|
" plot_method='contour',\n",
|
|
1042
1042
|
" levels=[0],\n",
|
|
1043
|
-
" colors='black'
|
|
1043
|
+
" colors='black',\n",
|
|
1044
1044
|
" )\n",
|
|
1045
|
-
"
|
|
1045
|
+
"\n",
|
|
1046
1046
|
" # add the points\n",
|
|
1047
1047
|
" ax = disp.ax_\n",
|
|
1048
1048
|
" ax.scatter(*X_test.T, color=y_test)\n",
|
|
@@ -1067,7 +1067,7 @@
|
|
|
1067
1067
|
"name": "python",
|
|
1068
1068
|
"nbconvert_exporter": "python",
|
|
1069
1069
|
"pygments_lexer": "ipython3",
|
|
1070
|
-
"version": "3.
|
|
1070
|
+
"version": "3.13.9"
|
|
1071
1071
|
}
|
|
1072
1072
|
},
|
|
1073
1073
|
"nbformat": 4,
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"source": [
|
|
63
63
|
"random_state = np.random.RandomState(42)\n",
|
|
64
64
|
"\n",
|
|
65
|
-
"X, y = make_moons(n_samples=1000, noise
|
|
65
|
+
"X, y = make_moons(n_samples=1000, noise=0.2, random_state=random_state)\n",
|
|
66
66
|
"y = np.array(['red', 'blue'])[y]"
|
|
67
67
|
]
|
|
68
68
|
},
|
|
@@ -889,7 +889,8 @@
|
|
|
889
889
|
"rdep = RDEP(verbose=1, random_state=random_state)\n",
|
|
890
890
|
"\n",
|
|
891
891
|
"X_train, X_test, y_train, y_test = train_test_split(\n",
|
|
892
|
-
" X, y, test_size
|
|
892
|
+
" X, y, test_size=0.3, random_state=random_state\n",
|
|
893
|
+
")\n",
|
|
893
894
|
"\n",
|
|
894
895
|
"rdep.fit(X_train, y_train)"
|
|
895
896
|
]
|
|
@@ -925,12 +926,7 @@
|
|
|
925
926
|
],
|
|
926
927
|
"source": [
|
|
927
928
|
"rdep_dccp = RDEP(use_dccp_library=True, verbose=1, random_state=random_state)\n",
|
|
928
|
-
"
|
|
929
|
-
"# ignore warnings coming from the DCCP library\n",
|
|
930
|
-
"import warnings\n",
|
|
931
|
-
"with warnings.catch_warnings(action=\"ignore\"):\n",
|
|
932
|
-
" rdep_dccp.fit(X_train, y_train)\n",
|
|
933
|
-
"del warnings"
|
|
929
|
+
"rdep_dccp.fit(X_train, y_train)"
|
|
934
930
|
]
|
|
935
931
|
},
|
|
936
932
|
{
|
|
@@ -990,7 +986,9 @@
|
|
|
990
986
|
"outputs": [],
|
|
991
987
|
"source": [
|
|
992
988
|
"score_test = f1_score(y_test, rdep.predict(X_test), pos_label=np.unique(y)[1])\n",
|
|
993
|
-
"score_test_dccp = f1_score(
|
|
989
|
+
"score_test_dccp = f1_score(\n",
|
|
990
|
+
" y_test, rdep_dccp.predict(X_test), pos_label=np.unique(y)[1]\n",
|
|
991
|
+
")"
|
|
994
992
|
]
|
|
995
993
|
},
|
|
996
994
|
{
|
|
@@ -1030,18 +1028,20 @@
|
|
|
1030
1028
|
],
|
|
1031
1029
|
"source": [
|
|
1032
1030
|
"for name, estimator, score in (\n",
|
|
1033
|
-
"
|
|
1034
|
-
"
|
|
1031
|
+
" ('r-DEP with manual linearization', rdep, score_test),\n",
|
|
1032
|
+
" ('r-DEP with dccp library', rdep_dccp, score_test_dccp),\n",
|
|
1035
1033
|
"):\n",
|
|
1036
1034
|
" # draw the decision boundary\n",
|
|
1037
1035
|
" disp = DecisionBoundaryDisplay.from_estimator(\n",
|
|
1038
|
-
" estimator
|
|
1036
|
+
" estimator,\n",
|
|
1037
|
+
" X_test,\n",
|
|
1038
|
+
" response_method='decision_function',\n",
|
|
1039
1039
|
" grid_resolution=200,\n",
|
|
1040
1040
|
" plot_method='contour',\n",
|
|
1041
1041
|
" levels=[0],\n",
|
|
1042
|
-
" colors='black'
|
|
1042
|
+
" colors='black',\n",
|
|
1043
1043
|
" )\n",
|
|
1044
|
-
"
|
|
1044
|
+
"\n",
|
|
1045
1045
|
" # add the points\n",
|
|
1046
1046
|
" ax = disp.ax_\n",
|
|
1047
1047
|
" ax.scatter(*X_test.T, color=y_test)\n",
|
|
@@ -1066,7 +1066,7 @@
|
|
|
1066
1066
|
"name": "python",
|
|
1067
1067
|
"nbconvert_exporter": "python",
|
|
1068
1068
|
"pygments_lexer": "ipython3",
|
|
1069
|
-
"version": "3.
|
|
1069
|
+
"version": "3.13.9"
|
|
1070
1070
|
}
|
|
1071
1071
|
},
|
|
1072
1072
|
"nbformat": 4,
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"source": [
|
|
65
65
|
"random_state = np.random.RandomState(42)\n",
|
|
66
66
|
"\n",
|
|
67
|
-
"X, y = make_moons(n_samples=1000, noise
|
|
67
|
+
"X, y = make_moons(n_samples=1000, noise=0.2, random_state=random_state)\n",
|
|
68
68
|
"y = np.array(['red', 'blue'])[y]"
|
|
69
69
|
]
|
|
70
70
|
},
|
|
@@ -907,7 +907,8 @@
|
|
|
907
907
|
"perceptron_min = MP(kind='min', verbose=1, random_state=random_state)\n",
|
|
908
908
|
"\n",
|
|
909
909
|
"X_train, X_test, y_train, y_test = train_test_split(\n",
|
|
910
|
-
" X, y, test_size
|
|
910
|
+
" X, y, test_size=0.3, random_state=random_state\n",
|
|
911
|
+
")\n",
|
|
911
912
|
"\n",
|
|
912
913
|
"perceptron_max.fit(X_train, y_train)\n",
|
|
913
914
|
"perceptron_min.fit(X_train, y_train)"
|
|
@@ -969,8 +970,12 @@
|
|
|
969
970
|
"metadata": {},
|
|
970
971
|
"outputs": [],
|
|
971
972
|
"source": [
|
|
972
|
-
"score_test_max = f1_score(
|
|
973
|
-
"
|
|
973
|
+
"score_test_max = f1_score(\n",
|
|
974
|
+
" y_test, perceptron_max.predict(X_test), pos_label=np.unique(y)[1]\n",
|
|
975
|
+
")\n",
|
|
976
|
+
"score_test_min = f1_score(\n",
|
|
977
|
+
" y_test, perceptron_min.predict(X_test), pos_label=np.unique(y)[1]\n",
|
|
978
|
+
")"
|
|
974
979
|
]
|
|
975
980
|
},
|
|
976
981
|
{
|
|
@@ -1010,18 +1015,20 @@
|
|
|
1010
1015
|
],
|
|
1011
1016
|
"source": [
|
|
1012
1017
|
"for name, estimator, score in (\n",
|
|
1013
|
-
"
|
|
1014
|
-
"
|
|
1018
|
+
" ('dilation perceptron', perceptron_max, score_test_max),\n",
|
|
1019
|
+
" ('erosion perceptron', perceptron_min, score_test_min),\n",
|
|
1015
1020
|
"):\n",
|
|
1016
1021
|
" # draw the decision boundary\n",
|
|
1017
1022
|
" disp = DecisionBoundaryDisplay.from_estimator(\n",
|
|
1018
|
-
" estimator
|
|
1023
|
+
" estimator,\n",
|
|
1024
|
+
" X_test,\n",
|
|
1025
|
+
" response_method='decision_function',\n",
|
|
1019
1026
|
" grid_resolution=200,\n",
|
|
1020
1027
|
" plot_method='contour',\n",
|
|
1021
1028
|
" levels=[0],\n",
|
|
1022
|
-
" colors='black'
|
|
1029
|
+
" colors='black',\n",
|
|
1023
1030
|
" )\n",
|
|
1024
|
-
"
|
|
1031
|
+
"\n",
|
|
1025
1032
|
" # add the points\n",
|
|
1026
1033
|
" ax = disp.ax_\n",
|
|
1027
1034
|
" ax.scatter(*X_test.T, color=y_test)\n",
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"X, y = fetch_openml('diabetes', version=1, return_X_y=True, as_frame=False)\n",
|
|
61
61
|
"\n",
|
|
62
62
|
"X_train, X_test, y_train, y_test = train_test_split(\n",
|
|
63
|
-
" X, y, test_size
|
|
63
|
+
" X, y, test_size=0.3, random_state=random_state\n",
|
|
64
|
+
")"
|
|
64
65
|
]
|
|
65
66
|
},
|
|
66
67
|
{
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sklearn-morpho
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
License-File: LICENSE
|
|
5
|
+
Requires-Dist: cvxpy<2.0.0,>=1.9.0
|
|
6
|
+
Requires-Dist: numpy>=2.0.0
|
|
7
|
+
Requires-Dist: scikit-learn<2.0.0,>=1.8.0
|
|
8
|
+
Provides-Extra: dccp
|
|
9
|
+
Requires-Dist: dccp<2.0.0,>=1.1.1; extra == 'dccp'
|
|
10
|
+
Requires-Dist: pytest<10.0.0,>=9.0.0; extra == 'dccp'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pkgname=python-sklearn-morpho
|
|
2
2
|
_name=sklearn_morpho
|
|
3
|
-
pkgver=0.2.
|
|
3
|
+
pkgver=0.2.2
|
|
4
4
|
pkgrel=1
|
|
5
5
|
pkgdesc="Scikit-learn estimator toolbox for morphological perceptrons"
|
|
6
6
|
arch=("any")
|
|
@@ -9,7 +9,7 @@ license=("MIT")
|
|
|
9
9
|
depends=("python" "python-cvxpy" "python-pytest" "python-scikit-learn")
|
|
10
10
|
makedepends=("python-build" "python-installer" "python-wheel" "python-hatchling")
|
|
11
11
|
source=("https://files.pythonhosted.org/packages/source/${_name::1}/${_name}/${_name}-${pkgver}.tar.gz")
|
|
12
|
-
sha256sums=('
|
|
12
|
+
sha256sums=('aaf94e85e2f546928ace464bf776c08681cd5349a48012076c3c66a9e801961f')
|
|
13
13
|
|
|
14
14
|
build() {
|
|
15
15
|
cd "${_name}-${pkgver}"
|
|
@@ -6,37 +6,18 @@ scikit-learn estimator toolbox for morphological perceptrons.
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
> [!WARNING]
|
|
10
|
-
> Some features of this repository use the `dccp` module.
|
|
11
|
-
>
|
|
12
|
-
> As of writing this (June 2026) the PyPI package has not been updated since
|
|
13
|
-
> 2025 ([this commit](https://github.com/cvxgrp/dccp/commit/4322809)).
|
|
14
|
-
> This means that currently the `dccp` module uses `cvxpy.reshape` with soon to
|
|
15
|
-
> be outdated parameters.
|
|
16
|
-
>
|
|
17
|
-
> As of writing this, when using this solver warnings from cvxpy are displayed,
|
|
18
|
-
> however these warnings are projected to be removed in future `cvxpy` versions,
|
|
19
|
-
> which may create silent errors during training.
|
|
20
|
-
>
|
|
21
|
-
> I will leave this notice here until the `dccp` library's maintainer team
|
|
22
|
-
> decides to update the package.
|
|
23
|
-
> Please let me know if that is the case and I did not remove the notice.
|
|
24
|
-
> See also: [issue](https://github.com/cvxgrp/dccp/issues/127)
|
|
25
|
-
>
|
|
26
|
-
> If you wish to use the solver in its current state without the warnings, you
|
|
27
|
-
> can manually clone the dccp repo into the hatch venv.
|
|
28
|
-
|
|
29
9
|
Current features:
|
|
30
10
|
|
|
31
11
|
- Scikit-learn estimators:
|
|
32
12
|
- Linear Dilation-Erosion Perceptron (l-DEP)
|
|
33
13
|
- Reduced Dilation-Erosion Perceptron (r-DEP)
|
|
34
|
-
- Simple
|
|
14
|
+
- Simple Dilation and Erosion Morphological Perceptrons
|
|
35
15
|
- Modular wrapper for DCCP optimization tasks with `cvxpy`
|
|
36
16
|
|
|
37
17
|
File tree:
|
|
38
18
|
|
|
39
|
-
- `src/sklearn_morpho`: contains the source code and a testsuite in
|
|
19
|
+
- `src/sklearn_morpho`: contains the source code and a testsuite in its `tests`
|
|
20
|
+
subdirectory.
|
|
40
21
|
- `testing`: standalone files that use this library, may contain tests but they
|
|
41
22
|
are not designed to be run as a CI testsuite for example.
|
|
42
23
|
- `MREs`: standalone jupyter notebooks to showcase some of this library's
|
|
@@ -45,7 +26,7 @@ File tree:
|
|
|
45
26
|
## Getting started
|
|
46
27
|
|
|
47
28
|
Take a look at the Jupyter code examples in the
|
|
48
|
-
[
|
|
29
|
+
[MREs](https://github.com/d-002/sklearn-morpho/tree/master/MREs) directory.
|
|
49
30
|
|
|
50
31
|
## Running the project
|
|
51
32
|
|
|
@@ -64,7 +45,8 @@ files to avoid training the estimators every time one wants to view the results.
|
|
|
64
45
|
|
|
65
46
|
The package is available in the
|
|
66
47
|
[Arch User Repository](https://aur.archlinux.org) as `python-sklearn-morpho`, as
|
|
67
|
-
are all its dependencies except
|
|
48
|
+
are all its dependencies not already in the official packages, except for
|
|
49
|
+
`dccp`.
|
|
68
50
|
|
|
69
51
|
Since the latter is an optional dependency, you can either:
|
|
70
52
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# General
|
|
2
|
+
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["hatchling"]
|
|
5
|
+
build-backend = "hatchling.build"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "sklearn-morpho"
|
|
9
|
+
version = "0.2.2"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cvxpy>=1.9.0,<2.0.0",
|
|
12
|
+
"numpy>=2.0.0",
|
|
13
|
+
"scikit-learn>=1.8.0,<2.0.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dccp = [
|
|
18
|
+
"dccp>=1.1.1,<2.0.0",
|
|
19
|
+
"pytest>=9.0.0,<10.0.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.pytest.ini_options]
|
|
23
|
+
testpaths = ["tests"]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.envs.default]
|
|
26
|
+
python = "3.13"
|
|
27
|
+
path = ".venv" # for LSPs
|
|
28
|
+
dependencies = [
|
|
29
|
+
"dccp>=1.1.1,<2.0.0",
|
|
30
|
+
"jupyterlab>=4.5.0,<5.0.0", # for coding the MREs
|
|
31
|
+
"matplotlib>=3.10.0,<4.0.0", # for files in testing
|
|
32
|
+
"pandas>=3.0.0", # for scikit-learn's check_estimator and openml datasets
|
|
33
|
+
"pyqt6>=6.11.0,<7.0.0", # for matplotlib display on some devices
|
|
34
|
+
"pytest>=9.0.0,<10.0.0",
|
|
35
|
+
"pytest-cov>=7.1.0,<8.0.0",
|
|
36
|
+
"ruff>=0.15.0,<1.0.0", # linter
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# Ruff
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
indent-width = 4
|
|
42
|
+
line-length = 80
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff.format]
|
|
48
|
+
quote-style = "single"
|
|
49
|
+
indent-style = "space"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from typing import Literal, cast
|
|
3
5
|
|
|
@@ -7,16 +9,17 @@ from sklearn.utils import check_random_state
|
|
|
7
9
|
from sklearn.utils.validation import validate_data, check_is_fitted
|
|
8
10
|
from sklearn.utils.multiclass import unique_labels
|
|
9
11
|
|
|
10
|
-
from ..
|
|
12
|
+
from ..training.dccp_ldep import LDEPDccpTrainer
|
|
11
13
|
from ..stopping import (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
StoppingMethod,
|
|
15
|
+
CostStoppingMethod,
|
|
16
|
+
EarlyStoppingMethod,
|
|
17
|
+
EpochStoppingMethod,
|
|
18
|
+
TrainStopStoppingMethod,
|
|
17
19
|
)
|
|
18
20
|
from ..weighting import SampleWeighting, NoneSampleWeighting
|
|
19
21
|
|
|
22
|
+
|
|
20
23
|
class LDEP(ClassifierMixin, BaseEstimator):
|
|
21
24
|
"""
|
|
22
25
|
Scikit-learn estimator wrapper around a l-DEP (linear Dilation-Erosion
|
|
@@ -35,12 +38,18 @@ class LDEP(ClassifierMixin, BaseEstimator):
|
|
|
35
38
|
times, but will allow the decision boundary to be more complex.
|
|
36
39
|
"""
|
|
37
40
|
|
|
38
|
-
def __init__(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
latent_dims: tuple[int, int] = (10, 10),
|
|
44
|
+
margin=1.0,
|
|
45
|
+
penalty=0.0,
|
|
46
|
+
validation_ratio=0.3,
|
|
47
|
+
weighting_method: SampleWeighting | None = None,
|
|
48
|
+
stopping_methods: list[StoppingMethod] | None = None,
|
|
49
|
+
use_dccp_library: bool = False,
|
|
50
|
+
verbose: Literal[0, 1, 2] = 0,
|
|
51
|
+
random_state: np.random.RandomState | None = None,
|
|
52
|
+
) -> None:
|
|
44
53
|
"""
|
|
45
54
|
Initialize the classifier, see class help for more.
|
|
46
55
|
|
|
@@ -70,9 +79,9 @@ class LDEP(ClassifierMixin, BaseEstimator):
|
|
|
70
79
|
to the epoch with the best validation cost.
|
|
71
80
|
If left to None, will use:
|
|
72
81
|
[
|
|
73
|
-
CostStoppingMethod(
|
|
74
|
-
EarlyStoppingMethod(
|
|
75
|
-
EpochStoppingMethod(
|
|
82
|
+
CostStoppingMethod(),
|
|
83
|
+
EarlyStoppingMethod(),
|
|
84
|
+
EpochStoppingMethod(),
|
|
76
85
|
TrainStopStoppingMethod(),
|
|
77
86
|
]
|
|
78
87
|
Ignored when using the dccp library solver.
|
|
@@ -110,7 +119,7 @@ class LDEP(ClassifierMixin, BaseEstimator):
|
|
|
110
119
|
|
|
111
120
|
# input data validation
|
|
112
121
|
random_state = check_random_state(self.random_state)
|
|
113
|
-
X, y = validate_data(self, X, y)
|
|
122
|
+
X, y = validate_data(self, X, y) # type: ignore
|
|
114
123
|
self.scaler_ = StandardScaler()
|
|
115
124
|
X_scaled = self.scaler_.fit_transform(X)
|
|
116
125
|
|
|
@@ -121,9 +130,9 @@ class LDEP(ClassifierMixin, BaseEstimator):
|
|
|
121
130
|
weighting_method = self.weighting_method
|
|
122
131
|
if self.stopping_methods is None:
|
|
123
132
|
stopping_methods = [
|
|
124
|
-
CostStoppingMethod(
|
|
125
|
-
EarlyStoppingMethod(
|
|
126
|
-
EpochStoppingMethod(
|
|
133
|
+
CostStoppingMethod(),
|
|
134
|
+
EarlyStoppingMethod(),
|
|
135
|
+
EpochStoppingMethod(),
|
|
127
136
|
TrainStopStoppingMethod(),
|
|
128
137
|
]
|
|
129
138
|
else:
|
|
@@ -133,18 +142,27 @@ class LDEP(ClassifierMixin, BaseEstimator):
|
|
|
133
142
|
# the classes are persisted inside the object for use in predict
|
|
134
143
|
self.classes_ = unique_labels(y)
|
|
135
144
|
classes_list = list(self.classes_)
|
|
136
|
-
y_integers = np.array(
|
|
137
|
-
|
|
145
|
+
y_integers = np.array(
|
|
146
|
+
[classes_list.index(c) for c in y], dtype=np.int32
|
|
147
|
+
)
|
|
138
148
|
|
|
139
149
|
if len(classes_list) != 2:
|
|
140
|
-
raise ValueError(
|
|
141
|
-
|
|
150
|
+
raise ValueError(
|
|
151
|
+
'Only binary classification is supported but '
|
|
152
|
+
f'got {len(classes_list)} class(es).'
|
|
153
|
+
)
|
|
142
154
|
|
|
143
155
|
# create and train perceptrons
|
|
144
156
|
trainer = LDEPDccpTrainer(
|
|
145
|
-
self.latent_dims,
|
|
146
|
-
|
|
147
|
-
self.
|
|
157
|
+
self.latent_dims,
|
|
158
|
+
self.margin,
|
|
159
|
+
self.penalty,
|
|
160
|
+
self.validation_ratio,
|
|
161
|
+
weighting_method,
|
|
162
|
+
stopping_methods,
|
|
163
|
+
self.use_dccp_library,
|
|
164
|
+
self.verbose,
|
|
165
|
+
random_state,
|
|
148
166
|
)
|
|
149
167
|
|
|
150
168
|
trainer.train(X_scaled, y_integers)
|
|
@@ -158,13 +176,15 @@ class LDEP(ClassifierMixin, BaseEstimator):
|
|
|
158
176
|
|
|
159
177
|
def decision_function(self, X: np.ndarray) -> np.ndarray:
|
|
160
178
|
check_is_fitted(self)
|
|
161
|
-
X = validate_data(self, X, reset=False)
|
|
179
|
+
X = validate_data(self, X, reset=False) # type: ignore
|
|
162
180
|
X_scaled = cast(np.ndarray, self.scaler_.transform(X))
|
|
163
181
|
|
|
164
|
-
expr_max = np.max(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
expr_max = np.max(
|
|
183
|
+
self.max_perceptron_ + X_scaled @ self.max_matrix_.T, axis=1
|
|
184
|
+
)
|
|
185
|
+
expr_min = np.min(
|
|
186
|
+
self.min_perceptron_ + X_scaled @ self.min_matrix_.T, axis=1
|
|
187
|
+
)
|
|
168
188
|
return expr_max * self.lambda_ + expr_min * (1 - self.lambda_)
|
|
169
189
|
|
|
170
190
|
def predict(self, X: np.ndarray) -> np.ndarray:
|