trosna-linear-regression
Русский
Линейная регрессия МНК с тремя видами регуляризации, которые можно сочетать в любой пропорции:
- L1 (лассо): отбор признаков, нулевые коэффициенты получаются точными нулями;
- L2 по Тихонову (гребневая регрессия): штраф
½λ‖β‖², решение без L1 и отталкивания — нормальная система(XᵀSX + λI)β = XᵀSy; - отталкивание от заданной точки: чем ближе предсказание к точке
c, тем больше штраф. Мягкие ядра лишь отталкивают, а барьерные делают штраф бесконечным в самой точке: предсказание может подойти к ней сколь угодно близко, но цена растёт без предела.
Эстиматор совместим со scikit-learn (BaseEstimator, RegressorMixin, проходит check_estimator) и работает в
Pipeline, GridSearchCV, cross_val_score.
pip install trosna-linear-regression
Зачем
В задачах управления часть значений физически невозможна: объём памяти виртуальной машины не может быть меньше минимума, при котором работает её ОС, нагрузка не может быть отрицательной. Обычная (в том числе взвешенная) регрессия об этом не знает. После резкого спада старые точки окна тянут прямую вниз, прогноз уходит в невозможную область, и система отбирает у машины память, без которой та «засыпает». Барьер в точке предела держит прогноз в допустимой области и почти не меняет модель, пока до предела далеко.
import numpy as np
from sklearn.linear_model import LinearRegression
from trosna_linear_regression import TrosnaLinearRegression
t = np.arange(5.0)[:, None] # 5 последних мониторингов
mem = np.array([2600, 2550, 1500, 900, 640.0]) # потребление памяти ВМ, МБ: резкий спад
w = 1 / np.abs(5.0 - t[:, 0]) # вес точки по близости к моменту прогноза
t_next = [[5.0]]
LinearRegression().fit(t, mem, sample_weight=w).predict(t_next) # 28 МБ: ниже минимума 512 МБ
model = TrosnaLinearRegression(
alpha=1.0,
repulsion_ratio=1.0, # только отталкивание
kernel="log_barrier", # штраф бесконечен в самой точке
repulsion_point=512.0, # минимум памяти ВМ
repulsion_side="above", # допустимы только значения выше
repulsion_width=200.0, # барьер действует в полосе 512…712 МБ
).fit(t, mem, w, repulsion_X=t_next) # отталкивать и прогноз в момент t_next
model.predict(t_next) # 564 МБ
model.converged_, model.solver_, model.optimality_ # (True, 'newton', 2.1e-07)
Для прямой во времени условие, выполненное в точках окна и в момент прогноза, выполняется и на всём отрезке между ними. Подробные примеры с графиками лежат в репозитории trosna-linear-regression-examples.
Постановка
J(β, b) = Σ s̃ᵢ ½(yᵢ − ŷᵢ)²
+ α ρ_rep [ Σ s̃ᵢ φ(ŷᵢ − c) + 1/n Σ_q φ(ŷ_q − c) ]
+ α ρ_l1 ‖β‖₁ + ½ α ρ_l2 ‖β‖²
ŷ = xᵀβ + b, s̃ᵢ — нормированные веса наблюдений (sample_weight), q пробегает точки repulsion_X без
отклика (моменты прогноза). Доли ρ_l1 = l1_ratio, ρ_rep = repulsion_ratio, ρ_l2 = 1 − ρ_l1 − ρ_rep,
общая сила α = alpha, как в ElasticNet. Свободный член b L1/L2 не штрафуется.
Ядра отталкивания
Все ядра изотропны: штраф зависит только от расстояния |ŷ − c| и одинаково убывает в обе стороны от точки.
kernel |
φ(d), u = |d|/σ |
В точке | Когда брать |
|---|---|---|---|
"gaussian" |
σ² exp(−u²/2) |
σ² |
лёгкое отталкивание |
"cauchy" |
σ² / (2(1 + u²)) |
σ²/2 |
лёгкое отталкивание, действует дальше |
"log_barrier" |
σ²(−ln u − (1−u²)/2) при u < 1, иначе 0 |
∞, как −ln|d| |
точка запрещена, мягкая стенка |
"inverse_barrier" |
σ²/2 (1/u − 1)² при u < 1, иначе 0 |
∞, как 1/d² |
точка запрещена, жёсткая стенка |
Мягкие ядра нормированы на кривизну φ''(0) = −1. Поэтому при α ρ_rep ≤ 1 задача выпукла, а при больших
значениях предсказания расталкиваются от точки (задача невыпукла, ищется локальный минимум из решения без
отталкивания). Барьерные ядра равны нулю вне зоны |d| < σ и выпуклы по каждую сторону от точки. Сторону
задаёт repulsion_side: "auto" оставляет каждое предсказание там, где оно было в решении без отталкивания,
а "above" и "below" требуют одной стороны для всех. При назначенных сторонах задача выпукла.
Физическая аналогия
Идея пришла из электростатики. Предсказание ведёт себя как заряженная частица в силовом поле между двумя
электродами: наблюдение притягивает её как пружина (квадрат отклонения), а одноимённо заряженный электрод в точке
c отталкивает тем сильнее, чем она ближе (ядро φ). Функционал J играет роль потенциальной энергии, решение
является положением равновесия, а обучение описывает движение частицы к нему. Логарифмический барьер повторяет
потенциал заряженной нити (−ln|d|), а inverse_barrier с точностью до множителя совпадает с отталкивающим
потенциалом Хатиба из метода потенциальных полей в робототехнике (Khatib, 1986), где σ играет роль радиуса
влияния препятствия. Чтобы коснуться электрода, нужна бесконечная энергия, поэтому точка недостижима. Для
регрессии все предсказания лежат на одной прямой и движутся вместе, как бусины на жёстком стержне: поле, действующее
на прогноз, поднимает всю прямую.
Сходимость
Выпуклые задачи решаются методом Ньютона с явным гессианом AᵀWA + λI (матричная форма регуляризации
Тихонова), дроблением шага по Армихо и L1 по ортантам (как в OWL-QN). Для квадратичной задачи это одна
итерация. Невыпуклые задачи с мягким ядром решаются L-BFGS-B. В обоих случаях сходимость проверяется по
одному критерию: нарушение условий оптимальности (ККТ) не больше tol, отнесённого к масштабу y. После
обучения доступны converged_, optimality_ (нарушение условий оптимальности в найденной точке), solver_
и n_iter_. Если решатель не сошёлся, выдаётся ConvergenceWarning с величиной нарушения.
Корректность проверяется тестами: совпадение с LinearRegression, Ridge, Lasso, ElasticNet, совпадение
двух независимых решателей, проверка градиента и гессиана конечными разностями, свойства на случайных задачах (hypothesis) и полный набор проверок scikit-learn.
Параметры
| Параметр | По умолчанию | Смысл |
|---|---|---|
alpha |
1.0 |
общая сила регуляризации |
l1_ratio |
0.0 |
доля L1 |
repulsion_ratio |
0.5 |
доля отталкивания, L2 получает остаток |
repulsion_point |
0.0 |
точка c |
repulsion_width |
None |
ширина σ (радиус зоны барьера), None — СКО y |
kernel |
"gaussian" |
ядро отталкивания |
repulsion_side |
"auto" |
разрешённая сторона для барьеров |
fit_intercept |
True |
оценивать свободный член |
tol, max_iter |
1e-8, 10000 |
допуск по условиям оптимальности и предел итераций |
fit(X, y, sample_weight=None, repulsion_X=None).
English
Least-squares linear regression with three kinds of regularization that can be mixed in any proportion:
- L1 (lasso): feature selection with exact zeros;
- Tikhonov L2 (ridge): penalty
½λ‖β‖²; without L1 and repulsion the solution is the normal system(XᵀSX + λI)β = XᵀSy; - repulsion from a given point: the closer a prediction is to the point
c, the larger the penalty. Soft kernels only push predictions away, while barrier kernels make the penalty infinite at the point itself: a prediction may come arbitrarily close, but the cost grows without bound.
The estimator is scikit-learn compatible (BaseEstimator, RegressorMixin, passes check_estimator) and works
with Pipeline, GridSearchCV and cross_val_score.
pip install trosna-linear-regression
Why
In control problems some values are physically impossible: a virtual machine cannot run with less memory than its OS needs, a load cannot be negative. Ordinary (including weighted) regression does not know that. After a sharp drop the old points of the window pull the line down, the forecast enters the impossible region and the system takes away memory the machine needs, so the machine "falls asleep". A barrier at the limit keeps the forecast in the feasible region and barely changes the model while the limit is far away.
import numpy as np
from sklearn.linear_model import LinearRegression
from trosna_linear_regression import TrosnaLinearRegression
t = np.arange(5.0)[:, None] # last 5 monitoring steps
mem = np.array([2600, 2550, 1500, 900, 640.0]) # VM memory usage, MB: a sharp drop
w = 1 / np.abs(5.0 - t[:, 0]) # weight by closeness to the forecast time
t_next = [[5.0]]
LinearRegression().fit(t, mem, sample_weight=w).predict(t_next) # 28 MB: below the 512 MB minimum
model = TrosnaLinearRegression(
alpha=1.0,
repulsion_ratio=1.0, # repulsion only
kernel="log_barrier", # infinite penalty at the point
repulsion_point=512.0, # VM memory minimum
repulsion_side="above", # only values above are allowed
repulsion_width=200.0, # the barrier acts within 512…712 MB
).fit(t, mem, w, repulsion_X=t_next) # also repel the forecast at t_next
model.predict(t_next) # 564 MB
model.converged_, model.solver_, model.optimality_ # (True, 'newton', 2.1e-07)
For a line in time, a condition that holds at the window points and at the forecast time holds on the whole segment between them. Detailed examples with plots are in trosna-linear-regression-examples.
Problem
J(β, b) = Σ s̃ᵢ ½(yᵢ − ŷᵢ)²
+ α ρ_rep [ Σ s̃ᵢ φ(ŷᵢ − c) + 1/n Σ_q φ(ŷ_q − c) ]
+ α ρ_l1 ‖β‖₁ + ½ α ρ_l2 ‖β‖²
ŷ = xᵀβ + b, s̃ᵢ are normalized sample weights, q runs over the response-free points repulsion_X
(forecast times). The shares are ρ_l1 = l1_ratio, ρ_rep = repulsion_ratio, ρ_l2 = 1 − ρ_l1 − ρ_rep, and
the overall strength is α = alpha, as in ElasticNet. The intercept b is not penalized by L1/L2.
Repulsion kernels
All kernels are isotropic: the penalty depends only on |ŷ − c| and decays equally on both sides of the point.
kernel |
φ(d), u = |d|/σ |
At the point | Use for |
|---|---|---|---|
"gaussian" |
σ² exp(−u²/2) |
σ² |
mild repulsion |
"cauchy" |
σ² / (2(1 + u²)) |
σ²/2 |
mild repulsion with a longer reach |
"log_barrier" |
σ²(−ln u − (1−u²)/2) for u < 1, else 0 |
∞, like −ln|d| |
forbidden point, soft wall |
"inverse_barrier" |
σ²/2 (1/u − 1)² for u < 1, else 0 |
∞, like 1/d² |
forbidden point, hard wall |
Soft kernels are normalized to curvature φ''(0) = −1. Hence the problem is convex for α ρ_rep ≤ 1, while
larger values push predictions apart (the problem is non-convex, a local minimum is sought starting from the
solution without repulsion). Barrier kernels vanish outside the zone |d| < σ and are convex on each side of
the point. The side is set by repulsion_side: "auto" keeps every prediction where it was in the solution
without repulsion, while "above" and "below" require one side for all. With the sides fixed, the problem is
convex.
Physical analogy
The idea comes from electrostatics. A prediction behaves like a charged particle in a force field between two
electrodes: the observation pulls it like a spring (the squared residual), while a like-charged electrode at c
pushes it away the harder the closer it gets (the kernel φ). The objective J is the potential energy, the
solution is the equilibrium, and fitting is the particle moving towards it. The log barrier is the potential of a
charged wire (−ln|d|), and inverse_barrier coincides up to a factor with Khatib's repulsive potential from the
potential field method in robotics (Khatib, 1986), with σ as the obstacle's radius of influence. Reaching the
electrode takes infinite energy, so the point is unreachable. In regression all predictions lie on one line and
move together like beads on a rigid rod: the field acting on the forecast lifts the whole line.
Convergence
Convex problems are solved by Newton's method with the explicit Hessian AᵀWA + λI (the matrix form of
Tikhonov regularization), Armijo backtracking and orthant-wise handling of L1 (as in OWL-QN). For a quadratic
problem this takes one iteration. Non-convex problems with a soft kernel are solved by L-BFGS-B. Both use the same
stopping rule: the violation of the optimality (KKT) conditions must not exceed tol relative to the scale of
y. After fitting, converged_, optimality_ (the KKT violation at the solution), solver_ and n_iter_ are
available. If the solver did not converge, a ConvergenceWarning reports the violation.
Correctness is covered by tests: agreement with LinearRegression, Ridge, Lasso, ElasticNet, agreement of two
independent solvers, finite-difference checks of the gradient and Hessian, property-based tests on
random problems (hypothesis) and the full scikit-learn estimator checks.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
alpha |
1.0 |
overall regularization strength |
l1_ratio |
0.0 |
L1 share |
repulsion_ratio |
0.5 |
repulsion share, L2 gets the rest |
repulsion_point |
0.0 |
the point c |
repulsion_width |
None |
width σ (barrier zone radius), None means the std of y |
kernel |
"gaussian" |
repulsion kernel |
repulsion_side |
"auto" |
allowed side for barriers |
fit_intercept |
True |
fit the intercept |
tol, max_iter |
1e-8, 10000 |
optimality tolerance and iteration limit |
fit(X, y, sample_weight=None, repulsion_X=None).
Разработка / Development
uv sync
uv run pytest # 618 тестов / tests
uv run ruff check . && uv run ruff format --check .
Python 3.11–3.14; тесты проходят и с минимальными numpy 1.26, scipy 1.11, scikit-learn 1.6. Tests also pass with the minimum supported numpy 1.26, scipy 1.11 and scikit-learn 1.6.
Лицензия / License
MIT, см. / see LICENSE.
Metadata
Release files for trosna-linear-regression 0.1.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 |
|---|---|---|---|---|
| trosna_linear_regression-0.1.0.tar.gz | 25.4 kB | Without provenance | Details for trosna_linear_regression-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| trosna_linear_regression-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for trosna_linear_regression-0.1.0-py3-none-any.whl |
Total release size: 54.6 kB
Release files / trosna_linear_regression-0.1.0.tar.gz
| Download URL | trosna_linear_regression-0.1.0.tar.gz |
|---|---|
| Size | 25.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
871a3b54564cb4af89d07cc48925f5b8beb8c873fa48d405ccd5bea72cd1c422
|
|
BLAKE2b-256 checksum How to use checksums |
21462ab01cd63655058611a401f7434adec1d8e1cef9129ad0329b8728b0e646
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / trosna_linear_regression-0.1.0-py3-none-any.whl
| Download URL | trosna_linear_regression-0.1.0-py3-none-any.whl |
|---|---|
| Size | 29.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
223ee512efead039fcd9c0062086de2ca9a45b173dfeda3ea99df4f2dbb38c9b
|
|
BLAKE2b-256 checksum How to use checksums |
2d2fe7ce6eef81da3c820fdf6e11c34abfe1ca82302e0dcd9fbfc435c9823dcc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for trosna-linear-regression 0.1.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.