abcopt 0.2.0__py3-none-any.whl

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.
abcopt/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Canonical Artificial Bee Colony optimization with a scikit-learn wrapper."""
2
+
3
+ from .core import CanonicalABCSolver
4
+ from .sklearn_wrapper import ABCSearchCV
5
+
6
+ __version__ = "0.2.0"
7
+ __all__ = ["CanonicalABCSolver", "ABCSearchCV", "__version__"]
abcopt/core.py ADDED
@@ -0,0 +1,182 @@
1
+ import numpy as np
2
+
3
+
4
+ class CanonicalABCSolver:
5
+ """
6
+ Canonical Artificial Bee Colony (ABC) algorithm.
7
+
8
+ A faithful implementation of the algorithm proposed by D. Karaboğa (2005):
9
+ employed bees, onlooker bees (roulette-wheel selection) and scout bees,
10
+ with a single dimension perturbed per move.
11
+
12
+ Parameters
13
+ ----------
14
+ objective_func : callable
15
+ Function taking a 1-D ``np.ndarray`` of length ``n_params`` and
16
+ returning a scalar cost to **minimize**.
17
+ n_params : int
18
+ Number of dimensions (decision variables).
19
+ lb, ub : array-like of shape (n_params,)
20
+ Lower and upper bounds for each dimension.
21
+ pop_size : int, default=20
22
+ Number of food sources (SN). Must be at least 2.
23
+ max_evals : int, default=1000
24
+ Total budget of objective-function evaluations.
25
+ limit : int or None, default=None
26
+ Number of unsuccessful trials before a food source is abandoned.
27
+ Defaults to ``pop_size * n_params``.
28
+ random_state : int, np.random.Generator or None, default=None
29
+ Seed or generator for reproducible runs.
30
+
31
+ Attributes
32
+ ----------
33
+ best_params : np.ndarray
34
+ Best solution found.
35
+ best_cost : float
36
+ Objective value of ``best_params``.
37
+ history : list of (int, float)
38
+ ``(evaluations_used, best_cost_so_far)`` after each cycle.
39
+ """
40
+
41
+ def __init__(self, objective_func, n_params, lb, ub, pop_size=20,
42
+ max_evals=1000, limit=None, random_state=None):
43
+ self.objective_func = objective_func
44
+ self.D = int(n_params)
45
+ self.lb = np.asarray(lb, dtype=float).ravel()
46
+ self.ub = np.asarray(ub, dtype=float).ravel()
47
+ self.SN = int(pop_size)
48
+ self.total_evaluation = int(max_evals)
49
+ self.limit = limit if limit is not None else self.SN * self.D
50
+ self.random_state = random_state
51
+
52
+ self._validate()
53
+
54
+ self.best_params = None
55
+ self.best_cost = float("inf")
56
+ self.history = []
57
+
58
+ def _validate(self):
59
+ if not callable(self.objective_func):
60
+ raise TypeError("objective_func must be callable.")
61
+ if self.D < 1:
62
+ raise ValueError("n_params must be at least 1.")
63
+ if self.lb.shape != (self.D,) or self.ub.shape != (self.D,):
64
+ raise ValueError(
65
+ f"lb and ub must both have length n_params={self.D}, "
66
+ f"got {self.lb.size} and {self.ub.size}."
67
+ )
68
+ if np.any(self.lb > self.ub):
69
+ raise ValueError("Every lower bound must be <= its upper bound.")
70
+ if self.SN < 2:
71
+ raise ValueError("pop_size must be at least 2 (neighbours are needed).")
72
+ if self.total_evaluation < self.SN:
73
+ raise ValueError(
74
+ f"max_evals ({self.total_evaluation}) must be >= pop_size ({self.SN}) "
75
+ "so that the initial population can be evaluated."
76
+ )
77
+ if self.limit < 1:
78
+ raise ValueError("limit must be at least 1.")
79
+
80
+ @staticmethod
81
+ def _calculate_fitness(cost):
82
+ """Convert a cost (to be minimized) into a fitness (to be maximized)."""
83
+ return 1.0 / (1.0 + cost) if cost >= 0 else 1.0 + abs(cost)
84
+
85
+ def _random_solution(self):
86
+ return self.lb + self._rng.random(self.D) * (self.ub - self.lb)
87
+
88
+ def _neighbour(self, i):
89
+ k = self._rng.integers(0, self.SN - 1)
90
+ return k if k < i else k + 1 # uniform over all indices except i
91
+
92
+ def _perturb(self, current, neighbour):
93
+ """Canonical move: change exactly one randomly chosen dimension."""
94
+ j = self._rng.integers(0, self.D)
95
+ phi = self._rng.uniform(-1.0, 1.0)
96
+ new_sol = np.copy(current)
97
+ new_sol[j] = current[j] + phi * (current[j] - neighbour[j])
98
+ new_sol[j] = np.clip(new_sol[j], self.lb[j], self.ub[j])
99
+ return new_sol
100
+
101
+ def _evaluate(self, x):
102
+ cost = float(self.objective_func(x))
103
+ if np.isnan(cost):
104
+ cost = float("inf")
105
+ self._evals += 1
106
+ return cost
107
+
108
+ def _try_improve(self, i):
109
+ """Greedy selection between food source i and a mutated copy."""
110
+ k = self._neighbour(i)
111
+ new_sol = self._perturb(self.foods[i], self.foods[k])
112
+ new_cost = self._evaluate(new_sol)
113
+ new_fit = self._calculate_fitness(new_cost)
114
+ if new_fit > self.fitness[i]:
115
+ self.foods[i] = new_sol
116
+ self.costs[i] = new_cost
117
+ self.fitness[i] = new_fit
118
+ self.trial[i] = 0
119
+ else:
120
+ self.trial[i] += 1
121
+
122
+ def _memorize_best(self):
123
+ idx = int(np.argmin(self.costs))
124
+ if self.costs[idx] < self.best_cost:
125
+ self.best_cost = float(self.costs[idx])
126
+ self.best_params = np.copy(self.foods[idx])
127
+
128
+ def solve(self):
129
+ """
130
+ Run the optimization.
131
+
132
+ Returns
133
+ -------
134
+ best_params : np.ndarray
135
+ best_cost : float
136
+ history : list of (int, float)
137
+ """
138
+ self._rng = np.random.default_rng(self.random_state)
139
+ self._evals = 0
140
+ self.best_params = None
141
+ self.best_cost = float("inf")
142
+ self.history = []
143
+
144
+ # Initialization
145
+ self.foods = self.lb + self._rng.random((self.SN, self.D)) * (self.ub - self.lb)
146
+ self.costs = np.array([self._evaluate(x) for x in self.foods])
147
+ self.fitness = np.array([self._calculate_fitness(c) for c in self.costs])
148
+ self.trial = np.zeros(self.SN, dtype=int)
149
+ self._memorize_best()
150
+ self.history.append((self._evals, self.best_cost))
151
+
152
+ while self._evals < self.total_evaluation:
153
+ # 1. Employed bees: every food source is visited once.
154
+ for i in range(self.SN):
155
+ if self._evals >= self.total_evaluation:
156
+ break
157
+ self._try_improve(i)
158
+
159
+ # 2. Onlooker bees: roulette-wheel selection proportional to fitness.
160
+ total_fit = np.sum(self.fitness)
161
+ prob = (self.fitness / total_fit) if total_fit > 0 else np.full(self.SN, 1.0 / self.SN)
162
+ t, i = 0, 0
163
+ while t < self.SN and self._evals < self.total_evaluation:
164
+ if self._rng.random() < prob[i]:
165
+ t += 1
166
+ self._try_improve(i)
167
+ i = (i + 1) % self.SN
168
+
169
+ self._memorize_best()
170
+
171
+ # 3. Scout bee: abandon the most exhausted source if over the limit.
172
+ worst = int(np.argmax(self.trial))
173
+ if self.trial[worst] > self.limit and self._evals < self.total_evaluation:
174
+ self.foods[worst] = self._random_solution()
175
+ self.costs[worst] = self._evaluate(self.foods[worst])
176
+ self.fitness[worst] = self._calculate_fitness(self.costs[worst])
177
+ self.trial[worst] = 0
178
+ self._memorize_best()
179
+
180
+ self.history.append((self._evals, self.best_cost))
181
+
182
+ return self.best_params, self.best_cost, self.history
@@ -0,0 +1,211 @@
1
+ import math
2
+ import warnings
3
+
4
+ import numpy as np
5
+ from sklearn.base import BaseEstimator, MetaEstimatorMixin, clone
6
+ from sklearn.model_selection import cross_val_score
7
+
8
+ from .core import CanonicalABCSolver
9
+
10
+ _VALID_TYPES = ("float", "int", "categorical")
11
+
12
+
13
+ class ABCSearchCV(MetaEstimatorMixin, BaseEstimator):
14
+ """
15
+ Hyperparameter search with the canonical Artificial Bee Colony algorithm.
16
+
17
+ Works like ``GridSearchCV`` / ``RandomizedSearchCV``: every candidate is
18
+ scored with cross-validation and the best one is refit on the full data.
19
+
20
+ Parameters
21
+ ----------
22
+ estimator : estimator object
23
+ Any scikit-learn compatible estimator.
24
+ param_space : dict
25
+ Maps each hyperparameter name to a specification:
26
+
27
+ - ``{"type": "float", "range": (low, high)}``
28
+ - ``{"type": "float", "range": (low, high), "log": True}``
29
+ - ``{"type": "int", "range": (low, high)}`` (both ends inclusive)
30
+ - ``{"type": "categorical", "choices": [a, b, c]}``
31
+ cv : int or cross-validation generator, default=3
32
+ scoring : str or callable, default="accuracy"
33
+ Any scikit-learn scorer. Higher is better (use ``neg_*`` scorers
34
+ for errors), exactly as in ``GridSearchCV``.
35
+ pop_size : int, default=20
36
+ Number of food sources.
37
+ max_evals : int, default=200
38
+ Total number of candidates evaluated (each one is a full CV run).
39
+ limit : int or None, default=None
40
+ Abandonment limit for scout bees.
41
+ random_state : int or None, default=None
42
+ Seed for reproducible searches.
43
+ n_jobs : int or None, default=None
44
+ Parallel jobs used inside each cross-validation.
45
+ error_score : "raise" or float, default=np.nan
46
+ What to do when a candidate fails to fit. ``"raise"`` re-raises the
47
+ error; otherwise the candidate gets this score (NaN is treated as the
48
+ worst possible score) and a warning is issued.
49
+ refit : bool, default=True
50
+ Refit the best estimator on the whole dataset.
51
+ verbose : bool, default=False
52
+
53
+ Attributes
54
+ ----------
55
+ best_params_ : dict
56
+ best_score_ : float
57
+ Mean cross-validated score of the best candidate.
58
+ best_estimator_ : estimator
59
+ Only available when ``refit=True``.
60
+ history_ : list of (int, float)
61
+ ``(evaluations_used, best_score_so_far)`` after each ABC cycle.
62
+ results_ : list of dict
63
+ Every evaluated candidate with its ``params`` and ``score``.
64
+ n_evals_ : int
65
+ Number of candidates evaluated.
66
+ """
67
+
68
+ def __init__(self, estimator, param_space, cv=3, scoring="accuracy",
69
+ pop_size=20, max_evals=200, limit=None, random_state=None,
70
+ n_jobs=None, error_score=np.nan, refit=True, verbose=False):
71
+ self.estimator = estimator
72
+ self.param_space = param_space
73
+ self.cv = cv
74
+ self.scoring = scoring
75
+ self.pop_size = pop_size
76
+ self.max_evals = max_evals
77
+ self.limit = limit
78
+ self.random_state = random_state
79
+ self.n_jobs = n_jobs
80
+ self.error_score = error_score
81
+ self.refit = refit
82
+ self.verbose = verbose
83
+
84
+ # ------------------------------------------------------------------ #
85
+ # Search-space encoding: every parameter becomes one continuous
86
+ # dimension that ABC can search; _decode maps it back.
87
+ # ------------------------------------------------------------------ #
88
+ def _build_space(self):
89
+ if not isinstance(self.param_space, dict) or not self.param_space:
90
+ raise ValueError("param_space must be a non-empty dict.")
91
+
92
+ keys, lb, ub = [], [], []
93
+ for name, spec in self.param_space.items():
94
+ p_type = spec.get("type")
95
+ if p_type not in _VALID_TYPES:
96
+ raise ValueError(
97
+ f"Parameter '{name}': type must be one of {_VALID_TYPES}, got {p_type!r}."
98
+ )
99
+ if p_type == "categorical":
100
+ choices = spec.get("choices")
101
+ if not choices:
102
+ raise ValueError(f"Parameter '{name}': 'choices' must be a non-empty list.")
103
+ lo, hi = 0.0, float(len(choices))
104
+ else:
105
+ low, high = spec.get("range", (None, None))
106
+ if low is None or high is None or low > high:
107
+ raise ValueError(f"Parameter '{name}': 'range' must be (low, high) with low <= high.")
108
+ if spec.get("log"):
109
+ if low <= 0:
110
+ raise ValueError(f"Parameter '{name}': log scale needs a positive range.")
111
+ lo, hi = math.log10(low), math.log10(high)
112
+ elif p_type == "int":
113
+ # widen by 0.5 so both ends are reachable after rounding
114
+ lo, hi = low - 0.5, high + 0.5
115
+ else:
116
+ lo, hi = float(low), float(high)
117
+ keys.append(name)
118
+ lb.append(lo)
119
+ ub.append(hi)
120
+ return keys, lb, ub
121
+
122
+ def _decode(self, vector):
123
+ params = {}
124
+ for name, val in zip(self._keys, vector):
125
+ spec = self.param_space[name]
126
+ p_type = spec["type"]
127
+ if p_type == "categorical":
128
+ choices = spec["choices"]
129
+ params[name] = choices[min(int(val), len(choices) - 1)]
130
+ continue
131
+ if spec.get("log"):
132
+ val = 10 ** val
133
+ if p_type == "int":
134
+ low, high = spec["range"]
135
+ params[name] = int(min(max(round(val), low), high))
136
+ else:
137
+ params[name] = float(val)
138
+ return params
139
+
140
+ # ------------------------------------------------------------------ #
141
+ def fit(self, X, y=None, groups=None):
142
+ self._keys, lb, ub = self._build_space()
143
+ self.results_ = []
144
+
145
+ def objective(vector):
146
+ params = self._decode(vector)
147
+ model = clone(self.estimator).set_params(**params)
148
+ try:
149
+ scores = cross_val_score(
150
+ model, X, y, groups=groups, cv=self.cv, scoring=self.scoring,
151
+ n_jobs=self.n_jobs, error_score="raise",
152
+ )
153
+ score = float(np.mean(scores))
154
+ except Exception as exc:
155
+ if self.error_score == "raise":
156
+ raise
157
+ warnings.warn(
158
+ f"Candidate {params} failed and was given score "
159
+ f"{self.error_score}: {type(exc).__name__}: {exc}",
160
+ RuntimeWarning,
161
+ stacklevel=2,
162
+ )
163
+ score = float(self.error_score)
164
+ self.results_.append({"params": params, "score": score})
165
+ if self.verbose:
166
+ print(f"[{len(self.results_)}/{self.max_evals}] score={score:.5f} {params}")
167
+ # ABC minimizes; NaN scores become +inf cost inside the solver.
168
+ return -score
169
+
170
+ solver = CanonicalABCSolver(
171
+ objective, len(self._keys), lb, ub,
172
+ pop_size=self.pop_size, max_evals=self.max_evals,
173
+ limit=self.limit, random_state=self.random_state,
174
+ )
175
+ best_vec, best_cost, history = solver.solve()
176
+
177
+ if not np.isfinite(best_cost):
178
+ raise RuntimeError(
179
+ "All candidates failed. Check param_space and the estimator; "
180
+ "set error_score='raise' to see the underlying error."
181
+ )
182
+
183
+ self.best_params_ = self._decode(best_vec)
184
+ self.best_score_ = -best_cost
185
+ self.history_ = [(n, -c) for n, c in history]
186
+ self.n_evals_ = len(self.results_)
187
+
188
+ if self.refit:
189
+ self.best_estimator_ = clone(self.estimator).set_params(**self.best_params_)
190
+ if y is None:
191
+ self.best_estimator_.fit(X)
192
+ else:
193
+ self.best_estimator_.fit(X, y)
194
+ return self
195
+
196
+ # ------------------------------------------------------------------ #
197
+ def _check_refit(self, method):
198
+ if not hasattr(self, "best_estimator_"):
199
+ raise AttributeError(
200
+ f"{method} is only available after fit() with refit=True."
201
+ )
202
+ return self.best_estimator_
203
+
204
+ def predict(self, X):
205
+ return self._check_refit("predict").predict(X)
206
+
207
+ def predict_proba(self, X):
208
+ return self._check_refit("predict_proba").predict_proba(X)
209
+
210
+ def score(self, X, y):
211
+ return self._check_refit("score").score(X, y)
@@ -0,0 +1,264 @@
1
+ Metadata-Version: 2.4
2
+ Name: abcopt
3
+ Version: 0.2.0
4
+ Summary: Canonical Artificial Bee Colony (ABC) optimization with a scikit-learn compatible hyperparameter search
5
+ Author-email: Yusuf Korkmazyiğit <yusuf.korkmazyigit@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/yusufkorkmazyigit/abc-optimizer-lib
8
+ Project-URL: Issues, https://github.com/yusufkorkmazyigit/abc-optimizer-lib/issues
9
+ Keywords: artificial bee colony,swarm intelligence,metaheuristics,hyperparameter optimization,scikit-learn
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy>=1.21
18
+ Requires-Dist: scikit-learn>=1.0
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=7; extra == "test"
21
+ Dynamic: license-file
22
+
23
+ # 🐝 abcopt
24
+
25
+ **Canonical Artificial Bee Colony (ABC) optimization in pure Python, with a scikit-learn compatible hyperparameter search.**
26
+
27
+ [![PyPI](https://img.shields.io/pypi/v/abcopt)](https://pypi.org/project/abcopt/)
28
+ [![tests](https://github.com/yusufkorkmazyigit/abc-optimizer-lib/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/yusufkorkmazyigit/abc-optimizer-lib/actions/workflows/tests.yml)
29
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
30
+ ![License](https://img.shields.io/badge/license-MIT-green)
31
+ ![scikit-learn](https://img.shields.io/badge/sklearn-compatible-orange)
32
+ ![Dependencies](https://img.shields.io/badge/deps-numpy%20%7C%20scikit--learn-lightgrey)
33
+
34
+ **abcopt** is a faithful implementation of the **Artificial Bee Colony** algorithm proposed by **Derviş Karaboğa (2005)** at Erciyes University. It is useful for two things:
35
+
36
+ 1. **Black-box optimization:** minimize any objective function within box constraints, with no gradients required.
37
+ 2. **Hyperparameter tuning:** a drop-in alternative to `GridSearchCV` / `RandomizedSearchCV` for any scikit-learn compatible estimator (scikit-learn, LightGBM, XGBoost, …).
38
+
39
+ ---
40
+
41
+ ## ✨ Features
42
+
43
+ - **Canonical algorithm:** employed, onlooker (roulette-wheel selection) and scout bee phases, following the original paper; one dimension is perturbed per move.
44
+ - **scikit-learn API:** `ABCSearchCV` exposes `fit`, `predict`, `score`, `best_params_`, `best_score_` and `best_estimator_`.
45
+ - **Mixed search spaces:** `float` (optionally log-scaled), `int` and `categorical` hyperparameters.
46
+ - **Reproducible:** pass `random_state` and every run is identical.
47
+ - **Evaluation budget:** you set `max_evals`, so the run cost is predictable.
48
+ - **Robust:** failing candidates raise a warning instead of being hidden (or stop the search with `error_score="raise"`), and invalid inputs fail fast with clear messages.
49
+ - **Convergence history:** every run returns its best-so-far curve for plotting and comparison.
50
+ - **Tested:** 30 unit tests run on every push via GitHub Actions.
51
+ - **Lightweight:** depends only on `numpy` and `scikit-learn`.
52
+
53
+ ---
54
+
55
+ ## 📦 Installation
56
+
57
+ ```bash
58
+ pip install abcopt
59
+ ```
60
+
61
+ ```python
62
+ from abcopt import ABCSearchCV, CanonicalABCSolver
63
+ ```
64
+
65
+ For development:
66
+
67
+ ```bash
68
+ git clone https://github.com/yusufkorkmazyigit/abc-optimizer-lib.git
69
+ cd abc-optimizer-lib
70
+ pip install -e ".[test]"
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 🚀 Quick Start
76
+
77
+ ### 1. Hyperparameter tuning with `ABCSearchCV`
78
+
79
+ ```python
80
+ import lightgbm as lgb
81
+ from sklearn.datasets import load_breast_cancer
82
+ from abcopt import ABCSearchCV
83
+
84
+ X, y = load_breast_cancer(return_X_y=True)
85
+
86
+ param_space = {
87
+ "learning_rate": {"type": "float", "range": (0.01, 0.3), "log": True},
88
+ "n_estimators": {"type": "int", "range": (50, 500)},
89
+ "num_leaves": {"type": "int", "range": (20, 100)},
90
+ "boosting_type": {"type": "categorical", "choices": ["gbdt", "dart"]},
91
+ }
92
+
93
+ search = ABCSearchCV(
94
+ estimator=lgb.LGBMClassifier(verbosity=-1),
95
+ param_space=param_space,
96
+ cv=3,
97
+ scoring="accuracy",
98
+ pop_size=20, # number of food sources
99
+ max_evals=100, # total objective evaluations (each one = one CV run)
100
+ random_state=42, # reproducible search
101
+ )
102
+ search.fit(X, y)
103
+
104
+ print("Best CV score:", search.best_score_)
105
+ print("Best params: ", search.best_params_)
106
+ y_pred = search.predict(X) # uses best_estimator_, refit on the full data
107
+ ```
108
+
109
+ ### 2. Function minimization with `CanonicalABCSolver`
110
+
111
+ ```python
112
+ import numpy as np
113
+ from abcopt import CanonicalABCSolver
114
+
115
+ def sphere(x):
116
+ return np.sum(x ** 2) # global minimum: 0 at x = 0
117
+
118
+ solver = CanonicalABCSolver(
119
+ objective_func=sphere,
120
+ n_params=3,
121
+ lb=[-10, -10, -10],
122
+ ub=[10, 10, 10],
123
+ max_evals=2000,
124
+ random_state=0,
125
+ )
126
+ best_x, best_cost, history = solver.solve()
127
+ print(f"x* = {best_x}, f(x*) = {best_cost:.5f}")
128
+ ```
129
+
130
+ ### 3. Plotting convergence
131
+
132
+ ```python
133
+ import matplotlib.pyplot as plt
134
+
135
+ evals, costs = zip(*history) # solver: best cost so far
136
+ # evals, scores = zip(*search.history_) # ABCSearchCV: best CV score so far
137
+ plt.plot(evals, costs)
138
+ plt.xlabel("Function evaluations")
139
+ plt.ylabel("Best cost")
140
+ plt.title("ABC convergence")
141
+ plt.show()
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 📖 API Reference
147
+
148
+ ### `CanonicalABCSolver`
149
+
150
+ ```python
151
+ CanonicalABCSolver(objective_func, n_params, lb, ub,
152
+ pop_size=20, max_evals=1000, limit=None, random_state=None)
153
+ ```
154
+
155
+ | Parameter | Description |
156
+ |---|---|
157
+ | `objective_func` | Callable taking a 1-D `np.ndarray` and returning a scalar cost to **minimize** |
158
+ | `n_params` | Number of dimensions |
159
+ | `lb`, `ub` | Lower / upper bounds per dimension |
160
+ | `pop_size` | Number of food sources (SN) |
161
+ | `max_evals` | Total objective-function evaluation budget |
162
+ | `limit` | Trials without improvement before a source is abandoned (default: `pop_size × n_params`) |
163
+ | `random_state` | Seed (or `np.random.Generator`) for reproducible runs |
164
+
165
+ `NaN` objective values are treated as the worst possible cost.
166
+
167
+ `solve()` returns `(best_params, best_cost, history)`, where `history` is a list of `(eval_count, best_cost)` tuples.
168
+
169
+ ### `ABCSearchCV`
170
+
171
+ ```python
172
+ ABCSearchCV(estimator, param_space, cv=3, scoring="accuracy",
173
+ pop_size=20, max_evals=200, limit=None, random_state=None,
174
+ n_jobs=None, error_score=np.nan, refit=True, verbose=False)
175
+ ```
176
+
177
+ **`param_space`**: one entry per hyperparameter:
178
+
179
+ | Spec | Example |
180
+ |---|---|
181
+ | Continuous | `{"type": "float", "range": (0.01, 0.3)}` |
182
+ | Continuous, log scale | `{"type": "float", "range": (1e-4, 1e-1), "log": True}` |
183
+ | Integer (both ends inclusive) | `{"type": "int", "range": (2, 12)}` |
184
+ | Categorical | `{"type": "categorical", "choices": ["gini", "entropy"]}` |
185
+
186
+ - **`scoring`**: any scikit-learn scorer; higher is better, exactly like `GridSearchCV` (use `neg_*` scorers for errors).
187
+ - **`error_score`**: score given to candidates that fail to fit (a `RuntimeWarning` is issued); `"raise"` stops the search instead.
188
+ - **`n_jobs`**: parallel jobs inside each cross-validation.
189
+ - **After `fit`:** `best_params_`, `best_score_`, `best_estimator_` (when `refit=True`), `history_`, `results_` (every candidate with its score), `n_evals_`.
190
+ - **Methods:** `predict`, `predict_proba`, `score`.
191
+
192
+ ---
193
+
194
+ ## 🧠 How ABC Works
195
+
196
+ ABC models the foraging behaviour of a honey bee colony. Each **food source** is a candidate solution, and its **nectar amount** is the solution's fitness.
197
+
198
+ | Phase | Role |
199
+ |---|---|
200
+ | **Employed bees** | Each one improves its own food source by perturbing one dimension towards or away from a random neighbour: `v_ij = x_ij + φ·(x_ij − x_kj)`, `φ ∈ [−1, 1]`. Greedy selection keeps the better solution. |
201
+ | **Onlooker bees** | Pick sources with probability proportional to fitness (roulette wheel), so promising regions are exploited more. |
202
+ | **Scout bees** | A source that has not improved for `limit` trials is abandoned and replaced by a random one, which keeps exploration alive. |
203
+
204
+ Fitness is derived from cost as `1 / (1 + f)` for `f ≥ 0` and `1 + |f|` otherwise.
205
+
206
+ ---
207
+
208
+ ## 🔬 Case Study: Federated Learning on MedMNIST
209
+
210
+ The library was used to tune a **federated learning** setup on **PathMNIST** (colon pathology images, 9 classes). The data is split across **5 simulated hospitals** with a **Dirichlet distribution (α = 0.5)**, a non-IID setting where FedAvg with default hyperparameters struggles.
211
+
212
+ `CanonicalABCSolver` searched the clients' **learning rate** (0.001–0.1) and **SGD momentum** (0.0–0.9), scoring each candidate with a short 3-round FL simulation as a proxy task. The best configuration was then trained for 15 rounds:
213
+
214
+ | Method | LR | Momentum | Test accuracy (round 15) |
215
+ |---|---|---|---|
216
+ | Standard FedAvg | 0.010 | 0.50 | 78.05% |
217
+ | **ABC-tuned FedAvg** | **0.069** | **0.57** | **85.28%** (+7.2 pts) |
218
+
219
+ For reference, centralized training with the default hyperparameters reached 80.29%.
220
+
221
+ ![ABC vs. standard FedAvg](https://raw.githubusercontent.com/yusufkorkmazyigit/abc-optimizer-lib/main/examples/abc_fedavg_final_result.png)
222
+
223
+ 📓 [Full notebook and analysis](https://github.com/yusufkorkmazyigit/abc-optimizer-lib/blob/main/examples/Federated_Learning_MedMNIST_Optimization.ipynb) · Related repo: [fedLearningwithABC](https://github.com/yusufkorkmazyigit/fedLearningwithABC)
224
+
225
+ ---
226
+
227
+ ## 🗺️ Roadmap
228
+
229
+ - [x] Categorical and log-scaled hyperparameters
230
+ - [x] `random_state` for reproducibility
231
+ - [x] Unit tests and continuous integration
232
+ - [x] Parallel cross-validation (`n_jobs`)
233
+ - [ ] Benchmarks against `GridSearchCV`, `RandomizedSearchCV` and Optuna
234
+ - [ ] Parallel evaluation of candidates within a colony
235
+ - [x] PyPI release
236
+
237
+ Contributions and issues are welcome.
238
+
239
+ ---
240
+
241
+ ## 🧪 Development
242
+
243
+ ```bash
244
+ pip install -e ".[test]"
245
+ pytest
246
+ ```
247
+
248
+ See [CHANGELOG.md](https://github.com/yusufkorkmazyigit/abc-optimizer-lib/blob/main/CHANGELOG.md) for release notes.
249
+
250
+ ---
251
+
252
+ ## 📚 Reference
253
+
254
+ Karaboğa, D. (2005). *An Idea Based on Honey Bee Swarm for Numerical Optimization*. Technical Report TR06, Erciyes University, Engineering Faculty, Computer Engineering Department.
255
+
256
+ If you use this library in your research, please cite the original paper above and link to this repository.
257
+
258
+ ## 📝 License
259
+
260
+ Released under the [MIT License](https://github.com/yusufkorkmazyigit/abc-optimizer-lib/blob/main/LICENSE).
261
+
262
+ ---
263
+
264
+ <sub>Developed by [Yusuf Korkmazyiğit](https://github.com/yusufkorkmazyigit), Computer Engineering, Erciyes University.</sub>
@@ -0,0 +1,8 @@
1
+ abcopt/__init__.py,sha256=EDd2gahJX5L4l_SDiHjk6tOABGyaTc-0f4GgNsqZTiM,245
2
+ abcopt/core.py,sha256=6dj41EqrJa1q7A-HjClgzLZ7i3iwUYhxPpIEq5pjp3o,7012
3
+ abcopt/sklearn_wrapper.py,sha256=aXEy3btmRP5CYgEvKk7Z16XW2VawjpsC_Le5PhUxV3o,8505
4
+ abcopt-0.2.0.dist-info/licenses/LICENSE,sha256=wOfJ9vBqnqhMsyLneh-DYtLNV575mO_dk5OibDsBNUs,1076
5
+ abcopt-0.2.0.dist-info/METADATA,sha256=mgEiLDmVEoEbAeCK8nwgeSgR6Ir8zc2Ep3c8xhn0Y14,10417
6
+ abcopt-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
7
+ abcopt-0.2.0.dist-info/top_level.txt,sha256=MwHgrEg0Ocm1R6pZKoUo4j6H0LMuYOIm1nvddMZetVk,7
8
+ abcopt-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf Korkmazyiğit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ abcopt