Skip to main content

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

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=1 is a single online step; larger values do heavier batch fine-tuning. This is a real knob you will use (e.g. epochs=3 when a batch carries more information).
  • classes=None (compatibility only, ignored) — scikit-learn's partial_fit convention passes the full label set on the first call so a classifier can size its output. wknn_1 is a regressor and never reads this argument; it is accepted solely so the method matches the scikit-learn partial_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, so epochs must be written as epochs=3, never as a bare positional 3. This prevents a call like partial_fit(X, y, something, 3) from silently binding the wrong value to 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)      # 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.

condense groups points by their y_ 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 weights spec cannot be serialized to npz/json (only a placeholder is stored). Use pickle, or re-attach the callable via set_params after 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-learn partial_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)

Source distribution for wknn_1 0.2.0
File Size Uploaded
wknn_1-0.2.0.tar.gz 118.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wknn_1 0.2.0
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page