diff --git a/Configuration/GlobalAssemblyInfo.cs b/Configuration/GlobalAssemblyInfo.cs index a7fdf8f75..a47313bff 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 = "3793"; + public const string Revision = "3799"; 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 bceaaea61..8081cb596 100644 --- a/Configuration/Version.defs +++ b/Configuration/Version.defs @@ -1,4 +1,4 @@ %MINOR%=11 -%REVISION%=3793 +%REVISION%=3799 %COREVERSION%=1 %MAJOR%=3 diff --git a/Release/pabcversion.txt b/Release/pabcversion.txt index 07ecdcc9e..e3822c53d 100644 --- a/Release/pabcversion.txt +++ b/Release/pabcversion.txt @@ -1 +1 @@ -3.11.1.3793 +3.11.1.3799 diff --git a/ReleaseGenerators/PascalABCNET_version.nsh b/ReleaseGenerators/PascalABCNET_version.nsh index 5d7169767..9f1c98b0b 100644 --- a/ReleaseGenerators/PascalABCNET_version.nsh +++ b/ReleaseGenerators/PascalABCNET_version.nsh @@ -1 +1 @@ -!define VERSION '3.11.1.3793' +!define VERSION '3.11.1.3799' diff --git a/ReleaseGenerators/RebuildStandartModules.pas b/ReleaseGenerators/RebuildStandartModules.pas index 04a6666d0..1c03e44c4 100644 --- a/ReleaseGenerators/RebuildStandartModules.pas +++ b/ReleaseGenerators/RebuildStandartModules.pas @@ -22,7 +22,7 @@ uses WPF, DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC, MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC, - PlotML, MLDatasets, DataAdapters + PlotML, MLDatasets, DataAdapters, MLUtilsABC ; begin diff --git a/ReleaseGenerators/RebuildStandartModulesMono.pas b/ReleaseGenerators/RebuildStandartModulesMono.pas index a75e259fb..9a8d03307 100644 --- a/ReleaseGenerators/RebuildStandartModulesMono.pas +++ b/ReleaseGenerators/RebuildStandartModulesMono.pas @@ -12,7 +12,7 @@ School, SF, TurtleABC, DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC, MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC, - PlotML, MLDatasets, DataAdapters + MLDatasets, DataAdapters, MLUtilsABC ; begin diff --git a/ReleaseGenerators/sect_Core.nsh b/ReleaseGenerators/sect_Core.nsh index 36bb13974..0dd82a1c0 100644 --- a/ReleaseGenerators/sect_Core.nsh +++ b/ReleaseGenerators/sect_Core.nsh @@ -194,6 +194,7 @@ File ..\bin\Lib\PlotML.pcu File ..\bin\Lib\MLDatasets.pcu File ..\bin\Lib\DataAdapters.pcu + File ..\bin\Lib\MLUtilsABC.pcu File ..\bin\Lib\PABCRtl.dll @@ -303,6 +304,7 @@ ${AddFile} "PlotML.pcu" ${AddFile} "MLDatasets.pcu" ${AddFile} "DataAdapters.pcu" + ${AddFile} "MLUtilsABC.pcu" ${AddFile} "turtle.png" @@ -448,6 +450,7 @@ File ..\bin\Lib\PlotML.pas File ..\bin\Lib\MLDatasets.pas File ..\bin\Lib\DataAdapters.pas + File ..\bin\Lib\MLUtilsABC.pas File ..\bin\Lib\__RedirectIOMode.vb @@ -544,6 +547,7 @@ ${AddFile} "PlotML.pas" ${AddFile} "MLDatasets.pas" ${AddFile} "DataAdapters.pas" + ${AddFile} "MLUtilsABC.pas" ${AddFile} "__RedirectIOMode.vb" diff --git a/bin/Lib/DataAdapters.pas b/bin/Lib/DataAdapters.pas index 22c37a7e3..c1f9266c9 100644 --- a/bin/Lib/DataAdapters.pas +++ b/bin/Lib/DataAdapters.pas @@ -5,13 +5,6 @@ interface uses DataFrameABC; uses LinearAlgebraML; -/// Преобразует вектор меток классов в массив целых чисел. -/// Используется при визуализации и других задачах, -/// где метки должны быть представлены как 0,1,2,... -/// Значения округляются функцией Round, чтобы устранить -/// возможные небольшие численные ошибки -function LabelsToInts(y: Vector): array of integer; - /// Кодирует строковые метки классов в целочисленные индексы. /// Каждому уникальному значению присваивается номер 0,1,2,... /// Порядок кодирования соответствует порядку первого появления меток. @@ -65,14 +58,6 @@ const ER_LABEL_INDEX_OUT_OF_RANGE = 'Индекс метки {0} вне диапазона [0, {1})!!Label index {0} is out of range [0, {1})'; -function LabelsToInts(y: Vector): array of integer; -begin - if y = nil then - ArgumentNullError(ER_ARG_NULL, 'y'); - - Result := ArrGen(y.Length, i -> Round(y[i])); -end; - function EncodeLabels(labels: array of string; var classes: array of string): array of integer; begin if labels = nil then diff --git a/bin/Lib/InspectionML.pas b/bin/Lib/InspectionML.pas index 1af427028..4747fdacd 100644 --- a/bin/Lib/InspectionML.pas +++ b/bin/Lib/InspectionML.pas @@ -45,7 +45,7 @@ type /// Вектор важностей признаков длины nFeatures. /// Чем больше значение, тем сильнее признак влияет на качество модели static function PermutationImportance(model: IPredictiveModel; X: Matrix; y: Vector; - scoreFunc: (Vector, Vector) -> real; nRepeats: integer := 5; seed: integer := 0): Vector; + scoreFunc: (Vector, Vector) -> real; nRepeats: integer := 5; seed: integer := -1): Vector; end; implementation @@ -65,7 +65,7 @@ static function Inspection.PermutationImportance( y: Vector; scoreFunc: (Vector, Vector) -> real; nRepeats: integer; - seed: integer + seed: integer ): Vector; begin if model = nil then @@ -88,6 +88,15 @@ begin var resultVec := new Vector(p); +// Базовый seed для всех параметров. +// Все модели оцениваются на одинаковых фолдах, +// что обеспечивает корректное сравнение. +// При seed = -1 разбиение случайное, но фиксируется +// один раз для всего GridSearch. + var baseSeed := + if seed >= 0 then seed + else System.Environment.TickCount and integer.MaxValue; + for var j := 0 to p - 1 do begin var acc := 0.0; @@ -96,14 +105,12 @@ begin begin var Xperm := X.Clone; - // --- seed для каждого (признак, повтор) - var runSeed := - if seed >= 0 then seed + j * 100000 + r - else System.Environment.TickCount and integer.MaxValue; + // --- детерминированный seed для (j, r) + var runSeed := baseSeed + j * 100000 + r; var rnd := new System.Random(runSeed); - // --- shuffle столбца j + // --- shuffle столбца j (Fisher–Yates) for var i := n - 1 downto 1 do begin var k := rnd.Next(i + 1); diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index 671150fd9..f83135189 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -17,6 +17,7 @@ uses InspectionML; uses MLPipelineABC; uses MLDatasets; uses DataAdapters; +uses MLUtilsABC; type Vector = LinearAlgebraML.Vector; @@ -121,7 +122,7 @@ implementation function LabelsToInts(y: Vector): array of integer; begin - Result := DataAdapters.LabelsToInts(y); + Result := MLUtilsABC.LabelsToInts(y); end; function EncodeLabels(labels: array of string): array of integer := DataAdapters.EncodeLabels(labels); diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index 5bb7cb199..1000b5f6e 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -416,7 +416,8 @@ type /// • maxDepth — максимальная глубина дерева. /// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesLeaf — минимальное число объектов в листе - constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1; seed: integer := -1); + constructor Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer; + criterion: ISplitCriterion; seed: integer); /// Возвращает вектор важности признаков. /// Важность вычисляется как суммарное уменьшение @@ -474,7 +475,8 @@ type /// • maxDepth — максимальная глубина дерева (-1 означает без ограничения). /// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesLeaf — минимальное число объектов в листе - constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1; seed: integer := -1); + constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1; + criterion: ISplitCriterion := nil; seed: integer := -1); /// Обучает классификационное дерево. /// X — матрица признаков. @@ -903,8 +905,11 @@ type seed: integer := -1 ); -/// Обучает модель на всей обучающей выборке. -/// Если включен early stopping и subsample < 1, может использоваться OOB loss. +/// Обучает модель градиентного бустинга на обучающей выборке. +/// Обучение выполняется без early stopping. +/// Для предотвращения переобучения используйте FitWithValidation. +/// Early stopping работает только при наличии валидационной выборки +/// и параметре earlyStoppingPatience > 0. /// Возвращает обученную модель. function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Предсказывает значения целевой переменной. @@ -914,10 +919,12 @@ type /// Копируются все деревья и внутреннее состояние. function Clone: IModel; -/// Обучает модель с использованием отдельной validation-выборки. -/// Early stopping (если включен) происходит по validation loss. - function FitWithValidation(XTrain: Matrix; yTrain: Vector; - XVal: Matrix; yVal: Vector): IModel; +/// Обучает модель градиентного бустинга с использованием валидационной выборки. +/// Поддерживает early stopping: +/// обучение останавливается, если метрика не улучшается +/// в течение earlyStoppingPatience итераций. +/// Возвращает обученную модель. + function FitWithValidation(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector): ISupervisedModel; /// История значения функции потерь на обучающей выборке. /// Один элемент на итерацию бустинга. @@ -1043,14 +1050,19 @@ type earlyStoppingPatience: integer := 20; seed: integer := -1); -/// Обучает классификатор на всей обучающей выборке. -/// Если включен early stopping и subsample < 1, то может использоваться OOB loss. +/// Обучает модель градиентного бустинга на обучающей выборке. +/// Обучение выполняется без early stopping. +/// Для предотвращения переобучения используйте FitWithValidation. +/// Early stopping работает только при наличии валидационной выборки +/// и параметре earlyStoppingPatience > 0. /// Возвращает обученную модель. function Fit(X: Matrix; y: Vector): ISupervisedModel; -/// Обучает классификатор с использованием validation-набора. -/// Если включен early stopping, он основан на validation loss. - function FitWithValidation(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector): IModel; +/// Обучает модель градиентного бустинга с использованием валидационной выборки. +/// Поддерживает early stopping: +/// обучение останавливается, если метрика не улучшается в течение earlyStoppingPatience итераций. +/// Возвращает обученную модель. + function FitWithValidation(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector): ISupervisedModel; /// Предсказывает метки классов. /// Возвращает исходные значения классов, а не внутренние индексы. @@ -1799,11 +1811,7 @@ type {$region Utility functions} -/// Преобразует вектор меток классов в массив целых. -/// Предполагается, что значения y являются целыми -/// (0,1,2,...) и могут содержать небольшие -/// численные ошибки, поэтому используется Round. -function LabelsToInts(y: Vector): array of integer; + {$endregion Utility functions} @@ -2993,6 +3001,7 @@ constructor DecisionTreeBase.Create( maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer; + criterion: ISplitCriterion; seed: integer); begin if maxDepth < -1 then @@ -3018,6 +3027,7 @@ begin fMaxDepth := maxDepth; fMinSamplesSplit := minSamplesSplit; fMinSamplesLeaf := minSamplesLeaf; + fCriterion := criterion; if seed < 0 then begin @@ -3488,9 +3498,12 @@ begin Result := bestClass; end; -constructor DecisionTreeClassifier.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer; seed: integer); +constructor DecisionTreeClassifier.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer; + criterion: ISplitCriterion; seed: integer); begin - inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf, seed); + inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf, + (if criterion = nil then new GiniCriterion else criterion), + seed); end; function DecisionTreeClassifier.Fit(X: Matrix; y: Vector): ISupervisedModel; @@ -3619,6 +3632,7 @@ begin fMaxDepth, fMinSamplesSplit, fMinSamplesLeaf, + fCriterion, fRandomSeed ); @@ -3674,15 +3688,25 @@ end; // DecisionTreeRegressor -constructor DecisionTreeRegressor.Create(maxDepth: integer; minSamplesSplit: integer; - minSamplesLeaf: integer; leafL2: real; seed: integer); +constructor DecisionTreeRegressor.Create( + maxDepth: integer; + minSamplesSplit: integer; + minSamplesLeaf: integer; + leafL2: real; + seed: integer +); begin - inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf, seed); - + inherited Create( + maxDepth, + minSamplesSplit, + minSamplesLeaf, + new VarianceCriterion, + seed + ); + if leafL2 < 0 then ArgumentOutOfRangeError(ER_LEAFL2_INVALID, leafL2); - - fCriterion := new VarianceCriterion; + fLeafL2 := leafL2; end; @@ -4224,6 +4248,7 @@ begin fMaxDepth, fMinSamplesSplit, fMinSamplesLeaf, + nil, treeSeed ); @@ -4858,7 +4883,7 @@ end; function GradientBoostingRegressor.FitWithValidation( XTrain: Matrix; yTrain: Vector; - XVal: Matrix; yVal: Vector): IModel; + XVal: Matrix; yVal: Vector): ISupervisedModel; begin Result := FitInternal(XTrain, yTrain, XVal, yVal, true); end; @@ -6253,7 +6278,7 @@ end; function GradientBoostingClassifier.FitWithValidation( XTrain: Matrix; yTrain: Vector; - XVal: Matrix; yVal: Vector): IModel; + XVal: Matrix; yVal: Vector): ISupervisedModel; begin Result := FitInternal(XTrain, yTrain, XVal, yVal, true); end; @@ -8558,10 +8583,5 @@ begin Result := t; end; -function LabelsToInts(y: Vector): array of integer; -begin - Result := ArrGen(y.Length, i -> Round(y[i])); -end; - end. \ No newline at end of file diff --git a/bin/Lib/MLUtilsABC.pas b/bin/Lib/MLUtilsABC.pas new file mode 100644 index 000000000..1c241ce69 --- /dev/null +++ b/bin/Lib/MLUtilsABC.pas @@ -0,0 +1,50 @@ +/// Вспомогательные функции для ML. +/// +/// Содержит утилиты, используемые в нескольких модулях, +/// не привязанные к DataFrame или конкретным моделям. +unit MLUtilsABC; + +interface + +uses LinearAlgebraML; + +/// Преобразует вектор меток классов в массив целых чисел. +/// Используется при визуализации и других задачах, +/// где метки должны быть представлены как 0,1,2,... +/// Значения округляются функцией Round, чтобы устранить +/// возможные небольшие численные ошибки +function LabelsToInts(y: Vector): array of integer; + +/// Преобразует массив целых меток в Vector. +function IntsToLabels(a: array of integer): Vector; + +implementation + +uses MLExceptions; + +const + ER_LABELS_NULL = + 'y не может быть nil!!y cannot be nil'; + ER_LABELS_ARRAY_NULL = + 'labels не может быть nil!!labels cannot be nil'; + +function LabelsToInts(y: Vector): array of integer; +begin + if y = nil then + ArgumentNullError(ER_LABELS_NULL, 'y'); + + Result := new integer[y.Length]; + + for var i := 0 to y.Length - 1 do + Result[i] := Round(y[i]); +end; + +function IntsToLabels(a: array of integer): Vector; +begin + if a = nil then + ArgumentNullError(ER_LABELS_ARRAY_NULL, 'a'); + + Result := new Vector(a); +end; + +end. \ No newline at end of file diff --git a/bin/Lib/MetricsABC.pas b/bin/Lib/MetricsABC.pas index aa5d9e87d..28df836d8 100644 --- a/bin/Lib/MetricsABC.pas +++ b/bin/Lib/MetricsABC.pas @@ -34,6 +34,17 @@ type /// Менее чувствительна к выбросам, чем MSE. static function MAE(yTrue, yPred: Vector): real; + /// Средняя абсолютная процентная ошибка (MAPE). + /// MAPE = (1/n) * Σ |(yTrue - yPred) / yTrue|. + /// Значения yTrue = 0 игнорируются. + /// Удобна для интерпретации в процентах. + static function MAPE(yTrue, yPred: Vector): real; + + /// Медианная абсолютная ошибка (MedianAE). + /// MedianAE = median(|yTrue - yPred|). + /// Устойчива к выбросам. + static function MedianAE(yTrue, yPred: Vector): real; + /// Коэффициент детерминации (R²). /// R2 = 1 - SS_res / SS_tot. /// Показывает долю объяснённой дисперсии. @@ -312,6 +323,17 @@ type /// Чем меньше значение, тем лучше модель static function MAE(yTrue, yPred: Vector): real := Metrics.MAE(yTrue, yPred); + /// Средняя абсолютная процентная ошибка (MAPE). + /// MAPE = (1/n) * Σ |(yTrue - yPred) / yTrue|. + /// Значения yTrue = 0 игнорируются. + /// Удобна для интерпретации в процентах. + static function MAPE(yTrue, yPred: Vector): real := Metrics.MAPE(yTrue, yPred); + + /// Медианная абсолютная ошибка (MedianAE). + /// MedianAE = median(|yTrue - yPred|). + /// Устойчива к выбросам. + static function MedianAE(yTrue, yPred: Vector): real := Metrics.MedianAE(yTrue, yPred); + /// Коэффициент детерминации (R²) /// R² = 1 - SS_res / SS_tot /// Показывает долю дисперсии, объяснённую моделью @@ -398,6 +420,10 @@ const 'Для Calinski–Harabasz требуется n > k!!Calinski–Harabasz requires n > k'; ER_LABELS_MUST_BE_INTEGERS = 'Метки кластеров должны быть целыми числами!!Cluster labels must be integers'; + ER_MAPE_ALL_ZERO_TARGET = + 'Все значения целевой переменной равны нулю — MAPE не определена!!All target values are zero — MAPE is undefined'; + + //----------------------------- // Metrics //----------------------------- @@ -478,6 +504,66 @@ begin Result := s / n; end; +static function Metrics.MAPE(yTrue, yPred: Vector): real; +begin + if yTrue = nil then + ArgumentNullError(ER_ARG_NULL, 'yTrue'); + + if yPred = nil then + ArgumentNullError(ER_ARG_NULL, 'yPred'); + + if yTrue.Length <> yPred.Length then + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length); + + var sum := 0.0; + var cnt := 0; + + for var i := 0 to yTrue.Length - 1 do + begin + var y := yTrue[i]; + var yp := yPred[i]; + + if y = 0.0 then continue; + + sum += Abs((y - yp) / y); + cnt += 1; + end; + + if cnt = 0 then + Error(ER_MAPE_ALL_ZERO_TARGET); + + Result := sum / cnt; +end; + +static function Metrics.MedianAE(yTrue, yPred: Vector): real; +begin + if yTrue = nil then + ArgumentNullError(ER_ARG_NULL, 'yTrue'); + + if yPred = nil then + ArgumentNullError(ER_ARG_NULL, 'yPred'); + + if yTrue.Length <> yPred.Length then + DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length); + + var n := yTrue.Length; + if n = 0 then + ArgumentError(ER_EMPTY_DATA, 'MedianAbsoluteError'); + + var errors := new real[n]; + + for var i := 0 to n - 1 do + errors[i] := Abs(yTrue[i] - yPred[i]); + + // сортировка + &Array.Sort(errors); + + if n mod 2 = 1 then + Result := errors[n div 2] + else + Result := (errors[n div 2 - 1] + errors[n div 2]) / 2.0; +end; + static function Metrics.R2(yTrue, yPred: Vector): real; begin if yTrue = nil then diff --git a/bin/Lib/ValidationML.pas b/bin/Lib/ValidationML.pas index 1d96ccbe7..9ee913f85 100644 --- a/bin/Lib/ValidationML.pas +++ b/bin/Lib/ValidationML.pas @@ -6,6 +6,11 @@ uses LinearAlgebraML, MLCoreABC; type Validation = static class + private + static function CrossValidateCore(model: ISupervisedModel; + X: Matrix; y: Vector; + folds: sequence of (array of integer, array of integer); + metric: (Vector, Vector) -> real): real; public /// Делит данные на обучающую и тестовую выборки. /// testRatio — доля объектов, попадающих в тестовую выборку (по умолчанию 0.2). @@ -74,6 +79,7 @@ type k: integer; metric: (Vector, Vector) -> real; maximize: boolean := True; + stratified: boolean := False; seed: integer := -1 ): (P, real, T); where T: class, ISupervisedModel; end; @@ -108,6 +114,57 @@ const // Validation //----------------------------- +static function Validation.CrossValidateCore( + model: ISupervisedModel; + X: Matrix; + y: Vector; + folds: sequence of (array of integer, array of integer); + metric: (Vector, Vector) -> real +): real; +begin + var total := 0.0; + var foldsCount := 0; + var p := X.ColCount; + + foreach var (trainIdx, testIdx) in folds do + begin + var Xtr := new Matrix(trainIdx.Length, p); + var ytr := new Vector(trainIdx.Length); + + for var i := 0 to trainIdx.Length - 1 do + begin + var r := trainIdx[i]; + for var j := 0 to p - 1 do + Xtr[i,j] := X[r,j]; + ytr[i] := y[r]; + end; + + var Xte := new Matrix(testIdx.Length, p); + var yte := new Vector(testIdx.Length); + + for var i := 0 to testIdx.Length - 1 do + begin + var r := testIdx[i]; + for var j := 0 to p - 1 do + Xte[i,j] := X[r,j]; + yte[i] := y[r]; + end; + + var m := model.Clone() as ISupervisedModel; + m := m.Fit(Xtr, ytr); + + var pred := m.Predict(Xte); + + total += metric(yte, pred); + foldsCount += 1; + end; + + if foldsCount = 0 then + ArgumentError(ER_EMPTY_DATA, 'CrossValidate'); + + Result := total / foldsCount; +end; + static function Validation.TrainTestSplit(X: Matrix; y: Vector; testRatio: real; seed: integer): (Matrix, Matrix, Vector, Vector); begin @@ -328,51 +385,17 @@ begin if (k < 2) or (k > X.RowCount) then ArgumentError(ER_K_INVALID, k, X.RowCount); - var total := 0.0; - var folds := 0; - var p := X.ColCount; - var baseSeed := if seed >= 0 then seed else System.Environment.TickCount and integer.MaxValue; - foreach var (trainIdx, testIdx) in KFold(X.RowCount, k, baseSeed) do - begin - var Xtr := new Matrix(trainIdx.Length, p); - var ytr := new Vector(trainIdx.Length); - - for var i := 0 to trainIdx.Length - 1 do - begin - var r := trainIdx[i]; - for var j := 0 to p - 1 do - Xtr[i,j] := X[r,j]; - ytr[i] := y[r]; - end; - - var Xte := new Matrix(testIdx.Length, p); - var yte := new Vector(testIdx.Length); - - for var i := 0 to testIdx.Length - 1 do - begin - var r := testIdx[i]; - for var j := 0 to p - 1 do - Xte[i,j] := X[r,j]; - yte[i] := y[r]; - end; - - var m := model.Clone() as ISupervisedModel; - m := m.Fit(Xtr, ytr); - - var pred := m.Predict(Xte); - - total += metric(yte, pred); - folds += 1; - end; - - if folds = 0 then - ArgumentError(ER_EMPTY_DATA, 'CrossValidate'); - - Result := total / folds; + Result := CrossValidateCore( + model, + X, + y, + KFold(X.RowCount, k, baseSeed), + metric + ); end; static function Validation.StratifiedCrossValidate( @@ -401,51 +424,17 @@ begin if (k < 2) or (k > X.RowCount) then ArgumentError(ER_K_INVALID_STRATIFIED, k, X.RowCount); - var total := 0.0; - var folds := 0; - var p := X.ColCount; - var baseSeed := if seed >= 0 then seed else System.Environment.TickCount and integer.MaxValue; - foreach var (trainIdx, testIdx) in StratifiedKFold(y, k, baseSeed) do - begin - var Xtr := new Matrix(trainIdx.Length, p); - var ytr := new Vector(trainIdx.Length); - - for var i := 0 to trainIdx.Length - 1 do - begin - var r := trainIdx[i]; - for var j := 0 to p - 1 do - Xtr[i,j] := X[r,j]; - ytr[i] := y[r]; - end; - - var Xte := new Matrix(testIdx.Length, p); - var yte := new Vector(testIdx.Length); - - for var i := 0 to testIdx.Length - 1 do - begin - var r := testIdx[i]; - for var j := 0 to p - 1 do - Xte[i,j] := X[r,j]; - yte[i] := y[r]; - end; - - var m := model.Clone() as ISupervisedModel; - m := m.Fit(Xtr, ytr); - - var pred := m.Predict(Xte); - - total += metric(yte, pred); - folds += 1; - end; - - if folds = 0 then - ArgumentError(ER_EMPTY_DATA, 'StratifiedCrossValidate'); - - Result := total / folds; + Result := CrossValidateCore( + model, + X, + y, + StratifiedKFold(y, k, baseSeed), + metric + ); end; //----------------------------- @@ -460,6 +449,7 @@ class function GridSearch.Search( k: integer; metric: (Vector, Vector) -> real; maximize: boolean; + stratified: boolean; seed: integer ): (P, real, T); where T: class, ISupervisedModel; begin @@ -498,7 +488,11 @@ begin if model = nil then ArgumentError(ER_MODEL_NULL); - var avgScore := Validation.CrossValidate(model, X, y, k, metric, baseSeed); + var avgScore := + if stratified then + Validation.StratifiedCrossValidate(model, X, y, k, metric, baseSeed) + else + Validation.CrossValidate(model, X, y, k, metric, baseSeed); if double.IsNaN(avgScore) or double.IsInfinity(avgScore) then ArgumentError(ER_INVALID_VALUE, 'avgScore');