ML - правки в интерфейсах и Pipeline
This commit is contained in:
parent
50e09e1b06
commit
2c49b0d2d8
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
%MINOR%=11
|
||||
%REVISION%=3779
|
||||
%REVISION%=3781
|
||||
%COREVERSION%=1
|
||||
%MAJOR%=3
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
uses MLABC;
|
||||
|
||||
begin
|
||||
var ds := Datasets.RussianCities;
|
||||
|
||||
var df := ds.Data;
|
||||
|
||||
df.Schema.Println;
|
||||
df.PrintlnInfo;
|
||||
end.
|
||||
|
|
@ -1 +1 @@
|
|||
3.11.1.3779
|
||||
3.11.1.3781
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
!define VERSION '3.11.1.3779'
|
||||
!define VERSION '3.11.1.3781'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -537,8 +537,6 @@ begin
|
|||
Assert(fIndexByName.Count = fNames.Length);
|
||||
end;
|
||||
|
||||
|
||||
|
||||
//-----------------------------
|
||||
// Columns
|
||||
//-----------------------------
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue