diff --git a/bin/Lib/LinearAlgebraML.pas b/bin/Lib/LinearAlgebraML.pas index 4f06ecd5f..f4ea39c78 100644 --- a/bin/Lib/LinearAlgebraML.pas +++ b/bin/Lib/LinearAlgebraML.pas @@ -5,7 +5,6 @@ interface type Vector = class private - static procedure CheckSameLength(const a, b: Vector); static procedure CheckNonEmpty(const v: Vector); procedure SetData(i: integer; value: real) := data[i] := value; @@ -93,7 +92,25 @@ type function ColumnMins: Vector; function ColumnMaxs: Vector; function RowMins: Vector; - function RowMaxs: Vector; + function RowMaxs: Vector; + + function RowSum(i: integer): real; + function RowMean(i: integer): real; + function RowVariance(i: integer): real; + function RowStd(i: integer): real; + function RowMin(i: integer): real; + function RowMax(i: integer): real; + function RowArgMin(i: integer): integer; + function RowArgMax(i: integer): integer; + + function ColumnSum(j: integer): real; + function ColumnMean(j: integer): real; + function ColumnVariance(j: integer): real; + function ColumnStd(j: integer): real; + function ColumnMin(j: integer): real; + function ColumnMax(j: integer): real; + function ColumnArgMin(j: integer): integer; + function ColumnArgMax(j: integer): integer; function FrobeniusNorm: real; procedure AddScaledIdentity(lambda: real); @@ -561,6 +578,154 @@ begin end; end; +function Matrix.RowArgMin(i: integer): integer; +begin + var minVal := data[i,0]; + var arg := 0; + + for var j := 1 to ColCount - 1 do + if data[i,j] < minVal then + begin + minVal := data[i,j]; + arg := j; + end; + + Result := arg; +end; + +function Matrix.RowMin(i: integer): real; +begin + Result := data[i, RowArgMin(i)]; +end; + +function Matrix.RowArgMax(i: integer): integer; +begin + var maxVal := data[i,0]; + var arg := 0; + + for var j := 1 to ColCount - 1 do + if data[i,j] > maxVal then + begin + maxVal := data[i,j]; + arg := j; + end; + + Result := arg; +end; + +function Matrix.RowMax(i: integer): real; +begin + Result := data[i, RowArgMax(i)]; +end; + +function Matrix.RowSum(i: integer): real; +begin + var sum := 0.0; + + for var j := 0 to ColCount - 1 do + sum += data[i,j]; + + Result := sum; +end; + +function Matrix.RowMean(i: integer): real; +begin + Result := RowSum(i) / ColCount; +end; + +function Matrix.RowVariance(i: integer): real; +begin + var mean := RowMean(i); + var sum := 0.0; + + for var j := 0 to ColCount - 1 do + begin + var d := data[i,j] - mean; + sum += d * d; + end; + + Result := sum / ColCount; +end; + +function Matrix.RowStd(i: integer): real; +begin + Result := Sqrt(RowVariance(i)); +end; + +function Matrix.ColumnArgMin(j: integer): integer; +begin + var minVal := data[0,j]; + var arg := 0; + + for var i := 1 to RowCount - 1 do + if data[i,j] < minVal then + begin + minVal := data[i,j]; + arg := i; + end; + + Result := arg; +end; + +function Matrix.ColumnMin(j: integer): real; +begin + Result := data[ColumnArgMin(j), j]; +end; + +function Matrix.ColumnArgMax(j: integer): integer; +begin + var maxVal := data[0,j]; + var arg := 0; + + for var i := 1 to RowCount - 1 do + if data[i,j] > maxVal then + begin + maxVal := data[i,j]; + arg := i; + end; + + Result := arg; +end; + +function Matrix.ColumnMax(j: integer): real; +begin + Result := data[ColumnArgMax(j), j]; +end; + +function Matrix.ColumnSum(j: integer): real; +begin + var sum := 0.0; + + for var i := 0 to RowCount - 1 do + sum += data[i,j]; + + Result := sum; +end; + +function Matrix.ColumnMean(j: integer): real; +begin + Result := ColumnSum(j) / RowCount; +end; + +function Matrix.ColumnVariance(j: integer): real; +begin + var mean := ColumnMean(j); + var sum := 0.0; + + for var i := 0 to RowCount - 1 do + begin + var d := data[i,j] - mean; + sum += d * d; + end; + + Result := sum / RowCount; +end; + +function Matrix.ColumnStd(j: integer): real; +begin + Result := Sqrt(ColumnVariance(j)); +end; + function Matrix.FrobeniusNorm: real; begin diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index dba353dbb..7db918b9d 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -24,13 +24,20 @@ type CsvLoader = DataFrameABC.CsvLoader; StandardScaler = MLModelsABC.StandardScaler; + PCATransformer = MLModelsABC.PCATransformer; + MinMaxScaler = MLModelsABC.MinMaxScaler; + VarianceThreshold = MLModelsABC.VarianceThreshold; + SelectKBest = MLModelsABC.SelectKBest; + FeatureScore = MLModelsABC.FeatureScore; + Activations = MLModelsABC.Activations; Pipeline = MLModelsABC.Pipeline; LinearRegression = MLModelsABC.LinearRegression; LogisticRegression = MLModelsABC.LogisticRegression; RidgeRegression = MLModelsABC.RidgeRegression; - ElasticNet = ElasticNet; + ElasticNet = MLModelsABC.ElasticNet; + MulticlassLogisticRegression = MLModelsABC.MulticlassLogisticRegression; implementation diff --git a/bin/Lib/MLCoreABC.pas b/bin/Lib/MLCoreABC.pas index 3092b64fd..33faefa5b 100644 --- a/bin/Lib/MLCoreABC.pas +++ b/bin/Lib/MLCoreABC.pas @@ -48,6 +48,17 @@ type function Transform(X: Matrix): Matrix; end; + /// Интерфейс преобразования признаков с учётом целевой переменной. + /// Используется для методов отбора признаков и других процедур, + /// в которых при обучении требуется вектор целевых значений. + ISupervisedTransformer = interface(ITransformer) + /// Обучает преобразование на данных с использованием + /// как признаков X, так и целевой переменной y. + /// Запоминает необходимые параметры, + /// которые будут использоваться при Transform. + function Fit(X: Matrix; y: Vector): ISupervisedTransformer; + end; + implementation end. \ No newline at end of file diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index bbc0c7316..b5bf6c37b 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -234,34 +234,80 @@ type /// Показывает, была ли модель обучена. /// После вызова 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} - /// Конвейер машинного обучения (Pipeline). - /// Объединяет несколько шагов подготовки данных и модель - /// в единую последовательность обработки. - /// Сначала данные проходят через все преобразования, - /// затем итоговые признаки передаются в модель. - /// Обеспечивает единый интерфейс Fit / Predict. +/// Последовательный конвейер машинного обучения. +/// Гарантирует строгий порядок выполнения шагов: +/// [преобразователи] → [модель]. +/// +/// Поддерживает: +/// - преобразователи без учёта целевой переменной; +/// - преобразователи с учётом целевой переменной; +/// - одну финальную модель. +/// +/// Обеспечивает единый интерфейс Fit / Predict +/// и воспроизводимость полного процесса обучения. Pipeline = class(IModel) private fTransformers: List; fModel: IModel; fFitted: boolean; public - /// Создаёт пайплайн (конвейер машинного обучения) для заданной модели. - /// model — финальная модель (регрессор или классификатор), - /// которая будет обучаться после всех преобразований. + /// Создаёт конвейер машинного обучения для заданной модели. + /// model — модель, которая будет обучена + /// после последовательного применения всех преобразователей. constructor Create(model: IModel); + + /// Создаёт пустой пайплайн (конвейер машинного обучения). + /// Модель должна быть установлена через SetModel. + constructor Create; - /// Строит пайплайн (конвейер машинного обучения). - /// model — финальная модель, которая будет обучаться. - /// transformers — последовательность преобразований признаков, - /// применяемых к данным перед обучением модели. - /// Возвращает настроенный конвейер. + /// Строит конвейер машинного обучения. + /// model — модель, обучаемая после применения всех преобразователей. + /// transformers — последовательность преобразователей признаков, + /// применяемых к данным перед обучением модели. + /// Возвращает сконструированный конвейер. static function Build(model: IModel; params transformers: array of ITransformer): Pipeline; + + /// Устанавливает или заменяет модель. + function SetModel(m: IModel): Pipeline; /// Добавляет преобразование в конец пайплайна function Add(t: ITransformer): Pipeline; @@ -342,6 +388,163 @@ type /// Признак того, что преобразование обучено. 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} @@ -650,54 +853,209 @@ begin 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; + 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; - fTransformers := new List; - fFitted := false; end; static function Pipeline.Build(model: IModel; transformers: array of ITransformer): Pipeline; begin - var pipe := new Pipeline(model); + Result := new Pipeline(model); foreach var t in transformers do - pipe.Add(t); - - Result := pipe; + 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; + 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 - var Xcur := X; + if fModel = nil then + raise new Exception('Model is not set'); + + var Xt := X; foreach var t in fTransformers do begin - t.Fit(Xcur); - Xcur := t.Transform(Xcur); + if t is ISupervisedTransformer (var sup) then + sup.Fit(Xt, y) + else t.Fit(Xt); + + Xt := t.Transform(Xt); end; - fModel.Fit(Xcur, y); - fFitted := true; + fModel.Fit(Xt, y); - Result := self; + fFitted := true; + Result := Self; end; function Pipeline.Transform(X: Matrix): Matrix; begin if not fFitted then - raise new Exception('Pipeline is not fitted'); + raise new Exception('Pipeline not fitted'); var Xt := X; @@ -712,28 +1070,21 @@ begin if not fFitted then raise new Exception('Pipeline not fitted'); - var Xcur := X; - - foreach var t in fTransformers do - Xcur := t.Transform(Xcur); - - Result := fModel.Predict(Xcur); + var Xt := Transform(X); + Result := fModel.Predict(Xt); end; function Pipeline.PredictProba(X: Matrix): Vector; begin if not fFitted then - raise new Exception('Pipeline is not fitted'); + raise new Exception('Pipeline not fitted'); - var Xt := X; + if not (fModel is IProbabilisticClassifier) then + raise new Exception('Model does not support probability prediction'); - foreach var t in fTransformers do - Xt := t.Transform(Xt); + var Xt := Transform(X); - if fModel is IProbabilisticClassifier then - exit(IProbabilisticClassifier(fModel).PredictProba(Xt)); - - raise new Exception('Model does not support probability prediction'); + Result := (fModel as IProbabilisticClassifier).PredictProba(Xt); end; //----------------------------- @@ -823,6 +1174,376 @@ begin 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; + + 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. \ No newline at end of file