ML - правки аудита 2
This commit is contained in:
parent
5c8f2b0caa
commit
f61cd7770c
|
|
@ -62,6 +62,8 @@ const
|
|||
'Неподдерживаемый тип столбца "{0}" для EncodeLabels!!Unsupported column type "{0}" for EncodeLabels';
|
||||
ER_UNKNOWN_CLASS_IN_TRANSFORM =
|
||||
'Неизвестное значение класса "{0}" при преобразовании меток!!Unknown class value "{0}" in TransformLabels';
|
||||
ER_LABEL_INDEX_OUT_OF_RANGE =
|
||||
'Индекс метки {0} вне диапазона [0, {1})!!Label index {0} is out of range [0, {1})';
|
||||
|
||||
function LabelsToInts(y: Vector): array of integer;
|
||||
begin
|
||||
|
|
@ -115,7 +117,7 @@ begin
|
|||
var lbl := labels[i];
|
||||
|
||||
if not map.ContainsKey(lbl) then
|
||||
Error('Unknown class in test');
|
||||
Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, lbl);
|
||||
|
||||
res[i] := map[lbl];
|
||||
end;
|
||||
|
|
@ -363,7 +365,7 @@ begin
|
|||
var lbl := data[i].ToString;
|
||||
|
||||
if not map.ContainsKey(lbl) then
|
||||
Error('Unknown class in TransformLabels');
|
||||
Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, lbl);
|
||||
|
||||
res[i] := map[lbl];
|
||||
end;
|
||||
|
|
@ -453,8 +455,15 @@ function DecodeLabels(y: array of integer; classes: array of string): array of s
|
|||
begin
|
||||
var res := new string[y.Length];
|
||||
|
||||
for var i := 0 to y.Length-1 do
|
||||
res[i] := classes[y[i]];
|
||||
for var i := 0 to y.Length - 1 do
|
||||
begin
|
||||
var idx := y[i];
|
||||
|
||||
if (idx < 0) or (idx >= classes.Length) then
|
||||
Error(ER_LABEL_INDEX_OUT_OF_RANGE, idx, classes.Length);
|
||||
|
||||
res[i] := classes[idx];
|
||||
end;
|
||||
|
||||
Result := res;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -504,8 +504,6 @@ const
|
|||
'Тип соединения не реализован!!Join kind not implemented';
|
||||
ER_JOIN_KEYS_LENGTH_MISMATCH =
|
||||
'leftKeys и rightKeys должны иметь одинаковую длину!!leftKeys and rightKeys must have the same length';
|
||||
ER_COLUMN_NOT_FOUND =
|
||||
'Столбец "{0}" не найден!!Column not found: {0}';
|
||||
ER_ADD_COLUMN_ROW_MISMATCH =
|
||||
'Несоответствие числа строк при добавлении столбца!!Row count mismatch when adding column';
|
||||
ER_COLUMN_VALID_LENGTH_MISMATCH =
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ type
|
|||
/// Вектор важностей признаков длины nFeatures.
|
||||
/// Чем больше значение, тем сильнее признак влияет на качество модели
|
||||
static function PermutationImportance(model: IPredictiveModel; X: Matrix; y: Vector;
|
||||
scoreFunc: (Vector, Vector) -> real; seed: integer := 0): Vector;
|
||||
scoreFunc: (Vector, Vector) -> real; nRepeats: integer := 5; seed: integer := 0): Vector;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -53,10 +53,20 @@ implementation
|
|||
uses MLExceptions;
|
||||
|
||||
const
|
||||
ER_SCORE_FUNC_NULL = 'scoreFunc не может быть nil!!scoreFunc cannot be nil';
|
||||
ER_SCORE_FUNC_NULL =
|
||||
'scoreFunc не может быть nil!!scoreFunc cannot be nil';
|
||||
ER_ARG_OUT_OF_RANGE =
|
||||
'Аргумент {0} имеет недопустимое значение {1}!!Argument {0} has invalid value {1}';
|
||||
|
||||
static function Inspection.PermutationImportance(model: IPredictiveModel; X: Matrix; y: Vector;
|
||||
scoreFunc: (Vector, Vector) -> real; seed: integer): Vector;
|
||||
|
||||
static function Inspection.PermutationImportance(
|
||||
model: IPredictiveModel;
|
||||
X: Matrix;
|
||||
y: Vector;
|
||||
scoreFunc: (Vector, Vector) -> real;
|
||||
nRepeats: integer;
|
||||
seed: integer
|
||||
): Vector;
|
||||
begin
|
||||
if model = nil then
|
||||
ArgumentNullError(ER_MODEL_NULL);
|
||||
|
|
@ -67,6 +77,9 @@ begin
|
|||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
if nRepeats < 1 then
|
||||
ArgumentOutOfRangeError(ER_ARG_OUT_OF_RANGE, 'nRepeats', nRepeats);
|
||||
|
||||
var baselinePred := model.Predict(X);
|
||||
var baselineScore := scoreFunc(y, baselinePred);
|
||||
|
||||
|
|
@ -74,25 +87,38 @@ begin
|
|||
var p := X.ColCount;
|
||||
|
||||
var resultVec := new Vector(p);
|
||||
var rng := new System.Random(seed);
|
||||
|
||||
for var j := 0 to p-1 do
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var Xperm := X.Clone;
|
||||
var acc := 0.0;
|
||||
|
||||
// Fisher–Yates shuffle столбца j (детерминированно через seed)
|
||||
for var i := n-1 downto 1 do
|
||||
for var r := 0 to nRepeats - 1 do
|
||||
begin
|
||||
var k := rng.Next(i+1);
|
||||
var tmp := Xperm[i,j];
|
||||
Xperm[i,j] := Xperm[k,j];
|
||||
Xperm[k,j] := tmp;
|
||||
var Xperm := X.Clone;
|
||||
|
||||
// --- seed для каждого (признак, повтор)
|
||||
var runSeed :=
|
||||
if seed >= 0 then seed + j * 100000 + r
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
var rnd := new System.Random(runSeed);
|
||||
|
||||
// --- shuffle столбца j
|
||||
for var i := n - 1 downto 1 do
|
||||
begin
|
||||
var k := rnd.Next(i + 1);
|
||||
var tmp := Xperm[i,j];
|
||||
Xperm[i,j] := Xperm[k,j];
|
||||
Xperm[k,j] := tmp;
|
||||
end;
|
||||
|
||||
var permPred := model.Predict(Xperm);
|
||||
var permScore := scoreFunc(y, permPred);
|
||||
|
||||
acc += (baselineScore - permScore);
|
||||
end;
|
||||
|
||||
var permPred := model.Predict(Xperm);
|
||||
var permScore := scoreFunc(y, permPred);
|
||||
|
||||
resultVec[j] := baselineScore - permScore;
|
||||
resultVec[j] := acc / nRepeats;
|
||||
end;
|
||||
|
||||
Result := resultVec;
|
||||
|
|
|
|||
|
|
@ -152,9 +152,6 @@ type
|
|||
static function operator *(A: Matrix; x: Vector): Vector;
|
||||
static function operator *(A, B: Matrix): Matrix;
|
||||
|
||||
//static function operator *(alpha: real; A: Matrix): Matrix;
|
||||
//static function operator *(A: Matrix; alpha: real): Matrix;
|
||||
|
||||
// ---------- in-place operators ----------
|
||||
static function operator +=(A, B: Matrix): Matrix;
|
||||
static function operator -=(A, B: Matrix): Matrix;
|
||||
|
|
|
|||
|
|
@ -18,20 +18,20 @@ type
|
|||
/// Предоставляет удобные методы для получения матриц признаков
|
||||
/// и целевых значений для обучения моделей
|
||||
Dataset = class
|
||||
private
|
||||
function ValueLabel(feature, value: string): string;
|
||||
function CloneMeta(df: DataFrame): Dataset;
|
||||
private
|
||||
function ValueLabel(feature, value: string): string;
|
||||
function CloneMeta(df: DataFrame): Dataset;
|
||||
public
|
||||
Name: string;
|
||||
Data: DataFrame;
|
||||
Features: array of string;
|
||||
Target: string;
|
||||
Task: TaskType;
|
||||
|
||||
FeatureLabels: Dictionary<string,string>;
|
||||
ValueLabels: Dictionary<string,Dictionary<string,string>>;
|
||||
Description: string;
|
||||
|
||||
auto property Name: string;
|
||||
auto property Data: DataFrame;
|
||||
auto property Features: array of string;
|
||||
auto property Target: string;
|
||||
auto property Task: TaskType;
|
||||
|
||||
/// Возвращает true, если датасет относится к задаче с учителем
|
||||
/// (classification или regression).
|
||||
function IsSupervised: boolean;
|
||||
|
|
|
|||
|
|
@ -1271,6 +1271,10 @@ type
|
|||
fIterations: integer;
|
||||
fHasConverged: boolean;
|
||||
|
||||
fRandomSeed: integer;
|
||||
fUserProvidedSeed: boolean;
|
||||
fRng: System.Random;
|
||||
|
||||
function RunSingle(X: Matrix; rnd: System.Random): (Matrix, real, integer, boolean);
|
||||
public
|
||||
/// Создаёт модель KMeans.
|
||||
|
|
@ -1361,9 +1365,6 @@ type
|
|||
/// Обучает модель на матрице признаков.
|
||||
function Fit(X: Matrix): IUnsupervisedModel;
|
||||
|
||||
/// Возвращает метки кластеров.
|
||||
function Predict(X: Matrix): Vector;
|
||||
|
||||
/// Возвращает метки кластеров.
|
||||
function PredictLabels(X: Matrix): array of integer;
|
||||
|
||||
|
|
@ -3042,16 +3043,24 @@ begin
|
|||
dest.fMaxFeatures := fMaxFeatures;
|
||||
|
||||
dest.fUserProvidedSeed := fUserProvidedSeed;
|
||||
dest.fRng := new System.Random(fRandomSeed);
|
||||
|
||||
if fUserProvidedSeed then
|
||||
dest.fRng := new System.Random(fRandomSeed)
|
||||
else
|
||||
dest.fRng := new System.Random;
|
||||
|
||||
// MUST be stateless
|
||||
if fCriterion <> nil then
|
||||
dest.fCriterion := fCriterion; // можно так, если критерий stateless
|
||||
dest.fCriterion := fCriterion;
|
||||
|
||||
if fFeatureImportances <> nil then
|
||||
dest.fFeatureImportances := fFeatureImportances.Clone;
|
||||
|
||||
if fRoot <> nil then
|
||||
dest.fRoot := fRoot.Clone;
|
||||
|
||||
if fRowIndices <> nil then
|
||||
dest.fRowIndices := Copy(fRowIndices);
|
||||
end;
|
||||
|
||||
function DecisionTreeBase.GetFeatureSubset(nFeatures: integer): array of integer;
|
||||
|
|
@ -3613,11 +3622,8 @@ begin
|
|||
fRandomSeed
|
||||
);
|
||||
|
||||
// --- базовое состояние (глубокое копирование дерева!)
|
||||
CopyBaseState(m);
|
||||
|
||||
m.fMaxFeatures := fMaxFeatures;
|
||||
|
||||
// --- классы
|
||||
m.fClassCount := fClassCount;
|
||||
|
||||
|
|
@ -3825,8 +3831,6 @@ begin
|
|||
|
||||
CopyBaseState(m);
|
||||
|
||||
m.fLeafL2 := fLeafL2;
|
||||
|
||||
Result := m;
|
||||
end;
|
||||
|
||||
|
|
@ -4099,7 +4103,15 @@ begin
|
|||
fRandomSeed
|
||||
);
|
||||
|
||||
// --- seed ---
|
||||
rf.fRandomSeed := fRandomSeed;
|
||||
rf.fUserProvidedSeed := fUserProvidedSeed;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
rf.fRng := new System.Random(fRandomSeed)
|
||||
else
|
||||
rf.fRng := new System.Random;
|
||||
|
||||
rf.fFitted := fFitted;
|
||||
rf.fFeatureCount := fFeatureCount;
|
||||
|
||||
|
|
@ -4439,9 +4451,16 @@ begin
|
|||
fRandomSeed
|
||||
);
|
||||
|
||||
// --- seed ---
|
||||
rf.fRandomSeed := fRandomSeed;
|
||||
rf.fUserProvidedSeed := fUserProvidedSeed;
|
||||
rf.fFitted := fFitted;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
rf.fRng := new System.Random(fRandomSeed)
|
||||
else
|
||||
rf.fRng := new System.Random;
|
||||
|
||||
rf.fFitted := fFitted;
|
||||
rf.fFeatureCount := fFeatureCount;
|
||||
|
||||
// --- classes ---
|
||||
|
|
@ -4467,7 +4486,7 @@ begin
|
|||
SetLength(rf.fTrees, fTrees.Length);
|
||||
for var i := 0 to fTrees.Length - 1 do
|
||||
rf.fTrees[i] := DecisionTreeClassifier(fTrees[i].Clone);
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := rf;
|
||||
end;
|
||||
|
|
@ -5301,31 +5320,35 @@ begin
|
|||
fMinSamplesSplit,
|
||||
fMinSamplesLeaf,
|
||||
fSubsample,
|
||||
seed := fRandomSeed
|
||||
fLoss,
|
||||
fHuberDelta,
|
||||
fEarlyStoppingPatience,
|
||||
fQuantileAlpha,
|
||||
fLeafL2,
|
||||
fUseOOBEarlyStopping,
|
||||
fRandomSeed
|
||||
);
|
||||
|
||||
// --- seed policy ---
|
||||
// --- seed ---
|
||||
copy.fRandomSeed := fRandomSeed;
|
||||
copy.fUserProvidedSeed := fUserProvidedSeed;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
copy.fRng := new System.Random(fRandomSeed)
|
||||
else
|
||||
copy.fRng := new System.Random;
|
||||
|
||||
// --- basic state ---
|
||||
copy.fInitValue := fInitValue;
|
||||
copy.fFeatureCount := fFeatureCount;
|
||||
copy.fFitted := fFitted;
|
||||
|
||||
// --- best iteration state ---
|
||||
// --- best iteration ---
|
||||
copy.fBestIteration := fBestIteration;
|
||||
copy.fBestTrainLoss := fBestTrainLoss;
|
||||
copy.fBestScoreLoss := fBestScoreLoss;
|
||||
|
||||
// --- loss configuration ---
|
||||
copy.fLoss := fLoss;
|
||||
copy.fHuberDelta := fHuberDelta;
|
||||
copy.fQuantileAlpha := fQuantileAlpha;
|
||||
copy.fLeafL2 := fLeafL2;
|
||||
copy.fEarlyStoppingPatience := fEarlyStoppingPatience;
|
||||
copy.fUseOOBEarlyStopping := fUseOOBEarlyStopping;
|
||||
|
||||
// --- deep copy histories ---
|
||||
// --- histories ---
|
||||
copy.fTrainLossHistory.Clear;
|
||||
copy.fTrainLossHistory.AddRange(fTrainLossHistory);
|
||||
|
||||
|
|
@ -5335,7 +5358,7 @@ begin
|
|||
copy.fOOBLossHistory.Clear;
|
||||
copy.fOOBLossHistory.AddRange(fOOBLossHistory);
|
||||
|
||||
// --- deep copy trees ---
|
||||
// --- estimators ---
|
||||
copy.fEstimators.Clear;
|
||||
foreach var tree in fEstimators do
|
||||
copy.fEstimators.Add(tree.Clone as DecisionTreeRegressor);
|
||||
|
|
@ -6174,9 +6197,15 @@ begin
|
|||
fRandomSeed
|
||||
);
|
||||
|
||||
// --- seed policy ---
|
||||
// --- seed ---
|
||||
model.fRandomSeed := fRandomSeed;
|
||||
model.fUserProvidedSeed := fUserProvidedSeed;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
model.fRng := new System.Random(fRandomSeed)
|
||||
else
|
||||
model.fRng := new System.Random;
|
||||
|
||||
// --- fitted state ---
|
||||
model.fFitted := fFitted;
|
||||
model.fFeatureCount := fFeatureCount;
|
||||
|
|
@ -6197,26 +6226,19 @@ begin
|
|||
model.fClassIndex.Add(kv.Key, kv.Value);
|
||||
end;
|
||||
|
||||
// --- estimators (deep copy) ---
|
||||
// --- estimators ---
|
||||
foreach var trees in fEstimators do
|
||||
begin
|
||||
var newTrees := new DecisionTreeRegressor[Length(trees)];
|
||||
|
||||
for var cls := 0 to Length(trees) - 1 do
|
||||
newTrees[cls] := trees[cls].Clone as DecisionTreeRegressor;
|
||||
|
||||
model.fEstimators.Add(newTrees);
|
||||
end;
|
||||
|
||||
// --- histories ---
|
||||
foreach var v in fTrainLossHistory do
|
||||
model.fTrainLossHistory.Add(v);
|
||||
|
||||
foreach var v in fValLossHistory do
|
||||
model.fValLossHistory.Add(v);
|
||||
|
||||
foreach var v in fOOBLossHistory do
|
||||
model.fOOBLossHistory.Add(v);
|
||||
foreach var v in fTrainLossHistory do model.fTrainLossHistory.Add(v);
|
||||
foreach var v in fValLossHistory do model.fValLossHistory.Add(v);
|
||||
foreach var v in fOOBLossHistory do model.fOOBLossHistory.Add(v);
|
||||
|
||||
model.fBestIteration := fBestIteration;
|
||||
model.fBestScoreLoss := fBestScoreLoss;
|
||||
|
|
@ -6938,8 +6960,17 @@ begin
|
|||
fMaxIter := maxIter;
|
||||
fTol := tol;
|
||||
fNInit := nInit;
|
||||
fSeed := seed;
|
||||
|
||||
// --- seed (единый стиль)
|
||||
fRandomSeed := seed;
|
||||
fUserProvidedSeed := seed >= 0;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
fRng := new System.Random(seed)
|
||||
else
|
||||
fRng := new System.Random;
|
||||
|
||||
// --- state
|
||||
fFitted := False;
|
||||
fFeatureCount := 0;
|
||||
|
||||
|
|
@ -7077,11 +7108,8 @@ begin
|
|||
|
||||
fFeatureCount := p;
|
||||
|
||||
var actualSeed :=
|
||||
if fSeed >= 0 then fSeed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
var rndBase := new System.Random(actualSeed);
|
||||
// --- базовый RNG (уже создан в конструкторе)
|
||||
var rndBase := fRng;
|
||||
|
||||
var bestInertia := 1e308;
|
||||
var bestCenters: Matrix := nil;
|
||||
|
|
@ -7090,7 +7118,8 @@ begin
|
|||
|
||||
for var run := 1 to fNInit do
|
||||
begin
|
||||
var runSeed := rndBase.Next;
|
||||
// --- независимый RNG для каждого запуска
|
||||
var runSeed := rndBase.Next(integer.MaxValue);
|
||||
var rnd := new System.Random(runSeed);
|
||||
|
||||
var (centers, inertia, iters, converged) := RunSingle(X, rnd);
|
||||
|
|
@ -7109,7 +7138,7 @@ begin
|
|||
fIterations := bestIterations;
|
||||
fHasConverged := bestConverged;
|
||||
fFitted := True;
|
||||
|
||||
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
|
|
@ -7188,25 +7217,28 @@ begin
|
|||
fMaxIter,
|
||||
fTol,
|
||||
fNInit,
|
||||
fSeed
|
||||
fRandomSeed
|
||||
);
|
||||
|
||||
// --- seed ---
|
||||
km.fRandomSeed := fRandomSeed;
|
||||
km.fUserProvidedSeed := fUserProvidedSeed;
|
||||
|
||||
if fUserProvidedSeed then
|
||||
km.fRng := new System.Random(fRandomSeed)
|
||||
else
|
||||
km.fRng := new System.Random;
|
||||
|
||||
// --- state ---
|
||||
km.fFitted := fFitted;
|
||||
km.fFeatureCount := fFeatureCount;
|
||||
km.fInertia := fInertia;
|
||||
km.fIterations := fIterations;
|
||||
km.fHasConverged := fHasConverged;
|
||||
|
||||
// --- centers ---
|
||||
if fCenters <> nil then
|
||||
begin
|
||||
var k := fCenters.RowCount;
|
||||
var p := fCenters.ColCount;
|
||||
km.fCenters := new Matrix(k, p);
|
||||
|
||||
for var i := 0 to k - 1 do
|
||||
for var j := 0 to p - 1 do
|
||||
km.fCenters[i,j] := fCenters[i,j];
|
||||
end;
|
||||
km.fCenters := fCenters.Clone;
|
||||
|
||||
Result := km;
|
||||
end;
|
||||
|
|
@ -7339,25 +7371,6 @@ begin
|
|||
Result := Self;
|
||||
end;
|
||||
|
||||
function DBSCAN.Predict(X: Matrix): Vector;
|
||||
begin
|
||||
if not fFitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
// честная защита: Predict работает только для той же выборки
|
||||
if X.RowCount <> Length(fLabels) then
|
||||
ArgumentError(ER_DBSCAN_PREDICT_ONLY_TRAIN_DATA);
|
||||
|
||||
var n := Length(fLabels);
|
||||
Result := new Vector(n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
Result[i] := fLabels[i];
|
||||
end;
|
||||
|
||||
function DBSCAN.PredictLabels(X: Matrix): array of integer;
|
||||
begin
|
||||
if X = nil then
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
unit PlotML;
|
||||
/// Модуль визуализации на базе InteractiveDataDisplay (WPF).
|
||||
/// ВАЖНО:
|
||||
/// • Требует Windows и WPF.
|
||||
/// • Использует GAC-сборку InteractiveDataDisplay.WPF.
|
||||
/// • Не поддерживается на Linux/macOS и в .NET Core без дополнительной настройки.
|
||||
/// Рекомендуется использовать только в desktop-сценариях.
|
||||
unit PlotML;
|
||||
|
||||
{$reference %GAC%\InteractiveDataDisplay.WPF.dll}
|
||||
{$reference 'PresentationFramework.dll'}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ type
|
|||
property ColumnName: string read col;
|
||||
end;
|
||||
|
||||
ImputeStrategy = (isMean, isConstant);
|
||||
ImputeStrategy = (isMean, isConstant, isMedian);
|
||||
|
||||
/// Заполняет пропущенные значения (NA) в числовых столбцах
|
||||
/// Поддерживает стратегии isMean и isConstant
|
||||
|
|
@ -102,6 +102,7 @@ type
|
|||
strategy: ImputeStrategy;
|
||||
constants: array of object;
|
||||
means: array of real;
|
||||
medians: array of real;
|
||||
fitted: boolean;
|
||||
public
|
||||
/// Создаёт Imputer с заполнением средним значением
|
||||
|
|
@ -189,8 +190,10 @@ const
|
|||
'Imputer(constant): value type mismatch for column "{0}"';
|
||||
ER_ONEHOT_EMPTY_COLUMN =
|
||||
'Столбец "{0}" не содержит категориальных значений!!Column "{0}" contains no categorical values';
|
||||
ER_COLUMN_NOT_FOUND =
|
||||
'Столбец "{0}" не найден!!Column "{0}" not found';
|
||||
ER_IMPUTER_CONSTANTS_INVALID =
|
||||
'Массив констант не задан или имеет неверный размер!!Constants array is null or has invalid length';
|
||||
ER_IMPUTER_STRATEGY_NOT_SUPPORTED =
|
||||
'Стратегия импутации {0} не поддерживается!!Imputation strategy {0} is not supported';
|
||||
|
||||
//-----------------------------
|
||||
// LabelEncoder
|
||||
|
|
@ -288,7 +291,13 @@ end;
|
|||
|
||||
function OneHotEncoder.Fit(df: DataFrame): IPreprocessor;
|
||||
begin
|
||||
if df = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'df');
|
||||
|
||||
var idx := df.Schema.IndexOf(col);
|
||||
if idx < 0 then
|
||||
Error(ER_COLUMN_NOT_FOUND, col);
|
||||
|
||||
if df.Schema.ColumnTypeAt(idx) <> ColumnType.ctStr then
|
||||
Error(ER_ONEHOT_NOT_STRING, col);
|
||||
|
||||
|
|
@ -299,8 +308,11 @@ begin
|
|||
while cur.MoveNext do
|
||||
begin
|
||||
if not cur.IsValid(idx) then continue;
|
||||
|
||||
var s := cur.Str(idx);
|
||||
if not indexByValue.ContainsKey(s) then
|
||||
|
||||
var dummy: integer;
|
||||
if not indexByValue.TryGetValue(s, dummy) then
|
||||
begin
|
||||
indexByValue[s] := values.Count;
|
||||
values.Add(s);
|
||||
|
|
@ -380,9 +392,6 @@ end;
|
|||
|
||||
constructor Imputer.Create(strategy: ImputeStrategy; params columns: array of string);
|
||||
begin
|
||||
if strategy <> isMean then
|
||||
ArgumentError(ER_IMPUTER_INVALID_STRATEGY_MEAN);
|
||||
|
||||
if (columns = nil) or (columns.Length = 0) then
|
||||
ArgumentError(ER_IMPUTER_NO_COLUMNS);
|
||||
|
||||
|
|
@ -415,35 +424,79 @@ end;
|
|||
|
||||
function Imputer.Fit(df: DataFrame): IPreprocessor;
|
||||
begin
|
||||
if strategy = isMean then
|
||||
begin
|
||||
SetLength(means, cols.Length);
|
||||
|
||||
for var i := 0 to cols.Length - 1 do
|
||||
case strategy of
|
||||
isMean:
|
||||
begin
|
||||
var name := cols[i];
|
||||
var idx := df.Schema.IndexOf(name);
|
||||
var ct := df.Schema.ColumnTypeAt(idx);
|
||||
SetLength(means, cols.Length);
|
||||
|
||||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_IMPUTER_COLUMN_NOT_NUMERIC, name);
|
||||
for var i := 0 to cols.Length - 1 do
|
||||
begin
|
||||
var name := cols[i];
|
||||
var idx := df.Schema.IndexOf(name);
|
||||
var ct := df.Schema.ColumnTypeAt(idx);
|
||||
|
||||
var sum := 0.0;
|
||||
var cnt := 0;
|
||||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_IMPUTER_COLUMN_NOT_NUMERIC, name);
|
||||
|
||||
var cur := df.GetCursor;
|
||||
while cur.MoveNext do
|
||||
if cur.IsValid(idx) then
|
||||
begin
|
||||
sum += cur.Float(idx);
|
||||
cnt += 1;
|
||||
end;
|
||||
var sum := 0.0;
|
||||
var cnt := 0;
|
||||
|
||||
if cnt = 0 then
|
||||
Error(ER_IMPUTER_NO_VALID_VALUES, name);
|
||||
var cur := df.GetCursor;
|
||||
while cur.MoveNext do
|
||||
if cur.IsValid(idx) then
|
||||
begin
|
||||
sum += cur.Float(idx);
|
||||
cnt += 1;
|
||||
end;
|
||||
|
||||
means[i] := sum / cnt;
|
||||
if cnt = 0 then
|
||||
Error(ER_IMPUTER_NO_VALID_VALUES, name);
|
||||
|
||||
means[i] := sum / cnt;
|
||||
end;
|
||||
end;
|
||||
|
||||
isConstant:
|
||||
begin
|
||||
// ничего делать не нужно
|
||||
end;
|
||||
|
||||
isMedian:
|
||||
begin
|
||||
SetLength(medians, cols.Length);
|
||||
|
||||
for var i := 0 to cols.Length - 1 do
|
||||
begin
|
||||
var name := cols[i];
|
||||
var idx := df.Schema.IndexOf(name);
|
||||
var ct := df.Schema.ColumnTypeAt(idx);
|
||||
|
||||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_IMPUTER_COLUMN_NOT_NUMERIC, name);
|
||||
|
||||
// --- собираем значения
|
||||
var values := new List<real>;
|
||||
|
||||
var cur := df.GetCursor;
|
||||
while cur.MoveNext do
|
||||
if cur.IsValid(idx) then
|
||||
values.Add(cur.Float(idx));
|
||||
|
||||
if values.Count = 0 then
|
||||
Error(ER_IMPUTER_NO_VALID_VALUES, name);
|
||||
|
||||
// --- сортируем
|
||||
values.Sort;
|
||||
|
||||
// --- медиана
|
||||
var n := values.Count;
|
||||
if n mod 2 = 1 then
|
||||
medians[i] := values[n div 2]
|
||||
else
|
||||
medians[i] := (values[n div 2 - 1] + values[n div 2]) / 2.0;
|
||||
end;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
fitted := true;
|
||||
|
|
@ -455,6 +508,11 @@ begin
|
|||
if not fitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
// --- проверка constants
|
||||
if (strategy = isConstant) and
|
||||
((constants = nil) or (constants.Length <> cols.Length)) then
|
||||
Error(ER_IMPUTER_CONSTANTS_INVALID);
|
||||
|
||||
var res := df;
|
||||
|
||||
for var i := 0 to cols.Length - 1 do
|
||||
|
|
@ -466,50 +524,65 @@ begin
|
|||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_IMPUTER_COLUMN_NOT_NUMERIC, name);
|
||||
|
||||
if strategy = isMean then
|
||||
begin
|
||||
var m := means[i];
|
||||
res := res.ReplaceColumnFloat(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else m)
|
||||
);
|
||||
end
|
||||
else
|
||||
begin
|
||||
var v := constants[i];
|
||||
if v = nil then
|
||||
Error(ER_IMPUTER_CONSTANT_VALUE_NULL, name);
|
||||
|
||||
if ct = ColumnType.ctInt then
|
||||
case strategy of
|
||||
isMean:
|
||||
begin
|
||||
var k: integer;
|
||||
try
|
||||
k := integer(v);
|
||||
except
|
||||
on e: Exception do
|
||||
Error(ER_IMPUTER_CONSTANT_TYPE_MISMATCH, name);
|
||||
end;
|
||||
|
||||
res := res.ReplaceColumnInt(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Int(idx) else k)
|
||||
);
|
||||
end
|
||||
else
|
||||
begin
|
||||
var r: real;
|
||||
try
|
||||
r := real(v);
|
||||
except
|
||||
on e: Exception do
|
||||
Error(ER_IMPUTER_CONSTANT_TYPE_MISMATCH, name);
|
||||
end;
|
||||
|
||||
var m := means[i];
|
||||
res := res.ReplaceColumnFloat(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else r)
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else m)
|
||||
);
|
||||
end;
|
||||
|
||||
isConstant:
|
||||
begin
|
||||
var v := constants[i];
|
||||
if v = nil then
|
||||
Error(ER_IMPUTER_CONSTANT_VALUE_NULL, name);
|
||||
|
||||
if ct = ColumnType.ctInt then
|
||||
begin
|
||||
var k: integer;
|
||||
try
|
||||
k := integer(v);
|
||||
except
|
||||
on e: Exception do
|
||||
Error(ER_IMPUTER_CONSTANT_TYPE_MISMATCH, name);
|
||||
end;
|
||||
|
||||
res := res.ReplaceColumnInt(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Int(idx) else k)
|
||||
);
|
||||
end
|
||||
else // ctFloat
|
||||
begin
|
||||
var r: real;
|
||||
try
|
||||
r := real(v);
|
||||
except
|
||||
on e: Exception do
|
||||
Error(ER_IMPUTER_CONSTANT_TYPE_MISMATCH, name);
|
||||
end;
|
||||
|
||||
res := res.ReplaceColumnFloat(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else r)
|
||||
);
|
||||
end;
|
||||
end;
|
||||
|
||||
isMedian:
|
||||
begin
|
||||
var m := medians[i];
|
||||
res := res.ReplaceColumnFloat(
|
||||
name,
|
||||
c -> (if c.IsValid(idx) then c.Float(idx) else m)
|
||||
);
|
||||
end;
|
||||
|
||||
else
|
||||
Error(ER_IMPUTER_STRATEGY_NOT_SUPPORTED, strategy);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -530,9 +603,23 @@ begin
|
|||
isMean:
|
||||
Result := 'Imputer(strategy=mean, columns=' + colsStr + ')';
|
||||
|
||||
isMedian:
|
||||
Result := 'Imputer(strategy=median, columns=' + colsStr + ')';
|
||||
|
||||
isConstant:
|
||||
begin
|
||||
var valStr :=
|
||||
if (constants <> nil) and (constants.Length > 0) and (constants[0] <> nil) then
|
||||
constants[0].ToString
|
||||
else
|
||||
'null';
|
||||
|
||||
Result := 'Imputer(strategy=constant, value=' +
|
||||
constants[0].ToString + ', columns=' + colsStr + ')';
|
||||
valStr + ', columns=' + colsStr + ')';
|
||||
end;
|
||||
|
||||
else
|
||||
Result := 'Imputer(strategy=unknown, columns=' + colsStr + ')';
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,22 +56,26 @@ type
|
|||
GridSearch = static class
|
||||
public
|
||||
/// Выполняет подбор гиперпараметра по заданной сетке значений.
|
||||
/// modelFactory — функция создания модели по значению параметра.
|
||||
/// paramValues — набор тестируемых значений гиперпараметра.
|
||||
/// X, y — обучающие данные.
|
||||
/// k — число фолдов в кросс-валидации.
|
||||
/// metric — функция оценки качества (yTrue, yPred) → real.
|
||||
/// Возвращает кортеж (лучший параметр, лучшее среднее значение метрики,
|
||||
/// модель, обученная на всём датасете с лучшим параметром).
|
||||
class function Search<T>(
|
||||
modelFactory: real -> T;
|
||||
paramValues: array of real;
|
||||
X: Matrix; y: Vector;
|
||||
k: integer;
|
||||
metric: (Vector, Vector) -> real;
|
||||
maximize: boolean := True;
|
||||
seed: integer := -1
|
||||
): (real, real, T); where T: class,ISupervisedModel;
|
||||
/// • modelFactory — функция создания модели по значению параметра (P -> T).
|
||||
/// • paramValues — набор тестируемых значений гиперпараметра типа P.
|
||||
/// • X, y — обучающие данные.
|
||||
/// • k — число фолдов в кросс-валидации.
|
||||
/// • metric — функция оценки качества (yTrue, yPred) → real.
|
||||
/// • maximize — если true, максимизируется метрика; иначе минимизируется.
|
||||
/// • seed — seed для разбиения на фолды (для воспроизводимости).
|
||||
/// Возвращает кортеж:
|
||||
/// • лучший параметр,
|
||||
/// • лучшее среднее значение метрики,
|
||||
/// • модель, обученная на всём датасете с лучшим параметром
|
||||
class function Search<T, P>(
|
||||
modelFactory: P -> T;
|
||||
paramValues: array of P;
|
||||
X: Matrix; y: Vector;
|
||||
k: integer;
|
||||
metric: (Vector, Vector) -> real;
|
||||
maximize: boolean := True;
|
||||
seed: integer := -1
|
||||
): (P, real, T); where T: class, ISupervisedModel;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -171,15 +175,15 @@ begin
|
|||
if (k < 2) or (k > n) then
|
||||
ArgumentError(ER_K_INVALID, k, n);
|
||||
|
||||
var actualSeed := if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
var rnd := new System.Random(actualSeed);
|
||||
// --- RNG (без дублирования логики seed)
|
||||
var rnd :=
|
||||
if seed >= 0 then new System.Random(seed)
|
||||
else new System.Random;
|
||||
|
||||
// --- 1. Индексы 0..n-1
|
||||
var idx := Arr(0..n-1);
|
||||
|
||||
// --- 2. Перемешивание через стандартный Shuffle
|
||||
// --- 2. Перемешивание
|
||||
idx.Shuffle(rnd);
|
||||
|
||||
var baseSize := n div k;
|
||||
|
|
@ -224,10 +228,9 @@ begin
|
|||
if (k < 2) or (k > n) then
|
||||
ArgumentError(ER_K_INVALID_STRATIFIED, k, n);
|
||||
|
||||
var actualSeed := if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
var rnd := new System.Random(actualSeed);
|
||||
var rnd :=
|
||||
if seed >= 0 then new System.Random(seed)
|
||||
else new System.Random;
|
||||
|
||||
// --- 1. Индексы по классам
|
||||
var classMap := new Dictionary<integer, List<integer>>();
|
||||
|
|
@ -235,7 +238,7 @@ begin
|
|||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var v := y[i];
|
||||
var cls := integer(v);
|
||||
var cls := Round(v);
|
||||
|
||||
if Abs(v - cls) > 1e-12 then
|
||||
ArgumentError(ER_STRATIFIED_LABELS_INVALID);
|
||||
|
|
@ -328,8 +331,12 @@ begin
|
|||
var total := 0.0;
|
||||
var folds := 0;
|
||||
var p := X.ColCount;
|
||||
|
||||
var baseSeed :=
|
||||
if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
foreach var (trainIdx, testIdx) in KFold(X.RowCount, k, seed) do
|
||||
foreach var (trainIdx, testIdx) in KFold(X.RowCount, k, baseSeed) do
|
||||
begin
|
||||
var Xtr := new Matrix(trainIdx.Length, p);
|
||||
var ytr := new Vector(trainIdx.Length);
|
||||
|
|
@ -398,7 +405,11 @@ begin
|
|||
var folds := 0;
|
||||
var p := X.ColCount;
|
||||
|
||||
foreach var (trainIdx, testIdx) in StratifiedKFold(y, k, seed) do
|
||||
var baseSeed :=
|
||||
if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
foreach var (trainIdx, testIdx) in StratifiedKFold(y, k, baseSeed) do
|
||||
begin
|
||||
var Xtr := new Matrix(trainIdx.Length, p);
|
||||
var ytr := new Vector(trainIdx.Length);
|
||||
|
|
@ -441,16 +452,16 @@ end;
|
|||
// GridSearch
|
||||
//-----------------------------
|
||||
|
||||
class function GridSearch.Search<T>(
|
||||
modelFactory: real -> T;
|
||||
paramValues: array of real;
|
||||
class function GridSearch.Search<T, P>(
|
||||
modelFactory: P -> T;
|
||||
paramValues: array of P;
|
||||
X: Matrix;
|
||||
y: Vector;
|
||||
k: integer;
|
||||
metric: (Vector, Vector) -> real;
|
||||
maximize: boolean;
|
||||
seed: integer
|
||||
): (real, real, T); where T: class,ISupervisedModel;
|
||||
): (P, real, T); where T: class, ISupervisedModel;
|
||||
begin
|
||||
if modelFactory = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'modelFactory');
|
||||
|
|
@ -474,15 +485,20 @@ begin
|
|||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
var bestParam := paramValues[0];
|
||||
var bestScore :=
|
||||
var bestScore :=
|
||||
if maximize then -1e308 else 1e308;
|
||||
|
||||
var baseSeed :=
|
||||
if seed >= 0 then seed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
foreach var param in paramValues do
|
||||
begin
|
||||
var model := modelFactory(param);
|
||||
if model = nil then
|
||||
ArgumentError(ER_MODEL_NULL);
|
||||
var avgScore := Validation.CrossValidate(model, X, y, k, metric, seed);
|
||||
|
||||
var avgScore := Validation.CrossValidate(model, X, y, k, metric, baseSeed);
|
||||
|
||||
if double.IsNaN(avgScore) or double.IsInfinity(avgScore) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'avgScore');
|
||||
|
|
@ -501,7 +517,7 @@ begin
|
|||
var bestModel := modelFactory(bestParam);
|
||||
if bestModel = nil then
|
||||
ArgumentError(ER_MODEL_NULL);
|
||||
|
||||
|
||||
bestModel := bestModel.Fit(X, y) as T;
|
||||
|
||||
Result := (bestParam, bestScore, bestModel);
|
||||
|
|
|
|||
Loading…
Reference in a new issue