pascalabcnet/bin/Lib/MLModelsABC.pas
2026-02-16 14:12:07 +03:00

1549 lines
47 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

unit MLModelsABC;
interface
uses MLCoreABC;
uses LinearAlgebraML;
type
{$region Activations}
/// Активационные функции для моделей
Activations = static class
public
/// Функция активации Sigmoid (логистическая функция).
/// Преобразует любое число в диапазон (0, 1).
/// Используется в логистической регрессии
/// и других моделях для получения вероятности.
/// Формула: σ(x) = 1 / (1 + e^(x)).
static function Sigmoid(v: Vector): Vector;
/// Функция активации Tanh (гиперболический тангенс).
/// Похожа на Sigmoid, но значения лежат в диапазоне (1, 1).
/// Применяется в моделях, где требуется
/// симметричная нелинейность относительно нуля.
/// Формула: tanh(x) = (e^x e^(x)) / (e^x + e^(x)).
static function Tanh(v: Vector): Vector;
/// Функция активации ReLU (Rectified Linear Unit).
/// Отрицательные числа заменяет на 0,
/// положительные оставляет без изменений.
/// Используется как простое нелинейное преобразование
/// в моделях машинного обучения.
/// Формула: ReLU(x) = max(0, x).
static function ReLU(v: Vector): Vector;
/// Функция активации Softmax.
/// Преобразует набор значений в вероятностное распределение:
/// все элементы неотрицательны,
/// их сумма равна 1.
/// Используется в задачах многоклассовой классификации.
/// Каждый элемент интерпретируется как вероятность класса.
/// Формула: softmax(x_i) = e^{x_i} / Σ e^{x_j}.
static function Softmax(v: Vector): Vector;
end;
{$endregion Activations}
{$region Models}
IModel = MLCoreABC.IModel;
/// Линейная регрессионная модель
/// Предсказывает числовое значение по линейной комбинации признаков
/// Используется в задачах регрессии при отсутствии сильной
/// мультиколлинеарности и когда число признаков существенно меньше числа объектов.
LinearRegression = class(IRegressor)
private
fCoef: Vector;
fIntercept: real;
fLambda: real;
fFitted: boolean;
public
constructor Create(lambda: real := 0.0);
/// Обучает модель на числовых данных
/// X — матрица m × n (m объектов, n признаков)
/// y — вектор длины m
function Fit(X: Matrix; y: Vector): IModel;
/// Предсказывает значения для матрицы признаков
/// Возвращает вектор длины m
function Predict(X: Matrix): Vector;
/// Вектор коэффициентов модели (веса признаков).
/// Каждый элемент показывает вклад соответствующего признака в линейную комбинацию.
/// Доступен после обучения (Fit).
property Coefficients: Vector read fCoef;
/// Свободный член модели (смещение, bias).
/// Добавляется к линейной комбинации признаков.
/// Интерпретируется как значение предсказания, когда все признаки равны нулю.
property Intercept: real read fIntercept;
/// Показывает, была ли модель обучена.
/// После вызова Fit значение становится true.
/// Используется для проверки корректности вызова Predict.
property IsFitted: boolean read fFitted;
end;
/// Логистическая регрессионная модель для бинарной классификации.
/// Предсказывает вероятность принадлежности объекта к классу 1
/// на основе линейной комбинации признаков и сигмоидной функции.
/// Поддерживает L2-регуляризацию.
/// Используется в задачах бинарной классификации,
/// когда требуется вероятностный вывод и интерпретируемые коэффициенты.
LogisticRegression = class(IClassifier)
private
fCoef: Vector;
fIntercept: real;
fLambda: real;
fLearningRate: real;
fEpochs: integer;
fFitted: boolean;
public
/// Создаёт модель логистической регрессии.
/// lambda — коэффициент L2-регуляризации (0 — без регуляризации).
/// lr — шаг градиентного спуска.
/// epochs — число итераций обучения.
constructor Create(lambda: real := 0.0; lr: real := 0.1; epochs: integer := 1000);
/// Обучает модель на числовых данных.
/// X — матрица m × n (m объектов, n признаков).
/// y — вектор длины m, содержащий метки классов (0 или 1).
function Fit(X: Matrix; y: Vector): IModel;
/// Возвращает вероятность принадлежности к положительному классу (1)
/// для каждого объекта.
/// Результат — вектор длины m со значениями в диапазоне (0, 1).
function PredictProba(X: Matrix): Vector;
/// Выполняет бинарную классификацию с порогом 0.5.
/// Возвращает вектор из 0 и 1.
function Predict(X: Matrix): Vector;
/// Выполняет бинарную классификацию с заданным порогом.
/// threshold — значение в диапазоне (0, 1).
/// Если вероятность ≥ threshold, возвращается 1, иначе 0.
function Predict(X: Matrix; threshold: real): Vector;
/// Вектор коэффициентов модели (веса признаков).
/// Длина равна числу признаков.
/// Доступен после обучения (Fit).
property Coefficients: Vector read fCoef;
/// Свободный член модели (смещение, bias).
/// Добавляется к линейной комбинации признаков.
property Intercept: real read fIntercept;
/// Показывает, была ли модель обучена.
/// После вызова Fit значение становится true.
property IsFitted: boolean read fFitted;
end;
/// Линейная регрессионная модель с L2-регуляризацией (Ridge).
/// Минимизирует функцию:
/// ||y - (Xβ + b)||² + λ ||β||².
/// Устойчива к мультиколлинеарности и плохо обусловленным данным.
/// Используется при коррелированных признаках
/// и в задачах, где важна численная стабильность решения.
RidgeRegression = class(IRegressor)
private
fLambda: real;
fCoef: Vector;
fIntercept: real;
fFitted: boolean;
public
/// Создаёт модель Ridge-регрессии.
/// lambda — коэффициент L2-регуляризации (0 — обычная линейная регрессия).
constructor Create(lambda: real := 1.0);
/// Обучает модель на числовых данных.
/// X — матрица m × n (m объектов, n признаков).
/// y — вектор длины m с непрерывными значениями.
/// Выполняется центрирование признаков и целевой переменной.
function Fit(X: Matrix; y: Vector): IModel;
/// Предсказывает непрерывные значения для объектов X.
/// Результат — вектор длины m.
function Predict(X: Matrix): Vector;
/// Вектор коэффициентов модели (веса признаков).
/// Длина равна числу признаков.
/// Доступен после обучения (Fit).
property Coefficients: Vector read fCoef;
/// Свободный член модели (смещение, bias).
/// Не подвергается регуляризации.
property Intercept: real read fIntercept;
/// Коэффициент L2-регуляризации.
property Lambda: real read fLambda;
/// Показывает, была ли модель обучена.
/// После вызова Fit значение становится true.
property IsFitted: boolean read fFitted;
end;
/// Линейная регрессионная модель ElasticNet.
/// Минимизирует функцию:
/// ||y - (Xβ + b)||² + λ1 ||β||₁ + λ2 ||β||².
/// Объединяет L1-регуляризацию (разреженность, отбор признаков)
/// и L2-регуляризацию (численная устойчивость).
/// Используется при большом числе признаков, особенно если признаки коррелированы.
/// Обучение выполняется методом покоординатного спуска
ElasticNet = class(IRegressor)
private
fLambda1: real; // L1
fLambda2: real; // L2
fMaxIter: integer;
fTol: real;
fCoef: Vector;
fIntercept: real;
fFitted: boolean;
/// Применяет оператор мягкого порога:
/// soft(z, γ) = sign(z) * max(|z| - γ, 0).
/// Используется для реализации L1-регуляризации.
function SoftThreshold(z, gamma: real): real;
public
/// Создаёт модель ElasticNet.
/// lambda1 — коэффициент L1-регуляризации (>= 0).
/// lambda2 — коэффициент L2-регуляризации (>= 0).
/// maxIter — максимальное число итераций coordinate descent.
/// tol — критерий остановки по изменению коэффициентов.
constructor Create(lambda1, lambda2: real; maxIter: integer := 1000; tol: real := 1e-6);
/// Обучает модель на числовых данных.
/// X — матрица m × n (m объектов, n признаков).
/// y — вектор длины m с непрерывными значениями.
/// Выполняется центрирование признаков и целевой переменной.
function Fit(X: Matrix; y: Vector): IModel;
/// Предсказывает непрерывные значения для объектов X.
/// Результат — вектор длины m.
function Predict(X: Matrix): Vector;
/// Вектор коэффициентов модели (веса признаков).
/// Длина равна числу признаков.
/// Доступен после обучения (Fit).
property Coefficients: Vector read fCoef;
/// Свободный член модели (смещение, bias).
/// Не подвергается регуляризации.
property Intercept: real read fIntercept;
/// Показывает, была ли модель обучена.
/// После вызова Fit значение становится true.
property IsFitted: boolean read fFitted;
end;
/// Многоклассовая логистическая регрессия (Softmax).
/// Предсказывает вероятности принадлежности к каждому классу
/// на основе линейной комбинации признаков и softmax-функции.
/// Использует кросс-энтропийную функцию потерь.
/// Поддерживает L2-регуляризацию.
MulticlassLogisticRegression = class(IProbabilisticClassifier)
private
fW: Matrix; // p x k
fIntercept: Vector; // k
fLambda: real;
fLearningRate: real;
fEpochs: integer;
fFitted: boolean;
fClassCount: integer;
public
/// lambda — коэффициент L2-регуляризации.
/// lr — шаг градиентного спуска.
/// epochs — число итераций обучения.
constructor Create(lambda: real := 0.0; lr: real := 0.1; epochs: integer := 1000);
function Fit(X: Matrix; y: Vector): IModel;
function PredictProba(X: Matrix): Vector;
/// Возвращает матрицу вероятностей (m x k).
function PredictProbaMatrix(X: Matrix): Matrix;
/// Возвращает вектор предсказанных классов.
function Predict(X: Matrix): Vector;
property IsFitted: boolean read fFitted;
end;
{$endregion Models}
{$region Pipeline}
/// Последовательный конвейер машинного обучения.
/// Гарантирует строгий порядок выполнения шагов:
/// [преобразователи] → [модель].
///
/// Поддерживает:
/// - преобразователи без учёта целевой переменной;
/// - преобразователи с учётом целевой переменной;
/// - одну финальную модель.
///
/// Обеспечивает единый интерфейс Fit / Predict
/// и воспроизводимость полного процесса обучения.
Pipeline = class(IModel)
private
fTransformers: List<ITransformer>;
fModel: IModel;
fFitted: boolean;
public
/// Создаёт конвейер машинного обучения для заданной модели.
/// model — модель, которая будет обучена
/// после последовательного применения всех преобразователей.
constructor Create(model: IModel);
/// Создаёт пустой пайплайн (конвейер машинного обучения).
/// Модель должна быть установлена через SetModel.
constructor Create;
/// Строит конвейер машинного обучения.
/// model — модель, обучаемая после применения всех преобразователей.
/// transformers — последовательность преобразователей признаков,
/// применяемых к данным перед обучением модели.
/// Возвращает сконструированный конвейер.
static function Build(model: IModel; params transformers: array of ITransformer): Pipeline;
/// Устанавливает или заменяет модель.
function SetModel(m: IModel): Pipeline;
/// Добавляет преобразование в конец пайплайна
function Add(t: ITransformer): Pipeline;
/// Обучает преобразования и модель
function Fit(X: Matrix; y: Vector): IModel;
/// Применяет только преобразования (без модели)
function Transform(X: Matrix): Matrix;
/// Делает предсказание
function Predict(X: Matrix): Vector;
/// Возвращает вероятности (если модель поддерживает)
function PredictProba(X: Matrix): Vector;
/// Показывает, был ли пайплайн обучен (вызван метод Fit).
property IsFitted: boolean read fFitted;
end;
{$endregion Pipeline}
{$region Transformers}
/// Стандартизирует признаки: вычитает среднее
/// и делит на стандартное отклонение по каждому столбцу.
/// Используется для приведения признаков к сопоставимому масштабу.
StandardScaler = class(ITransformer)
private
fMean: Vector;
fStd: Vector;
fFitted: boolean;
public
/// Вычисляет среднее и стандартное отклонение по каждому признаку.
function Fit(X: Matrix): ITransformer;
/// Применяет стандартизацию к данным.
function Transform(X: Matrix): Matrix;
/// Средние значения признаков, вычисленные при обучении.
property Mean: Vector read fMean;
/// Стандартные отклонения признаков, вычисленные при обучении.
property Std: Vector read fStd;
/// Признак того, что преобразование обучено.
property IsFitted: boolean read fFitted;
end;
/// Масштабирует признаки в заданный диапазон
/// (по умолчанию [0, 1]) на основе минимального и максимального значения каждого столбца.
/// Используется для приведения признаков к единому масштабу без центрирования.
MinMaxScaler = class(ITransformer)
private
fMin: Vector;
fMax: Vector;
fFitted: boolean;
fRangeMin: real;
fRangeMax: real;
public
/// Создаёт MinMaxScaler с диапазоном [rangeMin, rangeMax].
/// По умолчанию масштабирование выполняется к [0, 1].
constructor Create(rangeMin: real := 0.0; rangeMax: real := 1.0);
/// Вычисляет минимальные и максимальные значения
/// по каждому признаку.
function Fit(X: Matrix): ITransformer;
/// Применяет линейное масштабирование признаков
/// к диапазону [0, 1].
function Transform(X: Matrix): Matrix;
/// Минимальные значения признаков,
/// вычисленные при обучении.
property Min: Vector read fMin;
/// Максимальные значения признаков,
/// вычисленные при обучении.
property Max: Vector read fMax;
/// Признак того, что преобразование обучено.
property IsFitted: boolean read fFitted;
end;
/// Трансформер главных компонент (PCA).
/// Выполняет уменьшение размерности путём проекции данных
/// на первые k главных компонент.
/// На этапе Fit вычисляет главные компоненты ковариационной матрицы.
/// На этапе Transform проецирует данные:
/// Z = (X - μ) · W
PCATransformer = class(ITransformer)
private
fK: integer;
fComponents: Matrix; // W
fMean: Vector; // μ
fFitted: boolean;
public
/// Создаёт PCA-трансформер.
/// k — число главных компонент (k > 0).
constructor Create(k: integer);
/// Обучает трансформер на матрице признаков X.
/// X — матрица m × n.
function Fit(X: Matrix): ITransformer;
/// Преобразует матрицу X в пространство главных компонент.
/// Возвращает матрицу m × k.
function Transform(X: Matrix): Matrix;
property Components: Matrix read fComponents;
property Mean: Vector read fMean;
property IsFitted: boolean read fFitted;
end;
/// Трансформер, удаляющий признаки с малой дисперсией.
/// Удаляет столбцы X_j, для которых Var(X_j) < threshold.
/// Не использует целевую переменную (unsupervised).
VarianceThreshold = class(ITransformer)
private
fThreshold: real;
fSelected: array of integer;
fFitted: boolean;
public
/// threshold — минимальная допустимая дисперсия (>= 0).
constructor Create(threshold: real := 0.0);
/// Вычисляет дисперсии признаков и запоминает индексы
/// признаков, удовлетворяющих порогу.
function Fit(X: Matrix): ITransformer;
/// Возвращает матрицу, содержащую только отобранные признаки.
function Transform(X: Matrix): Matrix;
/// Индексы выбранных признаков.
property SelectedFeatures: array of integer read fSelected;
/// Показывает, был ли выполнен Fit.
property IsFitted: boolean read fFitted;
end;
/// Тип критерия оценки признаков для SelectKBest.
/// Определяет способ вычисления значимости признака
/// относительно целевой переменной.
FeatureScore = (
/// Абсолютное значение коэффициента корреляции Пирсона
/// между признаком и целевой переменной.
/// Подходит для задач регрессии и бинарной классификации при линейной зависимости.
Correlation,
/// F-статистика линейной регрессии (FRegression).
/// Оценивает статистическую значимость линейной связи
/// между признаком и целевой переменной.
/// Основан на коэффициенте детерминации (R²) и F-статистике.
/// Более строгий критерий, чем простая корреляция.
FRegression,
/// ANOVA F-критерий.
/// Используется в задачах классификации.
/// Оценивает различие средних значений признака между различными классами.
AnovaF,
/// Хи-квадрат (Chi-Square) критерий независимости.
/// Используется в задачах классификации.
/// Оценивает зависимость между признаком и классом
/// на основе различия наблюдаемых и ожидаемых частот.
/// Предполагает, что значения признака неотрицательны.
/// Часто применяется для текстовых данных и частотных представлений (bag-of-words).
ChiSquare
);
/// Преобразователь с учётом целевой переменной
/// Для каждого признака вычисляет score(X_j, y)
/// и оставляет k признаков с наибольшим значением score.
/// Может использовать встроенные критерии
/// или пользовательскую функцию оценки.
SelectKBest = class(ITransformer)
private
fK: integer;
fScoreType: FeatureScore;
fScoreFunc: (Vector, Vector) -> real;
fSelected: array of integer;
fFitted: boolean;
function ComputeScore(feature: Vector; y: Vector): real;
function ComputeCorrelation(x: Vector; y: Vector): real;
function ComputeFRegression(feature: Vector; y: Vector): real;
function ComputeAnovaF(feature: Vector; y: Vector): real;
function ComputeChiSquare(feature: Vector; y: Vector): real;
public
/// Создаёт трансформер с использованием встроенного критерия.
/// k — число отбираемых признаков.
/// score — тип критерия (например, Correlation).
constructor Create(k: integer; score: FeatureScore := FeatureScore.Correlation);
/// Создаёт трансформер с пользовательской функцией оценки.
/// scoreFunc — функция (feature, y) → real.
constructor Create(k: integer; scoreFunc: (Vector, Vector) -> real);
/// Выбрасывает исключение
/// Данный преобразователь требует целевую переменную.
/// Используйте перегруженный метод Fit(X, y).
function Fit(X: Matrix): ITransformer;
/// Вычисляет оценки признаков и выбирает k лучших.
function Fit(X: Matrix; y: Vector): ITransformer;
/// Возвращает матрицу, содержащую только выбранные признаки.
function Transform(X: Matrix): Matrix;
/// Индексы выбранных признаков.
property SelectedFeatures: array of integer read fSelected;
/// Показывает, был ли выполнен Fit.
property IsFitted: boolean read fFitted;
end;
/// Тип нормы для нормализации строк.
NormType = (L1, L2);
/// Преобразователь нормализации по строкам.
/// Для каждой строки X_i выполняет нормализацию:
/// L1: x := x / ||x||₁
/// L2: x := x / ||x||₂
/// Используется перед моделями, чувствительными к масштабу
/// (LogisticRegression, SVM, L1-регуляризация).
Normalizer = class(ITransformer)
private
fNormType: NormType;
fFitted: boolean;
public
constructor Create(norm: NormType := NormType.L2);
function Fit(X: Matrix): ITransformer;
function Transform(X: Matrix): Matrix;
property IsFitted: boolean read fFitted;
end;
{$endregion Transformers}
implementation
uses System;
//-----------------------------
// LinearRegression
//-----------------------------
constructor LinearRegression.Create(lambda: real);
begin
self.flambda := lambda;
ffitted := false;
end;
function LinearRegression.Fit(X: Matrix; y: Vector): IModel;
begin
var n := X.RowCount;
var p := X.ColCount;
if n = 0 then
raise new Exception('Empty dataset');
if y.Length <> n then
raise new Exception('Dimension mismatch in Fit');
// 1. Means
var meanX := X.ColumnMeans;
var meanY := y.Mean;
// 2. XtX and XtY
var Xt := X.Transpose;
var XtX := Xt * X;
var XtY := Xt * y;
// 3. Centering correction
XtX := XtX - n * Matrix.OuterProduct(meanX, meanX);
XtY := XtY - n * meanX * meanY;
// 4. Ridge regularization
if flambda > 0 then
XtX := XtX + Matrix.Identity(p) * flambda;
// 5. Solve
fcoef := Solve(XtX, XtY);
// 6. Intercept
fintercept := meanY - meanX.Dot(fcoef);
ffitted := true;
Result := Self;
end;
function LinearRegression.Predict(X: Matrix): Vector;
begin
if not ffitted then
raise new Exception('Model not fitted');
if X.ColCount <> fcoef.Length then
raise new Exception('Dimension mismatch in Predict');
Result := X * fcoef + intercept;
end;
//-----------------------------
// Activations
//-----------------------------
static function Activations.Sigmoid(v: Vector): Vector := v.Apply(x -> 1.0 / (1.0 + Exp(-x)));
static function Activations.Tanh(v: Vector): Vector := v.Apply(System.Math.Tanh);
static function Activations.ReLU(v: Vector): Vector := v.Apply(x -> (if x > 0 then x else 0.0));
static function Activations.Softmax(v: Vector): Vector;
begin
var maxVal := v.Max;
var shifted := v.Apply(x -> Exp(x - maxVal));
var sumExp := shifted.Sum;
if sumExp = 0 then
exit(new Vector(v.Length));
Result := shifted / sumExp;
end;
//-----------------------------
// LogisticRegression
//-----------------------------
constructor LogisticRegression.Create(lambda: real; lr: real; epochs: integer);
begin
fLambda := lambda;
fLearningRate := lr;
fEpochs := epochs;
fFitted := false;
end;
function LogisticRegression.Fit(X: Matrix; y: Vector): IModel;
begin
var n := X.RowCount;
var p := X.ColCount;
if y.Length <> n then
raise new Exception('Dimension mismatch in Fit');
fCoef := new Vector(p);
fIntercept := 0.0;
for var epoch := 1 to fEpochs do
begin
var z := X * fCoef + fIntercept;
var yHat := Activations.Sigmoid(z);
var error := yHat - y;
var gradW := (X.Transpose * error) / n;
if fLambda > 0 then
gradW := gradW + fLambda * fCoef;
var gradB := error.Sum / n;
fCoef := fCoef - fLearningRate * gradW;
fIntercept -= fLearningRate * gradB;
end;
fFitted := true;
Result := Self;
end;
function LogisticRegression.PredictProba(X: Matrix): Vector;
begin
if not fFitted then
raise new Exception('Model not fitted');
Result := Activations.Sigmoid(X * fCoef + fIntercept);
end;
function LogisticRegression.Predict(X: Matrix; threshold: real): Vector;
begin
var p := PredictProba(X);
Result := new Vector(p.Length);
for var i := 0 to p.Length - 1 do
if p[i] >= threshold then
Result[i] := 1.0
else
Result[i] := 0.0;
end;
function LogisticRegression.Predict(X: Matrix): Vector;
begin
Result := Predict(X, 0.5);
end;
//-----------------------------
// RidgeRegression
//-----------------------------
constructor RidgeRegression.Create(lambda: real);
begin
if lambda < 0 then
raise new ArgumentException('lambda must be >= 0');
fLambda := lambda;
fFitted := false;
end;
function RidgeRegression.Fit(X: Matrix; y: Vector): IModel;
begin
if X.RowCount <> y.Length then
raise new ArgumentException('X and y size mismatch');
var n := X.RowCount;
var p := X.ColCount;
// Means
var muX := X.ColumnMeans;
var muY := y.Mean;
// Centered copies
var Xc := X.Clone;
var yc := y.Clone;
for var j := 0 to p - 1 do
for var i := 0 to n - 1 do
Xc[i, j] -= muX[j];
for var i := 0 to n - 1 do
yc[i] -= muY;
// Ridge solution
fCoef := SolveRidge(Xc, yc, fLambda);
// Intercept (NOT regularized)
fIntercept := muY - muX.Dot(fCoef);
fFitted := true;
Result := Self;
end;
function RidgeRegression.Predict(X: Matrix): Vector;
begin
if not fFitted then
raise new InvalidOperationException('Model is not fitted');
Result := X * fCoef;
for var i := 0 to Result.Length - 1 do
Result[i] += fIntercept;
end;
constructor ElasticNet.Create(lambda1, lambda2: real; maxIter: integer; tol: real);
begin
if (lambda1 < 0) or (lambda2 < 0) then
raise new ArgumentException('lambda must be >= 0');
fLambda1 := lambda1;
fLambda2 := lambda2;
fMaxIter := maxIter;
fTol := tol;
fFitted := false;
end;
function ElasticNet.SoftThreshold(z, gamma: real): real;
begin
if z > gamma then
exit(z - gamma)
else if z < -gamma then
exit(z + gamma)
else
exit(0.0);
end;
function ElasticNet.Fit(X: Matrix; y: Vector): IModel;
begin
if X.RowCount <> y.Length then
raise new ArgumentException('X and y size mismatch');
// Loss(β) = ||y - (Xβ + b)||² + λ1||β||1 + λ2||β||2²
var n := X.RowCount;
var p := X.ColCount;
var muX := X.ColumnMeans;
var muY := y.Mean;
var Xc := X.Clone;
var yc := y.Clone;
for var j := 0 to p - 1 do
for var i := 0 to n - 1 do
Xc[i,j] -= muX[j];
for var i := 0 to n - 1 do
yc[i] -= muY;
fCoef := new Vector(p);
var residual := yc.Clone; // initial residual = yc (since β=0)
for var iter := 0 to fMaxIter - 1 do
begin
var maxChange := 0.0;
for var j := 0 to p - 1 do
begin
var oldBeta := fCoef[j];
var rho := 0.0;
var zj := 0.0;
for var i := 0 to n - 1 do
begin
rho += Xc[i,j] * (residual[i] + Xc[i,j] * oldBeta);
zj += Xc[i,j] * Xc[i,j];
end;
var newBeta := SoftThreshold(rho, fLambda1) / (zj + fLambda2);
var delta := newBeta - oldBeta;
if Abs(delta) > 0 then
for var i := 0 to n - 1 do
residual[i] -= Xc[i,j] * delta;
if Abs(delta) > maxChange then
maxChange := Abs(delta);
fCoef[j] := newBeta;
end;
if maxChange < fTol then
break;
end;
fIntercept := muY - muX.Dot(fCoef);
fFitted := true;
Result := Self;
end;
function ElasticNet.Predict(X: Matrix): Vector;
begin
if not fFitted then
raise new InvalidOperationException('Model is not fitted');
Result := X * fCoef;
for var i := 0 to Result.Length - 1 do
Result[i] += fIntercept;
end;
constructor MulticlassLogisticRegression.Create(lambda: real; lr: real; epochs: integer);
begin
fLambda := lambda;
fLearningRate := lr;
fEpochs := epochs;
fFitted := false;
end;
function MulticlassLogisticRegression.Fit(X: Matrix; y: Vector): IModel;
begin
var m := X.RowCount;
var p := X.ColCount;
// число классов
fClassCount := Round(y.Max) + 1;
// инициализация параметров
fW := new Matrix(p, fClassCount);
fIntercept := new Vector(fClassCount);
// one-hot матрица
var YoneHot := new Matrix(m, fClassCount);
for var i := 0 to m - 1 do
YoneHot[i, Round(y[i])] := 1.0;
for var epoch := 1 to fEpochs do
begin
// Z = XW + b
var Z := X * fW;
for var i := 0 to m - 1 do
for var k := 0 to fClassCount - 1 do
Z[i,k] += fIntercept[k];
// softmax
for var i := 0 to m - 1 do
begin
var maxVal := Z.RowMax(i);
var sumExp := 0.0;
for var k := 0 to fClassCount - 1 do
begin
Z[i,k] := Exp(Z[i,k] - maxVal);
sumExp += Z[i,k];
end;
for var k := 0 to fClassCount - 1 do
Z[i,k] /= sumExp;
end;
var PP := Z;
// градиенты
var diff := PP - YoneHot; // m x k
var gradW := X.Transpose * diff; // p x k
// L2 регуляризация (только веса)
if fLambda <> 0 then
gradW += fLambda * fW;
// обновление
fW -= fLearningRate * gradW;
// градиент по intercept
var gradB := diff.ColumnSums;
fIntercept -= fLearningRate * gradB;
end;
fFitted := true;
Result := Self;
end;
function MulticlassLogisticRegression.PredictProba(X: Matrix): Vector;
begin
var P := PredictProbaMatrix(X);
var m := P.RowCount;
Result := new Vector(m);
for var i := 0 to m - 1 do
begin
var k := P.RowArgMax(i);
Result[i] := P[i, k];
end;
end;
function MulticlassLogisticRegression.PredictProbaMatrix(X: Matrix): Matrix;
begin
if not fFitted then
raise new Exception('Model not fitted');
var m := X.RowCount;
var Z := X * fW;
for var i := 0 to m - 1 do
for var k := 0 to fClassCount - 1 do
Z[i,k] += fIntercept[k];
for var i := 0 to m - 1 do
begin
var maxVal := Z.RowMax(i);
var sumExp := 0.0;
for var k := 0 to fClassCount - 1 do
begin
Z[i,k] := Exp(Z[i,k] - maxVal);
sumExp += Z[i,k];
end;
for var k := 0 to fClassCount - 1 do
Z[i,k] /= sumExp;
end;
Result := Z;
end;
function MulticlassLogisticRegression.Predict(X: Matrix): Vector;
begin
var P := PredictProbaMatrix(X);
var m := P.RowCount;
Result := new Vector(m);
for var i := 0 to m - 1 do
Result[i] := P.RowArgMax(i);
end;
//-----------------------------
// Pipeline
//-----------------------------
constructor Pipeline.Create;
begin
fTransformers := new List<ITransformer>;
fModel := nil;
fFitted := false;
end;
constructor Pipeline.Create(model: IModel);
begin
Create;
if model = nil then
raise new ArgumentException('Model cannot be nil');
fModel := model;
end;
static function Pipeline.Build(model: IModel; transformers: array of ITransformer): Pipeline;
begin
Result := new Pipeline(model);
foreach var t in transformers do
Result.Add(t);
end;
function Pipeline.Add(t: ITransformer): Pipeline;
begin
if t = nil then
raise new ArgumentException('Transformer cannot be nil');
fTransformers.Add(t);
Result := Self;
end;
function Pipeline.SetModel(m: IModel): Pipeline;
begin
if m = nil then
raise new ArgumentException('Model cannot be nil');
fModel := m;
Result := Self;
end;
function Pipeline.Fit(X: Matrix; y: Vector): IModel;
begin
if fModel = nil then
raise new Exception('Model is not set');
var Xt := X;
foreach var t in fTransformers do
begin
if t is ISupervisedTransformer (var sup) then
sup.Fit(Xt, y)
else t.Fit(Xt);
Xt := t.Transform(Xt);
end;
fModel.Fit(Xt, y);
fFitted := true;
Result := Self;
end;
function Pipeline.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new Exception('Pipeline not fitted');
var Xt := X;
foreach var t in fTransformers do
Xt := t.Transform(Xt);
Result := Xt;
end;
function Pipeline.Predict(X: Matrix): Vector;
begin
if not fFitted then
raise new Exception('Pipeline not fitted');
var Xt := Transform(X);
Result := fModel.Predict(Xt);
end;
function Pipeline.PredictProba(X: Matrix): Vector;
begin
if not fFitted then
raise new Exception('Pipeline not fitted');
if not (fModel is IProbabilisticClassifier) then
raise new Exception('Model does not support probability prediction');
var Xt := Transform(X);
Result := (fModel as IProbabilisticClassifier).PredictProba(Xt);
end;
//-----------------------------
// StandardScaler
//-----------------------------
function StandardScaler.Fit(X: Matrix): ITransformer;
begin
fMean := X.ColumnMeans;
fStd := X.ColumnStd;
fFitted := true;
Result := self;
end;
function StandardScaler.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new Exception('StandardScaler not fitted');
var n := X.RowCount;
var p := X.ColCount;
Result := new Matrix(n, p);
for var j := 0 to p - 1 do
begin
var mean := fMean[j];
var std := fStd[j];
for var i := 0 to n - 1 do
begin
if std <> 0 then
Result[i,j] := (X[i,j] - mean) / std
else Result[i,j] := 0.0;
end;
end;
end;
//-----------------------------
// MinMaxScaler
//-----------------------------
constructor MinMaxScaler.Create(rangeMin: real; rangeMax: real);
begin
if rangeMax <= rangeMin then
raise new Exception('rangeMax must be greater than rangeMin');
fRangeMin := rangeMin;
fRangeMax := rangeMax;
fFitted := false;
end;
function MinMaxScaler.Fit(X: Matrix): ITransformer;
begin
fMin := X.ColumnMins;
fMax := X.ColumnMaxs;
fFitted := true;
Result := self;
end;
function MinMaxScaler.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new Exception('MinMaxScaler is not fitted');
var n := X.RowCount;
var p := X.ColCount;
Result := new Matrix(n, p);
var scale := fRangeMax - fRangeMin;
for var j := 0 to p - 1 do
begin
var minVal := fMin[j];
var maxVal := fMax[j];
var denom := maxVal - minVal;
for var i := 0 to n - 1 do
begin
if denom <> 0 then
Result[i,j] := fRangeMin + (X[i,j] - minVal) / denom * scale
else Result[i,j] := fRangeMin; // если столбец константный
end;
end;
end;
//-----------------------------
// PCATransformer
//-----------------------------
constructor PCATransformer.Create(k: integer);
begin
if k <= 0 then
raise new ArgumentException('k must be > 0');
fK := k;
fFitted := false;
end;
function PCATransformer.Fit(X: Matrix): ITransformer;
begin
if fK > X.ColCount then
raise new ArgumentException('k exceeds feature count');
fMean := X.ColumnMeans;
var Xc := X.Clone;
for var j := 0 to X.ColCount - 1 do
for var i := 0 to X.RowCount - 1 do
Xc[i,j] -= fMean[j];
var (W, xxx) := Xc.PCA(fK);
fComponents := W;
fFitted := true;
Result := Self;
end;
function PCATransformer.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new InvalidOperationException('PCA not fitted');
var Xc := X.Clone;
for var j := 0 to X.ColCount - 1 do
for var i := 0 to X.RowCount - 1 do
Xc[i,j] -= fMean[j];
Result := Xc * fComponents;
end;
//-----------------------------
// VarianceThreshold
//-----------------------------
constructor VarianceThreshold.Create(threshold: real);
begin
if threshold < 0 then
raise new ArgumentException('threshold must be >= 0');
fThreshold := threshold;
fFitted := false;
end;
function VarianceThreshold.Fit(X: Matrix): ITransformer;
begin
var vars := X.ColumnVariances;
var tmp := new List<integer>;
for var j := 0 to X.ColCount - 1 do
if vars[j] >= fThreshold then
tmp.Add(j);
fSelected := tmp.ToArray;
fFitted := true;
Result := Self;
end;
function VarianceThreshold.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new InvalidOperationException('VarianceThreshold not fitted');
var n := X.RowCount;
var k := fSelected.Length;
var R := new Matrix(n, k);
for var i := 0 to n - 1 do
for var c := 0 to k - 1 do
R[i,c] := X[i, fSelected[c]];
Result := R;
end;
//-----------------------------
// SelectKBest
//-----------------------------
constructor SelectKBest.Create(k: integer; score: FeatureScore);
begin
if k <= 0 then
raise new ArgumentException('k must be > 0');
fK := k;
fScoreType := score;
fScoreFunc := nil;
fFitted := false;
end;
constructor SelectKBest.Create(k: integer; scoreFunc: (Vector, Vector) -> real);
begin
if k <= 0 then
raise new ArgumentException('k must be > 0');
fK := k;
fScoreFunc := scoreFunc;
fFitted := false;
end;
function SelectKBest.ComputeCorrelation(x: Vector; y: Vector): real;
begin
var mx := x.Mean;
var my := y.Mean;
var num := 0.0;
var dx := 0.0;
var dy := 0.0;
for var i := 0 to x.Length - 1 do
begin
var vx := x[i] - mx;
var vy := y[i] - my;
num += vx * vy;
dx += vx * vx;
dy += vy * vy;
end;
if (dx = 0) or (dy = 0) then
exit(0.0);
Result := Abs(num / Sqrt(dx * dy));
end;
function SelectKBest.ComputeFRegression(feature: Vector; y: Vector): real;
begin
var r := ComputeCorrelation(feature, y);
var n := feature.Length;
if Abs(r) >= 1 then
exit(1e308);
Result := (r*r / (1 - r*r)) * (n - 2);
end;
function SelectKBest.ComputeAnovaF(feature: Vector; y: Vector): real;
begin
var mean0 := 0.0;
var mean1 := 0.0;
var n0 := 0;
var n1 := 0;
for var i := 0 to y.Length - 1 do
if y[i] = 0 then
begin
mean0 += feature[i];
n0 += 1;
end
else
begin
mean1 += feature[i];
n1 += 1;
end;
if (n0 = 0) or (n1 = 0) then
exit(0.0);
mean0 /= n0;
mean1 /= n1;
var grandMean := feature.Mean;
var ssBetween :=
n0 * Sqr(mean0 - grandMean) +
n1 * Sqr(mean1 - grandMean);
var ssWithin := 0.0;
for var i := 0 to y.Length - 1 do
if y[i] = 0 then
ssWithin += Sqr(feature[i] - mean0)
else
ssWithin += Sqr(feature[i] - mean1);
if ssWithin = 0 then
exit(1e308);
Result := (ssBetween) / (ssWithin / (y.Length - 2));
end;
function SelectKBest.ComputeChiSquare(feature: Vector; y: Vector): real;
begin
var n := y.Length;
var sum0 := 0.0;
var sum1 := 0.0;
var total := 0.0;
var n0 := 0;
var n1 := 0;
for var i := 0 to n - 1 do
begin
var v := feature[i];
if v < 0 then
raise new ArgumentException('ChiSquare requires non-negative features');
total += v;
if y[i] = 0 then
begin
sum0 += v;
n0 += 1;
end
else
begin
sum1 += v;
n1 += 1;
end;
end;
if (n0 = 0) or (n1 = 0) or (total = 0) then
exit(0.0);
var expected0 := total * n0 / n;
var expected1 := total * n1 / n;
var chi := 0.0;
if expected0 > 0 then
chi += Sqr(sum0 - expected0) / expected0;
if expected1 > 0 then
chi += Sqr(sum1 - expected1) / expected1;
Result := chi;
end;
function SelectKBest.ComputeScore(feature: Vector; y: Vector): real;
begin
case fScoreType of
FeatureScore.Correlation: Result := ComputeCorrelation(feature, y);
FeatureScore.FRegression: Result := ComputeFRegression(feature, y);
FeatureScore.AnovaF: Result := ComputeAnovaF(feature, y);
FeatureScore.ChiSquare: Result := ComputeChiSquare(feature, y);
else
raise new InvalidOperationException('Unknown FeatureScore type');
end;
end;
function SelectKBest.Fit(X: Matrix; y: Vector): ITransformer;
begin
var p := X.ColCount;
var scores: array of (real, integer);
SetLength(scores, p);
for var j := 0 to p - 1 do
begin
var col := new Vector(X.RowCount);
for var i := 0 to X.RowCount - 1 do
col[i] := X[i,j];
var s :=
if fScoreFunc <> nil then
fScoreFunc(col, y)
else
ComputeCorrelation(col, y);
scores[j] := (s, j);
end;
scores := scores.OrderByDescending(t -> t.Item1).ToArray;
var k := Min(fK, p);
SetLength(fSelected, k);
for var i := 0 to k - 1 do
fSelected[i] := scores[i].Item2;
fFitted := true;
Result := Self;
end;
function SelectKBest.Fit(X: Matrix): ITransformer;
begin
Result := nil;
raise new InvalidOperationException('Target variable is required');
end;
function SelectKBest.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new InvalidOperationException('SelectKBest not fitted');
var n := X.RowCount;
var k := fSelected.Length;
var R := new Matrix(n, k);
for var i := 0 to n - 1 do
for var c := 0 to k - 1 do
R[i,c] := X[i, fSelected[c]];
Result := R;
end;
//-----------------------------
// Normalizer
//-----------------------------
constructor Normalizer.Create(norm: NormType);
begin
fNormType := norm;
fFitted := false;
end;
function Normalizer.Fit(X: Matrix): ITransformer;
begin
// Нормализация не требует обучения
fFitted := true;
Result := Self;
end;
function Normalizer.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new InvalidOperationException('Normalizer not fitted');
var n := X.RowCount;
var p := X.ColCount;
var R := new Matrix(n, p);
for var i := 0 to n - 1 do
begin
var norm := 0.0;
case fNormType of
NormType.L1:
for var j := 0 to p - 1 do
norm += Abs(X[i,j]);
NormType.L2:
for var j := 0 to p - 1 do
norm += Sqr(X[i,j]);
end;
if fNormType = NormType.L2 then
norm := Sqrt(norm);
if norm = 0 then
continue;
for var j := 0 to p - 1 do
R[i,j] := X[i,j] / norm;
end;
Result := R;
end;
end.