Правки ML

This commit is contained in:
Mikhalkovich Stanislav 2026-04-27 09:34:44 +03:00
parent be48d378be
commit a5a96bc68c
17 changed files with 380 additions and 952 deletions

View file

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

View file

@ -1,4 +1,4 @@
%COREVERSION%=1 %COREVERSION%=1
%REVISION%=3810 %REVISION%=3812
%MINOR%=11 %MINOR%=11
%MAJOR%=3 %MAJOR%=3

View file

@ -1 +1 @@
3.11.1.3810 3.11.1.3812

View file

@ -1 +1 @@
!define VERSION '3.11.1.3810' !define VERSION '3.11.1.3812'

View file

@ -292,7 +292,7 @@ namespace PascalABCCompiler.TreeConverter
ccc.parameters.AddRange(exprs); ccc.parameters.AddRange(exprs);
return ccc; return ccc;
} }
//Метод только для внутнреннего использования. //Метод только для внутреннего использования.
//НЕ ПРЕОБРАЗУЕТ ТИПЫ. Только создает узел вызова метода. //НЕ ПРЕОБРАЗУЕТ ТИПЫ. Только создает узел вызова метода.
//Этот метод и используется для создания узлов преобразования типов. //Этот метод и используется для создания узлов преобразования типов.
//TODO: Переименовать. //TODO: Переименовать.

View file

@ -105,15 +105,24 @@ namespace VisualPascalABC
return; return;
} }
mes = Form1StringResources.Get("FILE_CHANGED_MESSAGE"); //mes = Form1StringResources.Get("FILE_CHANGED_MESSAGE");
if (MessageBox.Show(fileName + "\n\n" + mes, Form1StringResources.Get("CHANGED_FILE"), MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) var tab = VisualPABCSingleton.MainForm.FindTab(fileName);
{ if (tab != null)
WorkbenchServiceFactory.FileService.ReloadFile(fileName); if (tab.DocumentChanged)
} {
else if (MessageBox.Show(fileName + "\n\n" + mes, Form1StringResources.Get("CHANGED_FILE"), MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{ {
WorkbenchServiceFactory.FileService.SetFileAsChanged(fileName); WorkbenchServiceFactory.FileService.ReloadFile(fileName);
} }
else
{
WorkbenchServiceFactory.FileService.SetFileAsChanged(fileName);
}
}
else
{
WorkbenchServiceFactory.FileService.ReloadFile(fileName);
}
} }
void MainForm_Activated(object sender, EventArgs e) void MainForm_Activated(object sender, EventArgs e)

View file

@ -316,11 +316,16 @@ namespace VisualPascalABC
public void ReloadFile(string FileName) public void ReloadFile(string FileName)
{ {
CodeFileDocumentControl tab = FindTab(FileName); CodeFileDocumentControl tab = FindTab(FileName);
if (tab != null) if (tab != null)
{ {
var CaretLine = tab.TextEditor.CaretLine;
tab.LoadFromFile(FileName); tab.LoadFromFile(FileName);
tab.DocumentChanged = false; tab.DocumentChanged = false;
tab.TextEditor.ActiveTextAreaControl.Update();
tab.TextEditor.ActiveTextAreaControl.CenterViewOn(CaretLine + 2, 0);
tab.TextEditor.CaretLine = CaretLine;
SetTabPageText(tab); SetTabPageText(tab);
} }
} }

View file

@ -14,9 +14,6 @@ uses LinearAlgebraML;
const const
ER_TO_MATRIX_NO_COLUMNS = ER_TO_MATRIX_NO_COLUMNS =
'ToMatrix: не указаны столбцы!!ToMatrix: no columns specified'; 'ToMatrix: не указаны столбцы!!ToMatrix: no columns specified';
ER_TO_VECTOR_NON_NUMERIC =
'ToVector: столбец "{0}" содержит нечисловые или NA значения!!' +
'ToVector: column "{0}" contains non-numeric or NA values';
ER_TO_MATRIX_NON_NUMERIC = ER_TO_MATRIX_NON_NUMERIC =
'Столбец "{0}" содержит нечисловые или NA значения!!Column "{0}" contains non-numeric or NA values'; 'Столбец "{0}" содержит нечисловые или NA значения!!Column "{0}" contains non-numeric or NA values';
ER_ENCODELABELS_COLUMN_NOT_STRING = ER_ENCODELABELS_COLUMN_NOT_STRING =
@ -281,4 +278,4 @@ end;
end. end.

View file

@ -108,7 +108,7 @@ type
/// Столбец целых чисел /// Столбец целых чисел
IntColumn = class(Column) IntColumn = class(Column)
// Data и IsValid считаются immutable после создания // Data и IsValid считаются immutable после создания
Data: array of integer; // Данные столбца Data: array of integer;
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -120,7 +120,7 @@ type
/// Столбец вещественных чисел /// Столбец вещественных чисел
FloatColumn = class(Column) FloatColumn = class(Column)
Data: array of real; // Данные столбца Data: array of real;
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -133,7 +133,7 @@ type
/// Столбец строк /// Столбец строк
StrColumn = class(Column) StrColumn = class(Column)
Data: array of string; // Данные столбца Data: array of string;
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -142,15 +142,11 @@ type
function TryGetNumericValue(i: integer; var value: real): boolean; override; function TryGetNumericValue(i: integer; var value: real): boolean; override;
/// Возвращает количество строк в столбце /// Возвращает количество строк в столбце
function RowCount: integer; override := Data.Length; function RowCount: integer; override := Data.Length;
/// Добавляет невалидное (NA) значение в конец столбца
//procedure AppendInvalid; override;
/// Добавляет значение из курсора в указанной позиции
//procedure AppendFromCursor(cur: DataFrameCursor; colIndex: integer); override;
end; end;
/// Столбец булевых значений /// Столбец булевых значений
BoolColumn = class(Column) BoolColumn = class(Column)
Data: array of boolean; // Данные столбца Data: array of boolean;
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -780,16 +776,21 @@ begin
var n := cols.Length; var n := cols.Length;
colCnt := cols.Length; colCnt := cols.Length;
SetLength(intAcc, n); intAcc := new IntAccessor[n];
SetLength(floatAcc, n); floatAcc := new FloatAccessor[n];
SetLength(strAcc, n); strAcc := new StrAccessor[n];
SetLength(boolAcc, n); boolAcc := new BoolAccessor[n];
SetLength(validAcc, n); validAcc := new ValidAccessor[n];
for var i := 0 to n - 1 do for var i := 0 to n - 1 do
begin begin
var col := cols[i]; var col := cols[i];
intAcc[i] := NotInt;
floatAcc[i] := NotFloat;
strAcc[i] := NotStr;
boolAcc[i] := NotBool;
case fSchema.ColumnTypeAt(i) of case fSchema.ColumnTypeAt(i) of
ctInt: ctInt:
begin begin
@ -806,7 +807,6 @@ begin
validAcc[i] := pos -> c.IsValid[pos]; validAcc[i] := pos -> c.IsValid[pos];
floatAcc[i] := pos -> c.Data[pos]; floatAcc[i] := pos -> c.Data[pos];
intAcc[i] := NotInt;
end; end;
ctStr: ctStr:
@ -886,4 +886,4 @@ begin
pos := p; pos := p;
end; end;
end. end.

View file

@ -94,6 +94,7 @@ type
LinearRegression = MLModelsABC.LinearRegression; LinearRegression = MLModelsABC.LinearRegression;
LogisticRegression = MLModelsABC.LogisticRegression; LogisticRegression = MLModelsABC.LogisticRegression;
RidgeRegression = MLModelsABC.RidgeRegression; RidgeRegression = MLModelsABC.RidgeRegression;
LassoRegression = MLModelsABC.LassoRegression;
ElasticNet = MLModelsABC.ElasticNet; ElasticNet = MLModelsABC.ElasticNet;
DecisionTreeClassifier = MLModelsABC.DecisionTreeClassifier; DecisionTreeClassifier = MLModelsABC.DecisionTreeClassifier;
DecisionTreeRegressor = MLModelsABC.DecisionTreeRegressor; DecisionTreeRegressor = MLModelsABC.DecisionTreeRegressor;
@ -148,7 +149,16 @@ const
jkRight = JoinKind.jkRight; jkRight = JoinKind.jkRight;
jkFull = JoinKind.jkFull; jkFull = JoinKind.jkFull;
/// Преобразует вектор меток классов в массив целых чисел.
/// Значения округляются функцией Round, чтобы устранить
/// возможные небольшие численные ошибки
function LabelsToInts(y: Vector): array of integer; function LabelsToInts(y: Vector): array of integer;
/// Кодирует строковые метки классов в целочисленные индексы.
/// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления меток.
/// Используется при обучении моделей и визуализации.
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function EncodeLabels(labels: array of string): array of integer; function EncodeLabels(labels: array of string): array of integer;
@ -161,4 +171,4 @@ end;
function EncodeLabels(labels: array of string): array of integer := MLUtilsABC.EncodeLabels(labels); function EncodeLabels(labels: array of string): array of integer := MLUtilsABC.EncodeLabels(labels);
end. end.

View file

@ -5,6 +5,9 @@ interface
const const
ER_DIM_MISMATCH = ER_DIM_MISMATCH =
'Размерности не совпадают: {0} и {1}!!Dimension mismatch: {0} and {1}'; 'Размерности не совпадают: {0} и {1}!!Dimension mismatch: {0} and {1}';
ER_TO_VECTOR_NON_NUMERIC =
'ToVector: столбец "{0}" содержит нечисловые или NA значения!!' +
'ToVector: column "{0}" contains non-numeric or NA values';
ER_PARAM_VALUES_EMPTY = ER_PARAM_VALUES_EMPTY =
'Список paramValues пуст!!paramValues is empty'; 'Список paramValues пуст!!paramValues is empty';
ER_EMPTY_DATASET = ER_EMPTY_DATASET =

View file

@ -10,7 +10,7 @@
/// ///
/// Все алгоритмы работают с числовыми данными: /// Все алгоритмы работают с числовыми данными:
/// X — Matrix (объекты × признаки), y — Vector (целевая переменная). /// X — Matrix (объекты × признаки), y — Vector (целевая переменная).
unit MLModelsABC; unit MLModelsABC;
// ============================================================= // =============================================================
// СТАТИСТИЧЕСКОЕ СОГЛАШЕНИЕ (MLModelsABC) // СТАТИСТИЧЕСКОЕ СОГЛАШЕНИЕ (MLModelsABC)
@ -110,20 +110,8 @@ type
function ToString: string; override; function ToString: string; override;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -135,7 +123,7 @@ type
/// Устойчива к мультиколлинеарности и плохо обусловленным данным. /// Устойчива к мультиколлинеарности и плохо обусловленным данным.
/// Используется при коррелированных признаках /// Используется при коррелированных признаках
/// и в задачах, где важна численная стабильность решения /// и в задачах, где важна численная стабильность решения
RidgeRegression = class(IRegressor) RidgeRegression = class(IRegressor)
private private
fLambda: real; fLambda: real;
fCoef: Vector; fCoef: Vector;
@ -175,22 +163,10 @@ type
/// После вызова Fit значение становится true. /// После вызова Fit значение становится true.
property IsFitted: boolean read fFitted; property IsFitted: boolean read fFitted;
function ToString: string; override; function ToString: string; override;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -255,20 +231,8 @@ type
function ToString: string; override; function ToString: string; override;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -310,20 +274,8 @@ type
/// Возвращает вектор предсказаний длины nSamples. /// Возвращает вектор предсказаний длины nSamples.
function Predict(X: Matrix): Vector; function Predict(X: Matrix): Vector;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
/// Показывает, была ли модель обучена. /// Показывает, была ли модель обучена.
@ -421,20 +373,8 @@ type
function GetClassLabels: array of string; function GetClassLabels: array of string;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
end; end;
@ -573,13 +513,6 @@ type
function FindBestSplit(X: Matrix; y: Vector; indices: array of integer; function FindBestSplit(X: Matrix; y: Vector; indices: array of integer;
var bestF: integer; var bestT: real): boolean; var bestF: integer; var bestT: real): boolean;
procedure Split(
X: Matrix; y: Vector;
f: integer; t: real;
var Xl: Matrix; var yl: Vector;
var Xr: Matrix; var yr: Vector
);
function MajorityClass(y: Vector; indices: array of integer): integer; function MajorityClass(y: Vector; indices: array of integer): integer;
public public
property FeatureImportances: Vector read GetFeatureImportances; property FeatureImportances: Vector read GetFeatureImportances;
@ -656,20 +589,8 @@ type
/// Для классификации — метки классов. /// Для классификации — метки классов.
function Predict(X: Matrix): Vector; virtual; abstract; function Predict(X: Matrix): Vector; virtual; abstract;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; virtual; abstract; function Clone: IModel; virtual; abstract;
/// Возвращает true, если дерево обучено. /// Возвращает true, если дерево обучено.
@ -748,20 +669,8 @@ type
/// Если false — Predict вызовет ошибку. /// Если false — Predict вызовет ошибку.
property IsFitted: boolean read fFitted; property IsFitted: boolean read fFitted;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
end; end;
@ -816,20 +725,8 @@ type
/// Возвращает вектор вещественных значений. /// Возвращает вектор вещественных значений.
function Predict(X: Matrix): Vector; override; function Predict(X: Matrix): Vector; override;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; override; function Clone: IModel; override;
/// Возвращает строковое представление модели. /// Возвращает строковое представление модели.
@ -925,20 +822,8 @@ type
/// В классификации — голосование (majority vote) или усреднение вероятностей. /// В классификации — голосование (majority vote) или усреднение вероятностей.
function Predict(X: Matrix): Vector; virtual; abstract; function Predict(X: Matrix): Vector; virtual; abstract;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; virtual; abstract; function Clone: IModel; virtual; abstract;
/// Возвращает вектор важности признаков. /// Возвращает вектор важности признаков.
@ -994,20 +879,8 @@ type
/// Итоговое значение — среднее предсказаний всех деревьев ансамбля. /// Итоговое значение — среднее предсказаний всех деревьев ансамбля.
function Predict(X: Matrix): Vector; override; function Predict(X: Matrix): Vector; override;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; override; function Clone: IModel; override;
/// Возвращает усредненную важность признаков по всем деревьям ансамбля. /// Возвращает усредненную важность признаков по всем деревьям ансамбля.
@ -1075,20 +948,8 @@ type
/// Требует предварительного вызова Fit. /// Требует предварительного вызова Fit.
function PredictProba(X: Matrix): Matrix; function PredictProba(X: Matrix): Matrix;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; override; function Clone: IModel; override;
/// Возвращает усредненную важность признаков по всем деревьям ансамбля. /// Возвращает усредненную важность признаков по всем деревьям ансамбля.
@ -1245,20 +1106,8 @@ type
/// Используются все обученные деревья. /// Используются все обученные деревья.
function Predict(X: Matrix): Vector; function Predict(X: Matrix): Vector;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
/// Обучает модель градиентного бустинга с использованием валидационной выборки. /// Обучает модель градиентного бустинга с использованием валидационной выборки.
@ -1423,20 +1272,8 @@ type
function GetClasses: array of real; function GetClasses: array of real;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
/// История значения LogLoss на обучающей выборке. /// История значения LogLoss на обучающей выборке.
@ -1534,20 +1371,8 @@ type
/// Возвращает вектор предсказанных значений или меток /// Возвращает вектор предсказанных значений или меток
function Predict(X: Matrix): Vector; virtual; abstract; function Predict(X: Matrix): Vector; virtual; abstract;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; virtual; abstract; function Clone: IModel; virtual; abstract;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -1610,20 +1435,8 @@ type
/// Возвращает массив меток классов в порядке столбцов PredictProba /// Возвращает массив меток классов в порядке столбцов PredictProba
function GetClasses: array of real; function GetClasses: array of real;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; override; function Clone: IModel; override;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -1661,20 +1474,8 @@ type
/// Возвращает вектор предсказанных значений /// Возвращает вектор предсказанных значений
function Predict(X: Matrix): Vector; override; function Predict(X: Matrix): Vector; override;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; override; function Clone: IModel; override;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -1738,20 +1539,8 @@ type
/// Выполняет обучение и сразу возвращает метки кластеров. /// Выполняет обучение и сразу возвращает метки кластеров.
function FitPredict(X: Matrix): array of integer; function FitPredict(X: Matrix): array of integer;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
/// Количество кластеров /// Количество кластеров
@ -1819,20 +1608,8 @@ type
/// Возвращает метки после обучения. /// Возвращает метки после обучения.
function FitPredict(X: Matrix): array of integer; function FitPredict(X: Matrix): array of integer;
/// Создаёт копию модели с теми же гиперпараметрами. /// Копирует только конфигурацию модели (без обученного состояния).
/// /// Используется для создания независимых экземпляров модели.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline
/// • гарантирует независимое переобучение моделей
///
/// Примечание:
/// • для копирования обученной модели следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: IModel; function Clone: IModel;
property Labels: array of integer read fLabels; property Labels: array of integer read fLabels;
@ -1976,16 +1753,8 @@ type
/// Возвращает строковое представление пайплайна. /// Возвращает строковое представление пайплайна.
function ToString: string; override; function ToString: string; override;
/// Создаёт копию пайплайна с теми же шагами и моделью. /// Копирует только конфигурацию пайплайна (без обученного состояния).
/// /// Используется для создания независимых экземпляров пайплайна.
/// ВАЖНО:
/// • Clone копирует только конфигурацию (transformers и модель)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Все шаги и модель клонируются через их Clone (config-only)
///
/// Назначение:
/// • использование в CrossValidate и Pipeline
/// • обеспечивает независимое переобучение пайплайна
function Clone: IModel; function Clone: IModel;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -2068,16 +1837,8 @@ type
/// Возвращает строковое представление пайплайна. /// Возвращает строковое представление пайплайна.
function ToString: string; override; function ToString: string; override;
/// Создаёт копию пайплайна с теми же шагами и моделью. /// Копирует только конфигурацию пайплайна (без обученного состояния).
/// /// Используется для создания независимых экземпляров пайплайна.
/// ВАЖНО:
/// • Clone копирует только конфигурацию (transformers и модель)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Все шаги и модель клонируются через их Clone (config-only)
///
/// Назначение:
/// • использование в CrossValidate и MatrixPipeline
/// • обеспечивает независимое переобучение пайплайна
function Clone: IModel; function Clone: IModel;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
end; end;
@ -2130,20 +1891,8 @@ type
function ToString: string; override; function ToString: string; override;
/// Создаёт копию трансформера с теми же гиперпараметрами. /// Копирует только конфигурацию трансформера (без обученного состояния).
/// /// Используется для создания независимых экземпляров трансформера.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров
///
/// Примечание:
/// • для копирования обученного трансформера следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: ITransformer; function Clone: ITransformer;
end; end;
@ -2188,20 +1937,8 @@ type
function ToString: string; override; function ToString: string; override;
/// Создаёт копию трансформера с теми же гиперпараметрами. /// Копирует только конфигурацию трансформера (без обученного состояния).
/// /// Используется для создания независимых экземпляров трансформера.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров
///
/// Примечание:
/// • для копирования обученного трансформера следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: ITransformer; function Clone: ITransformer;
end; end;
@ -2242,20 +1979,8 @@ type
function ToString: string; override; function ToString: string; override;
/// Создаёт копию трансформера с теми же гиперпараметрами. /// Копирует только конфигурацию трансформера (без обученного состояния).
/// /// Используется для создания независимых экземпляров трансформера.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров
///
/// Примечание:
/// • для копирования обученного трансформера следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: ITransformer; function Clone: ITransformer;
end; end;
@ -2295,20 +2020,8 @@ type
function ToString: string; override; function ToString: string; override;
/// Создаёт копию трансформера с теми же гиперпараметрами. /// Копирует только конфигурацию трансформера (без обученного состояния).
/// /// Используется для создания независимых экземпляров трансформера.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров
///
/// Примечание:
/// • для копирования обученного трансформера следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: ITransformer; function Clone: ITransformer;
end; end;
@ -2392,20 +2105,8 @@ type
/// Возвращает строковое представление трансформера /// Возвращает строковое представление трансформера
function ToString: string; override; function ToString: string; override;
/// Создаёт копию трансформера с теми же гиперпараметрами. /// Копирует только конфигурацию трансформера (без обученного состояния).
/// /// Используется для создания независимых экземпляров трансформера.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров
///
/// Примечание:
/// • для копирования обученного трансформера следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: ITransformer; function Clone: ITransformer;
end; end;
@ -2450,20 +2151,8 @@ type
/// Возвращает строковое представление трансформера /// Возвращает строковое представление трансформера
function ToString: string; override; function ToString: string; override;
/// Создаёт копию трансформера с теми же гиперпараметрами. /// Копирует только конфигурацию трансформера (без обученного состояния).
/// /// Используется для создания независимых экземпляров трансформера.
/// ВАЖНО:
/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters)
/// • Clone НЕ копирует обученное состояние (fitted state)
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
///
/// Назначение:
/// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров
///
/// Примечание:
/// • для копирования обученного трансформера следует использовать отдельные механизмы
/// (сериализация, ModelIO и т.п.)
function Clone: ITransformer; function Clone: ITransformer;
end; end;
@ -3226,18 +2915,7 @@ begin
var p := X.ColCount; var p := X.ColCount;
// --- convert to integer labels // --- convert to integer labels
var yInt := new integer[m]; var yInt := LabelsToInts(y);
for var i := 0 to m - 1 do
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
yInt[i] := ir;
end;
// --- encode (порядок первого появления) // --- encode (порядок первого появления)
var unique: array of integer; var unique: array of integer;
@ -4053,140 +3731,6 @@ begin
Result.Right := right; Result.Right := right;
end; end;
procedure DecisionTreeCore.Split(X: Matrix; y: Vector; f: integer; t: real;
var Xl: Matrix; var yl: Vector;
var Xr: Matrix; var yr: Vector
);
begin
var leftIdx := new List<integer>;
var rightIdx := new List<integer>;
// 1. Разделяем индексы
for var i := 0 to X.RowCount - 1 do
if X[i, f] <= t then
leftIdx.Add(i)
else
rightIdx.Add(i);
// 2. Формируем подматрицы и подвекторы
var leftArr := leftIdx.ToArray;
var rightArr := rightIdx.ToArray;
Xl := X.TakeRows(leftArr);
yl := y.SubvectorBy(leftArr);
Xr := X.TakeRows(rightArr);
yr := y.SubvectorBy(rightArr);
end;
{function DecisionTreeCore.FindBestSplit(
X: Matrix;
y: Vector;
indices: array of integer;
var bestF: integer;
var bestT: real
): boolean;
begin
Result := false;
var n := indices.Length;
if n = 0 then exit;
var parentImp := fCriterion.Impurity(y, indices);
var bestScore := parentImp;
// --- выбор признаков (с учётом maxFeatures)
var featureCount := X.ColCount;
var features: array of integer;
if (fMaxFeatures > 0) and (fMaxFeatures < featureCount) then
begin
var perm := new integer[featureCount];
for var i := 0 to featureCount - 1 do
perm[i] := i;
for var i := 0 to fMaxFeatures - 1 do
begin
var j := i + fRng.Next(featureCount - i);
var tmp := perm[i];
perm[i] := perm[j];
perm[j] := tmp;
end;
SetLength(features, fMaxFeatures);
for var i := 0 to fMaxFeatures - 1 do
features[i] := perm[i];
end
else
begin
SetLength(features, featureCount);
for var i := 0 to featureCount - 1 do
features[i] := i;
end;
// --- перебор
for var fi := 0 to features.Length - 1 do
begin
var f := features[fi];
var leftBuf := new integer[n];
var rightBuf := new integer[n];
for var ii := 0 to n - 1 do
begin
var i := indices[ii];
var t := X[i, f];
var lcnt := 0;
var rcnt := 0;
for var jj := 0 to n - 1 do
begin
var j := indices[jj];
if X[j, f] <= t then
begin
leftBuf[lcnt] := j;
lcnt += 1;
end
else
begin
rightBuf[rcnt] := j;
rcnt += 1;
end;
end;
if (lcnt < fMinSamplesLeaf) or (rcnt < fMinSamplesLeaf) then
continue;
// создаём массивы нужного размера
var leftIdx := new integer[lcnt];
var rightIdx := new integer[rcnt];
for var i1 := 0 to lcnt - 1 do
leftIdx[i1] := leftBuf[i1];
for var i1 := 0 to rcnt - 1 do
rightIdx[i1] := rightBuf[i1];
var leftImp := fCriterion.Impurity(y, leftIdx);
var rightImp := fCriterion.Impurity(y, rightIdx);
var score :=
(leftIdx.Length * leftImp + rightIdx.Length * rightImp) / n;
if score < bestScore then
begin
bestScore := score;
bestF := f;
bestT := t;
Result := true;
end;
end;
end;
end;}
// Новая реализация (O(n log n · p)) // Новая реализация (O(n log n · p))
function DecisionTreeCore.FindBestSplit( function DecisionTreeCore.FindBestSplit(
X: Matrix; X: Matrix;
@ -4737,19 +4281,7 @@ begin
fFeatureImportances := new Vector(X.ColCount); fFeatureImportances := new Vector(X.ColCount);
// --- convert to integer labels // --- convert to integer labels
var m := y.Length; var yInt := LabelsToInts(y);
var yInt := new integer[m];
for var i := 0 to m - 1 do
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
yInt[i] := ir;
end;
// --- encode // --- encode
var classes: array of integer; var classes: array of integer;
@ -4947,17 +4479,9 @@ begin
fFeatureImportances := new Vector(X.ColCount); fFeatureImportances := new Vector(X.ColCount);
var indices: array of integer; var indices: array of integer := fRowIndices;
if indices = nil then
// 🔹 Ключевое изменение indices := Arr(0..X.RowCount - 1);
if fRowIndices = nil then
begin
SetLength(indices, X.RowCount);
for var i := 0 to X.RowCount - 1 do
indices[i] := i;
end
else
indices := fRowIndices;
fRoot := BuildTree(X, y, indices, 0); fRoot := BuildTree(X, y, indices, 0);
@ -4978,12 +4502,10 @@ begin
var node := fRoot; var node := fRoot;
while not node.IsLeaf do while not node.IsLeaf do
begin
if X[rowIndex, node.FeatureIndex] <= node.Threshold then if X[rowIndex, node.FeatureIndex] <= node.Threshold then
node := node.Left node := node.Left
else else
node := node.Right; node := node.Right;
end;
Result := node.LeafValue; // для регрессии это уже real Result := node.LeafValue; // для регрессии это уже real
end; end;
@ -5035,7 +4557,6 @@ begin
Result := s; Result := s;
end; end;
//----------------------------- //-----------------------------
// RandomForestBase // RandomForestBase
//----------------------------- //-----------------------------
@ -5364,18 +4885,7 @@ begin
// ЕДИНЫЙ ENCODING (forest-level) // ЕДИНЫЙ ENCODING (forest-level)
// ========================================================= // =========================================================
var yInt := new integer[n]; var yInt := LabelsToInts(y);
for var i := 0 to n - 1 do
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
yInt[i] := ir;
end;
var yEncArr := EncodeLabelsInt(yInt, fIndexToClass); var yEncArr := EncodeLabelsInt(yInt, fIndexToClass);
fClassCount := fIndexToClass.Length; fClassCount := fIndexToClass.Length;
@ -5383,9 +4893,7 @@ begin
if fClassCount < 2 then if fClassCount < 2 then
ArgumentError(ER_NEED_AT_LEAST_TWO_CLASSES); ArgumentError(ER_NEED_AT_LEAST_TWO_CLASSES);
var yEnc := new Vector(n); var yEnc := new Vector(yEncArr);
for var i := 0 to n - 1 do
yEnc[i] := yEncArr[i];
// ========================================================= // =========================================================
@ -6508,21 +6016,10 @@ begin
var nTrain := XTrain.RowCount; var nTrain := XTrain.RowCount;
// ========================================================= // =========================================================
// НОВЫЙ ЕДИНЫЙ ENCODING // Новый Encoding
// ========================================================= // =========================================================
var yTrainInt := new integer[nTrain]; var yTrainInt := LabelsToInts(yTrain);
for var i := 0 to nTrain - 1 do
begin
var r := yTrain[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
yTrainInt[i] := ir;
end;
var yEncoded := EncodeLabelsInt(yTrainInt, fClasses); var yEncoded := EncodeLabelsInt(yTrainInt, fClasses);
@ -6588,19 +6085,15 @@ begin
if useValidation then if useValidation then
begin begin
var nVal := yVal.Length; var yValInt := LabelsToInts(yVal);
yValEncoded := new integer[nVal]; yValEncoded := new integer[yValInt.Length];
for var i := 0 to nVal - 1 do for var i := 0 to yValInt.Length - 1 do
begin begin
var r := yVal[i]; var ir := yValInt[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then if ir not in fClassIndex then
ArgumentError(ER_LABELS_NOT_INTEGER);
if not fClassIndex.ContainsKey(ir) then
ArgumentError(ER_UNKNOWN_CLASS_LABEL, ir); ArgumentError(ER_UNKNOWN_CLASS_LABEL, ir);
yValEncoded[i] := fClassIndex[ir]; yValEncoded[i] := fClassIndex[ir];
@ -7350,7 +6843,7 @@ end;
function KNNBase.SquaredL2(trainRow: integer; XTest: Matrix; testRow: integer): double; function KNNBase.SquaredL2(trainRow: integer; XTest: Matrix; testRow: integer): double;
begin begin
var sum := 0.0; var sum := 0.0;
var d := fXTrain.ColCount; var d := fXTrain.ColCount;
var train := fXTrain; // локальная ссылка var train := fXTrain; // локальная ссылка
@ -7556,18 +7049,7 @@ begin
// ЕДИНЫЙ ENCODING // ЕДИНЫЙ ENCODING
// ========================================================= // =========================================================
var yInt := new integer[n]; var yInt := LabelsToInts(y);
for var i := 0 to n - 1 do
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
yInt[i] := ir;
end;
var classesInt: array of integer; var classesInt: array of integer;
var yEncArr := EncodeLabelsInt(yInt, classesInt); var yEncArr := EncodeLabelsInt(yInt, classesInt);
@ -9364,16 +8846,13 @@ begin
var classToIndex := new Dictionary<integer, integer>; var classToIndex := new Dictionary<integer, integer>;
var uniqueClasses := new List<integer>; var uniqueClasses := new List<integer>;
var yEncoded := new integer[n]; var yEncoded := new integer[n];
var yInt := LabelsToInts(y);
for var i := 0 to n - 1 do for var i := 0 to n - 1 do
begin begin
var r := y[i]; var ir := yInt[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then if ir not in classToIndex then
ArgumentError(ER_LABELS_NOT_INTEGER);
if not classToIndex.ContainsKey(ir) then
begin begin
classToIndex[ir] := uniqueClasses.Count; classToIndex[ir] := uniqueClasses.Count;
uniqueClasses.Add(ir); uniqueClasses.Add(ir);
@ -9454,16 +8933,13 @@ begin
var classToIndex := new Dictionary<integer, integer>; var classToIndex := new Dictionary<integer, integer>;
var uniqueClasses := new List<integer>; var uniqueClasses := new List<integer>;
var yEncoded := new integer[n]; var yEncoded := new integer[n];
var yInt := LabelsToInts(y);
for var i := 0 to n - 1 do for var i := 0 to n - 1 do
begin begin
var r := y[i]; var ir := yInt[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then if ir not in classToIndex then
ArgumentError(ER_LABELS_NOT_INTEGER);
if not classToIndex.ContainsKey(ir) then
begin begin
classToIndex[ir] := uniqueClasses.Count; classToIndex[ir] := uniqueClasses.Count;
uniqueClasses.Add(ir); uniqueClasses.Add(ir);
@ -9710,4 +9186,4 @@ begin
Result := new Normalizer(fNormType); Result := new Normalizer(fNormType);
end; end;
end. end.

View file

@ -20,8 +20,23 @@ type
fFitted: boolean; fFitted: boolean;
procedure ValidateSchema(df: DataFrame); virtual; abstract; procedure ValidateSchema(df: DataFrame); virtual; abstract;
class procedure ValidateFeatureList(features: array of string);
/// Вычисляет итоговый список признаков после всех DataFrame-преобразований.
///
/// Для каждого исходного признака из fFeatures:
/// • пытается получить расширенные столбцы через IColumnExpander;
/// • если расширения нет, использует сам исходный столбец.
///
/// Возвращает только те столбцы, которые реально присутствуют в current,
/// без повторов и в итоговом порядке для построения матрицы признаков.
///
/// Если после preprocessing не осталось ни одного признака,
/// выбрасывается исключение
function ResolveFinalFeatures(current: DataFrame): array of string;
function PrepareMatrix(df: DataFrame; var current: DataFrame): Matrix; function PrepareMatrix(df: DataFrame; var current: DataFrame): Matrix;
function TransformDataFrame(df: DataFrame): DataFrame;
function FitTransformMatrix(var X: Matrix; y: Vector := nil): Matrix; function FitTransformMatrix(var X: Matrix; y: Vector := nil): Matrix;
@ -29,7 +44,7 @@ type
public public
constructor Create; constructor Create;
/// Признак того, что был вызван Fit или FitTransform. /// Признак того, что был вызван Fit.
property IsFitted: boolean read fFitted; property IsFitted: boolean read fFitted;
end; end;
@ -45,11 +60,10 @@ type
/// • Затем — матричные шаги (ITransformer / ISupervisedTransformer). /// • Затем — матричные шаги (ITransformer / ISupervisedTransformer).
/// • Модель (IModel) добавляется последней и может быть только одна. /// • Модель (IModel) добавляется последней и может быть только одна.
/// ///
/// В режиме с моделью используются: /// В конвейере используются:
/// • features — признаки; /// • features — признаки;
/// • target — целевая переменная. /// • target — целевая переменная.
/// ///
/// Если модель не добавлена, DataPipeline работает как чистый DF-конвейер (Fit/Transform/FitTransform)
DataPipeline = class(PipelineBase, IModel) DataPipeline = class(PipelineBase, IModel)
private private
fModel: ISupervisedModel; fModel: ISupervisedModel;
@ -67,7 +81,7 @@ type
/// • IPreprocessor (DataFrame-уровень) /// • IPreprocessor (DataFrame-уровень)
/// • ITransformer (Matrix-уровень) /// • ITransformer (Matrix-уровень)
/// • ISupervisedModel (модель, должна быть последней) /// • ISupervisedModel (модель, должна быть последней)
/// Запрещено добавлять шаги после вызова Fit/FitTransform. /// Запрещено добавлять шаги после вызова Fit.
function Add(step: IPipelineStep): DataPipeline; function Add(step: IPipelineStep): DataPipeline;
/// Строит конвейер из шагов обработки данных и модели. /// Строит конвейер из шагов обработки данных и модели.
@ -90,16 +104,18 @@ type
/// затем обучает матричные трансформеры и модель. /// затем обучает матричные трансформеры и модель.
function Fit(df: DataFrame): DataPipeline; function Fit(df: DataFrame): DataPipeline;
/// Применяет обученные DataFrame-шаги к DataFrame и возвращает новый DataFrame. /// Применяет только обученные DataFrame-шаги pipeline и возвращает новый DataFrame.
/// В ML-режиме Transform не выполняет матричные шаги и не вызывает модель. ///
/// Матричные шаги и модель при этом не используются.
/// Метод предназначен в основном для просмотра и анализа промежуточного
/// результата preprocessing.
///
/// Полученный DataFrame обычно не следует повторно подавать в тот же pipeline,
/// так как DataFrame-преобразования будут выполнены ещё раз.
function Transform(df: DataFrame): DataFrame; function Transform(df: DataFrame): DataFrame;
/// Выполняет Fit и Transform за один проход для DataFrame-режима.
/// Недоступен, если конвейер содержит модель (IModel).
function FitTransform(df: DataFrame): DataFrame;
/// Делает предсказание модели для объектов из DataFrame. /// Делает предсказание модели для объектов из DataFrame.
/// Доступен только если конвейер содержит модель (IModel) и был обучен (Fit). /// Доступен после обучения конвейера (Fit).
function Predict(df: DataFrame): Vector; function Predict(df: DataFrame): Vector;
/// Возвращает матрицу вероятностей (nSamples × nClasses). /// Возвращает матрицу вероятностей (nSamples × nClasses).
@ -133,8 +149,6 @@ type
/// В конвейере используются: /// В конвейере используются:
/// • features — признаки (целевая переменная отсутствует). /// • features — признаки (целевая переменная отсутствует).
/// ///
/// Если модель не добавлена, UDataPipeline работает как чистый DF-конвейер
/// (Fit/Transform/FitTransform)
UDataPipeline = class(PipelineBase, IModel) UDataPipeline = class(PipelineBase, IModel)
private private
fModel: IUnsupervisedModel; fModel: IUnsupervisedModel;
@ -161,13 +175,16 @@ type
/// Обучает конвейер на DataFrame. /// Обучает конвейер на DataFrame.
function Fit(df: DataFrame): UDataPipeline; function Fit(df: DataFrame): UDataPipeline;
/// Применяет обученные DataFrame-шаги к DataFrame и возвращает новый DataFrame. /// Применяет только обученные DataFrame-шаги pipeline и возвращает новый DataFrame.
///
/// Матричные шаги и модель при этом не используются.
/// Метод предназначен в основном для просмотра и анализа промежуточного
/// результата preprocessing.
///
/// Полученный DataFrame обычно не следует повторно подавать в тот же pipeline,
/// так как DataFrame-преобразования будут выполнены ещё раз.
function Transform(df: DataFrame): DataFrame; function Transform(df: DataFrame): DataFrame;
/// Выполняет Fit и Transform за один проход для DataFrame-режима.
/// Недоступен, если конвейер содержит модель (IModel).
function FitTransform(df: DataFrame): DataFrame;
/// Обучает конвейер и сразу возвращает метки кластеров. /// Обучает конвейер и сразу возвращает метки кластеров.
/// ///
/// Выполняет полный pipeline: /// Выполняет полный pipeline:
@ -177,7 +194,7 @@ type
/// Используется для задач кластеризации, где нет разделения на train/test. /// Используется для задач кластеризации, где нет разделения на train/test.
/// ///
/// Доступен только если: /// Доступен только если:
/// • конвейер содержит модель кластеризации (IClusterer). /// • конечная модель поддерживает кластеризацию (IClusterer).
function FitPredict(df: DataFrame): array of integer; function FitPredict(df: DataFrame): array of integer;
/// Делает предсказание модели для объектов из DataFrame. /// Делает предсказание модели для объектов из DataFrame.
@ -189,8 +206,7 @@ type
/// затем применяет обученную модель кластеризации. /// затем применяет обученную модель кластеризации.
/// ///
/// Доступен только если: /// Доступен только если:
/// • конвейер содержит модель (IModel); /// • конечная модель поддерживает кластеризацию (IClusterer);
/// • модель поддерживает кластеризацию (IClusterer);
/// • конвейер был обучен (Fit). /// • конвейер был обучен (Fit).
function PredictLabels(df: DataFrame): array of integer; function PredictLabels(df: DataFrame): array of integer;
@ -219,11 +235,6 @@ const
'Матричный шаг не может идти после модели!!Matrix step cannot appear after the model'; 'Матричный шаг не может идти после модели!!Matrix step cannot appear after the model';
ER_DATAPIPE_UNKNOWN_STEP_TYPE = ER_DATAPIPE_UNKNOWN_STEP_TYPE =
'Неизвестный тип шага конвейера: {0}!!Unknown pipeline step type: {0}'; 'Неизвестный тип шага конвейера: {0}!!Unknown pipeline step type: {0}';
ER_TO_VECTOR_NON_NUMERIC =
'ToVector: столбец "{0}" содержит нечисловые или NA значения!!' +
'ToVector: column "{0}" contains non-numeric or NA values';
ER_FITTRANSFORM_WITH_MODEL =
'FitTransform недоступен, если конвейер содержит модель!!FitTransform is not available when the pipeline contains a model';
ER_PROBA_NOT_SUPPORTED = ER_PROBA_NOT_SUPPORTED =
'Модель не поддерживает предсказание вероятностей!!Model does not support probability prediction'; 'Модель не поддерживает предсказание вероятностей!!Model does not support probability prediction';
ER_TARGET_EMPTY = ER_TARGET_EMPTY =
@ -275,6 +286,8 @@ const
'LabelEncoder нельзя применять к целевой переменной — кодирование выполняется внутри модели!!LabelEncoder cannot be applied to target — encoding is handled internally by the model'; 'LabelEncoder нельзя применять к целевой переменной — кодирование выполняется внутри модели!!LabelEncoder cannot be applied to target — encoding is handled internally by the model';
ER_ENCODELABELS_NOT_CATEGORICAL = ER_ENCODELABELS_NOT_CATEGORICAL =
'Целевой столбец должен быть категориальным для задач классификации!!Target column must be categorical for classification tasks'; 'Целевой столбец должен быть категориальным для задач классификации!!Target column must be categorical for classification tasks';
ER_REGRESSION_TARGET_MUST_BE_NUMERIC =
'Целевой столбец "{0}" должен быть числовым для задач регрессии!!Target column "{0}" must be numeric for regression tasks';
ER_PIPELINE_TARGET_TRANSFORM_NOT_ALLOWED = ER_PIPELINE_TARGET_TRANSFORM_NOT_ALLOWED =
'Преобразование целевой переменной "{0}" запрещено в DataPipeline!!' + 'Преобразование целевой переменной "{0}" запрещено в DataPipeline!!' +
'Transformation of target variable "{0}" is not allowed in DataPipeline'; 'Transformation of target variable "{0}" is not allowed in DataPipeline';
@ -312,24 +325,30 @@ begin
fFitted := false; fFitted := false;
end; end;
function PipelineBase.PrepareMatrix(df: DataFrame; var current: DataFrame): Matrix; class procedure PipelineBase.ValidateFeatureList(features: array of string);
begin begin
if df = nil then if (features = nil) or (features.Length = 0) then
ArgumentNullError(ER_ARG_NULL, 'df'); ArgumentError(ER_FEATURES_EMPTY);
current := df; var seen := new HashSet<string>;
// --- schema foreach var f in features do
ValidateSchema(current);
// --- DataFrame steps
for var i := 0 to fDataSteps.Count - 1 do
begin begin
fDataSteps[i] := fDataSteps[i].Fit(current); if (f = nil) or (f = '') then
current := fDataSteps[i].Transform(current); ArgumentError(ER_FEATURE_EMPTY);
end;
if f in seen then
ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f);
seen.Add(f);
end;
end;
function PipelineBase.ResolveFinalFeatures(current: DataFrame): array of string;
begin
if current = nil then
ArgumentNullError(ER_ARG_NULL, 'current');
// --- features
var feats := new List<string>; var feats := new List<string>;
foreach var f in fFeatures do foreach var f in fFeatures do
@ -348,7 +367,7 @@ begin
begin begin
foreach var c in cols do foreach var c in cols do
if current.HasColumn(c) then if current.HasColumn(c) then
if not feats.Contains(c) then if c not in feats then
feats.Add(c); feats.Add(c);
expanded := true; expanded := true;
@ -361,18 +380,52 @@ begin
// fallback: обычный столбец // fallback: обычный столбец
if current.HasColumn(f) then if current.HasColumn(f) then
if not feats.Contains(f) then if f not in feats then
feats.Add(f); feats.Add(f);
end; end;
if feats.Count = 0 then if feats.Count = 0 then
ArgumentError(ER_PIPELINE_NO_FEATURES); ArgumentError(ER_PIPELINE_NO_FEATURES);
fFinalFeatures := feats.ToArray; Result := feats.ToArray;
end;
function PipelineBase.PrepareMatrix(df: DataFrame; var current: DataFrame): Matrix;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
current := df;
// --- schema
ValidateSchema(current);
// --- DataFrame steps
for var i := 0 to fDataSteps.Count - 1 do
begin
fDataSteps[i] := fDataSteps[i].Fit(current);
current := fDataSteps[i].Transform(current);
end;
fFinalFeatures := ResolveFinalFeatures(current);
Result := current.ToMatrix(fFinalFeatures); Result := current.ToMatrix(fFinalFeatures);
end; end;
function PipelineBase.TransformDataFrame(df: DataFrame): DataFrame;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
var current := df;
foreach var s in fDataSteps do
current := s.Transform(current);
Result := current;
end;
function PipelineBase.FitTransformMatrix(var X: Matrix; y: Vector): Matrix; function PipelineBase.FitTransformMatrix(var X: Matrix; y: Vector): Matrix;
begin begin
for var i := 0 to fMatrixSteps.Count - 1 do for var i := 0 to fMatrixSteps.Count - 1 do
@ -416,12 +469,9 @@ end;
constructor DataPipeline.Create; constructor DataPipeline.Create;
begin begin
fDataSteps := new List<IPreprocessor>; inherited Create;
fMatrixSteps := new List<ITransformer>;
fModel := nil; fModel := nil;
fTarget := ''; fTarget := '';
fFeatures := nil;
fFitted := false;
end; end;
function DataPipeline.Add(step: IPipelineStep): DataPipeline; function DataPipeline.Add(step: IPipelineStep): DataPipeline;
@ -493,25 +543,12 @@ begin
if (target = nil) or (target = '') then if (target = nil) or (target = '') then
ArgumentError(ER_TARGET_EMPTY); ArgumentError(ER_TARGET_EMPTY);
if (features = nil) or (features.Length = 0) then ValidateFeatureList(features);
ArgumentError(ER_FEATURES_EMPTY);
var seen := new HashSet<string>;
foreach var f in features do foreach var f in features do
begin
if (f = nil) or (f = '') then
ArgumentError(ER_FEATURE_EMPTY);
if f = target then if f = target then
ArgumentError(ER_DATAPIPE_TARGET_IN_FEATURES, target); ArgumentError(ER_DATAPIPE_TARGET_IN_FEATURES, target);
if seen.Contains(f) then
ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f);
seen.Add(f);
end;
if (steps = nil) or (Length(steps) = 0) then if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS); ArgumentError(ER_PIPELINE_NO_STEPS);
@ -604,38 +641,7 @@ begin
Result := Self; Result := Self;
end; end;
function DataPipeline.Transform(df: DataFrame): DataFrame; function DataPipeline.Transform(df: DataFrame): DataFrame := TransformDataFrame(df);
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
var current := df;
foreach var s in fDataSteps do
current := s.Transform(current);
Result := current;
end;
function DataPipeline.FitTransform(df: DataFrame): DataFrame;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if fModel <> nil then
ArgumentError(ER_FITTRANSFORM_WITH_MODEL);
var current := df;
for var i := 0 to fDataSteps.Count - 1 do
begin
fDataSteps[i] := fDataSteps[i].Fit(current);
current := fDataSteps[i].Transform(current);
end;
fFitted := true;
Result := current;
end;
function DataPipeline.Predict(df: DataFrame): Vector; function DataPipeline.Predict(df: DataFrame): Vector;
begin begin
@ -699,8 +705,7 @@ begin
if fTarget = '' then if fTarget = '' then
ArgumentError(ER_TARGET_EMPTY); ArgumentError(ER_TARGET_EMPTY);
if (fFeatures = nil) or (Length(fFeatures) = 0) then ValidateFeatureList(fFeatures);
ArgumentError(ER_FEATURES_EMPTY);
// target не должен входить в features // target не должен входить в features
for var i := 0 to High(fFeatures) do for var i := 0 to High(fFeatures) do
@ -714,29 +719,28 @@ begin
ArgumentError(ER_DATAPIPE_TARGET_NOT_FOUND, fTarget, cols); ArgumentError(ER_DATAPIPE_TARGET_NOT_FOUND, fTarget, cols);
end; end;
if fTask = tkClassification then case fTask of
if not df.IsCategorical(fTarget) then tkClassification:
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, fTarget); if not df.IsCategorical(fTarget) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, fTarget);
var seen := new HashSet<string>;
tkRegression:
begin
var ct := df.GetColumnType(fTarget);
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
ArgumentError(ER_REGRESSION_TARGET_MUST_BE_NUMERIC, fTarget);
end;
end;
for var i := 0 to High(fFeatures) do for var i := 0 to High(fFeatures) do
begin begin
var f := fFeatures[i]; var f := fFeatures[i];
if f = '' then
ArgumentError(ER_FEATURE_EMPTY);
if not df.Schema.HasColumn(f) then if not df.Schema.HasColumn(f) then
begin begin
var cols := df.Schema.ColumnNames.JoinToString(', '); var cols := df.Schema.ColumnNames.JoinToString(', ');
ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, f, cols); ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, f, cols);
end; end;
if seen.Contains(f) then
ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f);
seen.Add(f);
end; end;
end; end;
@ -804,11 +808,8 @@ end;
constructor UDataPipeline.Create; constructor UDataPipeline.Create;
begin begin
fDataSteps := new List<IPreprocessor>; inherited Create;
fMatrixSteps := new List<ITransformer>;
fModel := nil; fModel := nil;
fFeatures := nil;
fFitted := false;
end; end;
function UDataPipeline.Add(step: IPipelineStep): UDataPipeline; function UDataPipeline.Add(step: IPipelineStep): UDataPipeline;
@ -856,21 +857,7 @@ end;
class function UDataPipeline.Build(features: array of string; class function UDataPipeline.Build(features: array of string;
params steps: array of IPipelineStep): UDataPipeline; params steps: array of IPipelineStep): UDataPipeline;
begin begin
if (features = nil) or (features.Length = 0) then ValidateFeatureList(features);
ArgumentError(ER_FEATURES_EMPTY);
var seen := new HashSet<string>;
foreach var f in features do
begin
if (f = nil) or (f = '') then
ArgumentError(ER_FEATURE_EMPTY);
if seen.Contains(f) then
ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f);
seen.Add(f);
end;
if (steps = nil) or (Length(steps) = 0) then if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS); ArgumentError(ER_PIPELINE_NO_STEPS);
@ -921,38 +908,7 @@ begin
Result := Self; Result := Self;
end; end;
function UDataPipeline.Transform(df: DataFrame): DataFrame; function UDataPipeline.Transform(df: DataFrame): DataFrame := TransformDataFrame(df);
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
var current := df;
foreach var s in fDataSteps do
current := s.Transform(current);
Result := current;
end;
function UDataPipeline.FitTransform(df: DataFrame): DataFrame;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if fModel <> nil then
ArgumentError(ER_FITTRANSFORM_WITH_MODEL);
var current := df;
for var i := 0 to fDataSteps.Count - 1 do
begin
fDataSteps[i] := fDataSteps[i].Fit(current);
current := fDataSteps[i].Transform(current);
end;
fFitted := true;
Result := current;
end;
function UDataPipeline.FitPredict(df: DataFrame): array of integer; function UDataPipeline.FitPredict(df: DataFrame): array of integer;
begin begin
@ -978,27 +934,7 @@ begin
end; end;
// --- 2) вычислить финальные признаки // --- 2) вычислить финальные признаки
var feats := new List<string>; fFinalFeatures := ResolveFinalFeatures(current);
foreach var f in fFeatures do
begin
if current.HasColumn(f) then
begin
feats.Add(f);
continue;
end;
// искать производные признаки (например, OneHotEncoder)
foreach var c in current.Schema.ColumnNames do
if c.StartsWith(f + '_') then
if not feats.Contains(c) then
feats.Add(c);
end;
if feats.Count = 0 then
ArgumentError(ER_PIPELINE_NO_FEATURES);
fFinalFeatures := feats.ToArray;
var X := current.ToMatrix(fFinalFeatures); var X := current.ToMatrix(fFinalFeatures);
@ -1100,28 +1036,17 @@ end;
procedure UDataPipeline.ValidateSchema(df: DataFrame); procedure UDataPipeline.ValidateSchema(df: DataFrame);
begin begin
if (fFeatures = nil) or (Length(fFeatures) = 0) then ValidateFeatureList(fFeatures);
ArgumentError(ER_FEATURES_EMPTY);
var seen := new HashSet<string>;
for var i := 0 to High(fFeatures) do for var i := 0 to High(fFeatures) do
begin begin
var f := fFeatures[i]; var f := fFeatures[i];
if f = '' then
ArgumentError(ER_FEATURE_EMPTY);
if not df.Schema.HasColumn(f) then if not df.Schema.HasColumn(f) then
begin begin
var cols := df.Schema.ColumnNames.JoinToString(', '); var cols := df.Schema.ColumnNames.JoinToString(', ');
ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, f, cols); ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, f, cols);
end; end;
if seen.Contains(f) then
ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f);
seen.Add(f);
end; end;
end; end;
@ -1179,4 +1104,4 @@ begin
Result := p; Result := p;
end; end;
end. end.

View file

@ -97,9 +97,11 @@ uses MLExceptions;
const const
ER_LABELS_NULL = ER_LABELS_NULL =
'y не может быть nil!!y cannot be nil'; '{0} не может быть nil!!{0} cannot be nil';
ER_LABELS_ARRAY_NULL = ER_LABELS_ARRAY_NULL =
'labels не может быть nil!!labels cannot be nil'; 'labels не может быть nil!!labels cannot be nil';
ER_LABELS_NOT_INTEGER =
'Метки классов должны быть целыми числами!!Class labels must be integers';
ER_UNKNOWN_CLASS_IN_TRANSFORM = ER_UNKNOWN_CLASS_IN_TRANSFORM =
'Неизвестное значение класса "{0}" при преобразовании меток!!Unknown class value "{0}" in TransformLabels'; 'Неизвестное значение класса "{0}" при преобразовании меток!!Unknown class value "{0}" in TransformLabels';
ER_LABEL_INDEX_OUT_OF_RANGE = ER_LABEL_INDEX_OUT_OF_RANGE =
@ -128,7 +130,15 @@ begin
Result := new integer[y.Length]; Result := new integer[y.Length];
for var i := 0 to y.Length - 1 do for var i := 0 to y.Length - 1 do
Result[i] := Round(y[i]); begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
Result[i] := ir;
end;
end; end;
function IntsToLabels(a: array of integer): Vector; function IntsToLabels(a: array of integer): Vector;
@ -151,7 +161,7 @@ begin
for var i := 0 to labels.Length - 1 do for var i := 0 to labels.Length - 1 do
begin begin
var lbl := labels[i]; var lbl := labels[i];
if not map.ContainsKey(lbl) then if lbl not in map then
begin begin
map[lbl] := classList.Count; map[lbl] := classList.Count;
classList.Add(lbl); classList.Add(lbl);
@ -182,7 +192,7 @@ begin
begin begin
var lbl := labels[i]; var lbl := labels[i];
if not map.ContainsKey(lbl) then if lbl not in map then
Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, lbl); Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, lbl);
res[i] := map[lbl]; res[i] := map[lbl];
@ -210,7 +220,7 @@ begin
begin begin
var v := labels[i]; var v := labels[i];
if not map.ContainsKey(v) then if v not in map then
Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, v); Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, v);
res[i] := map[v]; res[i] := map[v];
@ -237,7 +247,7 @@ begin
for var i := 0 to labels.Length - 1 do for var i := 0 to labels.Length - 1 do
begin begin
var lbl := labels[i]; var lbl := labels[i];
if not map.ContainsKey(lbl) then if lbl not in map then
begin begin
map[lbl] := classList.Count; map[lbl] := classList.Count;
classList.Add(lbl); classList.Add(lbl);
@ -276,7 +286,7 @@ begin
var res := new List<string>; var res := new List<string>;
for var i := 0 to labels.Length - 1 do for var i := 0 to labels.Length - 1 do
if not seen.Contains(labels[i]) then if labels[i] not in seen then
begin begin
seen.Add(labels[i]); seen.Add(labels[i]);
res.Add(labels[i]); res.Add(labels[i]);
@ -298,4 +308,4 @@ begin
DimensionError(ER_DIM_MISMATCH, a.Length, b.Length); DimensionError(ER_DIM_MISMATCH, a.Length, b.Length);
end; end;
end. end.

View file

@ -53,6 +53,9 @@ type
/// R2 = 1 — идеальная модель, /// R2 = 1 — идеальная модель,
/// R2 = 0 — не лучше среднего, /// R2 = 0 — не лучше среднего,
/// R2 < 0 — хуже константной модели. /// R2 < 0 — хуже константной модели.
///
/// Если дисперсия целевой переменной равна нулю (все значения одинаковы),
/// метрика не определена. В этом случае возвращается 0.0 по соглашению.
static function R2(yTrue, yPred: Vector): real; static function R2(yTrue, yPred: Vector): real;
// ---- Вероятностные метрики классификации ---- // ---- Вероятностные метрики классификации ----
@ -104,14 +107,14 @@ type
/// Важна, когда критичны ложные положительные результаты («ложные тревоги»). /// Важна, когда критичны ложные положительные результаты («ложные тревоги»).
static function Precision(yTrue, yPred: Vector): real; static function Precision(yTrue, yPred: Vector): real;
/// Точность (Precision) для многоклассовой классификации. /// Точность (Precision) с усреднением по классам.
/// Вычисляется для каждого класса отдельно как: /// Вычисляется для каждого класса отдельно как:
/// Precision(c) = TP(c) / (TP(c) + FP(c)), /// Precision(c) = TP(c) / (TP(c) + FP(c)),
/// после чего агрегируется согласно параметру avg: /// а затем результаты объединяются способом, заданным параметром avg:
/// avMacro — среднее по всем классам (каждый класс равновесен); /// avMacro — среднее по всем классам (каждый класс равновесен);
/// avMicro — глобальная метрика по суммарным TP и FP; /// avMicro — глобальная метрика по суммарным TP и FP;
/// avWeighted — среднее с весами, пропорциональными размеру классов. /// avWeighted — среднее с весами, пропорциональными размеру классов.
/// Используется для задач с числом классов больше двух. /// Особенно полезна для многоклассовой классификации.
static function Precision(yTrue, yPred: Vector; avg: Averaging): real; static function Precision(yTrue, yPred: Vector; avg: Averaging): real;
/// Полнота (Recall) для положительного класса (1). /// Полнота (Recall) для положительного класса (1).
@ -122,14 +125,14 @@ type
/// Важна, когда нежелательны пропуски положительных случаев (например, заболеваний). /// Важна, когда нежелательны пропуски положительных случаев (например, заболеваний).
static function Recall(yTrue, yPred: Vector): real; static function Recall(yTrue, yPred: Vector): real;
/// Полнота (Recall) для многоклассовой классификации. /// Полнота (Recall) с усреднением по классам.
/// Вычисляется для каждого класса отдельно как: /// Вычисляется для каждого класса отдельно как:
/// Recall(c) = TP(c) / (TP(c) + FN(c)), /// Recall(c) = TP(c) / (TP(c) + FN(c)),
/// после чего агрегируется согласно параметру avg: /// а затем результаты объединяются способом, заданным параметром avg:
/// avMacro — среднее по всем классам (каждый класс равновесен); /// avMacro — среднее по всем классам (каждый класс равновесен);
/// avMicro — глобальная метрика по суммарным TP и FN; /// avMicro — глобальная метрика по суммарным TP и FN;
/// avWeighted — среднее с весами, пропорциональными размеру классов. /// avWeighted — среднее с весами, пропорциональными размеру классов.
/// Используется для задач с числом классов больше двух. /// Особенно полезна для многоклассовой классификации.
static function Recall(yTrue, yPred: Vector; avg: Averaging): real; static function Recall(yTrue, yPred: Vector; avg: Averaging): real;
/// F1-мера для бинарной классификации. /// F1-мера для бинарной классификации.
@ -140,14 +143,14 @@ type
/// Особенно полезна при несбалансированных классах. /// Особенно полезна при несбалансированных классах.
static function F1(yTrue, yPred: Vector): real; static function F1(yTrue, yPred: Vector): real;
/// F1-мера для многоклассовой классификации. /// F1-мера с усреднением по классам.
/// Вычисляется для каждого класса отдельно как: /// Вычисляется для каждого класса отдельно как:
/// F1(c) = 2 * Precision(c) * Recall(c) / (Precision(c) + Recall(c)), /// F1(c) = 2 * Precision(c) * Recall(c) / (Precision(c) + Recall(c)),
/// после чего агрегируется согласно параметру avg: /// а затем результаты объединяются способом, заданным параметром avg:
/// avMacro — среднее по всем классам (каждый класс равновесен); /// avMacro — среднее по всем классам (каждый класс равновесен);
/// avMicro — вычисляется по суммарным TP, FP и FN; /// avMicro — вычисляется по суммарным TP, FP и FN;
/// avWeighted — среднее с весами, пропорциональными размеру классов. /// avWeighted — среднее с весами, пропорциональными размеру классов.
/// Используется для задач с числом классов больше двух. /// Особенно полезна для многоклассовой классификации.
static function F1(yTrue, yPred: Vector; avg: Averaging): real; static function F1(yTrue, yPred: Vector; avg: Averaging): real;
/// Специфичность (Specificity, True Negative Rate) для бинарной классификации. /// Специфичность (Specificity, True Negative Rate) для бинарной классификации.
@ -212,24 +215,25 @@ type
end; end;
type type
/// Матрица ошибок (Confusion Matrix). /// Матрица ошибок (Confusion Matrix) для бинарной и многоклассовой классификации.
/// Представляет собой квадратную матрицу размера K × K, ///
/// где K — число классов. /// Показывает, сколько объектов каждого класса
/// Элемент [i, j] содержит количество объектов, /// модель распознала правильно и сколько перепутала.
/// истинный класс которых равен i, ///
/// а предсказанный — j. /// На вход можно передавать любые целочисленные метки классов
/// Используется для анализа качества классификатора /// (например, 0, 1, 2 или 2, 5, 8).
/// и вычисления метрик (Accuracy, Precision, Recall, F1 и др.). ///
/// Поддерживает как бинарную, так и многоклассовую классификацию. /// При построении матрицы все метки автоматически нумеруются
/// Для бинарной классификации дополнительно доступны /// числами от 0 до K-1, где K — число различных классов.
/// агрегированные показатели: TP, TN, FP, FN. ///
/// /// Элемент матрицы [i, j] показывает, сколько объектов
/// Ожидается, что yTrue и yPred содержат закодированные метки классов /// класса i модель предсказала как класс j.
/// (целые значения 0..K-1, допускается хранение в Vector типа real). ///
/// Для строковых меток необходимо предварительно применить EncodeLabels /// Свойства Labels, GetLabel и GetIndex позволяют узнать,
/// /// как новые внутренние номера классов связаны с исходными метками.
/// Метки классов упорядочиваются по возрастанию для обеспечения ///
/// стабильного и предсказуемого представления результатов /// Для бинарной классификации дополнительно доступны
/// показатели TP, TN, FP, FN, Precision, Recall, F1, Specificity и BalancedAccuracy
ConfusionMatrix = class ConfusionMatrix = class
private private
fMatrix: array[,] of integer; fMatrix: array[,] of integer;
@ -248,76 +252,87 @@ type
procedure EnsureClassIndex(c: integer); procedure EnsureClassIndex(c: integer);
public public
/// Создаёт матрицу ошибок по истинным меткам yTrue и предсказаниям модели yPred. /// Создаёт матрицу ошибок по истинным меткам yTrue и предсказаниям модели yPred.
/// Векторы должны иметь одинаковую длину и содержать метки классов, ///
/// представленные целыми числами (0, 1, 2, ...). /// Векторы должны иметь одинаковую длину и содержать целочисленные метки классов.
/// Автоматически определяет множество классов и строит ///
/// матрицу размера K × K, где K — число различных классов. /// Можно использовать любые целые метки классов. При построении матрицы они
/// Элемент [i, j] содержит количество объектов, /// автоматически перенумеровываются числами от 0 до K-1, где K — число
/// истинный класс которых равен i, /// различных классов.
/// а предсказанный — j. ///
/// Для бинарной классификации дополнительно доступны агрегированные /// Элемент [i, j] показывает, сколько объектов класса i модель предсказала
/// значения TP, TN, FP и FN /// как класс j.
///
/// Для бинарной классификации дополнительно доступны значения TP, TN, FP и FN
constructor Create(yTrue, yPred: Vector); constructor Create(yTrue, yPred: Vector);
/// Возвращает исходную метку класса по внутреннему индексу c. /// Возвращает исходную метку класса по его внутреннему номеру.
/// c — индекс класса в диапазоне [0, ClassCount). ///
/// Результат — метка класса (например, 2, 5, 8), соответствующая этому индексу. /// Внутренние номера классов используются внутри матрицы ошибок
/// Используется для интерпретации результатов, так как внутренние индексы /// и находятся в диапазоне от 0 до ClassCount-1.
/// могут не совпадать с исходными значениями меток. ///
/// Метод позволяет узнать, какой исходной метке соответствует
/// данный внутренний номер.
function GetLabel(c: integer): integer; function GetLabel(c: integer): integer;
/// Возвращает внутренний индекс класса по его метке label. /// Возвращает внутренний номер класса по его исходной метке.
/// label — исходная метка класса (например, 2, 5, 8). ///
/// Результат — индекс класса в диапазоне [0, ClassCount). /// Результат находится в диапазоне от 0 до ClassCount-1
/// Используется для преобразования внешних меток в внутренние индексы, /// и используется для обращения к элементам матрицы ошибок.
/// применяемые внутри матрицы ошибок. ///
/// Если такой метки нет, выбрасывается исключение.
function GetIndex(labl: integer): integer; function GetIndex(labl: integer): integer;
/// Массив меток классов в порядке внутренних индексов. /// Исходные метки классов в порядке внутренних номеров.
/// Labels[c] — исходная метка класса для индекса c. ///
/// Labels[c] — это исходная метка класса,
/// соответствующая внутреннему номеру c.
property Labels: array of integer read fLabels; property Labels: array of integer read fLabels;
/// Число различных классов в данных. /// Число различных классов, встретившихся в yTrue или yPred.
/// Определяется автоматически по объединению меток из yTrue и yPred. ///
/// Используется для построения матрицы размера K × K. /// Используется для построения матрицы ошибок размера K × K, где K = ClassCount.
property ClassCount: integer read fClassCount; property ClassCount: integer read fClassCount;
/// Число истинно положительных предсказаний для класса c. /// Число истинно положительных предсказаний для класса c.
/// TP(c) — количество объектов, для которых истинный и предсказанный класс равны c. /// Здесь c — номер класса в матрице ошибок.
/// Соответствует элементу матрицы [c, c]. /// Соответствует элементу матрицы [c, c].
function TPForClass(c: integer): integer; function TPForClass(c: integer): integer;
/// Число ложно положительных предсказаний для класса c. /// Число ложно положительных предсказаний для класса c.
/// Здесь c — номер класса в матрице ошибок.
/// FP(c) — количество объектов, предсказанных как класс c, /// FP(c) — количество объектов, предсказанных как класс c,
/// но в действительности принадлежащих другим классам. /// но в действительности принадлежащих другим классам.
/// Вычисляется как сумма по столбцу c минус TP(c). /// Вычисляется как сумма по столбцу c минус TP(c).
function FPForClass(c: integer): integer; function FPForClass(c: integer): integer;
/// Число ложно отрицательных предсказаний для класса c. /// Число ложно отрицательных предсказаний для класса c.
/// Здесь c — номер класса в матрице ошибок.
/// FN(c) — количество объектов, принадлежащих классу c, /// FN(c) — количество объектов, принадлежащих классу c,
/// но предсказанных как другие классы. /// но предсказанных как другие классы.
/// Вычисляется как сумма по строке c минус TP(c). /// Вычисляется как сумма по строке c минус TP(c).
function FNForClass(c: integer): integer; function FNForClass(c: integer): integer;
/// Число истинно положительных предсказаний (TP) для бинарной классификации. /// Число истинно положительных предсказаний (TP) для бинарной классификации.
/// Количество объектов, для которых истинный и предсказанный класс равны 1. /// Показывает, сколько объектов положительного класса
/// модель правильно распознала как положительные.
/// Доступно только при числе классов, равном 2. /// Доступно только при числе классов, равном 2.
property TP: integer read GetTPBinary; property TP: integer read GetTPBinary;
/// Число ложно положительных предсказаний (FP) для бинарной классификации. /// Число ложно положительных предсказаний (FP) для бинарной классификации.
/// Количество объектов, предсказанных как класс 1, /// Показывает, сколько объектов отрицательного класса
/// но в действительности принадлежащих классу 0. /// модель ошибочно отнесла к положительному классу.
/// Доступно только при числе классов, равном 2. /// Доступно только при числе классов, равном 2.
property FP: integer read GetFPBinary; property FP: integer read GetFPBinary;
/// Число ложно отрицательных предсказаний (FN) для бинарной классификации. /// Число ложно отрицательных предсказаний (FN) для бинарной классификации.
/// Количество объектов, принадлежащих классу 1, /// Показывает, сколько объектов положительного класса
/// но предсказанных как класс 0. /// модель ошибочно отнесла к отрицательному классу.
/// Доступно только при числе классов, равном 2. /// Доступно только при числе классов, равном 2.
property FN: integer read GetFNBinary; property FN: integer read GetFNBinary;
/// Число истинно отрицательных предсказаний (TN) для бинарной классификации. /// Число истинно отрицательных предсказаний (TN) для бинарной классификации.
/// Количество объектов, для которых истинный и предсказанный класс равны 0. /// Показывает, сколько объектов отрицательного класса
/// модель правильно распознала как отрицательные.
/// Доступно только при числе классов, равном 2. /// Доступно только при числе классов, равном 2.
property TN: integer read GetTNBinary; property TN: integer read GetTNBinary;
@ -483,6 +498,9 @@ type
/// Используется только для бинарной классификации. /// Используется только для бинарной классификации.
static function AUC(yTrue, yPred: Vector): real := Metrics.AUC(yTrue, yPred); static function AUC(yTrue, yPred: Vector): real := Metrics.AUC(yTrue, yPred);
/// Формирует текстовый отчёт по качеству классификации.
/// Для каждого класса выводит Precision, Recall, F1 и число объектов этого класса.
/// Также выводит усреднённые показатели по всем классам.
static function ClassificationReport(yTrue, yPred: Vector): string := Metrics.ClassificationReport(yTrue, yPred); static function ClassificationReport(yTrue, yPred: Vector): string := Metrics.ClassificationReport(yTrue, yPred);
end; end;
@ -527,7 +545,10 @@ type
/// 1.0 — идеальная модель /// 1.0 — идеальная модель
/// 0.0 — модель не лучше среднего /// 0.0 — модель не лучше среднего
/// <0 — модель хуже простого среднего /// <0 — модель хуже простого среднего
static function R2(yTrue, yPred: Vector): real := Metrics.R2(yTrue, yPred); ///
/// Если дисперсия целевой переменной равна нулю (все значения одинаковы),
/// метрика не определена. В этом случае возвращается 0.0 по соглашению.
static function R2(yTrue, yPred: Vector): real := Metrics.R2(yTrue, yPred);
end; end;
/// Метрики для оценки качества кластеризации /// Метрики для оценки качества кластеризации
@ -601,6 +622,8 @@ const
'Для вычисления Silhouette требуется минимум 2 кластера!!Silhouette requires at least 2 clusters'; 'Для вычисления Silhouette требуется минимум 2 кластера!!Silhouette requires at least 2 clusters';
ER_INVALID_VALUE_AT = ER_INVALID_VALUE_AT =
'Некорректное значение в {0} на позиции {1}!!Invalid value in {0} at index {1}'; 'Некорректное значение в {0} на позиции {1}!!Invalid value in {0} at index {1}';
ER_ARG_NAN_INF =
'Аргумент {0} содержит NaN или Infinity!!Argument {0} contains NaN or Infinity';
ER_CH_NEEDS_2_CLUSTERS = ER_CH_NEEDS_2_CLUSTERS =
'Для Calinski–Harabasz требуется минимум 2 кластера!!Calinski–Harabasz requires at least 2 clusters'; 'Для Calinski–Harabasz требуется минимум 2 кластера!!Calinski–Harabasz requires at least 2 clusters';
ER_CH_NEEDS_N_GT_K = ER_CH_NEEDS_N_GT_K =
@ -715,6 +738,12 @@ begin
var y := yTrue[i]; var y := yTrue[i];
var yp := yPred[i]; var yp := yPred[i];
if double.IsNaN(y) or double.IsInfinity(y) then
ArgumentError(ER_ARG_NAN_INF, 'yTrue');
if double.IsNaN(yp) or double.IsInfinity(yp) then
ArgumentError(ER_ARG_NAN_INF, 'yPred');
if y = 0.0 then continue; if y = 0.0 then continue;
sum += Abs((y - yp) / y); sum += Abs((y - yp) / y);
@ -932,9 +961,6 @@ begin
fprList.Add(fpr); fprList.Add(fpr);
end; end;
fprList.Add(1.0);
tprList.Add(1.0);
Result := (new Vector(fprList.ToArray), Result := (new Vector(fprList.ToArray),
new Vector(tprList.ToArray)); new Vector(tprList.ToArray));
end; end;
@ -1675,9 +1701,9 @@ end;
/// 1 — идеальное совпадение кластеризации и истинных меток /// 1 — идеальное совпадение кластеризации и истинных меток
/// 0 — случайное разбиение /// 0 — случайное разбиение
/// <0 — хуже случайного /// <0 — хуже случайного
/// Сложность: O(n²). При больших n может быть медленным. /// Использует таблицу сопряжённости (contingency table).
/// Для больших выборок рекомендуется использовать альтернативную реализацию /// Сложность: O(n + kT * kP),
/// через таблицу сопряжённости (contingency table) с асимптотикой O(n · k) /// где kT и kP — числа различных классов в yTrue и yPred.
static function Metrics.AdjustedRandIndex(yTrue, yPred: Vector): real; static function Metrics.AdjustedRandIndex(yTrue, yPred: Vector): real;
begin begin
if yTrue = nil then if yTrue = nil then
@ -1697,14 +1723,8 @@ begin
end; end;
// --- Vector -> int[] // --- Vector -> int[]
var yTInt := new integer[n]; var yTInt := LabelsToInts(yTrue);
var yPInt := new integer[n]; var yPInt := LabelsToInts(yPred);
for var i := 0 to n - 1 do
begin
yTInt[i] := Round(yTrue[i]);
yPInt[i] := Round(yPred[i]);
end;
// --- encoding // --- encoding
var classesTrue: array of integer; var classesTrue: array of integer;
@ -1977,7 +1997,7 @@ end;
function ConfusionMatrix.GetIndex(labl: integer): integer; function ConfusionMatrix.GetIndex(labl: integer): integer;
begin begin
if not fLabelToIndex.ContainsKey(labl) then if labl not in fLabelToIndex then
ArgumentError(ER_INVALID_CLASS_LABEL, labl); ArgumentError(ER_INVALID_CLASS_LABEL, labl);
Result := fLabelToIndex[labl]; Result := fLabelToIndex[labl];

View file

@ -36,6 +36,8 @@ type
/// Выполняет Fit и Transform последовательно /// Выполняет Fit и Transform последовательно
function FitTransform(df: DataFrame): DataFrame; function FitTransform(df: DataFrame): DataFrame;
/// Копирует только конфигурацию препроцессора (без обученного состояния).
/// Используется для создания независимых экземпляров препроцессора.
function Clone: IPreprocessor; function Clone: IPreprocessor;
end; end;
@ -72,15 +74,8 @@ type
property ColumnName: string read col; property ColumnName: string read col;
/// Создаёт копию препроцессора с той же конфигурацией. /// Копирует только конфигурацию препроцессора (без обученного состояния).
/// /// Используется для создания независимых экземпляров препроцессора.
/// ВАЖНО:
/// • Clone копирует только конфигурацию (например, имя столбца)
/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.)
///
/// Назначение:
/// • использование в Pipeline и CrossValidate
/// • обеспечивает независимое переобучение препроцессоров
function Clone: IPreprocessor; function Clone: IPreprocessor;
end; end;
@ -118,15 +113,8 @@ type
function GetExpandedColumns(sourceColumn: string): array of string; function GetExpandedColumns(sourceColumn: string): array of string;
/// Создаёт копию препроцессора с той же конфигурацией. /// Копирует только конфигурацию препроцессора (без обученного состояния).
/// /// Используется для создания независимых экземпляров препроцессора.
/// ВАЖНО:
/// • Clone копирует только конфигурацию (например, имя столбца)
/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.)
///
/// Назначение:
/// • использование в Pipeline и CrossValidate
/// • обеспечивает независимое переобучение препроцессоров
function Clone: IPreprocessor; function Clone: IPreprocessor;
end; end;
@ -171,15 +159,8 @@ type
property Columns: array of string read cols; property Columns: array of string read cols;
/// Создаёт копию препроцессора с той же конфигурацией. /// Копирует только конфигурацию препроцессора (без обученного состояния).
/// /// Используется для создания независимых экземпляров препроцессора.
/// ВАЖНО:
/// • Clone копирует только конфигурацию (например, имя столбца)
/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.)
///
/// Назначение:
/// • использование в Pipeline и CrossValidate
/// • обеспечивает независимое переобучение препроцессоров
function Clone: IPreprocessor; function Clone: IPreprocessor;
end; end;
@ -698,7 +679,7 @@ begin
res := res.ReplaceColumnInt( res := res.ReplaceColumnInt(
name, name,
c -> (if c.IsValid(idx) then c.Int(idx) else k) c -> (if c.IsValid(capturedIdx) then c.Int(capturedIdx) else k)
); );
end end
else // ctFloat else // ctFloat
@ -789,4 +770,4 @@ begin
end; end;
end; end;
end. end.

View file

@ -96,6 +96,9 @@ type
/// • лучший параметр, /// • лучший параметр,
/// • лучшее среднее значение метрики, /// • лучшее среднее значение метрики,
/// • модель, обученная на всём датасете с лучшим параметром /// • модель, обученная на всём датасете с лучшим параметром
///
/// Все параметры оцениваются на одном и том же разбиении данных
/// (используется фиксированный seed), что обеспечивает корректное и сопоставимое сравнение моделей
class function Search<T, P>( class function Search<T, P>(
modelFactory: P -> T; modelFactory: P -> T;
paramValues: array of P; paramValues: array of P;
@ -460,17 +463,6 @@ begin
if (k < 2) or (k > X.RowCount) then if (k < 2) or (k > X.RowCount) then
ArgumentError(ER_K_INVALID_STRATIFIED, k, X.RowCount); ArgumentError(ER_K_INVALID_STRATIFIED, k, X.RowCount);
var 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 := var baseSeed :=
if seed >= 0 then seed if seed >= 0 then seed
else System.Environment.TickCount and integer.MaxValue; else System.Environment.TickCount and integer.MaxValue;