ML - Metrics, Validation - Production уровень
PABCSystem - добавлен huffle с параметром rnd RandomForest - добавление OOB MLModels - сообщения об ошибках и доведение до Production
This commit is contained in:
parent
03fa44dc8b
commit
d2fd6c21d6
|
|
@ -4,7 +4,7 @@ interface
|
|||
|
||||
const
|
||||
ER_DIM_MISMATCH =
|
||||
'Несоответствие размерностей: {0} и {1}!!Dimension mismatch: {0} and {1}';
|
||||
'Несоответствие размерностей: {0} и {1}!!Dimension mismatch: {0} and {1}';
|
||||
ER_PARAM_VALUES_EMPTY =
|
||||
'Список paramValues пуст!!paramValues is empty';
|
||||
ER_EMPTY_DATASET =
|
||||
|
|
@ -17,7 +17,11 @@ const
|
|||
'Модель не может быть nil!!Model cannot be nil';
|
||||
ER_SELECTKBEST_K_INVALID =
|
||||
'Параметр k в SelectKBest должен быть >= 1. Передано: {0}.!!SelectKBest parameter k must be >= 1. Given: {0}.';
|
||||
|
||||
ER_ARG_NULL =
|
||||
'Аргумент {0} равен nil!!Argument {0} is null';
|
||||
ER_EMPTY_DATA =
|
||||
'Пустой набор данных для {0}!!Empty dataset for {0}';
|
||||
|
||||
type
|
||||
/// Базовое исключение ML-библиотеки
|
||||
MLException = class(Exception);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -14,6 +14,12 @@ type
|
|||
/// Чувствительна к большим отклонениям.
|
||||
static function MSE(yTrue, yPred: Vector): real;
|
||||
|
||||
/// Вычисляет среднеквадратичную ошибку (Root Mean Squared Error, RMSE)
|
||||
/// между истинными и предсказанными значениями.
|
||||
/// RMSE равна квадратному корню из MSE и измеряется
|
||||
/// в тех же единицах, что и целевая переменная.
|
||||
static function RMSE(yTrue, yPred: Vector): real;
|
||||
|
||||
/// Средняя абсолютная ошибка (MAE).
|
||||
/// MAE = (1/n) * Σ |yTrue - yPred|.
|
||||
/// Менее чувствительна к выбросам, чем MSE.
|
||||
|
|
@ -69,10 +75,22 @@ type
|
|||
static function Recall(yTrue, yPred: Vector): real;
|
||||
|
||||
/// F1-мера.
|
||||
/// Объединённая метрика, учитывающая и Precision, и Recall одновременно.
|
||||
/// Высока только тогда, когда и Precision, и Recall высоки.
|
||||
/// Используется, когда классы сильно различаются по количеству элементов
|
||||
/// Объединённая метрика, учитывающая одновременно Precision и Recall.
|
||||
/// Высока только тогда, когда и Precision, и Recall имеют высокие значения.
|
||||
/// Особенно полезна при несбалансированных классах.
|
||||
static function F1(yTrue, yPred: Vector): real;
|
||||
|
||||
/// Specificity (True Negative Rate).
|
||||
/// Доля правильно предсказанных отрицательных объектов:
|
||||
/// TN / (TN + FP).
|
||||
/// Показывает способность модели корректно распознавать отрицательный класс.
|
||||
static function Specificity(yTrue, yPred: Vector): real;
|
||||
|
||||
/// Balanced Accuracy.
|
||||
/// Среднее арифметическое Recall (чувствительности)
|
||||
/// и Specificity (доли истинно отрицательных).
|
||||
/// Устойчива к дисбалансу классов.
|
||||
static function BalancedAccuracy(yTrue, yPred: Vector): real;
|
||||
end;
|
||||
|
||||
type
|
||||
|
|
@ -134,90 +152,196 @@ type
|
|||
/// Высока только тогда, когда и Precision, и Recall высоки.
|
||||
/// Используется, когда классы сильно различаются по количеству элементов
|
||||
function F1: real;
|
||||
|
||||
function Specificity: real;
|
||||
function BalancedAccuracy: real;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses MLExceptions;
|
||||
|
||||
const
|
||||
ER_INVALID_VALUE =
|
||||
'Некорректное значение {0} на позиции {1}!!Invalid value in {0} at index {1}';
|
||||
ER_DIV_BY_ZERO =
|
||||
'Деление на ноль в {0}!!Division by zero in {0}';
|
||||
ER_INVALID_CLASS_LABEL =
|
||||
'Некорректная метка класса: {0}!!Invalid class label: {0}';
|
||||
|
||||
//-----------------------------
|
||||
// Metrics
|
||||
//-----------------------------
|
||||
|
||||
static function Metrics.MSE(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
if yTrue.Length <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yPred = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yPred');
|
||||
|
||||
var n := yTrue.Length;
|
||||
var s := 0.0;
|
||||
|
||||
if n <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'MSE');
|
||||
|
||||
var sum := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var d := yTrue[i] - yPred[i];
|
||||
s += d * d;
|
||||
var yt := yTrue[i];
|
||||
var yp := yPred[i];
|
||||
|
||||
if double.IsNaN(yt) or double.IsInfinity(yt) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yTrue', i);
|
||||
|
||||
if double.IsNaN(yp) or double.IsInfinity(yp) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yPred', i);
|
||||
|
||||
var d := yt - yp;
|
||||
sum += d * d;
|
||||
end;
|
||||
|
||||
Result := s / n;
|
||||
Result := sum / n;
|
||||
end;
|
||||
|
||||
static function Metrics.RMSE(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
Result := Sqrt(MSE(yTrue, yPred));
|
||||
end;
|
||||
|
||||
static function Metrics.MAE(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
if yTrue.Length <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yPred = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yPred');
|
||||
|
||||
var n := yTrue.Length;
|
||||
|
||||
if n <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'MAE');
|
||||
|
||||
var s := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
s += Abs(yTrue[i] - yPred[i]);
|
||||
begin
|
||||
var yt := yTrue[i];
|
||||
var yp := yPred[i];
|
||||
|
||||
if double.IsNaN(yt) or double.IsInfinity(yt) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yTrue', i);
|
||||
|
||||
if double.IsNaN(yp) or double.IsInfinity(yp) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yPred', i);
|
||||
|
||||
s += Abs(yt - yp);
|
||||
end;
|
||||
|
||||
Result := s / n;
|
||||
end;
|
||||
|
||||
static function Metrics.R2(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
if yTrue.Length <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yPred = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yPred');
|
||||
|
||||
var n := yTrue.Length;
|
||||
var meanY := yTrue.Mean;
|
||||
|
||||
if n <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'R2');
|
||||
|
||||
var sumY := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var yt := yTrue[i];
|
||||
if double.IsNaN(yt) or double.IsInfinity(yt) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yTrue', i);
|
||||
|
||||
sumY += yt;
|
||||
end;
|
||||
|
||||
var meanY := sumY / n;
|
||||
|
||||
var ssRes := 0.0;
|
||||
var ssTot := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var d1 := yTrue[i] - yPred[i];
|
||||
var yt := yTrue[i];
|
||||
var yp := yPred[i];
|
||||
|
||||
if double.IsNaN(yp) or double.IsInfinity(yp) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yPred', i);
|
||||
|
||||
var d1 := yt - yp;
|
||||
ssRes += d1 * d1;
|
||||
|
||||
var d2 := yTrue[i] - meanY;
|
||||
var d2 := yt - meanY;
|
||||
ssTot += d2 * d2;
|
||||
end;
|
||||
|
||||
if ssTot = 0 then
|
||||
exit(0.0);
|
||||
if ssTot = 0.0 then
|
||||
begin
|
||||
Result := 0.0;
|
||||
exit;
|
||||
end;
|
||||
|
||||
Result := 1 - ssRes / ssTot;
|
||||
end;
|
||||
|
||||
static function Metrics.LogLoss(yTrue, yProb: Vector): real;
|
||||
begin
|
||||
if yTrue.Length <> yProb.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yProb.Length);
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yProb = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yProb');
|
||||
|
||||
var n := yTrue.Length;
|
||||
|
||||
if n <> yProb.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yProb.Length);
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'LogLoss');
|
||||
|
||||
var eps := 1e-15;
|
||||
var s := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var yt := yTrue[i];
|
||||
var p := yProb[i];
|
||||
|
||||
if p < eps then p := eps;
|
||||
if p > 1 - eps then p := 1 - eps;
|
||||
if double.IsNaN(yt) or double.IsInfinity(yt) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yTrue', i);
|
||||
|
||||
s += yTrue[i] * Ln(p) + (1 - yTrue[i]) * Ln(1 - p);
|
||||
if double.IsNaN(p) or double.IsInfinity(p) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yProb', i);
|
||||
|
||||
if (yt <> 0.0) and (yt <> 1.0) then
|
||||
ArgumentError(ER_INVALID_CLASS_LABEL, yt);
|
||||
|
||||
if p < eps then p := eps;
|
||||
if p > 1.0 - eps then p := 1.0 - eps;
|
||||
|
||||
s += yt * Ln(p) + (1.0 - yt) * Ln(1.0 - p);
|
||||
end;
|
||||
|
||||
Result := -s / n;
|
||||
|
|
@ -225,49 +349,68 @@ end;
|
|||
|
||||
static function Metrics.ROC(yTrue, yProb: Vector): (Vector, Vector);
|
||||
begin
|
||||
if yTrue.Length <> yProb.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yProb.Length);
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yProb = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yProb');
|
||||
|
||||
var n := yTrue.Length;
|
||||
|
||||
// Индексы, отсортированные по убыванию вероятностей
|
||||
var idx :=
|
||||
(0..n-1)
|
||||
.OrderByDescending(i -> yProb[i])
|
||||
.ToArray;
|
||||
if n <> yProb.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yProb.Length);
|
||||
|
||||
var tp := 0;
|
||||
var fp := 0;
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'ROC');
|
||||
|
||||
var totalPos := 0;
|
||||
var totalNeg := 0;
|
||||
|
||||
for var i := 0 to n-1 do
|
||||
if yTrue[i] = 1 then
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var yt := yTrue[i];
|
||||
var p := yProb[i];
|
||||
|
||||
if double.IsNaN(yt) or double.IsInfinity(yt) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yTrue', i);
|
||||
|
||||
if double.IsNaN(p) or double.IsInfinity(p) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yProb', i);
|
||||
|
||||
if (yt <> 0.0) and (yt <> 1.0) then
|
||||
ArgumentError(ER_INVALID_CLASS_LABEL, yt);
|
||||
|
||||
if yt = 1.0 then
|
||||
totalPos += 1
|
||||
else
|
||||
totalNeg += 1;
|
||||
end;
|
||||
|
||||
if (totalPos = 0) or (totalNeg = 0) then
|
||||
ArgumentError(ER_DIV_BY_ZERO, 'ROC');
|
||||
|
||||
var idx :=
|
||||
(0..n-1)
|
||||
.OrderByDescending(i -> yProb[i])
|
||||
.ToArray;
|
||||
|
||||
var tp := 0;
|
||||
var fp := 0;
|
||||
|
||||
var fprList := new List<real>;
|
||||
var tprList := new List<real>;
|
||||
|
||||
for var k := 0 to n-1 do
|
||||
for var k := 0 to n - 1 do
|
||||
begin
|
||||
var i := idx[k];
|
||||
|
||||
if yTrue[i] = 1 then
|
||||
if yTrue[i] = 1.0 then
|
||||
tp += 1
|
||||
else
|
||||
fp += 1;
|
||||
|
||||
var tpr := 0.0;
|
||||
var fpr := 0.0;
|
||||
|
||||
if totalPos > 0 then
|
||||
tpr := tp / totalPos;
|
||||
|
||||
if totalNeg > 0 then
|
||||
fpr := fp / totalNeg;
|
||||
var tpr := tp / totalPos;
|
||||
var fpr := fp / totalNeg;
|
||||
|
||||
tprList.Add(tpr);
|
||||
fprList.Add(fpr);
|
||||
|
|
@ -279,14 +422,42 @@ end;
|
|||
|
||||
static function Metrics.AUC(yTrue, yProb: Vector): real;
|
||||
begin
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yProb = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yProb');
|
||||
|
||||
var n := yTrue.Length;
|
||||
|
||||
if n <> yProb.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yProb.Length);
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'AUC');
|
||||
|
||||
var (fpr, tpr) := ROC(yTrue, yProb);
|
||||
|
||||
if fpr.Length <> tpr.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, fpr.Length, tpr.Length);
|
||||
|
||||
if fpr.Length < 2 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'AUC');
|
||||
|
||||
var area := 0.0;
|
||||
|
||||
for var i := 1 to fpr.Length - 1 do
|
||||
begin
|
||||
var dx := fpr[i] - fpr[i - 1];
|
||||
|
||||
if double.IsNaN(dx) or double.IsInfinity(dx) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'fpr', i);
|
||||
|
||||
var avgY := (tpr[i] + tpr[i - 1]) / 2;
|
||||
|
||||
if double.IsNaN(avgY) or double.IsInfinity(avgY) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'tpr', i);
|
||||
|
||||
area += dx * avgY;
|
||||
end;
|
||||
|
||||
|
|
@ -313,6 +484,15 @@ begin
|
|||
Result := ConfusionMatrix.Create(yTrue, yPred).F1;
|
||||
end;
|
||||
|
||||
static function Metrics.Specificity(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
Result := ConfusionMatrix.Create(yTrue, yPred).Specificity;
|
||||
end;
|
||||
|
||||
static function Metrics.BalancedAccuracy(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
Result := ConfusionMatrix.Create(yTrue, yPred).BalancedAccuracy;
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// ConfusionMatrix
|
||||
|
|
@ -320,18 +500,49 @@ end;
|
|||
|
||||
constructor ConfusionMatrix.Create(yTrue, yPred: Vector);
|
||||
begin
|
||||
if yTrue.Length <> yPred.Length then
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yPred = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'yPred');
|
||||
|
||||
var n := yTrue.Length;
|
||||
|
||||
if n <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
|
||||
for var i := 0 to yTrue.Length - 1 do
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'ConfusionMatrix');
|
||||
|
||||
fTP := 0;
|
||||
fFP := 0;
|
||||
fTN := 0;
|
||||
fFN := 0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
if yTrue[i] = 1 then
|
||||
if yPred[i] = 1 then
|
||||
var yt := yTrue[i];
|
||||
var yp := yPred[i];
|
||||
|
||||
if double.IsNaN(yt) or double.IsInfinity(yt) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yTrue', i);
|
||||
|
||||
if double.IsNaN(yp) or double.IsInfinity(yp) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'yPred', i);
|
||||
|
||||
if (yt <> 0.0) and (yt <> 1.0) then
|
||||
ArgumentError(ER_INVALID_CLASS_LABEL, yt);
|
||||
|
||||
if (yp <> 0.0) and (yp <> 1.0) then
|
||||
ArgumentError(ER_INVALID_CLASS_LABEL, yp);
|
||||
|
||||
if yt = 1.0 then
|
||||
if yp = 1.0 then
|
||||
fTP += 1
|
||||
else
|
||||
fFN += 1
|
||||
else
|
||||
if yPred[i] = 1 then
|
||||
if yp = 1.0 then
|
||||
fFP += 1
|
||||
else
|
||||
fTN += 1;
|
||||
|
|
@ -371,6 +582,17 @@ begin
|
|||
Result := 2 * p * r / (p + r);
|
||||
end;
|
||||
|
||||
|
||||
function ConfusionMatrix.Specificity: real;
|
||||
begin
|
||||
if fTN + fFP = 0 then
|
||||
exit(0.0);
|
||||
|
||||
Result := fTN / (fTN + fFP);
|
||||
end;
|
||||
|
||||
function ConfusionMatrix.BalancedAccuracy: real;
|
||||
begin
|
||||
Result := (Recall + Specificity) / 2;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -2520,10 +2520,21 @@ procedure Reverse<T>(a: List<T>; index, count: integer);
|
|||
procedure Reverse(var s: string);
|
||||
/// Изменяет порядок символов в части строки длины count на противоположный, начиная с индекса index
|
||||
procedure Reverse(var s: string; index, count: integer);
|
||||
/// Случайно перемешивает элементы массива
|
||||
/// Перемешивает элементы массива случайным образом.
|
||||
/// Используется классический алгоритм перемешивания Фишера–Йетса
|
||||
procedure Shuffle<T>(a: array of T);
|
||||
/// Случайно перемешивает элементы списка
|
||||
/// Перемешивает элементы массива случайным образом
|
||||
/// с использованием заданного генератора случайных чисел.
|
||||
/// Используется классический алгоритм перемешивания Фишера–Йетса
|
||||
procedure Shuffle<T>(a: array of T; rnd: System.Random);
|
||||
/// Перемешивает элементы списка случайным образом.
|
||||
/// Используется классический алгоритм перемешивания Фишера–Йетса.
|
||||
procedure Shuffle<T>(l: List<T>);
|
||||
/// Перемешивает элементы списка случайным образом
|
||||
/// с использованием заданного генератора случайных чисел.
|
||||
/// Используется классический алгоритм перемешивания Фишера–Йетса.
|
||||
procedure Shuffle<T>(l: List<T>; rnd: System.Random);
|
||||
|
||||
|
||||
/// Проверяет равенство двух массивов
|
||||
function ArrEqual<T>(a, b: array of T): boolean;
|
||||
|
|
@ -3219,7 +3230,7 @@ implementation
|
|||
function NewSet<T>.ToString: string := $'{ObjectToString(hs)}';
|
||||
|
||||
var
|
||||
rnd: System.Random;
|
||||
rnd: System.Random; // глобальный генератор случайных чисел
|
||||
nfi: NumberFormatInfo;
|
||||
StartTime: DateTime;// Для Milliseconds
|
||||
|
||||
|
|
@ -9997,21 +10008,46 @@ begin
|
|||
s := new string(cc);
|
||||
end;
|
||||
|
||||
procedure Shuffle<T>(a: array of T; rnd: System.Random);
|
||||
begin
|
||||
if a = nil then
|
||||
raise new System.ArgumentNullException('a');
|
||||
|
||||
if rnd = nil then
|
||||
raise new System.ArgumentNullException('rnd');
|
||||
|
||||
for var i := a.Length - 1 downto 1 do
|
||||
begin
|
||||
var j := rnd.Next(i + 1);
|
||||
Swap(a[i], a[j]);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure Shuffle<T>(l: List<T>; rnd: System.Random);
|
||||
begin
|
||||
if l = nil then
|
||||
raise new System.ArgumentNullException('l');
|
||||
|
||||
if rnd = nil then
|
||||
raise new System.ArgumentNullException('rnd');
|
||||
|
||||
for var i := l.Count - 1 downto 1 do
|
||||
begin
|
||||
var j := rnd.Next(i + 1);
|
||||
var tmp := l[i];
|
||||
l[i] := l[j];
|
||||
l[j] := tmp;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure Shuffle<T>(a: array of T);
|
||||
begin
|
||||
for var i := a.Length - 1 downto 1 do
|
||||
Swap(a[i], a[Random(i + 1)]);
|
||||
Shuffle(a, rnd);
|
||||
end;
|
||||
|
||||
procedure Shuffle<T>(l: List<T>);
|
||||
begin
|
||||
for var i := l.Count - 1 downto 1 do
|
||||
begin
|
||||
var ind := Random(i + 1);
|
||||
var v := l[i];
|
||||
l[i] := l[ind];
|
||||
l[ind] := v;
|
||||
end;
|
||||
Shuffle(l, rnd);
|
||||
end;
|
||||
|
||||
// -----------------------------------------------------
|
||||
|
|
@ -12249,14 +12285,16 @@ end;
|
|||
/// Перемешивает элементы списка случайным образом
|
||||
function Shuffle<T>(Self: List<T>): List<T>; extensionmethod;
|
||||
begin
|
||||
for var i := Self.Count - 1 downto 1 do
|
||||
begin
|
||||
var r := Random(i + 1);
|
||||
var v := Self[i];
|
||||
Self[i] := Self[r];
|
||||
Self[r] := v;
|
||||
end;
|
||||
Result := Self;
|
||||
Shuffle(Self, rnd);
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
/// Перемешивает элементы списка случайным образом
|
||||
/// с использованием заданного генератора случайных чисел.
|
||||
function Shuffle<T>(Self: List<T>; rnd: System.Random): List<T>; extensionmethod;
|
||||
begin
|
||||
Shuffle(Self, rnd);
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
/// Находит первую пару подряд идущих одинаковых элементов и возвращает индекс первого элемента пары. Если не найден, возвращается -1
|
||||
|
|
@ -13421,13 +13459,23 @@ begin
|
|||
end;
|
||||
|
||||
/// Перемешивает элементы массива случайным образом
|
||||
/// с использованием глобального генератора случайных чисел.
|
||||
/// Используется классический алгоритм перемешивания Фишера–Йетса
|
||||
function Shuffle<T>(Self: array of T): array of T; extensionmethod;
|
||||
begin
|
||||
for var i := Self.Length - 1 downto 1 do
|
||||
Swap(Self[i], Self[Random(i + 1)]);
|
||||
Shuffle(Self, rnd);
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
/// Перемешивает элементы массива случайным образом
|
||||
/// с использованием заданного генератора случайных чисел.
|
||||
/// Используется классический алгоритм перемешивания Фишера–Йетса
|
||||
function Shuffle<T>(Self: array of T; rnd: System.Random): array of T; extensionmethod;
|
||||
begin
|
||||
Shuffle(Self, rnd);
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
/// Меняет местами элементы массива с индексами i и j
|
||||
function Swap<T>(Self: array of T; i, j: integer): array of T; extensionmethod;
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -108,50 +108,6 @@ type
|
|||
function FitTransform(df: DataFrame): DataFrame;
|
||||
end;
|
||||
|
||||
{/// Приводит числовые столбцы к нулевому среднему и единичному стандартному отклонению.
|
||||
/// При Fit вычисляет среднее значение и стандартное отклонение столбцов.
|
||||
/// Применяет преобразование: x' = (x - mean) / std.
|
||||
/// Пропущенные значения (NA) сохраняются.
|
||||
DataStandardScaler = class(IPreprocessor)
|
||||
private
|
||||
cols: array of string;
|
||||
means: array of real;
|
||||
stds: array of real;
|
||||
fitted: boolean;
|
||||
public
|
||||
/// Создаёт StandardScaler для указанных числовых столбцов.
|
||||
constructor Create(params columns: array of string);
|
||||
/// Вычисляет среднее значение и стандартное отклонение для каждого столбца.
|
||||
function Fit(df: DataFrame): IPreprocessor;
|
||||
/// Возвращает DataFrame со стандартизованными числовыми столбцами.
|
||||
function Transform(df: DataFrame): DataFrame;
|
||||
/// Последовательно выполняет Fit и Transform.
|
||||
function FitTransform(df: DataFrame): DataFrame;
|
||||
end;
|
||||
|
||||
/// Приводит числовые столбцы к заданному диапазону значений
|
||||
/// При Fit вычисляет минимальное и максимальное значения столбцов
|
||||
/// Применяет преобразование: x' = (x - min) / (max - min)
|
||||
/// Пропущенные значения (NA) сохраняются
|
||||
DataMinMaxScaler = class(IPreprocessor)
|
||||
private
|
||||
cols: array of string;
|
||||
mins: array of real;
|
||||
maxs: array of real;
|
||||
fitted: boolean;
|
||||
public
|
||||
/// Создаёт MinMaxScaler для указанных столбцов
|
||||
constructor Create(params columns: array of string);
|
||||
|
||||
/// Вычисляет минимальные и максимальные значения столбцов
|
||||
function Fit(df: DataFrame): IPreprocessor;
|
||||
/// Применяет масштабирование к DataFrame
|
||||
/// Возвращает новый DataFrame
|
||||
function Transform(df: DataFrame): DataFrame;
|
||||
/// Последовательно выполняет Fit и Transform.
|
||||
function FitTransform(df: DataFrame): DataFrame;
|
||||
end;}
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ type
|
|||
paramValues: array of real;
|
||||
X: Matrix; y: Vector;
|
||||
k: integer;
|
||||
metric: (Vector, Vector) -> real
|
||||
metric: (Vector, Vector) -> real;
|
||||
maximize: boolean := True
|
||||
): (real, real, T); where T: IModel;
|
||||
end;
|
||||
|
||||
|
|
@ -91,7 +92,9 @@ const
|
|||
'Invalid k in StratifiedKFold: k={0}, n={1}';
|
||||
ER_STRATIFIED_LABELS_INVALID =
|
||||
'StratifiedKFold поддерживает только метки 0/1!!' +
|
||||
'StratifiedKFold supports only 0/1 labels';
|
||||
'StratifiedKFold supports only 0/1 labels';
|
||||
ER_INVALID_VALUE =
|
||||
'Некорректное значение параметра {0}!!Invalid value for parameter {0}';
|
||||
|
||||
//-----------------------------
|
||||
// Validation
|
||||
|
|
@ -100,26 +103,34 @@ const
|
|||
static function Validation.TrainTestSplit(X: Matrix; y: Vector;
|
||||
testRatio: real; seed: integer): (Matrix, Matrix, Vector, Vector);
|
||||
begin
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if y = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'y');
|
||||
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH_TRAIN_TEST, X.RowCount, y.Length);
|
||||
|
||||
if (testRatio <= 0) or (testRatio >= 1) then
|
||||
if (testRatio <= 0.0) or (testRatio >= 1.0) then
|
||||
ArgumentError(ER_TEST_RATIO_INVALID, testRatio);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
var rnd := if seed >= 0 then
|
||||
new System.Random(seed)
|
||||
else
|
||||
new System.Random;
|
||||
if n < 2 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'TrainTestSplit');
|
||||
|
||||
// Перемешанные индексы
|
||||
var idx := (0..n-1).ToArray;
|
||||
idx := idx.OrderBy(i -> rnd.Next).ToArray;
|
||||
var actualSeed := if seed >= 0 then seed else System.Environment.TickCount and integer.MaxValue;
|
||||
var rnd := new System.Random(actualSeed);
|
||||
|
||||
var idx := Arr(0..n-1);
|
||||
|
||||
// --- 2. Перемешивание через стандартный Shuffle
|
||||
idx.Shuffle(rnd);
|
||||
|
||||
var rawSize := Round(n * testRatio);
|
||||
var testSize := Max(1, Min(n - 1, rawSize));
|
||||
var testSize := rawSize.Clamp(1, n - 1);
|
||||
var trainSize := n - testSize;
|
||||
|
||||
var X_train := new Matrix(trainSize, p);
|
||||
|
|
@ -128,25 +139,19 @@ begin
|
|||
var y_train := new Vector(trainSize);
|
||||
var y_test := new Vector(testSize);
|
||||
|
||||
// Заполняем train
|
||||
for var i := 0 to trainSize - 1 do
|
||||
begin
|
||||
var row := idx[i];
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
X_train[i,j] := X[row,j];
|
||||
|
||||
y_train[i] := y[row];
|
||||
end;
|
||||
|
||||
// Заполняем test
|
||||
for var i := 0 to testSize - 1 do
|
||||
begin
|
||||
var row := idx[trainSize + i];
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
X_test[i,j] := X[row,j];
|
||||
|
||||
y_test[i] := y[row];
|
||||
end;
|
||||
|
||||
|
|
@ -156,27 +161,22 @@ end;
|
|||
static function Validation.KFold(n, k: integer; seed: integer):
|
||||
sequence of (array of integer, array of integer);
|
||||
begin
|
||||
if n <= 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'KFold');
|
||||
|
||||
if (k < 2) or (k > n) then
|
||||
ArgumentError(ER_K_INVALID, k, n);
|
||||
|
||||
var rnd := if seed >= 0 then
|
||||
new System.Random(seed)
|
||||
else
|
||||
new System.Random;
|
||||
var actualSeed := if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
// --- 1. Создаём массив индексов 0..n-1
|
||||
var idx := new integer[n];
|
||||
for var i := 0 to n - 1 do
|
||||
idx[i] := i;
|
||||
var rnd := new System.Random(actualSeed);
|
||||
|
||||
// --- 2. Fisher–Yates shuffle (in-place, O(n))
|
||||
for var i := n - 1 downto 1 do
|
||||
begin
|
||||
var j := rnd.Next(i + 1);
|
||||
var tmp := idx[i];
|
||||
idx[i] := idx[j];
|
||||
idx[j] := tmp;
|
||||
end;
|
||||
// --- 1. Индексы 0..n-1
|
||||
var idx := Arr(0..n-1);
|
||||
|
||||
// --- 2. Перемешивание через стандартный Shuffle
|
||||
idx.Shuffle(rnd);
|
||||
|
||||
var baseSize := n div k;
|
||||
var extra := n mod k;
|
||||
|
|
@ -187,19 +187,15 @@ begin
|
|||
begin
|
||||
var size := baseSize + Ord(fold < extra);
|
||||
|
||||
// --- test indices
|
||||
var testIdx := new integer[size];
|
||||
System.Array.Copy(idx, start, testIdx, 0, size);
|
||||
|
||||
// --- train indices
|
||||
var trainSize := n - size;
|
||||
var trainIdx := new integer[trainSize];
|
||||
|
||||
// левая часть
|
||||
if start > 0 then
|
||||
System.Array.Copy(idx, 0, trainIdx, 0, start);
|
||||
|
||||
// правая часть
|
||||
var tailCount := n - (start + size);
|
||||
if tailCount > 0 then
|
||||
System.Array.Copy(idx, start + size, trainIdx, start, tailCount);
|
||||
|
|
@ -213,22 +209,30 @@ end;
|
|||
static function Validation.StratifiedKFold(y: Vector; k: integer; seed: integer):
|
||||
sequence of (array of integer, array of integer);
|
||||
begin
|
||||
if y = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'y');
|
||||
|
||||
var n := y.Length;
|
||||
|
||||
if n <= 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'StratifiedKFold');
|
||||
|
||||
if (k < 2) or (k > n) then
|
||||
ArgumentError(ER_K_INVALID_STRATIFIED, k, n);
|
||||
|
||||
var rnd :=
|
||||
if seed >= 0 then new System.Random(seed)
|
||||
else new System.Random;
|
||||
var actualSeed := if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
// 1) Собираем индексы по классам за один проход
|
||||
var rnd := new System.Random(actualSeed);
|
||||
|
||||
// --- 1. Индексы по классам
|
||||
var classMap := new Dictionary<integer, List<integer>>();
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var v := y[i];
|
||||
var cls := integer(v);
|
||||
|
||||
// Валидация: метки должны быть целыми (хотя тип double)
|
||||
if Abs(v - cls) > 1e-12 then
|
||||
ArgumentError(ER_STRATIFIED_LABELS_INVALID);
|
||||
|
||||
|
|
@ -243,27 +247,18 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
// 2) Подготавливаем контейнеры фолдов
|
||||
// --- 2. Контейнеры фолдов
|
||||
var folds := new List<integer>[k];
|
||||
for var f := 0 to k - 1 do
|
||||
folds[f] := new List<integer>;
|
||||
|
||||
// 3) Для каждого класса: shuffle + равномерно раскладываем по k фолдам
|
||||
// --- 3. Для каждого класса: shuffle + равномерное распределение
|
||||
foreach var pair in classMap do
|
||||
begin
|
||||
var indices := pair.Value;
|
||||
indices.Shuffle(rnd);
|
||||
|
||||
var m := indices.Count;
|
||||
|
||||
// Fisher–Yates shuffle in-place
|
||||
for var i := m - 1 downto 1 do
|
||||
begin
|
||||
var j := rnd.Next(i + 1);
|
||||
var tmp := indices[i];
|
||||
indices[i] := indices[j];
|
||||
indices[j] := tmp;
|
||||
end;
|
||||
|
||||
// Равномерное распределение (разница размеров ≤ 1)
|
||||
var baseSize := m div k;
|
||||
var extra := m mod k;
|
||||
var start := 0;
|
||||
|
|
@ -277,18 +272,16 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
// 4) Возвращаем (trainIdx, testIdx) для каждого фолда
|
||||
// --- 4. Формирование train/test
|
||||
for var fold := 0 to k - 1 do
|
||||
begin
|
||||
var testIdx := folds[fold].ToArray();
|
||||
var testIdx := folds[fold].ToArray;
|
||||
|
||||
// boolean-mask вместо Contains -> O(n)
|
||||
var mask := new boolean[n];
|
||||
foreach var id in testIdx do
|
||||
mask[id] := true;
|
||||
|
||||
var trainSize := n - testIdx.Length;
|
||||
var trainIdx := new integer[trainSize];
|
||||
var trainIdx := new integer[n - testIdx.Length];
|
||||
var p := 0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
|
|
@ -303,12 +296,32 @@ begin
|
|||
end;
|
||||
|
||||
|
||||
static function Validation.CrossValidate(model: IModel; X: Matrix; y: Vector;
|
||||
k: integer; metric: (Vector,Vector) -> real; seed: integer): real;
|
||||
static function Validation.CrossValidate(
|
||||
model: IModel;
|
||||
X: Matrix;
|
||||
y: Vector;
|
||||
k: integer;
|
||||
metric: (Vector,Vector) -> real;
|
||||
seed: integer): real;
|
||||
begin
|
||||
if model = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'model');
|
||||
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if y = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'y');
|
||||
|
||||
if metric = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'metric');
|
||||
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
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;
|
||||
|
|
@ -339,23 +352,45 @@ begin
|
|||
|
||||
var m := model.Clone();
|
||||
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;
|
||||
|
||||
static function Validation.StratifiedCrossValidate(
|
||||
model: IModel; X: Matrix; y: Vector;
|
||||
k: integer; metric: (Vector,Vector) -> real; seed: integer
|
||||
): real;
|
||||
model: IModel;
|
||||
X: Matrix;
|
||||
y: Vector;
|
||||
k: integer;
|
||||
metric: (Vector,Vector) -> real;
|
||||
seed: integer): real;
|
||||
begin
|
||||
if model = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'model');
|
||||
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if y = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'y');
|
||||
|
||||
if metric = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'metric');
|
||||
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
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;
|
||||
|
|
@ -386,12 +421,16 @@ begin
|
|||
|
||||
var m := model.Clone();
|
||||
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;
|
||||
|
||||
|
|
@ -402,23 +441,51 @@ end;
|
|||
class function GridSearch.Search<T>(
|
||||
modelFactory: real -> T;
|
||||
paramValues: array of real;
|
||||
X: Matrix; y: Vector;
|
||||
X: Matrix;
|
||||
y: Vector;
|
||||
k: integer;
|
||||
metric: (Vector, Vector) -> real
|
||||
metric: (Vector, Vector) -> real;
|
||||
maximize: boolean
|
||||
): (real, real, T); where T: IModel;
|
||||
begin
|
||||
if modelFactory = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'modelFactory');
|
||||
|
||||
if paramValues = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'paramValues');
|
||||
|
||||
if paramValues.Length = 0 then
|
||||
ArgumentError(ER_PARAM_VALUES_EMPTY);
|
||||
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if y = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'y');
|
||||
|
||||
if metric = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'metric');
|
||||
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
var bestParam := paramValues[0];
|
||||
var bestScore := -1e308;
|
||||
var bestScore :=
|
||||
if maximize then -1e308 else 1e308;
|
||||
|
||||
foreach var param in paramValues do
|
||||
begin
|
||||
var model := modelFactory(param);
|
||||
var avgScore := Validation.CrossValidate(model, X, y, k, metric);
|
||||
|
||||
if avgScore > bestScore then
|
||||
if double.IsNaN(avgScore) or double.IsInfinity(avgScore) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'avgScore');
|
||||
|
||||
var better :=
|
||||
(maximize and (avgScore > bestScore)) or
|
||||
(not maximize and (avgScore < bestScore));
|
||||
|
||||
if better then
|
||||
begin
|
||||
bestScore := avgScore;
|
||||
bestParam := param;
|
||||
|
|
|
|||
Loading…
Reference in a new issue