Compare commits

..

7 Commits

Author SHA1 Message Date
5f8ca8f3bb Reformat test with new black version 2024-03-05 18:46:19 +01:00
Ricardo Montañana Gómez
fb8b9b344f Update README.md
update installation instructions
2024-03-05 18:18:55 +01:00
036d1ba2a7 Add separate methods to return nodes/leaves/depth 2023-11-27 10:02:14 +01:00
4de74973b8 Black format issue 2023-07-12 14:16:08 +02:00
Ricardo Montañana Gómez
28dd04b95a Update benchmark.ipynb 2023-05-13 14:44:49 +02:00
Ricardo Montañana Gómez
542bbce7db ci: ⬆️ Update ci files and badges 2023-01-15 02:18:41 +01:00
Ricardo Montañana Gómez
5b791bc5bf New_version_sklearn (#56)
* test: 🧪 Update max_iter as int in test_multiclass_dataset

* refactor: 📝 Rename base_estimator to estimator as the former is deprectated in notebook

* refactor: 📌 Convert max_iter to int as needed in sklearn 1.2

* chore: 🔖 Update version info to 1.3.1
2023-01-15 01:21:32 +01:00
9 changed files with 339 additions and 295 deletions

View File

@@ -7,7 +7,7 @@ on:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [master] branches: [master]
schedule: schedule:
- cron: '16 17 * * 3' - cron: "16 17 * * 3"
jobs: jobs:
analyze: analyze:
@@ -17,7 +17,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'python' ] language: ["python"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more: # Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
@@ -28,7 +28,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -39,7 +39,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -53,4 +53,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@@ -16,9 +16,9 @@ jobs:
python: [3.8, "3.10"] python: [3.8, "3.10"]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }} - name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
- name: Install dependencies - name: Install dependencies
@@ -35,7 +35,7 @@ jobs:
coverage run -m unittest -v stree.tests coverage run -m unittest -v stree.tests
coverage xml coverage xml
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v3
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml files: ./coverage.xml

View File

@@ -1,7 +1,7 @@
![CI](https://github.com/Doctorado-ML/STree/workflows/CI/badge.svg) ![CI](https://github.com/Doctorado-ML/STree/workflows/CI/badge.svg)
[![CodeQL](https://github.com/Doctorado-ML/STree/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/Doctorado-ML/STree/actions/workflows/codeql-analysis.yml)
[![codecov](https://codecov.io/gh/doctorado-ml/stree/branch/master/graph/badge.svg)](https://codecov.io/gh/doctorado-ml/stree) [![codecov](https://codecov.io/gh/doctorado-ml/stree/branch/master/graph/badge.svg)](https://codecov.io/gh/doctorado-ml/stree)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/35fa3dfd53a24a339344b33d9f9f2f3d)](https://www.codacy.com/gh/Doctorado-ML/STree?utm_source=github.com&utm_medium=referral&utm_content=Doctorado-ML/STree&utm_campaign=Badge_Grade) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/35fa3dfd53a24a339344b33d9f9f2f3d)](https://www.codacy.com/gh/Doctorado-ML/STree?utm_source=github.com&utm_medium=referral&utm_content=Doctorado-ML/STree&utm_campaign=Badge_Grade)
[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/Doctorado-ML/STree.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Doctorado-ML/STree/context:python)
[![PyPI version](https://badge.fury.io/py/STree.svg)](https://badge.fury.io/py/STree) [![PyPI version](https://badge.fury.io/py/STree.svg)](https://badge.fury.io/py/STree)
![https://img.shields.io/badge/python-3.8%2B-blue](https://img.shields.io/badge/python-3.8%2B-brightgreen) ![https://img.shields.io/badge/python-3.8%2B-blue](https://img.shields.io/badge/python-3.8%2B-brightgreen)
[![DOI](https://zenodo.org/badge/262658230.svg)](https://zenodo.org/badge/latestdoi/262658230) [![DOI](https://zenodo.org/badge/262658230.svg)](https://zenodo.org/badge/latestdoi/262658230)
@@ -15,7 +15,7 @@ Oblique Tree classifier based on SVM nodes. The nodes are built and splitted wit
## Installation ## Installation
```bash ```bash
pip install git+https://github.com/doctorado-ml/stree pip install Stree
``` ```
## Documentation ## Documentation

View File

@@ -178,7 +178,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"# Stree\n", "# Stree\n",
"stree = Stree(random_state=random_state, C=.01, max_iter=1e3, kernel=\"liblinear\", multiclass_strategy=\"ovr\")" "stree = Stree(random_state=random_state, C=.01, max_iter=1000, kernel=\"liblinear\", multiclass_strategy=\"ovr\")"
] ]
}, },
{ {
@@ -198,7 +198,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"# SVC (linear)\n", "# SVC (linear)\n",
"svc = LinearSVC(random_state=random_state, C=.01, max_iter=1e3)" "svc = LinearSVC(random_state=random_state, C=.01, max_iter=1000)"
] ]
}, },
{ {

View File

@@ -133,33 +133,33 @@
" 'base_estimator': [Stree(random_state=random_state)],\n", " 'base_estimator': [Stree(random_state=random_state)],\n",
" 'n_estimators': [10, 25],\n", " 'n_estimators': [10, 25],\n",
" 'learning_rate': [.5, 1],\n", " 'learning_rate': [.5, 1],\n",
" 'base_estimator__split_criteria': ['max_samples', 'impurity'],\n", " 'estimator__split_criteria': ['max_samples', 'impurity'],\n",
" 'base_estimator__tol': [.1, 1e-02],\n", " 'estimator__tol': [.1, 1e-02],\n",
" 'base_estimator__max_depth': [3, 5, 7],\n", " 'estimator__max_depth': [3, 5, 7],\n",
" 'base_estimator__C': [1, 7, 55],\n", " 'estimator__C': [1, 7, 55],\n",
" 'base_estimator__kernel': ['linear']\n", " 'estimator__kernel': ['linear']\n",
"},\n", "},\n",
"{\n", "{\n",
" 'base_estimator': [Stree(random_state=random_state)],\n", " 'base_estimator': [Stree(random_state=random_state)],\n",
" 'n_estimators': [10, 25],\n", " 'n_estimators': [10, 25],\n",
" 'learning_rate': [.5, 1],\n", " 'learning_rate': [.5, 1],\n",
" 'base_estimator__split_criteria': ['max_samples', 'impurity'],\n", " 'estimator__split_criteria': ['max_samples', 'impurity'],\n",
" 'base_estimator__tol': [.1, 1e-02],\n", " 'estimator__tol': [.1, 1e-02],\n",
" 'base_estimator__max_depth': [3, 5, 7],\n", " 'estimator__max_depth': [3, 5, 7],\n",
" 'base_estimator__C': [1, 7, 55],\n", " 'estimator__C': [1, 7, 55],\n",
" 'base_estimator__degree': [3, 5, 7],\n", " 'estimator__degree': [3, 5, 7],\n",
" 'base_estimator__kernel': ['poly']\n", " 'estimator__kernel': ['poly']\n",
"},\n", "},\n",
"{\n", "{\n",
" 'base_estimator': [Stree(random_state=random_state)],\n", " 'base_estimator': [Stree(random_state=random_state)],\n",
" 'n_estimators': [10, 25],\n", " 'n_estimators': [10, 25],\n",
" 'learning_rate': [.5, 1],\n", " 'learning_rate': [.5, 1],\n",
" 'base_estimator__split_criteria': ['max_samples', 'impurity'],\n", " 'estimator__split_criteria': ['max_samples', 'impurity'],\n",
" 'base_estimator__tol': [.1, 1e-02],\n", " 'estimator__tol': [.1, 1e-02],\n",
" 'base_estimator__max_depth': [3, 5, 7],\n", " 'estimator__max_depth': [3, 5, 7],\n",
" 'base_estimator__C': [1, 7, 55],\n", " 'estimator__C': [1, 7, 55],\n",
" 'base_estimator__gamma': [.1, 1, 10],\n", " 'estimator__gamma': [.1, 1, 10],\n",
" 'base_estimator__kernel': ['rbf']\n", " 'estimator__kernel': ['rbf']\n",
"}]" "}]"
] ]
}, },
@@ -214,7 +214,7 @@
" base_estimator=Stree(C=55, max_depth=7, random_state=1,\n", " base_estimator=Stree(C=55, max_depth=7, random_state=1,\n",
" split_criteria='max_samples', tol=0.1),\n", " split_criteria='max_samples', tol=0.1),\n",
" learning_rate=0.5, n_estimators=25, random_state=1)\n", " learning_rate=0.5, n_estimators=25, random_state=1)\n",
"Best hyperparameters: {'base_estimator': Stree(C=55, max_depth=7, random_state=1, split_criteria='max_samples', tol=0.1), 'base_estimator__C': 55, 'base_estimator__kernel': 'linear', 'base_estimator__max_depth': 7, 'base_estimator__split_criteria': 'max_samples', 'base_estimator__tol': 0.1, 'learning_rate': 0.5, 'n_estimators': 25}" "Best hyperparameters: {'base_estimator': Stree(C=55, max_depth=7, random_state=1, split_criteria='max_samples', tol=0.1), 'estimator__C': 55, 'estimator__kernel': 'linear', 'estimator__max_depth': 7, 'estimator__split_criteria': 'max_samples', 'estimator__tol': 0.1, 'learning_rate': 0.5, 'n_estimators': 25}"
] ]
}, },
{ {

View File

@@ -267,7 +267,6 @@ class Splitter:
random_state=None, random_state=None,
normalize=False, normalize=False,
): ):
self._clf = clf self._clf = clf
self._random_state = random_state self._random_state = random_state
if random_state is not None: if random_state is not None:

View File

@@ -139,7 +139,7 @@ class Stree(BaseEstimator, ClassifierMixin):
self, self,
C: float = 1.0, C: float = 1.0,
kernel: str = "linear", kernel: str = "linear",
max_iter: int = 1e5, max_iter: int = int(1e5),
random_state: int = None, random_state: int = None,
max_depth: int = None, max_depth: int = None,
tol: float = 1e-4, tol: float = 1e-4,
@@ -153,7 +153,6 @@ class Stree(BaseEstimator, ClassifierMixin):
multiclass_strategy: str = "ovo", multiclass_strategy: str = "ovo",
normalize: bool = False, normalize: bool = False,
): ):
self.max_iter = max_iter self.max_iter = max_iter
self.C = C self.C = C
self.kernel = kernel self.kernel = kernel
@@ -485,6 +484,43 @@ class Stree(BaseEstimator, ClassifierMixin):
X = self.check_predict(X) X = self.check_predict(X)
return self.classes_[np.argmax(self.__predict_class(X), axis=1)] return self.classes_[np.argmax(self.__predict_class(X), axis=1)]
def get_nodes(self) -> int:
"""Return the number of nodes in the tree
Returns
-------
int
number of nodes
"""
nodes = 0
for _ in self:
nodes += 1
return nodes
def get_leaves(self) -> int:
"""Return the number of leaves in the tree
Returns
-------
int
number of leaves
"""
leaves = 0
for node in self:
if node.is_leaf():
leaves += 1
return leaves
def get_depth(self) -> int:
"""Return the depth of the tree
Returns
-------
int
depth of the tree
"""
return self.depth_
def nodes_leaves(self) -> tuple: def nodes_leaves(self) -> tuple:
"""Compute the number of nodes and leaves in the built tree """Compute the number of nodes and leaves in the built tree

View File

@@ -1 +1 @@
__version__ = "1.3.0" __version__ = "1.3.2"

View File

@@ -239,6 +239,7 @@ class Stree_test(unittest.TestCase):
) )
tcl.fit(*load_dataset(self._random_state)) tcl.fit(*load_dataset(self._random_state))
self.assertEqual(depth, tcl.depth_) self.assertEqual(depth, tcl.depth_)
self.assertEqual(depth, tcl.get_depth())
def test_unfitted_tree_is_iterable(self): def test_unfitted_tree_is_iterable(self):
tcl = Stree() tcl = Stree()
@@ -306,10 +307,10 @@ class Stree_test(unittest.TestCase):
for criteria in ["max_samples", "impurity"]: for criteria in ["max_samples", "impurity"]:
for kernel in self._kernels: for kernel in self._kernels:
clf = Stree( clf = Stree(
max_iter=1e4, max_iter=int(1e4),
multiclass_strategy="ovr" multiclass_strategy=(
if kernel == "liblinear" "ovr" if kernel == "liblinear" else "ovo"
else "ovo", ),
kernel=kernel, kernel=kernel,
random_state=self._random_state, random_state=self._random_state,
) )
@@ -640,10 +641,12 @@ class Stree_test(unittest.TestCase):
clf = Stree(random_state=self._random_state) clf = Stree(random_state=self._random_state)
clf.fit(X, y) clf.fit(X, y)
self.assertEqual(6, clf.depth_) self.assertEqual(6, clf.depth_)
self.assertEqual(6, clf.get_depth())
X, y = load_wine(return_X_y=True) X, y = load_wine(return_X_y=True)
clf = Stree(random_state=self._random_state) clf = Stree(random_state=self._random_state)
clf.fit(X, y) clf.fit(X, y)
self.assertEqual(4, clf.depth_) self.assertEqual(4, clf.depth_)
self.assertEqual(4, clf.get_depth())
def test_nodes_leaves(self): def test_nodes_leaves(self):
"""Check number of nodes and leaves.""" """Check number of nodes and leaves."""
@@ -657,13 +660,17 @@ class Stree_test(unittest.TestCase):
clf.fit(X, y) clf.fit(X, y)
nodes, leaves = clf.nodes_leaves() nodes, leaves = clf.nodes_leaves()
self.assertEqual(31, nodes) self.assertEqual(31, nodes)
self.assertEqual(31, clf.get_nodes())
self.assertEqual(16, leaves) self.assertEqual(16, leaves)
self.assertEqual(16, clf.get_leaves())
X, y = load_wine(return_X_y=True) X, y = load_wine(return_X_y=True)
clf = Stree(random_state=self._random_state) clf = Stree(random_state=self._random_state)
clf.fit(X, y) clf.fit(X, y)
nodes, leaves = clf.nodes_leaves() nodes, leaves = clf.nodes_leaves()
self.assertEqual(11, nodes) self.assertEqual(11, nodes)
self.assertEqual(11, clf.get_nodes())
self.assertEqual(6, leaves) self.assertEqual(6, leaves)
self.assertEqual(6, clf.get_leaves())
def test_nodes_leaves_artificial(self): def test_nodes_leaves_artificial(self):
"""Check leaves of artificial dataset.""" """Check leaves of artificial dataset."""
@@ -682,7 +689,9 @@ class Stree_test(unittest.TestCase):
clf.tree_ = n1 clf.tree_ = n1
nodes, leaves = clf.nodes_leaves() nodes, leaves = clf.nodes_leaves()
self.assertEqual(6, nodes) self.assertEqual(6, nodes)
self.assertEqual(6, clf.get_nodes())
self.assertEqual(2, leaves) self.assertEqual(2, leaves)
self.assertEqual(2, clf.get_leaves())
def test_bogus_multiclass_strategy(self): def test_bogus_multiclass_strategy(self):
"""Check invalid multiclass strategy.""" """Check invalid multiclass strategy."""