ML - правки в интерфейсах и Pipeline

This commit is contained in:
Mikhalkovich Stanislav 2026-03-21 16:30:25 +03:00
parent 50e09e1b06
commit 2c49b0d2d8
15 changed files with 920 additions and 224 deletions

View file

@ -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 = "3779";
public const string Revision = "3781";
public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;

View file

@ -1,4 +1,4 @@
%MINOR%=11
%REVISION%=3779
%REVISION%=3781
%COREVERSION%=1
%MAJOR%=3

View file

@ -5,10 +5,11 @@ begin
var ds := Datasets.MoscowHousing;
ds.Info;
var df := ds.Data;
Println;
Println('Первые строки:');
ds.Head.Println(1);
ds.Data.Schema.Print
df.Schema.Println
end.

View file

@ -0,0 +1,10 @@
uses MLABC;
begin
var ds := Datasets.RussianCities;
var df := ds.Data;
df.Schema.Println;
df.PrintlnInfo;
end.

View file

@ -1 +1 @@
3.11.1.3779
3.11.1.3781

View file

@ -1 +1 @@
!define VERSION '3.11.1.3779'
!define VERSION '3.11.1.3781'

View file

@ -101,7 +101,8 @@ type
property Schema: DataFrameSchema read fschema;
procedure SetSchema(schema: DataFrameSchema);
function SetCategorical(names: array of string): DataFrame;
property Item[name: string]: Column read GetColumn; default;
@ -2305,6 +2306,31 @@ begin
fSchema := schema;
end;
function DataFrame.SetCategorical(names: array of string): DataFrame;
begin
var n := fSchema.ColumnCount;
var cats := new boolean[n];
// копируем старые значения
for var i := 0 to n - 1 do
cats[i] := fSchema.IsCategorical[i];
// применяем новые
foreach var name in names do
cats[fSchema.IndexOf(name)] := true;
// создаём НОВУЮ schema
var newSchema := new DataFrameSchema(
fSchema.ColumnNames,
fSchema.Types,
cats
);
// создаём новый DataFrame (columns не копируем!)
Result := new DataFrame(columns, newSchema);
end;
function DataFrame.Filter(pred: CursorPredicate): DataFrame;
begin
var cursor := GetCursor;

View file

@ -537,8 +537,6 @@ begin
Assert(fIndexByName.Count = fNames.Length);
end;
//-----------------------------
// Columns
//-----------------------------

View file

@ -86,6 +86,8 @@ type
Datasets = MLDatasets.Datasets;
IModel = MLCoreABC.IModel;
UPipeline = MLModelsABC.UPipeline;
UDataPipeline = MLPipelineABC.UDataPipeline;
function LabelsToInts(y: Vector): array of integer;
function EncodeLabels(labels: array of string): array of integer;

View file

@ -14,19 +14,29 @@ type
/// Интерфейс шага конвейера, работающего в числовом (матричном) пространстве.
/// Реализуется трансформерами и моделями, которые принимают на вход Matrix и Vector
/// и используются в Matrix-уровне Pipeline
IMatrixStep = interface(IPipelineStep) end;
IMatrixStep = interface(IPipelineStep)
end;
/// Интерфейс шага конвейера, работающего на уровне DataFrame.
/// Реализуется препроцессорами, которые выполняют преобразования табличных данных
/// до перехода в числовое (Matrix) представление
IDataStep = interface(IPipelineStep) end;
IDataStep = interface(IPipelineStep)
end;
/// Базовый интерфейс модели машинного обучения
IModel = interface(IMatrixStep)
function Fit(X: Matrix; y: Vector): IModel;
function Predict(X: Matrix): Vector;
function Clone: IModel;
function Name: string;
property IsFitted: boolean read;
end;
/// Интерфейс модели с учителем (Supervised Model).
/// Наследуется от базового интерфейса IModel.
/// Предназначен для алгоритмов, обучающихся по признакам X
/// с использованием целевых значений (y)
ISupervisedModel = interface(IModel)
function Fit(X: Matrix; y: Vector): ISupervisedModel;
end;
/// Интерфейс модели без учителя (Unsupervised Model).
@ -34,7 +44,7 @@ type
/// Предназначен для алгоритмов, обучающихся только по признакам X
/// без использования целевых значений (y)
IUnsupervisedModel = interface(IModel)
procedure Fit(X: Matrix);
function Fit(X: Matrix): IUnsupervisedModel;
end;
/// Интерфейс алгоритма кластеризации.
@ -42,18 +52,17 @@ type
/// Предназначен для моделей, разбивающих объекты на кластеры
/// и возвращающих индекс кластера для каждого объекта
IClusterer = interface(IUnsupervisedModel)
function Predict(X: Matrix): Vector;
end;
/// Интерфейс древовидной модели машинного обучения
ITreeModel = interface(IModel)
ITreeModel = interface(ISupervisedModel)
function FeatureImportances: Vector;
end;
/// Интерфейс классификатора.
/// Наследуется от IModel.
/// Предназначен для моделей, выполняющих классификацию (предсказание меток классов).
IClassifier = interface(IModel)
IClassifier = interface(ISupervisedModel)
end;
/// Интерфейс классификатора, возвращающего вероятности.
@ -74,17 +83,13 @@ type
/// Интерфейс регрессионной модели.
/// Наследуется от IModel.
/// Предназначен для моделей, предсказывающих числовые значения.
IRegressor = interface(IModel)
IRegressor = interface(ISupervisedModel)
end;
/// Базовый интерфейс преобразования признаков.
/// Используется для масштабирования, отбора,
/// уменьшения размерности и других преобразований данных.
/// уменьшения размерности и других преобразований данных
ITransformer = interface(IMatrixStep)
/// Обучает преобразование на данных.
/// Запоминает необходимые параметры,
/// которые будут использоваться при Transform.
function Fit(X: Matrix): ITransformer;
/// Применяет обученное преобразование к данным.
/// Возвращает новую матрицу признаков.
function Transform(X: Matrix): Matrix;
@ -93,13 +98,21 @@ type
/// Интерфейс преобразования признаков с учётом целевой переменной.
/// Используется для методов отбора признаков и других процедур,
/// в которых при обучении требуется вектор целевых значений.
/// в которых при обучении требуется вектор целевых значений
ISupervisedTransformer = interface(ITransformer)
/// Обучает преобразование на данных с использованием
/// как признаков X, так и целевой переменной y.
/// Запоминает необходимые параметры,
/// которые будут использоваться при Transform.
function Fit(X: Matrix; y: Vector): ITransformer;
/// Запоминает необходимые параметры, которые будут использоваться при Transform.
function Fit(X: Matrix; y: Vector): ISupervisedTransformer;
end;
/// Интерфейс преобразования признаков без учёта целевой переменной.
/// Используется для методов отбора признаков и других процедур,
/// в которых при обучении не требуется вектор целевых значений
IUnsupervisedTransformer = interface(ITransformer)
/// Обучает преобразование на данных с использованием признаков X
/// Запоминает необходимые параметры, которые будут использоваться при Transform.
function Fit(X: Matrix): IUnsupervisedTransformer;
end;
implementation

View file

@ -32,21 +32,6 @@ type
ValueLabels: Dictionary<string,Dictionary<string,string>>;
Description: string;
{/// Возвращает матрицу признаков X.
/// Используются столбцы, указанные в Features.
function ToX: Matrix;
/// Возвращает вектор целевой переменной y.
/// Доступно только для задач с учителем.
function ToY: Vector;
/// Возвращает пару (X, y) для обучения модели.
function ToXY: (Matrix, Vector);
/// Возвращает (X, y), где y преобразован в целочисленные метки классов.
/// Используется для задач классификации.
function ToXYInt: (Matrix, array of integer);}
/// Возвращает true, если датасет относится к задаче с учителем
/// (classification или regression).
function IsSupervised: boolean;
@ -68,6 +53,10 @@ type
function ClassName(value: string): string;
function RowCount: integer := Data.RowCount;
function GetFeatureColumns: array of string;
function HasTarget: boolean;
end;
/// Набор генераторов и загрузчиков датасетов для задач машинного обучения.
@ -208,6 +197,9 @@ type
/// Датасет цен на квартиры (задача регрессии)
static function MoscowHousing: Dataset;
/// Датасет российских городов (задача кластеризации)
static function RussianCities: Dataset;
/// Датасет результатов экзамена студентов (классификация)
static function StudentExam: Dataset;
@ -266,6 +258,8 @@ const
'Classes доступны только для задач классификации!!Classes are only available for classification datasets';
ER_VALUECOUNTS_ONLY_CLASSIFICATION =
'ValueCounts доступны только для задач классификации!!ValueCounts are only available for classification datasets';
ER_DATASET_TARGET_MISSING =
'Target обязателен для задач обучения с учителем!!Target is required for supervised learning datasets';
C_DATASET = 'Датасет: {0}!!Dataset: {0}';
C_DESCRIPTION = 'Описание:!!Description:';
@ -463,6 +457,23 @@ begin
Result := ValueLabel(Target, value);
end;
function Dataset.GetFeatureColumns: array of string;
begin
if (Features <> nil) and (Features.Length > 0) then
Result := Features
else if Target <> nil then
Result := Data.Schema.ColumnNames
.Where(c -> c <> Target)
.ToArray
else
Result := Data.Schema.ColumnNames;
end;
function Dataset.HasTarget: boolean;
begin
Result := (Target <> nil) and (Target <> '');
end;
//-----------------------------
// Datasets
@ -814,7 +825,17 @@ begin
ds.Description := meta['description.en'];
ds.Task := ParseTask(meta['task']);
ds.Target := meta['target'];
if ds.Task in [TaskType.Regression, TaskType.Classification] then
begin
if not meta.ContainsKey('target') then
ArgumentError(ER_DATASET_TARGET_MISSING, name);
ds.Target := meta['target'];
end
else
ds.Target := nil;
ds.Features := ParseFeatures(meta);
ds.FeatureLabels := [];
@ -873,7 +894,25 @@ end;
static function Datasets.MoscowHousing: Dataset;
begin
Result := Load('moscow_housing');
var ds := Load('moscow_housing');
ds.Data := ds.Data.SetCategorical([
'renovation'
]);
Result := ds;
end;
static function Datasets.RussianCities: Dataset;
begin
var ds := Load('russian_cities');
ds.Data := ds.Data.SetCategorical([
'region_name',
'federal_district'
]);
Result := ds;
end;
static function Datasets.StudentExam: Dataset;

File diff suppressed because it is too large Load diff

View file

@ -120,12 +120,14 @@ type
}
/// Строит конвейер из последовательности шагов.
/// target и features используются только в режиме с моделью (когда в шагах присутствует IModel).
/// Шаги должны удовлетворять порядку: DataFrame* → Matrix* → Model.
class function Build(target: string; features: array of string;
/// Строит конвейер из шагов обработки данных и модели.
///
/// Используется в задачах с учителем (с target).
/// Шаги выполняются последовательно:
/// DataFrame-преобразования → (при необходимости) матричные шаги → модель.
static function Build(target: string; features: array of string;
params steps: array of IPipelineStep): DataPipeline;
/// Обучает конвейер на DataFrame.
/// Семантика:
/// • выполняет Fit/Transform для всех DataFrame-шагов;
@ -159,6 +161,53 @@ type
function ToString: string; override;
end;
UDataPipeline = class
private
fDataSteps: List<IPreprocessor>;
fMatrixSteps: List<ITransformer>;
fModel: IModel;
fFeatures: array of string;
fFinalFeatures: array of string;
fFitted: boolean;
procedure ValidateSchema(df: DataFrame);
public
/// Создаёт пустой конвейер
constructor Create;
/// Добавляет шаг в конец конвейера.
/// Принимает:
/// • IPreprocessor (DataFrame-уровень)
/// • ITransformer (Matrix-уровень)
/// • IModel (модель, должна быть последней)
function Add(step: IPipelineStep): UDataPipeline;
/// Строит unsupervised-конвейер из шагов обработки данных и модели.
static function Build(features: array of string;
params steps: array of IPipelineStep): UDataPipeline;
/// Обучает конвейер на DataFrame.
function Fit(df: DataFrame): UDataPipeline;
/// Применяет обученные DataFrame-шаги к DataFrame и возвращает новый DataFrame.
function Transform(df: DataFrame): DataFrame;
/// Выполняет Fit и Transform за один проход для DataFrame-режима.
/// Недоступен, если конвейер содержит модель (IModel).
function FitTransform(df: DataFrame): DataFrame;
/// Делает предсказание модели для объектов из DataFrame.
function Predict(df: DataFrame): Vector;
/// Признак того, что был вызван Fit или FitTransform.
property IsFitted: boolean read fFitted;
function ToString: string; override;
end;
implementation
uses MLExceptions;
@ -175,8 +224,9 @@ const
'В конвейере разрешена только одна модель!!Only one model is allowed in the pipeline';
ER_DATAPIPE_MATRIX_AFTER_MODEL =
'Матричный шаг не может идти после модели!!Matrix step cannot appear after the model';
ER_DATAPIPE_UNKNOWN_STEP =
'Неизвестный тип шага конвейера!!Unknown pipeline step type';
ER_DATAPIPE_UNKNOWN_STEP_TYPE =
'Неизвестный тип шага конвейера: {0}!!Unknown pipeline step type: {0}';
ER_TO_MATRIX_NO_COLUMNS =
'ToMatrix: не указаны столбцы!!ToMatrix: no columns specified';
ER_TO_VECTOR_NON_NUMERIC =
@ -197,7 +247,8 @@ const
ER_TO_MATRIX_NON_NUMERIC =
'Столбец "{0}" содержит нечисловые или NA значения!!Column "{0}" contains non-numeric or NA values';
ER_DATAPIPE_TARGET_NOT_FOUND =
'Целевой столбец "{0}" не найден!!Target column "{0}" not found';
'Целевой столбец "{0}" не найден. Доступные столбцы: {1}!!' +
'Target column "{0}" not found. Available columns: {1}';
ER_DATAPIPE_FEATURE_NOT_FOUND =
'Признак "{0}" не найден в DataFrame. Доступные столбцы: {1}!!'+
'Feature "{0}" not found in DataFrame. Available columns: {1}';
@ -209,6 +260,22 @@ const
'Целевая переменная "{0}" была удалена на этапе preprocessing pipeline!!Target column "{0}" was removed during pipeline preprocessing';
ER_PIPELINE_NO_FEATURES =
'После preprocessing pipeline не осталось признаков для обучения модели!!No features remain after pipeline preprocessing';
ER_MATRIXSTEP_NO_FIT =
'Шаг матричного конвейера #{0} не поддерживает Fit!!Matrix step #{0} does not support Fit';
ER_Model_NoFit =
'Модель (тип: {0}) не поддерживает Fit!!Model (type: {0}) does not support Fit';
ER_MODEL_NOT_SUPERVISED =
'Модель (тип: {0}) не поддерживает Fit(X, y)!!' +
'Model (type: {0}) does not support Fit(X, y)';
ER_XY_SIZE_MISMATCH =
'Несовпадение размеров X и y: X имеет {0} строк, y имеет {1} элементов!!' +
'X and y size mismatch: X has {0} rows, y has {1} elements';
ER_PIPELINE_FEATURE_NOT_FOUND =
'Признак "{0}" отсутствует во входных данных!!' +
'Feature "{0}" not found in input data';
ER_MODEL_NOT_UNSUPERVISED =
'Модель (тип: {0}) не является моделью без учителя!!' +
'Model (type: {0}) is not an unsupervised model';
//-----------------------------
// DataPipeline
@ -262,7 +329,7 @@ begin
exit(Self);
end;
ArgumentError(ER_DATAPIPE_UNKNOWN_STEP + ' ' + step.ToString);
ArgumentError(ER_DATAPIPE_UNKNOWN_STEP_TYPE, step.ToString);
Result := Self;
end;
@ -303,8 +370,10 @@ end;
function DataPipeline.Fit(df: DataFrame): DataPipeline;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if fModel = nil then
Error(ER_MODEL_NULL);
ArgumentError(ER_MODEL_NULL);
var current := df;
@ -336,7 +405,8 @@ begin
// искать производные признаки (OneHotEncoder)
foreach var c in current.Schema.ColumnNames do
if c.StartsWith(f + '_') then
feats.Add(c);
if not feats.Contains(c) then
feats.Add(c);
end;
if feats.Count = 0 then
@ -346,22 +416,28 @@ begin
var X := current.ToMatrix(fFinalFeatures);
var y := current.ToVector(fTarget);
if X.RowCount <> y.Length then
DimensionError(ER_XY_SIZE_MISMATCH, X.RowCount, y.Length);
// --- 3) Matrix transformers
for var i := 0 to fMatrixSteps.Count - 1 do
begin
var t := fMatrixSteps[i];
if t is ISupervisedTransformer(var sup) then
fMatrixSteps[i] := sup.Fit(X, y)
else
fMatrixSteps[i] := t.Fit(X);
else if t is IUnsupervisedTransformer(var unsup) then
fMatrixSteps[i] := unsup.Fit(X)
else ArgumentError(ER_MATRIXSTEP_NO_FIT, i);
X := fMatrixSteps[i].Transform(X);
end;
// --- 4) модель
fModel := fModel.Fit(X, y);
if fModel is ISupervisedModel(var supModel) then
fModel := supModel.Fit(X, y)
else ArgumentError(ER_MODEL_NOT_SUPERVISED, fModel.GetType.Name);
fFitted := true;
Result := Self;
@ -369,6 +445,8 @@ 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);
@ -381,6 +459,8 @@ 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);
@ -398,6 +478,8 @@ end;
function DataPipeline.Predict(df: DataFrame): Vector;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
@ -408,6 +490,10 @@ begin
if fFinalFeatures = nil then
Error(ER_PIPELINE_FINALFEATURES);
for var i := 0 to High(fFinalFeatures) do
if not current.HasColumn(fFinalFeatures[i]) then
ArgumentError(ER_PIPELINE_FEATURE_NOT_FOUND, fFinalFeatures[i]);
var X := current.ToMatrix(fFinalFeatures);
@ -419,11 +505,13 @@ end;
function DataPipeline.PredictProba(df: DataFrame): Matrix;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
if not (fModel is IProbabilisticClassifier) then
Error(ER_PROBA_NOT_SUPPORTED);
ArgumentError(ER_PROBA_NOT_SUPPORTED);
var current := Transform(df);
@ -440,6 +528,8 @@ end;
function DataPipeline.GetClasses: array of real;
begin
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
if not (fModel is IProbabilisticClassifier) then
ArgumentError(ER_PROBA_NOT_SUPPORTED);
@ -457,7 +547,7 @@ begin
// target не должен входить в features
for var i := 0 to High(fFeatures) do
if fFeatures[i] = fTarget then
ArgumentError(ER_DATAPIPE_TARGET_IN_FEATURES);
ArgumentError(ER_DATAPIPE_TARGET_IN_FEATURES, fTarget);
// проверка существования target
if not df.Schema.HasColumn(fTarget) then
@ -516,4 +606,266 @@ begin
Result := sb;
end;
//-----------------------------
// UDataPipeline
//-----------------------------
constructor UDataPipeline.Create;
begin
fDataSteps := new List<IPreprocessor>;
fMatrixSteps := new List<ITransformer>;
fModel := nil;
fFeatures := nil;
fFitted := false;
end;
function UDataPipeline.Add(step: IPipelineStep): UDataPipeline;
begin
if step = nil then
ArgumentError(ER_PIPELINE_STEP_NULL);
if fFitted then
Error(ER_PIPELINE_MODIFY_AFTER_FIT);
// --- DataFrame step
if step is IPreprocessor then
begin
if (fMatrixSteps.Count > 0) or (fModel <> nil) then
ArgumentError(ER_DATAPIPE_DF_AFTER_MATRIX);
fDataSteps.Add(step as IPreprocessor);
exit(Self);
end;
// --- Matrix transformer
if step is ITransformer then
begin
if fModel <> nil then
ArgumentError(ER_DATAPIPE_MATRIX_AFTER_MODEL);
fMatrixSteps.Add(step as ITransformer);
exit(Self);
end;
// --- Model (обязательно последний шаг)
if step is IModel then
begin
if fModel <> nil then
ArgumentError(ER_PIPELINE_MULTIPLE_MODELS);
fModel := step as IModel;
exit(Self);
end;
ArgumentError(ER_DATAPIPE_UNKNOWN_STEP_TYPE, step.ToString);
Result := Self;
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<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;
var p := new UDataPipeline;
p.fFeatures := features;
for var i := 0 to High(steps) do
p.Add(steps[i]);
Result := p;
end;
function UDataPipeline.Fit(df: DataFrame): UDataPipeline;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if fModel = nil then
ArgumentError(ER_MODEL_NULL);
var current := df;
// --- 0) Проверка входной схемы
ValidateSchema(current);
// --- 1) DataFrame шаги
for var i := 0 to fDataSteps.Count - 1 do
begin
fDataSteps[i] := fDataSteps[i].Fit(current);
current := fDataSteps[i].Transform(current);
end;
// --- 2) вычислить финальные признаки
var feats := new List<string>;
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);
// --- 3) Matrix transformers
for var i := 0 to fMatrixSteps.Count - 1 do
begin
var t := fMatrixSteps[i];
if t is IUnsupervisedTransformer(var unsup) then
fMatrixSteps[i] := unsup.Fit(X)
else
ArgumentError(ER_MATRIXSTEP_NO_FIT, i, t.GetType.Name);
X := fMatrixSteps[i].Transform(X);
end;
// --- 4) модель
if fModel is IUnsupervisedModel(var unsupModel) then
fModel := unsupModel.Fit(X)
else
ArgumentError(ER_MODEL_NOT_UNSUPERVISED, fModel.GetType.Name);
fFitted := true;
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.Predict(df: DataFrame): Vector;
begin
if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df');
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
if fModel = nil then
ArgumentError(ER_MODEL_NULL);
var current := Transform(df);
if fFinalFeatures = nil then
Error(ER_PIPELINE_FINALFEATURES);
for var i := 0 to High(fFinalFeatures) do
if not current.HasColumn(fFinalFeatures[i]) then
ArgumentError(ER_PIPELINE_FEATURE_NOT_FOUND, fFinalFeatures[i]);
var X := current.ToMatrix(fFinalFeatures);
foreach var t in fMatrixSteps do
X := t.Transform(X);
Result := fModel.Predict(X);
end;
procedure UDataPipeline.ValidateSchema(df: DataFrame);
begin
if (fFeatures = nil) or (Length(fFeatures) = 0) then
ArgumentError(ER_FEATURES_EMPTY);
var seen := new HashSet<string>;
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;
function UDataPipeline.ToString: string;
begin
var sb := 'UDataPipeline (' +
(if fFitted then 'trained' else 'not trained') + '):' + NewLine;
var idx := 1;
foreach var s in fDataSteps do
begin
sb += ' [' + idx + '] ' + s.ToString + NewLine;
idx += 1;
end;
foreach var t in fMatrixSteps do
begin
sb += ' [' + idx + '] ' + t.ToString + NewLine;
idx += 1;
end;
if fModel <> nil then
sb += ' [' + idx + '] ' + fModel.ToString;
Result := sb;
end;
end.

View file

@ -40,11 +40,11 @@ type
/// Кодирует строковый категориальный столбец в числовые значения
/// Категории фиксируются при Fit
/// Работает только со строковыми столбцами
LabelEncoder = class(IPreprocessor)
private
col: string;
mapping: Dictionary<string, integer>;
fitted: boolean;
LabelEncoder = class(IPreprocessor)
private
col: string;
mapping: Dictionary<string, integer>;
fitted: boolean;
public
/// Создаёт LabelEncoder для указанного столбца
constructor Create(column: string);

View file

@ -32,20 +32,20 @@ type
static function StratifiedKFold(y: Vector; k: integer;
seed: integer := -1): sequence of (array of integer, array of integer);
/// Выполняет k-fold кросс-валидацию модели.
/// Выполняет k-fold кросс-валидацию модели с учителем.
/// На каждом шаге модель обучается на обучающей части и оценивается на соответствующей тестовой части.
/// metric — функция качества, принимающая (y_true, y_pred)
/// и возвращающая значение метрики (например, Accuracy или MSE).
/// Возвращает среднее значение метрики по всем частям.
static function CrossValidate(model: IModel; X: Matrix; y: Vector;
static function CrossValidate(model: ISupervisedModel; X: Matrix; y: Vector;
k: integer; metric: (Vector,Vector) -> real; seed: integer := -1): real;
/// Выполняет стратифицированную k-fold кросс-валидацию модели.
/// Выполняет стратифицированную k-fold кросс-валидацию модели с учителем.
/// Разбиение данных выполняется методом StratifiedKFold
/// с сохранением пропорций классов в каждой части.
/// Рекомендуется для задач классификации, особенно при несбалансированных классах.
/// Возвращает среднее значение метрики по k разбиениям.
static function StratifiedCrossValidate(model: IModel; X: Matrix; y: Vector;
static function StratifiedCrossValidate(model: ISupervisedModel; X: Matrix; y: Vector;
k: integer; metric: (Vector,Vector) -> real; seed: integer := -1): real;
end;
@ -70,7 +70,7 @@ type
k: integer;
metric: (Vector, Vector) -> real;
maximize: boolean := True
): (real, real, T); where T: IModel;
): (real, real, T); where T: class,ISupervisedModel;
end;
implementation
@ -91,11 +91,14 @@ const
'Некорректное значение k в StratifiedKFold: k={0}, n={1}!!' +
'Invalid k in StratifiedKFold: k={0}, n={1}';
ER_STRATIFIED_LABELS_INVALID =
'StratifiedKFold поддерживает только метки 0/1!!' +
'StratifiedKFold supports only 0/1 labels';
'StratifiedKFold поддерживает только целочисленные метки классов!!' +
'StratifiedKFold supports only integer class labels';
ER_INVALID_VALUE =
'Некорректное значение параметра {0}!!Invalid value for parameter {0}';
'Некорректное значение параметра {0}!!Invalid value for parameter {0}';
ER_DATASET_TOO_SMALL =
'Для {0} требуется как минимум 2 объекта!!' +
'At least 2 samples are required for {0}';
//-----------------------------
// Validation
//-----------------------------
@ -119,7 +122,7 @@ begin
var p := X.ColCount;
if n < 2 then
ArgumentError(ER_EMPTY_DATA, 'TrainTestSplit');
ArgumentError(ER_DATASET_TOO_SMALL, 'TrainTestSplit');
var actualSeed := if seed >= 0 then seed else System.Environment.TickCount and integer.MaxValue;
var rnd := new System.Random(actualSeed);
@ -296,7 +299,7 @@ begin
end;
static function Validation.CrossValidate(
model: IModel;
model: ISupervisedModel;
X: Matrix;
y: Vector;
k: integer;
@ -349,8 +352,8 @@ begin
yte[i] := y[r];
end;
var m := model.Clone();
m.Fit(Xtr, ytr);
var m := model.Clone() as ISupervisedModel;
m := m.Fit(Xtr, ytr);
var pred := m.Predict(Xte);
@ -365,7 +368,7 @@ begin
end;
static function Validation.StratifiedCrossValidate(
model: IModel;
model: ISupervisedModel;
X: Matrix;
y: Vector;
k: integer;
@ -418,8 +421,8 @@ begin
yte[i] := y[r];
end;
var m := model.Clone();
m.Fit(Xtr, ytr);
var m := model.Clone() as ISupervisedModel;
m := m.Fit(Xtr, ytr);
var pred := m.Predict(Xte);
@ -445,7 +448,7 @@ class function GridSearch.Search<T>(
k: integer;
metric: (Vector, Vector) -> real;
maximize: boolean
): (real, real, T); where T: IModel;
): (real, real, T); where T: class,ISupervisedModel;
begin
if modelFactory = nil then
ArgumentNullError(ER_ARG_NULL, 'modelFactory');
@ -475,6 +478,8 @@ begin
foreach var param in paramValues do
begin
var model := modelFactory(param);
if model = nil then
ArgumentError(ER_MODEL_NULL);
var avgScore := Validation.CrossValidate(model, X, y, k, metric);
if double.IsNaN(avgScore) or double.IsInfinity(avgScore) then
@ -492,7 +497,10 @@ begin
end;
var bestModel := modelFactory(bestParam);
bestModel.Fit(X, y);
if bestModel = nil then
ArgumentError(ER_MODEL_NULL);
bestModel := bestModel.Fit(X, y) as T;
Result := (bestParam, bestScore, bestModel);
end;