Ряд исправлений в ML библиотеках

This commit is contained in:
Mikhalkovich Stanislav 2026-05-20 21:25:53 +03:00
parent 9e36ff2abc
commit 728852276d
16 changed files with 1851 additions and 169 deletions

View file

@ -15,7 +15,7 @@ internal static class RevisionClass
public const string Major = "3";
public const string Minor = "11";
public const string Build = "1";
public const string Revision = "3821";
public const string Revision = "3823";
public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;

View file

@ -1,4 +1,4 @@
%MINOR%=11
%REVISION%=3821
%REVISION%=3823
%COREVERSION%=1
%MAJOR%=3

View file

@ -1 +1 @@
3.11.1.3821
3.11.1.3823

View file

@ -1,4 +1,4 @@
name = TitanicRu
name = TitanicRu
task = classification
@ -56,9 +56,14 @@ value.Класс.1.en = first class
value.Класс.2.en = second class
value.Класс.3.en = third class
value.Пол.муж.ru = муж
value.Пол.жен.ru = жен
value.Пол.муж.en = male
value.Пол.жен.en = female
value.ПортПосадки.Саутгемптон.ru = Саутгемптон
value.ПортПосадки.Шербур.ru = Шербур
value.ПортПосадки.Квинстаун.ru = Квинстаун
value.ПортПосадки.Саутгемптон.en = Southampton
value.ПортПосадки.Шербур.en = Cherbourg
value.ПортПосадки.Квинстаун.en = Queenstown

View file

@ -1 +1 @@
!define VERSION '3.11.1.3821'
!define VERSION '3.11.1.3823'

View file

@ -39,15 +39,39 @@ begin
for var j := 0 to p - 1 do
begin
var col := df[colNames[j]];
for var i := 0 to n - 1 do
begin
var value: real;
if not col.TryGetNumericValue(i, value) then
case col.Info.ColType of
ColumnType.ctInt:
begin
var c := IntColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
Result[i,j] := c.Data[i];
end;
end;
ColumnType.ctFloat:
begin
var c := FloatColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
Result[i,j] := c.Data[i];
end;
end;
ColumnType.ctBool:
begin
var c := BoolColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
Result[i,j] := if c.Data[i] then 1.0 else 0.0;
end;
end;
else
ArgumentError(ER_TO_MATRIX_NON_NUMERIC, colNames[j]);
Result[i,j] := value;
end;
end;
end;
@ -59,15 +83,39 @@ begin
Result := new Vector(n);
var col := df[colName];
for var i := 0 to n - 1 do
begin
var value: real;
if not col.TryGetNumericValue(i, value) then
case col.Info.ColType of
ColumnType.ctInt:
begin
var c := IntColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
Result[i] := c.Data[i];
end;
end;
ColumnType.ctFloat:
begin
var c := FloatColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
Result[i] := c.Data[i];
end;
end;
ColumnType.ctBool:
begin
var c := BoolColumn(col);
for var i := 0 to n - 1 do
begin
if not c.IsValid[i] then
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
Result[i] := if c.Data[i] then 1.0 else 0.0;
end;
end;
else
ArgumentError(ER_TO_VECTOR_NON_NUMERIC, colName);
Result[i] := value;
end;
end;

View file

@ -47,7 +47,6 @@ type
columns: List<Column>;
fschema: DataFrameSchema;
procedure RebuildSchema;
procedure CommitAddedColumn(c: Column);
procedure ValidateColumnsAgainstSchema;
procedure ValidateColumnsAgainstSchema(candidate: DataFrameSchema);
@ -194,19 +193,24 @@ type
/// Не изменяйте их после передачи в DataFrame.
procedure AddBoolColumn(name: string; data: array of boolean; valid: array of boolean := nil);
/// Возвращает внутренний буфер целочисленного столбца без копирования.
/// Не изменяйте его, иначе DataFrame будет повреждён.
/// Возвращает целочисленный массив значений столбца с данным именем.
function GetIntColumn(name: string): array of integer;
/// Возвращает внутренний буфер вещественного столбца без копирования.
/// Не изменяйте его, иначе DataFrame будет повреждён.
/// Возвращает вещественный массив значений столбца с данным именем.
function GetFloatColumn(name: string): array of real;
/// Возвращает внутренний буфер строкового столбца без копирования.
/// Не изменяйте его, иначе DataFrame будет повреждён.
/// Возвращает строковый массив значений столбца с данным именем.
function GetStrColumn(name: string): array of string;
/// Возвращает внутренний буфер логического столбца без копирования.
/// Не изменяйте его, иначе DataFrame будет повреждён.
/// Возвращает логический массив значений столбца с данным именем.
function GetBoolColumn(name: string): array of boolean;
/// Возвращает целочисленный массив значений столбца с данным именем.
function Int(name: string): array of integer;
/// Возвращает вещественный массив значений столбца с данным именем.
function Float(name: string): array of real;
/// Возвращает строковый массив значений столбца с данным именем.
function Str(name: string): array of string;
/// Возвращает логический массив значений столбца с данным именем.
function Bool(name: string): array of boolean;
/// Вычисляет сумму значений столбца по индексу
function Sum(colIndex: integer): real;
/// Вычисляет сумму значений столбца по имени
@ -2170,29 +2174,6 @@ begin
Result.AssertSchemaConsistent;
end;
procedure DataFrame.RebuildSchema;
begin
var n := columns.Count;
var names := new string[n];
var types := new ColumnType[n];
var cats := new boolean[n];
for var i := 0 to n - 1 do
begin
var info := columns[i].Info;
names[i] := info.Name;
types[i] := info.ColType;
// берем из старой schema
if (fSchema <> nil) and (i < fSchema.ColumnCount) then
cats[i] := fSchema.CategoricalFlags[i]
else
cats[i] := false;
end;
fSchema := new DataFrameSchema(names, types, cats);
end;
procedure DataFrame.CommitAddedColumn(c: Column);
begin
var n := columns.Count;
@ -2206,7 +2187,7 @@ begin
names[i] := info.Name;
types[i] := info.ColType;
if fSchema <> nil then
cats[i] := fSchema.CategoricalFlags[i]
cats[i] := fSchema.IsCategoricalAt(i)
else
cats[i] := false;
end;
@ -2322,6 +2303,26 @@ begin
Result := c.Data;
end;
function DataFrame.Int(name: string): array of integer;
begin
Result := GetIntColumn(name);
end;
function DataFrame.Float(name: string): array of real;
begin
Result := GetFloatColumn(name);
end;
function DataFrame.Str(name: string): array of string;
begin
Result := GetStrColumn(name);
end;
function DataFrame.Bool(name: string): array of boolean;
begin
Result := GetBoolColumn(name);
end;
function DataFrame.TrainTestSplit(testRatio: real; shuffle: boolean; seed: integer): (DataFrame, DataFrame);
begin
if Self = nil then
@ -2595,7 +2596,7 @@ function DataFrame.Mean(colName: string): real
function DataFrame.Median(colIndex: integer): real;
begin
CheckColumnIndex(colIndex);
Result := Statistics.Median(Self, fSchema.ColumnNames[colIndex]);
Result := Statistics.Median(Self, fSchema.NameAt(colIndex));
end;
function DataFrame.Median(colName: string): real;
@ -3086,7 +3087,7 @@ begin
// копируем старые значения
for var i := 0 to n - 1 do
cats[i] := fSchema.CategoricalFlags[i];
cats[i] := fSchema.IsCategoricalAt(i);
// применяем новые
foreach var name in names do
@ -3207,7 +3208,7 @@ begin
// КЛЮЧЕВОЕ: перенос schema
var cats := new boolean[ColumnCount];
for var i := 0 to ColumnCount - 1 do
cats[i] := fSchema.CategoricalFlags[i];
cats[i] := fSchema.IsCategoricalAt(i);
res.SetSchema(new DataFrameSchema(
fSchema.ColumnNames,
@ -3311,12 +3312,12 @@ begin
for var i := 0 to n - 1 do
begin
var oldName := fSchema.ColumnNames[i];
var oldName := fSchema.NameAt(i);
var newName := if map.ContainsKey(oldName) then map[oldName] else oldName;
names[i] := newName;
types[i] := fSchema.Types[i];
cats[i] := fSchema.CategoricalFlags[i];
types[i] := fSchema.ColumnTypeAt(i);
cats[i] := fSchema.IsCategoricalAt(i);
end;
res.SetSchema(new DataFrameSchema(names, types, cats));
@ -3565,9 +3566,9 @@ begin
for var j := 0 to oldN - 1 do
begin
names[j] := fSchema.ColumnNames[j];
types[j] := fSchema.Types[j];
cats[j] := fSchema.CategoricalFlags[j];
names[j] := fSchema.NameAt(j);
types[j] := fSchema.ColumnTypeAt(j);
cats[j] := fSchema.IsCategoricalAt(j);
end;
names[oldN] := name;
@ -4260,7 +4261,7 @@ begin
for var i := 0 to ColumnCount - 1 do
begin
var name := fSchema.ColumnNames[i].PadRight(nameWidth);
var name := fSchema.NameAt(i).PadRight(nameWidth);
var typ := GetColumnType(i).ToString.Replace('ct','').PadRight(typeWidth);
PABCSystem.Println($'{name} : {typ}');
end;
@ -4290,7 +4291,7 @@ begin
for var i := 0 to ColumnCount - 1 do
begin
var t := ColumnTypeToString(GetColumnType(i));
if fSchema.CategoricalFlags[i] then
if fSchema.IsCategoricalAt(i) then
t += ' (categorical)';
types[i] := t;
@ -4305,7 +4306,7 @@ begin
for var i := 0 to ColumnCount - 1 do
begin
var name := fSchema.ColumnNames[i].PadRight(nameWidth);
var name := fSchema.NameAt(i).PadRight(nameWidth);
var typ := types[i].PadRight(maxTypeWidth);
var cnt := Count(i);
@ -4428,15 +4429,15 @@ begin
for var i := 0 to n - 1 do
begin
var name := fSchema.ColumnNames[i];
var name := fSchema.NameAt(i);
namesArr[i] := name;
cats[i] := fSchema.CategoricalFlags[i];
cats[i] := fSchema.IsCategoricalAt(i);
if toCast.Contains(name) then
types[i] := ctInt
else
types[i] := fSchema.Types[i];
types[i] := fSchema.ColumnTypeAt(i);
end;
res.SetSchema(new DataFrameSchema(namesArr, types, cats));
@ -5538,7 +5539,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.ColumnNames[i]);
names.Add(df.fSchema.NameAt(i));
var n := names.Count;
if n = 0 then
@ -5626,7 +5627,7 @@ begin
means[i] := mean;
stds[i] := Sqrt(variance);
if stds[i] = 0 then
Error(ER_ZERO_STD_COLUMN, df.fSchema.ColumnNames[i]);
Error(ER_ZERO_STD_COLUMN, df.fSchema.NameAt(i));
isNumeric[i] := true;
end;
end;
@ -5635,7 +5636,7 @@ begin
for var i := 0 to df.ColumnCount - 1 do
begin
if isNumeric[i] then
res.AddFloatColumn(df.fSchema.ColumnNames[i], new real[df.RowCount], nil)
res.AddFloatColumn(df.fSchema.NameAt(i), new real[df.RowCount], nil)
else
res.AddColumnAlias(df.GetColumn(i));
end;
@ -5708,7 +5709,7 @@ begin
begin
var (mn, mx) := df.MinMax(i);
if mn = mx then
Error(ER_ZERO_RANGE_COLUMN, df.fSchema.ColumnNames[i]);
Error(ER_ZERO_RANGE_COLUMN, df.fSchema.NameAt(i));
mins[i] := mn;
maxs[i] := mx;
isNumeric[i] := true;
@ -5719,7 +5720,7 @@ begin
for var i := 0 to df.ColumnCount - 1 do
begin
if isNumeric[i] then
res.AddFloatColumn(df.fSchema.ColumnNames[i], new real[df.RowCount], nil)
res.AddFloatColumn(df.fSchema.NameAt(i), new real[df.RowCount], nil)
else
res.AddColumnAlias(df.GetColumn(i));
end;

View file

@ -39,36 +39,55 @@ type
function GetTypes: array of ColumnType;
function GetCategoricalFlags: array of boolean;
public
/// Возвращает количество столбцов в схеме
property ColumnCount: integer read fNames.Length;
/// Возвращает копию массива имён столбцов
property ColumnNames: array of string read GetColumnNames;
/// Возвращает копию массива типов столбцов
property Types: array of ColumnType read GetTypes;
/// Возвращает копию массива categorical-флагов
property CategoricalFlags: array of boolean read GetCategoricalFlags;
/// Возвращает индекс столбца по имени
function IndexOf(name: string): integer;
/// Проверяет наличие столбца с указанным именем
function HasColumn(name: string): boolean;
/// Возвращает тип столбца по индексу
function ColumnTypeAt(i: integer): ColumnType;
/// Проверяет categorical-флаг столбца по индексу
function IsCategoricalAt(i: integer): boolean;
/// Возвращает тип столбца по имени
function GetColumnType(name: string): ColumnType;
/// Проверяет categorical-флаг столбца по имени
function IsCategorical(name: string): boolean;
/// Возвращает имя столбца по индексу
function NameAt(i: integer): string;
/// Создаёт схему по именам, типам и categorical-флагам
constructor Create(names: array of string; types: array of ColumnType;
isCategorical: array of boolean := nil);
/// Печатает схему без перевода строки в конце
procedure Print;
/// Печатает схему и переводит строку
procedure Println;
{ --- schema operations (immutable) --- }
/// Возвращает схему, содержащую только указанные столбцы
function Select(indices: array of integer): DataFrameSchema;
/// Возвращает схему без указанных столбцов
function Drop(indices: array of integer): DataFrameSchema;
/// Возвращает схему с переименованным столбцом
function Rename(oldName, newName: string): DataFrameSchema;
/// Возвращает схему с изменённым categorical-флагом столбца
function WithCategorical(name: string; value: boolean := True): DataFrameSchema;
{ --- join helpers --- }
/// Объединяет две схемы по правилам Join
class function Merge(
left, right: DataFrameSchema;
leftKeys, rightKeys: array of integer;

View file

@ -71,6 +71,7 @@ type
DataFrame = DataFrameABC.DataFrame;
DataFrameCursor = DataFrameABCCore.DataFrameCursor;
ColumnType = DataFrameABCCore.ColumnType;
Column = DataFrameABCCore.Column;
Statistics = DataFrameABC.Statistics;
CsvLoader = DataFrameABC.CsvLoader;
@ -127,6 +128,7 @@ type
Datasets = MLDatasets.Datasets;
Dataset = MLDatasets.Dataset;
LabelEncoder = MLDatasets.LabelEncoder;
IModel = MLCoreABC.IModel;
ISupervisedModel = MLCoreABC.ISupervisedModel;

View file

@ -158,6 +158,7 @@ type
/// как признаков X, так и целевой переменной y.
/// Запоминает необходимые параметры, которые будут использоваться при Transform.
function Fit(X: Matrix; y: Vector): ISupervisedTransformer;
function FitTransform(X: Matrix; y: Vector): Matrix;
end;
/// Интерфейс преобразования признаков без учёта целевой переменной.
@ -167,6 +168,7 @@ type
/// Обучает преобразование на данных с использованием признаков X
/// Запоминает необходимые параметры, которые будут использоваться при Transform.
function Fit(X: Matrix): IUnsupervisedTransformer;
function FitTransform(X: Matrix): Matrix;
end;
IColumnExpander = interface

View file

@ -21,6 +21,7 @@ type
private
function ValueLabel(feature, value: string): string;
function CloneMeta(df: DataFrame): Dataset;
function GetCategoricalFeatures: array of string;
public
FeatureLabels: Dictionary<string,string>;
ValueLabels: Dictionary<string,Dictionary<string,string>>;
@ -65,10 +66,34 @@ type
function RowCount: integer := Data.RowCount;
function GetFeatureColumns: array of string;
property CategoricalFeatures: array of string read GetCategoricalFeatures;
function HasTarget: boolean;
end;
/// Кодирует target-колонку классификационного Dataset в числовой Vector.
/// Используется только для целевой переменной, не для признаков.
LabelEncoder = class
private
fClasses: array of string;
fClassToIndex: Dictionary<string, integer>;
function GetClasses: array of string;
procedure EnsureFitted;
procedure CheckDataset(ds: Dataset);
function TargetLabels(ds: Dataset): array of string;
public
function Fit(ds: Dataset): LabelEncoder;
function Transform(ds: Dataset): Vector;
function FitTransform(ds: Dataset): Vector;
property Classes: array of string read GetClasses;
function ClassName(index: integer): string;
function ClassIndex(name: string): integer;
function Decode(y: Vector): array of string;
end;
/// Набор генераторов и загрузчиков датасетов для задач машинного обучения.
/// Содержит синтетические генераторы (MakeBlobs, MakeMoons, MakeRegression)
/// и реальные учебные датасеты (например, RussianHousing, StudentExam).
@ -263,6 +288,10 @@ type
/// Датасет пассажиров Титаника (задача классификации)
static function TitanicRu: Dataset;
/// Датасет цен на автомобили с пробегом (задача регрессии)
static function UsedCarsPrice: Dataset;
{/// Датасет результатов экзамена студентов (классификация)
static function StudentExam: Dataset;
@ -340,6 +369,13 @@ const
ER_ENCODELABELS_UNSUPPORTED_TYPE =
'Неподдерживаемый тип столбца для кодирования меток: {0}!!' +
'Unsupported column type for label encoding: {0}';
ER_LABEL_ENCODER_NOT_FITTED =
'LabelEncoder не обучен. Сначала вызовите Fit или FitTransform.!!' +
'LabelEncoder is not fitted. Call Fit or FitTransform first.';
ER_LABEL_ENCODER_UNKNOWN_CLASS =
'Неизвестная метка класса: {0}!!Unknown class label: {0}';
ER_LABEL_ENCODER_INDEX_OUT_OF_RANGE =
'Индекс класса вне диапазона: {0}!!Class index out of range: {0}';
C_DATASET = 'Датасет: {0}!!Dataset: {0}';
C_DESCRIPTION = 'Описание:!!Description:';
@ -352,6 +388,7 @@ const
C_URL = 'Ссылка: {0}!!URL: {0}';
C_CLASSES = 'Классов: {0}!!Classes: {0}';
C_FEATURE_LIST = 'Признаки:!!Features:';
C_CATEGORICAL = 'Категориальные признаки: {0}!!Categorical features: {0}';
function Normal(rnd: System.Random): real;
begin
@ -509,6 +546,9 @@ begin
if (Target <> nil) and (Target <> '') then
PrintlnTr(C_TARGET, Target);
if CategoricalFeatures.Length > 0 then
PrintlnTr(C_CATEGORICAL, CategoricalFeatures.JoinToString(', '));
Println;
var maxLen := Features.Max(f -> f.Length);
@ -604,12 +644,153 @@ begin
Result := Data.Schema.ColumnNames;
end;
function Dataset.GetCategoricalFeatures: array of string;
begin
Result := Features
.Where(f -> Data.IsCategorical(f))
.ToArray;
end;
function Dataset.HasTarget: boolean;
begin
Result := (Target <> nil) and (Target <> '');
end;
//-----------------------------
// LabelEncoder
//-----------------------------
function LabelEncoder.GetClasses: array of string;
begin
EnsureFitted;
Result := Copy(fClasses);
end;
procedure LabelEncoder.EnsureFitted;
begin
if (fClasses = nil) or (fClassToIndex = nil) then
Error(ER_LABEL_ENCODER_NOT_FITTED);
end;
procedure LabelEncoder.CheckDataset(ds: Dataset);
begin
if ds = nil then
ArgumentNullError(ER_ARG_NULL, 'ds');
if ds.Data = nil then
ArgumentNullError(ER_ARG_NULL, 'Data');
if ds.Task <> TaskType.Classification then
ArgumentError(ER_CLASSES_ONLY_CLASSIFICATION);
if not ds.HasTarget then
ArgumentError(ER_DATASET_TARGET_MISSING);
if not ds.Data.HasColumn(ds.Target) then
ArgumentError(ER_DATASET_TARGET_NOT_FOUND, ds.Target);
end;
function LabelEncoder.TargetLabels(ds: Dataset): array of string;
begin
CheckDataset(ds);
case ds.Data.GetColumnType(ds.Target) of
ColumnType.ctStr:
Result := ds.Data.GetStrColumn(ds.Target);
ColumnType.ctInt:
Result := ds.Data.GetIntColumn(ds.Target).Select(x -> x.ToString).ToArray;
else
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, ds.Target);
end;
end;
function LabelEncoder.Fit(ds: Dataset): LabelEncoder;
begin
var labels := TargetLabels(ds);
var classes := new List<string>;
fClassToIndex := new Dictionary<string, integer>;
foreach var labelName in labels do
if not fClassToIndex.ContainsKey(labelName) then
begin
fClassToIndex[labelName] := classes.Count;
classes.Add(labelName);
end;
fClasses := classes.ToArray;
Result := self;
end;
function LabelEncoder.Transform(ds: Dataset): Vector;
begin
EnsureFitted;
var labels := TargetLabels(ds);
var y := new integer[labels.Length];
for var i := 0 to labels.Length - 1 do
begin
var labelName := labels[i];
if not fClassToIndex.ContainsKey(labelName) then
ArgumentError(ER_LABEL_ENCODER_UNKNOWN_CLASS, labelName);
y[i] := fClassToIndex[labelName];
end;
Result := new Vector(y);
end;
function LabelEncoder.FitTransform(ds: Dataset): Vector;
begin
Fit(ds);
Result := Transform(ds);
end;
function LabelEncoder.ClassName(index: integer): string;
begin
EnsureFitted;
if (index < 0) or (index >= fClasses.Length) then
ArgumentError(ER_LABEL_ENCODER_INDEX_OUT_OF_RANGE, index);
Result := fClasses[index];
end;
function LabelEncoder.ClassIndex(name: string): integer;
begin
EnsureFitted;
if not fClassToIndex.ContainsKey(name) then
ArgumentError(ER_LABEL_ENCODER_UNKNOWN_CLASS, name);
Result := fClassToIndex[name];
end;
function LabelEncoder.Decode(y: Vector): array of string;
begin
EnsureFitted;
if y = nil then
ArgumentNullError(ER_ARG_NULL, 'y');
Result := new string[y.Length];
for var i := 0 to y.Length - 1 do
begin
var value := y.Data[i];
var index := Round(value);
if (Abs(value - index) > 1e-12) or (index < 0) or (index >= fClasses.Length) then
ArgumentError(ER_LABEL_ENCODER_INDEX_OUT_OF_RANGE, index);
Result[i] := fClasses[index];
end;
end;
//-----------------------------
// Datasets
//-----------------------------
@ -1174,6 +1355,18 @@ begin
ArgumentError(ER_DATASET_TASK_MISSING, name);
var df := DataFrame.FromCsv(csvPath);
var categoricalCols := new List<string>;
foreach var k in meta.Keys do
if k.StartsWith('feature.') and not k.EndsWith('.ru') and not k.EndsWith('.en') then
begin
var colName := k.Substring('feature.'.Length);
if (meta[k] = 'categorical') and df.HasColumn(colName) then
categoricalCols.Add(colName);
end;
if categoricalCols.Count > 0 then
df := df.SetCategorical(categoricalCols.ToArray);
var ds := new Dataset;
@ -1255,50 +1448,27 @@ end;
static function Datasets.Iris: Dataset;
begin
var ds := Load('Iris');
ds.Data := ds.Data.SetCategorical([
'species'
]);
Result := ds;
Result := Load('Iris');
end;
static function Datasets.MoscowHousing: Dataset;
begin
var ds := Load('moscow_housing');
ds.Data := ds.Data.SetCategorical([
'renovation'
]);
Result := ds;
Result := Load('moscow_housing');
end;
static function Datasets.RussianCities: Dataset;
begin
var ds := Load('russian_cities');
ds.Data := ds.Data.SetCategorical([
'region_name',
'federal_district'
]);
Result := ds;
Result := Load('russian_cities');
end;
static function Datasets.TitanicRu: Dataset;
begin
var ds := Load('titanic_ru');
Result := Load('titanic_ru');
end;
ds.Data := ds.Data.SetCategorical([
'Выжил',
'Класс',
'Пол',
'ПортПосадки'
]);
Result := ds;
static function Datasets.UsedCarsPrice: Dataset;
begin
Result := Load('used_cars_price');
end;
{static function Datasets.StudentExam: Dataset;

View file

@ -1735,7 +1735,7 @@ type
// В отличие от этого, DataPipeline.Build используется,
// когда исходные данные представлены в виде DataFrame
// и требуется выполнить препроцессинг таблицы
// (Imputer, OneHotEncoder, LabelEncoder и др.)
// (Imputer, OneHotEncoder, OrdinalEncoder и др.)
// перед преобразованием данных в Matrix/Vector.
var pipe1 :=
@ -1932,6 +1932,9 @@ type
/// Примечание:
/// вычисленные параметры НЕ копируются методом Clone
function Fit(X: Matrix): IUnsupervisedTransformer;
/// Последовательно выполняет Fit и Transform на одних и тех же данных.
function FitTransform(X: Matrix): Matrix;
/// Применяет стандартизацию к данным.
function Transform(X: Matrix): Matrix;
@ -1978,6 +1981,9 @@ type
/// Примечание:
/// вычисленные параметры НЕ копируются методом Clone
function Fit(X: Matrix): IUnsupervisedTransformer;
/// Последовательно выполняет Fit и Transform на одних и тех же данных.
function FitTransform(X: Matrix): Matrix;
/// Применяет линейное масштабирование признаков к диапазону [0, 1].
function Transform(X: Matrix): Matrix;
@ -2027,6 +2033,9 @@ type
/// Примечание:
/// вычисленные параметры НЕ копируются методом Clone
function Fit(X: Matrix): IUnsupervisedTransformer;
/// Последовательно выполняет Fit и Transform на одних и тех же данных.
function FitTransform(X: Matrix): Matrix;
/// Преобразует матрицу X в пространство главных компонент.
/// Возвращает матрицу m × k.
@ -2067,6 +2076,9 @@ type
/// Примечание:
/// выбранные признаки НЕ копируются методом Clone
function Fit(X: Matrix): IUnsupervisedTransformer;
/// Последовательно выполняет Fit и Transform на одних и тех же данных.
function FitTransform(X: Matrix): Matrix;
/// Возвращает матрицу, содержащую только отобранные признаки.
function Transform(X: Matrix): Matrix;
@ -2151,6 +2163,9 @@ type
/// Примечание:
/// выбранные признаки НЕ копируются методом Clone
function Fit(X: Matrix; y: Vector): ISupervisedTransformer;
/// Последовательно выполняет Fit и Transform на одних и тех же данных.
function FitTransform(X: Matrix; y: Vector): Matrix;
/// Возвращает матрицу, содержащую только выбранные признаки.
function Transform(X: Matrix): Matrix;
@ -2198,6 +2213,9 @@ type
/// у трансформера отсутствует обученное состояние
function Fit(X: Matrix): IUnsupervisedTransformer;
/// Последовательно выполняет Fit и Transform на одних и тех же данных.
function FitTransform(X: Matrix): Matrix;
/// Применяет нормализацию к матрице X.
/// Каждая строка масштабируется так, чтобы ее норма соответствовала выбранному типу.
/// Возвращает новую матрицу с нормализованными объектами.
@ -3416,12 +3434,40 @@ end;
function LogisticRegression.PredictLabels(X: Matrix): array of integer;
begin
var P := PredictProba(X);
SetLength(Result, P.RowCount);
for var i := 0 to P.RowCount - 1 do
Result[i] := P.RowArgMax(i);
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
if X = nil then
ArgumentNullError(ER_X_NULL);
if MLConfig.ValidateFiniteInputs then
CheckXForPredict(X);
if X.ColCount <> fW.RowCount then
DimensionError(ER_FEATURE_COUNT_MISMATCH, X.ColCount, fW.RowCount);
var m := X.RowCount;
var Z := X * fW;
SetLength(Result, m);
for var i := 0 to m - 1 do
begin
var best := 0;
var bestVal := Z[i,0] + fIntercept[0];
for var k := 1 to fClassCount - 1 do
begin
var score := Z[i,k] + fIntercept[k];
if score > bestVal then
begin
bestVal := score;
best := k;
end;
end;
Result[i] := best;
end;
end;
function LogisticRegression.ToString: string;
@ -4074,8 +4120,8 @@ begin
var leftOrders, rightOrders: array of array of integer;
SplitNodeOrders(nodeOrders, split.Feature, split.LeftCount, split.LeftOrderSize, leftOrders, rightOrders);
var leftArr := leftOrders[0];
var rightArr := rightOrders[0];
//var leftArr := leftOrders[0];
//var rightArr := rightOrders[0];
var rightCount := n - split.LeftCount;
if (split.LeftCount < fMinSamplesLeaf) or
@ -6105,11 +6151,8 @@ begin
yPred[i] := fInitValue;
foreach var tree in fEstimators do
begin
var delta := tree.Predict(X);
for var i := 0 to n - 1 do
yPred[i] += fLearningRate * delta[i];
end;
yPred[i] += fLearningRate * tree.PredictOne(X, i);
Result := yPred;
end;
@ -6912,12 +6955,8 @@ begin
// --- накопление логитов
foreach var trees in fEstimators do
for var cls := 0 to classCount - 1 do
begin
var delta := trees[cls].Predict(X);
for var i := 0 to nSamples - 1 do
logits[i, cls] += fLearningRate * delta[i];
end;
logits[i, cls] += fLearningRate * trees[cls].PredictOne(X, i);
var probs := new Matrix(nSamples, classCount);
@ -8786,6 +8825,12 @@ begin
Result := Self;
end;
function StandardScaler.FitTransform(X: Matrix): Matrix;
begin
Fit(X);
Result := Transform(X);
end;
function StandardScaler.Transform(X: Matrix): Matrix;
begin
if not fFitted then
@ -8880,6 +8925,12 @@ begin
Result := Self;
end;
function MinMaxScaler.FitTransform(X: Matrix): Matrix;
begin
Fit(X);
Result := Transform(X);
end;
function MinMaxScaler.Transform(X: Matrix): Matrix;
begin
if not fFitted then
@ -8998,6 +9049,12 @@ begin
Result := Self;
end;
function PCATransformer.FitTransform(X: Matrix): Matrix;
begin
Fit(X);
Result := Transform(X);
end;
function PCATransformer.Transform(X: Matrix): Matrix;
begin
if not fFitted then
@ -9075,6 +9132,12 @@ begin
Result := Self;
end;
function VarianceThreshold.FitTransform(X: Matrix): Matrix;
begin
Fit(X);
Result := Transform(X);
end;
function VarianceThreshold.Transform(X: Matrix): Matrix;
begin
if not fFitted then
@ -9396,6 +9459,12 @@ begin
Result := Self;
end;
function SelectKBest.FitTransform(X: Matrix; y: Vector): Matrix;
begin
Fit(X, y);
Result := Transform(X);
end;
function SelectKBest.Transform(X: Matrix): Matrix;
begin
if not fFitted then
@ -9465,6 +9534,12 @@ begin
Result := Self;
end;
function Normalizer.FitTransform(X: Matrix): Matrix;
begin
Fit(X);
Result := Transform(X);
end;
function Normalizer.Transform(X: Matrix): Matrix;
begin
if not fFitted then

View file

@ -96,6 +96,18 @@ type
features: array of string;
params steps: array of IPipelineStep
): DataPipeline;
/// Строит только preprocessing-часть supervised-конвейера без модели.
/// Модель затем можно присоединить методом WithModel
static function BuildPreprocessing(
task: TaskKind;
target: string;
features: array of string;
params steps: array of IPipelineStep
): DataPipeline;
/// Возвращает копию текущего preprocessing-конвейера с добавленной моделью.
function WithModel(model: ISupervisedModel): DataPipeline;
/// Обучает конвейер на DataFrame.
/// Семантика:
@ -185,6 +197,13 @@ type
/// Строит unsupervised-конвейер из шагов обработки данных и модели.
static function Build(features: array of string;
params steps: array of IPipelineStep): UDataPipeline;
/// Строит только preprocessing-часть unsupervised-конвейера без модели.
static function BuildPreprocessing(features: array of string;
params steps: array of IPipelineStep): UDataPipeline;
/// Возвращает копию текущего preprocessing-конвейера с добавленной моделью.
function WithModel(model: IUnsupervisedModel): UDataPipeline;
/// Обучает конвейер на DataFrame.
function Fit(df: DataFrame): UDataPipeline;
@ -299,10 +318,13 @@ const
'Метки классов недоступны. Убедитесь, что конвейер обучен и задача — классификация!!Class labels are not available. Ensure the pipeline is fitted and the task is classification';
ER_LABEL_INDEX_OUT_OF_RANGE =
'Индекс метки {0} вне диапазона [0, {1})!!Label index {0} is out of range [0, {1})';
ER_LABELENCODER_TARGET_NOT_ALLOWED =
'LabelEncoder нельзя применять к целевой переменной — кодирование выполняется внутри модели!!LabelEncoder cannot be applied to target — encoding is handled internally by the model';
ER_ORDINALENCODER_TARGET_NOT_ALLOWED =
'OrdinalEncoder нельзя применять к целевой переменной — кодирование выполняется внутри модели!!OrdinalEncoder cannot be applied to target — encoding is handled internally by the model';
ER_ENCODELABELS_NOT_CATEGORICAL =
'Целевой столбец должен быть категориальным для задач классификации!!Target column must be categorical for classification tasks';
ER_CLASSIFICATION_TARGET_MUST_BE_CATEGORICAL_STR_OR_INT =
'Целевой столбец "{0}" должен быть категориальным строковым или целочисленным для задач классификации!!' +
'Target column "{0}" must be a categorical string or integer column for classification tasks';
ER_REGRESSION_TARGET_MUST_BE_NUMERIC =
'Целевой столбец "{0}" должен быть числовым для задач регрессии!!Target column "{0}" must be numeric for regression tasks';
ER_PREPROCESSOR_ROWCOUNT_CHANGED =
@ -557,10 +579,10 @@ begin
Result := Self;
end;
class function DataPipeline.Build(
class function DataPipeline.BuildPreprocessing(
task: TaskKind;
target: string;
features: array of string;
features: array of string;
params steps: array of IPipelineStep
): DataPipeline;
begin
@ -579,6 +601,38 @@ begin
if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS);
for var i := 0 to High(steps) do
begin
var step := steps[i];
if step = nil then
ArgumentError(ER_PIPELINE_STEP_NULL, i);
if step is ISupervisedModel then
ArgumentError(ER_PIPELINE_INVALID_STEP_ORDER);
end;
var p := new DataPipeline;
p.fTarget := target;
p.fFeatures := Copy(features);
p.fTask := task;
for var i := 0 to High(steps) do
p.Add(steps[i]);
Result := p;
end;
class function DataPipeline.Build(
task: TaskKind;
target: string;
features: array of string;
params steps: array of IPipelineStep
): DataPipeline;
begin
if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS);
var last := steps[High(steps)];
if last = nil then
@ -611,14 +665,32 @@ begin
ArgumentError(ER_PIPELINE_INVALID_STEP_ORDER);
end;
var p := new DataPipeline;
p.fTarget := target;
p.fFeatures := Copy(features);
p.fTask := task;
var prepSteps := new IPipelineStep[steps.Length - 1];
for var i := 0 to High(prepSteps) do
prepSteps[i] := steps[i];
for var i := 0 to High(steps) do
p.Add(steps[i]);
Result :=
BuildPreprocessing(task, target, features, prepSteps)
.WithModel(last as ISupervisedModel);
end;
function DataPipeline.WithModel(model: ISupervisedModel): DataPipeline;
begin
if model = nil then
ArgumentError(ER_MODEL_NULL);
var p := Clone as DataPipeline;
if p = nil then
Error(ER_MODEL_CLONE_TYPE);
if p.fFitted then
Error(ER_PIPELINE_MODIFY_AFTER_FIT);
if p.fModel <> nil then
ArgumentError(ER_PIPELINE_MULTIPLE_MODELS);
p.Add(model as IPipelineStep);
Result := p;
end;
@ -790,8 +862,13 @@ begin
case fTask of
tkClassification:
if not df.IsCategorical(fTarget) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, fTarget);
begin
if not df.IsCategorical(fTarget) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, fTarget);
var ct := df.GetColumnType(fTarget);
if not (ct in [ColumnType.ctStr, ColumnType.ctInt]) then
ArgumentError(ER_CLASSIFICATION_TARGET_MUST_BE_CATEGORICAL_STR_OR_INT, fTarget);
end;
tkRegression:
begin
@ -923,11 +1000,37 @@ begin
Result := Self;
end;
class function UDataPipeline.Build(features: array of string;
class function UDataPipeline.BuildPreprocessing(features: array of string;
params steps: array of IPipelineStep): UDataPipeline;
begin
ValidateFeatureList(features);
if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS);
for var i := 0 to High(steps) do
begin
var step := steps[i];
if step = nil then
ArgumentError(ER_PIPELINE_STEP_NULL, i);
if step is IUnsupervisedModel then
ArgumentError(ER_PIPELINE_INVALID_STEP_ORDER);
end;
var p := new UDataPipeline;
p.fFeatures := Copy(features);
for var i := 0 to High(steps) do
p.Add(steps[i]);
Result := p;
end;
class function UDataPipeline.Build(features: array of string;
params steps: array of IPipelineStep): UDataPipeline;
begin
if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS);
@ -950,12 +1053,32 @@ begin
ArgumentError(ER_PIPELINE_INVALID_STEP_ORDER);
end;
var p := new UDataPipeline;
p.fFeatures := Copy(features);
var prepSteps := new IPipelineStep[steps.Length - 1];
for var i := 0 to High(prepSteps) do
prepSteps[i] := steps[i];
for var i := 0 to High(steps) do
p.Add(steps[i]);
Result :=
BuildPreprocessing(features, prepSteps)
.WithModel(last as IUnsupervisedModel);
end;
function UDataPipeline.WithModel(model: IUnsupervisedModel): UDataPipeline;
begin
if model = nil then
ArgumentError(ER_MODEL_NULL);
var p := Clone as UDataPipeline;
if p = nil then
Error(ER_INVALID_MODEL_TYPE, 'UDataPipeline');
if p.fFitted then
Error(ER_PIPELINE_MODIFY_AFTER_FIT);
if p.fModel <> nil then
ArgumentError(ER_PIPELINE_MULTIPLE_MODELS);
p.Add(model as IPipelineStep);
Result := p;
end;

View file

@ -244,6 +244,8 @@ type
procedure EnsureBinary;
function GetMatrix: array[,] of integer;
function GetTPBinary: integer;
function GetFPBinary: integer;
function GetFNBinary: integer;
@ -292,6 +294,11 @@ type
///
/// Используется для построения матрицы ошибок размера K × K, где K = ClassCount.
property ClassCount: integer read fClassCount;
/// Матрица ошибок.
///
/// Matrix[i, j] количество объектов класса i, которые модель предсказала как класс j.
property Matrix: array[,] of integer read GetMatrix;
/// Число истинно положительных предсказаний для класса c.
/// Здесь c номер класса в матрице ошибок.
@ -2001,6 +2008,15 @@ begin
end;
end;
function ConfusionMatrix.GetMatrix: array[,] of integer;
begin
Result := new integer[fClassCount, fClassCount];
for var i := 0 to fClassCount - 1 do
for var j := 0 to fClassCount - 1 do
Result[i, j] := fMatrix[i, j];
end;
function ConfusionMatrix.GetLabel(c: integer): integer;
begin
if (c < 0) or (c >= fClassCount) then

File diff suppressed because it is too large Load diff

View file

@ -445,6 +445,8 @@ begin
var srcIdx := df.Schema.IndexOf(col);
if srcIdx < 0 then
ArgumentError(ER_COLUMN_NOT_FOUND, col);
if df.Schema.ColumnTypeAt(srcIdx) <> ColumnType.ctStr then
Error(ER_ONEHOT_NOT_STRING, col);
var rowCount := df.RowCount;
var catCount := categories.Length;