MLUtilsABC.pas

MAPE MedianAE
Правки в ML
This commit is contained in:
Mikhalkovich Stanislav 2026-04-06 23:04:46 +03:00
parent c69daac622
commit d41a519d53
14 changed files with 292 additions and 145 deletions

View file

@ -15,7 +15,7 @@ internal static class RevisionClass
public const string Major = "3"; public const string Major = "3";
public const string Minor = "11"; public const string Minor = "11";
public const string Build = "1"; public const string Build = "1";
public const string Revision = "3793"; public const string Revision = "3799";
public const string MainVersion = Major + "." + Minor; public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision; public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;

View file

@ -1,4 +1,4 @@
%MINOR%=11 %MINOR%=11
%REVISION%=3793 %REVISION%=3799
%COREVERSION%=1 %COREVERSION%=1
%MAJOR%=3 %MAJOR%=3

View file

@ -1 +1 @@
3.11.1.3793 3.11.1.3799

View file

@ -1 +1 @@
!define VERSION '3.11.1.3793' !define VERSION '3.11.1.3799'

View file

@ -22,7 +22,7 @@ uses
WPF, WPF,
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC, DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC,
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC, MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC,
PlotML, MLDatasets, DataAdapters PlotML, MLDatasets, DataAdapters, MLUtilsABC
; ;
begin begin

View file

@ -12,7 +12,7 @@
School, SF, TurtleABC, School, SF, TurtleABC,
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC, DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC,
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC, MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC,
PlotML, MLDatasets, DataAdapters MLDatasets, DataAdapters, MLUtilsABC
; ;
begin begin

View file

@ -194,6 +194,7 @@
File ..\bin\Lib\PlotML.pcu File ..\bin\Lib\PlotML.pcu
File ..\bin\Lib\MLDatasets.pcu File ..\bin\Lib\MLDatasets.pcu
File ..\bin\Lib\DataAdapters.pcu File ..\bin\Lib\DataAdapters.pcu
File ..\bin\Lib\MLUtilsABC.pcu
File ..\bin\Lib\PABCRtl.dll File ..\bin\Lib\PABCRtl.dll
@ -303,6 +304,7 @@
${AddFile} "PlotML.pcu" ${AddFile} "PlotML.pcu"
${AddFile} "MLDatasets.pcu" ${AddFile} "MLDatasets.pcu"
${AddFile} "DataAdapters.pcu" ${AddFile} "DataAdapters.pcu"
${AddFile} "MLUtilsABC.pcu"
${AddFile} "turtle.png" ${AddFile} "turtle.png"
@ -448,6 +450,7 @@
File ..\bin\Lib\PlotML.pas File ..\bin\Lib\PlotML.pas
File ..\bin\Lib\MLDatasets.pas File ..\bin\Lib\MLDatasets.pas
File ..\bin\Lib\DataAdapters.pas File ..\bin\Lib\DataAdapters.pas
File ..\bin\Lib\MLUtilsABC.pas
File ..\bin\Lib\__RedirectIOMode.vb File ..\bin\Lib\__RedirectIOMode.vb
@ -544,6 +547,7 @@
${AddFile} "PlotML.pas" ${AddFile} "PlotML.pas"
${AddFile} "MLDatasets.pas" ${AddFile} "MLDatasets.pas"
${AddFile} "DataAdapters.pas" ${AddFile} "DataAdapters.pas"
${AddFile} "MLUtilsABC.pas"
${AddFile} "__RedirectIOMode.vb" ${AddFile} "__RedirectIOMode.vb"

View file

@ -5,13 +5,6 @@ interface
uses DataFrameABC; uses DataFrameABC;
uses LinearAlgebraML; uses LinearAlgebraML;
/// Преобразует вектор меток классов в массив целых чисел.
/// Используется при визуализации и других задачах,
/// где метки должны быть представлены как 0,1,2,...
/// Значения округляются функцией Round, чтобы устранить
/// возможные небольшие численные ошибки
function LabelsToInts(y: Vector): array of integer;
/// Кодирует строковые метки классов в целочисленные индексы. /// Кодирует строковые метки классов в целочисленные индексы.
/// Каждому уникальному значению присваивается номер 0,1,2,... /// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления меток. /// Порядок кодирования соответствует порядку первого появления меток.
@ -65,14 +58,6 @@ const
ER_LABEL_INDEX_OUT_OF_RANGE = ER_LABEL_INDEX_OUT_OF_RANGE =
'Индекс метки {0} вне диапазона [0, {1})!!Label index {0} is out of range [0, {1})'; 'Индекс метки {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; function EncodeLabels(labels: array of string; var classes: array of string): array of integer;
begin begin
if labels = nil then if labels = nil then

View file

@ -45,7 +45,7 @@ type
/// Вектор важностей признаков длины nFeatures. /// Вектор важностей признаков длины nFeatures.
/// Чем больше значение, тем сильнее признак влияет на качество модели /// Чем больше значение, тем сильнее признак влияет на качество модели
static function PermutationImportance(model: IPredictiveModel; X: Matrix; y: Vector; 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; end;
implementation implementation
@ -65,7 +65,7 @@ static function Inspection.PermutationImportance(
y: Vector; y: Vector;
scoreFunc: (Vector, Vector) -> real; scoreFunc: (Vector, Vector) -> real;
nRepeats: integer; nRepeats: integer;
seed: integer seed: integer
): Vector; ): Vector;
begin begin
if model = nil then if model = nil then
@ -88,6 +88,15 @@ begin
var resultVec := new Vector(p); 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 for var j := 0 to p - 1 do
begin begin
var acc := 0.0; var acc := 0.0;
@ -96,14 +105,12 @@ begin
begin begin
var Xperm := X.Clone; var Xperm := X.Clone;
// --- seed для каждого (признак, повтор) // --- детерминированный seed для (j, r)
var runSeed := var runSeed := baseSeed + j * 100000 + r;
if seed >= 0 then seed + j * 100000 + r
else System.Environment.TickCount and integer.MaxValue;
var rnd := new System.Random(runSeed); var rnd := new System.Random(runSeed);
// --- shuffle столбца j // --- shuffle столбца j (Fisher–Yates)
for var i := n - 1 downto 1 do for var i := n - 1 downto 1 do
begin begin
var k := rnd.Next(i + 1); var k := rnd.Next(i + 1);

View file

@ -17,6 +17,7 @@ uses InspectionML;
uses MLPipelineABC; uses MLPipelineABC;
uses MLDatasets; uses MLDatasets;
uses DataAdapters; uses DataAdapters;
uses MLUtilsABC;
type type
Vector = LinearAlgebraML.Vector; Vector = LinearAlgebraML.Vector;
@ -121,7 +122,7 @@ implementation
function LabelsToInts(y: Vector): array of integer; function LabelsToInts(y: Vector): array of integer;
begin begin
Result := DataAdapters.LabelsToInts(y); Result := MLUtilsABC.LabelsToInts(y);
end; end;
function EncodeLabels(labels: array of string): array of integer := DataAdapters.EncodeLabels(labels); function EncodeLabels(labels: array of string): array of integer := DataAdapters.EncodeLabels(labels);

View file

@ -416,7 +416,8 @@ type
/// • maxDepth — максимальная глубина дерева. /// • maxDepth — максимальная глубина дерева.
/// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesSplit — минимальное число объектов для разбиения узла.
/// • minSamplesLeaf — минимальное число объектов в листе /// • 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 означает без ограничения). /// • maxDepth — максимальная глубина дерева (-1 означает без ограничения).
/// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesSplit — минимальное число объектов для разбиения узла.
/// • minSamplesLeaf — минимальное число объектов в листе /// • 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 — матрица признаков. /// X — матрица признаков.
@ -903,8 +905,11 @@ type
seed: integer := -1 seed: integer := -1
); );
/// Обучает модель на всей обучающей выборке. /// Обучает модель градиентного бустинга на обучающей выборке.
/// Если включен early stopping и subsample < 1, может использоваться OOB loss. /// Обучение выполняется без early stopping.
/// Для предотвращения переобучения используйте FitWithValidation.
/// Early stopping работает только при наличии валидационной выборки
/// и параметре earlyStoppingPatience > 0.
/// Возвращает обученную модель. /// Возвращает обученную модель.
function Fit(X: Matrix; y: Vector): ISupervisedModel; function Fit(X: Matrix; y: Vector): ISupervisedModel;
/// Предсказывает значения целевой переменной. /// Предсказывает значения целевой переменной.
@ -914,10 +919,12 @@ type
/// Копируются все деревья и внутреннее состояние. /// Копируются все деревья и внутреннее состояние.
function Clone: IModel; function Clone: IModel;
/// Обучает модель с использованием отдельной validation-выборки. /// Обучает модель градиентного бустинга с использованием валидационной выборки.
/// Early stopping (если включен) происходит по validation loss. /// Поддерживает early stopping:
function FitWithValidation(XTrain: Matrix; yTrain: Vector; /// обучение останавливается, если метрика не улучшается
XVal: Matrix; yVal: Vector): IModel; /// в течение earlyStoppingPatience итераций.
/// Возвращает обученную модель.
function FitWithValidation(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector): ISupervisedModel;
/// История значения функции потерь на обучающей выборке. /// История значения функции потерь на обучающей выборке.
/// Один элемент на итерацию бустинга. /// Один элемент на итерацию бустинга.
@ -1043,14 +1050,19 @@ type
earlyStoppingPatience: integer := 20; earlyStoppingPatience: integer := 20;
seed: integer := -1); seed: integer := -1);
/// Обучает классификатор на всей обучающей выборке. /// Обучает модель градиентного бустинга на обучающей выборке.
/// Если включен early stopping и subsample < 1, то может использоваться OOB loss. /// Обучение выполняется без early stopping.
/// Для предотвращения переобучения используйте FitWithValidation.
/// Early stopping работает только при наличии валидационной выборки
/// и параметре earlyStoppingPatience > 0.
/// Возвращает обученную модель. /// Возвращает обученную модель.
function Fit(X: Matrix; y: Vector): ISupervisedModel; function Fit(X: Matrix; y: Vector): ISupervisedModel;
/// Обучает классификатор с использованием validation-набора. /// Обучает модель градиентного бустинга с использованием валидационной выборки.
/// Если включен early stopping, он основан на validation loss. /// Поддерживает early stopping:
function FitWithValidation(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector): IModel; /// обучение останавливается, если метрика не улучшается в течение earlyStoppingPatience итераций.
/// Возвращает обученную модель.
function FitWithValidation(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector): ISupervisedModel;
/// Предсказывает метки классов. /// Предсказывает метки классов.
/// Возвращает исходные значения классов, а не внутренние индексы. /// Возвращает исходные значения классов, а не внутренние индексы.
@ -1799,11 +1811,7 @@ type
{$region Utility functions} {$region Utility functions}
/// Преобразует вектор меток классов в массив целых.
/// Предполагается, что значения y являются целыми
/// (0,1,2,...) и могут содержать небольшие
/// численные ошибки, поэтому используется Round.
function LabelsToInts(y: Vector): array of integer;
{$endregion Utility functions} {$endregion Utility functions}
@ -2993,6 +3001,7 @@ constructor DecisionTreeBase.Create(
maxDepth: integer; maxDepth: integer;
minSamplesSplit: integer; minSamplesSplit: integer;
minSamplesLeaf: integer; minSamplesLeaf: integer;
criterion: ISplitCriterion;
seed: integer); seed: integer);
begin begin
if maxDepth < -1 then if maxDepth < -1 then
@ -3018,6 +3027,7 @@ begin
fMaxDepth := maxDepth; fMaxDepth := maxDepth;
fMinSamplesSplit := minSamplesSplit; fMinSamplesSplit := minSamplesSplit;
fMinSamplesLeaf := minSamplesLeaf; fMinSamplesLeaf := minSamplesLeaf;
fCriterion := criterion;
if seed < 0 then if seed < 0 then
begin begin
@ -3488,9 +3498,12 @@ begin
Result := bestClass; Result := bestClass;
end; 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 begin
inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf, seed); inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf,
(if criterion = nil then new GiniCriterion else criterion),
seed);
end; end;
function DecisionTreeClassifier.Fit(X: Matrix; y: Vector): ISupervisedModel; function DecisionTreeClassifier.Fit(X: Matrix; y: Vector): ISupervisedModel;
@ -3619,6 +3632,7 @@ begin
fMaxDepth, fMaxDepth,
fMinSamplesSplit, fMinSamplesSplit,
fMinSamplesLeaf, fMinSamplesLeaf,
fCriterion,
fRandomSeed fRandomSeed
); );
@ -3674,15 +3688,25 @@ end;
// DecisionTreeRegressor // DecisionTreeRegressor
constructor DecisionTreeRegressor.Create(maxDepth: integer; minSamplesSplit: integer; constructor DecisionTreeRegressor.Create(
minSamplesLeaf: integer; leafL2: real; seed: integer); maxDepth: integer;
minSamplesSplit: integer;
minSamplesLeaf: integer;
leafL2: real;
seed: integer
);
begin begin
inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf, seed); inherited Create(
maxDepth,
minSamplesSplit,
minSamplesLeaf,
new VarianceCriterion,
seed
);
if leafL2 < 0 then if leafL2 < 0 then
ArgumentOutOfRangeError(ER_LEAFL2_INVALID, leafL2); ArgumentOutOfRangeError(ER_LEAFL2_INVALID, leafL2);
fCriterion := new VarianceCriterion;
fLeafL2 := leafL2; fLeafL2 := leafL2;
end; end;
@ -4224,6 +4248,7 @@ begin
fMaxDepth, fMaxDepth,
fMinSamplesSplit, fMinSamplesSplit,
fMinSamplesLeaf, fMinSamplesLeaf,
nil,
treeSeed treeSeed
); );
@ -4858,7 +4883,7 @@ end;
function GradientBoostingRegressor.FitWithValidation( function GradientBoostingRegressor.FitWithValidation(
XTrain: Matrix; yTrain: Vector; XTrain: Matrix; yTrain: Vector;
XVal: Matrix; yVal: Vector): IModel; XVal: Matrix; yVal: Vector): ISupervisedModel;
begin begin
Result := FitInternal(XTrain, yTrain, XVal, yVal, true); Result := FitInternal(XTrain, yTrain, XVal, yVal, true);
end; end;
@ -6253,7 +6278,7 @@ end;
function GradientBoostingClassifier.FitWithValidation( function GradientBoostingClassifier.FitWithValidation(
XTrain: Matrix; yTrain: Vector; XTrain: Matrix; yTrain: Vector;
XVal: Matrix; yVal: Vector): IModel; XVal: Matrix; yVal: Vector): ISupervisedModel;
begin begin
Result := FitInternal(XTrain, yTrain, XVal, yVal, true); Result := FitInternal(XTrain, yTrain, XVal, yVal, true);
end; end;
@ -8558,10 +8583,5 @@ begin
Result := t; Result := t;
end; end;
function LabelsToInts(y: Vector): array of integer;
begin
Result := ArrGen(y.Length, i -> Round(y[i]));
end;
end. end.

50
bin/Lib/MLUtilsABC.pas Normal file
View file

@ -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.

View file

@ -34,6 +34,17 @@ type
/// Менее чувствительна к выбросам, чем MSE. /// Менее чувствительна к выбросам, чем MSE.
static function MAE(yTrue, yPred: Vector): real; 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²). /// Коэффициент детерминации (R²).
/// R2 = 1 - SS_res / SS_tot. /// R2 = 1 - SS_res / SS_tot.
/// Показывает долю объяснённой дисперсии. /// Показывает долю объяснённой дисперсии.
@ -312,6 +323,17 @@ type
/// Чем меньше значение, тем лучше модель /// Чем меньше значение, тем лучше модель
static function MAE(yTrue, yPred: Vector): real := Metrics.MAE(yTrue, yPred); 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²)
/// R² = 1 - SS_res / SS_tot /// R² = 1 - SS_res / SS_tot
/// Показывает долю дисперсии, объяснённую моделью /// Показывает долю дисперсии, объяснённую моделью
@ -398,6 +420,10 @@ const
'Для Calinski–Harabasz требуется n > k!!Calinski–Harabasz requires n > k'; 'Для Calinski–Harabasz требуется n > k!!Calinski–Harabasz requires n > k';
ER_LABELS_MUST_BE_INTEGERS = ER_LABELS_MUST_BE_INTEGERS =
'Метки кластеров должны быть целыми числами!!Cluster labels must be integers'; 'Метки кластеров должны быть целыми числами!!Cluster labels must be integers';
ER_MAPE_ALL_ZERO_TARGET =
'Все значения целевой переменной равны нулю — MAPE не определена!!All target values are zero — MAPE is undefined';
//----------------------------- //-----------------------------
// Metrics // Metrics
//----------------------------- //-----------------------------
@ -478,6 +504,66 @@ begin
Result := s / n; Result := s / n;
end; 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; static function Metrics.R2(yTrue, yPred: Vector): real;
begin begin
if yTrue = nil then if yTrue = nil then

View file

@ -6,6 +6,11 @@ uses LinearAlgebraML, MLCoreABC;
type type
Validation = static class 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 public
/// Делит данные на обучающую и тестовую выборки. /// Делит данные на обучающую и тестовую выборки.
/// testRatio — доля объектов, попадающих в тестовую выборку (по умолчанию 0.2). /// testRatio — доля объектов, попадающих в тестовую выборку (по умолчанию 0.2).
@ -74,6 +79,7 @@ type
k: integer; k: integer;
metric: (Vector, Vector) -> real; metric: (Vector, Vector) -> real;
maximize: boolean := True; maximize: boolean := True;
stratified: boolean := False;
seed: integer := -1 seed: integer := -1
): (P, real, T); where T: class, ISupervisedModel; ): (P, real, T); where T: class, ISupervisedModel;
end; end;
@ -108,6 +114,57 @@ const
// Validation // 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; static function Validation.TrainTestSplit(X: Matrix; y: Vector;
testRatio: real; seed: integer): (Matrix, Matrix, Vector, Vector); testRatio: real; seed: integer): (Matrix, Matrix, Vector, Vector);
begin begin
@ -328,51 +385,17 @@ begin
if (k < 2) or (k > X.RowCount) then if (k < 2) or (k > X.RowCount) then
ArgumentError(ER_K_INVALID, k, X.RowCount); ArgumentError(ER_K_INVALID, k, X.RowCount);
var total := 0.0;
var folds := 0;
var p := X.ColCount;
var baseSeed := var baseSeed :=
if seed >= 0 then seed if seed >= 0 then seed
else System.Environment.TickCount and integer.MaxValue; else System.Environment.TickCount and integer.MaxValue;
foreach var (trainIdx, testIdx) in KFold(X.RowCount, k, baseSeed) do Result := CrossValidateCore(
begin model,
var Xtr := new Matrix(trainIdx.Length, p); X,
var ytr := new Vector(trainIdx.Length); y,
KFold(X.RowCount, k, baseSeed),
for var i := 0 to trainIdx.Length - 1 do metric
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;
end; end;
static function Validation.StratifiedCrossValidate( static function Validation.StratifiedCrossValidate(
@ -401,51 +424,17 @@ begin
if (k < 2) or (k > X.RowCount) then if (k < 2) or (k > X.RowCount) then
ArgumentError(ER_K_INVALID_STRATIFIED, k, X.RowCount); ArgumentError(ER_K_INVALID_STRATIFIED, k, X.RowCount);
var total := 0.0;
var folds := 0;
var p := X.ColCount;
var baseSeed := var baseSeed :=
if seed >= 0 then seed if seed >= 0 then seed
else System.Environment.TickCount and integer.MaxValue; else System.Environment.TickCount and integer.MaxValue;
foreach var (trainIdx, testIdx) in StratifiedKFold(y, k, baseSeed) do Result := CrossValidateCore(
begin model,
var Xtr := new Matrix(trainIdx.Length, p); X,
var ytr := new Vector(trainIdx.Length); y,
StratifiedKFold(y, k, baseSeed),
for var i := 0 to trainIdx.Length - 1 do metric
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;
end; end;
//----------------------------- //-----------------------------
@ -460,6 +449,7 @@ class function GridSearch.Search<T, P>(
k: integer; k: integer;
metric: (Vector, Vector) -> real; metric: (Vector, Vector) -> real;
maximize: boolean; maximize: boolean;
stratified: boolean;
seed: integer seed: integer
): (P, real, T); where T: class, ISupervisedModel; ): (P, real, T); where T: class, ISupervisedModel;
begin begin
@ -498,7 +488,11 @@ begin
if model = nil then if model = nil then
ArgumentError(ER_MODEL_NULL); 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 if double.IsNaN(avgScore) or double.IsInfinity(avgScore) then
ArgumentError(ER_INVALID_VALUE, 'avgScore'); ArgumentError(ER_INVALID_VALUE, 'avgScore');