wknn_1
A scikit-learn compatible weighted k-Nearest-Neighbours regressor with learnable, transferable weights, online fine-tuning, regularization, an adaptive neighbourhood size, and training-set condensing.
Документация доступна на двух языках. English first, русская версия ниже.
- English
- Why this library
- Installation
- Quick start
- Using it for classification
- Constructor parameters
- Core methods
- Learnable & transferable weights (v0.2)
- Online / batch fine-tuning (v0.2)
- Regularization (v0.2)
- Adaptive neighbourhood size (v0.2)
- Condensing the training set (v0.2)
- Weight functions
- Persistence
- Exceptions
- scikit-learn integration
- Fitted attributes
- Development
- Русская версия
English
Why this library
Classical kNN in scikit-learn already supports distance weighting, custom weight
callables and custom metrics. wknn_1 adds the pieces a non-parametric kNN
cannot offer, because here the neighbour weights become a learnable
parameter:
- a learnable, transferable per-rank weight vector (train it, save it to
.npz, reuse it on a related dataset); - online / batch fine-tuning via
partial_fit(scikit-learn's kNN has none); - regularization of both the prediction and the learned weights;
- an adaptive neighbourhood size bounded by a max deviation parameter;
- condensing to keep an ever-growing memorised set bounded;
- a per-dimension weighting via
MixedWeightFunction(a different formula per predictor column — sklearn's callable only sees scalar distances).
It stays a drop-in scikit-learn estimator: Pipeline, clone, GridSearchCV,
get_params / set_params and score all work.
Installation
pip install wknn_1
# or, with uv:
uv add wknn_1
From a local build:
pip install dist/wknn_1-0.2.0-py3-none-any.whl
Requirements: Python ≥ 3.8, numpy, scikit-learn.
Quick start
import numpy as np
from wknn_1 import WKNNRegressor
X = np.random.rand(200, 3)
y = X @ np.array([1.0, 2.0, -1.0]) # a linear target, for illustration
model = WKNNRegressor(n_neighbors=7, weights="inverse").fit(X, y)
y_pred = model.predict(X[:5])
print(y_pred)
print("R^2:", model.score(X, y)) # RegressorMixin.score
Inputs must be numpy arrays. Passing a Python list raises InvalidInputError
(the only exception is a per-rank weight vector, which may be a list).
Using it for classification
wknn_1 is a regressor. To classify, regress on the numeric class labels and
round to the nearest label. A minimal adapter:
import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixin
from wknn_1 import WKNNRegressor
class WKNNClassifier(BaseEstimator, ClassifierMixin):
def __init__(self, n_neighbors=5, weights="inverse", **kw):
self.n_neighbors, self.weights, self.kw = n_neighbors, weights, kw
def fit(self, X, y):
self.classes_ = np.unique(y)
self._lo, self._hi = self.classes_.min(), self.classes_.max()
self._r = WKNNRegressor(self.n_neighbors, weights=self.weights,
**self.kw).fit(np.asarray(X, float), y.astype(float))
return self
def predict(self, X):
raw = self._r.predict(np.asarray(X, float))
return np.clip(np.rint(raw), self._lo, self._hi).astype(int)
Constructor parameters
WKNNRegressor(
n_neighbors=5, *, weights="linear", metric="minkowski", p=2,
standardize=True, regularization=0.0, max_neighbor_deviation=0,
learning_rate=0.5, momentum=0.9, random_state=None,
)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
n_neighbors |
int | 5 |
Base number of neighbours k per prediction. |
weights |
see below | "linear" |
How neighbours are weighted: a name, a BaseWeightFunction, a callable, or a per-rank vector. |
metric |
str | "minkowski" |
Distance metric, forwarded to NearestNeighbors. |
p |
int | 2 |
Minkowski order (2 = Euclidean, 1 = Manhattan). |
standardize |
bool | True |
Standardize predictors (zero mean, unit variance) before neighbour search. |
regularization |
float | 0.0 |
λ ≥ 0. Shrinks predictions toward the global mean and learned weights toward uniform. |
max_neighbor_deviation |
int | 0 |
d ≥ 0. Allows the per-query neighbourhood to vary in [k−d, k+d]. 0 = fixed k. |
learning_rate |
float | 0.5 |
Nesterov GD step η for weight learning. |
momentum |
float | 0.9 |
Nesterov GD momentum μ. |
random_state |
int / None | None |
Seed for the internal split in fit_weights. |
Core methods
fit(X, y) -> self
Fit on numpy predictors X (2-D) and 1-D responses y. Validates input, checks
n_neighbors ≤ n_samples, standardizes, builds the neighbour index, and resolves
the weight spec (so a bad spec fails here, not at predict time).
model = WKNNRegressor(5, weights="gaussian").fit(X, y)
predict(X) -> np.ndarray
Predict 1-D targets for X. Applies regularization and adaptive-k if configured.
preds = model.predict(X_new)
refit(X, y) -> self
Retrain from scratch, discarding all previous state (equivalent to clearing the
model and calling fit again; reads clearly at call sites).
model.refit(X2, y2)
score(X, y, sample_weight=None) -> float
Coefficient of determination R² (from RegressorMixin).
r2 = model.score(X_test, y_test)
Learnable & transferable weights (v0.2)
The prediction is a differentiable function of a per-rank weight vector w ∈ ℝk, so w can be learned by Nesterov accelerated gradient descent, saved, and reused on a related dataset.
fit_weights(X=None, y=None, *, epochs=100, validation_fraction=0.3, record=False)
Learn the per-rank vector. With X/y omitted, the stored training set is split
internally (a temporary index on one part, the learning signal from the other) so
the solution does not collapse to 1-NN. Sets weight_vector_ and switches
prediction to use it. Returns the loss history when record=True, else self.
model = WKNNRegressor(7, weights="inverse", regularization=0.05).fit(X, y)
history = model.fit_weights(epochs=100, record=True)
print("loss:", history[0], "->", history[-1])
print("learned weights:", model.weight_vector_)
export_weights(path) -> path / import_weights(path) -> self
Save / load only the learned weight vector as .npz — a small portable
artefact for transfer learning.
model.export_weights("weights.npz")
fresh = WKNNRegressor(7, weights="inverse").fit(X_similar, y_similar)
fresh.import_weights("weights.npz") # reuse the learned weights
transfer_weights(source) -> self
Copy the learned vector from another fitted model.
target.transfer_weights(model)
set_weight_vector(w) -> self
Install a weight vector directly; its length must equal n_neighbors.
model.set_weight_vector(np.array([0.4, 0.25, 0.15, 0.1, 0.1])) # k == 5
Online / batch fine-tuning (v0.2)
partial_fit(X, y, classes=None, *, epochs=1) -> self
Incremental training, like a neural-network step. The first call behaves like
fit; later calls learn on the new batch as a held-out signal (before adding it),
then append the batch to memory and rebuild the index. n_samples_ grows with
each batch.
About the signature. The three arguments after X, y are there for
different reasons, so none of them is accidental:
epochs(functional) — how many gradient fine-tuning steps to take on the new batch.epochs=1is a single online step; larger values do heavier batch fine-tuning. This is a real knob you will use (e.g.epochs=3when a batch carries more information).classes=None(compatibility only, ignored) — scikit-learn'spartial_fitconvention passes the full label set on the first call so a classifier can size its output.wknn_1is a regressor and never reads this argument; it is accepted solely so the method matches the scikit-learnpartial_fit(X, y, classes=...)signature and drops into tools that call it that way. You can always omit it.*— the bare star simply marks everything after it as keyword-only, soepochsmust be written asepochs=3, never as a bare positional3. This prevents a call likepartial_fit(X, y, something, 3)from silently binding the wrong value toclasses.
model = WKNNRegressor(7, weights="inverse", regularization=0.05)
model.partial_fit(X0, y0) # ~ fit
for Xb, yb in batches:
model.partial_fit(Xb, yb, epochs=3) # fine-tune + grow memory
print("memory size:", model.n_samples_)
Regularization (v0.2)
Set regularization=λ (λ ≥ 0). It acts in two places:
- prediction — a scale-invariant shrinkage toward the global mean
μ_y:y_reg = (y_hat + λ·μ_y) / (1 + λ); - weight learning — an L2 penalty pulling the learned weights toward the uniform vector, which reduces overfitting and improves transfer.
model = WKNNRegressor(7, weights="inverse", regularization=0.3).fit(X, y)
Adaptive neighbourhood size (v0.2)
Set max_neighbor_deviation=d. For each query the neighbourhood size is chosen in
[k−d, k+d] to minimise the variance of neighbour targets — the region shrinks
where a tight, consistent cluster exists and expands where neighbours disagree.
d = 0 reproduces fixed-k behaviour exactly.
model = WKNNRegressor(5, weights="inverse", max_neighbor_deviation=3).fit(X, y)
Condensing the training set (v0.2)
condense(ratio) -> self
Remove a fraction ratio ∈ (0, 1) of memorised points. Redundant interior points
in dense same-class regions are dropped first; boundary points (with other-class
neighbours) are protected; removal is stratified so no class is wiped out. Lets an
ever-growing stream stay bounded when paired with partial_fit.
condensegroups points by theiry_value, so it is designed for discrete / class-like labels (the classification setting). With a continuous target, every point is its own group and nothing is removed.
y_cls = (X[:, 0] > 0.5).astype(float) # class-like labels
model = WKNNRegressor(7, weights="inverse").fit(X, y_cls)
model.condense(0.4) # drop ~40% of points
print("kept:", model.n_samples_)
Weight functions
The weights parameter accepts four kinds of specification.
1. A named standard function. One of:
| Name | Formula (d = neighbour distance) | Notes |
|---|---|---|
uniform |
w = 1 |
Reduces to plain kNN averaging. |
linear |
w = 1 − (d − lo)/(hi − lo) |
Nearest → 1, farthest → 0; no hyperparameters. |
inverse / distance |
`w = 1 / ( | d |
exponential |
`w = exp(−alpha· | d |
gaussian |
w = exp(−d² / (2·sigma²)) |
RBF kernel; sigma controls width. |
WKNNRegressor(5, weights="inverse").fit(X, y)
2. A StandardWeightFunction instance — the named functions with explicit
options:
from wknn_1 import StandardWeightFunction
wf = StandardWeightFunction("gaussian", sigma=0.5, aggregate="mean",
center=False, scale=True, normalize=False)
WKNNRegressor(5, weights=wf).fit(X, y)
Constructor: StandardWeightFunction(formula="linear", *, center=False, scale=True, normalize=False, aggregate="mean", alpha=1.0, sigma=1.0, eps=1e-12).
center / scale / normalize are applied to the distances (along the
neighbour axis); aggregate ∈ {"mean", "sum", "product"} collapses the
per-feature weights into one weight per neighbour.
3. A MixedWeightFunction — a different formula per predictor dimension
(not possible with sklearn's callable, which only sees scalar distances):
from wknn_1 import MixedWeightFunction, StandardWeightFunction
mixed = MixedWeightFunction([
StandardWeightFunction("linear"), # dimension 0
StandardWeightFunction("gaussian", sigma=0.3), # dimension 1
], aggregate="product")
WKNNRegressor(5, weights=mixed).fit(X[:, :2], y) # X has 2 columns here
4. A per-rank vector (list or ndarray of length n_neighbors) — the i-th
nearest neighbour gets weight w[i]:
WKNNRegressor(3, weights=[0.6, 0.3, 0.1]).fit(X, y)
Other weight helpers.
FixedFeatureWeightFunction(feature_weights, *, eps=1e-12) — a kernel driven by a
fixed per-feature importance vector v:
w = 1 / (1 + Σ_j v_j·|Δ_j| + eps).
from wknn_1 import FixedFeatureWeightFunction
wf = FixedFeatureWeightFunction(np.array([2.0, 0.5, 1.0])) # 3 features
WKNNRegressor(5, weights=wf).fit(X, y)
RankWeightFunction(rank_weights) — the explicit per-rank form used internally by
option 4.
Registry
from wknn_1 import list_weight_functions, register_weight_function, get_weight_function
list_weight_functions()
# ['distance', 'exponential', 'gaussian', 'inverse', 'linear', 'uniform']
# register your own named factory:
from wknn_1 import StandardWeightFunction
register_weight_function("sharp_gauss",
lambda: StandardWeightFunction("gaussian", sigma=0.2),
overwrite=True)
WKNNRegressor(5, weights="sharp_gauss").fit(X, y)
# resolve a spec to a concrete function (mostly internal):
fn = get_weight_function("inverse", n_neighbors=5)
Persistence
save_weights(path, fmt=None) -> Path / load_weights(path, fmt=None) -> self
Save / load the full fitted state (standardized training matrix, targets,
scaler, params, and any learned weight_vector_). Format is chosen by fmt or by
the file extension.
| Format | Extension | Notes |
|---|---|---|
| npz | .npz |
Recommended; numpy-native, portable. |
| pickle | .pkl / .pickle |
Preserves arbitrary callable weight specs. |
| json | .json |
Human-readable; callable specs stored as a placeholder. |
model.save_weights("model.npz")
restored = WKNNRegressor().load_weights("model.npz")
np.allclose(model.predict(X[:5]), restored.predict(X[:5])) # True
A callable
weightsspec cannot be serialized to npz/json (only a placeholder is stored). Use pickle, or re-attach the callable viaset_paramsafter load.
delete(*, remove_files=True) -> None
Tear down the model: remove on-disk files it created, drop learned weights, fitted
attributes and metadata. Hyperparameters are kept, so the object is ready for a
fresh fit.
model.delete() # also deletes files saved by save_weights
Exceptions
All inherit from WKNNError.
| Exception | Raised when |
|---|---|
WKNNError |
Base class for every library error. |
InvalidInputError |
Non-numpy input, wrong shapes, bad n_neighbors, bad hyperparameters. |
NotFittedError |
predict (etc.) called before fit / load_weights. |
WeightFunctionError |
A weight spec is unknown or a callable returns a wrong shape. |
PersistenceError |
Save/load fails, or transfer/export without a learned vector. |
from wknn_1 import NotFittedError
try:
WKNNRegressor().predict(X)
except NotFittedError as e:
print("fit first:", e)
scikit-learn integration
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GridSearchCV
pipe = make_pipeline(StandardScaler(), WKNNRegressor())
grid = GridSearchCV(pipe, {
"wknnregressor__n_neighbors": [3, 5, 7],
"wknnregressor__weights": ["uniform", "inverse", "gaussian"],
"wknnregressor__regularization": [0.0, 0.3],
})
grid.fit(X, y)
print(grid.best_params_)
clone, get_params and set_params behave as expected because only the
estimator defines __init__ and it merely stores its arguments.
Fitted attributes
Available after fit:
| Attribute | Meaning |
|---|---|
n_features_in_ |
Number of predictor columns seen during fit. |
n_samples_ |
Current size of the memorised set (grows via partial_fit, shrinks via condense). |
Xt_ |
Standardized training matrix. |
y_ |
Stored 1-D targets. |
scaler_ |
The fitted standardizer (or None if standardize=False). |
weight_vector_ |
The learned per-rank vector (present after fit_weights / import_weights / set_weight_vector). |
Development
# editable install with dev extras
pip install -e .
pip install pytest
# run the test suite (45 tests: 35 core + 10 v0.2 features)
pytest -q
# build wheel + sdist
python -m build # or: uv build
Русская версия
Библиотека wknn_1 — совместимый со scikit-learn регрессор на основе
взвешенного метода k ближайших соседей с обучаемыми и переносимыми весами,
онлайн-дообучением, регуляризацией, адаптивным размером окрестности и
прореживанием обучающей выборки.
Зачем нужна библиотека
Классический kNN в scikit-learn уже умеет взвешивать по расстоянию, принимать
пользовательскую весовую функцию и произвольную метрику. wknn_1 добавляет то,
чего у непараметрического kNN нет в принципе, — потому что здесь веса соседей
становятся обучаемым параметром:
- обучаемый и переносимый вектор весов по рангам (обучить, сохранить в
.npz, применить к похожему набору); - онлайн- и батч-дообучение через
partial_fit(у kNN из sklearn его нет); - регуляризация предсказания и обучаемых весов;
- адаптивное число соседей, ограниченное параметром максимального отклонения;
- прореживание (
condense), чтобы бесконечно растущая память оставалась ограниченной; - своя формула на каждое измерение через
MixedWeightFunction(callable в sklearn видит только скалярные расстояния и так не умеет).
При этом модель остаётся полноценным эстиматором scikit-learn: работают
Pipeline, clone, GridSearchCV, get_params / set_params и score.
Установка
pip install wknn_1
# или через uv:
uv add wknn_1
Из локальной сборки:
pip install dist/wknn_1-0.2.0-py3-none-any.whl
Требования: Python ≥ 3.8, numpy, scikit-learn.
Быстрый старт
import numpy as np
from wknn_1 import WKNNRegressor
X = np.random.rand(200, 3)
y = X @ np.array([1.0, 2.0, -1.0])
model = WKNNRegressor(n_neighbors=7, weights="inverse").fit(X, y)
print(model.predict(X[:5]))
print("R^2:", model.score(X, y))
Входные данные — только numpy-массивы. Передача списка вызывает
InvalidInputError (исключение — вектор весов по рангам, он может быть списком).
Применение для классификации
wknn_1 — это регрессор. Для классификации выполняют регрессию по числовым
меткам классов и округляют к ближайшей метке. Минимальный адаптер:
import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixin
from wknn_1 import WKNNRegressor
class WKNNClassifier(BaseEstimator, ClassifierMixin):
def __init__(self, n_neighbors=5, weights="inverse", **kw):
self.n_neighbors, self.weights, self.kw = n_neighbors, weights, kw
def fit(self, X, y):
self.classes_ = np.unique(y)
self._lo, self._hi = self.classes_.min(), self.classes_.max()
self._r = WKNNRegressor(self.n_neighbors, weights=self.weights,
**self.kw).fit(np.asarray(X, float), y.astype(float))
return self
def predict(self, X):
raw = self._r.predict(np.asarray(X, float))
return np.clip(np.rint(raw), self._lo, self._hi).astype(int)
Параметры конструктора
WKNNRegressor(
n_neighbors=5, *, weights="linear", metric="minkowski", p=2,
standardize=True, regularization=0.0, max_neighbor_deviation=0,
learning_rate=0.5, momentum=0.9, random_state=None,
)
| Параметр | Тип | По умолчанию | Значение |
|---|---|---|---|
n_neighbors |
int | 5 |
Базовое число соседей k. |
weights |
см. ниже | "linear" |
Способ взвешивания: имя, BaseWeightFunction, callable или вектор по рангам. |
metric |
str | "minkowski" |
Метрика расстояния (передаётся в NearestNeighbors). |
p |
int | 2 |
Порядок Минковского (2 — евклидова, 1 — манхэттенская). |
standardize |
bool | True |
Стандартизация признаков перед поиском соседей. |
regularization |
float | 0.0 |
λ ≥ 0. Сдвиг предсказания к глобальному среднему и весов к равномерным. |
max_neighbor_deviation |
int | 0 |
d ≥ 0. Размер окрестности варьируется в [k−d, k+d]. 0 — фиксированное k. |
learning_rate |
float | 0.5 |
Шаг η для обучения весов (NAG). |
momentum |
float | 0.9 |
Момент μ (NAG). |
random_state |
int / None | None |
Зерно для внутреннего сплита в fit_weights. |
Основные методы
fit(X, y) -> self
Обучение на numpy-предикторах X (2-D) и одномерном y. Проверяет вход,
условие n_neighbors ≤ n_samples, стандартизует, строит индекс соседей и
разрешает спецификацию весов (некорректная спецификация падает здесь, а не при
предсказании).
model = WKNNRegressor(5, weights="gaussian").fit(X, y)
predict(X) -> np.ndarray
Предсказание одномерных ответов для X. Применяет регуляризацию и адаптивное
число соседей, если они заданы.
preds = model.predict(X_new)
refit(X, y) -> self
Полное переобучение с нуля, отбрасывая всё прежнее состояние.
model.refit(X2, y2)
score(X, y, sample_weight=None) -> float
Коэффициент детерминации R² (из RegressorMixin).
r2 = model.score(X_test, y_test)
Обучаемые и переносимые веса (v0.2)
Предсказание — дифференцируемая функция вектора весов по рангам w ∈ ℝk, поэтому w можно обучать ускоренным градиентным спуском Нестерова, сохранять и применять к похожему набору данных.
fit_weights(X=None, y=None, *, epochs=100, validation_fraction=0.3, record=False)
Обучает вектор по рангам. Если X/y не заданы, обучающая выборка делится
внутренне (индекс соседей — по одной части, обучающий сигнал — по другой), чтобы
решение не вырождалось в 1-NN. Устанавливает weight_vector_ и переключает
предсказание на него. При record=True возвращает историю потерь, иначе self.
model = WKNNRegressor(7, weights="inverse", regularization=0.05).fit(X, y)
history = model.fit_weights(epochs=100, record=True)
print("потери:", history[0], "->", history[-1])
print("выученные веса:", model.weight_vector_)
export_weights(path) -> path / import_weights(path) -> self
Сохранить / загрузить только выученный вектор весов в .npz — компактный
переносимый артефакт для трансферного обучения.
model.export_weights("weights.npz")
fresh = WKNNRegressor(7, weights="inverse").fit(X_similar, y_similar)
fresh.import_weights("weights.npz")
transfer_weights(source) -> self
Скопировать выученный вектор из другой обученной модели.
target.transfer_weights(model)
set_weight_vector(w) -> self
Установить вектор весов напрямую; его длина обязана равняться n_neighbors.
model.set_weight_vector(np.array([0.4, 0.25, 0.15, 0.1, 0.1])) # k == 5
Онлайн- и батч-дообучение (v0.2)
partial_fit(X, y, classes=None, *, epochs=1) -> self
Инкрементальное обучение, как шаг нейросети. Первый вызов эквивалентен fit;
последующие обучают веса на новом батче как на отложенном сигнале (до его
добавления), затем добавляют батч в память и перестраивают индекс. n_samples_
растёт с каждым батчем.
О сигнатуре. Три аргумента после X, y присутствуют по разным причинам,
и ни один из них не случаен:
epochs(рабочий параметр) — сколько шагов градиентного дообучения сделать на новом батче.epochs=1— один онлайн-шаг; большие значения дают более основательное батч-дообучение. Это реально используемый параметр (например,epochs=3, когда батч несёт больше информации).classes=None(только для совместимости, игнорируется) — по соглашению scikit-learn методpartial_fitполучает на первом вызове полный набор меток, чтобы классификатор мог задать размер выхода.wknn_1— это регрессор, и он никогда не читает этот аргумент; он принимается лишь для того, чтобы сигнатура совпадала со scikit-learnpartial_fit(X, y, classes=...)и метод подходил инструментам, вызывающим его именно так. Его всегда можно не указывать.*— одиночная звёздочка помечает всё, что идёт после неё, как «только по имени», поэтомуepochsпишется какepochs=3, а не как позиционный3. Это защищает от вызова вродеpartial_fit(X, y, нечто, 3), где значение по ошибке связалось бы сclasses.
model = WKNNRegressor(7, weights="inverse", regularization=0.05)
model.partial_fit(X0, y0) # ~ fit
for Xb, yb in batches:
model.partial_fit(Xb, yb, epochs=3) # дообучение + рост памяти
print("размер памяти:", model.n_samples_)
Регуляризация (v0.2)
Параметр regularization=λ (λ ≥ 0) действует в двух местах:
- предсказание — масштабно-инвариантный сдвиг к глобальному среднему
μ_y:y_reg = (y_hat + λ·μ_y) / (1 + λ); - обучение весов — штраф L2, притягивающий веса к равномерному вектору, что снижает переобучение и улучшает перенос.
model = WKNNRegressor(7, weights="inverse", regularization=0.3).fit(X, y)
Адаптивное число соседей (v0.2)
Параметр max_neighbor_deviation=d. Для каждого запроса размер окрестности
выбирается в [k−d, k+d] так, чтобы минимизировать дисперсию ответов соседей:
окрестность сжимается там, где есть плотное согласованное ядро, и расширяется
там, где соседи расходятся. d = 0 в точности воспроизводит фиксированное k.
model = WKNNRegressor(5, weights="inverse", max_neighbor_deviation=3).fit(X, y)
Прореживание обучающей выборки (v0.2)
condense(ratio) -> self
Удаляет долю ratio ∈ (0, 1) запомненных точек. Сначала отбрасываются
избыточные внутренние точки плотных однородных областей; граничные точки (с
соседями других классов) защищены; удаление стратифицировано, ни один класс не
исчезает. В связке с partial_fit удерживает память ограниченной.
condenseгруппирует точки по значениюy_, поэтому предназначен для дискретных меток (задача классификации). При непрерывном отклике каждая точка образует свою группу и ничего не удаляется.
y_cls = (X[:, 0] > 0.5).astype(float) # метки-классы
model = WKNNRegressor(7, weights="inverse").fit(X, y_cls)
model.condense(0.4) # удалить ~40% точек
print("осталось:", model.n_samples_)
Весовые функции
Параметр weights принимает четыре вида спецификации.
1. Имя стандартной функции:
| Имя | Формула (d — расстояние соседа) | Примечание |
|---|---|---|
uniform |
w = 1 |
Сводится к обычному усреднению kNN. |
linear |
w = 1 − (d − lo)/(hi − lo) |
Ближайший → 1, дальний → 0; без гиперпараметров. |
inverse / distance |
`w = 1 / ( | d |
exponential |
`w = exp(−alpha· | d |
gaussian |
w = exp(−d² / (2·sigma²)) |
RBF-ядро; ширина — sigma. |
WKNNRegressor(5, weights="inverse").fit(X, y)
2. Экземпляр StandardWeightFunction — те же функции с явными параметрами:
from wknn_1 import StandardWeightFunction
wf = StandardWeightFunction("gaussian", sigma=0.5, aggregate="mean",
center=False, scale=True, normalize=False)
WKNNRegressor(5, weights=wf).fit(X, y)
Конструктор: StandardWeightFunction(formula="linear", *, center=False, scale=True, normalize=False, aggregate="mean", alpha=1.0, sigma=1.0, eps=1e-12).
Флаги center / scale / normalize применяются к расстояниям (вдоль оси
соседей); aggregate ∈ {"mean", "sum", "product"} сворачивает покоординатные
веса в один вес на соседа.
3. MixedWeightFunction — своя формула на каждое измерение (в sklearn
так нельзя, callable видит только скалярные расстояния):
from wknn_1 import MixedWeightFunction, StandardWeightFunction
mixed = MixedWeightFunction([
StandardWeightFunction("linear"), # измерение 0
StandardWeightFunction("gaussian", sigma=0.3), # измерение 1
], aggregate="product")
WKNNRegressor(5, weights=mixed).fit(X[:, :2], y) # здесь у X два столбца
4. Вектор по рангам (список или ndarray длины n_neighbors) — i-й по
близости сосед получает вес w[i]:
WKNNRegressor(3, weights=[0.6, 0.3, 0.1]).fit(X, y)
Дополнительные функции весов.
FixedFeatureWeightFunction(feature_weights, *, eps=1e-12) — ядро по фиксированному
вектору важностей признаков v: w = 1 / (1 + Σ_j v_j·|Δ_j| + eps).
from wknn_1 import FixedFeatureWeightFunction
wf = FixedFeatureWeightFunction(np.array([2.0, 0.5, 1.0])) # 3 признака
WKNNRegressor(5, weights=wf).fit(X, y)
RankWeightFunction(rank_weights) — явная форма весов по рангам (её использует
вариант 4).
Реестр функций
from wknn_1 import list_weight_functions, register_weight_function, get_weight_function
from wknn_1 import StandardWeightFunction
list_weight_functions()
# ['distance', 'exponential', 'gaussian', 'inverse', 'linear', 'uniform']
register_weight_function("sharp_gauss",
lambda: StandardWeightFunction("gaussian", sigma=0.2),
overwrite=True)
WKNNRegressor(5, weights="sharp_gauss").fit(X, y)
fn = get_weight_function("inverse", n_neighbors=5) # разрешить спецификацию
Сохранение и загрузка
save_weights(path, fmt=None) -> Path / load_weights(path, fmt=None) -> self
Сохранить / загрузить полное обученное состояние (стандартизованная матрица,
ответы, скейлер, параметры и, если есть, выученный weight_vector_). Формат
определяется fmt или расширением файла.
| Формат | Расширение | Примечание |
|---|---|---|
| npz | .npz |
Рекомендуется; numpy-совместимо, переносимо. |
| pickle | .pkl / .pickle |
Сохраняет произвольные callable-спецификации. |
| json | .json |
Читаемый; callable сохраняется как заполнитель. |
model.save_weights("model.npz")
restored = WKNNRegressor().load_weights("model.npz")
np.allclose(model.predict(X[:5]), restored.predict(X[:5])) # True
Спецификацию
weightsв виде callable нельзя сериализовать в npz/json (сохраняется лишь заполнитель). Используйте pickle или заново задайте функцию черезset_paramsпосле загрузки.
delete(*, remove_files=True) -> None
Полностью сбрасывает модель: удаляет созданные ею файлы, выученные веса,
обученные атрибуты и метаданные. Гиперпараметры сохраняются, объект готов к
новому fit.
model.delete()
Исключения
Все наследуются от WKNNError.
| Исключение | Когда возникает |
|---|---|
WKNNError |
Базовый класс всех ошибок библиотеки. |
InvalidInputError |
Не-numpy вход, неверные формы, некорректный n_neighbors или гиперпараметры. |
NotFittedError |
predict и т. п. вызваны до fit / load_weights. |
WeightFunctionError |
Неизвестная спецификация весов или callable вернул неверную форму. |
PersistenceError |
Ошибка сохранения/загрузки либо перенос/экспорт без выученного вектора. |
from wknn_1 import NotFittedError
try:
WKNNRegressor().predict(X)
except NotFittedError as e:
print("сначала fit:", e)
Интеграция со scikit-learn
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GridSearchCV
pipe = make_pipeline(StandardScaler(), WKNNRegressor())
grid = GridSearchCV(pipe, {
"wknnregressor__n_neighbors": [3, 5, 7],
"wknnregressor__weights": ["uniform", "inverse", "gaussian"],
"wknnregressor__regularization": [0.0, 0.3],
})
grid.fit(X, y)
print(grid.best_params_)
clone, get_params и set_params работают штатно, поскольку __init__ только
сохраняет аргументы и ничего не преобразует.
Атрибуты после обучения
| Атрибут | Значение |
|---|---|
n_features_in_ |
Число столбцов-признаков, увиденных при fit. |
n_samples_ |
Текущий размер памяти (растёт при partial_fit, падает при condense). |
Xt_ |
Стандартизованная обучающая матрица. |
y_ |
Сохранённые одномерные ответы. |
scaler_ |
Обученный стандартизатор (или None при standardize=False). |
weight_vector_ |
Выученный вектор по рангам (после fit_weights / import_weights / set_weight_vector). |
Разработка
pip install -e .
pip install pytest
# запуск тестов (45: 35 базовых + 10 на возможности v0.2)
pytest -q
# сборка wheel + sdist
python -m build # или: uv build
License: MIT. Version 0.2.0.
Release files for wknn_1 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wknn_1-0.2.0.tar.gz | 118.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wknn_1-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 159.3 kB
Release files / wknn_1-0.2.0.tar.gz
| Download URL | wknn_1-0.2.0.tar.gz |
|---|---|
| Size | 118.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9145c4286ccbc564f51961439014de5afe29f2b3eda9af454a653d7a5680366a
|
|
BLAKE2b-256 checksum How to use checksums |
a5a310b17293d59347a3fca6e58720fa545d59579a589480e7e7df288acc8bee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / wknn_1-0.2.0-py3-none-any.whl
| Download URL | wknn_1-0.2.0-py3-none-any.whl |
|---|---|
| Size | 41.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3d59fe3dc03d2425ad5edd00a1e71f15962fc4072f8d033a98edfc1dfaaf581f
|
|
BLAKE2b-256 checksum How to use checksums |
a1a35e129685903d4f43ce464f730508eb3376ab1b9addae1e168bcbaceae9dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|