From a5a96bc68cccb6b19584572d0ab2f3054878b6be Mon Sep 17 00:00:00 2001 From: Mikhalkovich Stanislav Date: Mon, 27 Apr 2026 09:34:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20ML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Configuration/GlobalAssemblyInfo.cs | 2 +- Configuration/Version.defs | 2 +- Release/pabcversion.txt | 2 +- ReleaseGenerators/PascalABCNET_version.nsh | 2 +- .../convertion_data_and_alghoritms.cs | 2 +- VisualPascalABCNET/FileMonitoring.cs | 27 +- .../Workbench/FileOperations.cs | 7 +- bin/Lib/DataAdapters.pas | 5 +- bin/Lib/DataFrameABCCore.pas | 30 +- bin/Lib/MLABC.pas | 12 +- bin/Lib/MLExceptions.pas | 3 + bin/Lib/MLModelsABC.pas | 680 ++---------------- bin/Lib/MLPipelineABC.pas | 309 +++----- bin/Lib/MLUtilsABC.pas | 26 +- bin/Lib/MetricsABC.pas | 170 +++-- bin/Lib/PreprocessorABC.pas | 39 +- bin/Lib/ValidationML.pas | 14 +- 17 files changed, 380 insertions(+), 952 deletions(-) diff --git a/Configuration/GlobalAssemblyInfo.cs b/Configuration/GlobalAssemblyInfo.cs index de1b4c426..64fe7363e 100644 --- a/Configuration/GlobalAssemblyInfo.cs +++ b/Configuration/GlobalAssemblyInfo.cs @@ -15,7 +15,7 @@ internal static class RevisionClass public const string Major = "3"; public const string Minor = "11"; public const string Build = "1"; - public const string Revision = "3810"; + public const string Revision = "3812"; public const string MainVersion = Major + "." + Minor; public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision; diff --git a/Configuration/Version.defs b/Configuration/Version.defs index 9bc6d5ec3..f399f3dad 100644 --- a/Configuration/Version.defs +++ b/Configuration/Version.defs @@ -1,4 +1,4 @@ %COREVERSION%=1 -%REVISION%=3810 +%REVISION%=3812 %MINOR%=11 %MAJOR%=3 diff --git a/Release/pabcversion.txt b/Release/pabcversion.txt index 812a9586e..bfb3f8943 100644 --- a/Release/pabcversion.txt +++ b/Release/pabcversion.txt @@ -1 +1 @@ -3.11.1.3810 +3.11.1.3812 diff --git a/ReleaseGenerators/PascalABCNET_version.nsh b/ReleaseGenerators/PascalABCNET_version.nsh index 7a41d17d7..d58b1e3ab 100644 --- a/ReleaseGenerators/PascalABCNET_version.nsh +++ b/ReleaseGenerators/PascalABCNET_version.nsh @@ -1 +1 @@ -!define VERSION '3.11.1.3810' +!define VERSION '3.11.1.3812' diff --git a/TreeConverter/TreeConversion/convertion_data_and_alghoritms.cs b/TreeConverter/TreeConversion/convertion_data_and_alghoritms.cs index 8231d9f41..6f0669211 100644 --- a/TreeConverter/TreeConversion/convertion_data_and_alghoritms.cs +++ b/TreeConverter/TreeConversion/convertion_data_and_alghoritms.cs @@ -292,7 +292,7 @@ namespace PascalABCCompiler.TreeConverter ccc.parameters.AddRange(exprs); return ccc; } - //Метод только для внутнреннего использования. + //Метод только для внутреннего использования. //НЕ ПРЕОБРАЗУЕТ ТИПЫ. Только создает узел вызова метода. //Этот метод и используется для создания узлов преобразования типов. //TODO: Переименовать. diff --git a/VisualPascalABCNET/FileMonitoring.cs b/VisualPascalABCNET/FileMonitoring.cs index 3dbc57744..182209f33 100644 --- a/VisualPascalABCNET/FileMonitoring.cs +++ b/VisualPascalABCNET/FileMonitoring.cs @@ -105,15 +105,24 @@ namespace VisualPascalABC return; } - mes = Form1StringResources.Get("FILE_CHANGED_MESSAGE"); - if (MessageBox.Show(fileName + "\n\n" + mes, Form1StringResources.Get("CHANGED_FILE"), MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) - { - WorkbenchServiceFactory.FileService.ReloadFile(fileName); - } - else - { - WorkbenchServiceFactory.FileService.SetFileAsChanged(fileName); - } + //mes = Form1StringResources.Get("FILE_CHANGED_MESSAGE"); + var tab = VisualPABCSingleton.MainForm.FindTab(fileName); + if (tab != null) + if (tab.DocumentChanged) + { + if (MessageBox.Show(fileName + "\n\n" + mes, Form1StringResources.Get("CHANGED_FILE"), MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) + { + WorkbenchServiceFactory.FileService.ReloadFile(fileName); + } + else + { + WorkbenchServiceFactory.FileService.SetFileAsChanged(fileName); + } + } + else + { + WorkbenchServiceFactory.FileService.ReloadFile(fileName); + } } void MainForm_Activated(object sender, EventArgs e) diff --git a/VisualPascalABCNET/Workbench/FileOperations.cs b/VisualPascalABCNET/Workbench/FileOperations.cs index 535e6158b..4fccbddfb 100644 --- a/VisualPascalABCNET/Workbench/FileOperations.cs +++ b/VisualPascalABCNET/Workbench/FileOperations.cs @@ -316,11 +316,16 @@ namespace VisualPascalABC public void ReloadFile(string FileName) { - CodeFileDocumentControl tab = FindTab(FileName); + CodeFileDocumentControl tab = FindTab(FileName); if (tab != null) { + var CaretLine = tab.TextEditor.CaretLine; + tab.LoadFromFile(FileName); tab.DocumentChanged = false; + tab.TextEditor.ActiveTextAreaControl.Update(); + tab.TextEditor.ActiveTextAreaControl.CenterViewOn(CaretLine + 2, 0); + tab.TextEditor.CaretLine = CaretLine; SetTabPageText(tab); } } diff --git a/bin/Lib/DataAdapters.pas b/bin/Lib/DataAdapters.pas index 89507073e..36d5e3520 100644 --- a/bin/Lib/DataAdapters.pas +++ b/bin/Lib/DataAdapters.pas @@ -14,9 +14,6 @@ uses LinearAlgebraML; const ER_TO_MATRIX_NO_COLUMNS = '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 = 'Столбец "{0}" содержит нечисловые или NA значения!!Column "{0}" contains non-numeric or NA values'; ER_ENCODELABELS_COLUMN_NOT_STRING = @@ -281,4 +278,4 @@ end; -end. \ No newline at end of file +end. diff --git a/bin/Lib/DataFrameABCCore.pas b/bin/Lib/DataFrameABCCore.pas index fd75b9899..d7f51203f 100644 --- a/bin/Lib/DataFrameABCCore.pas +++ b/bin/Lib/DataFrameABCCore.pas @@ -108,7 +108,7 @@ type /// Столбец целых чисел IntColumn = class(Column) // Data и IsValid считаются immutable после создания - Data: array of integer; // Данные столбца + Data: array of integer; public constructor Create; begin end; constructor Create(name: string); @@ -120,7 +120,7 @@ type /// Столбец вещественных чисел FloatColumn = class(Column) - Data: array of real; // Данные столбца + Data: array of real; public constructor Create; begin end; constructor Create(name: string); @@ -133,7 +133,7 @@ type /// Столбец строк StrColumn = class(Column) - Data: array of string; // Данные столбца + Data: array of string; public constructor Create; begin end; constructor Create(name: string); @@ -142,15 +142,11 @@ type function TryGetNumericValue(i: integer; var value: real): boolean; override; /// Возвращает количество строк в столбце function RowCount: integer; override := Data.Length; - /// Добавляет невалидное (NA) значение в конец столбца - //procedure AppendInvalid; override; - /// Добавляет значение из курсора в указанной позиции - //procedure AppendFromCursor(cur: DataFrameCursor; colIndex: integer); override; end; /// Столбец булевых значений BoolColumn = class(Column) - Data: array of boolean; // Данные столбца + Data: array of boolean; public constructor Create; begin end; constructor Create(name: string); @@ -780,16 +776,21 @@ begin var n := cols.Length; colCnt := cols.Length; - SetLength(intAcc, n); - SetLength(floatAcc, n); - SetLength(strAcc, n); - SetLength(boolAcc, n); - SetLength(validAcc, n); + intAcc := new IntAccessor[n]; + floatAcc := new FloatAccessor[n]; + strAcc := new StrAccessor[n]; + boolAcc := new BoolAccessor[n]; + validAcc := new ValidAccessor[n]; for var i := 0 to n - 1 do begin var col := cols[i]; + intAcc[i] := NotInt; + floatAcc[i] := NotFloat; + strAcc[i] := NotStr; + boolAcc[i] := NotBool; + case fSchema.ColumnTypeAt(i) of ctInt: begin @@ -806,7 +807,6 @@ begin validAcc[i] := pos -> c.IsValid[pos]; floatAcc[i] := pos -> c.Data[pos]; - intAcc[i] := NotInt; end; ctStr: @@ -886,4 +886,4 @@ begin pos := p; end; -end. \ No newline at end of file +end. diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index 95b72a5c6..2275e7c64 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -94,6 +94,7 @@ type LinearRegression = MLModelsABC.LinearRegression; LogisticRegression = MLModelsABC.LogisticRegression; RidgeRegression = MLModelsABC.RidgeRegression; + LassoRegression = MLModelsABC.LassoRegression; ElasticNet = MLModelsABC.ElasticNet; DecisionTreeClassifier = MLModelsABC.DecisionTreeClassifier; DecisionTreeRegressor = MLModelsABC.DecisionTreeRegressor; @@ -148,7 +149,16 @@ const jkRight = JoinKind.jkRight; jkFull = JoinKind.jkFull; + /// Преобразует вектор меток классов в массив целых чисел. + /// Значения округляются функцией Round, чтобы устранить + /// возможные небольшие численные ошибки function LabelsToInts(y: Vector): array of integer; + + /// Кодирует строковые метки классов в целочисленные индексы. + /// Каждому уникальному значению присваивается номер 0,1,2,... + /// Порядок кодирования соответствует порядку первого появления меток. + /// Используется при обучении моделей и визуализации. + /// Предполагается, что входные данные уже очищены от пропущенных значений. 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); -end. \ No newline at end of file +end. diff --git a/bin/Lib/MLExceptions.pas b/bin/Lib/MLExceptions.pas index bda250059..3db5d765e 100644 --- a/bin/Lib/MLExceptions.pas +++ b/bin/Lib/MLExceptions.pas @@ -5,6 +5,9 @@ interface const ER_DIM_MISMATCH = 'Размерности не совпадают: {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 = 'Список paramValues пуст!!paramValues is empty'; ER_EMPTY_DATASET = diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index 6ec1649f0..7400627e9 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -10,7 +10,7 @@ /// /// Все алгоритмы работают с числовыми данными: /// X — Matrix (объекты × признаки), y — Vector (целевая переменная). -unit MLModelsABC; +unit MLModelsABC; // ============================================================= // СТАТИСТИЧЕСКОЕ СОГЛАШЕНИЕ (MLModelsABC) @@ -110,20 +110,8 @@ type function ToString: string; override; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; function Name: string := Self.GetType.Name; @@ -135,7 +123,7 @@ type /// Устойчива к мультиколлинеарности и плохо обусловленным данным. /// Используется при коррелированных признаках /// и в задачах, где важна численная стабильность решения - RidgeRegression = class(IRegressor) + RidgeRegression = class(IRegressor) private fLambda: real; fCoef: Vector; @@ -175,22 +163,10 @@ type /// После вызова Fit значение становится true. 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 Name: string := Self.GetType.Name; @@ -255,20 +231,8 @@ type function ToString: string; override; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; function Name: string := Self.GetType.Name; @@ -310,20 +274,8 @@ type /// Возвращает вектор предсказаний длины nSamples. function Predict(X: Matrix): Vector; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; /// Показывает, была ли модель обучена. @@ -421,20 +373,8 @@ type function GetClassLabels: array of string; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; end; @@ -573,13 +513,6 @@ type function FindBestSplit(X: Matrix; y: Vector; indices: array of integer; 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; public property FeatureImportances: Vector read GetFeatureImportances; @@ -656,20 +589,8 @@ type /// Для классификации — метки классов. function Predict(X: Matrix): Vector; virtual; abstract; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; virtual; abstract; /// Возвращает true, если дерево обучено. @@ -748,20 +669,8 @@ type /// Если false — Predict вызовет ошибку. property IsFitted: boolean read fFitted; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; end; @@ -816,20 +725,8 @@ type /// Возвращает вектор вещественных значений. function Predict(X: Matrix): Vector; override; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; override; /// Возвращает строковое представление модели. @@ -925,20 +822,8 @@ type /// В классификации — голосование (majority vote) или усреднение вероятностей. function Predict(X: Matrix): Vector; virtual; abstract; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; virtual; abstract; /// Возвращает вектор важности признаков. @@ -994,20 +879,8 @@ type /// Итоговое значение — среднее предсказаний всех деревьев ансамбля. function Predict(X: Matrix): Vector; override; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; override; /// Возвращает усредненную важность признаков по всем деревьям ансамбля. @@ -1075,20 +948,8 @@ type /// Требует предварительного вызова Fit. function PredictProba(X: Matrix): Matrix; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; override; /// Возвращает усредненную важность признаков по всем деревьям ансамбля. @@ -1245,20 +1106,8 @@ type /// Используются все обученные деревья. function Predict(X: Matrix): Vector; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; /// Обучает модель градиентного бустинга с использованием валидационной выборки. @@ -1423,20 +1272,8 @@ type function GetClasses: array of real; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; /// История значения LogLoss на обучающей выборке. @@ -1534,20 +1371,8 @@ type /// Возвращает вектор предсказанных значений или меток function Predict(X: Matrix): Vector; virtual; abstract; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; virtual; abstract; function Name: string := Self.GetType.Name; @@ -1610,20 +1435,8 @@ type /// Возвращает массив меток классов в порядке столбцов PredictProba function GetClasses: array of real; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; override; function Name: string := Self.GetType.Name; @@ -1661,20 +1474,8 @@ type /// Возвращает вектор предсказанных значений function Predict(X: Matrix): Vector; override; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; override; function Name: string := Self.GetType.Name; @@ -1738,20 +1539,8 @@ type /// Выполняет обучение и сразу возвращает метки кластеров. function FitPredict(X: Matrix): array of integer; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; /// Количество кластеров @@ -1819,20 +1608,8 @@ type /// Возвращает метки после обучения. function FitPredict(X: Matrix): array of integer; -/// Создаёт копию модели с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и Pipeline -/// • гарантирует независимое переобучение моделей -/// -/// Примечание: -/// • для копирования обученной модели следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию модели (без обученного состояния). +/// Используется для создания независимых экземпляров модели. function Clone: IModel; property Labels: array of integer read fLabels; @@ -1976,16 +1753,8 @@ type /// Возвращает строковое представление пайплайна. function ToString: string; override; -/// Создаёт копию пайплайна с теми же шагами и моделью. -/// -/// ВАЖНО: -/// • Clone копирует только конфигурацию (transformers и модель) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Все шаги и модель клонируются через их Clone (config-only) -/// -/// Назначение: -/// • использование в CrossValidate и Pipeline -/// • обеспечивает независимое переобучение пайплайна +/// Копирует только конфигурацию пайплайна (без обученного состояния). +/// Используется для создания независимых экземпляров пайплайна. function Clone: IModel; function Name: string := Self.GetType.Name; @@ -2068,16 +1837,8 @@ type /// Возвращает строковое представление пайплайна. function ToString: string; override; -/// Создаёт копию пайплайна с теми же шагами и моделью. -/// -/// ВАЖНО: -/// • Clone копирует только конфигурацию (transformers и модель) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Все шаги и модель клонируются через их Clone (config-only) -/// -/// Назначение: -/// • использование в CrossValidate и MatrixPipeline -/// • обеспечивает независимое переобучение пайплайна +/// Копирует только конфигурацию пайплайна (без обученного состояния). +/// Используется для создания независимых экземпляров пайплайна. function Clone: IModel; function Name: string := Self.GetType.Name; end; @@ -2130,20 +1891,8 @@ type function ToString: string; override; -/// Создаёт копию трансформера с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и MatrixPipeline -/// • гарантирует независимое переобучение трансформеров -/// -/// Примечание: -/// • для копирования обученного трансформера следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию трансформера (без обученного состояния). +/// Используется для создания независимых экземпляров трансформера. function Clone: ITransformer; end; @@ -2188,20 +1937,8 @@ type function ToString: string; override; -/// Создаёт копию трансформера с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и MatrixPipeline -/// • гарантирует независимое переобучение трансформеров -/// -/// Примечание: -/// • для копирования обученного трансформера следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию трансформера (без обученного состояния). +/// Используется для создания независимых экземпляров трансформера. function Clone: ITransformer; end; @@ -2242,20 +1979,8 @@ type function ToString: string; override; -/// Создаёт копию трансформера с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и MatrixPipeline -/// • гарантирует независимое переобучение трансформеров -/// -/// Примечание: -/// • для копирования обученного трансформера следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию трансформера (без обученного состояния). +/// Используется для создания независимых экземпляров трансформера. function Clone: ITransformer; end; @@ -2295,20 +2020,8 @@ type function ToString: string; override; -/// Создаёт копию трансформера с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и MatrixPipeline -/// • гарантирует независимое переобучение трансформеров -/// -/// Примечание: -/// • для копирования обученного трансформера следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию трансформера (без обученного состояния). +/// Используется для создания независимых экземпляров трансформера. function Clone: ITransformer; end; @@ -2392,20 +2105,8 @@ type /// Возвращает строковое представление трансформера function ToString: string; override; -/// Создаёт копию трансформера с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и MatrixPipeline -/// • гарантирует независимое переобучение трансформеров -/// -/// Примечание: -/// • для копирования обученного трансформера следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию трансформера (без обученного состояния). +/// Используется для создания независимых экземпляров трансформера. function Clone: ITransformer; end; @@ -2450,20 +2151,8 @@ type /// Возвращает строковое представление трансформера function ToString: string; override; -/// Создаёт копию трансформера с теми же гиперпараметрами. -/// -/// ВАЖНО: -/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) -/// • Clone НЕ копирует обученное состояние (fitted state) -/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) -/// -/// Назначение: -/// • использование в CrossValidate, GridSearch и MatrixPipeline -/// • гарантирует независимое переобучение трансформеров -/// -/// Примечание: -/// • для копирования обученного трансформера следует использовать отдельные механизмы -/// (сериализация, ModelIO и т.п.) +/// Копирует только конфигурацию трансформера (без обученного состояния). +/// Используется для создания независимых экземпляров трансформера. function Clone: ITransformer; end; @@ -3226,18 +2915,7 @@ begin var p := X.ColCount; // --- convert to integer labels - 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; + var yInt := LabelsToInts(y); // --- encode (порядок первого появления) var unique: array of integer; @@ -4053,140 +3731,6 @@ begin Result.Right := right; 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; - var rightIdx := new List; - - // 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)) function DecisionTreeCore.FindBestSplit( X: Matrix; @@ -4737,19 +4281,7 @@ begin fFeatureImportances := new Vector(X.ColCount); // --- convert to integer labels - var m := y.Length; - 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; + var yInt := LabelsToInts(y); // --- encode var classes: array of integer; @@ -4947,17 +4479,9 @@ begin fFeatureImportances := new Vector(X.ColCount); - var indices: array of integer; - - // 🔹 Ключевое изменение - 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; + var indices: array of integer := fRowIndices; + if indices = nil then + indices := Arr(0..X.RowCount - 1); fRoot := BuildTree(X, y, indices, 0); @@ -4978,12 +4502,10 @@ begin var node := fRoot; while not node.IsLeaf do - begin if X[rowIndex, node.FeatureIndex] <= node.Threshold then node := node.Left else node := node.Right; - end; Result := node.LeafValue; // для регрессии это уже real end; @@ -5035,7 +4557,6 @@ begin Result := s; end; - //----------------------------- // RandomForestBase //----------------------------- @@ -5364,18 +4885,7 @@ begin // ЕДИНЫЙ ENCODING (forest-level) // ========================================================= - var yInt := new integer[n]; - - 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 yInt := LabelsToInts(y); var yEncArr := EncodeLabelsInt(yInt, fIndexToClass); fClassCount := fIndexToClass.Length; @@ -5383,9 +4893,7 @@ begin if fClassCount < 2 then ArgumentError(ER_NEED_AT_LEAST_TWO_CLASSES); - var yEnc := new Vector(n); - for var i := 0 to n - 1 do - yEnc[i] := yEncArr[i]; + var yEnc := new Vector(yEncArr); // ========================================================= @@ -6508,21 +6016,10 @@ begin var nTrain := XTrain.RowCount; // ========================================================= - // НОВЫЙ ЕДИНЫЙ ENCODING + // Новый Encoding // ========================================================= - var yTrainInt := new integer[nTrain]; - - 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 yTrainInt := LabelsToInts(yTrain); var yEncoded := EncodeLabelsInt(yTrainInt, fClasses); @@ -6588,19 +6085,15 @@ begin if useValidation then 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 - var r := yVal[i]; - var ir := Round(r); + var ir := yValInt[i]; - if Abs(r - ir) > 1e-12 then - ArgumentError(ER_LABELS_NOT_INTEGER); - - if not fClassIndex.ContainsKey(ir) then + if ir not in fClassIndex then ArgumentError(ER_UNKNOWN_CLASS_LABEL, ir); yValEncoded[i] := fClassIndex[ir]; @@ -7350,7 +6843,7 @@ end; function KNNBase.SquaredL2(trainRow: integer; XTest: Matrix; testRow: integer): double; begin - var sum := 0.0; + var sum := 0.0; var d := fXTrain.ColCount; var train := fXTrain; // локальная ссылка @@ -7556,18 +7049,7 @@ begin // ЕДИНЫЙ ENCODING // ========================================================= - var yInt := new integer[n]; - - 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 yInt := LabelsToInts(y); var classesInt: array of integer; var yEncArr := EncodeLabelsInt(yInt, classesInt); @@ -9364,16 +8846,13 @@ begin var classToIndex := new Dictionary; var uniqueClasses := new List; var yEncoded := new integer[n]; + var yInt := LabelsToInts(y); for var i := 0 to n - 1 do begin - var r := y[i]; - var ir := Round(r); + var ir := yInt[i]; - if Abs(r - ir) > 1e-12 then - ArgumentError(ER_LABELS_NOT_INTEGER); - - if not classToIndex.ContainsKey(ir) then + if ir not in classToIndex then begin classToIndex[ir] := uniqueClasses.Count; uniqueClasses.Add(ir); @@ -9454,16 +8933,13 @@ begin var classToIndex := new Dictionary; var uniqueClasses := new List; var yEncoded := new integer[n]; + var yInt := LabelsToInts(y); for var i := 0 to n - 1 do begin - var r := y[i]; - var ir := Round(r); + var ir := yInt[i]; - if Abs(r - ir) > 1e-12 then - ArgumentError(ER_LABELS_NOT_INTEGER); - - if not classToIndex.ContainsKey(ir) then + if ir not in classToIndex then begin classToIndex[ir] := uniqueClasses.Count; uniqueClasses.Add(ir); @@ -9710,4 +9186,4 @@ begin Result := new Normalizer(fNormType); end; -end. \ No newline at end of file +end. diff --git a/bin/Lib/MLPipelineABC.pas b/bin/Lib/MLPipelineABC.pas index 8c1e1cd19..543448ea5 100644 --- a/bin/Lib/MLPipelineABC.pas +++ b/bin/Lib/MLPipelineABC.pas @@ -20,8 +20,23 @@ type fFitted: boolean; 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 TransformDataFrame(df: DataFrame): DataFrame; function FitTransformMatrix(var X: Matrix; y: Vector := nil): Matrix; @@ -29,7 +44,7 @@ type public constructor Create; - /// Признак того, что был вызван Fit или FitTransform. + /// Признак того, что был вызван Fit. property IsFitted: boolean read fFitted; end; @@ -45,11 +60,10 @@ type /// • Затем — матричные шаги (ITransformer / ISupervisedTransformer). /// • Модель (IModel) добавляется последней и может быть только одна. /// - /// В режиме с моделью используются: + /// В конвейере используются: /// • features — признаки; /// • target — целевая переменная. /// - /// Если модель не добавлена, DataPipeline работает как чистый DF-конвейер (Fit/Transform/FitTransform) DataPipeline = class(PipelineBase, IModel) private fModel: ISupervisedModel; @@ -67,7 +81,7 @@ type /// • IPreprocessor (DataFrame-уровень) /// • ITransformer (Matrix-уровень) /// • ISupervisedModel (модель, должна быть последней) - /// Запрещено добавлять шаги после вызова Fit/FitTransform. + /// Запрещено добавлять шаги после вызова Fit. function Add(step: IPipelineStep): DataPipeline; /// Строит конвейер из шагов обработки данных и модели. @@ -90,16 +104,18 @@ type /// затем обучает матричные трансформеры и модель. function Fit(df: DataFrame): DataPipeline; - /// Применяет обученные DataFrame-шаги к DataFrame и возвращает новый DataFrame. - /// В ML-режиме Transform не выполняет матричные шаги и не вызывает модель. + /// Применяет только обученные DataFrame-шаги pipeline и возвращает новый DataFrame. + /// + /// Матричные шаги и модель при этом не используются. + /// Метод предназначен в основном для просмотра и анализа промежуточного + /// результата preprocessing. + /// + /// Полученный DataFrame обычно не следует повторно подавать в тот же pipeline, + /// так как DataFrame-преобразования будут выполнены ещё раз. function Transform(df: DataFrame): DataFrame; - /// Выполняет Fit и Transform за один проход для DataFrame-режима. - /// Недоступен, если конвейер содержит модель (IModel). - function FitTransform(df: DataFrame): DataFrame; - /// Делает предсказание модели для объектов из DataFrame. - /// Доступен только если конвейер содержит модель (IModel) и был обучен (Fit). + /// Доступен после обучения конвейера (Fit). function Predict(df: DataFrame): Vector; /// Возвращает матрицу вероятностей (nSamples × nClasses). @@ -133,8 +149,6 @@ type /// В конвейере используются: /// • features — признаки (целевая переменная отсутствует). /// - /// Если модель не добавлена, UDataPipeline работает как чистый DF-конвейер - /// (Fit/Transform/FitTransform) UDataPipeline = class(PipelineBase, IModel) private fModel: IUnsupervisedModel; @@ -161,13 +175,16 @@ type /// Обучает конвейер на DataFrame. function Fit(df: DataFrame): UDataPipeline; - /// Применяет обученные DataFrame-шаги к DataFrame и возвращает новый DataFrame. + /// Применяет только обученные DataFrame-шаги pipeline и возвращает новый DataFrame. + /// + /// Матричные шаги и модель при этом не используются. + /// Метод предназначен в основном для просмотра и анализа промежуточного + /// результата preprocessing. + /// + /// Полученный DataFrame обычно не следует повторно подавать в тот же pipeline, + /// так как DataFrame-преобразования будут выполнены ещё раз. function Transform(df: DataFrame): DataFrame; - /// Выполняет Fit и Transform за один проход для DataFrame-режима. - /// Недоступен, если конвейер содержит модель (IModel). - function FitTransform(df: DataFrame): DataFrame; - /// Обучает конвейер и сразу возвращает метки кластеров. /// /// Выполняет полный pipeline: @@ -177,7 +194,7 @@ type /// Используется для задач кластеризации, где нет разделения на train/test. /// /// Доступен только если: - /// • конвейер содержит модель кластеризации (IClusterer). + /// • конечная модель поддерживает кластеризацию (IClusterer). function FitPredict(df: DataFrame): array of integer; /// Делает предсказание модели для объектов из DataFrame. @@ -189,8 +206,7 @@ type /// затем применяет обученную модель кластеризации. /// /// Доступен только если: - /// • конвейер содержит модель (IModel); - /// • модель поддерживает кластеризацию (IClusterer); + /// • конечная модель поддерживает кластеризацию (IClusterer); /// • конвейер был обучен (Fit). function PredictLabels(df: DataFrame): array of integer; @@ -219,11 +235,6 @@ const 'Матричный шаг не может идти после модели!!Matrix step cannot appear after the model'; ER_DATAPIPE_UNKNOWN_STEP_TYPE = 'Неизвестный тип шага конвейера: {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 = 'Модель не поддерживает предсказание вероятностей!!Model does not support probability prediction'; ER_TARGET_EMPTY = @@ -275,6 +286,8 @@ const 'LabelEncoder нельзя применять к целевой переменной — кодирование выполняется внутри модели!!LabelEncoder cannot be applied to target — encoding is handled internally by the model'; ER_ENCODELABELS_NOT_CATEGORICAL = 'Целевой столбец должен быть категориальным для задач классификации!!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 = 'Преобразование целевой переменной "{0}" запрещено в DataPipeline!!' + 'Transformation of target variable "{0}" is not allowed in DataPipeline'; @@ -312,24 +325,30 @@ begin fFitted := false; end; -function PipelineBase.PrepareMatrix(df: DataFrame; var current: DataFrame): Matrix; +class procedure PipelineBase.ValidateFeatureList(features: array of string); begin - if df = nil then - ArgumentNullError(ER_ARG_NULL, 'df'); + if (features = nil) or (features.Length = 0) then + ArgumentError(ER_FEATURES_EMPTY); - current := df; + var seen := new HashSet; - // --- schema - ValidateSchema(current); - - // --- DataFrame steps - for var i := 0 to fDataSteps.Count - 1 do + foreach var f in features do begin - fDataSteps[i] := fDataSteps[i].Fit(current); - current := fDataSteps[i].Transform(current); - end; + if (f = nil) or (f = '') then + ArgumentError(ER_FEATURE_EMPTY); + + 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; foreach var f in fFeatures do @@ -348,7 +367,7 @@ begin begin foreach var c in cols do if current.HasColumn(c) then - if not feats.Contains(c) then + if c not in feats then feats.Add(c); expanded := true; @@ -361,18 +380,52 @@ begin // fallback: обычный столбец if current.HasColumn(f) then - if not feats.Contains(f) then + if f not in feats then feats.Add(f); end; if feats.Count = 0 then 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); 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; begin for var i := 0 to fMatrixSteps.Count - 1 do @@ -416,12 +469,9 @@ end; constructor DataPipeline.Create; begin - fDataSteps := new List; - fMatrixSteps := new List; + inherited Create; fModel := nil; fTarget := ''; - fFeatures := nil; - fFitted := false; end; function DataPipeline.Add(step: IPipelineStep): DataPipeline; @@ -493,25 +543,12 @@ begin if (target = nil) or (target = '') then ArgumentError(ER_TARGET_EMPTY); - if (features = nil) or (features.Length = 0) then - ArgumentError(ER_FEATURES_EMPTY); - - var seen := new HashSet; + ValidateFeatureList(features); foreach var f in features do - begin - if (f = nil) or (f = '') then - ArgumentError(ER_FEATURE_EMPTY); - if f = target then 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 ArgumentError(ER_PIPELINE_NO_STEPS); @@ -604,38 +641,7 @@ begin Result := Self; end; -function DataPipeline.Transform(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 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.Transform(df: DataFrame): DataFrame := TransformDataFrame(df); function DataPipeline.Predict(df: DataFrame): Vector; begin @@ -699,8 +705,7 @@ begin if fTarget = '' then ArgumentError(ER_TARGET_EMPTY); - if (fFeatures = nil) or (Length(fFeatures) = 0) then - ArgumentError(ER_FEATURES_EMPTY); + ValidateFeatureList(fFeatures); // target не должен входить в features for var i := 0 to High(fFeatures) do @@ -714,29 +719,28 @@ begin ArgumentError(ER_DATAPIPE_TARGET_NOT_FOUND, fTarget, cols); end; - if fTask = tkClassification then - if not df.IsCategorical(fTarget) then - ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, fTarget); - - var seen := new HashSet; + case fTask of + tkClassification: + if not df.IsCategorical(fTarget) then + ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, fTarget); + 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 begin var f := fFeatures[i]; - if f = '' then - ArgumentError(ER_FEATURE_EMPTY); - if not df.Schema.HasColumn(f) then begin var cols := df.Schema.ColumnNames.JoinToString(', '); ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, f, cols); end; - - if seen.Contains(f) then - ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f); - - seen.Add(f); end; end; @@ -804,11 +808,8 @@ end; constructor UDataPipeline.Create; begin - fDataSteps := new List; - fMatrixSteps := new List; + inherited Create; fModel := nil; - fFeatures := nil; - fFitted := false; end; function UDataPipeline.Add(step: IPipelineStep): UDataPipeline; @@ -856,21 +857,7 @@ end; class function UDataPipeline.Build(features: array of string; params steps: array of IPipelineStep): UDataPipeline; begin - if (features = nil) or (features.Length = 0) then - ArgumentError(ER_FEATURES_EMPTY); - - var seen := new HashSet; - - 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; + ValidateFeatureList(features); if (steps = nil) or (Length(steps) = 0) then ArgumentError(ER_PIPELINE_NO_STEPS); @@ -921,38 +908,7 @@ begin Result := Self; end; -function UDataPipeline.Transform(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 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.Transform(df: DataFrame): DataFrame := TransformDataFrame(df); function UDataPipeline.FitPredict(df: DataFrame): array of integer; begin @@ -978,27 +934,7 @@ begin end; // --- 2) вычислить финальные признаки - var feats := new List; - - 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; + fFinalFeatures := ResolveFinalFeatures(current); var X := current.ToMatrix(fFinalFeatures); @@ -1100,28 +1036,17 @@ end; procedure UDataPipeline.ValidateSchema(df: DataFrame); begin - if (fFeatures = nil) or (Length(fFeatures) = 0) then - ArgumentError(ER_FEATURES_EMPTY); - - var seen := new HashSet; + ValidateFeatureList(fFeatures); for var i := 0 to High(fFeatures) do begin var f := fFeatures[i]; - if f = '' then - ArgumentError(ER_FEATURE_EMPTY); - if not df.Schema.HasColumn(f) then begin var cols := df.Schema.ColumnNames.JoinToString(', '); ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, f, cols); end; - - if seen.Contains(f) then - ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f); - - seen.Add(f); end; end; @@ -1179,4 +1104,4 @@ begin Result := p; end; -end. \ No newline at end of file +end. diff --git a/bin/Lib/MLUtilsABC.pas b/bin/Lib/MLUtilsABC.pas index b5c6183d7..f965e5039 100644 --- a/bin/Lib/MLUtilsABC.pas +++ b/bin/Lib/MLUtilsABC.pas @@ -97,9 +97,11 @@ uses MLExceptions; const ER_LABELS_NULL = - 'y не может быть nil!!y cannot be nil'; + '{0} не может быть nil!!{0} cannot be nil'; ER_LABELS_ARRAY_NULL = 'labels не может быть nil!!labels cannot be nil'; + ER_LABELS_NOT_INTEGER = + 'Метки классов должны быть целыми числами!!Class labels must be integers'; ER_UNKNOWN_CLASS_IN_TRANSFORM = 'Неизвестное значение класса "{0}" при преобразовании меток!!Unknown class value "{0}" in TransformLabels'; ER_LABEL_INDEX_OUT_OF_RANGE = @@ -128,7 +130,15 @@ begin Result := new integer[y.Length]; 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; function IntsToLabels(a: array of integer): Vector; @@ -151,7 +161,7 @@ begin for var i := 0 to labels.Length - 1 do begin var lbl := labels[i]; - if not map.ContainsKey(lbl) then + if lbl not in map then begin map[lbl] := classList.Count; classList.Add(lbl); @@ -182,7 +192,7 @@ begin begin var lbl := labels[i]; - if not map.ContainsKey(lbl) then + if lbl not in map then Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, lbl); res[i] := map[lbl]; @@ -210,7 +220,7 @@ begin begin var v := labels[i]; - if not map.ContainsKey(v) then + if v not in map then Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, v); res[i] := map[v]; @@ -237,7 +247,7 @@ begin for var i := 0 to labels.Length - 1 do begin var lbl := labels[i]; - if not map.ContainsKey(lbl) then + if lbl not in map then begin map[lbl] := classList.Count; classList.Add(lbl); @@ -276,7 +286,7 @@ begin var res := new List; for var i := 0 to labels.Length - 1 do - if not seen.Contains(labels[i]) then + if labels[i] not in seen then begin seen.Add(labels[i]); res.Add(labels[i]); @@ -298,4 +308,4 @@ begin DimensionError(ER_DIM_MISMATCH, a.Length, b.Length); end; -end. \ No newline at end of file +end. diff --git a/bin/Lib/MetricsABC.pas b/bin/Lib/MetricsABC.pas index a416b723a..dff74ceb6 100644 --- a/bin/Lib/MetricsABC.pas +++ b/bin/Lib/MetricsABC.pas @@ -53,6 +53,9 @@ type /// R2 = 1 — идеальная модель, /// R2 = 0 — не лучше среднего, /// R2 < 0 — хуже константной модели. + /// + /// Если дисперсия целевой переменной равна нулю (все значения одинаковы), + /// метрика не определена. В этом случае возвращается 0.0 по соглашению. static function R2(yTrue, yPred: Vector): real; // ---- Вероятностные метрики классификации ---- @@ -104,14 +107,14 @@ type /// Важна, когда критичны ложные положительные результаты («ложные тревоги»). static function Precision(yTrue, yPred: Vector): real; - /// Точность (Precision) для многоклассовой классификации. + /// Точность (Precision) с усреднением по классам. /// Вычисляется для каждого класса отдельно как: /// Precision(c) = TP(c) / (TP(c) + FP(c)), - /// после чего агрегируется согласно параметру avg: + /// а затем результаты объединяются способом, заданным параметром avg: /// avMacro — среднее по всем классам (каждый класс равновесен); /// avMicro — глобальная метрика по суммарным TP и FP; /// avWeighted — среднее с весами, пропорциональными размеру классов. - /// Используется для задач с числом классов больше двух. + /// Особенно полезна для многоклассовой классификации. static function Precision(yTrue, yPred: Vector; avg: Averaging): real; /// Полнота (Recall) для положительного класса (1). @@ -122,14 +125,14 @@ type /// Важна, когда нежелательны пропуски положительных случаев (например, заболеваний). static function Recall(yTrue, yPred: Vector): real; - /// Полнота (Recall) для многоклассовой классификации. + /// Полнота (Recall) с усреднением по классам. /// Вычисляется для каждого класса отдельно как: /// Recall(c) = TP(c) / (TP(c) + FN(c)), - /// после чего агрегируется согласно параметру avg: + /// а затем результаты объединяются способом, заданным параметром avg: /// avMacro — среднее по всем классам (каждый класс равновесен); /// avMicro — глобальная метрика по суммарным TP и FN; /// avWeighted — среднее с весами, пропорциональными размеру классов. - /// Используется для задач с числом классов больше двух. + /// Особенно полезна для многоклассовой классификации. static function Recall(yTrue, yPred: Vector; avg: Averaging): real; /// F1-мера для бинарной классификации. @@ -140,14 +143,14 @@ type /// Особенно полезна при несбалансированных классах. static function F1(yTrue, yPred: Vector): real; - /// F1-мера для многоклассовой классификации. + /// F1-мера с усреднением по классам. /// Вычисляется для каждого класса отдельно как: /// F1(c) = 2 * Precision(c) * Recall(c) / (Precision(c) + Recall(c)), - /// после чего агрегируется согласно параметру avg: + /// а затем результаты объединяются способом, заданным параметром avg: /// avMacro — среднее по всем классам (каждый класс равновесен); /// avMicro — вычисляется по суммарным TP, FP и FN; /// avWeighted — среднее с весами, пропорциональными размеру классов. - /// Используется для задач с числом классов больше двух. + /// Особенно полезна для многоклассовой классификации. static function F1(yTrue, yPred: Vector; avg: Averaging): real; /// Специфичность (Specificity, True Negative Rate) для бинарной классификации. @@ -212,24 +215,25 @@ type end; type - /// Матрица ошибок (Confusion Matrix). - /// Представляет собой квадратную матрицу размера K × K, - /// где K — число классов. - /// Элемент [i, j] содержит количество объектов, - /// истинный класс которых равен i, - /// а предсказанный — j. - /// Используется для анализа качества классификатора - /// и вычисления метрик (Accuracy, Precision, Recall, F1 и др.). - /// Поддерживает как бинарную, так и многоклассовую классификацию. - /// Для бинарной классификации дополнительно доступны - /// агрегированные показатели: TP, TN, FP, FN. - /// - /// Ожидается, что yTrue и yPred содержат закодированные метки классов - /// (целые значения 0..K-1, допускается хранение в Vector типа real). - /// Для строковых меток необходимо предварительно применить EncodeLabels - /// - /// Метки классов упорядочиваются по возрастанию для обеспечения - /// стабильного и предсказуемого представления результатов +/// Матрица ошибок (Confusion Matrix) для бинарной и многоклассовой классификации. +/// +/// Показывает, сколько объектов каждого класса +/// модель распознала правильно и сколько перепутала. +/// +/// На вход можно передавать любые целочисленные метки классов +/// (например, 0, 1, 2 или 2, 5, 8). +/// +/// При построении матрицы все метки автоматически нумеруются +/// числами от 0 до K-1, где K — число различных классов. +/// +/// Элемент матрицы [i, j] показывает, сколько объектов +/// класса i модель предсказала как класс j. +/// +/// Свойства Labels, GetLabel и GetIndex позволяют узнать, +/// как новые внутренние номера классов связаны с исходными метками. +/// +/// Для бинарной классификации дополнительно доступны +/// показатели TP, TN, FP, FN, Precision, Recall, F1, Specificity и BalancedAccuracy ConfusionMatrix = class private fMatrix: array[,] of integer; @@ -248,76 +252,87 @@ type procedure EnsureClassIndex(c: integer); public /// Создаёт матрицу ошибок по истинным меткам yTrue и предсказаниям модели yPred. - /// Векторы должны иметь одинаковую длину и содержать метки классов, - /// представленные целыми числами (0, 1, 2, ...). - /// Автоматически определяет множество классов и строит - /// матрицу размера K × K, где K — число различных классов. - /// Элемент [i, j] содержит количество объектов, - /// истинный класс которых равен i, - /// а предсказанный — j. - /// Для бинарной классификации дополнительно доступны агрегированные - /// значения TP, TN, FP и FN + /// + /// Векторы должны иметь одинаковую длину и содержать целочисленные метки классов. + /// + /// Можно использовать любые целые метки классов. При построении матрицы они + /// автоматически перенумеровываются числами от 0 до K-1, где K — число + /// различных классов. + /// + /// Элемент [i, j] показывает, сколько объектов класса i модель предсказала + /// как класс j. + /// + /// Для бинарной классификации дополнительно доступны значения TP, TN, FP и FN constructor Create(yTrue, yPred: Vector); - - /// Возвращает исходную метку класса по внутреннему индексу c. - /// c — индекс класса в диапазоне [0, ClassCount). - /// Результат — метка класса (например, 2, 5, 8), соответствующая этому индексу. - /// Используется для интерпретации результатов, так как внутренние индексы - /// могут не совпадать с исходными значениями меток. + + /// Возвращает исходную метку класса по его внутреннему номеру. + /// + /// Внутренние номера классов используются внутри матрицы ошибок + /// и находятся в диапазоне от 0 до ClassCount-1. + /// + /// Метод позволяет узнать, какой исходной метке соответствует + /// данный внутренний номер. function GetLabel(c: integer): integer; - /// Возвращает внутренний индекс класса по его метке label. - /// label — исходная метка класса (например, 2, 5, 8). - /// Результат — индекс класса в диапазоне [0, ClassCount). - /// Используется для преобразования внешних меток в внутренние индексы, - /// применяемые внутри матрицы ошибок. + /// Возвращает внутренний номер класса по его исходной метке. + /// + /// Результат находится в диапазоне от 0 до ClassCount-1 + /// и используется для обращения к элементам матрицы ошибок. + /// + /// Если такой метки нет, выбрасывается исключение. function GetIndex(labl: integer): integer; - /// Массив меток классов в порядке внутренних индексов. - /// Labels[c] — исходная метка класса для индекса c. + /// Исходные метки классов в порядке внутренних номеров. + /// + /// Labels[c] — это исходная метка класса, + /// соответствующая внутреннему номеру c. property Labels: array of integer read fLabels; - /// Число различных классов в данных. - /// Определяется автоматически по объединению меток из yTrue и yPred. - /// Используется для построения матрицы размера K × K. + /// Число различных классов, встретившихся в yTrue или yPred. + /// + /// Используется для построения матрицы ошибок размера K × K, где K = ClassCount. property ClassCount: integer read fClassCount; /// Число истинно положительных предсказаний для класса c. - /// TP(c) — количество объектов, для которых истинный и предсказанный класс равны c. + /// Здесь c — номер класса в матрице ошибок. /// Соответствует элементу матрицы [c, c]. function TPForClass(c: integer): integer; /// Число ложно положительных предсказаний для класса c. + /// Здесь c — номер класса в матрице ошибок. /// FP(c) — количество объектов, предсказанных как класс c, /// но в действительности принадлежащих другим классам. /// Вычисляется как сумма по столбцу c минус TP(c). function FPForClass(c: integer): integer; /// Число ложно отрицательных предсказаний для класса c. + /// Здесь c — номер класса в матрице ошибок. /// FN(c) — количество объектов, принадлежащих классу c, /// но предсказанных как другие классы. /// Вычисляется как сумма по строке c минус TP(c). function FNForClass(c: integer): integer; /// Число истинно положительных предсказаний (TP) для бинарной классификации. - /// Количество объектов, для которых истинный и предсказанный класс равны 1. + /// Показывает, сколько объектов положительного класса + /// модель правильно распознала как положительные. /// Доступно только при числе классов, равном 2. property TP: integer read GetTPBinary; /// Число ложно положительных предсказаний (FP) для бинарной классификации. - /// Количество объектов, предсказанных как класс 1, - /// но в действительности принадлежащих классу 0. + /// Показывает, сколько объектов отрицательного класса + /// модель ошибочно отнесла к положительному классу. /// Доступно только при числе классов, равном 2. property FP: integer read GetFPBinary; /// Число ложно отрицательных предсказаний (FN) для бинарной классификации. - /// Количество объектов, принадлежащих классу 1, - /// но предсказанных как класс 0. + /// Показывает, сколько объектов положительного класса + /// модель ошибочно отнесла к отрицательному классу. /// Доступно только при числе классов, равном 2. property FN: integer read GetFNBinary; /// Число истинно отрицательных предсказаний (TN) для бинарной классификации. - /// Количество объектов, для которых истинный и предсказанный класс равны 0. + /// Показывает, сколько объектов отрицательного класса + /// модель правильно распознала как отрицательные. /// Доступно только при числе классов, равном 2. property TN: integer read GetTNBinary; @@ -483,6 +498,9 @@ type /// Используется только для бинарной классификации. 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); end; @@ -527,7 +545,10 @@ type /// 1.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; /// Метрики для оценки качества кластеризации @@ -601,6 +622,8 @@ const 'Для вычисления Silhouette требуется минимум 2 кластера!!Silhouette requires at least 2 clusters'; ER_INVALID_VALUE_AT = 'Некорректное значение в {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 = 'Для Calinski–Harabasz требуется минимум 2 кластера!!Calinski–Harabasz requires at least 2 clusters'; ER_CH_NEEDS_N_GT_K = @@ -715,6 +738,12 @@ begin var y := yTrue[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; sum += Abs((y - yp) / y); @@ -932,9 +961,6 @@ begin fprList.Add(fpr); end; - fprList.Add(1.0); - tprList.Add(1.0); - Result := (new Vector(fprList.ToArray), new Vector(tprList.ToArray)); end; @@ -1675,9 +1701,9 @@ end; /// 1 — идеальное совпадение кластеризации и истинных меток /// 0 — случайное разбиение /// <0 — хуже случайного -/// Сложность: O(n²). При больших n может быть медленным. -/// Для больших выборок рекомендуется использовать альтернативную реализацию -/// через таблицу сопряжённости (contingency table) с асимптотикой O(n · k) +/// Использует таблицу сопряжённости (contingency table). +/// Сложность: O(n + kT * kP), +/// где kT и kP — числа различных классов в yTrue и yPred. static function Metrics.AdjustedRandIndex(yTrue, yPred: Vector): real; begin if yTrue = nil then @@ -1697,14 +1723,8 @@ begin end; // --- Vector -> int[] - var yTInt := new integer[n]; - var yPInt := new integer[n]; - - for var i := 0 to n - 1 do - begin - yTInt[i] := Round(yTrue[i]); - yPInt[i] := Round(yPred[i]); - end; + var yTInt := LabelsToInts(yTrue); + var yPInt := LabelsToInts(yPred); // --- encoding var classesTrue: array of integer; @@ -1977,7 +1997,7 @@ end; function ConfusionMatrix.GetIndex(labl: integer): integer; begin - if not fLabelToIndex.ContainsKey(labl) then + if labl not in fLabelToIndex then ArgumentError(ER_INVALID_CLASS_LABEL, labl); Result := fLabelToIndex[labl]; diff --git a/bin/Lib/PreprocessorABC.pas b/bin/Lib/PreprocessorABC.pas index 8fb88ca65..febaa5784 100644 --- a/bin/Lib/PreprocessorABC.pas +++ b/bin/Lib/PreprocessorABC.pas @@ -36,6 +36,8 @@ type /// Выполняет Fit и Transform последовательно function FitTransform(df: DataFrame): DataFrame; + /// Копирует только конфигурацию препроцессора (без обученного состояния). + /// Используется для создания независимых экземпляров препроцессора. function Clone: IPreprocessor; end; @@ -72,15 +74,8 @@ type property ColumnName: string read col; -/// Создаёт копию препроцессора с той же конфигурацией. -/// -/// ВАЖНО: -/// • Clone копирует только конфигурацию (например, имя столбца) -/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.) -/// -/// Назначение: -/// • использование в Pipeline и CrossValidate -/// • обеспечивает независимое переобучение препроцессоров +/// Копирует только конфигурацию препроцессора (без обученного состояния). +/// Используется для создания независимых экземпляров препроцессора. function Clone: IPreprocessor; end; @@ -118,15 +113,8 @@ type function GetExpandedColumns(sourceColumn: string): array of string; -/// Создаёт копию препроцессора с той же конфигурацией. -/// -/// ВАЖНО: -/// • Clone копирует только конфигурацию (например, имя столбца) -/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.) -/// -/// Назначение: -/// • использование в Pipeline и CrossValidate -/// • обеспечивает независимое переобучение препроцессоров +/// Копирует только конфигурацию препроцессора (без обученного состояния). +/// Используется для создания независимых экземпляров препроцессора. function Clone: IPreprocessor; end; @@ -171,15 +159,8 @@ type property Columns: array of string read cols; -/// Создаёт копию препроцессора с той же конфигурацией. -/// -/// ВАЖНО: -/// • Clone копирует только конфигурацию (например, имя столбца) -/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.) -/// -/// Назначение: -/// • использование в Pipeline и CrossValidate -/// • обеспечивает независимое переобучение препроцессоров +/// Копирует только конфигурацию препроцессора (без обученного состояния). +/// Используется для создания независимых экземпляров препроцессора. function Clone: IPreprocessor; end; @@ -698,7 +679,7 @@ begin res := res.ReplaceColumnInt( name, - c -> (if c.IsValid(idx) then c.Int(idx) else k) + c -> (if c.IsValid(capturedIdx) then c.Int(capturedIdx) else k) ); end else // ctFloat @@ -789,4 +770,4 @@ begin end; end; -end. \ No newline at end of file +end. diff --git a/bin/Lib/ValidationML.pas b/bin/Lib/ValidationML.pas index 3e2e7ff7d..7d81a7df7 100644 --- a/bin/Lib/ValidationML.pas +++ b/bin/Lib/ValidationML.pas @@ -96,6 +96,9 @@ type /// • лучший параметр, /// • лучшее среднее значение метрики, /// • модель, обученная на всём датасете с лучшим параметром + /// + /// Все параметры оцениваются на одном и том же разбиении данных + /// (используется фиксированный seed), что обеспечивает корректное и сопоставимое сравнение моделей class function Search( modelFactory: P -> T; paramValues: array of P; @@ -460,17 +463,6 @@ begin if (k < 2) or (k > X.RowCount) then ArgumentError(ER_K_INVALID_STRATIFIED, k, X.RowCount); - var labels := y.ToIntArray; - var classCounts := labels.EachCount; - - var minCount := integer.MaxValue; - foreach var pair in classCounts do - if pair.Value < minCount then - minCount := pair.Value; - - if k > minCount then - ArgumentError(ER_STRATIFIED_K_TOO_LARGE, k, minCount); - var baseSeed := if seed >= 0 then seed else System.Environment.TickCount and integer.MaxValue;