sklearn-morpho 0.2.2__tar.gz → 0.2.3__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.2 → sklearn_morpho-0.2.3}/.SRCINFO +3 -3
- sklearn_morpho-0.2.3/.gitattributes +1 -0
- sklearn_morpho-0.2.3/.github/workflows/code_quality.yml +47 -0
- sklearn_morpho-0.2.2/.github/workflows/pypi-publish.yml → sklearn_morpho-0.2.3/.github/workflows/pypi_publish.yml +2 -1
- sklearn_morpho-0.2.3/.github/workflows/tests.yml +68 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/.gitignore +1 -1
- sklearn_morpho-0.2.3/PKG-INFO +30 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/PKGBUILD +3 -3
- sklearn_morpho-0.2.3/README-PyPI.md +14 -0
- sklearn_morpho-0.2.3/README.md +101 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/pyproject.toml +36 -2
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/testing/compare_estimators.py +13 -14
- sklearn_morpho-0.2.3/testing/compare_estimators_multiprocess.py +368 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/testing/compare_estimators_show.py +24 -13
- sklearn_morpho-0.2.2/.github/workflows/ci.yml +0 -90
- sklearn_morpho-0.2.2/PKG-INFO +0 -10
- sklearn_morpho-0.2.2/README.md +0 -56
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/.gitlab-ci.yml +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/.qbuild/scripts/coverage +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/.qbuild/scripts/main +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/.qbuild/scripts/tests +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/LICENSE +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/MREs/l-DEP.ipynb +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/MREs/r-DEP.ipynb +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/MREs/simple_perceptron.ipynb +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/MREs/train_multiclass.ipynb +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/check_todos.sh +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/pkgbuild_helper.sh +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/pyrightconfig.json +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/__init__.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/classifiers/__init__.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/classifiers/ldep.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/classifiers/rdep.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/classifiers/simple_perceptron.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/stopping/__init__.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/stopping/stopping_base.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/stopping/stopping_cost.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/stopping/stopping_early.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/stopping/stopping_epoch.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/stopping/stopping_train_stop.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/training/__init__.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/training/dccp_ldep.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/training/dccp_rdep.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/training/dccp_simple_perceptron.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/training/dccp_wrapper.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/weighting/__init__.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/weighting/weighting_base.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/weighting/weighting_dist.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/weighting/weighting_none.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/testing/display_boundary.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/integration/sklearn_test.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/stopping/cost_test.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/stopping/early_test.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/stopping/epoch_test.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/stopping/train_stop_test.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/training/friendly_dataset.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/training/test_dccp_wrapper.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/training/test_ldep.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/training/test_rdep.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/training/test_simple_perceptron.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/weighting/dist_test.py +0 -0
- {sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/tests/weighting/none_test.py +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.2
|
|
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.2.tar.gz
|
|
17
|
+
sha256sums = 4dc5a155a32332c4da33f375c7e4e83af15fa2a0ea8bb68f1843e74d0b47bce9
|
|
18
18
|
|
|
19
19
|
pkgname = python-sklearn-morpho
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.ipynb linguist-vendored
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Code quality
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "master" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "master" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
todo:
|
|
11
|
+
name: Check for TODOs in code
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Scan for TODOs in the code
|
|
19
|
+
run: ./check_todos.sh
|
|
20
|
+
|
|
21
|
+
lint:
|
|
22
|
+
name: Check for ${{ matrix.name }} errors
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
strategy:
|
|
25
|
+
fail-fast: false
|
|
26
|
+
matrix:
|
|
27
|
+
include:
|
|
28
|
+
- name: "linting"
|
|
29
|
+
command: "python -m ruff check"
|
|
30
|
+
- name: "coding style"
|
|
31
|
+
command: "[ -z \"$(python -m ruff format | grep reformatted)\" ]"
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- name: Checkout repository
|
|
35
|
+
uses: actions/checkout@v4
|
|
36
|
+
|
|
37
|
+
- name: Set up Python
|
|
38
|
+
uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.13"
|
|
41
|
+
cache: "pip"
|
|
42
|
+
|
|
43
|
+
- name: Install linter
|
|
44
|
+
run: pip install ruff
|
|
45
|
+
|
|
46
|
+
- name: Run linter for coding style errors
|
|
47
|
+
run: ${{ matrix.command }}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Testsuite
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "master" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "master" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ "ubuntu-latest" ]
|
|
17
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
18
|
+
# also test on other OSes, but not for all versions to save minutes
|
|
19
|
+
include:
|
|
20
|
+
- os: windows-latest
|
|
21
|
+
python-version: "3.13"
|
|
22
|
+
- os: macos-latest
|
|
23
|
+
python-version: "3.13"
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout repository
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
cache: "pip"
|
|
34
|
+
|
|
35
|
+
- name: Install Hatch
|
|
36
|
+
run: pip install hatch
|
|
37
|
+
|
|
38
|
+
- name: Run tests with Hatch
|
|
39
|
+
run: |
|
|
40
|
+
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
|
|
41
|
+
hatch run test.py${{ matrix.python-version }}:pytest
|
|
42
|
+
else
|
|
43
|
+
hatch run pytest
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
coverage:
|
|
47
|
+
name: Coverage test (ignore failed tests)
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
|
|
50
|
+
steps:
|
|
51
|
+
- name: Checkout repository
|
|
52
|
+
uses: actions/checkout@v4
|
|
53
|
+
|
|
54
|
+
- name: Set up Python
|
|
55
|
+
uses: actions/setup-python@v5
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.13"
|
|
58
|
+
cache: "pip"
|
|
59
|
+
|
|
60
|
+
- name: Install Hatch
|
|
61
|
+
run: pip install hatch
|
|
62
|
+
|
|
63
|
+
- name: Install coverage utilities
|
|
64
|
+
run: pip install coverage
|
|
65
|
+
|
|
66
|
+
- name: Check coverage
|
|
67
|
+
run: |
|
|
68
|
+
hatch run pytest --cov --cov-fail-under=90
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sklearn-morpho
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
License-File: LICENSE
|
|
5
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: cvxpy<2.0.0,>=1.9.0
|
|
11
|
+
Requires-Dist: numpy>=2.0.0
|
|
12
|
+
Requires-Dist: scikit-learn<2.0.0,>=1.8.0
|
|
13
|
+
Provides-Extra: dccp
|
|
14
|
+
Requires-Dist: dccp<2.0.0,>=1.1.1; extra == 'dccp'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# sklearn-morpho
|
|
18
|
+
|
|
19
|
+
Scikit-learn classifiers toolbox for morphological perceptrons.
|
|
20
|
+
|
|
21
|
+
## Current features
|
|
22
|
+
|
|
23
|
+
- Scikit-learn classifiers:
|
|
24
|
+
- Linear Dilation-Erosion Perceptron (l-DEP)
|
|
25
|
+
- Reduced Dilation-Erosion Perceptron (r-DEP)
|
|
26
|
+
- Simple Dilation and Erosion Morphological Perceptrons
|
|
27
|
+
- Modular wrapper for DCCP optimization tasks with `cvxpy`
|
|
28
|
+
|
|
29
|
+
More information and explanations
|
|
30
|
+
[here](https://github.com/d-002/sklearn-morpho).
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
pkgname=python-sklearn-morpho
|
|
2
2
|
_name=sklearn_morpho
|
|
3
|
-
pkgver=0.2.
|
|
3
|
+
pkgver=0.2.3
|
|
4
4
|
pkgrel=1
|
|
5
5
|
pkgdesc="Scikit-learn estimator toolbox for morphological perceptrons"
|
|
6
6
|
arch=("any")
|
|
7
7
|
url="https://github.com/d-002/sklearn-morpho"
|
|
8
8
|
license=("MIT")
|
|
9
|
-
depends=("python" "python-cvxpy" "python-
|
|
9
|
+
depends=("python" "python-cvxpy" "python-numpy" "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=('4dc5a155a32332c4da33f375c7e4e83af15fa2a0ea8bb68f1843e74d0b47bce9')
|
|
13
13
|
|
|
14
14
|
build() {
|
|
15
15
|
cd "${_name}-${pkgver}"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# sklearn-morpho
|
|
2
|
+
|
|
3
|
+
Scikit-learn classifiers toolbox for morphological perceptrons.
|
|
4
|
+
|
|
5
|
+
## Current features
|
|
6
|
+
|
|
7
|
+
- Scikit-learn classifiers:
|
|
8
|
+
- Linear Dilation-Erosion Perceptron (l-DEP)
|
|
9
|
+
- Reduced Dilation-Erosion Perceptron (r-DEP)
|
|
10
|
+
- Simple Dilation and Erosion Morphological Perceptrons
|
|
11
|
+
- Modular wrapper for DCCP optimization tasks with `cvxpy`
|
|
12
|
+
|
|
13
|
+
More information and explanations
|
|
14
|
+
[here](https://github.com/d-002/sklearn-morpho).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# sklearn-morpho
|
|
2
|
+
|
|
3
|
+
Scikit-learn classifiers toolbox for [morphological perceptrons](#what-are-morphological-perceptrons).
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Current features
|
|
12
|
+
|
|
13
|
+
- Scikit-learn classifiers:
|
|
14
|
+
- Linear Dilation-Erosion Perceptron (l-DEP)
|
|
15
|
+
- Reduced Dilation-Erosion Perceptron (r-DEP)
|
|
16
|
+
- Simple Dilation and Erosion Morphological Perceptrons
|
|
17
|
+
- Modular wrapper for DCCP optimization tasks with `cvxpy`
|
|
18
|
+
|
|
19
|
+
File tree:
|
|
20
|
+
|
|
21
|
+
- `src/sklearn_morpho`: contains the source code and a testsuite in its `tests`
|
|
22
|
+
subdirectory.
|
|
23
|
+
- `tests`: pytest testsuite.
|
|
24
|
+
- `testing`: standalone files that use this library.
|
|
25
|
+
They are used internally for testing, but they are not tests nor part of the
|
|
26
|
+
library.
|
|
27
|
+
- `MREs`: standalone jupyter notebooks to showcase some of this library's
|
|
28
|
+
features.
|
|
29
|
+
|
|
30
|
+
## Getting started
|
|
31
|
+
|
|
32
|
+
Take a look at the Jupyter code examples in the
|
|
33
|
+
[MREs](https://github.com/d-002/sklearn-morpho/tree/master/MREs) directory.
|
|
34
|
+
|
|
35
|
+
## Running the project
|
|
36
|
+
|
|
37
|
+
Install Python 3 and hatch.
|
|
38
|
+
Then run one of these commands:
|
|
39
|
+
|
|
40
|
+
- `hatch run jupyter lab` to run the Jupyter notebooks
|
|
41
|
+
- `hatch run pytest` for tests
|
|
42
|
+
- `hatch shell` to run testing files like `testing/display_boundary.py` in the
|
|
43
|
+
right environment.
|
|
44
|
+
|
|
45
|
+
Special note for the estimators comparison testing files: they are split in two
|
|
46
|
+
files to avoid training the estimators every time one wants to view the results.
|
|
47
|
+
|
|
48
|
+
## Contributing
|
|
49
|
+
|
|
50
|
+
As the project is open source, any help is greatly appreciated!
|
|
51
|
+
|
|
52
|
+
To keep code clean, this repository uses a CI/CD pipeline with tests and an
|
|
53
|
+
enforced coding style, which can all be found in the project's configuration
|
|
54
|
+
files.
|
|
55
|
+
|
|
56
|
+
Please adhere to these rules when contributing.
|
|
57
|
+
|
|
58
|
+
Regarding AI pull requests, they are generally discouraged as you should be able
|
|
59
|
+
to understand and help maintaining any features you add to this repo.
|
|
60
|
+
|
|
61
|
+
## For Arch users
|
|
62
|
+
|
|
63
|
+
The Python package is available in the
|
|
64
|
+
[Arch User Repository](https://aur.archlinux.org) as `python-sklearn-morpho`, as
|
|
65
|
+
are all its dependencies not already in the official packages, except for
|
|
66
|
+
`dccp`.
|
|
67
|
+
|
|
68
|
+
Since the latter is an optional dependency, you can either:
|
|
69
|
+
|
|
70
|
+
- Download `dccp` through pip or use `pip install sklearn-morpho[dccp]` in a
|
|
71
|
+
Python virtual environment.
|
|
72
|
+
- Download `python-sklearn-morpho` from the AUR and use
|
|
73
|
+
`pip install dccp --user`.
|
|
74
|
+
|
|
75
|
+
## What are morphological perceptrons?
|
|
76
|
+
|
|
77
|
+
Perceptrons are the fundamental building blocks of many machine learning
|
|
78
|
+
constructs, namely the well-known neural networks.
|
|
79
|
+
|
|
80
|
+
They fulfill this role because their simple yet effective activation function
|
|
81
|
+
($y = w \cdot x + b$) allows for the emergence of arbitrarily complex behavior,
|
|
82
|
+
for example when such perceptrons are wired together in networks.
|
|
83
|
+
|
|
84
|
+
In the equation above, $w$ and $x$ are row and column matrices respectively,
|
|
85
|
+
meaning their multiplication equates to a set of scalar multiplications,
|
|
86
|
+
followed by a summation of all these multiplication results.
|
|
87
|
+
|
|
88
|
+
"Morphological" perceptrons are similar to the aforedescribed "classical"
|
|
89
|
+
perceptrons, but differ in the fact that they generally do not have a bias $b$,
|
|
90
|
+
and most importantly that the operations performed are different:
|
|
91
|
+
|
|
92
|
+
While, as established above, a classical perceptron computes several
|
|
93
|
+
multiplications followed by a summation, a morphological perceptron performs
|
|
94
|
+
multiple additions, followed by a $\min$ or $\max$.
|
|
95
|
+
|
|
96
|
+
This simple shift into a different semiring creates a more complex decision
|
|
97
|
+
boundary, making these constructs a natural choice for designing binary
|
|
98
|
+
classifiers.
|
|
99
|
+
|
|
100
|
+
This repository explores the behavior of such perceptrons, namely standalone
|
|
101
|
+
ones, but also more complex structures, such as r-DEP and l-DEP.
|
|
@@ -4,24 +4,35 @@
|
|
|
4
4
|
requires = ["hatchling"]
|
|
5
5
|
build-backend = "hatchling.build"
|
|
6
6
|
|
|
7
|
+
# Production
|
|
8
|
+
|
|
7
9
|
[project]
|
|
8
10
|
name = "sklearn-morpho"
|
|
9
|
-
version = "0.2.
|
|
11
|
+
version = "0.2.3"
|
|
12
|
+
requires-python = ">=3.11"
|
|
10
13
|
dependencies = [
|
|
11
14
|
"cvxpy>=1.9.0,<2.0.0",
|
|
12
15
|
"numpy>=2.0.0",
|
|
13
16
|
"scikit-learn>=1.8.0,<2.0.0",
|
|
14
17
|
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
]
|
|
24
|
+
readme = "README-PyPI.md"
|
|
15
25
|
|
|
16
26
|
[project.optional-dependencies]
|
|
17
27
|
dccp = [
|
|
18
28
|
"dccp>=1.1.1,<2.0.0",
|
|
19
|
-
"pytest>=9.0.0,<10.0.0",
|
|
20
29
|
]
|
|
21
30
|
|
|
22
31
|
[tool.pytest.ini_options]
|
|
23
32
|
testpaths = ["tests"]
|
|
24
33
|
|
|
34
|
+
# Dev
|
|
35
|
+
|
|
25
36
|
[tool.hatch.envs.default]
|
|
26
37
|
python = "3.13"
|
|
27
38
|
path = ".venv" # for LSPs
|
|
@@ -36,7 +47,30 @@ dependencies = [
|
|
|
36
47
|
"ruff>=0.15.0,<1.0.0", # linter
|
|
37
48
|
]
|
|
38
49
|
|
|
50
|
+
[tool.hatch.envs.default.env-vars]
|
|
51
|
+
SCIPY_ARRAY_API = "1" # prevent skip warnings in check_estimator
|
|
52
|
+
|
|
53
|
+
# Matrix test
|
|
54
|
+
|
|
55
|
+
[tool.hatch.envs.test]
|
|
56
|
+
template = "test" # break inheritance from default to ignore .venv
|
|
57
|
+
features = [
|
|
58
|
+
"dccp",
|
|
59
|
+
]
|
|
60
|
+
dependencies = [
|
|
61
|
+
"pandas>=3.0.0",
|
|
62
|
+
"pytest>=9.0.0,<10.0.0",
|
|
63
|
+
"pytest-cov>=7.1.0,<8.0.0",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.hatch.envs.test.env-vars]
|
|
67
|
+
SCIPY_ARRAY_API = "1"
|
|
68
|
+
|
|
69
|
+
[[tool.hatch.envs.test.matrix]]
|
|
70
|
+
python = ["3.11", "3.12", "3.13", "3.14"]
|
|
71
|
+
|
|
39
72
|
# Ruff
|
|
73
|
+
|
|
40
74
|
[tool.ruff]
|
|
41
75
|
indent-width = 4
|
|
42
76
|
line-length = 80
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Create and train different estimators and display their respective scores
|
|
3
3
|
averaged from multiple datasets.
|
|
4
|
+
|
|
4
5
|
Estimators selection inspired by arxiv/2011.06512
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
import json
|
|
8
9
|
import signal
|
|
10
|
+
import warnings
|
|
9
11
|
import numpy as np
|
|
10
12
|
from time import time
|
|
11
13
|
from scipy.sparse._csr import csr_matrix
|
|
@@ -24,8 +26,8 @@ from sklearn.pipeline import make_pipeline
|
|
|
24
26
|
from sklearn.neural_network import MLPClassifier
|
|
25
27
|
from sklearn.multiclass import OneVsRestClassifier
|
|
26
28
|
|
|
27
|
-
FILE = '
|
|
28
|
-
|
|
29
|
+
FILE = 'comparison.json'
|
|
30
|
+
n_folds = 5
|
|
29
31
|
timeout = 60
|
|
30
32
|
|
|
31
33
|
# set up estimators and datasets
|
|
@@ -34,11 +36,11 @@ print(f'Random state: {random_state}')
|
|
|
34
36
|
|
|
35
37
|
estimators = {
|
|
36
38
|
'l-DEP': OneVsRestClassifier(LDEP(random_state=random_state)),
|
|
37
|
-
'DCCP
|
|
39
|
+
'DCCP l_DEP': OneVsRestClassifier(
|
|
38
40
|
LDEP(use_dccp_library=True, random_state=random_state)
|
|
39
41
|
),
|
|
40
42
|
'r-DEP': OneVsRestClassifier(RDEP(random_state=random_state)),
|
|
41
|
-
'DCCP
|
|
43
|
+
'DCCP r_DEP': OneVsRestClassifier(
|
|
42
44
|
RDEP(use_dccp_library=True, random_state=random_state)
|
|
43
45
|
),
|
|
44
46
|
'Morpho_max': OneVsRestClassifier(
|
|
@@ -133,13 +135,13 @@ signal.signal(signal.SIGALRM, timeout_handler)
|
|
|
133
135
|
def save_data():
|
|
134
136
|
with open(FILE, 'w') as f:
|
|
135
137
|
json.dump(
|
|
136
|
-
{'
|
|
138
|
+
{'n_folds': n_folds, 'scores': scores, 'times': times},
|
|
137
139
|
f,
|
|
138
140
|
indent=2,
|
|
139
141
|
)
|
|
140
142
|
|
|
141
143
|
|
|
142
|
-
skf = StratifiedKFold(n_splits=
|
|
144
|
+
skf = StratifiedKFold(n_splits=n_folds)
|
|
143
145
|
for dataset_name in datasets_names:
|
|
144
146
|
print(f'Training with dataset "{dataset_name}"...')
|
|
145
147
|
scores[dataset_name] = {}
|
|
@@ -164,22 +166,19 @@ for dataset_name in datasets_names:
|
|
|
164
166
|
estimator,
|
|
165
167
|
)
|
|
166
168
|
|
|
167
|
-
for
|
|
168
|
-
X_train, X_test
|
|
169
|
-
|
|
170
|
-
)
|
|
169
|
+
for i_train, i_test in skf.split(X, y):
|
|
170
|
+
X_train, X_test = X[i_train], X[i_test]
|
|
171
|
+
y_train, y_test = y[i_train], y[i_test]
|
|
171
172
|
|
|
172
173
|
signal.alarm(timeout)
|
|
173
174
|
t0 = time()
|
|
174
175
|
try:
|
|
175
176
|
estimator.fit(X_train, y_train)
|
|
176
177
|
except TimeoutException:
|
|
177
|
-
|
|
178
|
-
f' Warning: {estimator_name} timed out after {timeout}s.'
|
|
179
|
-
)
|
|
178
|
+
warnings.warn(f'{estimator_name} timed out after {timeout}s.')
|
|
180
179
|
scores[dataset_name][estimator_name].append(0)
|
|
181
180
|
times[dataset_name][estimator_name].append(timeout)
|
|
182
|
-
|
|
181
|
+
break
|
|
183
182
|
|
|
184
183
|
t1 = time()
|
|
185
184
|
signal.alarm(0)
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Create and train different estimators and display their respective scores
|
|
3
|
+
averaged from multiple datasets.
|
|
4
|
+
Train perceptrons in parallel using multiple processes, meaning timing
|
|
5
|
+
information (as well as scores depending on internal solver timeouts) may be
|
|
6
|
+
inaccurate, while training will take less time.
|
|
7
|
+
|
|
8
|
+
Because of the multiprocessing nature of this, timeouts are not asynchronous and
|
|
9
|
+
long-running cpp code can make processing take longer than is theoretically
|
|
10
|
+
permitted by the timeout.
|
|
11
|
+
|
|
12
|
+
Estimators selection inspired by arxiv/2011.06512
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import json
|
|
18
|
+
import time
|
|
19
|
+
import signal
|
|
20
|
+
import warnings
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from time import time, sleep
|
|
24
|
+
from threading import Thread
|
|
25
|
+
from multiprocessing import Manager
|
|
26
|
+
from joblib import Parallel, delayed
|
|
27
|
+
from scipy.sparse._csr import csr_matrix
|
|
28
|
+
|
|
29
|
+
from sklearn.metrics import f1_score
|
|
30
|
+
from sklearn.datasets import load_breast_cancer, fetch_openml
|
|
31
|
+
from sklearn.preprocessing import OrdinalEncoder
|
|
32
|
+
from sklearn.model_selection import train_test_split, StratifiedKFold
|
|
33
|
+
|
|
34
|
+
from sklearn_morpho import LDEP, RDEP, MorphoPerceptron
|
|
35
|
+
from sklearn.svm import LinearSVC, SVC
|
|
36
|
+
from sklearn.impute import SimpleImputer
|
|
37
|
+
from sklearn.pipeline import make_pipeline
|
|
38
|
+
from sklearn.neural_network import MLPClassifier
|
|
39
|
+
from sklearn.multiclass import OneVsRestClassifier
|
|
40
|
+
|
|
41
|
+
## Timeouts handling
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TimeoutException(Exception):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def timeout_handler(sugnum, frame):
|
|
49
|
+
raise TimeoutException()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
FILE = 'comparison.json'
|
|
55
|
+
# ignore warnings for cleaner logs
|
|
56
|
+
ignore_warnings = True
|
|
57
|
+
n_folds = 5
|
|
58
|
+
max_jobs = 15
|
|
59
|
+
random_state = np.random.RandomState()
|
|
60
|
+
LINE_SIZE = 100
|
|
61
|
+
timeout = 60
|
|
62
|
+
|
|
63
|
+
if ignore_warnings:
|
|
64
|
+
warnings.filterwarnings('ignore')
|
|
65
|
+
|
|
66
|
+
print(f'Random state: {random_state}')
|
|
67
|
+
print(f'Comparison data will be outputted to: "{FILE}"')
|
|
68
|
+
|
|
69
|
+
## Set up datasets
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_clean_openml(name: str, **kwargs) -> tuple[np.ndarray, np.ndarray]:
|
|
73
|
+
kwargs.setdefault('as_frame', False)
|
|
74
|
+
kwargs.setdefault('version', 1)
|
|
75
|
+
X, y = fetch_openml(name, return_X_y=True, **kwargs)
|
|
76
|
+
|
|
77
|
+
# make sure X and y are not sparse
|
|
78
|
+
if isinstance(X, csr_matrix):
|
|
79
|
+
X = X.toarray()
|
|
80
|
+
if isinstance(y, csr_matrix):
|
|
81
|
+
y = y.toarray()
|
|
82
|
+
|
|
83
|
+
# make sure X contains only numbers, not yes/no like in australian
|
|
84
|
+
oe = OrdinalEncoder()
|
|
85
|
+
X = oe.fit_transform(X)
|
|
86
|
+
|
|
87
|
+
return X, y
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
skf = StratifiedKFold(n_splits=n_folds)
|
|
91
|
+
datasets_names = [
|
|
92
|
+
'acute-inflammations',
|
|
93
|
+
'australian',
|
|
94
|
+
'banana',
|
|
95
|
+
'banknote-authentication',
|
|
96
|
+
'blood-transfusion-service-center',
|
|
97
|
+
'breast-cancer',
|
|
98
|
+
'colic',
|
|
99
|
+
'credit-approval',
|
|
100
|
+
'credit-g',
|
|
101
|
+
'cylinder-bands',
|
|
102
|
+
'diabetes',
|
|
103
|
+
'eeg-eye-state',
|
|
104
|
+
'haberman',
|
|
105
|
+
'hill-valley',
|
|
106
|
+
'ilpd',
|
|
107
|
+
'ionosphere',
|
|
108
|
+
'mofn-3-7-10',
|
|
109
|
+
'monks-problems-2',
|
|
110
|
+
'mushroom',
|
|
111
|
+
'phoneme',
|
|
112
|
+
'PhishingWebsites',
|
|
113
|
+
'sick',
|
|
114
|
+
'sonar',
|
|
115
|
+
'spambase',
|
|
116
|
+
'steel-plates-fault',
|
|
117
|
+
'thoracic-surgery',
|
|
118
|
+
'tic-tac-toe',
|
|
119
|
+
'titanic',
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# datasets not included compared to arxiv/2011.06512:
|
|
123
|
+
# - chess (nonexistent)
|
|
124
|
+
# - internet-advertisements (internally referring to a nonexistent dataset)
|
|
125
|
+
|
|
126
|
+
datasets_options = {
|
|
127
|
+
'australian': {'version': 4},
|
|
128
|
+
'Breast_Cancer_Wisconsin': {'as_frame': True},
|
|
129
|
+
'cylinder-bands': {'version': 6},
|
|
130
|
+
'titanic': {'as_frame': True},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
## Create estimators
|
|
134
|
+
|
|
135
|
+
print('Creating estimators...')
|
|
136
|
+
estimators = {
|
|
137
|
+
'l-DEP': OneVsRestClassifier(LDEP(random_state=random_state)),
|
|
138
|
+
#'DCCP l_DEP': OneVsRestClassifier(
|
|
139
|
+
# LDEP(use_dccp_library=True, random_state=random_state)
|
|
140
|
+
# ),
|
|
141
|
+
'r-DEP': OneVsRestClassifier(RDEP(random_state=random_state)),
|
|
142
|
+
#'DCCP r_DEP': OneVsRestClassifier(
|
|
143
|
+
# RDEP(use_dccp_library=True, random_state=random_state)
|
|
144
|
+
# ),
|
|
145
|
+
'Morpho_max': OneVsRestClassifier(
|
|
146
|
+
MorphoPerceptron(kind='max', random_state=random_state)
|
|
147
|
+
),
|
|
148
|
+
'Morpho_min': OneVsRestClassifier(
|
|
149
|
+
MorphoPerceptron(kind='min', random_state=random_state)
|
|
150
|
+
),
|
|
151
|
+
'Linear SVC': LinearSVC(random_state=random_state),
|
|
152
|
+
'RBF SVC': SVC(kernel='rbf', random_state=random_state),
|
|
153
|
+
'MLP': MLPClassifier(max_iter=1000, random_state=random_state),
|
|
154
|
+
'Poly SVC': SVC(kernel='poly', random_state=random_state),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
## Load datasets
|
|
158
|
+
print('\nLoading datasets...')
|
|
159
|
+
|
|
160
|
+
datasets = {}
|
|
161
|
+
for i, dataset_name in enumerate(datasets_names):
|
|
162
|
+
print(
|
|
163
|
+
f'\033[32m=>\033[m Loading {dataset_name:<35} '
|
|
164
|
+
f'({i:02}/{len(datasets_names):02})',
|
|
165
|
+
end='\r',
|
|
166
|
+
)
|
|
167
|
+
match dataset_name:
|
|
168
|
+
case 'breast-cancer':
|
|
169
|
+
datasets[dataset_name] = load_breast_cancer(return_X_y=True)
|
|
170
|
+
case _:
|
|
171
|
+
datasets[dataset_name] = get_clean_openml(
|
|
172
|
+
dataset_name, **datasets_options.get(dataset_name, {})
|
|
173
|
+
)
|
|
174
|
+
print(' ' * LINE_SIZE)
|
|
175
|
+
|
|
176
|
+
## Main logic
|
|
177
|
+
|
|
178
|
+
# centralized data
|
|
179
|
+
manager = Manager()
|
|
180
|
+
scores = manager.dict({d: {e: [] for e in estimators} for d in datasets_names})
|
|
181
|
+
times = manager.dict({d: {e: [] for e in estimators} for d in datasets_names})
|
|
182
|
+
|
|
183
|
+
# the elements are a list of:
|
|
184
|
+
# - start timestamp, or 0 if not started
|
|
185
|
+
# - number from 0 to 1 for progress, or a timestamp if ended
|
|
186
|
+
progress_states = manager.dict(
|
|
187
|
+
{d: {e: [0.0, 0.0] for e in estimators} for d in datasets_names}
|
|
188
|
+
)
|
|
189
|
+
total_jobs = len(datasets) * len(estimators)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def worker(dataset_name: str, estimator_name: str) -> None:
|
|
193
|
+
if ignore_warnings:
|
|
194
|
+
warnings.filterwarnings('ignore')
|
|
195
|
+
|
|
196
|
+
score_arr = []
|
|
197
|
+
time_arr = []
|
|
198
|
+
start = time()
|
|
199
|
+
|
|
200
|
+
# need to reassign because of shared data getters/setters
|
|
201
|
+
temp = progress_states[dataset_name]
|
|
202
|
+
temp[estimator_name][0] = start
|
|
203
|
+
progress_states[dataset_name] = temp
|
|
204
|
+
|
|
205
|
+
X, y = datasets[dataset_name]
|
|
206
|
+
estimator = make_pipeline(
|
|
207
|
+
SimpleImputer(strategy='mean'), # remove NaNs
|
|
208
|
+
estimators[estimator_name],
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
for i, (i_train, i_test) in enumerate(skf.split(X, y)):
|
|
212
|
+
try:
|
|
213
|
+
signal.signal(signal.SIGALRM, timeout_handler)
|
|
214
|
+
signal.alarm(timeout)
|
|
215
|
+
|
|
216
|
+
X_train, X_test = X[i_train], X[i_test]
|
|
217
|
+
y_train, y_test = y[i_train], y[i_test]
|
|
218
|
+
|
|
219
|
+
t0 = time()
|
|
220
|
+
estimator.fit(X_train, y_train)
|
|
221
|
+
t1 = time()
|
|
222
|
+
|
|
223
|
+
score = f1_score(y_test, estimator.predict(X_test), average='micro')
|
|
224
|
+
score_arr.append(score)
|
|
225
|
+
time_arr.append(t1 - t0)
|
|
226
|
+
|
|
227
|
+
temp = progress_states[dataset_name]
|
|
228
|
+
temp[estimator_name][1] = (i + 1) / n_folds
|
|
229
|
+
progress_states[dataset_name] = temp
|
|
230
|
+
except TimeoutException:
|
|
231
|
+
break
|
|
232
|
+
|
|
233
|
+
for data_source, value in (
|
|
234
|
+
(scores, score_arr),
|
|
235
|
+
(times, time_arr),
|
|
236
|
+
(progress_states, [start, time()]),
|
|
237
|
+
):
|
|
238
|
+
temp = data_source[dataset_name]
|
|
239
|
+
temp[estimator_name] = value
|
|
240
|
+
data_source[dataset_name] = temp
|
|
241
|
+
signal.alarm(0)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def save_data():
|
|
245
|
+
with open(FILE, 'w') as f:
|
|
246
|
+
data = {
|
|
247
|
+
'n_folds': n_folds,
|
|
248
|
+
'scores': dict(scores),
|
|
249
|
+
'times': dict(times),
|
|
250
|
+
}
|
|
251
|
+
json.dump(data, f, indent=2)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def progress_bar():
|
|
255
|
+
print('=' * LINE_SIZE)
|
|
256
|
+
print('\n')
|
|
257
|
+
prev_running = 0
|
|
258
|
+
|
|
259
|
+
def format_time(t):
|
|
260
|
+
hm, s = divmod(int(t), 60)
|
|
261
|
+
h, m = divmod(hm, 60)
|
|
262
|
+
return f'\033[34m{h:02}:{m:02}:{s:02}\033[m'
|
|
263
|
+
|
|
264
|
+
def eta_str(progress, time_spent):
|
|
265
|
+
if not progress:
|
|
266
|
+
return f'--:--:--'
|
|
267
|
+
|
|
268
|
+
eta = time_spent / progress * (1 - progress)
|
|
269
|
+
return format_time(eta)
|
|
270
|
+
|
|
271
|
+
def bar(name, time_spent, progress):
|
|
272
|
+
print(
|
|
273
|
+
f'[{"#" * round(progress * 30):-<30}] '
|
|
274
|
+
f'(\033[33m{int(progress * 100):>3}%\033[m) '
|
|
275
|
+
f'{format_time(time_spent)} {name:<50}|'
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
while True:
|
|
279
|
+
running = []
|
|
280
|
+
total_time = 0
|
|
281
|
+
total_progress = 0
|
|
282
|
+
states = []
|
|
283
|
+
for dataset_name, elt in dict(progress_states).items():
|
|
284
|
+
for estimator_name, (start, progress) in elt.items():
|
|
285
|
+
if progress > 1:
|
|
286
|
+
states.append(2)
|
|
287
|
+
total_time += progress - start
|
|
288
|
+
elif start != 0:
|
|
289
|
+
states.append(1)
|
|
290
|
+
total_time += time() - start
|
|
291
|
+
else:
|
|
292
|
+
states.append(0)
|
|
293
|
+
if start != 0:
|
|
294
|
+
if progress < 1:
|
|
295
|
+
running.append(
|
|
296
|
+
(dataset_name, estimator_name, start, progress)
|
|
297
|
+
)
|
|
298
|
+
total_progress += progress
|
|
299
|
+
else:
|
|
300
|
+
total_progress += 1
|
|
301
|
+
|
|
302
|
+
total_done, total_running, total_queued = (
|
|
303
|
+
states.count(k) for k in [2, 1, 0]
|
|
304
|
+
)
|
|
305
|
+
total_progress /= total_jobs
|
|
306
|
+
running.sort(key=lambda x: x[3])
|
|
307
|
+
now_running = len(running)
|
|
308
|
+
|
|
309
|
+
for i in range(prev_running + 1, -1, -1):
|
|
310
|
+
if i < now_running:
|
|
311
|
+
print(end='\033[F')
|
|
312
|
+
else:
|
|
313
|
+
print(' ' * LINE_SIZE, end='\033[F')
|
|
314
|
+
|
|
315
|
+
for dataset_name, estimator_name, start, progress in running:
|
|
316
|
+
bar(f'{dataset_name} - {estimator_name}', time() - start, progress)
|
|
317
|
+
|
|
318
|
+
print('=' * LINE_SIZE)
|
|
319
|
+
print(
|
|
320
|
+
f'TOTAL [{"#" * round(total_progress * 24):-<24}] '
|
|
321
|
+
f'(\033[33m{int(total_progress * 100):>3}%\033[m) '
|
|
322
|
+
f'ETA: {eta_str(total_progress, total_time)} - '
|
|
323
|
+
f'{total_done} done, {total_running} running, {total_queued} queued'
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# a job was just done, update the results file
|
|
327
|
+
if now_running != prev_running:
|
|
328
|
+
save_data()
|
|
329
|
+
|
|
330
|
+
if total_done == total_jobs:
|
|
331
|
+
break
|
|
332
|
+
|
|
333
|
+
prev_running = now_running
|
|
334
|
+
sleep(0.2)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
## train in parallel
|
|
338
|
+
print('Preparing to train')
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
n_jobs = min(os.process_cpu_count(), total_jobs, max_jobs)
|
|
342
|
+
if n_jobs <= 0:
|
|
343
|
+
print('Nothing to do')
|
|
344
|
+
sys.exit(0)
|
|
345
|
+
|
|
346
|
+
print(f'{len(datasets)} datasets to process, splitting work in {n_jobs} jobs.')
|
|
347
|
+
print()
|
|
348
|
+
|
|
349
|
+
progress_bar_thread = Thread(target=progress_bar)
|
|
350
|
+
progress_bar_thread.start()
|
|
351
|
+
|
|
352
|
+
Parallel(n_jobs=n_jobs)(
|
|
353
|
+
delayed(worker)(dataset_name, estimator_name)
|
|
354
|
+
for dataset_name in datasets
|
|
355
|
+
for estimator_name in estimators
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# force end all datasets
|
|
359
|
+
for d in datasets:
|
|
360
|
+
for e in estimators:
|
|
361
|
+
progress_states[e][d][0] = time()
|
|
362
|
+
progress_states[e][d][1] = time()
|
|
363
|
+
progress_bar_thread.join()
|
|
364
|
+
|
|
365
|
+
scores, times = dict(scores), dict(times)
|
|
366
|
+
|
|
367
|
+
print('Done.')
|
|
368
|
+
save_data()
|
|
@@ -6,19 +6,25 @@ import json
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import matplotlib.pyplot as plt
|
|
8
8
|
|
|
9
|
-
FILE = '
|
|
9
|
+
FILE = 'comparison.json'
|
|
10
10
|
|
|
11
11
|
with open(FILE, 'r') as f:
|
|
12
12
|
data = json.load(f)
|
|
13
13
|
scores = data['scores']
|
|
14
14
|
times = data['times']
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
n_folds = data['n_folds']
|
|
17
17
|
|
|
18
18
|
# display summary table in console
|
|
19
19
|
datasets_names = list(scores.keys())
|
|
20
20
|
estimators_names = list(scores[datasets_names[0]].keys())
|
|
21
21
|
|
|
22
|
+
for dataset_name in datasets_names:
|
|
23
|
+
for estimator_name in estimators_names:
|
|
24
|
+
for data_source in (scores, times):
|
|
25
|
+
arr = data_source[dataset_name][estimator_name]
|
|
26
|
+
data_source[dataset_name][estimator_name] = np.array(arr)
|
|
27
|
+
|
|
22
28
|
print()
|
|
23
29
|
params = (
|
|
24
30
|
('F1 score', scores, np.argmax, np.argmin),
|
|
@@ -36,7 +42,7 @@ for name, data_source, best_func, worst_func in params:
|
|
|
36
42
|
|
|
37
43
|
dataset_res = [] # list of (avg, std)
|
|
38
44
|
for estimator_name in estimators_names:
|
|
39
|
-
res =
|
|
45
|
+
res = data_source[dataset_name][estimator_name]
|
|
40
46
|
|
|
41
47
|
avg = np.average(res)
|
|
42
48
|
std = res.std()
|
|
@@ -56,19 +62,24 @@ for name, data_source, best_func, worst_func in params:
|
|
|
56
62
|
print(f'{dataset_name:>35}' + line)
|
|
57
63
|
print()
|
|
58
64
|
|
|
59
|
-
# merge and display results averaged over all datasets
|
|
65
|
+
# merge and display results averaged over all datasets where there are results
|
|
60
66
|
estimators_scores = {}
|
|
61
67
|
estimators_times = {}
|
|
62
68
|
for estimator_name in estimators_names:
|
|
63
|
-
estimators_scores[estimator_name] = np.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
estimators_scores[estimator_name] = np.array(
|
|
70
|
+
[
|
|
71
|
+
score
|
|
72
|
+
for dataset_name in datasets_names
|
|
73
|
+
for score in scores[dataset_name][estimator_name]
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
estimators_times[estimator_name] = np.array(
|
|
77
|
+
[
|
|
78
|
+
time
|
|
79
|
+
for dataset_name in datasets_names
|
|
80
|
+
for time in times[dataset_name][estimator_name]
|
|
81
|
+
]
|
|
82
|
+
)
|
|
72
83
|
|
|
73
84
|
fig, axs = plt.subplots(ncols=2, nrows=1)
|
|
74
85
|
# log scale for times
|
|
@@ -1,90 +0,0 @@
|
|
|
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 }}
|
sklearn_morpho-0.2.2/PKG-INFO
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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'
|
sklearn_morpho-0.2.2/README.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# sklearn-morpho
|
|
2
|
-
|
|
3
|
-
scikit-learn estimator toolbox for morphological perceptrons.
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-

|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
Current features:
|
|
10
|
-
|
|
11
|
-
- Scikit-learn estimators:
|
|
12
|
-
- Linear Dilation-Erosion Perceptron (l-DEP)
|
|
13
|
-
- Reduced Dilation-Erosion Perceptron (r-DEP)
|
|
14
|
-
- Simple Dilation and Erosion Morphological Perceptrons
|
|
15
|
-
- Modular wrapper for DCCP optimization tasks with `cvxpy`
|
|
16
|
-
|
|
17
|
-
File tree:
|
|
18
|
-
|
|
19
|
-
- `src/sklearn_morpho`: contains the source code and a testsuite in its `tests`
|
|
20
|
-
subdirectory.
|
|
21
|
-
- `testing`: standalone files that use this library, may contain tests but they
|
|
22
|
-
are not designed to be run as a CI testsuite for example.
|
|
23
|
-
- `MREs`: standalone jupyter notebooks to showcase some of this library's
|
|
24
|
-
features.
|
|
25
|
-
|
|
26
|
-
## Getting started
|
|
27
|
-
|
|
28
|
-
Take a look at the Jupyter code examples in the
|
|
29
|
-
[MREs](https://github.com/d-002/sklearn-morpho/tree/master/MREs) directory.
|
|
30
|
-
|
|
31
|
-
## Running the project
|
|
32
|
-
|
|
33
|
-
Install Python 3 and hatch.
|
|
34
|
-
Then run one of these commands:
|
|
35
|
-
|
|
36
|
-
- `hatch run jupyter lab` to run the Jupyter notebooks
|
|
37
|
-
- `hatch run pytest` for tests
|
|
38
|
-
- `hatch shell` to run testing files like `testing/display_boundary.py` in the
|
|
39
|
-
right environment.
|
|
40
|
-
|
|
41
|
-
Special note for the estimators comparison testing files: they are split in two
|
|
42
|
-
files to avoid training the estimators every time one wants to view the results.
|
|
43
|
-
|
|
44
|
-
## For Arch users
|
|
45
|
-
|
|
46
|
-
The package is available in the
|
|
47
|
-
[Arch User Repository](https://aur.archlinux.org) as `python-sklearn-morpho`, as
|
|
48
|
-
are all its dependencies not already in the official packages, except for
|
|
49
|
-
`dccp`.
|
|
50
|
-
|
|
51
|
-
Since the latter is an optional dependency, you can either:
|
|
52
|
-
|
|
53
|
-
- Download `dccp` through pip or use `pip install sklearn-morpho[dccp]` in a
|
|
54
|
-
Python virtual environment.
|
|
55
|
-
- Download `python-sklearn-morpho` from the AUR and use
|
|
56
|
-
`pip install dccp --user`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/classifiers/simple_perceptron.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/stopping/stopping_train_stop.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/training/dccp_simple_perceptron.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/weighting/weighting_base.py
RENAMED
|
File without changes
|
{sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/weighting/weighting_dist.py
RENAMED
|
File without changes
|
{sklearn_morpho-0.2.2 → sklearn_morpho-0.2.3}/src/sklearn_morpho/weighting/weighting_none.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|