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.
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| wknn_1-0.2.0.tar.gz | 118.1 kB | Without provenance | Details for wknn_1-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wknn_1-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for wknn_1-0.2.0-py3-none-any.whl |
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}
|
Security for wknn_1 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.