diff --git a/Configuration/GlobalAssemblyInfo.cs b/Configuration/GlobalAssemblyInfo.cs index 3616bfdcf..9d0b43efc 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 = "3764"; + public const string Revision = "3765"; 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 8fa2a5486..a087cbe9b 100644 --- a/Configuration/Version.defs +++ b/Configuration/Version.defs @@ -1,4 +1,4 @@ -%COREVERSION%=1 -%REVISION%=3764 %MINOR%=11 +%REVISION%=3765 +%COREVERSION%=1 %MAJOR%=3 diff --git a/Release/pabcversion.txt b/Release/pabcversion.txt index ac195c16e..993ed43d9 100644 --- a/Release/pabcversion.txt +++ b/Release/pabcversion.txt @@ -1 +1 @@ -3.11.1.3764 +3.11.1.3765 diff --git a/ReleaseGenerators/PascalABCNET_version.nsh b/ReleaseGenerators/PascalABCNET_version.nsh index 1934e27bd..0dd0e4edc 100644 --- a/ReleaseGenerators/PascalABCNET_version.nsh +++ b/ReleaseGenerators/PascalABCNET_version.nsh @@ -1 +1 @@ -!define VERSION '3.11.1.3764' +!define VERSION '3.11.1.3765' diff --git a/ReleaseGenerators/RebuildStandartModules.pas b/ReleaseGenerators/RebuildStandartModules.pas index 36c7fdf93..25ef1bd61 100644 --- a/ReleaseGenerators/RebuildStandartModules.pas +++ b/ReleaseGenerators/RebuildStandartModules.pas @@ -21,7 +21,7 @@ uses TasksArr, TasksMatr, TasksStr, Tasks1Begin, Tasks1BoolIfCase, Tasks1Loops, Tasks1Arr, WPF, DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC, - MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML + MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC ; begin diff --git a/ReleaseGenerators/RebuildStandartModulesMono.pas b/ReleaseGenerators/RebuildStandartModulesMono.pas index 91fb09cef..a6fe17608 100644 --- a/ReleaseGenerators/RebuildStandartModulesMono.pas +++ b/ReleaseGenerators/RebuildStandartModulesMono.pas @@ -11,7 +11,7 @@ ABCDatabases, School, SF, TurtleABC, DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC, - MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML + MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC ; begin diff --git a/ReleaseGenerators/sect_Core.nsh b/ReleaseGenerators/sect_Core.nsh index a51d7c4c1..0164e3f93 100644 --- a/ReleaseGenerators/sect_Core.nsh +++ b/ReleaseGenerators/sect_Core.nsh @@ -190,6 +190,7 @@ File ..\bin\Lib\ValidationML.pcu File ..\bin\Lib\MLExceptions.pcu File ..\bin\Lib\InspectionML.pcu + File ..\bin\Lib\MLPipelineABC.pcu File ..\bin\Lib\PABCRtl.dll File ..\bin\Lib\HelixToolkit.Wpf.dll @@ -294,6 +295,7 @@ ${AddFile} "ValidationML.pcu" ${AddFile} "MLExceptions.pcu" ${AddFile} "InspectionML.pcu" + ${AddFile} "MLPipelineABC.pcu" ${AddFile} "turtle.png" @@ -435,6 +437,7 @@ File ..\bin\Lib\ValidationML.pas File ..\bin\Lib\MLExceptions.pas File ..\bin\Lib\InspectionML.pas + File ..\bin\Lib\MLPipelineABC.pas File ..\bin\Lib\__RedirectIOMode.vb File ..\bin\Lib\VBSystem.vb @@ -526,6 +529,8 @@ ${AddFile} "ValidationML.pas" ${AddFile} "MLExceptions.pas" ${AddFile} "InspectionML.pas" + ${AddFile} "MLPipelineABC.pas" + ${AddFile} "__RedirectIOMode.vb" ${AddFile} "VBSystem.vb" diff --git a/_GenerateLinuxVersion.bat b/_GenerateLinuxVersion.bat index 140bf35cf..93df2df6e 100644 --- a/_GenerateLinuxVersion.bat +++ b/_GenerateLinuxVersion.bat @@ -119,6 +119,9 @@ copy bin\Lib\MLABC.pcu Release\PascalABCNETLinux\Lib\MLABC.pcu copy bin\Lib\MLCoreABC.pcu Release\PascalABCNETLinux\Lib\MLCoreABC.pcu copy bin\Lib\MLModelsABC.pcu Release\PascalABCNETLinux\Lib\MLModelsABC.pcu copy bin\Lib\ValidationML.pcu Release\PascalABCNETLinux\Lib\ValidationML.pcu +copy bin\Lib\MLExceptions.pcu Release\PascalABCNETLinux\Lib\MLExceptions.pcu +copy bin\Lib\InspectionML.pcu Release\PascalABCNETLinux\Lib\InspectionML.pcu +copy bin\Lib\MLPipelineABC.pcu Release\PascalABCNETLinux\Lib\MLPipelineABC.pcu copy bin\Lib\ABCDatabases.pas Release\PascalABCNETLinux\LibSource\ABCDatabases.pas copy bin\Lib\BBCMicrobit.pas Release\PascalABCNETLinux\LibSource\BBCMicrobit.pas @@ -169,6 +172,10 @@ copy bin\Lib\MLABC.pas Release\PascalABCNETLinux\LibSource\MLABC.pas copy bin\Lib\MLCoreABC.pas Release\PascalABCNETLinux\LibSource\MLCoreABC.pas copy bin\Lib\MLModelsABC.pas Release\PascalABCNETLinux\LibSource\MLModelsABC.pas copy bin\Lib\ValidationML.pas Release\PascalABCNETLinux\LibSource\ValidationML.pas +copy bin\Lib\MLExceptions.pas Release\PascalABCNETLinux\LibSource\MLExceptions.pas +copy bin\Lib\InspectionML.pas Release\PascalABCNETLinux\LibSource\InspectionML.pas +copy bin\Lib\MLPipelineABC.pas Release\PascalABCNETLinux\LibSource\MLPipelineABC.pas + copy bin\Lng\Eng\.LanguageName Release\PascalABCNETLinux\Lng\Eng\.LanguageName copy bin\Lng\Eng\AspectsTree.dat Release\PascalABCNETLinux\Lng\Eng\AspectsTree.dat diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index c9b99c034..e8ae8c4ac 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -6,24 +6,31 @@ interface uses LinearAlgebraML; uses ValidationML; +uses MLCoreABC; uses MLModelsABC; uses MetricsABC; uses PreprocessorABC; uses DataFrameABC; uses MLExceptions; uses InspectionML; +uses MLPipelineABC; type Vector = LinearAlgebraML.Vector; Matrix = LinearAlgebraML.Matrix; + Validation = ValidationML.Validation; + ConfusionMatrix = MetricsABC.ConfusionMatrix; Metrics = MetricsABC.Metrics; - DataPipeline = PreprocessorABC.DataPipeline; - DataStandardScaler = PreprocessorABC.DataStandardScaler; + + DataPipeline = MLPipelineABC.DataPipeline; + DataFrame = DataFrameABC.DataFrame; Statistics = DataFrameABC.Statistics; CsvLoader = DataFrameABC.CsvLoader; + + IProbabilisticClassifier = MLCoreABC.IProbabilisticClassifier; StandardScaler = MLModelsABC.StandardScaler; PCATransformer = MLModelsABC.PCATransformer; @@ -63,60 +70,5 @@ type implementation -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'; - -function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod; -begin - var df := Self; - var n := df.RowCount; - var p := colNames.Length; - - if p = 0 then - ArgumentError(ER_TO_MATRIX_NO_COLUMNS); - - Result := new Matrix(n, p); - - for var j := 0 to p - 1 do - begin - var col := df[colNames[j]]; - - for var i := 0 to n - 1 do - begin - var value: real; - - if not col.TryGetNumericValue(i, value) then - raise new Exception( - 'ToMatrix: column "' + colNames[j] + - '" contains non-numeric or NA values'); - - Result[i,j] := value; - end; - end; -end; - -function ToVector(Self: DataFrame; colName: string): Vector; extensionmethod; -begin - var df := Self; - var n := df.RowCount; - Result := new Vector(n); - - var col := df[colName]; - - for var i := 0 to n - 1 do - begin - var value: real; - - if not col.TryGetNumericValue(i, value) then - ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName); - - Result[i] := value; - end; -end; - end. \ No newline at end of file diff --git a/bin/Lib/MLCoreABC.pas b/bin/Lib/MLCoreABC.pas index f8250318c..1f3f9c075 100644 --- a/bin/Lib/MLCoreABC.pas +++ b/bin/Lib/MLCoreABC.pas @@ -5,13 +5,24 @@ interface uses LinearAlgebraML; type - /// Базовый интерфейс шага конвейера машинного обучения. - /// Используется для объединения преобразователей и моделей - /// в единую последовательность выполнения внутри Pipeline. - IPipeStep = interface end; +/// Базовый интерфейс шага конвейера машинного обучения. +/// Используется для объединения всех типов шагов +/// (DataFrame-преобразователей, матричных трансформеров и моделей) +/// в единую последовательность выполнения внутри различных Pipeline + IPipelineStep = interface end; + +/// Интерфейс шага конвейера, работающего в числовом (матричном) пространстве. +/// Реализуется трансформерами и моделями, которые принимают на вход Matrix и Vector +/// и используются в Matrix-уровне Pipeline + IMatrixStep = interface(IPipelineStep) end; + +/// Интерфейс шага конвейера, работающего на уровне DataFrame. +/// Реализуется препроцессорами, которые выполняют преобразования табличных данных +/// до перехода в числовое (Matrix) представление + IDataStep = interface(IPipelineStep) end; /// Базовый интерфейс модели машинного обучения - IModel = interface(IPipeStep) + IModel = interface(IMatrixStep) function Fit(X: Matrix; y: Vector): IModel; function Predict(X: Matrix): Vector; function Clone: IModel; @@ -36,8 +47,12 @@ type /// Позволяет получать значения в диапазоне (0, 1) /// вместо только итогового решения. IProbabilisticClassifier = interface(IClassifier) - /// Возвращает вероятность принадлежности к положительному классу для каждого объекта. + /// Возвращает матрицу вероятностей размера (nSamples × nClasses). + /// Столбцы соответствуют классам в порядке внутреннего кодирования модели function PredictProba(X: Matrix): Matrix; + + /// Возвращает массив меток классов в порядке столбцов PredictProba. + function GetClasses: array of real; end; /// Интерфейс регрессионной модели. @@ -49,7 +64,7 @@ type /// Базовый интерфейс преобразования признаков. /// Используется для масштабирования, отбора, /// уменьшения размерности и других преобразований данных. - ITransformer = interface(IPipeStep) + ITransformer = interface(IMatrixStep) /// Обучает преобразование на данных. /// Запоминает необходимые параметры, /// которые будут использоваться при Transform. diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index 7d1f9ec1c..cc28f558d 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -223,6 +223,9 @@ type /// Возвращает матрицу вероятностей (m x k). function PredictProba(X: Matrix): Matrix; + /// Возвращает массив меток классов в порядке столбцов PredictProba. + function GetClasses: array of real; + /// Возвращает вектор предсказанных классов. function Predict(X: Matrix): Vector; @@ -905,6 +908,8 @@ type /// Вероятности получаются через softmax. function PredictProba(X: Matrix): Matrix; + function GetClasses: array of real; + /// Создает глубокую копию классификатора. /// Копируются все деревья и внутреннее состояние. function Clone: IModel; @@ -949,8 +954,14 @@ type idx: integer; end; +/// Режим взвешивания в алгоритме k ближайших соседей. +/// Uniform — равномерное голосование/усреднение. +/// Distance — веса обратно пропорциональны расстоянию (1 / dist) KNNWeighting = (Uniform, Distance); +/// Базовый абстрактный класс для алгоритма k ближайших соседей (kNN). +/// Реализует общий механизм поиска k ближайших объектов, +/// но не определяет способ агрегации (классификация или регрессия) KNNBase = abstract class(IModel) protected // ==== train state ==== @@ -973,14 +984,26 @@ type function Partition(left, right: integer): integer; public + /// Создаёт модель kNN. + /// k — число ближайших соседей (k > 0). + /// weighting — режим взвешивания соседей constructor Create(k: integer; weighting: KNNWeighting := KNNWeighting.Uniform); - + + /// Обучает модель на матрице признаков X и целевом векторе y. + /// Возвращает текущий экземпляр модели function Fit(X: Matrix; y: Vector): IModel; virtual; abstract; + + /// Выполняет предсказание для матрицы признаков X. + /// Возвращает вектор предсказанных значений или меток function Predict(X: Matrix): Vector; virtual; abstract; - + + /// Создаёт глубокую копию модели function Clone: IModel; virtual; abstract; end; + /// Классификатор на основе алгоритма k ближайших соседей (kNN). + /// Поддерживает равномерное (Uniform) и взвешенное по расстоянию (Distance) голосование. + /// Реализует вероятностные предсказания через PredictProba KNNClassifier = class(KNNBase, IProbabilisticClassifier) private // ==== classification state ==== @@ -997,27 +1020,57 @@ type procedure EncodeClasses(y: Vector); public + /// Создаёт классификатор kNN. + /// k — число ближайших соседей (k > 0). + /// weighting — режим взвешивания голосов соседей constructor Create(k: integer; weighting: KNNWeighting := KNNWeighting.Uniform); - + + /// Обучает модель на матрице признаков X и векторе меток y. + /// Метки классов могут быть произвольными числами. + /// Возвращает текущий экземпляр модели function Fit(X: Matrix; y: Vector): IModel; override; + + /// Выполняет предсказание меток классов для объектов X. + /// Возвращает вектор предсказанных меток function Predict(X: Matrix): Vector; override; + + /// Возвращает матрицу вероятностей размера (nSamples × nClasses). + /// Столбцы соответствуют классам в порядке, возвращаемом GetClasses() function PredictProba(X: Matrix): Matrix; - function GetClasses: array of double; - + + /// Возвращает массив меток классов в порядке столбцов PredictProba + function GetClasses: array of real; + + /// Создаёт глубокую копию классификатора function Clone: IModel; override; end; + /// Регрессор на основе алгоритма k ближайших соседей (kNN). + /// Поддерживает равномерное (Uniform) усреднение и взвешенное по расстоянию (Distance) усреднение. + /// Предсказание вычисляется как среднее (или взвешенное среднее) значений целевой переменной + /// по k ближайшим обучающим объектам KNNRegressor = class(KNNBase, IRegressor) private fYTrain: Vector; public + /// Создаёт регрессор kNN. + /// k — число ближайших соседей (k > 0). + /// weighting — режим взвешивания вкладов соседей constructor Create(k: integer; weighting: KNNWeighting := KNNWeighting.Uniform); - + + /// Обучает модель на матрице признаков X и целевом векторе y. + /// Возвращает текущий экземпляр модели function Fit(X: Matrix; y: Vector): IModel; override; + + /// Выполняет предсказание числовых значений для объектов X. + /// Возвращает вектор предсказанных значений function Predict(X: Matrix): Vector; override; + + /// Создаёт глубокую копию регрессора function Clone: IModel; override; end; + {$endregion Models} {$region Pipeline} @@ -1032,7 +1085,7 @@ type /// /// Обеспечивает единый интерфейс Fit / Predict /// и воспроизводимость полного процесса обучения - Pipeline = class(IModel) + Pipeline = class(IModel, IProbabilisticClassifier) private fTransformers: List; fModel: IModel; @@ -1052,7 +1105,7 @@ type /// сначала преобразователи, затем модель. /// Последний шаг обязан быть моделью (IModel). /// Возвращает сконструированный конвейер. - static function Build(params steps: array of IPipeStep): Pipeline; + static function Build(params steps: array of IPipelineStep): Pipeline; /// Устанавливает или заменяет модель. function SetModel(m: IModel): Pipeline; @@ -1072,6 +1125,8 @@ type /// Возвращает матрицу вероятностей (m x k) /// для вероятностной модели в конце пайплайна. function PredictProba(X: Matrix): Matrix; + + function GetClasses: array of real; /// Показывает, был ли пайплайн обучен (вызван метод Fit). property IsFitted: boolean read fFitted; @@ -1080,6 +1135,7 @@ type function Clone: IModel; end; + {$endregion Pipeline} {$region Transformers} @@ -1922,6 +1978,13 @@ begin Result := Z; end; +function LogisticRegression.GetClasses: array of real; +begin + SetLength(Result, fClassCount); + for var i := 0 to fClassCount - 1 do + Result[i] := fIndexToClass[i]; +end; + function LogisticRegression.Predict(X: Matrix): Vector; begin if not fFitted then @@ -4328,6 +4391,13 @@ begin Result := probs; end; +function GradientBoostingClassifier.GetClasses: array of real; +begin + SetLength(Result, fClassCount); + for var i := 0 to fClassCount - 1 do + Result[i] := fClasses[i]; +end; + function GradientBoostingClassifier.PredictStageProba( X: Matrix; m: integer): Matrix; begin @@ -4724,7 +4794,7 @@ begin exit(self); end; -function KNNClassifier.GetClasses: array of double; +function KNNClassifier.GetClasses: array of real; begin Result := fClasses; end; @@ -5117,7 +5187,7 @@ begin fModel := model; end; -class function Pipeline.Build(params steps: array of IPipeStep): Pipeline; +class function Pipeline.Build(params steps: array of IPipelineStep): Pipeline; begin if (steps = nil) or (Length(steps) = 0) then ArgumentError(ER_PIPELINE_NO_STEPS); @@ -5220,6 +5290,14 @@ begin .PredictProba(Xt); end; +function Pipeline.GetClasses: array of real; +begin + if not (fModel is IProbabilisticClassifier) then + ArgumentError(ER_PROBA_NOT_SUPPORTED); + + Result := (fModel as IProbabilisticClassifier).GetClasses; +end; + function Pipeline.ToString: string; begin var sb := 'Pipeline (' + @@ -5250,6 +5328,8 @@ begin p.Add(t.Clone); p.SetModel(fModel.Clone); + + p.fFitted := fFitted; Result := p; end; diff --git a/bin/Lib/MLPipelineABC.pas b/bin/Lib/MLPipelineABC.pas new file mode 100644 index 000000000..042c6c03b --- /dev/null +++ b/bin/Lib/MLPipelineABC.pas @@ -0,0 +1,372 @@ +unit MLPipelineABC; + +interface + +uses MLCoreABC; +uses PreprocessorABC; +uses DataFrameABC; +uses LinearAlgebraML; + +type +/// DataPipeline — конвейер подготовки данных и обучения модели на DataFrame. + /// Поддерживает два уровня шагов: + /// • DataFrame-уровень: IPreprocessor (Fit/Transform над DataFrame) + /// • Matrix-уровень: ITransformer + IModel (после преобразования DataFrame → Matrix/Vector) + /// + /// Правила порядка шагов: + /// • Сначала идут только DataFrame-шаги (IPreprocessor). + /// • Затем — матричные шаги (ITransformer). + /// • Модель (IModel) добавляется последней и может быть только одна. + /// + /// Если модель не добавлена, DataPipeline работает как чистый DF-конвейер (Fit/Transform/FitTransform) + DataPipeline = class + private + fDataSteps: List; + fMatrixSteps: List; + fModel: IModel; + + fTarget: string; + fFeatures: array of string; + + fFitted: boolean; + + procedure ValidateSchema(df: DataFrame); + public + /// Создаёт пустой конвейер + constructor Create; + + /// Добавляет шаг в конец конвейера. + /// Принимает: + /// • IPreprocessor (DataFrame-уровень) + /// • ITransformer (Matrix-уровень) + /// • IModel (модель, должна быть последней) + /// Запрещено добавлять шаги после вызова Fit/FitTransform. + function Add(step: IPipelineStep): DataPipeline; + + /// Строит конвейер из последовательности шагов. + /// target и features используются только в режиме с моделью (когда в шагах присутствует IModel). + /// Шаги должны удовлетворять порядку: DataFrame* → Matrix* → Model. + class function Build(target: string; features: array of string; + params steps: array of IPipelineStep): DataPipeline; + + /// Обучает конвейер на DataFrame. + /// Семантика: + /// • выполняет Fit/Transform для всех DataFrame-шагов; + /// • если присутствует модель — преобразует DataFrame в (X, y) по features/target, + /// затем обучает матричные трансформеры и модель. + function Fit(df: DataFrame): DataPipeline; + + /// Применяет обученные DataFrame-шаги к DataFrame и возвращает новый DataFrame. + /// В ML-режиме Transform не выполняет матричные шаги и не вызывает модель. + function Transform(df: DataFrame): DataFrame; + + /// Выполняет Fit и Transform за один проход для DataFrame-режима. + /// Недоступен, если конвейер содержит модель (IModel). + function FitTransform(df: DataFrame): DataFrame; + + /// Делает предсказание модели для объектов из DataFrame. + /// Доступен только если конвейер содержит модель (IModel) и был обучен (Fit). + function Predict(df: DataFrame): Vector; + + /// Возвращает матрицу вероятностей (nSamples × nClasses). + /// Доступен только если конечная модель поддерживает IProbabilisticClassifier. + function PredictProba(df: DataFrame): Matrix; + + /// Возвращает список классов в порядке столбцов PredictProba. + /// Доступен только если конечная модель поддерживает IProbabilisticClassifier. + function GetClasses: array of real; + + /// Признак того, что был вызван Fit или FitTransform. + property IsFitted: boolean read fFitted; + end; + +implementation + +uses MLExceptions; + +const + ER_PIPELINE_MODIFY_AFTER_FIT = + 'Нельзя добавлять шаг после вызова Fit()!!Cannot add step after Fit'; + ER_PIPELINE_STEP_NULL = + 'Шаг конвейера не может быть nil!!Pipeline step cannot be nil'; + ER_DATAPIPE_DF_AFTER_MATRIX = + 'Шаг DataFrame не может идти после матричных шагов!!DataFrame step cannot appear after matrix steps'; + ER_PIPELINE_MULTIPLE_MODELS = + 'В конвейере разрешена только одна модель!!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_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_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 = + 'Имя целевой переменной не задано!!Target column name is not specified'; + ER_FEATURES_EMPTY = + 'Список признаков пуст!!Feature list must not be empty'; + ER_DATAPIPE_TARGET_IN_FEATURES = + 'Целевая переменная не должна входить в список признаков!!Target column must not be included in feature list'; + 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'; + ER_DATAPIPE_FEATURE_NOT_FOUND = + 'Столбец признака "{0}" не найден!!Feature column "{0}" not found'; + +function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod; +begin + var df := Self; + var n := df.RowCount; + var p := colNames.Length; + + if p = 0 then + ArgumentError(ER_TO_MATRIX_NO_COLUMNS); + + Result := new Matrix(n, p); + + for var j := 0 to p - 1 do + begin + var col := df[colNames[j]]; + + for var i := 0 to n - 1 do + begin + var value: real; + + if not col.TryGetNumericValue(i, value) then + ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]); + + Result[i,j] := value; + end; + end; +end; + +function ToVector(Self: DataFrame; colName: string): Vector; extensionmethod; +begin + var df := Self; + var n := df.RowCount; + Result := new Vector(n); + + var col := df[colName]; + + for var i := 0 to n - 1 do + begin + var value: real; + + if not col.TryGetNumericValue(i, value) then + ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName); + + Result[i] := value; + end; +end; + +//----------------------------- +// DataPipeline +//----------------------------- + +constructor DataPipeline.Create; +begin + fDataSteps := new List; + fMatrixSteps := new List; + fModel := nil; + fTarget := ''; + fFeatures := nil; + fFitted := false; +end; + +function DataPipeline.Add(step: IPipelineStep): DataPipeline; +begin + if step = nil then + ArgumentError(ER_PIPELINE_STEP_NULL); + + if fFitted then + Error(ER_PIPELINE_MODIFY_AFTER_FIT); + + if step is IDataStep then + begin + if (fMatrixSteps.Count > 0) or (fModel <> nil) then + ArgumentError(ER_DATAPIPE_DF_AFTER_MATRIX); + + fDataSteps.Add(step as IPreprocessor); + exit(Self); + end; + + if step is IMatrixStep then + begin + if step is IModel then + begin + if fModel <> nil then + ArgumentError(ER_PIPELINE_MULTIPLE_MODELS); + + fModel := step as IModel; + exit(Self); + end; + + if fModel <> nil then + ArgumentError(ER_DATAPIPE_MATRIX_AFTER_MODEL); + + fMatrixSteps.Add(step as ITransformer); + exit(Self); + end; + + ArgumentError(ER_DATAPIPE_UNKNOWN_STEP); + Result := Self; +end; + +class function DataPipeline.Build(target: string; + features: array of string; params steps: array of IPipelineStep): DataPipeline; +begin + var p := new DataPipeline; + p.fTarget := target; + p.fFeatures := features; + + for var i := 0 to High(steps) do + p.Add(steps[i]); + + Result := p; +end; + +function DataPipeline.Fit(df: DataFrame): DataPipeline; +begin + var current := df; + + // 1) DataFrame слой (с сохранением fitted-объектов) + for var i := 0 to fDataSteps.Count - 1 do + begin + fDataSteps[i] := fDataSteps[i].Fit(current); + current := fDataSteps[i].Transform(current); + end; + + // DF-only режим + if fModel = nil then + begin + fFitted := true; + exit(Self); + end; + + // 2) Проверка схемы + ValidateSchema(current); + + // 3) Split + var X := current.ToMatrix(fFeatures); + var y := current.ToVector(fTarget); + + // 4) Matrix слой (с сохранением fitted-объектов) + 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); + + X := fMatrixSteps[i].Transform(X); + end; + + // 5) Модель + fModel.Fit(X, y); + + fFitted := true; + Result := Self; +end; + +function DataPipeline.Transform(df: DataFrame): DataFrame; +begin + 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 fModel <> nil then + ArgumentError(ER_FITTRANSFORM_WITH_MODEL); + + var current := df; + + for var i := 0 to fDataSteps.Count - 1 do + begin + fDataSteps[i] := fDataSteps[i].Fit(current); + current := fDataSteps[i].Transform(current); + end; + + fFitted := true; + Result := current; +end; + +function DataPipeline.Predict(df: DataFrame): Vector; +begin + if not fFitted then + NotFittedError(ER_FIT_NOT_CALLED); + + if fModel = nil then + ArgumentError(ER_MODEL_NULL); + + var current := Transform(df); + var X := current.ToMatrix(fFeatures); + + foreach var t in fMatrixSteps do + X := t.Transform(X); + + Result := fModel.Predict(X); +end; + +function DataPipeline.PredictProba(df: DataFrame): Matrix; +begin + if not fFitted then + NotFittedError(ER_FIT_NOT_CALLED); + + if not (fModel is IProbabilisticClassifier) then + Error(ER_PROBA_NOT_SUPPORTED); + + var current := Transform(df); + var X := current.ToMatrix(fFeatures); + + foreach var t in fMatrixSteps do + X := t.Transform(X); + + Result := (fModel as IProbabilisticClassifier).PredictProba(X); +end; + +function DataPipeline.GetClasses: array of real; +begin + if not (fModel is IProbabilisticClassifier) then + ArgumentError(ER_PROBA_NOT_SUPPORTED); + + Result := (fModel as IProbabilisticClassifier).GetClasses; +end; + +procedure DataPipeline.ValidateSchema(df: DataFrame); +begin + if fTarget = '' then + ArgumentError(ER_TARGET_EMPTY); + + if (fFeatures = nil) or (Length(fFeatures) = 0) then + ArgumentError(ER_FEATURES_EMPTY); + + for var i := 0 to High(fFeatures) do + if fFeatures[i] = fTarget then + ArgumentError(ER_DATAPIPE_TARGET_IN_FEATURES); + + if not df.Schema.HasColumn(fTarget) then + ArgumentError(ER_DATAPIPE_TARGET_NOT_FOUND, fTarget); + + for var i := 0 to High(fFeatures) do + if not df.Schema.HasColumn(fFeatures[i]) then + ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, fFeatures[i]); +end; + + + +end. \ No newline at end of file diff --git a/bin/Lib/PreprocessorABC.pas b/bin/Lib/PreprocessorABC.pas index 1d23ceb2e..d279a2daa 100644 --- a/bin/Lib/PreprocessorABC.pas +++ b/bin/Lib/PreprocessorABC.pas @@ -22,11 +22,12 @@ interface uses DataFrameABC; uses System; +uses MLCoreABC; type /// Базовый интерфейс шагов подготовки данных. /// Определяет семантику операций Fit и Transform - IPreprocessor = interface + IPreprocessor = interface(IDataStep) /// Анализирует DataFrame и и сохраняет параметры шага function Fit(df: DataFrame): IPreprocessor; /// Применяет сохранённые параметры к DataFrame. @@ -36,50 +37,6 @@ type function FitTransform(df: DataFrame): DataFrame; end; -/// Приводит числовые столбцы к нулевому среднему и единичному стандартному отклонению. -/// При Fit вычисляет среднее значение и стандартное отклонение столбцов. -/// Применяет преобразование: x' = (x - mean) / std. -/// Пропущенные значения (NA) сохраняются. - DataStandardScaler = class(IPreprocessor) - private - cols: array of string; - means: array of real; - stds: array of real; - fitted: boolean; - public - /// Создаёт StandardScaler для указанных числовых столбцов. - constructor Create(params columns: array of string); - /// Вычисляет среднее значение и стандартное отклонение для каждого столбца. - function Fit(df: DataFrame): IPreprocessor; - /// Возвращает DataFrame со стандартизованными числовыми столбцами. - function Transform(df: DataFrame): DataFrame; - /// Последовательно выполняет Fit и Transform. - function FitTransform(df: DataFrame): DataFrame; - end; - -/// Приводит числовые столбцы к заданному диапазону значений -/// При Fit вычисляет минимальное и максимальное значения столбцов -/// Применяет преобразование: x' = (x - min) / (max - min) -/// Пропущенные значения (NA) сохраняются - DataMinMaxScaler = class(IPreprocessor) - private - cols: array of string; - mins: array of real; - maxs: array of real; - fitted: boolean; - public - /// Создаёт MinMaxScaler для указанных столбцов - constructor Create(params columns: array of string); - - /// Вычисляет минимальные и максимальные значения столбцов - function Fit(df: DataFrame): IPreprocessor; - /// Применяет масштабирование к DataFrame - /// Возвращает новый DataFrame - function Transform(df: DataFrame): DataFrame; - /// Последовательно выполняет Fit и Transform. - function FitTransform(df: DataFrame): DataFrame; - end; - /// Кодирует строковый категориальный столбец в числовые значения /// Категории фиксируются при Fit /// Работает только со строковыми столбцами @@ -150,26 +107,51 @@ type /// Выполняет Fit и Transform последовательно function FitTransform(df: DataFrame): DataFrame; end; - -type -/// DataPipeline (конвейер) шагов подготовки данных. -/// Выполняет шаги последовательно с семантикой Fit / Transform. - DataPipeline = class +{/// Приводит числовые столбцы к нулевому среднему и единичному стандартному отклонению. +/// При Fit вычисляет среднее значение и стандартное отклонение столбцов. +/// Применяет преобразование: x' = (x - mean) / std. +/// Пропущенные значения (NA) сохраняются. + DataStandardScaler = class(IPreprocessor) private - steps: List; + cols: array of string; + means: array of real; + stds: array of real; fitted: boolean; public - constructor; - /// Добавляет шаг в конец pipeline - function Add(p: IPreprocessor): DataPipeline; - /// Обучает все шаги pipeline на DataFrame - function Fit(df: DataFrame): DataPipeline; - /// Применяет обученный pipeline к DataFrame + /// Создаёт StandardScaler для указанных числовых столбцов. + constructor Create(params columns: array of string); + /// Вычисляет среднее значение и стандартное отклонение для каждого столбца. + function Fit(df: DataFrame): IPreprocessor; + /// Возвращает DataFrame со стандартизованными числовыми столбцами. function Transform(df: DataFrame): DataFrame; - /// Выполняет Fit и Transform последовательно + /// Последовательно выполняет Fit и Transform. function FitTransform(df: DataFrame): DataFrame; end; + +/// Приводит числовые столбцы к заданному диапазону значений +/// При Fit вычисляет минимальное и максимальное значения столбцов +/// Применяет преобразование: x' = (x - min) / (max - min) +/// Пропущенные значения (NA) сохраняются + DataMinMaxScaler = class(IPreprocessor) + private + cols: array of string; + mins: array of real; + maxs: array of real; + fitted: boolean; + public + /// Создаёт MinMaxScaler для указанных столбцов + constructor Create(params columns: array of string); + + /// Вычисляет минимальные и максимальные значения столбцов + function Fit(df: DataFrame): IPreprocessor; + /// Применяет масштабирование к DataFrame + /// Возвращает новый DataFrame + function Transform(df: DataFrame): DataFrame; + /// Последовательно выполняет Fit и Transform. + function FitTransform(df: DataFrame): DataFrame; + end;} + implementation @@ -233,14 +215,12 @@ const ER_IMPUTER_CONSTANT_TYPE_MISMATCH = 'Imputer(constant): несоответствие типа значения для столбца "{0}"!!' + 'Imputer(constant): value type mismatch for column "{0}"'; - ER_PIPELINE_MODIFY_AFTER_FIT = - 'Нельзя добавлять шаг после вызова Fit()!!Cannot add step after Fit'; - + //----------------------------- // StandardScaler //----------------------------- -constructor DataStandardScaler.Create(params columns: array of string); +{constructor DataStandardScaler.Create(params columns: array of string); begin if (columns = nil) or (columns.Length = 0) then ArgumentError(ER_SCALER_NO_COLUMNS); @@ -420,7 +400,7 @@ function DataMinMaxScaler.FitTransform(df: DataFrame): DataFrame; begin Fit(df); Result := Transform(df); -end; +end;} //----------------------------- // LabelEncoder @@ -733,56 +713,5 @@ begin Result := Transform(df); end; -//----------------------------- -// Pipeline -//----------------------------- - -constructor DataPipeline.Create; -begin - steps := new List; - fitted := false; -end; - -function DataPipeline.Add(p: IPreprocessor): DataPipeline; -begin - if fitted then - Error(ER_PIPELINE_MODIFY_AFTER_FIT); - - steps.Add(p); - Result := Self; -end; - -function DataPipeline.Fit(df: DataFrame): DataPipeline; -begin - var current := df; - - for var i := 0 to steps.Count - 1 do - begin - steps[i] := steps[i].Fit(current); - current := steps[i].Transform(current); - end; - - fitted := true; - Result := Self; -end; - -function DataPipeline.Transform(df: DataFrame): DataFrame; -begin - if not fitted then - NotFittedError(ER_FIT_NOT_CALLED); - - var current := df; - foreach var step in steps do - current := step.Transform(current); - - Result := current; -end; - -function DataPipeline.FitTransform(df: DataFrame): DataFrame; -begin - Fit(df); - Result := Transform(df); -end; - end. \ No newline at end of file