ML - очередной аудит
This commit is contained in:
parent
2294ce5844
commit
be48d378be
|
|
@ -54,6 +54,7 @@ type
|
|||
implementation
|
||||
|
||||
uses MLExceptions;
|
||||
uses MLUtilsABC;
|
||||
|
||||
const
|
||||
ER_SCORE_FUNC_NULL =
|
||||
|
|
@ -90,9 +91,8 @@ begin
|
|||
|
||||
var resultVec := new Vector(p);
|
||||
|
||||
var baseSeed :=
|
||||
if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
var userProvidedSeed: boolean;
|
||||
var baseSeed := ResolveRandomSeed(seed, userProvidedSeed);
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
// Используется дисперсия генеральной совокупности (деление на n),
|
||||
// как принято в численных методах и алгоритмах машинного обучения.
|
||||
//
|
||||
// Исключение: Matrix.PCA использует (n-1) для ковариационной матрицы
|
||||
// согласно статистической политике MLABC (пункт 3).
|
||||
//
|
||||
// См. статистическую политику в модуле MLABC.
|
||||
// =============================================================
|
||||
|
||||
|
|
@ -339,7 +342,8 @@ const
|
|||
'Матрица вырождена или плохо обусловлена!!Matrix is singular or ill-conditioned';
|
||||
ER_EMPTY_MATRIX =
|
||||
'Матрица пуста!!Matrix is empty';
|
||||
|
||||
ER_EIGEN_NOT_CONVERGED =
|
||||
'EigenSymmetric: не сошлось за {0} итераций (off={1}, tol={2})!!EigenSymmetric did not converge in {0} iterations (off={1}, tol={2})';
|
||||
|
||||
type
|
||||
MLNotSPDException = class(MLException);
|
||||
|
|
@ -1276,10 +1280,11 @@ begin
|
|||
var M := Clone;
|
||||
var V := Matrix.Identity(n);
|
||||
|
||||
var off := 0.0;
|
||||
for var iter := 0 to maxIter - 1 do
|
||||
begin
|
||||
// --- Норма вне-диагонали
|
||||
var off := 0.0;
|
||||
off := 0.0;
|
||||
for var i := 0 to n - 1 do
|
||||
for var j := i + 1 to n - 1 do
|
||||
off += M[i, j] * M[i, j];
|
||||
|
|
@ -1358,6 +1363,12 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
var offNorm := Sqrt(off);
|
||||
|
||||
if offNorm >= tol then
|
||||
Error(ER_EIGEN_NOT_CONVERGED, maxIter, offNorm, tol);
|
||||
|
||||
|
||||
// --- Eigenvalues
|
||||
var values := new Vector(n);
|
||||
for var i := 0 to n - 1 do
|
||||
|
|
@ -1449,7 +1460,8 @@ begin
|
|||
end;
|
||||
|
||||
// --- Eigen
|
||||
var (values, V) := C.EigenSymmetric;
|
||||
var adaptiveIter := Max(100, n * n * 10);
|
||||
var (values, V) := C.EigenSymmetric(1e-12, adaptiveIter);
|
||||
|
||||
// --- Выбор k компонент
|
||||
var components := new Matrix(n, k);
|
||||
|
|
|
|||
|
|
@ -166,6 +166,10 @@ type
|
|||
function Fit(X: Matrix): IUnsupervisedTransformer;
|
||||
end;
|
||||
|
||||
IColumnExpander = interface
|
||||
function GetExpandedColumns(sourceColumn: string): array of string;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
end.
|
||||
|
|
@ -88,7 +88,9 @@ type
|
|||
/// • clusterStd — базовое стандартное отклонение
|
||||
/// • clusterStdVar — разброс std между кластерами (0 → одинаковые)
|
||||
/// • centerBox — диапазон генерации центров [-centerBox, centerBox]
|
||||
/// • classBalance — равномерность кластеров (0..1, 1 = равномерно)
|
||||
/// • classBalance — равномерность распределения объектов по кластерам (0..1]
|
||||
/// • classBalance = 1.0 — строго равномерное распределение (детерминированное, не зависит от seed)
|
||||
/// • classBalance < 1.0 — случайное распределение; чем меньше значение, тем выше дисбаланс в среднем
|
||||
/// • noisePoints — число шумовых точек (outliers)
|
||||
/// • shuffle — перемешивание
|
||||
/// • seed — генератор (seed < 0 → случайный)
|
||||
|
|
@ -748,7 +750,7 @@ begin
|
|||
// --- вероятности кластеров
|
||||
var probs := new real[centers];
|
||||
|
||||
if classBalance = 1 then
|
||||
if Abs(classBalance - 1.0) < 1e-12 then
|
||||
begin
|
||||
for var c := 0 to centers - 1 do
|
||||
probs[c] := 1.0 / centers;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const
|
|||
ER_XY_SIZE_MISMATCH =
|
||||
'Размеры X и y не совпадают: X={0}, y={1}!!X and y size mismatch: X={0}, y={1}';
|
||||
ER_FEATURE_COUNT_MISMATCH =
|
||||
'Число признаков не совпадает!!Feature count mismatch';
|
||||
'Число признаков не совпадает: {0} и {1}!!Feature count mismatch: {0}, {1}';
|
||||
ER_NAN_IN_X =
|
||||
'X содержит NaN (пропуски)!!X contains NaN';
|
||||
ER_NAN_IN_Y =
|
||||
|
|
|
|||
|
|
@ -2475,7 +2475,15 @@ type
|
|||
|
||||
{$endregion Utility functions}
|
||||
|
||||
type
|
||||
type
|
||||
/// Проверять ли входные данные моделей на NaN и Infinity.
|
||||
///
|
||||
/// По умолчанию: True — модели валидируют вход и выбрасывают исключение.
|
||||
///
|
||||
/// Установка в False отключает проверки ГЛОБАЛЬНО для всех моделей.
|
||||
/// Использовать только если данные гарантированно очищены.
|
||||
///
|
||||
/// При наличии NaN/Inf поведение моделей не определено
|
||||
MLConfig = static class
|
||||
public
|
||||
/// Проверять ли входные данные моделей на NaN, Inf
|
||||
|
|
@ -2624,9 +2632,9 @@ const
|
|||
ER_LEAFL2_INVALID =
|
||||
'leafL2 должно быть >= 0 ({0}).!!' +
|
||||
'leafL2 must be >= 0 ({0}).';
|
||||
ER_MIN_LEAF_GE_SPLIT =
|
||||
'minSamplesLeaf ({0}) должно быть меньше minSamplesSplit ({1}).!!' +
|
||||
'minSamplesLeaf ({0}) must be less than minSamplesSplit ({1}).';
|
||||
ER_MIN_LEAF_GT_SPLIT =
|
||||
'minSamplesSplit ({1}) должен быть > 2 * minSamplesLeaf ({0}).!!' +
|
||||
'minSamplesSplit ({1}) must be > 2 * minSamplesLeaf ({0}).';
|
||||
ER_OOB_NOT_ENABLED =
|
||||
'OOB score не включен для этой модели. Установите computeOOB = true в конструкторе.!!' +
|
||||
'OOB score is not enabled for this model. Set computeOOB = true in the constructor.';
|
||||
|
|
@ -3252,10 +3260,6 @@ begin
|
|||
|
||||
// --- init
|
||||
fW := new Matrix(p, fClassCount);
|
||||
var scale := 0.01;
|
||||
for var j := 0 to p - 1 do
|
||||
for var k := 0 to fClassCount - 1 do
|
||||
fW.Data[j,k] := (Random - 0.5) * 2 * scale;
|
||||
|
||||
fIntercept := new Vector(fClassCount);
|
||||
|
||||
|
|
@ -3718,7 +3722,7 @@ begin
|
|||
if fClassLabels = nil then
|
||||
ArgumentError(ER_CLASSES_NOT_AVAILABLE);
|
||||
|
||||
Result := fClassLabels;
|
||||
Result := Copy(fClassLabels);
|
||||
end;
|
||||
|
||||
function GiniCriterion.Impurity(y: Vector; indices: array of integer): real;
|
||||
|
|
@ -4375,11 +4379,9 @@ begin
|
|||
if minSamplesLeaf < 1 then
|
||||
ArgumentOutOfRangeError(ER_MIN_SAMPLES_LEAF_INVALID, minSamplesLeaf);
|
||||
|
||||
if 2 * minSamplesLeaf >= minSamplesSplit then
|
||||
ArgumentOutOfRangeError(
|
||||
ER_MIN_LEAF_GE_SPLIT,
|
||||
minSamplesLeaf, minSamplesSplit
|
||||
);
|
||||
|
||||
if minSamplesSplit < 2 * minSamplesLeaf then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GT_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
|
||||
// --- parameters valid → assign
|
||||
|
||||
|
|
@ -4694,8 +4696,8 @@ begin
|
|||
if minSamplesLeaf < 1 then
|
||||
ArgumentOutOfRangeError(ER_MIN_SAMPLES_LEAF_INVALID, minSamplesLeaf);
|
||||
|
||||
if minSamplesLeaf >= minSamplesSplit then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GE_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
if minSamplesSplit < 2 * minSamplesLeaf then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GT_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
|
||||
if maxFeatures < 0 then
|
||||
ArgumentOutOfRangeError(ER_MAX_FEATURES_INVALID);
|
||||
|
|
@ -4753,22 +4755,17 @@ begin
|
|||
var classes: array of integer;
|
||||
var yEncArr := EncodeLabelsInt(yInt, classes);
|
||||
|
||||
if fCriterion = nil then
|
||||
fCriterion := new GiniCriterion(classes.Length);
|
||||
|
||||
if classes.Length < 2 then
|
||||
ArgumentError(ER_NEED_AT_LEAST_TWO_CLASSES);
|
||||
|
||||
fIndexToClass := classes;
|
||||
|
||||
// --- criterion
|
||||
if fCriterion = nil then
|
||||
fCriterion := new GiniCriterion(classes.Length);
|
||||
|
||||
fIndexToClass := classes;
|
||||
|
||||
// --- encoded vector
|
||||
var yEncoded := new Vector(m);
|
||||
for var i := 0 to m - 1 do
|
||||
yEncoded[i] := yEncArr[i];
|
||||
var yEncoded := new Vector(yEncArr);
|
||||
|
||||
// --- Core
|
||||
fCore := new DecisionTreeCore(
|
||||
|
|
@ -4854,7 +4851,7 @@ begin
|
|||
if fClassLabels = nil then
|
||||
ArgumentError(ER_CLASSES_NOT_AVAILABLE);
|
||||
|
||||
Result := fClassLabels;
|
||||
Result := Copy(fClassLabels);
|
||||
end;
|
||||
|
||||
// DecisionTreeRegressor
|
||||
|
|
@ -5003,9 +5000,7 @@ begin
|
|||
CheckXForPredict(X);
|
||||
|
||||
if X.ColCount <> fFeatureImportances.Length then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,
|
||||
X.ColCount,
|
||||
fFeatureImportances.Length);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH, X.ColCount, fFeatureImportances.Length);
|
||||
|
||||
var n := X.RowCount;
|
||||
Result := new Vector(n);
|
||||
|
|
@ -5066,8 +5061,8 @@ begin
|
|||
if minSamplesLeaf < 1 then
|
||||
ArgumentOutOfRangeError(ER_MIN_SAMPLES_LEAF_INVALID, minSamplesLeaf);
|
||||
|
||||
if minSamplesLeaf >= minSamplesSplit then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GE_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
if minSamplesSplit < 2 * minSamplesLeaf then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GT_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
|
||||
fNTrees := nTrees;
|
||||
fMaxDepth := maxDepth;
|
||||
|
|
@ -5680,7 +5675,7 @@ begin
|
|||
if fClassLabels = nil then
|
||||
ArgumentError(ER_CLASSES_NOT_AVAILABLE);
|
||||
|
||||
Result := fClassLabels;
|
||||
Result := Copy(fClassLabels);
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
|
|
@ -5764,6 +5759,9 @@ begin
|
|||
if minSamplesLeaf < 1 then
|
||||
ArgumentOutOfRangeError(ER_MIN_SAMPLES_LEAF_INVALID, minSamplesLeaf);
|
||||
|
||||
if minSamplesSplit < 2 * minSamplesLeaf then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GT_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
|
||||
if maxDepth > MAX_ALLOWED_TREE_DEPTH then
|
||||
ArgumentOutOfRangeError(ER_MAX_DEPTH_TOO_LARGE, maxDepth);
|
||||
|
||||
|
|
@ -6026,10 +6024,10 @@ begin
|
|||
end;
|
||||
|
||||
if XVal.RowCount <> yVal.Length then
|
||||
DimensionError(ER_XY_SIZE_MISMATCH);
|
||||
DimensionError(ER_XY_SIZE_MISMATCH,XVal.RowCount,yVal.Length);
|
||||
|
||||
if XVal.ColCount <> XTrain.ColCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,XVal.ColCount,XTrain.ColCount);
|
||||
end;
|
||||
|
||||
// --- OOB checks ---
|
||||
|
|
@ -6241,7 +6239,7 @@ begin
|
|||
CheckXForPredict(X);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var yPred := new Vector(n);
|
||||
|
|
@ -6473,7 +6471,7 @@ begin
|
|||
ArgumentError(ER_EMPTY_DATASET);
|
||||
|
||||
if XTrain.RowCount <> yTrain.Length then
|
||||
DimensionError(ER_XY_SIZE_MISMATCH);
|
||||
DimensionError(ER_XY_SIZE_MISMATCH,XTrain.RowCount,yTrain.Length);
|
||||
|
||||
if useValidation then
|
||||
begin
|
||||
|
|
@ -6490,10 +6488,10 @@ begin
|
|||
end;
|
||||
|
||||
if XVal.RowCount <> yVal.Length then
|
||||
DimensionError(ER_XY_SIZE_MISMATCH);
|
||||
DimensionError(ER_XY_SIZE_MISMATCH,XVal.RowCount,yVal.Length);
|
||||
|
||||
if XVal.ColCount <> XTrain.ColCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,XVal.ColCount,XTrain.ColCount);
|
||||
end;
|
||||
|
||||
// --- reset state
|
||||
|
|
@ -7007,6 +7005,9 @@ begin
|
|||
if minSamplesLeaf < 1 then
|
||||
ArgumentOutOfRangeError(ER_MIN_SAMPLES_LEAF_INVALID, minSamplesLeaf);
|
||||
|
||||
if minSamplesSplit < 2 * minSamplesLeaf then
|
||||
ArgumentOutOfRangeError(ER_MIN_LEAF_GT_SPLIT, minSamplesLeaf, minSamplesSplit);
|
||||
|
||||
if (subsample <= 0) or (subsample > 1) then
|
||||
ArgumentOutOfRangeError(ER_SUBSAMPLE_OUT_OF_RANGE, subsample);
|
||||
|
||||
|
|
@ -7050,7 +7051,7 @@ begin
|
|||
CheckXForPredict(X);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var nSamples := X.RowCount;
|
||||
var classCount := fClassCount;
|
||||
|
|
@ -7127,7 +7128,7 @@ begin
|
|||
CheckXForPredict(X);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var total := fEstimators.Count;
|
||||
|
||||
|
|
@ -7318,7 +7319,7 @@ begin
|
|||
if fClassLabels = nil then
|
||||
ArgumentError(ER_CLASSES_NOT_AVAILABLE);
|
||||
|
||||
Result := fClassLabels;
|
||||
Result := Copy(fClassLabels);
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
|
|
@ -7344,7 +7345,7 @@ begin
|
|||
CheckXForPredict(X);
|
||||
|
||||
if X.ColCount <> fXTrain.ColCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fXTrain.ColCount);
|
||||
end;
|
||||
|
||||
function KNNBase.SquaredL2(trainRow: integer; XTest: Matrix; testRow: integer): double;
|
||||
|
|
@ -7532,7 +7533,7 @@ begin
|
|||
ArgumentNullError(ER_Y_NULL);
|
||||
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_XY_SIZE_MISMATCH);
|
||||
DimensionError(ER_XY_SIZE_MISMATCH,X.RowCount,y.Length);
|
||||
|
||||
if X.RowCount = 0 then
|
||||
ArgumentError(ER_EMPTY_DATASET);
|
||||
|
|
@ -7900,7 +7901,7 @@ begin
|
|||
if fClassLabels = nil then
|
||||
ArgumentError(ER_CLASSES_NOT_AVAILABLE);
|
||||
|
||||
Result := fClassLabels;
|
||||
Result := Copy(fClassLabels);
|
||||
end;
|
||||
|
||||
|
||||
|
|
@ -7922,7 +7923,7 @@ begin
|
|||
ArgumentNullError(ER_Y_NULL);
|
||||
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_XY_SIZE_MISMATCH);
|
||||
DimensionError(ER_XY_SIZE_MISMATCH,X.RowCount,y.Length);
|
||||
|
||||
if X.RowCount = 0 then
|
||||
ArgumentError(ER_EMPTY_DATASET);
|
||||
|
|
@ -8076,13 +8077,8 @@ begin
|
|||
fNInit := nInit;
|
||||
|
||||
// --- seed (единый стиль)
|
||||
fRandomSeed := seed;
|
||||
fUserProvidedSeed := seed >= 0;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
fRng := new System.Random(seed)
|
||||
else
|
||||
fRng := new System.Random;
|
||||
fRandomSeed := ResolveRandomSeed(seed, fUserProvidedSeed);
|
||||
fRng := new System.Random(fRandomSeed);
|
||||
|
||||
// --- state
|
||||
fFitted := False;
|
||||
|
|
@ -8636,7 +8632,7 @@ begin
|
|||
ArgumentNullError(ER_Y_NULL);
|
||||
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_XY_SIZE_MISMATCH);
|
||||
DimensionError(ER_XY_SIZE_MISMATCH,X.RowCount,y.Length);
|
||||
|
||||
if X.RowCount = 0 then
|
||||
ArgumentError(ER_EMPTY_DATASET);
|
||||
|
|
@ -8962,11 +8958,6 @@ begin
|
|||
fMean := X.ColumnMeans;
|
||||
fStd := X.ColumnStd;
|
||||
|
||||
// защита от нулевой дисперсии
|
||||
for var j := 0 to fStd.Length - 1 do
|
||||
if Abs(fStd[j]) < 1e-12 then
|
||||
fStd[j] := 1.0;
|
||||
|
||||
fFitted := true;
|
||||
Result := Self;
|
||||
end;
|
||||
|
|
@ -8980,7 +8971,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
|
@ -8988,11 +8979,11 @@ begin
|
|||
Result := new Matrix(n, p);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
for var j := 0 to p - 1 do
|
||||
if fStd[j] <> 0 then
|
||||
Result[i,j] := (X[i,j] - fMean[j]) / fStd[j]
|
||||
else
|
||||
Result[i,j] := 0.0;
|
||||
for var j := 0 to p - 1 do
|
||||
if Abs(fStd[j]) < 1e-12 then
|
||||
Result[i,j] := 0.0
|
||||
else
|
||||
Result[i,j] := (X[i,j] - fMean[j]) / fStd[j];
|
||||
end;
|
||||
|
||||
function StandardScaler.InverseTransform(X: Matrix): Matrix;
|
||||
|
|
@ -9004,7 +8995,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
|
@ -9074,7 +9065,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
|
@ -9113,7 +9104,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
|
@ -9198,7 +9189,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
|
@ -9275,7 +9266,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
if fSelected = nil then
|
||||
Error(ER_MODEL_NOT_INITIALIZED);
|
||||
|
|
@ -9602,7 +9593,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
if fSelected = nil then
|
||||
Error(ER_MODEL_NOT_INITIALIZED);
|
||||
|
|
@ -9671,7 +9662,7 @@ begin
|
|||
ArgumentNullError(ER_X_NULL);
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH);
|
||||
DimensionError(ER_FEATURE_COUNT_MISMATCH,X.ColCount,fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
|
|
|||
|
|
@ -334,16 +334,35 @@ begin
|
|||
|
||||
foreach var f in fFeatures do
|
||||
begin
|
||||
if current.HasColumn(f) then
|
||||
var expanded := false;
|
||||
|
||||
// ищем expander-ы В ПРЯМОМ порядке pipeline
|
||||
for var i := 0 to fDataSteps.Count - 1 do
|
||||
begin
|
||||
feats.Add(f);
|
||||
continue;
|
||||
var expander := fDataSteps[i] as IColumnExpander;
|
||||
if expander = nil then
|
||||
continue;
|
||||
|
||||
var cols := expander.GetExpandedColumns(f);
|
||||
if (cols <> nil) and (cols.Length > 0) then
|
||||
begin
|
||||
foreach var c in cols do
|
||||
if current.HasColumn(c) then
|
||||
if not feats.Contains(c) then
|
||||
feats.Add(c);
|
||||
|
||||
expanded := true;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
|
||||
foreach var c in current.Schema.ColumnNames do
|
||||
if c.StartsWith(f + '_') then
|
||||
if not feats.Contains(c) then
|
||||
feats.Add(c);
|
||||
if expanded then
|
||||
continue;
|
||||
|
||||
// fallback: обычный столбец
|
||||
if current.HasColumn(f) then
|
||||
if not feats.Contains(f) then
|
||||
feats.Add(f);
|
||||
end;
|
||||
|
||||
if feats.Count = 0 then
|
||||
|
|
|
|||
|
|
@ -1681,10 +1681,10 @@ end;
|
|||
static function Metrics.AdjustedRandIndex(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
if yTrue = nil then
|
||||
ArgumentNullError(ER_Y_NULL);
|
||||
ArgumentNullError(ER_ARG_NULL, 'yTrue');
|
||||
|
||||
if yPred = nil then
|
||||
ArgumentNullError(ER_Y_NULL);
|
||||
ArgumentNullError(ER_ARG_NULL, 'yPred');
|
||||
|
||||
var n := yTrue.Length;
|
||||
if n <> yPred.Length then
|
||||
|
|
@ -1774,56 +1774,6 @@ begin
|
|||
Result := (sumNij - expected) / denom;
|
||||
end;
|
||||
|
||||
{static function Metrics.AdjustedRandIndex(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
if yTrue.Length <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
|
||||
var n := yTrue.Length;
|
||||
|
||||
if n < 2 then
|
||||
Result := 1
|
||||
else
|
||||
begin
|
||||
var tp := 0;
|
||||
var tn := 0;
|
||||
var fp := 0;
|
||||
var fn := 0;
|
||||
|
||||
for var i := 0 to n-2 do
|
||||
for var j := i+1 to n-1 do
|
||||
begin
|
||||
var sameTrue := yTrue[i] = yTrue[j];
|
||||
var samePred := yPred[i] = yPred[j];
|
||||
|
||||
if sameTrue and samePred then
|
||||
tp += 1
|
||||
else if (not sameTrue) and (not samePred) then
|
||||
tn += 1
|
||||
else if (not sameTrue) and samePred then
|
||||
fp += 1
|
||||
else
|
||||
fn += 1;
|
||||
end;
|
||||
|
||||
var total := tp + tn + fp + fn;
|
||||
|
||||
var ri := (tp + tn) / total;
|
||||
|
||||
var a := tp + fp;
|
||||
var b := tp + fn;
|
||||
var c := fn + tn;
|
||||
var d := fp + tn;
|
||||
|
||||
var expected := ((a*b) + (c*d)) / (total*total);
|
||||
|
||||
if 1 - expected = 0 then
|
||||
Result := 0
|
||||
else
|
||||
Result := (ri - expected) / (1 - expected);
|
||||
end;
|
||||
end;}
|
||||
|
||||
function R(s: string; w: integer): string;
|
||||
begin
|
||||
Result := s.PadLeft(w);
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ type
|
|||
/// Категории фиксируются при Fit
|
||||
/// Неизвестные категории кодируются нулями
|
||||
/// Пропущенные значения (NA) кодируются нулями
|
||||
OneHotEncoder = class(IPreprocessor, IColumnBoundStep)
|
||||
OneHotEncoder = class(IPreprocessor, IColumnBoundStep, IColumnExpander)
|
||||
private
|
||||
col: string;
|
||||
categories: array of string;
|
||||
|
|
@ -115,6 +115,8 @@ type
|
|||
function ToString: string; override;
|
||||
|
||||
property ColumnName: string read col;
|
||||
|
||||
function GetExpandedColumns(sourceColumn: string): array of string;
|
||||
|
||||
/// Создаёт копию препроцессора с той же конфигурацией.
|
||||
///
|
||||
|
|
@ -251,7 +253,13 @@ const
|
|||
ER_IMPUTER_STRATEGY_NOT_SUPPORTED =
|
||||
'Стратегия импутации {0} не поддерживается!!Imputation strategy {0} is not supported';
|
||||
ER_UNSUPPORTED_IMPUTE_STRATEGY =
|
||||
'Неподдерживаемая стратегия заполнения: {0}!!Unsupported impute strategy: {0}';
|
||||
'Неподдерживаемая стратегия заполнения: {0}!!Unsupported impute strategy: {0}';
|
||||
ER_ONEHOT_NAME_EQUALS_SOURCE =
|
||||
'Сгенерированная колонка совпадает с исходной: {0}!!Generated column equals source column: {0}';
|
||||
ER_ONEHOT_COLUMN_COLLISION =
|
||||
'Конфликт имён колонок: {0}!!Column name collision: {0}';
|
||||
ER_ONEHOT_DUPLICATE_COLUMN =
|
||||
'Дублирующаяся сгенерированная колонка: {0}!!Duplicate generated column: {0}';
|
||||
|
||||
|
||||
//-----------------------------
|
||||
|
|
@ -407,6 +415,29 @@ begin
|
|||
Error(ER_ONEHOT_EMPTY_COLUMN, col);
|
||||
|
||||
categories := values.ToArray;
|
||||
|
||||
// --- проверка коллизий имён колонок
|
||||
var used := new HashSet<string>;
|
||||
|
||||
for var i := 0 to categories.Length - 1 do
|
||||
begin
|
||||
var newName := col + '_' + categories[i];
|
||||
|
||||
// 1. совпадение с исходным именем
|
||||
if newName = col then
|
||||
Error(ER_ONEHOT_NAME_EQUALS_SOURCE, newName);
|
||||
|
||||
// 2. коллизия с существующими колонками DataFrame
|
||||
if df.HasColumn(newName) then
|
||||
Error(ER_ONEHOT_COLUMN_COLLISION, newName);
|
||||
|
||||
// 3. дубликаты среди сгенерированных колонок
|
||||
if used.Contains(newName) then
|
||||
Error(ER_ONEHOT_DUPLICATE_COLUMN, newName);
|
||||
|
||||
used.Add(newName);
|
||||
end;
|
||||
|
||||
fitted := true;
|
||||
Result := Self;
|
||||
end;
|
||||
|
|
@ -465,6 +496,21 @@ begin
|
|||
Result := Transform(df);
|
||||
end;
|
||||
|
||||
function OneHotEncoder.GetExpandedColumns(sourceColumn: string): array of string;
|
||||
begin
|
||||
if not fitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
if sourceColumn <> col then
|
||||
exit(nil);
|
||||
|
||||
var res := new string[categories.Length];
|
||||
for var i := 0 to categories.Length - 1 do
|
||||
res[i] := col + '_' + categories[i];
|
||||
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
function OneHotEncoder.ToString: string;
|
||||
begin
|
||||
Result := 'OneHotEncoder(column=' + col + ')';
|
||||
|
|
@ -615,13 +661,15 @@ begin
|
|||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_IMPUTER_COLUMN_NOT_NUMERIC, name);
|
||||
|
||||
var capturedIdx := idx;
|
||||
|
||||
case strategy of
|
||||
isMean:
|
||||
begin
|
||||
var m := means[i];
|
||||
res := res.ReplaceColumnFloat(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else m)
|
||||
c -> (if c.IsValid(capturedIdx) then c.Float(capturedIdx) else m)
|
||||
);
|
||||
end;
|
||||
|
||||
|
|
@ -665,7 +713,7 @@ begin
|
|||
|
||||
res := res.ReplaceColumnFloat(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else r)
|
||||
c -> (if c.IsValid(capturedIdx) then c.Float(capturedIdx) else r)
|
||||
);
|
||||
end;
|
||||
end;
|
||||
|
|
@ -675,7 +723,7 @@ begin
|
|||
var m := medians[i];
|
||||
res := res.ReplaceColumnFloat(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else m)
|
||||
c -> (if c.IsValid(capturedIdx) then c.Float(capturedIdx) else m)
|
||||
);
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,24 @@ interface
|
|||
uses LinearAlgebraML, MLCoreABC;
|
||||
|
||||
type
|
||||
/// Методы для разбиения данных и оценки моделей.
|
||||
///
|
||||
/// Содержит утилиты для:
|
||||
/// • разделения выборки на обучающую и тестовую (TrainTestSplit)
|
||||
/// • k-fold кросс-валидации (KFold)
|
||||
/// • стратифицированной кросс-валидации (StratifiedKFold)
|
||||
/// • оценки моделей через кросс-валидацию (CrossValidate, StratifiedCrossValidate)
|
||||
///
|
||||
/// Методы возвращают индексы или подвыборки без изменения исходных данных.
|
||||
///
|
||||
/// • KFold — простое разбиение без учёта распределения классов
|
||||
/// • StratifiedKFold — сохраняет пропорции классов в каждом fold (для классификации)
|
||||
///
|
||||
/// Для стратифицированных методов требуется:
|
||||
/// • целочисленные метки классов
|
||||
/// • число объектов каждого класса ≥ числа фолдов
|
||||
///
|
||||
/// Все методы используют генератор случайных чисел (seed) для воспроизводимости
|
||||
Validation = static class
|
||||
private
|
||||
static function CrossValidateCore(model: ISupervisedModel;
|
||||
|
|
@ -93,6 +111,7 @@ type
|
|||
implementation
|
||||
|
||||
uses MLExceptions;
|
||||
uses MLUtilsABC;
|
||||
|
||||
const
|
||||
ER_DIM_MISMATCH_TRAIN_TEST =
|
||||
|
|
@ -114,7 +133,9 @@ const
|
|||
'At least 2 samples are required for {0}';
|
||||
ER_STRATIFIED_CLASS_TOO_SMALL =
|
||||
'Класс {0} содержит {1} объектов, что меньше числа фолдов ({2})!!Class {0} has {1} samples, which is less than the number of folds ({2})';
|
||||
|
||||
ER_STRATIFIED_K_TOO_LARGE =
|
||||
'Stratified CV: число фолдов ({0}) превышает минимальный размер класса ({1})!!Stratified CV: number of folds ({0}) exceeds smallest class size ({1})';
|
||||
|
||||
//-----------------------------
|
||||
// Validation
|
||||
//-----------------------------
|
||||
|
|
@ -263,12 +284,8 @@ begin
|
|||
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);
|
||||
System.Array.Copy(idx, 0, trainIdx, 0, start);
|
||||
System.Array.Copy(idx, start + size, trainIdx, start, n - (start + size));
|
||||
|
||||
yield (trainIdx, testIdx);
|
||||
|
||||
|
|
@ -442,6 +459,17 @@ begin
|
|||
|
||||
if (k < 2) or (k > X.RowCount) then
|
||||
ArgumentError(ER_K_INVALID_STRATIFIED, k, X.RowCount);
|
||||
|
||||
var labels := y.ToIntArray;
|
||||
var classCounts := labels.EachCount;
|
||||
|
||||
var minCount := integer.MaxValue;
|
||||
foreach var pair in classCounts do
|
||||
if pair.Value < minCount then
|
||||
minCount := pair.Value;
|
||||
|
||||
if k > minCount then
|
||||
ArgumentError(ER_STRATIFIED_K_TOO_LARGE, k, minCount);
|
||||
|
||||
var baseSeed :=
|
||||
if seed >= 0 then seed
|
||||
|
|
|
|||
Loading…
Reference in a new issue