parzen_window 0.0.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.
@@ -0,0 +1,22 @@
1
+ .idea
2
+ .idea/
3
+ .claude
4
+ .claude/
5
+ .vscode
6
+ .vscode/
7
+ /.idea
8
+ /.claude
9
+ /.vscode
10
+ CLAUDE.md
11
+ /scripts
12
+ /docs
13
+
14
+ # build artifacts
15
+ dist/
16
+ build/
17
+ *.egg-info/
18
+ __pycache__/
19
+ *.pyc
20
+
21
+ # uv
22
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andery Ferubko
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,224 @@
1
+ Metadata-Version: 2.5
2
+ Name: parzen_window
3
+ Version: 0.0.2
4
+ Summary: Parzen window classification with incremental learning, memory-efficient compaction and adaptive bandwidth.
5
+ Project-URL: Homepage, https://github.com/89605502155/parzen_window
6
+ Project-URL: Download, https://github.com/89605502155/parzen_window/archive/main.zip
7
+ Author-email: Andrey Ferubko <ferubko1999@yandex.ru>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: Implementation :: CPython
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: numpy
23
+ Requires-Dist: scikit-learn
24
+ Requires-Dist: scipy
25
+ Description-Content-Type: text/markdown
26
+
27
+ # parzen_window
28
+
29
+ [Русский](#русский) | [English](#english)
30
+
31
+ ---
32
+
33
+ ## English
34
+
35
+ `parzen_window` is a Python library implementing the Parzen window method for
36
+ classification, built on top of `numpy` and `scikit-learn` (`BaseEstimator`,
37
+ `ClassifierMixin`).
38
+
39
+ ### Features
40
+
41
+ - Five kernel shapes: `gaussian`, `epanechnikov`, `quartic`, `triangular`, `rectangular`.
42
+ - **Adaptive bandwidth** — each training point can get its own kernel width,
43
+ derived from the distance to its `k`-th nearest neighbor, instead of a
44
+ single fixed `h` for the whole dataset (a variable-kernel / "balloon"
45
+ estimator). This is the library's scientific contribution over plain
46
+ fixed-bandwidth Parzen window implementations: it widens the kernel in
47
+ sparse regions and narrows it in dense ones automatically.
48
+ - **Incremental learning** — `partial_fit()` extends the model with a new
49
+ batch of data without discarding what it already learned.
50
+ - **Dataset compaction** — `compact()` reduces memory usage by removing
51
+ training points that are deep inside their own class's territory (found
52
+ via convex-neighborhood analysis: a point is dropped only if all its
53
+ nearest neighbors share its label *and* it is far from the nearest point
54
+ of a different class). This shrinks the stored dataset with minimal
55
+ impact on the decision boundary.
56
+ - **Thread-safe compaction with a call scheduler** — while `compact()` is
57
+ running, the training data is locked for writes. `predict()` and
58
+ `partial_fit()` calls from other threads do not fail during this time:
59
+ they are queued and executed automatically as soon as compaction
60
+ finishes.
61
+ - **`.npz` persistence** — `save()`/`load()` for backups and moving a model
62
+ between machines.
63
+ - Fully compatible with `pickle`/`joblib`.
64
+
65
+ ### Installation
66
+
67
+ ```bash
68
+ pip install parzen_window
69
+ ```
70
+
71
+ ### Quick start
72
+
73
+ ```python
74
+ from parzen_window import ParzenWindowClassifier
75
+ from sklearn.datasets import load_iris
76
+ from sklearn.model_selection import train_test_split
77
+
78
+ X, y = load_iris(return_X_y=True)
79
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
80
+
81
+ model = ParzenWindowClassifier(h=1.0, kernel="quartic")
82
+ model.fit(X_train, y_train)
83
+ predictions = model.predict(X_test)
84
+ probabilities = model.predict_proba(X_test)
85
+ ```
86
+
87
+ Incremental learning:
88
+
89
+ ```python
90
+ model = ParzenWindowClassifier(h=0.5, kernel="gaussian")
91
+ model.fit(X_batch_1, y_batch_1)
92
+ model.partial_fit(X_batch_2, y_batch_2) # extends, does not discard X_batch_1
93
+ ```
94
+
95
+ Adaptive bandwidth:
96
+
97
+ ```python
98
+ model = ParzenWindowClassifier(
99
+ h=1.0, kernel="gaussian", adaptive_bandwidth=True, bandwidth_neighbors=5
100
+ )
101
+ model.fit(X_train, y_train)
102
+ ```
103
+
104
+ Compacting a large dataset to cut memory usage by ~25%:
105
+
106
+ ```python
107
+ removed = model.compact(0.25) # returns the number of points actually removed
108
+ ```
109
+
110
+ Backups:
111
+
112
+ ```python
113
+ model.save("model.npz")
114
+ restored = ParzenWindowClassifier.load("model.npz")
115
+ ```
116
+
117
+ ### Development
118
+
119
+ The project uses [uv](https://docs.astral.sh/uv/) for dependency management.
120
+
121
+ ```bash
122
+ uv sync --dev # install dependencies
123
+ uv run ruff check . # lint
124
+ uv run ruff format . # format
125
+ uv build # build sdist + wheel
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Русский
131
+
132
+ `parzen_window` — библиотека на Python, реализующая метод Парзеновского окна
133
+ для классификации, построенная поверх `numpy` и `scikit-learn`
134
+ (`BaseEstimator`, `ClassifierMixin`).
135
+
136
+ ### Возможности
137
+
138
+ - Пять видов ядер: `gaussian`, `epanechnikov`, `quartic`, `triangular`, `rectangular`.
139
+ - **Адаптивная ширина окна** — каждая точка обучающей выборки может получить
140
+ собственную ширину ядра, вычисленную по расстоянию до её `k`-го ближайшего
141
+ соседа, вместо единого фиксированного `h` на весь датасет (вариант
142
+ variable-kernel / "balloon"-оценки). Это научная новизна библиотеки по
143
+ сравнению с обычными реализациями Парзеновского окна с фиксированной
144
+ шириной: окно автоматически расширяется в разреженных областях и
145
+ сужается в плотных.
146
+ - **Дообучение** — `partial_fit()` расширяет модель новым батчем данных, не
147
+ теряя уже накопленное.
148
+ - **Разрядка датасета** — `compact()` уменьшает потребление памяти, удаляя
149
+ точки обучающей выборки, которые лежат глубоко на территории своего
150
+ класса (метод анализа выпуклых окрестностей: точка удаляется, только
151
+ если все её ближайшие соседи имеют ту же метку класса *и* она далека от
152
+ ближайшей точки другого класса). Это сокращает хранимый датасет с
153
+ минимальным влиянием на границу между классами.
154
+ - **Потокобезопасная разрядка с планировщиком вызовов** — пока выполняется
155
+ `compact()`, обучающие данные заблокированы для записи. Вызовы
156
+ `predict()` и `partial_fit()` из других потоков в это время не падают:
157
+ они встают в очередь и выполняются автоматически сразу после завершения
158
+ разрядки.
159
+ - **Сохранение в `.npz`** — `save()`/`load()` для бэкапов и переноса модели
160
+ между устройствами.
161
+ - Полная совместимость с `pickle`/`joblib`.
162
+
163
+ ### Установка
164
+
165
+ ```bash
166
+ pip install parzen_window
167
+ ```
168
+
169
+ ### Быстрый старт
170
+
171
+ ```python
172
+ from parzen_window import ParzenWindowClassifier
173
+ from sklearn.datasets import load_iris
174
+ from sklearn.model_selection import train_test_split
175
+
176
+ X, y = load_iris(return_X_y=True)
177
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
178
+
179
+ model = ParzenWindowClassifier(h=1.0, kernel="quartic")
180
+ model.fit(X_train, y_train)
181
+ predictions = model.predict(X_test)
182
+ probabilities = model.predict_proba(X_test)
183
+ ```
184
+
185
+ Дообучение:
186
+
187
+ ```python
188
+ model = ParzenWindowClassifier(h=0.5, kernel="gaussian")
189
+ model.fit(X_batch_1, y_batch_1)
190
+ model.partial_fit(X_batch_2, y_batch_2) # расширяет, не теряя X_batch_1
191
+ ```
192
+
193
+ Адаптивная ширина окна:
194
+
195
+ ```python
196
+ model = ParzenWindowClassifier(
197
+ h=1.0, kernel="gaussian", adaptive_bandwidth=True, bandwidth_neighbors=5
198
+ )
199
+ model.fit(X_train, y_train)
200
+ ```
201
+
202
+ Разрядка большого датасета, чтобы сократить потребление памяти примерно на 25%:
203
+
204
+ ```python
205
+ removed = model.compact(0.25) # возвращает число реально удалённых точек
206
+ ```
207
+
208
+ Бэкапы:
209
+
210
+ ```python
211
+ model.save("model.npz")
212
+ restored = ParzenWindowClassifier.load("model.npz")
213
+ ```
214
+
215
+ ### Разработка
216
+
217
+ Проект использует [uv](https://docs.astral.sh/uv/) для управления зависимостями.
218
+
219
+ ```bash
220
+ uv sync --dev # установить зависимости
221
+ uv run ruff check . # линтер
222
+ uv run ruff format . # форматирование
223
+ uv build # сборка sdist + wheel
224
+ ```
@@ -0,0 +1,198 @@
1
+ # parzen_window
2
+
3
+ [Русский](#русский) | [English](#english)
4
+
5
+ ---
6
+
7
+ ## English
8
+
9
+ `parzen_window` is a Python library implementing the Parzen window method for
10
+ classification, built on top of `numpy` and `scikit-learn` (`BaseEstimator`,
11
+ `ClassifierMixin`).
12
+
13
+ ### Features
14
+
15
+ - Five kernel shapes: `gaussian`, `epanechnikov`, `quartic`, `triangular`, `rectangular`.
16
+ - **Adaptive bandwidth** — each training point can get its own kernel width,
17
+ derived from the distance to its `k`-th nearest neighbor, instead of a
18
+ single fixed `h` for the whole dataset (a variable-kernel / "balloon"
19
+ estimator). This is the library's scientific contribution over plain
20
+ fixed-bandwidth Parzen window implementations: it widens the kernel in
21
+ sparse regions and narrows it in dense ones automatically.
22
+ - **Incremental learning** — `partial_fit()` extends the model with a new
23
+ batch of data without discarding what it already learned.
24
+ - **Dataset compaction** — `compact()` reduces memory usage by removing
25
+ training points that are deep inside their own class's territory (found
26
+ via convex-neighborhood analysis: a point is dropped only if all its
27
+ nearest neighbors share its label *and* it is far from the nearest point
28
+ of a different class). This shrinks the stored dataset with minimal
29
+ impact on the decision boundary.
30
+ - **Thread-safe compaction with a call scheduler** — while `compact()` is
31
+ running, the training data is locked for writes. `predict()` and
32
+ `partial_fit()` calls from other threads do not fail during this time:
33
+ they are queued and executed automatically as soon as compaction
34
+ finishes.
35
+ - **`.npz` persistence** — `save()`/`load()` for backups and moving a model
36
+ between machines.
37
+ - Fully compatible with `pickle`/`joblib`.
38
+
39
+ ### Installation
40
+
41
+ ```bash
42
+ pip install parzen_window
43
+ ```
44
+
45
+ ### Quick start
46
+
47
+ ```python
48
+ from parzen_window import ParzenWindowClassifier
49
+ from sklearn.datasets import load_iris
50
+ from sklearn.model_selection import train_test_split
51
+
52
+ X, y = load_iris(return_X_y=True)
53
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
54
+
55
+ model = ParzenWindowClassifier(h=1.0, kernel="quartic")
56
+ model.fit(X_train, y_train)
57
+ predictions = model.predict(X_test)
58
+ probabilities = model.predict_proba(X_test)
59
+ ```
60
+
61
+ Incremental learning:
62
+
63
+ ```python
64
+ model = ParzenWindowClassifier(h=0.5, kernel="gaussian")
65
+ model.fit(X_batch_1, y_batch_1)
66
+ model.partial_fit(X_batch_2, y_batch_2) # extends, does not discard X_batch_1
67
+ ```
68
+
69
+ Adaptive bandwidth:
70
+
71
+ ```python
72
+ model = ParzenWindowClassifier(
73
+ h=1.0, kernel="gaussian", adaptive_bandwidth=True, bandwidth_neighbors=5
74
+ )
75
+ model.fit(X_train, y_train)
76
+ ```
77
+
78
+ Compacting a large dataset to cut memory usage by ~25%:
79
+
80
+ ```python
81
+ removed = model.compact(0.25) # returns the number of points actually removed
82
+ ```
83
+
84
+ Backups:
85
+
86
+ ```python
87
+ model.save("model.npz")
88
+ restored = ParzenWindowClassifier.load("model.npz")
89
+ ```
90
+
91
+ ### Development
92
+
93
+ The project uses [uv](https://docs.astral.sh/uv/) for dependency management.
94
+
95
+ ```bash
96
+ uv sync --dev # install dependencies
97
+ uv run ruff check . # lint
98
+ uv run ruff format . # format
99
+ uv build # build sdist + wheel
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Русский
105
+
106
+ `parzen_window` — библиотека на Python, реализующая метод Парзеновского окна
107
+ для классификации, построенная поверх `numpy` и `scikit-learn`
108
+ (`BaseEstimator`, `ClassifierMixin`).
109
+
110
+ ### Возможности
111
+
112
+ - Пять видов ядер: `gaussian`, `epanechnikov`, `quartic`, `triangular`, `rectangular`.
113
+ - **Адаптивная ширина окна** — каждая точка обучающей выборки может получить
114
+ собственную ширину ядра, вычисленную по расстоянию до её `k`-го ближайшего
115
+ соседа, вместо единого фиксированного `h` на весь датасет (вариант
116
+ variable-kernel / "balloon"-оценки). Это научная новизна библиотеки по
117
+ сравнению с обычными реализациями Парзеновского окна с фиксированной
118
+ шириной: окно автоматически расширяется в разреженных областях и
119
+ сужается в плотных.
120
+ - **Дообучение** — `partial_fit()` расширяет модель новым батчем данных, не
121
+ теряя уже накопленное.
122
+ - **Разрядка датасета** — `compact()` уменьшает потребление памяти, удаляя
123
+ точки обучающей выборки, которые лежат глубоко на территории своего
124
+ класса (метод анализа выпуклых окрестностей: точка удаляется, только
125
+ если все её ближайшие соседи имеют ту же метку класса *и* она далека от
126
+ ближайшей точки другого класса). Это сокращает хранимый датасет с
127
+ минимальным влиянием на границу между классами.
128
+ - **Потокобезопасная разрядка с планировщиком вызовов** — пока выполняется
129
+ `compact()`, обучающие данные заблокированы для записи. Вызовы
130
+ `predict()` и `partial_fit()` из других потоков в это время не падают:
131
+ они встают в очередь и выполняются автоматически сразу после завершения
132
+ разрядки.
133
+ - **Сохранение в `.npz`** — `save()`/`load()` для бэкапов и переноса модели
134
+ между устройствами.
135
+ - Полная совместимость с `pickle`/`joblib`.
136
+
137
+ ### Установка
138
+
139
+ ```bash
140
+ pip install parzen_window
141
+ ```
142
+
143
+ ### Быстрый старт
144
+
145
+ ```python
146
+ from parzen_window import ParzenWindowClassifier
147
+ from sklearn.datasets import load_iris
148
+ from sklearn.model_selection import train_test_split
149
+
150
+ X, y = load_iris(return_X_y=True)
151
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
152
+
153
+ model = ParzenWindowClassifier(h=1.0, kernel="quartic")
154
+ model.fit(X_train, y_train)
155
+ predictions = model.predict(X_test)
156
+ probabilities = model.predict_proba(X_test)
157
+ ```
158
+
159
+ Дообучение:
160
+
161
+ ```python
162
+ model = ParzenWindowClassifier(h=0.5, kernel="gaussian")
163
+ model.fit(X_batch_1, y_batch_1)
164
+ model.partial_fit(X_batch_2, y_batch_2) # расширяет, не теряя X_batch_1
165
+ ```
166
+
167
+ Адаптивная ширина окна:
168
+
169
+ ```python
170
+ model = ParzenWindowClassifier(
171
+ h=1.0, kernel="gaussian", adaptive_bandwidth=True, bandwidth_neighbors=5
172
+ )
173
+ model.fit(X_train, y_train)
174
+ ```
175
+
176
+ Разрядка большого датасета, чтобы сократить потребление памяти примерно на 25%:
177
+
178
+ ```python
179
+ removed = model.compact(0.25) # возвращает число реально удалённых точек
180
+ ```
181
+
182
+ Бэкапы:
183
+
184
+ ```python
185
+ model.save("model.npz")
186
+ restored = ParzenWindowClassifier.load("model.npz")
187
+ ```
188
+
189
+ ### Разработка
190
+
191
+ Проект использует [uv](https://docs.astral.sh/uv/) для управления зависимостями.
192
+
193
+ ```bash
194
+ uv sync --dev # установить зависимости
195
+ uv run ruff check . # линтер
196
+ uv run ruff format . # форматирование
197
+ uv build # сборка sdist + wheel
198
+ ```
@@ -0,0 +1,7 @@
1
+ from .core import ParzenWindowClassifier
2
+
3
+ __author__ = "Andrey Ferubko"
4
+ __version__ = "0.0.2"
5
+ __email__ = "ferubko1999@yandex.ru"
6
+
7
+ __all__ = ["ParzenWindowClassifier"]
@@ -0,0 +1,83 @@
1
+ """Dataset compaction via convex-neighborhood analysis.
2
+
3
+ A training point contributes almost nothing to the decision boundary when
4
+ it is (a) surrounded entirely by same-class neighbors -- i.e. it sits deep
5
+ inside a convex, single-class neighborhood -- and (b) far from the nearest
6
+ point of any other class. Such points are safe to drop to free up memory,
7
+ since the kernel weight they contribute is dominated by points closer to
8
+ the actual boundary.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import numpy as np
14
+ from sklearn.neighbors import NearestNeighbors
15
+
16
+
17
+ def compute_removal_scores(X: np.ndarray, y: np.ndarray, n_neighbors: int) -> np.ndarray:
18
+ """Score each point by how safe it is to remove (higher = safer).
19
+
20
+ A point scores 0 (never removed) unless all of its ``n_neighbors``
21
+ nearest neighbors share its label; otherwise its score is the distance
22
+ to the nearest point of a *different* class -- a proxy for how far it
23
+ sits from the decision boundary.
24
+ """
25
+ n_samples = len(X)
26
+ k = min(n_neighbors, n_samples - 1)
27
+ if k < 1:
28
+ return np.zeros(n_samples)
29
+
30
+ neighbors = NearestNeighbors(n_neighbors=k + 1).fit(X)
31
+ _, indices = neighbors.kneighbors(X)
32
+ indices = indices[:, 1:] # drop the point itself (distance 0)
33
+ homogeneous = (y[indices] == y[:, None]).all(axis=1)
34
+
35
+ margin = np.zeros(n_samples)
36
+ for label in np.unique(y):
37
+ own, other = y == label, y != label
38
+ if not other.any() or not own.any():
39
+ continue
40
+ boundary = NearestNeighbors(n_neighbors=1).fit(X[other])
41
+ boundary_distance, _ = boundary.kneighbors(X[own])
42
+ margin[own] = boundary_distance[:, 0]
43
+
44
+ return np.where(homogeneous, margin, 0.0)
45
+
46
+
47
+ def select_points_to_remove(
48
+ X: np.ndarray,
49
+ y: np.ndarray,
50
+ reduction_fraction: float,
51
+ n_neighbors: int = 5,
52
+ min_per_class: int = 2,
53
+ ) -> np.ndarray:
54
+ """Pick indices that can be dropped without touching the decision boundary.
55
+
56
+ Points are removed greedily, safest (highest score) first, until either
57
+ ``reduction_fraction`` of the dataset has been removed, points with a
58
+ zero score are reached (mixed neighborhood or near the boundary), or a
59
+ class would drop below ``min_per_class`` remaining points.
60
+ """
61
+ if not 0.0 < reduction_fraction < 1.0:
62
+ raise ValueError("reduction_fraction must be in (0, 1)")
63
+
64
+ n_samples = len(X)
65
+ target_removals = int(n_samples * reduction_fraction)
66
+ if target_removals <= 0:
67
+ return np.array([], dtype=int)
68
+
69
+ scores = compute_removal_scores(X, y, n_neighbors)
70
+ order = np.argsort(scores)[::-1]
71
+ remaining_per_class = {label: int(np.sum(y == label)) for label in np.unique(y)}
72
+
73
+ to_remove = []
74
+ for idx in order:
75
+ if len(to_remove) >= target_removals or scores[idx] <= 0:
76
+ break
77
+ label = y[idx]
78
+ if remaining_per_class[label] - 1 < min_per_class:
79
+ continue
80
+ remaining_per_class[label] -= 1
81
+ to_remove.append(idx)
82
+
83
+ return np.array(sorted(to_remove), dtype=int)