From f0599232a97bca02d56466a75940becd60c4840f Mon Sep 17 00:00:00 2001 From: Mikhalkovich Stanislav Date: Sun, 15 Feb 2026 08:43:44 +0300 Subject: [PATCH] gridSearch RidgeRegression ElasticNet --- Configuration/GlobalAssemblyInfo.cs | 2 +- Configuration/Version.defs | 2 +- Release/pabcversion.txt | 2 +- ReleaseGenerators/PascalABCNET_version.nsh | 2 +- _GenerateLinuxVersion.bat | 10 + bin/Lib/MLABC.pas | 14 +- bin/Lib/MLModelsABC.pas | 270 ++++++++++++++++++++- bin/Lib/ValidationML.pas | 100 ++++++++ 8 files changed, 392 insertions(+), 10 deletions(-) diff --git a/Configuration/GlobalAssemblyInfo.cs b/Configuration/GlobalAssemblyInfo.cs index 2a7bdaf6b..f5cb60675 100644 --- a/Configuration/GlobalAssemblyInfo.cs +++ b/Configuration/GlobalAssemblyInfo.cs @@ -15,7 +15,7 @@ internal static class RevisionClass public const string Major = "3"; public const string Minor = "11"; public const string Build = "1"; - public const string Revision = "3749"; + public const string Revision = "3751"; public const string MainVersion = Major + "." + Minor; public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision; diff --git a/Configuration/Version.defs b/Configuration/Version.defs index 4eedb6e2d..6d465f1a2 100644 --- a/Configuration/Version.defs +++ b/Configuration/Version.defs @@ -1,4 +1,4 @@ %MINOR%=11 -%REVISION%=3749 +%REVISION%=3751 %COREVERSION%=1 %MAJOR%=3 diff --git a/Release/pabcversion.txt b/Release/pabcversion.txt index d890865c6..6d60b906d 100644 --- a/Release/pabcversion.txt +++ b/Release/pabcversion.txt @@ -1 +1 @@ -3.11.1.3749 +3.11.1.3751 diff --git a/ReleaseGenerators/PascalABCNET_version.nsh b/ReleaseGenerators/PascalABCNET_version.nsh index 1bf0a6dfc..ef46e1b11 100644 --- a/ReleaseGenerators/PascalABCNET_version.nsh +++ b/ReleaseGenerators/PascalABCNET_version.nsh @@ -1 +1 @@ -!define VERSION '3.11.1.3749' +!define VERSION '3.11.1.3751' diff --git a/_GenerateLinuxVersion.bat b/_GenerateLinuxVersion.bat index 30d3a3eab..140bf35cf 100644 --- a/_GenerateLinuxVersion.bat +++ b/_GenerateLinuxVersion.bat @@ -114,6 +114,11 @@ copy bin\Lib\DataFrameABC.pcu Release\PascalABCNETLinux\Lib\DataFrameABC.pcu copy bin\Lib\DataFrameABCCore.pcu Release\PascalABCNETLinux\Lib\DataFrameABCCore.pcu copy bin\Lib\LinearAlgebraML.pcu Release\PascalABCNETLinux\Lib\LinearAlgebraML.pcu copy bin\Lib\PreprocessorABC.pcu Release\PascalABCNETLinux\Lib\PreprocessorABC.pcu +copy bin\Lib\MetricsABC.pcu Release\PascalABCNETLinux\Lib\MetricsABC.pcu +copy bin\Lib\MLABC.pcu Release\PascalABCNETLinux\Lib\MLABC.pcu +copy bin\Lib\MLCoreABC.pcu Release\PascalABCNETLinux\Lib\MLCoreABC.pcu +copy bin\Lib\MLModelsABC.pcu Release\PascalABCNETLinux\Lib\MLModelsABC.pcu +copy bin\Lib\ValidationML.pcu Release\PascalABCNETLinux\Lib\ValidationML.pcu copy bin\Lib\ABCDatabases.pas Release\PascalABCNETLinux\LibSource\ABCDatabases.pas copy bin\Lib\BBCMicrobit.pas Release\PascalABCNETLinux\LibSource\BBCMicrobit.pas @@ -159,6 +164,11 @@ copy bin\Lib\DataFrameABC.pas Release\PascalABCNETLinux\LibSource\DataFrameABC.p copy bin\Lib\DataFrameABCCore.pas Release\PascalABCNETLinux\LibSource\DataFrameABCCore.pas copy bin\Lib\LinearAlgebraML.pas Release\PascalABCNETLinux\LibSource\LinearAlgebraML.pas copy bin\Lib\PreprocessorABC.pas Release\PascalABCNETLinux\LibSource\PreprocessorABC.pas +copy bin\Lib\MetricsABC.pas Release\PascalABCNETLinux\LibSource\MetricsABC.pas +copy bin\Lib\MLABC.pas Release\PascalABCNETLinux\LibSource\MLABC.pas +copy bin\Lib\MLCoreABC.pas Release\PascalABCNETLinux\LibSource\MLCoreABC.pas +copy bin\Lib\MLModelsABC.pas Release\PascalABCNETLinux\LibSource\MLModelsABC.pas +copy bin\Lib\ValidationML.pas Release\PascalABCNETLinux\LibSource\ValidationML.pas copy bin\Lng\Eng\.LanguageName Release\PascalABCNETLinux\Lng\Eng\.LanguageName copy bin\Lng\Eng\AspectsTree.dat Release\PascalABCNETLinux\Lng\Eng\AspectsTree.dat diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index 807670951..dba353dbb 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -15,19 +15,23 @@ type Vector = LinearAlgebraML.Vector; Matrix = LinearAlgebraML.Matrix; Validation = ValidationML.Validation; - LinearRegression = MLModelsABC.LinearRegression; - LogisticRegression = MLModelsABC.LogisticRegression; ConfusionMatrix = MetricsABC.ConfusionMatrix; Metrics = MetricsABC.Metrics; - Activations = MLModelsABC.Activations; - Pipeline = MLModelsABC.Pipeline; DataPipeline = PreprocessorABC.DataPipeline; DataStandardScaler = PreprocessorABC.DataStandardScaler; - StandardScaler = MLModelsABC.StandardScaler; DataFrame = DataFrameABC.DataFrame; Statistics = DataFrameABC.Statistics; CsvLoader = DataFrameABC.CsvLoader; + StandardScaler = MLModelsABC.StandardScaler; + Activations = MLModelsABC.Activations; + Pipeline = MLModelsABC.Pipeline; + + LinearRegression = MLModelsABC.LinearRegression; + LogisticRegression = MLModelsABC.LogisticRegression; + RidgeRegression = MLModelsABC.RidgeRegression; + ElasticNet = ElasticNet; + implementation function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod; diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index 69ec9aa32..bbc0c7316 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -6,6 +6,7 @@ uses MLCoreABC; uses LinearAlgebraML; type +{$region Activations} /// Активационные функции для моделей Activations = static class public @@ -40,10 +41,16 @@ type /// Формула: 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; @@ -82,6 +89,8 @@ type /// Предсказывает вероятность принадлежности объекта к классу 1 /// на основе линейной комбинации признаков и сигмоидной функции. /// Поддерживает L2-регуляризацию. + /// Используется в задачах бинарной классификации, + /// когда требуется вероятностный вывод и интерпретируемые коэффициенты. LogisticRegression = class(IClassifier) private fCoef: Vector; @@ -129,7 +138,107 @@ type /// После вызова 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; + +{$endregion Models} + +{$region Pipeline} /// Конвейер машинного обучения (Pipeline). /// Объединяет несколько шагов подготовки данных и модель /// в единую последовательность обработки. @@ -172,7 +281,9 @@ type /// Показывает, был ли пайплайн обучен (вызван метод Fit). property IsFitted: boolean read fFitted; end; +{$endregion Pipeline} +{$region Transformers} /// Стандартизирует признаки: вычитает среднее /// и делит на стандартное отклонение по каждому столбцу. /// Используется для приведения признаков к сопоставимому масштабу. @@ -231,10 +342,13 @@ type /// Признак того, что преобразование обучено. property IsFitted: boolean read fFitted; end; +{$endregion Transformers} implementation +uses System; + //----------------------------- // LinearRegression //----------------------------- @@ -270,7 +384,7 @@ begin // 4. Ridge regularization if flambda > 0 then - XtX := XtX + flambda * Matrix.Identity(p); + XtX := XtX + Matrix.Identity(p) * flambda; // 5. Solve fcoef := Solve(XtX, XtY); @@ -383,6 +497,160 @@ 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; + + //----------------------------- // Pipeline //----------------------------- diff --git a/bin/Lib/ValidationML.pas b/bin/Lib/ValidationML.pas index 173700545..8ebfb6944 100644 --- a/bin/Lib/ValidationML.pas +++ b/bin/Lib/ValidationML.pas @@ -44,9 +44,35 @@ type static function StratifiedCrossValidate(model: IModel; X: Matrix; y: Vector; k: integer; metric: (Vector,Vector) -> real; seed: integer := 0): real; end; + +/// Класс для подбора гиперпараметров методом перебора по сетке (Grid Search). +/// Для каждого значения параметра выполняется k-кратная кросс-валидация. +/// Выбирается параметр, дающий наилучшее среднее значение метрики. +/// Используется для настройки регуляризации и других гиперпараметров моделей. + GridSearch = static class + public + /// Выполняет подбор гиперпараметра по заданной сетке значений. + /// modelFactory — функция создания модели по значению параметра. + /// paramValues — набор тестируемых значений гиперпараметра. + /// X, y — обучающие данные. + /// k — число фолдов в кросс-валидации. + /// metric — функция оценки качества (yTrue, yPred) → real. + /// Возвращает кортеж (лучший параметр, лучшее среднее значение метрики). + class function Search( + modelFactory: real -> IModel; + paramValues: array of real; + X: Matrix; y: Vector; + k: integer; + metric: (Vector, Vector) -> real + ): (real, real); where T: IModel; + end; implementation +//----------------------------- +// Validation +//----------------------------- + static function Validation.TrainTestSplit(X: Matrix; y: Vector; testRatio: real; seed: integer): (Matrix, Matrix, Vector, Vector); begin @@ -265,6 +291,80 @@ begin Result := total / folds; end; +//----------------------------- +// GridSearch +//----------------------------- + +class function GridSearch.Search( + modelFactory: real -> IModel; + paramValues: array of real; + X: Matrix; y: Vector; + k: integer; + metric: (Vector, Vector) -> real +): (real, real); where T: IModel; +begin + var bestParam := 0.0; + var bestScore := -1e308; + + var n := X.RowCount; + var foldSize := n div k; + + foreach var param in paramValues do + begin + var totalScore := 0.0; + + for var fold := 0 to k - 1 do + begin + var startIdx := fold * foldSize; + var endIdx := if fold = k - 1 then n - 1 else (startIdx + foldSize - 1); + + var trainCount := n - (endIdx - startIdx + 1); + var testCount := endIdx - startIdx + 1; + + var Xtrain := new Matrix(trainCount, X.ColCount); + var ytrain := new Vector(trainCount); + var Xtest := new Matrix(testCount, X.ColCount); + var ytest := new Vector(testCount); + + var ti := 0; + var si := 0; + + for var i := 0 to n - 1 do + begin + if (i >= startIdx) and (i <= endIdx) then + begin + for var j := 0 to X.ColCount - 1 do + Xtest[si,j] := X[i,j]; + ytest[si] := y[i]; + si += 1; + end + else + begin + for var j := 0 to X.ColCount - 1 do + Xtrain[ti,j] := X[i,j]; + ytrain[ti] := y[i]; + ti += 1; + end; + end; + + var model := modelFactory(param); + model.Fit(Xtrain, ytrain); + + var pred := model.Predict(Xtest); + totalScore += metric(ytest, pred); + end; + + var avgScore := totalScore / k; + + if avgScore > bestScore then + begin + bestScore := avgScore; + bestParam := param; + end; + end; + + Result := (bestParam, bestScore); +end; end.