MLDatasets.pas
PlotML - новые стандартные ML-модули
This commit is contained in:
parent
d2fd6c21d6
commit
b5730957f8
|
|
@ -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 = "3765";
|
||||
public const string Revision = "3767";
|
||||
|
||||
public const string MainVersion = Major + "." + Minor;
|
||||
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
%MINOR%=11
|
||||
%REVISION%=3765
|
||||
%REVISION%=3767
|
||||
%COREVERSION%=1
|
||||
%MAJOR%=3
|
||||
|
|
|
|||
|
|
@ -2,27 +2,27 @@
|
|||
uses PlotWPF;
|
||||
|
||||
begin
|
||||
var g := new GridWPF(2,2,10);
|
||||
|
||||
var c := new LineGraphWPF(0,Pi,v -> v*Sin(v*10));
|
||||
c.PlotRect := Rect(0,0,10,10);
|
||||
c.Graph[0].ChangeData(0,Pi,x->x*x);
|
||||
var g := new GridWPF(2, 2, 10);
|
||||
|
||||
var c := new LineGraphWPF(0, Pi, v -> v * Sin(v * 10));
|
||||
c.PlotRect := Rect(0, 0, 10, 10);
|
||||
c.Graph[0].ChangeData(0, Pi, x -> x * x);
|
||||
c.Graph[0].Color := Colors.Green;
|
||||
c.Graph[0].Thickness := 3;
|
||||
c.AddLineGraph(0,Pi,v -> Sqrt(v));
|
||||
c.AddLineGraph(0, Pi, v -> Sqrt(v));
|
||||
|
||||
var c2 := new LineGraphWPF(0,Pi,v -> Sin(v*10)-Cos(v*7));
|
||||
var c2 := new LineGraphWPF(0, Pi, v -> Sin(v * 10) - Cos(v * 7));
|
||||
|
||||
var c4 := new MarkerGraphWPF(|1.0,2,3,4,5|,|5.0,15,7,12,2|);
|
||||
c4.AddLineGraph(|1.0,2,3,4,5|,|5.0+1,15+1,7+1,12+1,2+1|);
|
||||
c4.AddMarkerGraph(|1.0,2,3,4,5|,|5.0+1,15+1,7+1,12+1,2+1|,Colors.Bisque,MarkerType.Diamond,8);
|
||||
var c4 := new MarkerGraphWPF([1.0, 2, 3, 4, 5], [5.0, 15, 7, 12, 2]);
|
||||
c4.AddLineGraph([1.0, 2, 3, 4, 5], [5.0 + 1, 15 + 1, 7 + 1, 12 + 1, 2 + 1]);
|
||||
c4.AddMarkerGraph([1.0, 2, 3, 4, 5], [5.0 + 1, 15 + 1, 7 + 1, 12 + 1, 2 + 1], Colors.Bisque, MarkerType.Diamond, 8);
|
||||
c4.Graph[0].Thickness := 0.7;
|
||||
c4.Graph[1].MarkerType := MarkerType.Box;
|
||||
c4.Graph[2].Thickness := 0.7;
|
||||
|
||||
var gg := new GridWPF(2,2,3);
|
||||
new LineGraphWPF(0,2,x->Cos(10*x));
|
||||
new LineGraphWPF(0,2,x->Sqrt(x));
|
||||
new LineGraphWPF(0,2,x->Sin(10*x));
|
||||
new LineGraphWPF(0,2,x->exp(x));
|
||||
|
||||
var gg := new GridWPF(2, 2, 3);
|
||||
new LineGraphWPF(0, 2, x -> Cos(10 * x));
|
||||
new LineGraphWPF(0, 2, x -> Sqrt(x));
|
||||
new LineGraphWPF(0, 2, x -> Sin(10 * x));
|
||||
new LineGraphWPF(0, 2, x -> exp(x));
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3.11.1.3765
|
||||
3.11.1.3767
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
!define VERSION '3.11.1.3765'
|
||||
!define VERSION '3.11.1.3767'
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ uses
|
|||
TasksArr, TasksMatr, TasksStr, Tasks1Begin, Tasks1BoolIfCase, Tasks1Loops, Tasks1Arr,
|
||||
WPF,
|
||||
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC,
|
||||
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC
|
||||
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC,
|
||||
PlotML, MLDatasets
|
||||
;
|
||||
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
ABCDatabases,
|
||||
School, SF, TurtleABC,
|
||||
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC,
|
||||
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC
|
||||
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML, MLExceptions, InspectionML, MLPipelineABC,
|
||||
PlotML, MLDatasets
|
||||
;
|
||||
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -191,6 +191,8 @@
|
|||
File ..\bin\Lib\MLExceptions.pcu
|
||||
File ..\bin\Lib\InspectionML.pcu
|
||||
File ..\bin\Lib\MLPipelineABC.pcu
|
||||
File ..\bin\Lib\PlotML.pcu
|
||||
File ..\bin\Lib\MLDatasets.pcu
|
||||
|
||||
File ..\bin\Lib\PABCRtl.dll
|
||||
File ..\bin\Lib\HelixToolkit.Wpf.dll
|
||||
|
|
@ -296,6 +298,8 @@
|
|||
${AddFile} "MLExceptions.pcu"
|
||||
${AddFile} "InspectionML.pcu"
|
||||
${AddFile} "MLPipelineABC.pcu"
|
||||
${AddFile} "PlotML.pcu"
|
||||
${AddFile} "MLDatasets.pcu"
|
||||
|
||||
|
||||
${AddFile} "turtle.png"
|
||||
|
|
@ -438,6 +442,9 @@
|
|||
File ..\bin\Lib\MLExceptions.pas
|
||||
File ..\bin\Lib\InspectionML.pas
|
||||
File ..\bin\Lib\MLPipelineABC.pas
|
||||
File ..\bin\Lib\PlotML.pas
|
||||
File ..\bin\Lib\MLDatasets.pas
|
||||
|
||||
|
||||
File ..\bin\Lib\__RedirectIOMode.vb
|
||||
File ..\bin\Lib\VBSystem.vb
|
||||
|
|
@ -530,7 +537,8 @@
|
|||
${AddFile} "MLExceptions.pas"
|
||||
${AddFile} "InspectionML.pas"
|
||||
${AddFile} "MLPipelineABC.pas"
|
||||
|
||||
${AddFile} "PlotML.pas"
|
||||
${AddFile} "MLDatasets.pas"
|
||||
|
||||
${AddFile} "__RedirectIOMode.vb"
|
||||
${AddFile} "VBSystem.vb"
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ 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\MLPipelineABC.pcu Release\PascalABCNETLinux\Lib\MLDatasets.pcu
|
||||
copy bin\Lib\MLPipelineABC.pcu Release\PascalABCNETLinux\Lib\PlotML.pcu
|
||||
|
||||
|
||||
copy bin\Lib\ABCDatabases.pas Release\PascalABCNETLinux\LibSource\ABCDatabases.pas
|
||||
copy bin\Lib\BBCMicrobit.pas Release\PascalABCNETLinux\LibSource\BBCMicrobit.pas
|
||||
|
|
@ -175,6 +178,8 @@ copy bin\Lib\ValidationML.pas Release\PascalABCNETLinux\LibSource\ValidationML.p
|
|||
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\Lib\MLPipelineABC.pas Release\PascalABCNETLinux\LibSource\MLDatasets.pas
|
||||
copy bin\Lib\MLPipelineABC.pas Release\PascalABCNETLinux\LibSource\PlotML.pas
|
||||
|
||||
|
||||
copy bin\Lng\Eng\.LanguageName Release\PascalABCNETLinux\Lng\Eng\.LanguageName
|
||||
|
|
|
|||
|
|
@ -937,7 +937,7 @@ begin
|
|||
var cols := new Column[schema.ColumnCount];
|
||||
|
||||
for var i := 0 to schema.ColumnCount - 1 do
|
||||
cols[i] := columns[fSchema.IndexOf(schema.Names[i])];
|
||||
cols[i] := columns[fSchema.IndexOf(schema.ColumnNames[i])];
|
||||
|
||||
Result := new DataFrame(cols.ToList, schema);
|
||||
end;
|
||||
|
|
@ -1649,7 +1649,7 @@ function DataFrame.Mean(colName: string): real
|
|||
function DataFrame.Median(colIndex: integer): real;
|
||||
begin
|
||||
CheckColumnIndex(colIndex);
|
||||
Result := Statistics.Median(Self, fSchema.Names[colIndex]);
|
||||
Result := Statistics.Median(Self, fSchema.ColumnNames[colIndex]);
|
||||
end;
|
||||
|
||||
function DataFrame.Median(colName: string): real;
|
||||
|
|
@ -2454,13 +2454,13 @@ begin
|
|||
begin
|
||||
case schema.Types[i] of
|
||||
ctInt:
|
||||
cols.Add(new IntColumn(schema.Names[i], schema.IsCategorical[i]));
|
||||
cols.Add(new IntColumn(schema.ColumnNames[i], schema.IsCategorical[i]));
|
||||
ctFloat:
|
||||
cols.Add(new FloatColumn(schema.Names[i]));
|
||||
cols.Add(new FloatColumn(schema.ColumnNames[i]));
|
||||
ctStr:
|
||||
cols.Add(new StrColumn(schema.Names[i], schema.IsCategorical[i]));
|
||||
cols.Add(new StrColumn(schema.ColumnNames[i], schema.IsCategorical[i]));
|
||||
ctBool:
|
||||
cols.Add(new BoolColumn(schema.Names[i]));
|
||||
cols.Add(new BoolColumn(schema.ColumnNames[i]));
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -3046,12 +3046,12 @@ end;
|
|||
|
||||
procedure DataFrame.PrintSchema;
|
||||
begin
|
||||
var nameWidth := fSchema.Names.Max(s -> s.Length);
|
||||
var nameWidth := fSchema.ColumnNames.Max(s -> s.Length);
|
||||
var typeWidth := 6; // Int / Float / Bool
|
||||
|
||||
for var i := 0 to ColumnCount - 1 do
|
||||
begin
|
||||
var name := fSchema.Names[i].PadRight(nameWidth);
|
||||
var name := fSchema.ColumnNames[i].PadRight(nameWidth);
|
||||
var typ := GetColumnType(i).ToString.Replace('ct','').PadRight(typeWidth);
|
||||
PABCSystem.Println($'{name} : {typ}');
|
||||
end;
|
||||
|
|
@ -3062,7 +3062,7 @@ begin
|
|||
PABCSystem.Println($'Rows : {RowCount}');
|
||||
PABCSystem.Println($'Columns : {ColumnCount}');
|
||||
|
||||
var nameWidth := fSchema.Names.Max(s -> s.Length);
|
||||
var nameWidth := fSchema.ColumnNames.Max(s -> s.Length);
|
||||
var typeWidth := 6; // Int / Float / Bool
|
||||
var infoWidth := nameWidth + 3 + typeWidth + 12;
|
||||
|
||||
|
|
@ -3070,7 +3070,7 @@ begin
|
|||
|
||||
for var i := 0 to ColumnCount - 1 do
|
||||
begin
|
||||
var name := fSchema.Names[i].PadRight(nameWidth);
|
||||
var name := fSchema.ColumnNames[i].PadRight(nameWidth);
|
||||
var typ := GetColumnType(i).ToString.Replace('ct','').PadRight(typeWidth);
|
||||
var cnt := Count(i);
|
||||
PABCSystem.Println($'{name} : {typ} ({cnt} non-NA)');
|
||||
|
|
@ -3695,7 +3695,7 @@ begin
|
|||
// выбираем только числовые столбцы
|
||||
for var i := 0 to df.ColumnCount - 1 do
|
||||
if df.GetColumnType(i) in [ColumnType.ctInt, ColumnType.ctFloat] then
|
||||
names.Add(df.fSchema.Names[i]);
|
||||
names.Add(df.fSchema.ColumnNames[i]);
|
||||
|
||||
var n := names.Count;
|
||||
if n = 0 then
|
||||
|
|
@ -3758,7 +3758,7 @@ begin
|
|||
means[i] := df.Mean(i);
|
||||
stds[i] := df.Std(i);
|
||||
if stds[i] = 0 then
|
||||
Error(ER_ZERO_STD_COLUMN, df.fSchema.Names[i]);
|
||||
Error(ER_ZERO_STD_COLUMN, df.fSchema.ColumnNames[i]);
|
||||
isNumeric[i] := true;
|
||||
end;
|
||||
end;
|
||||
|
|
@ -3767,7 +3767,7 @@ begin
|
|||
for var i := 0 to df.ColumnCount - 1 do
|
||||
begin
|
||||
if isNumeric[i] then
|
||||
res.AddFloatColumn(df.fSchema.Names[i], new real[df.RowCount], nil)
|
||||
res.AddFloatColumn(df.fSchema.ColumnNames[i], new real[df.RowCount], nil)
|
||||
else
|
||||
res.AddColumnView(df.columns[i]); // private helper
|
||||
end;
|
||||
|
|
@ -3839,7 +3839,7 @@ begin
|
|||
begin
|
||||
var (mn, mx) := df.MinMax(i);
|
||||
if mn = mx then
|
||||
Error(ER_ZERO_RANGE_COLUMN, df.fSchema.Names[i]);
|
||||
Error(ER_ZERO_RANGE_COLUMN, df.fSchema.ColumnNames[i]);
|
||||
mins[i] := mn;
|
||||
maxs[i] := mx;
|
||||
isNumeric[i] := true;
|
||||
|
|
@ -3850,7 +3850,7 @@ begin
|
|||
for var i := 0 to df.ColumnCount - 1 do
|
||||
begin
|
||||
if isNumeric[i] then
|
||||
res.AddFloatColumn(df.fSchema.Names[i], new real[df.RowCount], nil)
|
||||
res.AddFloatColumn(df.fSchema.ColumnNames[i], new real[df.RowCount], nil)
|
||||
else
|
||||
res.AddColumnView(df.columns[i]); // private helper
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ type
|
|||
class function BuildIndex(names: array of string): Dictionary<string, integer>;
|
||||
public
|
||||
property ColumnCount: integer read fNames.Length;
|
||||
property Names: array of string read fNames;
|
||||
property ColumnNames: array of string read fNames;
|
||||
property Types: array of ColumnType read fTypes;
|
||||
property IsCategorical: array of boolean read fIsCategorical;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ type
|
|||
|
||||
function Clone: Vector;
|
||||
|
||||
function ToArray: array of real;
|
||||
|
||||
static function operator +(a, b: Vector): Vector;
|
||||
static function operator -(a, b: Vector): Vector;
|
||||
|
||||
|
|
@ -82,6 +84,9 @@ type
|
|||
constructor Create(r, c: integer);
|
||||
constructor Create(values: array[,] of real);
|
||||
|
||||
function ToArray2D: array[,] of real;
|
||||
function RowToArray(r: integer): array of real;
|
||||
|
||||
function Clone: Matrix;
|
||||
|
||||
function ColumnSums: Vector;
|
||||
|
|
@ -320,6 +325,11 @@ begin
|
|||
data := values.Select(x -> real(x)).ToArray;
|
||||
end;
|
||||
|
||||
function Vector.ToArray: array of real;
|
||||
begin
|
||||
Result := Copy(data);
|
||||
end;
|
||||
|
||||
function Vector.Clone: Vector;
|
||||
begin
|
||||
Result := new Vector(data);
|
||||
|
|
@ -492,6 +502,16 @@ begin
|
|||
data := Copy(values);
|
||||
end;
|
||||
|
||||
function Matrix.ToArray2D: array[,] of real;
|
||||
begin
|
||||
Result := Copy(data);
|
||||
end;
|
||||
|
||||
function Matrix.RowToArray(r: integer): array of real;
|
||||
begin
|
||||
Result := data.Row(r);
|
||||
end;
|
||||
|
||||
function Matrix.Clone: Matrix;
|
||||
begin
|
||||
Result := new Matrix(data);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ type
|
|||
GradientBoostingClassifier = MLModelsABC.GradientBoostingClassifier;
|
||||
KNNClassifier = MLModelsABC.KNNClassifier;
|
||||
KNNRegressor = MLModelsABC.KNNRegressor;
|
||||
KMeans = MLModelsABC.KMeans;
|
||||
DBSCAN = MLModelsABC.DBSCAN;
|
||||
|
||||
KNNWeighting = MLModelsABC.KNNWeighting;
|
||||
TGBLoss = MLModelsABC.TGBLoss;
|
||||
|
|
@ -68,6 +70,12 @@ type
|
|||
|
||||
Inspection = InspectionML.Inspection;
|
||||
|
||||
IPreprocessor = PreprocessorABC.IPreprocessor;
|
||||
LabelEncoder = PreprocessorABC.LabelEncoder;
|
||||
OneHotEncoder = PreprocessorABC.OneHotEncoder;
|
||||
ImputeStrategy = PreprocessorABC.ImputeStrategy;
|
||||
Imputer = PreprocessorABC.Imputer;
|
||||
|
||||
implementation
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,22 @@ type
|
|||
function Clone: IModel;
|
||||
end;
|
||||
|
||||
/// Интерфейс модели без учителя (Unsupervised Model).
|
||||
/// Наследуется от базового интерфейса IModel.
|
||||
/// Предназначен для алгоритмов, обучающихся только по признакам X
|
||||
/// без использования целевых значений (y)
|
||||
IUnsupervisedModel = interface(IModel)
|
||||
procedure Fit(X: Matrix);
|
||||
end;
|
||||
|
||||
/// Интерфейс алгоритма кластеризации.
|
||||
/// Наследуется от IUnsupervisedModel.
|
||||
/// Предназначен для моделей, разбивающих объекты на кластеры
|
||||
/// и возвращающих индекс кластера для каждого объекта
|
||||
IClusterer = interface(IUnsupervisedModel)
|
||||
function Predict(X: Matrix): Vector;
|
||||
end;
|
||||
|
||||
/// Интерфейс древовидной модели машинного обучения
|
||||
ITreeModel = interface(IModel)
|
||||
function FeatureImportances: Vector;
|
||||
|
|
@ -35,8 +51,7 @@ type
|
|||
|
||||
/// Интерфейс классификатора.
|
||||
/// Наследуется от IModel.
|
||||
/// Предназначен для моделей, выполняющих классификацию
|
||||
/// (предсказание меток классов).
|
||||
/// Предназначен для моделей, выполняющих классификацию (предсказание меток классов).
|
||||
IClassifier = interface(IModel)
|
||||
end;
|
||||
|
||||
|
|
|
|||
138
bin/Lib/MLDatasets.pas
Normal file
138
bin/Lib/MLDatasets.pas
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
unit MLDatasets;
|
||||
|
||||
interface
|
||||
|
||||
uses DataFrameABC, LinearAlgebraML;
|
||||
|
||||
type
|
||||
Datasets = static class
|
||||
public
|
||||
// --- synthetic datasets (Matrix + Vector)
|
||||
|
||||
/// Генерирует k кластеров (blobs) для задач классификации и кластеризации
|
||||
static function MakeBlobs(
|
||||
n: integer := 300;
|
||||
centers: integer := 3;
|
||||
seed: integer := 1): (Matrix, Vector);
|
||||
|
||||
/// Генерирует датасет "две луны" для демонстрации нелинейной классификации и кластеризации
|
||||
static function MakeMoons(
|
||||
n: integer := 300;
|
||||
noise: real := 0.05;
|
||||
seed: integer := 1): (Matrix, Vector);
|
||||
|
||||
/// Генерирует синтетический датасет для задач регрессии
|
||||
static function MakeRegression(
|
||||
n: integer := 300;
|
||||
noise: real := 0.1;
|
||||
seed: integer := 1): (Matrix, Vector);
|
||||
|
||||
/// Генерирует концентрические круги (сложная нелинейная классификация)
|
||||
static function MakeCircles(
|
||||
n: integer := 300;
|
||||
noise: real := 0.05;
|
||||
seed: integer := 1): (Matrix, Vector);
|
||||
|
||||
/// Генерирует спиральный датасет (очень сложная нелинейная классификация)
|
||||
static function MakeSpiral(
|
||||
n: integer := 300;
|
||||
classes: integer := 2;
|
||||
seed: integer := 1): (Matrix, Vector);
|
||||
|
||||
|
||||
// --- DataFrame datasets (реалистичные таблицы)
|
||||
|
||||
/// Датасет цен на квартиры (задача регрессии)
|
||||
static function RussianHousing: DataFrame;
|
||||
|
||||
/// Датасет результатов экзамена студентов (классификация)
|
||||
static function StudentExam: DataFrame;
|
||||
|
||||
/// Датасет банковских клиентов (классификация одобрения кредита)
|
||||
static function BankClients: DataFrame;
|
||||
|
||||
/// Датасет поездок такси (регрессия стоимости поездки)
|
||||
static function TaxiTrips: DataFrame;
|
||||
|
||||
/// Датасет транспортной активности пассажиров (кластеризация)
|
||||
static function MoscowTransport: DataFrame;
|
||||
|
||||
/// Датасет интернет-покупок пользователей (классификация покупки)
|
||||
static function OnlineShopping: DataFrame;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
static function Datasets.MakeBlobs(
|
||||
n: integer ;
|
||||
centers: integer ;
|
||||
seed: integer): (Matrix, Vector);
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.MakeMoons(
|
||||
n: integer ;
|
||||
noise: real ;
|
||||
seed: integer): (Matrix, Vector);
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.MakeRegression(
|
||||
n: integer ;
|
||||
noise: real ;
|
||||
seed: integer): (Matrix, Vector);
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.MakeCircles(
|
||||
n: integer ;
|
||||
noise: real ;
|
||||
seed: integer): (Matrix, Vector);
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.MakeSpiral(
|
||||
n: integer ;
|
||||
classes: integer ;
|
||||
seed: integer): (Matrix, Vector);
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.RussianHousing: DataFrame;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.StudentExam: DataFrame;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.BankClients: DataFrame;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.TaxiTrips: DataFrame;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.MoscowTransport: DataFrame;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
static function Datasets.OnlineShopping: DataFrame;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
|
@ -213,6 +213,9 @@ type
|
|||
fTol: real := 1e-6;
|
||||
fCheckConvergence: boolean := true;
|
||||
fMinImprovement: real := 1e-8;
|
||||
|
||||
function GetWeights: Matrix;
|
||||
function GetIntercept: Vector;
|
||||
public
|
||||
/// lambda — коэффициент L2-регуляризации.
|
||||
/// lr — шаг градиентного спуска.
|
||||
|
|
@ -244,6 +247,12 @@ type
|
|||
|
||||
/// Создает глубокую копию модели.
|
||||
function Clone: IModel;
|
||||
|
||||
/// Матрица коэффициентов модели (p × k).
|
||||
property Weights: Matrix read GetWeights;
|
||||
|
||||
/// Вектор свободных членов (bias) для каждого класса.
|
||||
property Intercept: Vector read GetIntercept;
|
||||
end;
|
||||
|
||||
DecisionTreeNode = class
|
||||
|
|
@ -1099,6 +1108,7 @@ type
|
|||
function Clone: IModel; override;
|
||||
end;
|
||||
|
||||
|
||||
/// Регрессор на основе алгоритма k ближайших соседей (kNN).
|
||||
/// Поддерживает равномерное (Uniform) усреднение и взвешенное по расстоянию (Distance) усреднение.
|
||||
/// Предсказание вычисляется как среднее (или взвешенное среднее) значений целевой переменной
|
||||
|
|
@ -1125,6 +1135,125 @@ type
|
|||
function Clone: IModel; override;
|
||||
end;
|
||||
|
||||
|
||||
/// Модель кластеризации методом k-средних (KMeans).
|
||||
/// Разбивает объекты на k кластеров на основе евклидова расстояния.
|
||||
/// Реализует алгоритм без учителя
|
||||
KMeans = class(IClusterer)
|
||||
private
|
||||
fNClusters: integer;
|
||||
fMaxIter: integer;
|
||||
fTol: real;
|
||||
fNInit: integer;
|
||||
fSeed: integer;
|
||||
|
||||
fIsFitted: boolean;
|
||||
fFeatureCount: integer;
|
||||
|
||||
fCenters: Matrix;
|
||||
fInertia: real;
|
||||
fIterations: integer;
|
||||
fHasConverged: boolean;
|
||||
|
||||
function RunSingle(X: Matrix; rnd: System.Random): (Matrix, real, integer, boolean);
|
||||
public
|
||||
/// Создаёт модель KMeans.
|
||||
/// nClusters — количество кластеров.
|
||||
/// maxIter — максимальное число итераций.
|
||||
/// tol — порог сходимости.
|
||||
/// nInit — количество независимых запусков.
|
||||
/// seed — значение генератора случайных чисел
|
||||
constructor Create(
|
||||
nClusters: integer;
|
||||
maxIter: integer := 300;
|
||||
tol: real := 1e-4;
|
||||
nInit: integer := 10;
|
||||
seed: integer := -1
|
||||
);
|
||||
|
||||
/// Обучает модель по матрице признаков X.
|
||||
/// Выполняет nInit запусков и выбирает решение с минимальной инерцией.
|
||||
procedure Fit(X: Matrix);
|
||||
|
||||
function Fit(X: Matrix; y: Vector): IModel;
|
||||
|
||||
/// Возвращает индекс кластера для каждого объекта из X.
|
||||
/// Требует предварительного вызова Fit.
|
||||
function Predict(X: Matrix): Vector;
|
||||
|
||||
/// Выполняет обучение и сразу возвращает метки кластеров.
|
||||
function FitPredict(X: Matrix): Vector;
|
||||
|
||||
/// Создаёт глубокую копию модели.
|
||||
function Clone: IModel;
|
||||
|
||||
/// Количество кластеров.
|
||||
property NClusters: integer read fNClusters;
|
||||
/// Максимальное число итераций.
|
||||
property MaxIter: integer read fMaxIter;
|
||||
/// Порог сходимости.
|
||||
property Tol: real read fTol;
|
||||
/// Количество запусков алгоритма.
|
||||
property NInit: integer read fNInit;
|
||||
/// Используемый seed.
|
||||
property Seed: integer read fSeed;
|
||||
/// Матрица центроидов (k × p).
|
||||
property ClusterCenters: Matrix read fCenters;
|
||||
/// Значение инерции (сумма квадратов расстояний до центроидов).
|
||||
property Inertia: real read fInertia;
|
||||
/// Число выполненных итераций.
|
||||
property Iterations: integer read fIterations;
|
||||
/// Признак сходимости алгоритма.
|
||||
property HasConverged: boolean read fHasConverged;
|
||||
/// Число признаков модели.
|
||||
property FeatureCount: integer read fFeatureCount;
|
||||
end;
|
||||
|
||||
|
||||
DBSCAN = class(IClusterer)
|
||||
private
|
||||
fEps: real;
|
||||
fMinSamples: integer;
|
||||
fSeed: integer;
|
||||
|
||||
fIsFitted: boolean;
|
||||
fFeatureCount: integer;
|
||||
|
||||
fLabels: Vector;
|
||||
fClusterCount: integer;
|
||||
|
||||
function RegionQuery(X: Matrix; i: integer; neighbors: List<integer>): integer;
|
||||
|
||||
public
|
||||
/// Создаёт модель DBSCAN.
|
||||
/// eps — радиус соседства.
|
||||
/// minSamples — минимальное число точек в eps-окрестности.
|
||||
/// seed — параметр для совместимости API.
|
||||
constructor Create(
|
||||
eps: real;
|
||||
minSamples: integer := 5;
|
||||
seed: integer := -1
|
||||
);
|
||||
|
||||
/// Обучает модель на матрице признаков.
|
||||
procedure Fit(X: Matrix);
|
||||
|
||||
/// Совместимость с интерфейсом IModel.
|
||||
function Fit(X: Matrix; y: Vector): IModel;
|
||||
|
||||
/// Возвращает метки кластеров.
|
||||
function Predict(X: Matrix): Vector;
|
||||
|
||||
/// Возвращает метки после обучения.
|
||||
function FitPredict(X: Matrix): Vector;
|
||||
|
||||
/// Копия модели.
|
||||
function Clone: IModel;
|
||||
|
||||
property Labels: Vector read fLabels;
|
||||
property ClusterCount: integer read fClusterCount;
|
||||
end;
|
||||
|
||||
{$endregion Models}
|
||||
|
||||
{$region Pipeline}
|
||||
|
|
@ -1153,6 +1282,63 @@ type
|
|||
/// Создаёт пустой пайплайн (конвейер машинного обучения).
|
||||
/// Модель должна быть установлена через SetModel.
|
||||
constructor Create;
|
||||
|
||||
{
|
||||
// Pipeline.Build используется, когда данные уже представлены
|
||||
// в виде числовой матрицы признаков X и вектора целевой переменной y.
|
||||
// В этом случае DataFrame и препроцессоры уровня таблицы не требуются.
|
||||
//
|
||||
// Типичные ситуации:
|
||||
// • экспериментирование с ML-алгоритмами
|
||||
// • сравнение моделей
|
||||
// • кросс-валидация
|
||||
// • подбор гиперпараметров
|
||||
// • тестирование моделей
|
||||
//
|
||||
// Pipeline объединяет несколько матричных преобразований (ITransformer)
|
||||
// и модель (IModel) в единый объект, который можно обучать и использовать
|
||||
// для предсказаний.
|
||||
//
|
||||
// В отличие от этого, DataPipeline.Build используется,
|
||||
// когда исходные данные представлены в виде DataFrame
|
||||
// и требуется выполнить препроцессинг таблицы
|
||||
// (Imputer, OneHotEncoder, LabelEncoder и др.)
|
||||
// перед преобразованием данных в Matrix/Vector.
|
||||
|
||||
var pipe1 :=
|
||||
Pipeline.Build(
|
||||
new StandardScaler,
|
||||
new LogisticRegression
|
||||
);
|
||||
|
||||
var pipe2 :=
|
||||
Pipeline.Build(
|
||||
new StandardScaler,
|
||||
new PCATransformer(2),
|
||||
new LogisticRegression
|
||||
);
|
||||
|
||||
var score1 :=
|
||||
Validation.CrossValidate(
|
||||
pipe1,
|
||||
X,
|
||||
y,
|
||||
5,
|
||||
Metrics.Accuracy
|
||||
);
|
||||
|
||||
var score2 :=
|
||||
Validation.CrossValidate(
|
||||
pipe2,
|
||||
X,
|
||||
y,
|
||||
5,
|
||||
Metrics.Accuracy
|
||||
);
|
||||
|
||||
Println('Scaler + LogisticRegression = ', score1);
|
||||
Println('Scaler + PCA + LogisticRegression = ', score2);
|
||||
}
|
||||
|
||||
/// Строит конвейер машинного обучения из последовательности шагов.
|
||||
/// Шаги указываются в порядке выполнения:
|
||||
|
|
@ -1193,6 +1379,7 @@ type
|
|||
{$endregion Pipeline}
|
||||
|
||||
{$region Transformers}
|
||||
|
||||
/// Стандартизирует признаки: вычитает среднее
|
||||
/// и делит на стандартное отклонение по каждому столбцу.
|
||||
/// Используется для приведения признаков к сопоставимому масштабу
|
||||
|
|
@ -1632,8 +1819,16 @@ const
|
|||
'minSamplesLeaf ({0}) must be less than minSamplesSplit ({1}).';
|
||||
ER_OOB_NOT_ENABLED =
|
||||
'OOB score не включен для этой модели. Установите computeOOB = true в конструкторе.!!' +
|
||||
'OOB score is not enabled for this model. Set computeOOB = true in the constructor.';
|
||||
|
||||
'OOB score is not enabled for this model. Set computeOOB = true in the constructor.';
|
||||
ER_NINIT_INVALID =
|
||||
'Некорректное значение nInit: {0}. Должно быть не меньше 1!!Invalid nInit value: {0}. Must be at least 1';
|
||||
ER_INVALID_VALUE_AT =
|
||||
'Некорректное значение в {0} на позиции {1}!!Invalid value in {0} at index {1}';
|
||||
ER_EPS_INVALID =
|
||||
'eps должен быть положительным!!eps must be positive';
|
||||
ER_MINSAMPLES_INVALID =
|
||||
'minSamples должен быть >= 1!!minSamples must be >= 1';
|
||||
|
||||
{$endregion ErrConstants}
|
||||
|
||||
//-----------------------------
|
||||
|
|
@ -2427,6 +2622,22 @@ begin
|
|||
Result := m;
|
||||
end;
|
||||
|
||||
function LogisticRegression.GetWeights: Matrix;
|
||||
begin
|
||||
if not fFitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
Result := fW;
|
||||
end;
|
||||
|
||||
function LogisticRegression.GetIntercept: Vector;
|
||||
begin
|
||||
if not fFitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
Result := fIntercept;
|
||||
end;
|
||||
|
||||
function GiniCriterion.Impurity(y: Vector; indices: array of integer): real;
|
||||
begin
|
||||
var n := indices.Length;
|
||||
|
|
@ -6435,6 +6646,478 @@ begin
|
|||
Result := clone;
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// KMeans
|
||||
//-----------------------------
|
||||
|
||||
constructor KMeans.Create(
|
||||
nClusters: integer;
|
||||
maxIter: integer;
|
||||
tol: real;
|
||||
nInit: integer;
|
||||
seed: integer
|
||||
);
|
||||
begin
|
||||
if nClusters < 1 then
|
||||
ArgumentOutOfRangeError(ER_K_INVALID, nClusters);
|
||||
|
||||
if maxIter < 1 then
|
||||
ArgumentOutOfRangeError(ER_MAXITER_INVALID, maxIter);
|
||||
|
||||
if tol <= 0 then
|
||||
ArgumentOutOfRangeError(ER_TOL_INVALID, tol);
|
||||
|
||||
if nInit < 1 then
|
||||
ArgumentOutOfRangeError(ER_NINIT_INVALID, nInit);
|
||||
|
||||
fNClusters := nClusters;
|
||||
fMaxIter := maxIter;
|
||||
fTol := tol;
|
||||
fNInit := nInit;
|
||||
fSeed := seed;
|
||||
|
||||
fIsFitted := False;
|
||||
fFeatureCount := 0;
|
||||
|
||||
fCenters := nil;
|
||||
fInertia := 0.0;
|
||||
fIterations := 0;
|
||||
fHasConverged := False;
|
||||
end;
|
||||
|
||||
function KMeans.RunSingle(X: Matrix; rnd: System.Random): (Matrix, real, integer, boolean);
|
||||
begin
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
var k := fNClusters;
|
||||
|
||||
// --- 1. Инициализация центроидов (случайные строки X)
|
||||
|
||||
var idx := Arr(0..n-1);
|
||||
idx.Shuffle(rnd);
|
||||
|
||||
var centers := new Matrix(k, p);
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
begin
|
||||
var r := idx[c];
|
||||
for var j := 0 to p - 1 do
|
||||
centers[c,j] := X[r,j];
|
||||
end;
|
||||
|
||||
var inertia := 0.0;
|
||||
var converged := False;
|
||||
var iterations := 0;
|
||||
|
||||
// --- 2. Основной цикл
|
||||
for var iter := 1 to fMaxIter do
|
||||
begin
|
||||
iterations := iter;
|
||||
|
||||
// Assignment + накопление сумм
|
||||
var counts := new integer[k];
|
||||
var sums := new Matrix(k, p);
|
||||
|
||||
inertia := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var bestC := 0;
|
||||
var bestDist := double.MaxValue;
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
begin
|
||||
var dist := 0.0;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var xij := X[i,j];
|
||||
if double.IsNaN(xij) or double.IsInfinity(xij) then
|
||||
ArgumentError(ER_INVALID_VALUE_AT, 'X', i);
|
||||
|
||||
var cj := centers[c,j];
|
||||
if double.IsNaN(cj) or double.IsInfinity(cj) then
|
||||
ArgumentError(ER_INVALID_VALUE_AT, 'Centers', c);
|
||||
|
||||
var d := xij - cj;
|
||||
dist += d * d;
|
||||
end;
|
||||
|
||||
if dist < bestDist then
|
||||
begin
|
||||
bestDist := dist;
|
||||
bestC := c;
|
||||
end;
|
||||
end;
|
||||
|
||||
inertia += bestDist;
|
||||
|
||||
counts[bestC] += 1;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
sums[bestC,j] += X[i,j];
|
||||
end;
|
||||
|
||||
// --- 3. Пересчёт центроидов
|
||||
var maxShift := 0.0;
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
begin
|
||||
if counts[c] = 0 then
|
||||
begin
|
||||
// Пустой кластер — переинициализация случайной точкой
|
||||
var r := rnd.Next(n);
|
||||
for var j := 0 to p - 1 do
|
||||
centers[c,j] := X[r,j];
|
||||
continue;
|
||||
end;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var newVal := sums[c,j] / counts[c];
|
||||
var shift := Abs(newVal - centers[c,j]);
|
||||
|
||||
if shift > maxShift then
|
||||
maxShift := shift;
|
||||
|
||||
centers[c,j] := newVal;
|
||||
end;
|
||||
end;
|
||||
|
||||
if maxShift < fTol then
|
||||
begin
|
||||
converged := True;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := (centers, inertia, iterations, converged);
|
||||
end;
|
||||
|
||||
procedure KMeans.Fit(X: Matrix);
|
||||
begin
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'KMeans');
|
||||
|
||||
if p = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'KMeans');
|
||||
|
||||
if fNClusters > n then
|
||||
ArgumentOutOfRangeError(ER_K_INVALID, fNClusters);
|
||||
|
||||
fFeatureCount := p;
|
||||
|
||||
var actualSeed :=
|
||||
if fSeed >= 0 then fSeed
|
||||
else System.Environment.TickCount and integer.MaxValue;
|
||||
|
||||
var rndBase := new System.Random(actualSeed);
|
||||
|
||||
var bestInertia := 1e308;
|
||||
var bestCenters: Matrix := nil;
|
||||
var bestIterations := 0;
|
||||
var bestConverged := False;
|
||||
|
||||
for var run := 1 to fNInit do
|
||||
begin
|
||||
var runSeed := rndBase.Next;
|
||||
var rnd := new System.Random(runSeed);
|
||||
|
||||
var (centers, inertia, iters, converged) := RunSingle(X, rnd);
|
||||
|
||||
if inertia < bestInertia then
|
||||
begin
|
||||
bestInertia := inertia;
|
||||
bestCenters := centers;
|
||||
bestIterations := iters;
|
||||
bestConverged := converged;
|
||||
end;
|
||||
end;
|
||||
|
||||
fCenters := bestCenters;
|
||||
fInertia := bestInertia;
|
||||
fIterations := bestIterations;
|
||||
fHasConverged := bestConverged;
|
||||
fIsFitted := True;
|
||||
end;
|
||||
|
||||
function KMeans.Fit(X: Matrix; y: Vector): IModel;
|
||||
begin
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if y <> nil then
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
Fit(X);
|
||||
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function KMeans.Predict(X: Matrix): Vector;
|
||||
begin
|
||||
if not fIsFitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if X.ColCount <> fFeatureCount then
|
||||
DimensionError(ER_DIM_MISMATCH, X.ColCount, fFeatureCount);
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
var k := fNClusters;
|
||||
|
||||
Result := new Vector(n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var bestC := 0;
|
||||
var bestDist := 1e308;
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
begin
|
||||
var dist := 0.0;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var xij := X[i,j];
|
||||
if double.IsNaN(xij) or double.IsInfinity(xij) then
|
||||
ArgumentError(ER_INVALID_VALUE_AT, 'X', i);
|
||||
|
||||
var cj := fCenters[c,j];
|
||||
if double.IsNaN(cj) or double.IsInfinity(cj) then
|
||||
ArgumentError(ER_INVALID_VALUE_AT, 'Centers', c);
|
||||
|
||||
var d := xij - cj;
|
||||
dist += d * d;
|
||||
end;
|
||||
|
||||
if dist < bestDist then
|
||||
begin
|
||||
bestDist := dist;
|
||||
bestC := c;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result[i] := bestC; // 0..k-1
|
||||
end;
|
||||
end;
|
||||
|
||||
function KMeans.FitPredict(X: Matrix): Vector;
|
||||
begin
|
||||
Fit(X);
|
||||
Result := Predict(X);
|
||||
end;
|
||||
|
||||
function KMeans.Clone: IModel;
|
||||
begin
|
||||
var km := new KMeans(
|
||||
fNClusters,
|
||||
fMaxIter,
|
||||
fTol,
|
||||
fNInit,
|
||||
fSeed
|
||||
);
|
||||
|
||||
km.fIsFitted := fIsFitted;
|
||||
km.fFeatureCount := fFeatureCount;
|
||||
km.fInertia := fInertia;
|
||||
km.fIterations := fIterations;
|
||||
km.fHasConverged := fHasConverged;
|
||||
|
||||
if fCenters <> nil then
|
||||
begin
|
||||
var k := fCenters.RowCount;
|
||||
var p := fCenters.ColCount;
|
||||
km.fCenters := new Matrix(k, p);
|
||||
|
||||
for var i := 0 to k - 1 do
|
||||
for var j := 0 to p - 1 do
|
||||
km.fCenters[i,j] := fCenters[i,j];
|
||||
end;
|
||||
|
||||
Result := km;
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// DBSCAN
|
||||
//-----------------------------
|
||||
|
||||
constructor DBSCAN.Create(
|
||||
eps: real;
|
||||
minSamples: integer;
|
||||
seed: integer
|
||||
);
|
||||
begin
|
||||
if eps <= 0 then
|
||||
ArgumentOutOfRangeError(ER_EPS_INVALID, eps);
|
||||
|
||||
if minSamples < 1 then
|
||||
ArgumentOutOfRangeError(ER_MINSAMPLES_INVALID, minSamples);
|
||||
|
||||
fEps := eps;
|
||||
fMinSamples := minSamples;
|
||||
fSeed := seed;
|
||||
|
||||
fIsFitted := False;
|
||||
end;
|
||||
|
||||
function DBSCAN.RegionQuery(X: Matrix; i: integer; neighbors: List<integer>): integer;
|
||||
begin
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
var eps2 := fEps * fEps;
|
||||
|
||||
neighbors.Clear;
|
||||
|
||||
for var j := 0 to n-1 do
|
||||
begin
|
||||
var dist := 0.0;
|
||||
|
||||
for var k := 0 to p-1 do
|
||||
begin
|
||||
var d := X[i,k] - X[j,k];
|
||||
dist += d*d;
|
||||
end;
|
||||
|
||||
if dist <= eps2 then
|
||||
neighbors.Add(j);
|
||||
end;
|
||||
|
||||
Result := neighbors.Count;
|
||||
end;
|
||||
|
||||
procedure DBSCAN.Fit(X: Matrix);
|
||||
begin
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'DBSCAN');
|
||||
|
||||
fFeatureCount := p;
|
||||
|
||||
var labels := new integer[n];
|
||||
|
||||
for var i := 0 to n-1 do
|
||||
labels[i] := -1; // noise
|
||||
|
||||
var visited := new boolean[n];
|
||||
|
||||
var neighbors := new List<integer>;
|
||||
var queue := new Queue<integer>;
|
||||
|
||||
var clusterId := 0;
|
||||
|
||||
for var i := 0 to n-1 do
|
||||
begin
|
||||
if visited[i] then
|
||||
continue;
|
||||
|
||||
visited[i] := True;
|
||||
|
||||
var count := RegionQuery(X, i, neighbors);
|
||||
|
||||
if count < fMinSamples then
|
||||
continue;
|
||||
|
||||
labels[i] := clusterId;
|
||||
|
||||
queue.Clear;
|
||||
|
||||
foreach var j in neighbors do
|
||||
queue.Enqueue(j);
|
||||
|
||||
while queue.Count > 0 do
|
||||
begin
|
||||
var q := queue.Dequeue;
|
||||
|
||||
if not visited[q] then
|
||||
begin
|
||||
visited[q] := True;
|
||||
|
||||
var count2 := RegionQuery(X, q, neighbors);
|
||||
|
||||
if count2 >= fMinSamples then
|
||||
foreach var t in neighbors do
|
||||
queue.Enqueue(t);
|
||||
end;
|
||||
|
||||
if labels[q] = -1 then
|
||||
labels[q] := clusterId;
|
||||
end;
|
||||
|
||||
clusterId += 1;
|
||||
end;
|
||||
|
||||
fClusterCount := clusterId;
|
||||
|
||||
fLabels := new Vector(n);
|
||||
|
||||
for var i := 0 to n-1 do
|
||||
fLabels[i] := labels[i];
|
||||
|
||||
fIsFitted := True;
|
||||
end;
|
||||
|
||||
function DBSCAN.Fit(X: Matrix; y: Vector): IModel;
|
||||
begin
|
||||
if y <> nil then
|
||||
if X.RowCount <> y.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
|
||||
|
||||
Fit(X);
|
||||
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function DBSCAN.Predict(X: Matrix): Vector;
|
||||
begin
|
||||
if not fIsFitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
Result := fLabels;
|
||||
end;
|
||||
|
||||
function DBSCAN.FitPredict(X: Matrix): Vector;
|
||||
begin
|
||||
Fit(X);
|
||||
Result := fLabels;
|
||||
end;
|
||||
|
||||
function DBSCAN.Clone: IModel;
|
||||
begin
|
||||
var m := new DBSCAN(fEps, fMinSamples, fSeed);
|
||||
|
||||
m.fIsFitted := fIsFitted;
|
||||
m.fFeatureCount := fFeatureCount;
|
||||
m.fClusterCount := fClusterCount;
|
||||
|
||||
if fLabels <> nil then
|
||||
begin
|
||||
var n := fLabels.Length;
|
||||
m.fLabels := new Vector(n);
|
||||
|
||||
for var i := 0 to n-1 do
|
||||
m.fLabels[i] := fLabels[i];
|
||||
end;
|
||||
|
||||
Result := m;
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// Pipeline
|
||||
//-----------------------------
|
||||
|
|
|
|||
|
|
@ -43,6 +43,82 @@ type
|
|||
/// Запрещено добавлять шаги после вызова Fit/FitTransform.
|
||||
function Add(step: IPipelineStep): DataPipeline;
|
||||
|
||||
{ // Примеры использования препроцессоров и моделей в DataPipeline.
|
||||
// Препроцессоры работают на уровне DataFrame.
|
||||
// После них Pipeline автоматически преобразует данные в Matrix/Vector.
|
||||
// Далее выполняются матричные трансформеры и модель.
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Пример 1. Классификация с категориальным целевым признаком
|
||||
var pipe :=
|
||||
DataPipeline.Build(
|
||||
'species',
|
||||
['length','width'],
|
||||
new LabelEncoder('species'), // DataFrame-препроцессор (строки → числа)
|
||||
new StandardScaler, // матричный трансформер
|
||||
new LogisticRegression // модель
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Пример 2. Регрессия с пропущенными значениями и категориальным признаком
|
||||
var pipe :=
|
||||
DataPipeline.Build(
|
||||
'price',
|
||||
['area','floor','district'],
|
||||
new Imputer('area'), // DataFrame-препроцессор (заполнение NA)
|
||||
new OneHotEncoder('district'), // DataFrame-препроцессор
|
||||
new RandomForestRegressor // модель
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Пример 3. Сложный pipeline
|
||||
var pipe :=
|
||||
DataPipeline.Build(
|
||||
'target',
|
||||
['f1','f2','f3','category'],
|
||||
new Imputer('f2'), // DataFrame-препроцессор
|
||||
new OneHotEncoder('category'), // DataFrame-препроцессор
|
||||
new StandardScaler, // матричный трансформер
|
||||
new PCATransformer(2), // матричный трансформер
|
||||
new GradientBoostingRegressor // модель
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Пример 4. Классификация без Pipeline
|
||||
var df := Datasets.Flowers;
|
||||
|
||||
var enc := new LabelEncoder('species');
|
||||
df := enc.FitTransform(df); // DataFrame уровень
|
||||
|
||||
var (X,y) := df.ToXY(['length','width'],'species');
|
||||
|
||||
var scaler := new StandardScaler;
|
||||
X := scaler.FitTransform(X); // Matrix уровень
|
||||
|
||||
var model := new LogisticRegression;
|
||||
model.Fit(X,y);
|
||||
|
||||
// Pipeline.Build используется, когда данные уже представлены
|
||||
// в виде числовой матрицы признаков X и вектора целевой переменной y.
|
||||
// В этом случае DataFrame и препроцессоры уровня таблицы не требуются.
|
||||
//
|
||||
// Типичные ситуации:
|
||||
// • экспериментирование с ML-алгоритмами
|
||||
// • сравнение моделей
|
||||
// • кросс-валидация
|
||||
// • подбор гиперпараметров
|
||||
// • тестирование моделей
|
||||
|
||||
// Пример 5. Pipeline на матричном уровне
|
||||
var pipe :=
|
||||
Pipeline.Build(
|
||||
new StandardScaler,
|
||||
new PCATransformer(2),
|
||||
new LogisticRegression
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// Строит конвейер из последовательности шагов.
|
||||
/// target и features используются только в режиме с моделью (когда в шагах присутствует IModel).
|
||||
/// Шаги должны удовлетворять порядку: DataFrame* → Matrix* → Model.
|
||||
|
|
@ -78,6 +154,8 @@ type
|
|||
|
||||
/// Признак того, что был вызван Fit или FitTransform.
|
||||
property IsFitted: boolean read fFitted;
|
||||
|
||||
function ToString: string; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -110,14 +188,19 @@ const
|
|||
'Имя целевой переменной не задано!!Target column name is not specified';
|
||||
ER_FEATURES_EMPTY =
|
||||
'Список признаков пуст!!Feature list must not be empty';
|
||||
ER_FEATURE_EMPTY =
|
||||
'Имя признака не может быть пустым!!Feature name cannot be empty';
|
||||
ER_DATAPIPE_TARGET_IN_FEATURES =
|
||||
'Целевая переменная не должна входить в список признаков!!Target column must not be included in feature list';
|
||||
'Целевая переменная "{0}" не должна входить в список признаков!!Target variable "{0}" must not appear 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';
|
||||
'Признак "{0}" не найден в DataFrame. Доступные столбцы: {1}!!'+
|
||||
'Feature "{0}" not found in DataFrame. Available columns: {1}';
|
||||
ER_DATAPIPE_DUPLICATE_FEATURE =
|
||||
'Повторяющийся признак: {0}!!Duplicate feature: {0}';
|
||||
|
||||
function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod;
|
||||
begin
|
||||
|
|
@ -187,26 +270,19 @@ begin
|
|||
if fFitted then
|
||||
Error(ER_PIPELINE_MODIFY_AFTER_FIT);
|
||||
|
||||
if step is IPreprocessor then // Data Step
|
||||
// --- 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;
|
||||
|
||||
if step is ITransformer then // Matrix Step
|
||||
// --- Matrix transformer
|
||||
if step is ITransformer 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);
|
||||
|
||||
|
|
@ -214,13 +290,45 @@ begin
|
|||
exit(Self);
|
||||
end;
|
||||
|
||||
ArgumentError(ER_DATAPIPE_UNKNOWN_STEP);
|
||||
// --- 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 + ' ' + step.ToString);
|
||||
Result := Self;
|
||||
end;
|
||||
end;
|
||||
|
||||
class function DataPipeline.Build(target: string;
|
||||
features: array of string; params steps: array of IPipelineStep): DataPipeline;
|
||||
begin
|
||||
if (target = nil) or (target = '') then
|
||||
ArgumentError(ER_TARGET_EMPTY);
|
||||
|
||||
if (features = nil) or (features.Length = 0) then
|
||||
ArgumentError(ER_FEATURES_EMPTY);
|
||||
|
||||
var seen := new HashSet<string>;
|
||||
|
||||
foreach var f in features do
|
||||
begin
|
||||
if (f = nil) or (f = '') then
|
||||
ArgumentError(ER_FEATURE_EMPTY);
|
||||
|
||||
if f = target then
|
||||
ArgumentError(ER_DATAPIPE_TARGET_IN_FEATURES, target);
|
||||
|
||||
if seen.Contains(f) then
|
||||
ArgumentError(ER_DATAPIPE_DUPLICATE_FEATURE, f);
|
||||
|
||||
seen.Add(f);
|
||||
end;
|
||||
|
||||
var p := new DataPipeline;
|
||||
p.fTarget := target;
|
||||
p.fFeatures := features;
|
||||
|
|
@ -233,6 +341,9 @@ end;
|
|||
|
||||
function DataPipeline.Fit(df: DataFrame): DataPipeline;
|
||||
begin
|
||||
if fModel = nil then
|
||||
Error(ER_MODEL_NULL);
|
||||
|
||||
var current := df;
|
||||
|
||||
// 1) DataFrame слой (с сохранением fitted-объектов)
|
||||
|
|
@ -355,18 +466,66 @@ begin
|
|||
if (fFeatures = nil) or (Length(fFeatures) = 0) then
|
||||
ArgumentError(ER_FEATURES_EMPTY);
|
||||
|
||||
// target не должен входить в features
|
||||
for var i := 0 to High(fFeatures) do
|
||||
if fFeatures[i] = fTarget then
|
||||
ArgumentError(ER_DATAPIPE_TARGET_IN_FEATURES);
|
||||
|
||||
// проверка существования target
|
||||
if not df.Schema.HasColumn(fTarget) then
|
||||
ArgumentError(ER_DATAPIPE_TARGET_NOT_FOUND, fTarget);
|
||||
begin
|
||||
var cols := df.Schema.ColumnNames.JoinToString(', ');
|
||||
ArgumentError(ER_DATAPIPE_TARGET_NOT_FOUND, fTarget, cols);
|
||||
end;
|
||||
|
||||
var seen := new HashSet<string>;
|
||||
|
||||
for var i := 0 to High(fFeatures) do
|
||||
if not df.Schema.HasColumn(fFeatures[i]) then
|
||||
ArgumentError(ER_DATAPIPE_FEATURE_NOT_FOUND, fFeatures[i]);
|
||||
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 DataPipeline.ToString: string;
|
||||
begin
|
||||
var sb := 'DataPipeline (' +
|
||||
(if fFitted then 'trained' else 'not trained') + '):' + NewLine;
|
||||
|
||||
sb += ' Target: ' + fTarget + NewLine;
|
||||
sb += ' Features: ' + fFeatures.JoinToString(', ') + 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.
|
||||
|
|
@ -91,45 +91,82 @@ type
|
|||
/// и Specificity (доли истинно отрицательных).
|
||||
/// Устойчива к дисбалансу классов.
|
||||
static function BalancedAccuracy(yTrue, yPred: Vector): real;
|
||||
|
||||
/// Вычисляет силуэт (Silhouette) для каждой точки.
|
||||
/// Для каждой строки матрицы X возвращает значение s(i) в диапазоне [-1, 1].
|
||||
/// Требует как минимум 2 различных кластера.
|
||||
/// Использует евклидово расстояние (квадратный корень не извлекается).
|
||||
/// Сложность O(n²) - для умеренных размеров данных.
|
||||
static function SilhouetteSamples(X: Matrix; labels: Vector): Vector;
|
||||
|
||||
/// Вычисляет средний силуэт (Silhouette Score).
|
||||
/// Возвращает среднее значение Silhouette по всем точкам.
|
||||
/// Чем ближе к 1 — тем лучше разделены кластеры.
|
||||
/// Значение около 0 означает пересечение кластеров.
|
||||
/// Отрицательное значение указывает на возможную ошибку кластеризации
|
||||
static function SilhouetteScore(X: Matrix; labels: Vector): real;
|
||||
|
||||
/// Вычисляет индекс Калински–Харабаса (Calinski–Harabasz Index).
|
||||
/// Возвращает отношение межкластерной дисперсии к внутрикластерной дисперсии.
|
||||
/// Чем больше значение — тем лучше разделены кластеры.
|
||||
/// Требует как минимум 2 кластера и n > k.
|
||||
/// Сложность O(n·p).
|
||||
static function CalinskiHarabaszScore(X: Matrix; labels: Vector): real;
|
||||
|
||||
/// Вычисляет индекс Дэвиса–Булдина (Davies–Bouldin Index).
|
||||
/// Чем меньше значение — тем лучше разделены кластеры.
|
||||
/// Требует минимум 2 кластера.
|
||||
/// Сложность O(n·p + k²·p).
|
||||
static function DaviesBouldinScore(X: Matrix; labels: Vector): real;
|
||||
|
||||
/// Adjusted Rand Index (ARI).
|
||||
/// Внешняя метрика качества кластеризации.
|
||||
/// Сравнивает предсказанные кластеры с истинными метками.
|
||||
///
|
||||
/// Основана на анализе всех пар объектов:
|
||||
/// проверяет, совпадает ли решение алгоритма и истинная разметка
|
||||
/// в вопросе "находятся ли две точки в одном кластере".
|
||||
///
|
||||
/// Корректирует обычный Rand Index на случайные совпадения.
|
||||
///
|
||||
/// Диапазон значений:
|
||||
/// 1 — идеальное совпадение кластеризации и истинных меток
|
||||
/// 0 — случайное разбиение
|
||||
/// <0 — хуже случайного
|
||||
static function AdjustedRandIndex(yTrue, yPred: Vector): real;
|
||||
end;
|
||||
|
||||
type
|
||||
/// Матрица ошибок (Confusion Matrix) для бинарной классификации.
|
||||
/// Хранит количество истинно положительных (TP),
|
||||
/// истинно отрицательных (TN),
|
||||
/// ложно положительных (FP)
|
||||
/// и ложно отрицательных (FN) предсказаний.
|
||||
/// истинно отрицательных (TN),
|
||||
/// ложно положительных (FP)
|
||||
/// и ложно отрицательных (FN) предсказаний.
|
||||
/// Используется для анализа качества классификатора
|
||||
/// и вычисления метрик Accuracy, Precision, Recall и F1
|
||||
/// и вычисления метрик Accuracy, Precision, Recall и F1
|
||||
ConfusionMatrix = class
|
||||
private
|
||||
fTP, fTN, fFP, fFN: integer;
|
||||
public
|
||||
/// Создаёт матрицу ошибок по истинным меткам yTrue
|
||||
/// и предсказаниям модели yPred.
|
||||
/// Ожидается, что векторы содержат значения 0 и 1
|
||||
/// и имеют одинаковую длину.
|
||||
/// Подсчитывает TP, TN, FP и FN.
|
||||
/// Создаёт матрицу ошибок по истинным меткам yTrue и предсказаниям модели yPred.
|
||||
/// Ожидается, что векторы содержат значения 0 и 1 и имеют одинаковую длину.
|
||||
/// Подсчитывает TP, TN, FP и FN
|
||||
constructor Create(yTrue, yPred: Vector);
|
||||
|
||||
/// Истинно положительные (True Positives).
|
||||
/// Количество объектов класса 1,
|
||||
/// которые модель правильно предсказала как 1.
|
||||
/// Количество объектов класса 1, которые модель правильно предсказала как 1.
|
||||
property TP: integer read fTP;
|
||||
|
||||
/// Истинно отрицательные (True Negatives).
|
||||
/// Количество объектов класса 0,
|
||||
/// которые модель правильно предсказала как 0.
|
||||
/// Количество объектов класса 0, которые модель правильно предсказала как 0.
|
||||
property TN: integer read fTN;
|
||||
|
||||
/// Ложно положительные (False Positives).
|
||||
/// Количество объектов класса 0,
|
||||
/// которые модель ошибочно предсказала как 1.
|
||||
/// Количество объектов класса 0, которые модель ошибочно предсказала как 1.
|
||||
property FP: integer read fFP;
|
||||
|
||||
/// Ложно отрицательные (False Negatives).
|
||||
/// Количество объектов класса 1,
|
||||
/// которые модель ошибочно предсказала как 0.
|
||||
/// Количество объектов класса 1, которые модель ошибочно предсказала как 0.
|
||||
property FN: integer read fFN;
|
||||
|
||||
/// Доля правильных предсказаний.
|
||||
|
|
@ -139,12 +176,12 @@ type
|
|||
function Accuracy: real;
|
||||
/// Точность (Precision) для положительного класса (1).
|
||||
/// Среди всех объектов, которые модель предсказала как 1,
|
||||
/// показывает долю действительно принадлежащих этому классу.
|
||||
/// показывает долю действительно принадлежащих этому классу.
|
||||
/// Важна, когда критичны ложные положительные результаты («ложные тревоги»).
|
||||
function Precision: real;
|
||||
/// Полнота (Recall) для положительного класса (1).
|
||||
/// Среди всех объектов, которые в действительности равны 1,
|
||||
/// показывает долю правильно найденных моделью.
|
||||
/// показывает долю правильно найденных моделью.
|
||||
/// Важна, когда нежелательны пропуски положительных случаев (например, заболеваний).
|
||||
function Recall: real;
|
||||
/// F1-мера.
|
||||
|
|
@ -167,8 +204,19 @@ const
|
|||
ER_DIV_BY_ZERO =
|
||||
'Деление на ноль в {0}!!Division by zero in {0}';
|
||||
ER_INVALID_CLASS_LABEL =
|
||||
'Некорректная метка класса: {0}!!Invalid class label: {0}';
|
||||
|
||||
'Некорректная метка класса: {0}!!Invalid class label: {0}';
|
||||
ER_STRATIFIED_LABELS_INVALID =
|
||||
'Метки кластеров должны быть целыми числами!!Cluster labels must be integers';
|
||||
ER_SILHOUETTE_NEEDS_2_CLUSTERS =
|
||||
'Для вычисления Silhouette требуется минимум 2 кластера!!Silhouette requires at least 2 clusters';
|
||||
ER_INVALID_VALUE_AT =
|
||||
'Некорректное значение в {0} на позиции {1}!!Invalid value in {0} at index {1}';
|
||||
ER_CH_NEEDS_2_CLUSTERS =
|
||||
'Для Calinski–Harabasz требуется минимум 2 кластера!!Calinski–Harabasz requires at least 2 clusters';
|
||||
ER_CH_NEEDS_N_GT_K =
|
||||
'Для Calinski–Harabasz требуется n > k!!Calinski–Harabasz requires n > k';
|
||||
ER_LABELS_MUST_BE_INTEGERS =
|
||||
'Метки кластеров должны быть целыми числами!!Cluster labels must be integers';
|
||||
//-----------------------------
|
||||
// Metrics
|
||||
//-----------------------------
|
||||
|
|
@ -494,6 +542,457 @@ begin
|
|||
Result := ConfusionMatrix.Create(yTrue, yPred).BalancedAccuracy;
|
||||
end;
|
||||
|
||||
static function Metrics.SilhouetteSamples(X: Matrix; labels: Vector): Vector;
|
||||
begin
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
if labels = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'labels');
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'SilhouetteSamples');
|
||||
|
||||
if labels.Length <> n then
|
||||
DimensionError(ER_DIM_MISMATCH, labels.Length, n);
|
||||
|
||||
// --- собираем размеры кластеров и маппинг label -> 0..c-1
|
||||
var map := new Dictionary<integer, integer>();
|
||||
var counts := new List<integer>();
|
||||
var clsId := new integer[n];
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var v := labels[i];
|
||||
var lab := integer(v);
|
||||
|
||||
if Abs(v - lab) > 1e-12 then
|
||||
ArgumentError(ER_LABELS_MUST_BE_INTEGERS);
|
||||
|
||||
var id: integer;
|
||||
if map.TryGetValue(lab, id) then
|
||||
begin
|
||||
clsId[i] := id;
|
||||
counts[id] += 1;
|
||||
end
|
||||
else
|
||||
begin
|
||||
id := map.Count;
|
||||
map.Add(lab, id);
|
||||
clsId[i] := id;
|
||||
counts.Add(1);
|
||||
end;
|
||||
end;
|
||||
|
||||
var cCount := counts.Count;
|
||||
if cCount < 2 then
|
||||
ArgumentError(ER_SILHOUETTE_NEEDS_2_CLUSTERS);
|
||||
|
||||
// --- список уникальных кластеров, чтобы не обращаться к Dictionary в hot-path
|
||||
var sizes := counts.ToArray;
|
||||
|
||||
// --- результат
|
||||
Result := new Vector(n);
|
||||
|
||||
// --- рабочие массивы (переиспользуем, без аллокаций в цикле по i)
|
||||
var sumToCluster := new real[cCount];
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
// обнуляем суммы
|
||||
for var c := 0 to cCount - 1 do
|
||||
sumToCluster[c] := 0.0;
|
||||
|
||||
// суммируем расстояния от i до всех остальных по кластерам
|
||||
for var t := 0 to n - 1 do
|
||||
if t <> i then
|
||||
begin
|
||||
var dist := 0.0;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var a := X[i,j];
|
||||
if double.IsNaN(a) or double.IsInfinity(a) then
|
||||
ArgumentError(ER_INVALID_VALUE_AT, 'X', i);
|
||||
|
||||
var b := X[t,j];
|
||||
if double.IsNaN(b) or double.IsInfinity(b) then
|
||||
ArgumentError(ER_INVALID_VALUE_AT, 'X', t);
|
||||
|
||||
var d := a - b;
|
||||
dist += d * d;
|
||||
end;
|
||||
|
||||
sumToCluster[clsId[t]] += Sqrt(dist);
|
||||
end;
|
||||
|
||||
var my := clsId[i];
|
||||
|
||||
// a(i): среднее расстояние до своего кластера (без самой точки)
|
||||
var aMean := 0.0;
|
||||
if sizes[my] > 1 then
|
||||
aMean := sumToCluster[my] / (sizes[my] - 1)
|
||||
else
|
||||
aMean := 0.0;
|
||||
|
||||
// b(i): минимальное среднее расстояние до другого кластера
|
||||
var bMean := double.MaxValue;
|
||||
|
||||
for var c := 0 to cCount - 1 do
|
||||
if c <> my then
|
||||
begin
|
||||
if sizes[c] = 0 then
|
||||
continue;
|
||||
|
||||
var mean := sumToCluster[c] / sizes[c];
|
||||
if mean < bMean then
|
||||
bMean := mean;
|
||||
end;
|
||||
|
||||
if double.IsInfinity(bMean) then
|
||||
bMean := 0.0;
|
||||
|
||||
var denom := Max(aMean, bMean);
|
||||
if denom = 0 then
|
||||
Result[i] := 0.0
|
||||
else
|
||||
Result[i] := (bMean - aMean) / denom;
|
||||
end;
|
||||
end;
|
||||
|
||||
static function Metrics.SilhouetteScore(X: Matrix; labels: Vector): real;
|
||||
begin
|
||||
var s := SilhouetteSamples(X, labels);
|
||||
|
||||
var n := s.Length;
|
||||
var sum := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
sum += s[i];
|
||||
|
||||
if n = 0 then
|
||||
exit(0.0);
|
||||
|
||||
Result := sum / n;
|
||||
end;
|
||||
|
||||
/// Вычисляет индекс Калински–Харабаса (Calinski–Harabasz Index).
|
||||
/// Возвращает отношение межкластерной дисперсии к внутрикластерной дисперсии.
|
||||
/// Чем больше значение — тем лучше разделены кластеры.
|
||||
/// При нулевой внутрикластерной дисперсии возвращает +∞.
|
||||
/// Требует как минимум 2 кластера и n > k.
|
||||
/// Сложность O(n·p).
|
||||
static function Metrics.CalinskiHarabaszScore(X: Matrix; labels: Vector): real;
|
||||
begin
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if labels = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'labels');
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
if n = 0 then
|
||||
ArgumentError(ER_EMPTY_DATA, 'CalinskiHarabaszScore');
|
||||
|
||||
if labels.Length <> n then
|
||||
DimensionError(ER_DIM_MISMATCH, labels.Length, n);
|
||||
|
||||
// --- label -> 0..k-1 + sizes
|
||||
var map := new Dictionary<integer, integer>();
|
||||
var counts := new List<integer>();
|
||||
var clsId := new integer[n];
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var v := labels[i];
|
||||
var lab := integer(v);
|
||||
|
||||
if Abs(v - lab) > 1e-12 then
|
||||
ArgumentError(ER_LABELS_MUST_BE_INTEGERS);
|
||||
|
||||
var id: integer;
|
||||
if map.TryGetValue(lab, id) then
|
||||
begin
|
||||
clsId[i] := id;
|
||||
counts[id] += 1;
|
||||
end
|
||||
else
|
||||
begin
|
||||
id := map.Count;
|
||||
map.Add(lab, id);
|
||||
clsId[i] := id;
|
||||
counts.Add(1);
|
||||
end;
|
||||
end;
|
||||
|
||||
var k := counts.Count;
|
||||
|
||||
if k < 2 then
|
||||
ArgumentError(ER_CH_NEEDS_2_CLUSTERS);
|
||||
|
||||
if k >= n then
|
||||
ArgumentError(ER_CH_NEEDS_N_GT_K);
|
||||
|
||||
var sizes := counts.ToArray;
|
||||
|
||||
// --- глобальное среднее
|
||||
var globalMean := new real[p];
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var xij := X[i,j];
|
||||
if double.IsNaN(xij) or double.IsInfinity(xij) then
|
||||
ArgumentError(ER_INVALID_VALUE_AT, 'X', i);
|
||||
globalMean[j] += xij;
|
||||
end;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
globalMean[j] /= n;
|
||||
|
||||
// --- центры кластеров
|
||||
var centers := new Matrix(k, p);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var c := clsId[i];
|
||||
for var j := 0 to p - 1 do
|
||||
centers[c,j] += X[i,j];
|
||||
end;
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
for var j := 0 to p - 1 do
|
||||
centers[c,j] /= sizes[c];
|
||||
|
||||
// --- Within (W): сумма квадратов расстояний до центров кластеров
|
||||
var W := 0.0;
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var c := clsId[i];
|
||||
var dist := 0.0;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var d := X[i,j] - centers[c,j];
|
||||
dist += d * d;
|
||||
end;
|
||||
|
||||
W += dist;
|
||||
end;
|
||||
|
||||
// --- Between (B): сумма n_c * ||mu_c - mu||^2
|
||||
var B := 0.0;
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
begin
|
||||
var dist := 0.0;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var d := centers[c,j] - globalMean[j];
|
||||
dist += d * d;
|
||||
end;
|
||||
|
||||
B += sizes[c] * dist;
|
||||
end;
|
||||
|
||||
if W = 0 then
|
||||
exit(double.PositiveInfinity);
|
||||
|
||||
var ch := (B / (k - 1)) / (W / (n - k));
|
||||
|
||||
if double.IsNaN(ch) or double.IsInfinity(ch) then
|
||||
ArgumentError(ER_INVALID_VALUE, 'CalinskiHarabaszScore');
|
||||
|
||||
Result := ch;
|
||||
end;
|
||||
|
||||
static function Metrics.DaviesBouldinScore(X: Matrix; labels: Vector): real;
|
||||
begin
|
||||
if X = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'X');
|
||||
|
||||
if labels = nil then
|
||||
ArgumentNullError(ER_ARG_NULL, 'labels');
|
||||
|
||||
var n := X.RowCount;
|
||||
var p := X.ColCount;
|
||||
|
||||
if labels.Length <> n then
|
||||
DimensionError(ER_DIM_MISMATCH, labels.Length, n);
|
||||
|
||||
// --- label -> 0..k-1
|
||||
var map := new Dictionary<integer, integer>();
|
||||
var counts := new List<integer>();
|
||||
var clsId := new integer[n];
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var v := labels[i];
|
||||
var lab := integer(v);
|
||||
|
||||
if Abs(v - lab) > 1e-12 then
|
||||
ArgumentError(ER_LABELS_MUST_BE_INTEGERS);
|
||||
|
||||
var id: integer;
|
||||
if map.TryGetValue(lab, id) then
|
||||
begin
|
||||
clsId[i] := id;
|
||||
counts[id] += 1;
|
||||
end
|
||||
else
|
||||
begin
|
||||
id := map.Count;
|
||||
map.Add(lab, id);
|
||||
clsId[i] := id;
|
||||
counts.Add(1);
|
||||
end;
|
||||
end;
|
||||
|
||||
var k := counts.Count;
|
||||
if k < 2 then
|
||||
ArgumentError(ER_CH_NEEDS_2_CLUSTERS);
|
||||
|
||||
var sizes := counts.ToArray;
|
||||
|
||||
// --- центры
|
||||
var centers := new Matrix(k, p);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var c := clsId[i];
|
||||
for var j := 0 to p - 1 do
|
||||
centers[c,j] += X[i,j];
|
||||
end;
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
for var j := 0 to p - 1 do
|
||||
centers[c,j] /= sizes[c];
|
||||
|
||||
// --- Si: среднее расстояние точек до центра
|
||||
var S := new real[k];
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var c := clsId[i];
|
||||
var dist := 0.0;
|
||||
|
||||
for var j := 0 to p - 1 do
|
||||
begin
|
||||
var d := X[i,j] - centers[c,j];
|
||||
dist += d * d;
|
||||
end;
|
||||
|
||||
S[c] += Sqrt(dist);
|
||||
end;
|
||||
|
||||
for var c := 0 to k - 1 do
|
||||
S[c] /= sizes[c];
|
||||
|
||||
// --- считаем DB
|
||||
var total := 0.0;
|
||||
|
||||
for var i := 0 to k - 1 do
|
||||
begin
|
||||
var maxR := 0.0;
|
||||
|
||||
for var j := 0 to k - 1 do
|
||||
if i <> j then
|
||||
begin
|
||||
var dist := 0.0;
|
||||
|
||||
for var d := 0 to p - 1 do
|
||||
begin
|
||||
var delta := centers[i,d] - centers[j,d];
|
||||
dist += delta * delta;
|
||||
end;
|
||||
|
||||
dist := Sqrt(dist);
|
||||
|
||||
if dist = 0 then
|
||||
continue;
|
||||
|
||||
var Rij := (S[i] + S[j]) / dist;
|
||||
|
||||
if Rij > maxR then
|
||||
maxR := Rij;
|
||||
end;
|
||||
|
||||
total += maxR;
|
||||
end;
|
||||
|
||||
Result := total / k;
|
||||
end;
|
||||
|
||||
/// Adjusted Rand Index (ARI).
|
||||
/// Внешняя метрика качества кластеризации.
|
||||
/// Сравнивает предсказанные кластеры с истинными метками.
|
||||
///
|
||||
/// Основана на анализе всех пар объектов:
|
||||
/// проверяет, совпадает ли решение алгоритма и истинная разметка
|
||||
/// в вопросе "находятся ли две точки в одном кластере".
|
||||
///
|
||||
/// Корректирует обычный Rand Index на случайные совпадения.
|
||||
///
|
||||
/// Диапазон значений:
|
||||
/// 1 — идеальное совпадение кластеризации и истинных меток
|
||||
/// 0 — случайное разбиение
|
||||
/// <0 — хуже случайного
|
||||
static function Metrics.AdjustedRandIndex(yTrue, yPred: Vector): real;
|
||||
begin
|
||||
if yTrue.Length <> yPred.Length then
|
||||
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
|
||||
|
||||
var n := yTrue.Length;
|
||||
|
||||
if n < 2 then
|
||||
Result := 1
|
||||
else
|
||||
begin
|
||||
var tp := 0;
|
||||
var tn := 0;
|
||||
var fp := 0;
|
||||
var fn := 0;
|
||||
|
||||
for var i := 0 to n-2 do
|
||||
for var j := i+1 to n-1 do
|
||||
begin
|
||||
var sameTrue := yTrue[i] = yTrue[j];
|
||||
var samePred := yPred[i] = yPred[j];
|
||||
|
||||
if sameTrue and samePred then
|
||||
tp += 1
|
||||
else if (not sameTrue) and (not samePred) then
|
||||
tn += 1
|
||||
else if (not sameTrue) and samePred then
|
||||
fp += 1
|
||||
else
|
||||
fn += 1;
|
||||
end;
|
||||
|
||||
var total := tp + tn + fp + fn;
|
||||
|
||||
var ri := (tp + tn) / total;
|
||||
|
||||
var a := tp + fp;
|
||||
var b := tp + fn;
|
||||
var c := fn + tn;
|
||||
var d := fp + tn;
|
||||
|
||||
var expected := ((a*b) + (c*d)) / (total*total);
|
||||
|
||||
if 1 - expected = 0 then
|
||||
Result := 0
|
||||
else
|
||||
Result := (ri - expected) / (1 - expected);
|
||||
end;
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// ConfusionMatrix
|
||||
//-----------------------------
|
||||
|
|
|
|||
682
bin/Lib/PlotML.pas
Normal file
682
bin/Lib/PlotML.pas
Normal file
|
|
@ -0,0 +1,682 @@
|
|||
unit PlotML;
|
||||
|
||||
{$reference %GAC%\InteractiveDataDisplay.WPF.dll}
|
||||
{$reference 'PresentationFramework.dll'}
|
||||
{$reference 'WindowsBase.dll'}
|
||||
{$reference 'PresentationCore.dll'}
|
||||
|
||||
interface
|
||||
|
||||
uses System,
|
||||
System.Windows,
|
||||
System.Windows.Controls,
|
||||
System.Windows.Media,
|
||||
System.Threading,
|
||||
System.Windows.Threading,
|
||||
InteractiveDataDisplay.WPF;
|
||||
|
||||
type
|
||||
ApplicationWPF = System.Windows.Application;
|
||||
WindowWPF = System.Windows.Window;
|
||||
GridWPF = System.Windows.Controls.Grid;
|
||||
ChartWPF = InteractiveDataDisplay.WPF.Chart;
|
||||
LineGraphWPF = InteractiveDataDisplay.WPF.LineGraph;
|
||||
MarkerGraphWPF = InteractiveDataDisplay.WPF.CircleMarkerGraph;
|
||||
BrushWPF = System.Windows.Media.SolidColorBrush;
|
||||
Colors = System.Windows.Media.Colors;
|
||||
ColorWPF = System.Windows.Media.Color;
|
||||
|
||||
MarkerType = (Circle, Box, Triangle, Diamond, Cross);
|
||||
|
||||
Palettes = static class
|
||||
public
|
||||
const &Default = 'default';
|
||||
const Dark = 'dark';
|
||||
const Pastel = 'pastel';
|
||||
const Bright = 'bright';
|
||||
const Muted = 'muted';
|
||||
end;
|
||||
|
||||
Palette = class
|
||||
public
|
||||
Colors: array of Color;
|
||||
constructor Create(params c: array of Color);
|
||||
end;
|
||||
|
||||
Figure = class;
|
||||
|
||||
Cell = class
|
||||
private
|
||||
parentGrid: GridWPF;
|
||||
row,col: integer;
|
||||
chart: ChartWPF;
|
||||
|
||||
palette: Palette;
|
||||
paletteIndex: integer := 0;
|
||||
|
||||
function NextColor: Color;
|
||||
procedure EnsureChart;
|
||||
|
||||
public
|
||||
constructor Create(g: GridWPF; r,c: integer);
|
||||
|
||||
procedure LineGraph(x, y: array of real; color: ColorWPF? := nil;
|
||||
thickness: real := 2; legend: string := nil);
|
||||
|
||||
procedure Points(x, y: array of real; color: ColorWPF? := nil;
|
||||
size: real := 6; marker: MarkerType := MarkerType.Circle; legend: string := nil);
|
||||
|
||||
procedure Heatmap(m: array[,] of real);
|
||||
|
||||
procedure SetPalette(name: string);
|
||||
procedure Title(s: string);
|
||||
procedure XLabel(s: string);
|
||||
procedure YLabel(s: string);
|
||||
|
||||
procedure Limits(xmin, xmax, ymin, ymax: real);
|
||||
procedure XLim(xmin,xmax: real);
|
||||
procedure YLim(ymin,ymax: real);
|
||||
|
||||
procedure Clear;
|
||||
end;
|
||||
|
||||
Figure = class
|
||||
private
|
||||
grid: GridWPF;
|
||||
cells: array[,] of Cell;
|
||||
public
|
||||
constructor Create(rows,cols: integer);
|
||||
property Item[r,c: integer]: Cell read cells[r,c]; default;
|
||||
end;
|
||||
|
||||
Plot = static class
|
||||
private
|
||||
static procedure RunUI(a: procedure);
|
||||
|
||||
static function CreateLineSeries(x,y: array of real; c: Color): LineGraphWPF;
|
||||
static function CreatePointSeries(x, y: array of real;
|
||||
color: ColorWPF; size: real; marker: MarkerType): MarkerGraphWPF;
|
||||
|
||||
static procedure DrawLine(chart: ChartWPF; x, y: array of real;
|
||||
color: ColorWPF; thickness: real; legend: string);
|
||||
|
||||
static procedure DrawPoints(chart: ChartWPF; x, y: array of real;
|
||||
color: ColorWPF; size: real; marker: MarkerType; legend: string);
|
||||
|
||||
static procedure DrawHeatmap(chart: ChartWPF; m: array[,] of real);
|
||||
|
||||
public
|
||||
static procedure AddSeries(chart: ChartWPF; series: UIElement);
|
||||
|
||||
static procedure LineGraph(x, y: array of real;
|
||||
color: ColorWPF? := nil; thickness: real := 2; legend: string := nil);
|
||||
static procedure Points(x, y: array of real;
|
||||
color: ColorWPF? := nil; size: real := 6; marker: MarkerType := MarkerType.Circle; legend: string := nil);
|
||||
static procedure Heatmap(m: array[,] of real);
|
||||
|
||||
static function Grid(rows,cols: integer): Figure;
|
||||
|
||||
static procedure SetPalette(name: string);
|
||||
|
||||
static procedure Limits(xmin,xmax,ymin,ymax: real);
|
||||
static procedure XLim(xmin,xmax: real);
|
||||
static procedure YLim(ymin,ymax: real);
|
||||
|
||||
static procedure Clear;
|
||||
|
||||
static procedure Save(filename: string);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
var
|
||||
uiThread: Thread;
|
||||
uiDispatcher: Dispatcher;
|
||||
|
||||
app: ApplicationWPF;
|
||||
win: WindowWPF;
|
||||
rootChart: ChartWPF;
|
||||
|
||||
paletteDict: Dictionary<string,Palette>;
|
||||
currentPalette: Palette;
|
||||
rootPaletteIndex: integer := 0;
|
||||
|
||||
function CreateMarker(t: MarkerType): InteractiveDataDisplay.WPF.ColorSizeMarker;
|
||||
begin
|
||||
case t of
|
||||
MarkerType.Circle: Result := new CircleMarker;
|
||||
MarkerType.Box: Result := new BoxMarker;
|
||||
MarkerType.Triangle: Result := new TriangleMarker;
|
||||
MarkerType.Diamond: Result := new DiamondMarker;
|
||||
MarkerType.Cross: Result := new CrossMarker;
|
||||
end;
|
||||
end;
|
||||
|
||||
function NextRootColor: ColorWPF;
|
||||
begin
|
||||
var c := currentPalette.Colors[
|
||||
rootPaletteIndex mod currentPalette.Colors.Length
|
||||
];
|
||||
|
||||
rootPaletteIndex += 1;
|
||||
Result := c;
|
||||
end;
|
||||
|
||||
function MakeHistogram(data: array of real; bins: integer): (array of real, array of real);
|
||||
begin
|
||||
var xmin := data.Min;
|
||||
var xmax := data.Max;
|
||||
|
||||
var h := (xmax-xmin)/bins;
|
||||
|
||||
var counts := new real[bins];
|
||||
|
||||
foreach var v in data do
|
||||
begin
|
||||
var k := integer((v-xmin)/h);
|
||||
if k=bins then k := bins-1;
|
||||
counts[k] += 1;
|
||||
end;
|
||||
|
||||
var xs := ArrGen(bins, i -> xmin + (i+0.5)*h);
|
||||
|
||||
Result := (xs,counts);
|
||||
end;
|
||||
|
||||
procedure InitPalettes;
|
||||
begin
|
||||
paletteDict := new Dictionary<string,Palette>;
|
||||
|
||||
paletteDict['default'] := new Palette(
|
||||
Color.FromRgb($1f,$77,$b4),
|
||||
Color.FromRgb($ff,$7f,$0e),
|
||||
Color.FromRgb($2c,$a0,$2c),
|
||||
Color.FromRgb($d6,$27,$28),
|
||||
Color.FromRgb($94,$67,$bd),
|
||||
Color.FromRgb($8c,$56,$4b),
|
||||
Color.FromRgb($e3,$77,$c2),
|
||||
Color.FromRgb($7f,$7f,$7f),
|
||||
Color.FromRgb($bc,$bd,$22),
|
||||
Color.FromRgb($17,$be,$cf)
|
||||
);
|
||||
|
||||
paletteDict['pastel'] := new Palette(
|
||||
Color.FromRgb(141,211,199),
|
||||
Color.FromRgb(255,255,179),
|
||||
Color.FromRgb(190,186,218),
|
||||
Color.FromRgb(251,128,114),
|
||||
Color.FromRgb(128,177,211),
|
||||
Color.FromRgb(253,180,98),
|
||||
Color.FromRgb(179,222,105),
|
||||
Color.FromRgb(252,205,229)
|
||||
);
|
||||
|
||||
paletteDict['dark'] := new Palette(
|
||||
Color.FromRgb(27,158,119),
|
||||
Color.FromRgb(217,95,2),
|
||||
Color.FromRgb(117,112,179),
|
||||
Color.FromRgb(231,41,138),
|
||||
Color.FromRgb(102,166,30),
|
||||
Color.FromRgb(230,171,2),
|
||||
Color.FromRgb(166,118,29),
|
||||
Color.FromRgb(102,102,102)
|
||||
);
|
||||
|
||||
paletteDict['bright'] := new Palette(
|
||||
Color.FromRgb(0,114,178),
|
||||
Color.FromRgb(230,159,0),
|
||||
Color.FromRgb(0,158,115),
|
||||
Color.FromRgb(213,94,0),
|
||||
Color.FromRgb(204,121,167),
|
||||
Color.FromRgb(86,180,233)
|
||||
);
|
||||
|
||||
paletteDict['muted'] := new Palette(
|
||||
Color.FromRgb(76,114,176),
|
||||
Color.FromRgb(221,132,82),
|
||||
Color.FromRgb(85,168,104),
|
||||
Color.FromRgb(196,78,82),
|
||||
Color.FromRgb(129,114,179),
|
||||
Color.FromRgb(147,120,96)
|
||||
);
|
||||
|
||||
currentPalette := paletteDict['default'];
|
||||
end;
|
||||
|
||||
procedure InitUI;
|
||||
begin
|
||||
uiThread := new Thread(() ->
|
||||
begin
|
||||
app := new ApplicationWPF;
|
||||
|
||||
app.Dispatcher.UnhandledException += (o,e) ->
|
||||
begin
|
||||
Println(e.Exception.Message);
|
||||
if e.Exception.InnerException <> nil then
|
||||
Println(e.Exception.InnerException.Message);
|
||||
halt;
|
||||
end;
|
||||
|
||||
rootChart := new ChartWPF;
|
||||
|
||||
win := new WindowWPF;
|
||||
win.Title := 'PlotML';
|
||||
win.Width := 800;
|
||||
win.Height := 600;
|
||||
win.Content := rootChart;
|
||||
|
||||
win.Closed += (o,e) ->
|
||||
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(
|
||||
DispatcherPriority.Normal);
|
||||
|
||||
uiDispatcher := Dispatcher.CurrentDispatcher;
|
||||
|
||||
InitPalettes;
|
||||
|
||||
win.Show;
|
||||
|
||||
Dispatcher.Run;
|
||||
end);
|
||||
|
||||
uiThread.SetApartmentState(ApartmentState.STA);
|
||||
uiThread.Start;
|
||||
|
||||
while uiDispatcher = nil do
|
||||
Sleep(10);
|
||||
end;
|
||||
|
||||
constructor Palette.Create(params c: array of Color);
|
||||
begin
|
||||
Colors := c;
|
||||
end;
|
||||
|
||||
constructor Cell.Create(g: GridWPF; r,c: integer);
|
||||
begin
|
||||
parentGrid := g;
|
||||
row := r;
|
||||
col := c;
|
||||
palette := currentPalette;
|
||||
end;
|
||||
|
||||
function Cell.NextColor: ColorWPF;
|
||||
begin
|
||||
var c := palette.Colors[
|
||||
paletteIndex mod palette.Colors.Length
|
||||
];
|
||||
|
||||
paletteIndex += 1;
|
||||
|
||||
Result := c;
|
||||
end;
|
||||
|
||||
procedure Cell.SetPalette(name: string);
|
||||
begin
|
||||
if paletteDict.ContainsKey(name) then
|
||||
begin
|
||||
palette := paletteDict[name];
|
||||
paletteIndex := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure Cell.Title(s: string);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
chart.Title := s;
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.XLabel(s: string);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
chart.BottomTitle := s;
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.YLabel(s: string);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
chart.LeftTitle := s;
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.Limits(xmin,xmax,ymin,ymax: real);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
|
||||
chart.PlotOriginX := xmin;
|
||||
chart.PlotOriginY := ymin;
|
||||
|
||||
chart.PlotWidth := xmax - xmin;
|
||||
chart.PlotHeight := ymax - ymin;
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.XLim(xmin,xmax: real);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
|
||||
chart.PlotOriginX := xmin;
|
||||
chart.PlotWidth := xmax - xmin;
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.YLim(ymin,ymax: real);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
|
||||
chart.PlotOriginY := ymin;
|
||||
chart.PlotHeight := ymax - ymin;
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.Clear;
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
if chart = nil then exit;
|
||||
|
||||
var container := chart.Content as GridWPF;
|
||||
if container <> nil then
|
||||
container.Children.Clear;
|
||||
|
||||
paletteIndex := 0;
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.EnsureChart;
|
||||
begin
|
||||
if chart <> nil then exit;
|
||||
|
||||
chart := new ChartWPF;
|
||||
chart.LegendVisibility := Visibility.Hidden;
|
||||
|
||||
var container := new GridWPF;
|
||||
chart.Content := container;
|
||||
|
||||
GridWPF.SetRow(chart,row);
|
||||
GridWPF.SetColumn(chart,col);
|
||||
|
||||
parentGrid.Children.Add(chart);
|
||||
end;
|
||||
|
||||
procedure Cell.LineGraph(x, y: array of real; color: ColorWPF?;
|
||||
thickness: real; legend: string);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
|
||||
var clr := if color.HasValue then color.Value else NextColor;
|
||||
|
||||
Plot.DrawLine(chart, x, y, clr, thickness, legend);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.Points(x, y: array of real; color: ColorWPF?;
|
||||
size: real; marker: MarkerType; legend: string);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
|
||||
var clr := if color.HasValue then color.Value else NextColor;
|
||||
|
||||
Plot.DrawPoints(chart, x, y, clr, size, marker, legend);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure Cell.Heatmap(m: array[,] of real);
|
||||
begin
|
||||
Plot.RunUI(() ->
|
||||
begin
|
||||
EnsureChart;
|
||||
Plot.DrawHeatmap(chart, m);
|
||||
end);
|
||||
end;
|
||||
|
||||
constructor Figure.Create(rows,cols: integer);
|
||||
begin
|
||||
grid := new GridWPF;
|
||||
|
||||
for var i:=0 to rows-1 do
|
||||
grid.RowDefinitions.Add(new RowDefinition);
|
||||
|
||||
for var j:=0 to cols-1 do
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition);
|
||||
|
||||
cells := new Cell[rows,cols];
|
||||
|
||||
for var i:=0 to rows-1 do
|
||||
for var j:=0 to cols-1 do
|
||||
cells[i,j] := new Cell(grid,i,j);
|
||||
end;
|
||||
|
||||
static procedure Plot.RunUI(a: procedure);
|
||||
begin
|
||||
if Dispatcher.CurrentDispatcher = uiDispatcher then
|
||||
a()
|
||||
else
|
||||
uiDispatcher.Invoke(a);
|
||||
end;
|
||||
|
||||
static procedure Plot.Limits(xmin,xmax,ymin,ymax: real);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
rootChart.PlotOriginX := xmin;
|
||||
rootChart.PlotOriginY := ymin;
|
||||
|
||||
rootChart.PlotWidth := xmax - xmin;
|
||||
rootChart.PlotHeight := ymax - ymin;
|
||||
end);
|
||||
end;
|
||||
|
||||
static procedure Plot.XLim(xmin,xmax: real);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
rootChart.PlotOriginX := xmin;
|
||||
rootChart.PlotWidth := xmax - xmin;
|
||||
end);
|
||||
end;
|
||||
|
||||
static procedure Plot.YLim(ymin,ymax: real);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
rootChart.PlotOriginY := ymin;
|
||||
rootChart.PlotHeight := ymax - ymin;
|
||||
end);
|
||||
end;
|
||||
|
||||
static procedure Plot.Clear;
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
var container := rootChart.Content as GridWPF;
|
||||
if container <> nil then
|
||||
container.Children.Clear;
|
||||
end);
|
||||
end;
|
||||
|
||||
static procedure Plot.Save(filename: string);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
var rtb := new System.Windows.Media.Imaging.RenderTargetBitmap(
|
||||
integer(win.Width),
|
||||
integer(win.Height),
|
||||
96, 96,
|
||||
PixelFormats.Pbgra32
|
||||
);
|
||||
|
||||
rtb.Render(win);
|
||||
|
||||
var encoder := new System.Windows.Media.Imaging.PngBitmapEncoder;
|
||||
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(rtb));
|
||||
|
||||
var fs := new System.IO.FileStream(filename, System.IO.FileMode.Create);
|
||||
encoder.Save(fs);
|
||||
fs.Close;
|
||||
end);
|
||||
end;
|
||||
|
||||
static function Plot.CreateLineSeries(x,y: array of real; c: Color): LineGraphWPF;
|
||||
begin
|
||||
var g := new LineGraphWPF;
|
||||
|
||||
g.Stroke := new BrushWPF(c);
|
||||
g.StrokeThickness := 2;
|
||||
|
||||
g.Plot(x,y);
|
||||
|
||||
Result := g;
|
||||
end;
|
||||
|
||||
static function Plot.CreatePointSeries(x, y: array of real;
|
||||
color: ColorWPF; size: real; marker: MarkerType): MarkerGraphWPF;
|
||||
begin
|
||||
var g := new MarkerGraphWPF;
|
||||
|
||||
{var alpha := 0.5;
|
||||
var a := round(alpha * 255);
|
||||
var c := ColorWPF.FromArgb(a, color.R, color.G, color.B);
|
||||
|
||||
g.Color := c;}
|
||||
|
||||
g.Color := color;
|
||||
g.Size := size;
|
||||
g.MarkerType := CreateMarker(marker);
|
||||
g.StrokeThickness := 0;
|
||||
|
||||
g.Plot(x,y);
|
||||
|
||||
Result := g;
|
||||
end;
|
||||
|
||||
static procedure Plot.DrawLine(chart: ChartWPF; x, y: array of real;
|
||||
color: ColorWPF; thickness: real; legend: string);
|
||||
begin
|
||||
var g := CreateLineSeries(x, y, color);
|
||||
|
||||
g.StrokeThickness := thickness;
|
||||
|
||||
if legend <> nil then
|
||||
begin
|
||||
g.Description := legend;
|
||||
chart.LegendVisibility := Visibility.Visible;
|
||||
end;
|
||||
|
||||
AddSeries(chart, g);
|
||||
end;
|
||||
|
||||
static procedure Plot.DrawPoints(chart: ChartWPF; x, y: array of real;
|
||||
color: ColorWPF; size: real; marker: MarkerType; legend: string);
|
||||
begin
|
||||
var g := CreatePointSeries(x, y, color, size, marker);
|
||||
|
||||
if legend <> nil then
|
||||
begin
|
||||
g.Description := legend;
|
||||
chart.LegendVisibility := Visibility.Visible;
|
||||
end;
|
||||
|
||||
AddSeries(chart, g);
|
||||
end;
|
||||
|
||||
static procedure Plot.DrawHeatmap(chart: ChartWPF; m: array[,] of real);
|
||||
begin
|
||||
var rows := m.GetLength(0);
|
||||
var cols := m.GetLength(1);
|
||||
|
||||
var x := ArrGen(cols, i -> i);
|
||||
var y := ArrGen(rows, i -> i);
|
||||
|
||||
var g := new HeatmapGraph;
|
||||
|
||||
g.Plot(m, x, y);
|
||||
|
||||
AddSeries(chart, g);
|
||||
end;
|
||||
|
||||
class procedure Plot.AddSeries(chart: ChartWPF; series: UIElement);
|
||||
begin
|
||||
var container := chart.Content as GridWPF;
|
||||
|
||||
if container = nil then
|
||||
begin
|
||||
container := new GridWPF;
|
||||
chart.Content := container;
|
||||
end;
|
||||
|
||||
container.Children.Add(series);
|
||||
end;
|
||||
|
||||
class procedure Plot.LineGraph(x, y: array of real;
|
||||
color: ColorWPF?; thickness: real; legend: string);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
var clr := if color.HasValue then color.Value else NextRootColor;
|
||||
|
||||
DrawLine(rootChart, x, y, clr, thickness, legend);
|
||||
end);
|
||||
end;
|
||||
|
||||
static procedure Plot.Points(x, y: array of real;
|
||||
color: ColorWPF?; size: real; marker: MarkerType; legend: string);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
var clr := if color.HasValue then color.Value else NextRootColor;
|
||||
|
||||
DrawPoints(rootChart, x, y, clr, size, marker, legend);
|
||||
end);
|
||||
end;
|
||||
|
||||
static procedure Plot.Heatmap(m: array[,] of real);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
DrawHeatmap(rootChart, m);
|
||||
end);
|
||||
end;
|
||||
|
||||
static function Plot.Grid(rows,cols: integer): Figure;
|
||||
begin
|
||||
var fig: Figure;
|
||||
|
||||
RunUI(() ->
|
||||
begin
|
||||
fig := new Figure(rows,cols);
|
||||
win.Content := fig.grid;
|
||||
end);
|
||||
|
||||
Result := fig;
|
||||
end;
|
||||
|
||||
static procedure Plot.SetPalette(name: string);
|
||||
begin
|
||||
RunUI(() ->
|
||||
begin
|
||||
if name in paletteDict then
|
||||
currentPalette := paletteDict[name];
|
||||
end);
|
||||
end;
|
||||
|
||||
initialization
|
||||
InitUI;
|
||||
|
||||
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);
|
||||
|
|
@ -56,11 +56,13 @@ type
|
|||
function Transform(df: DataFrame): DataFrame;
|
||||
/// Выполняет Fit и Transform последовательно
|
||||
function FitTransform(df: DataFrame): DataFrame;
|
||||
|
||||
function ToString: string; override;
|
||||
end;
|
||||
|
||||
/// Кодирует строковый категориальный столбец в набор бинарных (one-hot) столбцов
|
||||
/// Категории фиксируются при Fit
|
||||
/// Неизвестные категории приводят к ошибке
|
||||
/// Неизвестные категории кодируются нулями
|
||||
/// Пропущенные значения (NA) кодируются нулями
|
||||
OneHotEncoder = class(IPreprocessor)
|
||||
private
|
||||
|
|
@ -79,6 +81,8 @@ type
|
|||
function Transform(df: DataFrame): DataFrame;
|
||||
/// Выполняет Fit и Transform последовательно
|
||||
function FitTransform(df: DataFrame): DataFrame;
|
||||
|
||||
function ToString: string; override;
|
||||
end;
|
||||
|
||||
ImputeStrategy = (isMean, isConstant);
|
||||
|
|
@ -94,10 +98,12 @@ type
|
|||
means: array of real;
|
||||
fitted: boolean;
|
||||
public
|
||||
/// Создаёт Imputer с заполнением средним значением
|
||||
constructor Create(params columns: array of string);
|
||||
/// Создаёт Imputer с заданной стратегией заполнения
|
||||
constructor Create(strategy: ImputeStrategy; params columns: array of string);
|
||||
/// Создаёт Imputer с константной стратегией заполнения
|
||||
constructor Create(strategy: ImputeStrategy; value: object; params columns: array of string);
|
||||
constructor Create(value: object; params columns: array of string);
|
||||
|
||||
/// Вычисляет значения для заполнения пропусков
|
||||
function Fit(df: DataFrame): IPreprocessor;
|
||||
|
|
@ -106,6 +112,8 @@ type
|
|||
function Transform(df: DataFrame): DataFrame;
|
||||
/// Выполняет Fit и Transform последовательно
|
||||
function FitTransform(df: DataFrame): DataFrame;
|
||||
|
||||
function ToString: string; override;
|
||||
end;
|
||||
|
||||
|
||||
|
|
@ -171,193 +179,11 @@ const
|
|||
ER_IMPUTER_CONSTANT_TYPE_MISMATCH =
|
||||
'Imputer(constant): несоответствие типа значения для столбца "{0}"!!' +
|
||||
'Imputer(constant): value type mismatch for column "{0}"';
|
||||
ER_ONEHOT_EMPTY_COLUMN =
|
||||
'Столбец "{0}" не содержит категориальных значений!!Column "{0}" contains no categorical values';
|
||||
ER_COLUMN_NOT_FOUND =
|
||||
'Столбец "{0}" не найден!!Column "{0}" not found';
|
||||
|
||||
//-----------------------------
|
||||
// StandardScaler
|
||||
//-----------------------------
|
||||
|
||||
{constructor DataStandardScaler.Create(params columns: array of string);
|
||||
begin
|
||||
if (columns = nil) or (columns.Length = 0) then
|
||||
ArgumentError(ER_SCALER_NO_COLUMNS);
|
||||
|
||||
cols := columns;
|
||||
fitted := false;
|
||||
end;
|
||||
|
||||
function DataStandardScaler.Fit(df: DataFrame): IPreprocessor;
|
||||
begin
|
||||
var n := cols.Length;
|
||||
SetLength(means, n);
|
||||
SetLength(stds, n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var colName := cols[i];
|
||||
var idx := df.Schema.IndexOf(colName);
|
||||
|
||||
var ct := df.Schema.ColumnTypeAt(idx);
|
||||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_SCALER_COLUMN_NOT_NUMERIC, colName);
|
||||
|
||||
var sum := 0.0;
|
||||
var sum2 := 0.0;
|
||||
var cnt := 0;
|
||||
|
||||
var cur := df.GetCursor;
|
||||
while cur.MoveNext do
|
||||
begin
|
||||
if not cur.IsValid(idx) then continue;
|
||||
var x := cur.Float(idx);
|
||||
sum += x;
|
||||
sum2 += x * x;
|
||||
cnt += 1;
|
||||
end;
|
||||
|
||||
if cnt = 0 then
|
||||
Error(ER_SCALER_NO_VALID_VALUES, colName);
|
||||
|
||||
means[i] := sum / cnt;
|
||||
var v := sum2 / cnt - means[i] * means[i];
|
||||
stds[i] := Sqrt(v);
|
||||
|
||||
if stds[i] = 0 then
|
||||
Error(ER_SCALER_ZERO_VARIANCE, colName);
|
||||
end;
|
||||
|
||||
fitted := true;
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function DataStandardScaler.Transform(df: DataFrame): DataFrame;
|
||||
begin
|
||||
if not fitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
var res := df;
|
||||
|
||||
for var i := 0 to cols.Length - 1 do
|
||||
begin
|
||||
var colName := cols[i];
|
||||
var idx := df.Schema.IndexOf(colName);
|
||||
var mean := means[i];
|
||||
var std := stds[i];
|
||||
|
||||
res := res.ReplaceColumnFloat(
|
||||
colName,
|
||||
c ->
|
||||
(if c.IsValid(idx)
|
||||
then (c.Float(idx) - mean) / std
|
||||
else real.NaN)
|
||||
);
|
||||
end;
|
||||
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
function DataStandardScaler.FitTransform(df: DataFrame): DataFrame;
|
||||
begin
|
||||
Fit(df);
|
||||
Result := Transform(df);
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// MinMaxScaler
|
||||
//-----------------------------
|
||||
|
||||
constructor DataMinMaxScaler.Create(params columns: array of string);
|
||||
begin
|
||||
if (columns = nil) or (columns.Length = 0) then
|
||||
ArgumentError(ER_MINMAX_NO_COLUMNS);
|
||||
|
||||
cols := columns;
|
||||
fitted := false;
|
||||
end;
|
||||
|
||||
function DataMinMaxScaler.Fit(df: DataFrame): IPreprocessor;
|
||||
begin
|
||||
var n := cols.Length;
|
||||
SetLength(mins, n);
|
||||
SetLength(maxs, n);
|
||||
|
||||
for var i := 0 to n - 1 do
|
||||
begin
|
||||
var colName := cols[i];
|
||||
var idx := df.Schema.IndexOf(colName);
|
||||
|
||||
var ct := df.Schema.ColumnTypeAt(idx);
|
||||
if not (ct in [ColumnType.ctInt, ColumnType.ctFloat]) then
|
||||
Error(ER_MINMAX_COLUMN_NOT_NUMERIC, colName);
|
||||
|
||||
var first := true;
|
||||
var minv, maxv: real;
|
||||
|
||||
var cur := df.GetCursor;
|
||||
while cur.MoveNext do
|
||||
begin
|
||||
if not cur.IsValid(idx) then continue;
|
||||
var x := cur.Float(idx);
|
||||
|
||||
if first then
|
||||
begin
|
||||
minv := x;
|
||||
maxv := x;
|
||||
first := false;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if x < minv then minv := x;
|
||||
if x > maxv then maxv := x;
|
||||
end;
|
||||
end;
|
||||
|
||||
if first then
|
||||
Error(ER_MINMAX_NO_VALID_VALUES, colName);
|
||||
|
||||
if minv = maxv then
|
||||
Error(ER_MINMAX_CONSTANT_COLUMN, colName);
|
||||
|
||||
mins[i] := minv;
|
||||
maxs[i] := maxv;
|
||||
end;
|
||||
|
||||
fitted := true;
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function DataMinMaxScaler.Transform(df: DataFrame): DataFrame;
|
||||
begin
|
||||
if not fitted then
|
||||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
var res := df;
|
||||
|
||||
for var i := 0 to cols.Length - 1 do
|
||||
begin
|
||||
var colName := cols[i];
|
||||
var idx := df.Schema.IndexOf(colName);
|
||||
var minv := mins[i];
|
||||
var maxv := maxs[i];
|
||||
var scale := maxv - minv;
|
||||
|
||||
res := res.ReplaceColumnFloat(
|
||||
colName,
|
||||
c ->
|
||||
(if c.IsValid(idx)
|
||||
then (c.Float(idx) - minv) / scale
|
||||
else real.NaN)
|
||||
);
|
||||
end;
|
||||
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
function DataMinMaxScaler.FitTransform(df: DataFrame): DataFrame;
|
||||
begin
|
||||
Fit(df);
|
||||
Result := Transform(df);
|
||||
end;}
|
||||
|
||||
//-----------------------------
|
||||
// LabelEncoder
|
||||
//-----------------------------
|
||||
|
|
@ -427,6 +253,11 @@ begin
|
|||
Result := Transform(df);
|
||||
end;
|
||||
|
||||
function LabelEncoder.ToString: string;
|
||||
begin
|
||||
Result := 'LabelEncoder(' + col + ')';
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// OneHotEncoder
|
||||
//-----------------------------
|
||||
|
|
@ -459,6 +290,9 @@ begin
|
|||
values.Add(s);
|
||||
end;
|
||||
end;
|
||||
|
||||
if values.Count = 0 then
|
||||
Error(ER_ONEHOT_EMPTY_COLUMN, col);
|
||||
|
||||
categories := values.ToArray;
|
||||
fitted := true;
|
||||
|
|
@ -471,22 +305,14 @@ begin
|
|||
NotFittedError(ER_FIT_NOT_CALLED);
|
||||
|
||||
var srcIdx := df.Schema.IndexOf(col);
|
||||
if srcIdx < 0 then
|
||||
ArgumentError(ER_COLUMN_NOT_FOUND, col);
|
||||
|
||||
var catCount := categories.Length;
|
||||
|
||||
// === ШАГ 1. Проверка на unseen категории ===
|
||||
var cur := df.GetCursor;
|
||||
while cur.MoveNext do
|
||||
begin
|
||||
if not cur.IsValid(srcIdx) then continue;
|
||||
|
||||
var s := cur.Str(srcIdx);
|
||||
if not indexByValue.ContainsKey(s) then
|
||||
Error(ER_ONEHOT_UNSEEN_CATEGORY, s);
|
||||
end;
|
||||
|
||||
// === ШАГ 2. Генерация one-hot столбцов ===
|
||||
var res := df;
|
||||
|
||||
// === Генерация one-hot столбцов ===
|
||||
for var j := 0 to catCount - 1 do
|
||||
begin
|
||||
var catIdx := j;
|
||||
|
|
@ -500,7 +326,17 @@ begin
|
|||
exit;
|
||||
end;
|
||||
|
||||
if indexByValue[c.Str(srcIdx)] = catIdx then
|
||||
var s := c.Str(srcIdx);
|
||||
|
||||
var idx: integer;
|
||||
if not indexByValue.TryGetValue(s, idx) then
|
||||
begin
|
||||
// неизвестная категория → все нули
|
||||
Result := 0;
|
||||
exit;
|
||||
end;
|
||||
|
||||
if idx = catIdx then
|
||||
Result := 1
|
||||
else
|
||||
Result := 0;
|
||||
|
|
@ -509,20 +345,23 @@ begin
|
|||
res := res.AddDerivedIntColumn(newName, Encode);
|
||||
end;
|
||||
|
||||
// === ШАГ 3. Удаление исходного столбца ===
|
||||
// === удаление исходного столбца ===
|
||||
res := res.Drop([srcIdx]);
|
||||
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
|
||||
|
||||
function OneHotEncoder.FitTransform(df: DataFrame): DataFrame;
|
||||
begin
|
||||
Fit(df);
|
||||
Result := Transform(df);
|
||||
end;
|
||||
|
||||
function OneHotEncoder.ToString: string;
|
||||
begin
|
||||
Result := 'OneHotEncoder(column=' + col + ')';
|
||||
end;
|
||||
|
||||
//-----------------------------
|
||||
// Imputer
|
||||
//-----------------------------
|
||||
|
|
@ -541,15 +380,17 @@ begin
|
|||
fitted := false;
|
||||
end;
|
||||
|
||||
constructor Imputer.Create(strategy: ImputeStrategy; value: object; params columns: array of string);
|
||||
constructor Imputer.Create(params columns: array of string);
|
||||
begin
|
||||
if strategy <> isConstant then
|
||||
ArgumentError(ER_IMPUTER_INVALID_STRATEGY_CONSTANT);
|
||||
Create(ImputeStrategy.isMean, columns);
|
||||
end;
|
||||
|
||||
constructor Imputer.Create(value: object; params columns: array of string);
|
||||
begin
|
||||
if (columns = nil) or (columns.Length = 0) then
|
||||
ArgumentError(ER_IMPUTER_NO_COLUMNS);
|
||||
|
||||
self.strategy := strategy;
|
||||
self.strategy := ImputeStrategy.isConstant;
|
||||
self.cols := columns;
|
||||
|
||||
// одна и та же константа для всех столбцов
|
||||
|
|
@ -669,5 +510,18 @@ begin
|
|||
Result := Transform(df);
|
||||
end;
|
||||
|
||||
function Imputer.ToString: string;
|
||||
begin
|
||||
var colsStr := '[' + cols.JoinToString(', ') + ']';
|
||||
|
||||
case strategy of
|
||||
isMean:
|
||||
Result := 'Imputer(strategy=mean, columns=' + colsStr + ')';
|
||||
|
||||
isConstant:
|
||||
Result := 'Imputer(strategy=constant, value=' +
|
||||
constants[0].ToString + ', columns=' + colsStr + ')';
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
Loading…
Reference in a new issue