ML -
Проверки на NaN Inf
This commit is contained in:
parent
398ea2440f
commit
03fa44dc8b
|
|
@ -51,14 +51,14 @@ begin
|
|||
if scoreFunc = nil then
|
||||
ArgumentNullError(ER_SCORE_FUNC_NULL);
|
||||
|
||||
if X.Rows <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.Rows, y.Length);
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
var baselinePred := model.Predict(X);
|
||||
var baselineScore := scoreFunc(y, baselinePred);
|
||||
|
||||
var n := X.Rows;
|
||||
var p := X.Cols;
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
var resultVec := new Vector(p);
|
||||
var rng := new System.Random(seed);
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ type
|
|||
procedure SetData(i, j: integer; value: real) := data[i, j] := value;
|
||||
public
|
||||
data: array[,] of real;
|
||||
property Rows: integer read data.GetLength(0);
|
||||
property Cols: integer read data.GetLength(1);
|
||||
property RowCount: integer read data.RowCount;
|
||||
property ColCount: integer read data.ColCount;
|
||||
|
||||
property Item[i, j: integer]: real
|
||||
read data[i, j] write SetData; default;
|
||||
|
|
@ -140,9 +140,6 @@ type
|
|||
|
||||
// ---------- Основные методы ----------
|
||||
|
||||
function RowCount: integer := data.RowCount;
|
||||
function ColCount: integer := data.ColCount;
|
||||
|
||||
/// Возвращает транспонированную матрицу Aᵀ
|
||||
function Transpose: Matrix;
|
||||
/// Проверяет, является ли матрица симметричной с заданным допуском.
|
||||
|
|
@ -241,6 +238,8 @@ function SolveAuto(A: Matrix; b: Vector): Vector;
|
|||
/// Входные A и b изменяются. Возвращаемый вектор x имеет длину n.
|
||||
function SolveRidge(A: Matrix; b: Vector; lambda: real := 0): Vector;
|
||||
|
||||
function SolveLeastSquaresQR(A: Matrix; b: Vector): Vector;
|
||||
|
||||
implementation
|
||||
|
||||
uses MLExceptions;
|
||||
|
|
@ -291,6 +290,10 @@ const
|
|||
'Матрица вырождена!!Matrix is singular';
|
||||
ER_VECTOR_SIZE_MISMATCH =
|
||||
'Несоответствие длины вектора: {0} и {1}!!Vector size mismatch: {0} and {1}';
|
||||
ER_QR_REQUIRES_M_GE_N =
|
||||
'Для QR-разложения требуется m >= n!!QR decomposition requires m >= n';
|
||||
ER_SINGULAR_MATRIX =
|
||||
'Матрица вырождена или плохо обусловлена!!Matrix is singular or ill-conditioned';
|
||||
|
||||
//-----------------------------
|
||||
// Vector
|
||||
|
|
@ -813,20 +816,20 @@ end;
|
|||
|
||||
static procedure Matrix.CheckSameSize(A, B: Matrix);
|
||||
begin
|
||||
if (A.Rows <> B.Rows) or (A.Cols <> B.Cols) then
|
||||
DimensionError(ER_MATRIX_SIZE_MISMATCH, A.Rows, A.Cols, B.Rows, B.Cols);
|
||||
if (A.RowCount <> B.RowCount) or (A.ColCount <> B.ColCount) then
|
||||
DimensionError(ER_MATRIX_SIZE_MISMATCH, A.RowCount, A.ColCount, B.RowCount, B.ColCount);
|
||||
end;
|
||||
|
||||
static procedure Matrix.CheckMulSize(A, B: Matrix);
|
||||
begin
|
||||
if A.Cols <> B.Rows then
|
||||
DimensionError(ER_MATRIX_MUL_SIZE_MISMATCH, A.Rows, A.Cols, B.Rows, B.Cols);
|
||||
if A.ColCount <> B.RowCount then
|
||||
DimensionError(ER_MATRIX_MUL_SIZE_MISMATCH, A.RowCount, A.ColCount, B.RowCount, B.ColCount);
|
||||
end;
|
||||
|
||||
static procedure Matrix.CheckVecSize(A: Matrix; x: Vector);
|
||||
begin
|
||||
if A.Cols <> x.Length then
|
||||
DimensionError(ER_MATRIX_VECTOR_SIZE_MISMATCH, A.Rows, A.Cols, x.Length);
|
||||
if A.ColCount <> x.Length then
|
||||
DimensionError(ER_MATRIX_VECTOR_SIZE_MISMATCH, A.RowCount, A.ColCount, x.Length);
|
||||
end;
|
||||
|
||||
|
||||
|
|
@ -834,26 +837,26 @@ end;
|
|||
static function Matrix.operator +(A, B: Matrix): Matrix;
|
||||
begin
|
||||
CheckSameSize(A, B);
|
||||
Result := new Matrix(A.Rows, A.Cols);
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
for var j := 0 to A.Cols - 1 do
|
||||
Result := new Matrix(A.RowCount, A.ColCount);
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
for var j := 0 to A.ColCount - 1 do
|
||||
Result.data[i, j] := A.data[i, j] + B.data[i, j];
|
||||
end;
|
||||
|
||||
static function Matrix.operator -(A, B: Matrix): Matrix;
|
||||
begin
|
||||
CheckSameSize(A, B);
|
||||
Result := new Matrix(A.Rows, A.Cols);
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
for var j := 0 to A.Cols - 1 do
|
||||
Result := new Matrix(A.RowCount, A.ColCount);
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
for var j := 0 to A.ColCount - 1 do
|
||||
Result.data[i, j] := A.data[i, j] - B.data[i, j];
|
||||
end;
|
||||
|
||||
function operator *(A: Matrix; alpha: real): Matrix; extensionmethod;
|
||||
begin
|
||||
Result := new Matrix(A.Rows, A.Cols);
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
for var j := 0 to A.Cols - 1 do
|
||||
Result := new Matrix(A.RowCount, A.ColCount);
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
for var j := 0 to A.ColCount - 1 do
|
||||
Result.data[i, j] := alpha * A.data[i, j];
|
||||
end;
|
||||
|
||||
|
|
@ -877,11 +880,11 @@ end;
|
|||
static function Matrix.operator *(A: Matrix; x: Vector): Vector;
|
||||
begin
|
||||
CheckVecSize(A, x);
|
||||
Result := new Vector(A.Rows);
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
Result := new Vector(A.RowCount);
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
begin
|
||||
var s := 0.0;
|
||||
for var j := 0 to A.Cols - 1 do
|
||||
for var j := 0 to A.ColCount - 1 do
|
||||
s += A.data[i, j] * x[j];
|
||||
Result[i] := s;
|
||||
end;
|
||||
|
|
@ -890,13 +893,13 @@ end;
|
|||
static function Matrix.operator *(A, B: Matrix): Matrix;
|
||||
begin
|
||||
CheckMulSize(A, B);
|
||||
Result := new Matrix(A.Rows, B.Cols);
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
for var k := 0 to A.Cols - 1 do
|
||||
Result := new Matrix(A.RowCount, B.ColCount);
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
for var k := 0 to A.ColCount - 1 do
|
||||
begin
|
||||
var aik := A.data[i, k];
|
||||
if aik <> 0.0 then
|
||||
for var j := 0 to B.Cols - 1 do
|
||||
for var j := 0 to B.ColCount - 1 do
|
||||
Result.data[i, j] += aik * B.data[k, j];
|
||||
end;
|
||||
end;
|
||||
|
|
@ -904,8 +907,8 @@ end;
|
|||
static function Matrix.operator +=(A, B: Matrix): Matrix;
|
||||
begin
|
||||
CheckSameSize(A, B);
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
for var j := 0 to A.Cols - 1 do
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
for var j := 0 to A.ColCount - 1 do
|
||||
A.data[i, j] += B.data[i, j];
|
||||
Result := A;
|
||||
end;
|
||||
|
|
@ -913,43 +916,43 @@ end;
|
|||
static function Matrix.operator -=(A, B: Matrix): Matrix;
|
||||
begin
|
||||
CheckSameSize(A, B);
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
for var j := 0 to A.Cols - 1 do
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
for var j := 0 to A.ColCount - 1 do
|
||||
A.data[i, j] -= B.data[i, j];
|
||||
Result := A;
|
||||
end;
|
||||
|
||||
static function Matrix.operator *=(A: Matrix; alpha: real): Matrix;
|
||||
begin
|
||||
for var i := 0 to A.Rows - 1 do
|
||||
for var j := 0 to A.Cols - 1 do
|
||||
for var i := 0 to A.RowCount - 1 do
|
||||
for var j := 0 to A.ColCount - 1 do
|
||||
A.data[i, j] *= alpha;
|
||||
Result := A;
|
||||
end;
|
||||
|
||||
function Matrix.Transpose: Matrix;
|
||||
begin
|
||||
Result := new Matrix(Cols, Rows);
|
||||
for var i := 0 to Rows - 1 do
|
||||
for var j := 0 to Cols - 1 do
|
||||
Result := new Matrix(ColCount, RowCount);
|
||||
for var i := 0 to RowCount - 1 do
|
||||
for var j := 0 to ColCount - 1 do
|
||||
Result.data[j, i] := data[i, j];
|
||||
end;
|
||||
|
||||
function Matrix.GetRow(i: integer): Vector;
|
||||
begin
|
||||
if (i < 0) or (i >= Rows) then
|
||||
ArgumentOutOfRangeError(ER_ROW_INDEX_OUT_OF_RANGE, i, Rows);
|
||||
Result := new Vector(Cols);
|
||||
for var j := 0 to Cols - 1 do
|
||||
if (i < 0) or (i >= RowCount) then
|
||||
ArgumentOutOfRangeError(ER_ROW_INDEX_OUT_OF_RANGE, i, RowCount);
|
||||
Result := new Vector(ColCount);
|
||||
for var j := 0 to ColCount - 1 do
|
||||
Result[j] := data[i, j];
|
||||
end;
|
||||
|
||||
function Matrix.GetCol(j: integer): Vector;
|
||||
begin
|
||||
if (j < 0) or (j >= Cols) then
|
||||
ArgumentOutOfRangeError(ER_COL_INDEX_OUT_OF_RANGE, j, Cols);
|
||||
Result := new Vector(Rows);
|
||||
for var i := 0 to Rows - 1 do
|
||||
if (j < 0) or (j >= ColCount) then
|
||||
ArgumentOutOfRangeError(ER_COL_INDEX_OUT_OF_RANGE, j, ColCount);
|
||||
Result := new Vector(RowCount);
|
||||
for var i := 0 to RowCount - 1 do
|
||||
Result[i] := data[i, j];
|
||||
end;
|
||||
|
||||
|
|
@ -976,14 +979,14 @@ end;
|
|||
|
||||
function Matrix.IsSymmetric(tol: real): boolean;
|
||||
begin
|
||||
if Rows <> Cols then
|
||||
if RowCount <> ColCount then
|
||||
begin
|
||||
Result := false;
|
||||
exit;
|
||||
end;
|
||||
|
||||
for var i := 0 to Rows - 1 do
|
||||
for var j := i + 1 to Cols - 1 do
|
||||
for var i := 0 to RowCount - 1 do
|
||||
for var j := i + 1 to ColCount - 1 do
|
||||
if Abs(data[i, j] - data[j, i]) > tol then
|
||||
begin
|
||||
Result := false;
|
||||
|
|
@ -995,13 +998,13 @@ end;
|
|||
|
||||
function Matrix.EigenSymmetric(tol: real; maxIter: integer): (Vector, Matrix);
|
||||
begin
|
||||
if Rows <> Cols then
|
||||
if RowCount <> ColCount then
|
||||
ArgumentError(ER_MATRIX_NOT_SQUARE);
|
||||
|
||||
if not IsSymmetric(tol) then
|
||||
ArgumentError(ER_MATRIX_NOT_SYMMETRIC);
|
||||
|
||||
var n := Rows;
|
||||
var n := RowCount;
|
||||
|
||||
var M := Clone;
|
||||
var V := Matrix.Identity(n);
|
||||
|
|
@ -1136,8 +1139,8 @@ end;
|
|||
|
||||
function Matrix.PCA(k: integer): (Matrix, Vector);
|
||||
begin
|
||||
var m := Rows;
|
||||
var n := Cols;
|
||||
var m := RowCount;
|
||||
var n := ColCount;
|
||||
|
||||
if k < 1 then
|
||||
ArgumentError(ER_PCA_K_INVALID);
|
||||
|
|
@ -1200,10 +1203,10 @@ end;
|
|||
// Helper
|
||||
function Cholesky(A: Matrix): Matrix;
|
||||
begin
|
||||
if A.Rows <> A.Cols then
|
||||
if A.RowCount <> A.ColCount then
|
||||
ArgumentError(ER_CHOLESKY_NOT_SQUARE);
|
||||
|
||||
var n := A.Rows;
|
||||
var n := A.RowCount;
|
||||
var L := new Matrix(n, n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
|
|
@ -1229,10 +1232,10 @@ end;
|
|||
// Helper
|
||||
function LUDecompose(A: Matrix): (Matrix, array of integer);
|
||||
begin
|
||||
if A.Rows <> A.Cols then
|
||||
if A.RowCount <> A.ColCount then
|
||||
ArgumentError(ER_MATRIX_NOT_SQUARE);
|
||||
|
||||
var n := A.Rows;
|
||||
var n := A.RowCount;
|
||||
var LU := A.Clone;
|
||||
var p := new integer[n];
|
||||
|
||||
|
|
@ -1294,7 +1297,7 @@ end;
|
|||
// Helper
|
||||
function SolveLowerTriangularUnit(LU: Matrix; b: Vector): Vector;
|
||||
begin
|
||||
var n := LU.Rows;
|
||||
var n := LU.RowCount;
|
||||
var y := new Vector(n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
|
|
@ -1310,7 +1313,7 @@ end;
|
|||
|
||||
function SolveLowerTriangular(L: Matrix; b: Vector): Vector;
|
||||
begin
|
||||
var n := L.Rows;
|
||||
var n := L.RowCount;
|
||||
var y := new Vector(n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
|
|
@ -1328,7 +1331,7 @@ end;
|
|||
// Helper
|
||||
function SolveUpperTriangular(U: Matrix; y: Vector): Vector;
|
||||
begin
|
||||
var n := U.Rows;
|
||||
var n := U.RowCount;
|
||||
var x := new Vector(n);
|
||||
|
||||
for var i := n - 1 downto 0 do
|
||||
|
|
@ -1344,11 +1347,11 @@ end;
|
|||
|
||||
function Solve(A: Matrix; b: Vector): Vector;
|
||||
begin
|
||||
if A.Rows <> A.Cols then
|
||||
if A.RowCount <> A.ColCount then
|
||||
ArgumentError(ER_MATRIX_NOT_SQUARE);
|
||||
|
||||
if b.Length <> A.Rows then
|
||||
DimensionError(ER_VECTOR_SIZE_MISMATCH, b.Length, A.Rows);
|
||||
if b.Length <> A.RowCount then
|
||||
DimensionError(ER_VECTOR_SIZE_MISMATCH, b.Length, A.RowCount);
|
||||
|
||||
var (LU, p) := LUDecompose(A);
|
||||
var pb := Permute(b, p);
|
||||
|
|
@ -1358,11 +1361,11 @@ end;
|
|||
|
||||
function SolveSPD(A: Matrix; b: Vector): Vector;
|
||||
begin
|
||||
if A.Rows <> A.Cols then
|
||||
if A.RowCount <> A.ColCount then
|
||||
ArgumentError(ER_MATRIX_NOT_SQUARE);
|
||||
|
||||
if b.Length <> A.Rows then
|
||||
DimensionError(ER_VECTOR_SIZE_MISMATCH, b.Length, A.Rows);
|
||||
if b.Length <> A.RowCount then
|
||||
DimensionError(ER_VECTOR_SIZE_MISMATCH, b.Length, A.RowCount);
|
||||
|
||||
var L := Cholesky(A);
|
||||
var y := SolveLowerTriangular(L, b);
|
||||
|
|
@ -1383,8 +1386,8 @@ begin
|
|||
if lambda < 0.0 then
|
||||
ArgumentError(ER_LAMBDA_NEGATIVE);
|
||||
|
||||
var m := A.Rows;
|
||||
var n := A.Cols;
|
||||
var m := A.RowCount;
|
||||
var n := A.ColCount;
|
||||
|
||||
if b.Length <> m then
|
||||
DimensionError(ER_VECTOR_SIZE_MISMATCH, b.Length, m);
|
||||
|
|
@ -1430,6 +1433,89 @@ begin
|
|||
end;
|
||||
|
||||
|
||||
function SolveLeastSquaresQR(A: Matrix; b: Vector): Vector;
|
||||
begin
|
||||
if A.RowCount <> b.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, A.RowCount, b.Length);
|
||||
|
||||
var m := A.RowCount;
|
||||
var n := A.ColCount;
|
||||
|
||||
if m < n then
|
||||
ArgumentError(ER_QR_REQUIRES_M_GE_N);
|
||||
|
||||
var R := A.Clone;
|
||||
var y := b.Clone;
|
||||
|
||||
for var k := 0 to n - 1 do
|
||||
begin
|
||||
// ---- вычислить норму столбца k начиная с строки k
|
||||
var normx := 0.0;
|
||||
for var i := k to m - 1 do
|
||||
normx += R[i,k] * R[i,k];
|
||||
|
||||
normx := Sqrt(normx);
|
||||
|
||||
if normx = 0 then
|
||||
continue;
|
||||
|
||||
// ---- знак для устойчивости
|
||||
if R[k,k] >= 0 then
|
||||
normx := -normx;
|
||||
|
||||
// ---- построить Householder-вектор v
|
||||
var beta := 1.0 / (normx * R[k,k] - normx * normx);
|
||||
|
||||
R[k,k] -= normx;
|
||||
|
||||
// ---- применить отражение к R
|
||||
for var j := k + 1 to n - 1 do
|
||||
begin
|
||||
var s := 0.0;
|
||||
for var i := k to m - 1 do
|
||||
s += R[i,k] * R[i,j];
|
||||
|
||||
s *= beta;
|
||||
|
||||
for var i := k to m - 1 do
|
||||
R[i,j] -= s * R[i,k];
|
||||
end;
|
||||
|
||||
// ---- применить отражение к y
|
||||
var sy := 0.0;
|
||||
for var i := k to m - 1 do
|
||||
sy += R[i,k] * y[i];
|
||||
|
||||
sy *= beta;
|
||||
|
||||
for var i := k to m - 1 do
|
||||
y[i] -= sy * R[i,k];
|
||||
|
||||
// ---- восстановить диагональный элемент
|
||||
R[k,k] := normx;
|
||||
|
||||
for var i := k + 1 to m - 1 do
|
||||
R[i,k] := 0.0;
|
||||
end;
|
||||
|
||||
// ---- Back substitution (решаем R[0:n,0:n] * x = y[0:n])
|
||||
var x := new Vector(n);
|
||||
|
||||
for var i := n - 1 downto 0 do
|
||||
begin
|
||||
var sum := y[i];
|
||||
|
||||
for var j := i + 1 to n - 1 do
|
||||
sum -= R[i,j] * x[j];
|
||||
|
||||
if Abs(R[i,i]) < 1e-14 then
|
||||
ArgumentError(ER_SINGULAR_MATRIX);
|
||||
|
||||
x[i] := sum / R[i,i];
|
||||
end;
|
||||
|
||||
Result := x;
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
|
@ -15,6 +15,8 @@ const
|
|||
'Метод Fit() не вызван!!Fit() must be called first';
|
||||
ER_MODEL_NULL =
|
||||
'Модель не может быть nil!!Model cannot be nil';
|
||||
ER_SELECTKBEST_K_INVALID =
|
||||
'Параметр k в SelectKBest должен быть >= 1. Передано: {0}.!!SelectKBest parameter k must be >= 1. Given: {0}.';
|
||||
|
||||
type
|
||||
/// Базовое исключение ML-библиотеки
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -187,7 +187,7 @@ begin
|
|||
if fFitted then
|
||||
Error(ER_PIPELINE_MODIFY_AFTER_FIT);
|
||||
|
||||
if step is IDataStep then
|
||||
if step is IPreprocessor then // Data Step
|
||||
begin
|
||||
if (fMatrixSteps.Count > 0) or (fModel <> nil) then
|
||||
ArgumentError(ER_DATAPIPE_DF_AFTER_MATRIX);
|
||||
|
|
@ -196,7 +196,7 @@ begin
|
|||
exit(Self);
|
||||
end;
|
||||
|
||||
if step is IMatrixStep then
|
||||
if step is ITransformer then // Matrix Step
|
||||
begin
|
||||
if step is IModel then
|
||||
begin
|
||||
|
|
@ -270,7 +270,7 @@ begin
|
|||
end;
|
||||
|
||||
// 5) Модель
|
||||
fModel.Fit(X, y);
|
||||
fModel := fModel.Fit(X, y);
|
||||
|
||||
fFitted := true;
|
||||
Result := Self;
|
||||
|
|
|
|||
|
|
@ -9,40 +9,44 @@ type
|
|||
public
|
||||
/// Делит данные на обучающую и тестовую выборки.
|
||||
/// testRatio — доля объектов, попадающих в тестовую выборку (по умолчанию 0.2).
|
||||
/// Перемешивает объекты перед разбиением.
|
||||
/// Возвращает (X_train, X_test, y_train, y_test).
|
||||
/// Перед разбиением объекты перемешиваются.
|
||||
/// Возвращает кортеж (X_train, X_test, y_train, y_test).
|
||||
static function TrainTestSplit(X: Matrix; y: Vector;
|
||||
testRatio: real := 0.2; seed: integer := 0): (Matrix, Matrix, Vector, Vector);
|
||||
testRatio: real := 0.2; seed: integer := -1): (Matrix, Matrix, Vector, Vector);
|
||||
|
||||
/// Разбивает индексы объектов на k непересекающихся частей (fold).
|
||||
/// На каждом шаге одна часть используется как тестовая, остальные — как обучающая.
|
||||
/// Применяется для k-fold кросс-валидации.
|
||||
/// На каждом шаге одна часть используется как тестовая, остальные — как обучающая выборка.
|
||||
/// Используется для k-fold кросс-валидации.
|
||||
/// Возвращает последовательность пар (trainIdx, testIdx).
|
||||
static function KFold(n, k: integer; seed: integer := 0):
|
||||
static function KFold(n, k: integer; seed: integer := -1):
|
||||
sequence of (array of integer, array of integer);
|
||||
|
||||
/// Разбивает данные на k частей с сохранением пропорций классов.
|
||||
/// В каждой части сохраняется примерно то же соотношение
|
||||
/// объектов разных классов, что и во всей выборке.
|
||||
/// Рекомендуется для задач классификации,
|
||||
/// особенно при неравномерном распределении классов.
|
||||
/// Возвращает последовательность пар (trainIdx, testIdx).
|
||||
/// Разбивает данные на k частей (k-fold) с сохранением пропорций классов
|
||||
/// (стратифицированная k-fold кросс-валидация).
|
||||
/// В каждой части доля объектов каждого класса
|
||||
/// максимально близка к их доле во всей выборке
|
||||
/// (разница не превышает одного объекта на класс).
|
||||
/// Рекомендуется для задач классификации,
|
||||
/// особенно при несбалансированных классах.
|
||||
/// Возвращает последовательность пар (trainIdx, testIdx)
|
||||
static function StratifiedKFold(y: Vector; k: integer;
|
||||
seed: integer := 0): sequence of (array of integer, array of integer);
|
||||
seed: integer := -1): sequence of (array of integer, array of integer);
|
||||
|
||||
/// Выполняет k-fold кросс-валидацию модели.
|
||||
/// На каждом шаге модель обучается на обучающей части и оценивается на тестовой.
|
||||
/// metric — функция качества (например, Accuracy или MSE).
|
||||
/// На каждом шаге модель обучается на обучающей части и оценивается на соответствующей тестовой части.
|
||||
/// metric — функция качества, принимающая (y_true, y_pred)
|
||||
/// и возвращающая значение метрики (например, Accuracy или MSE).
|
||||
/// Возвращает среднее значение метрики по всем частям.
|
||||
static function CrossValidate(model: IModel; X: Matrix; y: Vector;
|
||||
k: integer; metric: (Vector,Vector) -> real; seed: integer := 0): real;
|
||||
k: integer; metric: (Vector,Vector) -> real; seed: integer := -1): real;
|
||||
|
||||
/// Выполняет k-fold кросс-валидацию с сохранением пропорций классов.
|
||||
/// Использует StratifiedKFold для разбиения данных.
|
||||
/// Подходит для задач классификации.
|
||||
/// Возвращает среднее значение метрики по всем частям.
|
||||
/// Выполняет стратифицированную k-fold кросс-валидацию модели.
|
||||
/// Разбиение данных выполняется методом StratifiedKFold
|
||||
/// с сохранением пропорций классов в каждой части.
|
||||
/// Рекомендуется для задач классификации, особенно при несбалансированных классах.
|
||||
/// Возвращает среднее значение метрики по k разбиениям.
|
||||
static function StratifiedCrossValidate(model: IModel; X: Matrix; y: Vector;
|
||||
k: integer; metric: (Vector,Vector) -> real; seed: integer := 0): real;
|
||||
k: integer; metric: (Vector,Vector) -> real; seed: integer := -1): real;
|
||||
end;
|
||||
|
||||
/// Класс для подбора гиперпараметров методом перебора по сетке (Grid Search).
|
||||
|
|
@ -105,13 +109,17 @@ begin
|
|||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
var rnd := new System.Random(seed);
|
||||
var rnd := if seed >= 0 then
|
||||
new System.Random(seed)
|
||||
else
|
||||
new System.Random;
|
||||
|
||||
// Перемешанные индексы
|
||||
var idx := (0..n-1).ToArray;
|
||||
idx := idx.OrderBy(i -> rnd.Next).ToArray;
|
||||
|
||||
var testSize := Round(n * testRatio);
|
||||
var rawSize := Round(n * testRatio);
|
||||
var testSize := Max(1, Min(n - 1, rawSize));
|
||||
var trainSize := n - testSize;
|
||||
|
||||
var X_train := new Matrix(trainSize, p);
|
||||
|
|
@ -151,70 +159,144 @@ begin
|
|||
if (k < 2) or (k > n) then
|
||||
ArgumentError(ER_K_INVALID, k, n);
|
||||
|
||||
var rnd := new System.Random(seed);
|
||||
var idx := (0..n-1).OrderBy(i -> rnd.Next).ToArray;
|
||||
var rnd := if seed >= 0 then
|
||||
new System.Random(seed)
|
||||
else
|
||||
new System.Random;
|
||||
|
||||
// --- 1. Создаём массив индексов 0..n-1
|
||||
var idx := new integer[n];
|
||||
for var i := 0 to n - 1 do
|
||||
idx[i] := i;
|
||||
|
||||
// --- 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;
|
||||
|
||||
var baseSize := n div k;
|
||||
var extra := n mod k;
|
||||
var start := 0;
|
||||
|
||||
// --- 3. Формируем фолды
|
||||
for var fold := 0 to k - 1 do
|
||||
begin
|
||||
var size := baseSize + (if fold < extra then 1 else 0);
|
||||
var size := baseSize + Ord(fold < extra);
|
||||
|
||||
var testIdx := idx.Skip(start).Take(size).ToArray;
|
||||
var trainIdx := idx.Take(start).Concat(idx.Skip(start + size)).ToArray;
|
||||
// --- 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);
|
||||
|
||||
yield (trainIdx, testIdx);
|
||||
|
||||
start += size;
|
||||
end;
|
||||
end;
|
||||
|
||||
static function Validation.StratifiedKFold(y: Vector; k: integer;
|
||||
seed: integer): sequence of (array of integer, array of integer);
|
||||
static function Validation.StratifiedKFold(y: Vector; k: integer; seed: integer):
|
||||
sequence of (array of integer, array of integer);
|
||||
begin
|
||||
var n := y.Length;
|
||||
if (k < 2) or (k > n) then
|
||||
ArgumentError(ER_K_INVALID_STRATIFIED, k, n);
|
||||
|
||||
var rnd := new System.Random(seed);
|
||||
var rnd :=
|
||||
if seed >= 0 then new System.Random(seed)
|
||||
else new System.Random;
|
||||
|
||||
var idx0 := (0..n-1)
|
||||
.Where(i -> y[i] = 0)
|
||||
.OrderBy(i -> rnd.Next)
|
||||
.ToArray;
|
||||
// 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);
|
||||
|
||||
var idx1 := (0..n-1)
|
||||
.Where(i -> y[i] = 1)
|
||||
.OrderBy(i -> rnd.Next)
|
||||
.ToArray;
|
||||
// Валидация: метки должны быть целыми (хотя тип double)
|
||||
if Abs(v - cls) > 1e-12 then
|
||||
ArgumentError(ER_STRATIFIED_LABELS_INVALID);
|
||||
|
||||
if idx0.Length + idx1.Length <> n then
|
||||
ArgumentError(ER_STRATIFIED_LABELS_INVALID);
|
||||
var lst: List<integer>;
|
||||
if classMap.TryGetValue(cls, lst) then
|
||||
lst.Add(i)
|
||||
else
|
||||
begin
|
||||
lst := new List<integer>;
|
||||
lst.Add(i);
|
||||
classMap.Add(cls, lst);
|
||||
end;
|
||||
end;
|
||||
|
||||
var base0 := idx0.Length div k;
|
||||
var extra0 := idx0.Length mod k;
|
||||
// 2) Подготавливаем контейнеры фолдов
|
||||
var folds := new List<integer>[k];
|
||||
for var f := 0 to k - 1 do
|
||||
folds[f] := new List<integer>;
|
||||
|
||||
var base1 := idx1.Length div k;
|
||||
var extra1 := idx1.Length mod k;
|
||||
// 3) Для каждого класса: shuffle + равномерно раскладываем по k фолдам
|
||||
foreach var pair in classMap do
|
||||
begin
|
||||
var indices := pair.Value;
|
||||
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;
|
||||
|
||||
for var fold := 0 to k - 1 do
|
||||
begin
|
||||
var size := baseSize + Ord(fold < extra);
|
||||
for var t := 0 to size - 1 do
|
||||
folds[fold].Add(indices[start + t]);
|
||||
start += size;
|
||||
end;
|
||||
end;
|
||||
|
||||
// 4) Возвращаем (trainIdx, testIdx) для каждого фолда
|
||||
for var fold := 0 to k - 1 do
|
||||
begin
|
||||
var start0 := fold * base0 + Min(fold, extra0);
|
||||
var size0 := base0 + (if fold < extra0 then 1 else 0);
|
||||
var testIdx := folds[fold].ToArray();
|
||||
|
||||
var start1 := fold * base1 + Min(fold, extra1);
|
||||
var size1 := base1 + (if fold < extra1 then 1 else 0);
|
||||
// boolean-mask вместо Contains -> O(n)
|
||||
var mask := new boolean[n];
|
||||
foreach var id in testIdx do
|
||||
mask[id] := true;
|
||||
|
||||
var testIdx :=
|
||||
idx0.Skip(start0).Take(size0)
|
||||
.Concat(idx1.Skip(start1).Take(size1))
|
||||
.ToArray;
|
||||
var trainSize := n - testIdx.Length;
|
||||
var trainIdx := new integer[trainSize];
|
||||
var p := 0;
|
||||
|
||||
var trainIdx :=
|
||||
(0..n-1)
|
||||
.Where(i -> not testIdx.Contains(i))
|
||||
.ToArray;
|
||||
for var i := 0 to n - 1 do
|
||||
if not mask[i] then
|
||||
begin
|
||||
trainIdx[p] := i;
|
||||
p += 1;
|
||||
end;
|
||||
|
||||
yield (trainIdx, testIdx);
|
||||
end;
|
||||
|
|
|
|||
Loading…
Reference in a new issue