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.
- parzen_window-0.0.2/.gitignore +22 -0
- parzen_window-0.0.2/LICENSE +21 -0
- parzen_window-0.0.2/PKG-INFO +224 -0
- parzen_window-0.0.2/README.md +198 -0
- parzen_window-0.0.2/parzen_window/__init__.py +7 -0
- parzen_window-0.0.2/parzen_window/compaction.py +83 -0
- parzen_window-0.0.2/parzen_window/core.py +219 -0
- parzen_window-0.0.2/parzen_window/io.py +33 -0
- parzen_window-0.0.2/parzen_window/kernels.py +45 -0
- parzen_window-0.0.2/parzen_window/scheduler.py +82 -0
- parzen_window-0.0.2/pyproject.toml +52 -0
- parzen_window-0.0.2/uv.lock +772 -0
|
@@ -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,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)
|