Новые ML-модули и исправление старых

This commit is contained in:
Mikhalkovich Stanislav 2026-02-14 11:55:00 +03:00
parent 4dc3470dbb
commit bc77b1564a
11 changed files with 1717 additions and 55 deletions

View file

@ -20,7 +20,8 @@ uses
BBCMicroBit, School, SF, TwoPanelsWindow, NUnitABC, PlotWPF, XLSX, LightPT, Tasks, Мозаика,
TasksArr, TasksMatr, TasksStr, Tasks1Begin, Tasks1BoolIfCase, Tasks1Loops, Tasks1Arr,
WPF,
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC
DataFrameABC, DataFrameABCCore, LinearAlgebraML, PreprocessorABC,
MetricsABC, MLABC, MLCoreABC, MLModelsABC, ValidationML
;
begin

View file

@ -183,6 +183,11 @@
File ..\bin\Lib\DataFrameABCCore.pcu
File ..\bin\Lib\LinearAlgebraML.pcu
File ..\bin\Lib\PreprocessorABC.pcu
File ..\bin\Lib\MetricsABC.pcu
File ..\bin\Lib\MLABC.pcu
File ..\bin\Lib\MLCoreABC.pcu
File ..\bin\Lib\MLModelsABC.pcu
File ..\bin\Lib\ValidationML.pcu
File ..\bin\Lib\PABCRtl.dll
File ..\bin\Lib\HelixToolkit.Wpf.dll
@ -280,6 +285,12 @@
${AddFile} "DataFrameABCCore.pcu"
${AddFile} "LinearAlgebraML.pcu"
${AddFile} "PreprocessorABC.pcu"
${AddFile} "MetricsABC.pcu"
${AddFile} "MLABC.pcu"
${AddFile} "MLCoreABC.pcu"
${AddFile} "MLModelsABC.pcu"
${AddFile} "ValidationML.pcu"
${AddFile} "turtle.png"
@ -413,6 +424,11 @@
File ..\bin\Lib\DataFrameABCCore.pas
File ..\bin\Lib\LinearAlgebraML.pas
File ..\bin\Lib\PreprocessorABC.pas
File ..\bin\Lib\MetricsABC.pas
File ..\bin\Lib\MLABC.pas
File ..\bin\Lib\MLCoreABC.pas
File ..\bin\Lib\MLModelsABC.pas
File ..\bin\Lib\ValidationML.pas
File ..\bin\Lib\__RedirectIOMode.vb
@ -498,6 +514,11 @@
${AddFile} "DataFrameABCCore.pas"
${AddFile} "LinearAlgebraML.pas"
${AddFile} "PreprocessorABC.pas"
${AddFile} "MetricsABC.pas"
${AddFile} "MLABC.pas"
${AddFile} "MLCoreABC.pas"
${AddFile} "MLModelsABC.pas"
${AddFile} "ValidationML.pas"
${AddFile} "__RedirectIOMode.vb"
${AddFile} "VBSystem.vb"

View file

@ -27,6 +27,11 @@ type
/// Статистическая информация о числовом столбце
DescribeStats = DataFrameABCCore.DescribeStats;
IntColumn = DataFrameABCCore.IntColumn;
FloatColumn = DataFrameABCCore.FloatColumn;
BoolColumn = DataFrameABCCore.BoolColumn;
StrColumn = DataFrameABCCore.StrColumn;
IGroupByContext = interface;
/// Класс для работы с табличными данными (датасетами).
@ -85,9 +90,13 @@ type
function CreateEmptyBySchema(schema: DataFrameSchema): DataFrame;
function GetColumnIndex(name: string): integer;
function GetColumn(name: string): Column;
public
/// Схема DataFrame: имена, типы и признаки категориальности
property Schema: DataFrameSchema read fschema;
property Item[name: string]: Column read GetColumn; default;
/// Добавляет в DataFrame столбец-представление (view),
/// использующий те же данные, что и исходный столбец
@ -109,13 +118,13 @@ type
function GetCursor: DataFrameCursor;
/// Добавляет столбец целых чисел
procedure AddIntColumn(name: string; data: array of integer; valid: array of boolean; isCategorical: boolean := false);
procedure AddIntColumn(name: string; data: array of integer; valid: array of boolean := nil; isCategorical: boolean := false);
/// Добавляет столбец вещественных чисел
procedure AddFloatColumn(name: string; data: array of real; valid: array of boolean);
procedure AddFloatColumn(name: string; data: array of real; valid: array of boolean := nil);
/// Добавляет строковый столбец
procedure AddStrColumn(name: string; data: array of string; valid: array of boolean; isCategorical: boolean := true);
procedure AddStrColumn(name: string; data: array of string; valid: array of boolean := nil; isCategorical: boolean := true);
/// Добавляет столбец логических значений
procedure AddBoolColumn(name: string; data: array of boolean; valid: array of boolean);
procedure AddBoolColumn(name: string; data: array of boolean; valid: array of boolean := nil);
/// Возвращает данные целочисленного столбца по имени
function GetIntColumn(name: string): array of integer;
@ -171,7 +180,7 @@ type
function Describe(colNames: array of string): Dictionary<string, DescribeStats>;
/// Возвращает статистику по нескольким столбцам по индексам
function Describe(colIndices: array of integer): Dictionary<integer, DescribeStats>;
/// Возвращает статистику по всем числовым столбц ам
/// Возвращает статистику по всем числовым столбцам
function DescribeAll: Dictionary<string, DescribeStats>;
/// Группирует данные по столбцу по индексу
@ -1429,10 +1438,22 @@ end;
procedure DataFrame.AddIntColumn(name: string; data: array of integer; valid: array of boolean; isCategorical: boolean);
begin
if (columns.Count > 0) and (data.Length <> RowCount) then
raise new Exception('Row count mismatch');
var c := new IntColumn;
c.Info := new ColumnInfo(name, ctInt, isCategorical);
c.Data := data;
c.IsValid := valid;
if valid = nil then
c.IsValid := [True] * data.Length
else
begin
if valid.Length <> data.Length then
raise new Exception('AddIntColumn: data and valid length mismatch');
c.IsValid := valid;
end;
columns.Add(c);
RebuildSchema;
end;
@ -1443,9 +1464,17 @@ begin
raise new Exception('Row count mismatch');
var c := new FloatColumn;
c.Info := new ColumnInfo(name, ctFloat, false);
c.Info := new ColumnInfo(name, ctFloat, False);
c.Data := data;
c.IsValid := valid;
if valid = nil then
c.IsValid := [True] * data.Length
else
begin
if valid.Length <> data.Length then
raise new Exception('AddFloatColumn: data and valid length mismatch');
c.IsValid := valid;
end;
columns.Add(c);
RebuildSchema;
@ -1459,7 +1488,15 @@ begin
var c := new StrColumn;
c.Info := new ColumnInfo(name, ctStr, isCategorical);
c.Data := data;
c.IsValid := valid;
if valid = nil then
c.IsValid := [True] * data.Length
else
begin
if valid.Length <> data.Length then
raise new Exception('AddStrColumn: data and valid length mismatch');
c.IsValid := valid;
end;
columns.Add(c);
RebuildSchema;
@ -1471,9 +1508,17 @@ begin
raise new Exception('Row count mismatch');
var c := new BoolColumn;
c.Info := new ColumnInfo(name, ctBool, false);
c.Info := new ColumnInfo(name, ctBool, False);
c.Data := data;
c.IsValid := valid;
if valid = nil then
c.IsValid := [True] * data.Length
else
begin
if valid.Length <> data.Length then
raise new Exception('AddBoolColumn: data and valid length mismatch');
c.IsValid := valid;
end;
columns.Add(c);
RebuildSchema;
@ -2363,6 +2408,17 @@ begin
Result := new DataFrame(cols, schema);
end;
function DataFrame.GetColumnIndex(name: string): integer;
begin
Result := fschema.IndexOf(name); // если у вас есть такой метод
end;
function DataFrame.GetColumn(name: string): Column;
begin
var idx := GetColumnIndex(name);
Result := columns[idx];
end;
function DataFrame.WithColumnInt(name: string; f: DataFrameCursor -> integer): DataFrame;
begin
if fSchema.HasColumn(name) then
@ -2966,7 +3022,7 @@ end;
procedure DataFrame.AssertSchemaConsistent;
begin
{$IFDEF Test}
{$IFNDEF Test}
// --- 1. одинаковая RowCount у всех столбцов ---
var rc := columns[0].RowCount;
@ -2981,7 +3037,7 @@ begin
raise new Exception(
$'Schema inconsistent: ColumnCount={fschema.ColumnCount}, columns.Count={columns.Count}'
);
// --- 3. имена уникальны и корректно индексированы ---
for var i := 0 to columns.Count - 1 do
begin

View file

@ -65,6 +65,8 @@ type
Column = abstract class
Info: ColumnInfo;
public
/// Пытается извлечь i-тое данное из столбца как числовое если это возможно
function TryGetNumericValue(i: integer; var value: real): boolean; virtual; abstract;
/// Возвращает количество строк в столбце
function RowCount: integer; virtual; abstract;
/// Добавляет невалидное (NA) значение в конец столбца
@ -80,6 +82,8 @@ type
public
constructor Create; begin end;
constructor Create(name: string; isCategorical: boolean);
function TryGetNumericValue(i: integer; var value: real): boolean; override;
/// Возвращает количество строк в столбце
function RowCount: integer; override := Data.Length;
/// Добавляет невалидное (NA) значение в конец столбца
@ -95,6 +99,8 @@ type
public
constructor Create; begin end;
constructor Create(name: string);
function TryGetNumericValue(i: integer; var value: real): boolean; override;
/// Возвращает количество строк в столбце
function RowCount: integer; override := Data.Length;
/// Добавляет невалидное (NA) значение в конец столбца
@ -110,6 +116,7 @@ type
public
constructor Create; begin end;
constructor Create(name: string; isCategorical: boolean);
function TryGetNumericValue(i: integer; var value: real): boolean; override;
/// Возвращает количество строк в столбце
function RowCount: integer; override := Data.Length;
/// Добавляет невалидное (NA) значение в конец столбца
@ -125,6 +132,7 @@ type
public
constructor Create; begin end;
constructor Create(name: string);
function TryGetNumericValue(i: integer; var value: real): boolean; override;
/// Возвращает количество строк в столбце
function RowCount: integer; override := Data.Length;
/// Добавляет невалидное (NA) значение в конец столбца
@ -468,6 +476,15 @@ begin
else AppendInvalid;
end;
function IntColumn.TryGetNumericValue(i: integer; var value: real): boolean;
begin
if not IsValid[i] then
exit(false);
value := Data[i];
exit(true);
end;
constructor FloatColumn.Create(name: string);
begin
inherited Create;
@ -500,6 +517,15 @@ begin
IsValid := IsValid + [false];
end;
function FloatColumn.TryGetNumericValue(i: integer; var value: real): boolean;
begin
if not IsValid[i] then
exit(false);
value := Data[i];
exit(true);
end;
constructor StrColumn.Create(name: string; isCategorical: boolean);
begin
inherited Create;
@ -533,6 +559,24 @@ begin
IsValid := IsValid + [false];
end;
function StrColumn.TryGetNumericValue(i: integer; var value: real): boolean;
begin
exit(false);
end;
function BoolColumn.TryGetNumericValue(i: integer; var value: real): boolean;
begin
if not IsValid[i] then
exit(false);
if Data[i] then
value := 1.0
else
value := 0.0;
exit(true);
end;
constructor BoolColumn.Create(name: string);
begin
inherited Create;

View file

@ -27,24 +27,37 @@ type
static function operator *(v: Vector; alpha: real): Vector;
static function operator /(v: Vector; alpha: real): Vector;
// Dot product
static function operator *(a, b: Vector): real;
static function operator +=(a: Vector; b: Vector): Vector;
static function operator -=(a: Vector; b: Vector): Vector;
static function operator *=(a: Vector; alpha: real): Vector;
static function operator +(v: Vector; c: real): Vector;
static function operator +(c: real; v: Vector): Vector;
static function operator implicit(a: array of real): Vector := new Vector(a);
static function operator implicit(a: array of integer): Vector := new Vector(a);
// ---------- Векторные функции ----------
/// Применить функцию ко всем элементам вектора
function Apply(f: real -> real): Vector;
function Sqrt: Vector;
function Exp: Vector := Apply(PABCSystem.Exp);
function Ln: Vector := Apply(PABCSystem.Ln);
function Abs: Vector := Apply(PABCSystem.Abs);
// ---------- Основные методы ----------
function Sum: real;
function Mean: real;
function Norm2: real;
function Norm: real;
function Max: real;
function Min: real;
/// Скалярное произведение
function Dot(b: Vector): real;
// ---------- Сервисные методы ----------
function ToString: string; override := $'{data}';
function ToString: string; override := $'{data.Select(x -> x.ToString(''G3''))}';
function ToString(digits: integer): string := $'{data.Select(x -> x.ToString(''G''+digits))}';
procedure Print := data.Print;
procedure Println := data.Println;
end;
@ -69,6 +82,23 @@ type
function Clone: Matrix;
function ColumnSums: Vector;
function RowSums: Vector;
function ColumnMeans: Vector;
function RowMeans: Vector;
function ColumnVariances: Vector;
function RowVariances: Vector;
function ColumnStd: Vector;
function RowStd: Vector;
function ColumnMins: Vector;
function ColumnMaxs: Vector;
function RowMins: Vector;
function RowMaxs: Vector;
function FrobeniusNorm: real;
procedure AddScaledIdentity(lambda: real);
static function operator implicit(a: array [,] of real): Matrix := new Matrix(a);
static function operator implicit(a: array of array of real): Matrix := new Matrix(Matr(a));
static function operator implicit(a: array of array of integer): Matrix := new Matrix(Matr(a.ConvertAll(x -> x.ConvertAll(y -> real(y)))));
@ -89,6 +119,10 @@ type
static function operator *=(A: Matrix; alpha: real): Matrix;
// ---------- Основные методы ----------
function RowCount: integer := data.RowCount;
function ColCount: integer := data.ColCount;
/// Возвращает транспонированную матрицу Aᵀ
function Transpose: Matrix;
/// Проверяет, является ли матрица симметричной с заданным допуском.
@ -129,6 +163,9 @@ type
// ---------- Статические методы ----------
/// Возвращает единичную матрицу размера n
static function Identity(n: integer): Matrix;
/// Возвращает внешнее произведение двух векторов
static function OuterProduct(a, b: Vector): Matrix;
end;
/// Решает систему линейных уравнений A * x = b с помощью LU-разложения с частичным выбором главного элемента.
@ -247,11 +284,6 @@ begin
Result.data[i] := a.data[i] - b.data[i];
end;
static function Vector.operator *(alpha: real; v: Vector): Vector;
begin
Result := v * alpha;
end;
static function Vector.operator *(v: Vector; alpha: real): Vector;
begin
Result := new Vector(v.Length);
@ -259,6 +291,11 @@ begin
Result.data[i] := alpha * v.data[i];
end;
static function Vector.operator *(alpha: real; v: Vector): Vector;
begin
Result := v * alpha;
end;
static function Vector.operator /(v: Vector; alpha: real): Vector;
begin
if alpha = 0.0 then
@ -269,15 +306,6 @@ begin
Result.data[i] := v.data[i] * inv;
end;
static function Vector.operator *(a, b: Vector): real;
begin
CheckSameLength(a, b);
var s := 0.0;
for var i := 0 to a.Length - 1 do
s += a.data[i] * b.data[i];
Result := s;
end;
static function Vector.operator +=(a, b: Vector): Vector;
begin
CheckSameLength(a, b);
@ -301,6 +329,20 @@ begin
Result := a;
end;
static function Vector.operator +(v: Vector; c: real): Vector;
begin
Result := new Vector(v.Length);
for var i := 0 to v.Length - 1 do
Result[i] := v[i] + c;
end;
static function Vector.operator +(c: real; v: Vector): Vector;
begin
Result := v + c;
end;
function Vector.Sqrt: Vector := Apply(PABCSystem.Sqrt);
function Vector.Sum: real;
begin
var s := 0.0;
@ -317,14 +359,34 @@ end;
function Vector.Norm2: real;
begin
Result := Self * Self;
Result := Self.Dot(Self);
end;
function Vector.Norm: real;
function Vector.Norm: real := PABCSystem.Sqrt(Norm2);
function Vector.Max: real := data.Max;
function Vector.Min: real := data.Min;
function Vector.Dot(b: Vector): real;
begin
Result := Sqrt(Norm2);
if Length <> b.Length then
raise new Exception('Dimension mismatch in Dot');
var s := 0.0;
for var i := 0 to Length - 1 do
s += self[i] * b[i];
Result := s;
end;
function Vector.Apply(f: real -> real): Vector;
begin
Result := new Vector(Length);
for var i := 0 to Length - 1 do
Result[i] := f(self[i]);
end;
//-----------------------------
// Matrix
//-----------------------------
@ -347,6 +409,175 @@ begin
Result := new Matrix(data);
end;
function Matrix.ColumnSums: Vector;
begin
var n := RowCount;
var p := ColCount;
Result := new Vector(p);
for var j := 0 to p - 1 do
for var i := 0 to n - 1 do
Result[j] += data[i, j];
end;
function Matrix.ColumnMeans: Vector;
begin
var n := RowCount;
if n = 0 then
exit( new Vector(ColCount) );
Result := ColumnSums / n;
end;
function Matrix.RowSums: Vector;
begin
var n := RowCount;
var p := ColCount;
Result := new Vector(n);
for var i := 0 to n - 1 do
for var j := 0 to p - 1 do
Result[i] += data[i, j];
end;
function Matrix.RowMeans: Vector;
begin
var p := ColCount;
if p = 0 then
exit (new Vector(RowCount));
Result := RowSums / p;
end;
function Matrix.ColumnVariances: Vector;
begin
var n := RowCount;
if n = 0 then
exit (new Vector(ColCount));
var means := ColumnMeans;
var p := ColCount;
Result := new Vector(p);
for var j := 0 to p - 1 do
for var i := 0 to n - 1 do
begin
var d := data[i, j] - means[j];
Result[j] += d * d;
end;
Result := Result / n;
end;
function Matrix.ColumnStd: Vector;
begin
Result := ColumnVariances.Sqrt;
end;
function Matrix.RowVariances: Vector;
begin
var p := ColCount;
if p = 0 then
exit (new Vector(RowCount));
var means := RowMeans;
var n := RowCount;
Result := new Vector(n);
for var i := 0 to n - 1 do
for var j := 0 to p - 1 do
begin
var d := data[i, j] - means[i];
Result[i] += d * d;
end;
Result := Result / p;
end;
function Matrix.RowStd: Vector;
begin
Result := RowVariances.Sqrt;
end;
function Matrix.ColumnMins: Vector;
begin
var n := RowCount;
var p := ColCount;
Result := new Vector(p);
for var j := 0 to p - 1 do
begin
Result[j] := data[0, j];
for var i := 1 to n - 1 do
if data[i, j] < Result[j] then
Result[j] := data[i, j];
end;
end;
function Matrix.ColumnMaxs: Vector;
begin
var n := RowCount;
var p := ColCount;
Result := new Vector(p);
for var j := 0 to p - 1 do
begin
Result[j] := data[0, j];
for var i := 1 to n - 1 do
if data[i, j] > Result[j] then
Result[j] := data[i, j];
end;
end;
function Matrix.RowMins: Vector;
begin
var n := RowCount;
var p := ColCount;
Result := new Vector(n);
for var i := 0 to n - 1 do
begin
Result[i] := data[i, 0];
for var j := 1 to p - 1 do
if data[i, j] < Result[i] then
Result[i] := data[i, j];
end;
end;
function Matrix.RowMaxs: Vector;
begin
var n := RowCount;
var p := ColCount;
Result := new Vector(n);
for var i := 0 to n - 1 do
begin
Result[i] := data[i, 0];
for var j := 1 to p - 1 do
if data[i, j] > Result[i] then
Result[i] := data[i, j];
end;
end;
function Matrix.FrobeniusNorm: real;
begin
var s := 0.0;
for var i := 0 to RowCount - 1 do
for var j := 0 to ColCount - 1 do
s += data[i, j] * data[i, j];
Result := PABCSystem.Sqrt(s);
end;
procedure Matrix.AddScaledIdentity(lambda: real);
begin
var n := RowCount;
for var i := 0 to n - 1 do
data[i, i] += lambda;
end;
static procedure Matrix.CheckSameSize(A, B: Matrix);
begin
if (A.Rows <> B.Rows) or (A.Cols <> B.Cols) then
@ -368,6 +599,8 @@ begin
$'Matrix-vector size mismatch: {A.Rows}x{A.Cols} * {x.Length}');
end;
static function Matrix.operator +(A, B: Matrix): Matrix;
begin
CheckSameSize(A, B);
@ -488,6 +721,17 @@ begin
Result[i, i] := 1.0;
end;
static function Matrix.OuterProduct(a, b: Vector): Matrix;
begin
var m := a.Length;
var n := b.Length;
Result := new Matrix(m, n);
for var i := 0 to m - 1 do
for var j := 0 to n - 1 do
Result[i, j] := a[i] * b[j];
end;
function Matrix.IsSymmetric(tol: real): boolean;
begin
if Rows <> Cols then

84
bin/Lib/MLABC.pas Normal file
View file

@ -0,0 +1,84 @@
/// Основной модуль библиотеки машинного обучения.
/// Объединяет модели, метрики, валидацию и вспомогательные компоненты.
unit MLABC;
interface
uses LinearAlgebraML;
uses ValidationML;
uses MLModelsABC;
uses MetricsABC;
uses PreprocessorABC;
USES DataFrameABC;
type
Vector = LinearAlgebraML.Vector;
Matrix = LinearAlgebraML.Matrix;
Validation = ValidationML.Validation;
LinearRegression = MLModelsABC.LinearRegression;
LogisticRegression = MLModelsABC.LogisticRegression;
ConfusionMatrix = MetricsABC.ConfusionMatrix;
Metrics = MetricsABC.Metrics;
Activations = MLModelsABC.Activations;
Pipeline = MLModelsABC.Pipeline;
DataPipeline = PreprocessorABC.DataPipeline;
DataStandardScaler = PreprocessorABC.DataStandardScaler;
StandardScaler = MLModelsABC.StandardScaler;
DataFrame = DataFrameABC.DataFrame;
Statistics = DataFrameABC.Statistics;
CsvLoader = DataFrameABC.CsvLoader;
implementation
function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod;
begin
var df := Self;
var n := df.RowCount;
var p := colNames.Length;
if p = 0 then
raise new Exception('ToMatrix: no columns specified');
Result := new Matrix(n, p);
for var j := 0 to p - 1 do
begin
var col := df[colNames[j]];
for var i := 0 to n - 1 do
begin
var value: real;
if not col.TryGetNumericValue(i, value) then
raise new Exception(
'ToMatrix: column "' + colNames[j] +
'" contains non-numeric or NA values');
Result[i,j] := value;
end;
end;
end;
function ToVector(Self: DataFrame; colName: string): Vector; extensionmethod;
begin
var df := Self;
var n := df.RowCount;
Result := new Vector(n);
var col := df[colName];
for var i := 0 to n - 1 do
begin
var value: real;
if not col.TryGetNumericValue(i, value) then
raise new Exception(
'ToVector: column "' + colName +
'" contains non-numeric or NA values');
Result[i] := value;
end;
end;
end.

53
bin/Lib/MLCoreABC.pas Normal file
View file

@ -0,0 +1,53 @@
unit MLCoreABC;
interface
uses LinearAlgebraML;
type
/// Базовый интерфейс модели машинного обучения
IModel = interface
function Fit(X: Matrix; y: Vector): IModel;
function Predict(X: Matrix): Vector;
end;
/// Интерфейс классификатора.
/// Наследуется от IModel.
/// Предназначен для моделей, выполняющих классификацию
/// (предсказание меток классов).
IClassifier = interface(IModel)
end;
/// Интерфейс классификатора, возвращающего вероятности.
/// Расширяет IClassifier.
/// Используется для моделей, которые умеют оценивать
/// вероятность принадлежности к классу.
/// Позволяет получать значения в диапазоне (0, 1)
/// вместо только итогового решения.
IProbabilisticClassifier = interface(IClassifier)
/// Возвращает вероятность принадлежности к положительному классу для каждого объекта.
function PredictProba(X: Matrix): Vector;
end;
/// Интерфейс регрессионной модели.
/// Наследуется от IModel.
/// Предназначен для моделей, предсказывающих числовые значения.
IRegressor = interface(IModel)
end;
/// Базовый интерфейс преобразования признаков.
/// Используется для масштабирования, отбора,
/// уменьшения размерности и других преобразований данных.
ITransformer = interface
/// Обучает преобразование на данных.
/// Запоминает необходимые параметры,
/// которые будут использоваться при Transform.
function Fit(X: Matrix): ITransformer;
/// Применяет обученное преобразование к данным.
/// Возвращает новую матрицу признаков.
function Transform(X: Matrix): Matrix;
end;
implementation
end.

560
bin/Lib/MLModelsABC.pas Normal file
View file

@ -0,0 +1,560 @@
unit MLModelsABC;
interface
uses MLCoreABC;
uses LinearAlgebraML;
type
/// Активационные функции для моделей
Activations = static class
public
/// Функция активации Sigmoid (логистическая функция).
/// Преобразует любое число в диапазон (0, 1).
/// Используется в логистической регрессии
/// и других моделях для получения вероятности.
/// Формула: σ(x) = 1 / (1 + e^(−x)).
static function Sigmoid(v: Vector): Vector;
/// Функция активации Tanh (гиперболический тангенс).
/// Похожа на Sigmoid, но значения лежат в диапазоне (−1, 1).
/// Применяется в моделях, где требуется
/// симметричная нелинейность относительно нуля.
/// Формула: tanh(x) = (e^x − e^(−x)) / (e^x + e^(−x)).
static function Tanh(v: Vector): Vector;
/// Функция активации ReLU (Rectified Linear Unit).
/// Отрицательные числа заменяет на 0,
/// положительные оставляет без изменений.
/// Используется как простое нелинейное преобразование
/// в моделях машинного обучения.
/// Формула: ReLU(x) = max(0, x).
static function ReLU(v: Vector): Vector;
/// Функция активации Softmax.
/// Преобразует набор значений в вероятностное распределение:
/// все элементы неотрицательны,
/// их сумма равна 1.
/// Используется в задачах многоклассовой классификации.
/// Каждый элемент интерпретируется как вероятность класса.
/// Формула: softmax(x_i) = e^{x_i} / Σ e^{x_j}.
static function Softmax(v: Vector): Vector;
end;
IModel = MLCoreABC.IModel;
/// Линейная регрессионная модель
/// Предсказывает числовое значение по линейной комбинации признаков
LinearRegression = class(IRegressor)
private
fCoef: Vector;
fIntercept: real;
fLambda: real;
fFitted: boolean;
public
constructor Create(lambda: real := 0.0);
/// Обучает модель на числовых данных
/// X — матрица m × n (m объектов, n признаков)
/// y — вектор длины m
function Fit(X: Matrix; y: Vector): IModel;
/// Предсказывает значения для матрицы признаков
/// Возвращает вектор длины m
function Predict(X: Matrix): Vector;
/// Вектор коэффициентов модели (веса признаков).
/// Каждый элемент показывает вклад соответствующего признака в линейную комбинацию.
/// Доступен после обучения (Fit).
property Coefficients: Vector read fCoef;
/// Свободный член модели (смещение, bias).
/// Добавляется к линейной комбинации признаков.
/// Интерпретируется как значение предсказания, когда все признаки равны нулю.
property Intercept: real read fIntercept;
/// Показывает, была ли модель обучена.
/// После вызова Fit значение становится true.
/// Используется для проверки корректности вызова Predict.
property IsFitted: boolean read fFitted;
end;
/// Логистическая регрессионная модель для бинарной классификации.
/// Предсказывает вероятность принадлежности объекта к классу 1
/// на основе линейной комбинации признаков и сигмоидной функции.
/// Поддерживает L2-регуляризацию.
LogisticRegression = class(IClassifier)
private
fCoef: Vector;
fIntercept: real;
fLambda: real;
fLearningRate: real;
fEpochs: integer;
fFitted: boolean;
public
/// Создаёт модель логистической регрессии.
/// lambda — коэффициент L2-регуляризации (0 — без регуляризации).
/// lr — шаг градиентного спуска.
/// epochs — число итераций обучения.
constructor Create(lambda: real := 0.0; lr: real := 0.1; epochs: integer := 1000);
/// Обучает модель на числовых данных.
/// X — матрица m × n (m объектов, n признаков).
/// y — вектор длины m, содержащий метки классов (0 или 1).
function Fit(X: Matrix; y: Vector): IModel;
/// Возвращает вероятность принадлежности к положительному классу (1)
/// для каждого объекта.
/// Результат — вектор длины m со значениями в диапазоне (0, 1).
function PredictProba(X: Matrix): Vector;
/// Выполняет бинарную классификацию с порогом 0.5.
/// Возвращает вектор из 0 и 1.
function Predict(X: Matrix): Vector;
/// Выполняет бинарную классификацию с заданным порогом.
/// threshold — значение в диапазоне (0, 1).
/// Если вероятность ≥ threshold, возвращается 1, иначе 0.
function Predict(X: Matrix; threshold: real): Vector;
/// Вектор коэффициентов модели (веса признаков).
/// Длина равна числу признаков.
/// Доступен после обучения (Fit).
property Coefficients: Vector read fCoef;
/// Свободный член модели (смещение, bias).
/// Добавляется к линейной комбинации признаков.
property Intercept: real read fIntercept;
/// Показывает, была ли модель обучена.
/// После вызова Fit значение становится true.
property IsFitted: boolean read fFitted;
end;
/// Конвейер машинного обучения (Pipeline).
/// Объединяет несколько шагов подготовки данных и модель
/// в единую последовательность обработки.
/// Сначала данные проходят через все преобразования,
/// затем итоговые признаки передаются в модель.
/// Обеспечивает единый интерфейс Fit / Predict.
Pipeline = class(IModel)
private
fTransformers: List<ITransformer>;
fModel: IModel;
fFitted: boolean;
public
/// Создаёт пайплайн (конвейер машинного обучения) для заданной модели.
/// model — финальная модель (регрессор или классификатор),
/// которая будет обучаться после всех преобразований.
constructor Create(model: IModel);
/// Строит пайплайн (конвейер машинного обучения).
/// model — финальная модель, которая будет обучаться.
/// transformers — последовательность преобразований признаков,
/// применяемых к данным перед обучением модели.
/// Возвращает настроенный конвейер.
static function Build(model: IModel; params transformers: array of ITransformer): Pipeline;
/// Добавляет преобразование в конец пайплайна
function Add(t: ITransformer): Pipeline;
/// Обучает преобразования и модель
function Fit(X: Matrix; y: Vector): IModel;
/// Применяет только преобразования (без модели)
function Transform(X: Matrix): Matrix;
/// Делает предсказание
function Predict(X: Matrix): Vector;
/// Возвращает вероятности (если модель поддерживает)
function PredictProba(X: Matrix): Vector;
/// Показывает, был ли пайплайн обучен (вызван метод Fit).
property IsFitted: boolean read fFitted;
end;
/// Стандартизирует признаки: вычитает среднее
/// и делит на стандартное отклонение по каждому столбцу.
/// Используется для приведения признаков к сопоставимому масштабу.
StandardScaler = class(ITransformer)
private
fMean: Vector;
fStd: Vector;
fFitted: boolean;
public
/// Вычисляет среднее и стандартное отклонение по каждому признаку.
function Fit(X: Matrix): ITransformer;
/// Применяет стандартизацию к данным.
function Transform(X: Matrix): Matrix;
/// Средние значения признаков, вычисленные при обучении.
property Mean: Vector read fMean;
/// Стандартные отклонения признаков, вычисленные при обучении.
property Std: Vector read fStd;
/// Признак того, что преобразование обучено.
property IsFitted: boolean read fFitted;
end;
/// Масштабирует признаки в заданный диапазон
/// (по умолчанию [0, 1]) на основе минимального и максимального значения каждого столбца.
/// Используется для приведения признаков к единому масштабу без центрирования.
MinMaxScaler = class(ITransformer)
private
fMin: Vector;
fMax: Vector;
fFitted: boolean;
fRangeMin: real;
fRangeMax: real;
public
/// Создаёт MinMaxScaler с диапазоном [rangeMin, rangeMax].
/// По умолчанию масштабирование выполняется к [0, 1].
constructor Create(rangeMin: real := 0.0; rangeMax: real := 1.0);
/// Вычисляет минимальные и максимальные значения
/// по каждому признаку.
function Fit(X: Matrix): ITransformer;
/// Применяет линейное масштабирование признаков
/// к диапазону [0, 1].
function Transform(X: Matrix): Matrix;
/// Минимальные значения признаков,
/// вычисленные при обучении.
property Min: Vector read fMin;
/// Максимальные значения признаков,
/// вычисленные при обучении.
property Max: Vector read fMax;
/// Признак того, что преобразование обучено.
property IsFitted: boolean read fFitted;
end;
implementation
//-----------------------------
// LinearRegression
//-----------------------------
constructor LinearRegression.Create(lambda: real);
begin
self.flambda := lambda;
ffitted := false;
end;
function LinearRegression.Fit(X: Matrix; y: Vector): IModel;
begin
var n := X.RowCount;
var p := X.ColCount;
if n = 0 then
raise new Exception('Empty dataset');
if y.Length <> n then
raise new Exception('Dimension mismatch in Fit');
// 1. Means
var meanX := X.ColumnMeans;
var meanY := y.Mean;
// 2. XtX and XtY
var Xt := X.Transpose;
var XtX := Xt * X;
var XtY := Xt * y;
// 3. Centering correction
XtX := XtX - n * Matrix.OuterProduct(meanX, meanX);
XtY := XtY - n * meanX * meanY;
// 4. Ridge regularization
if flambda > 0 then
XtX := XtX + flambda * Matrix.Identity(p);
// 5. Solve
fcoef := Solve(XtX, XtY);
// 6. Intercept
fintercept := meanY - meanX.Dot(fcoef);
ffitted := true;
Result := Self;
end;
function LinearRegression.Predict(X: Matrix): Vector;
begin
if not ffitted then
raise new Exception('Model not fitted');
if X.ColCount <> fcoef.Length then
raise new Exception('Dimension mismatch in Predict');
Result := X * fcoef + intercept;
end;
//-----------------------------
// Activations
//-----------------------------
static function Activations.Sigmoid(v: Vector): Vector := v.Apply(x -> 1.0 / (1.0 + Exp(-x)));
static function Activations.Tanh(v: Vector): Vector := v.Apply(System.Math.Tanh);
static function Activations.ReLU(v: Vector): Vector := v.Apply(x -> (if x > 0 then x else 0.0));
static function Activations.Softmax(v: Vector): Vector;
begin
var maxVal := v.Max;
var shifted := v.Apply(x -> Exp(x - maxVal));
var sumExp := shifted.Sum;
if sumExp = 0 then
exit(new Vector(v.Length));
Result := shifted / sumExp;
end;
//-----------------------------
// LogisticRegression
//-----------------------------
constructor LogisticRegression.Create(lambda: real; lr: real; epochs: integer);
begin
fLambda := lambda;
fLearningRate := lr;
fEpochs := epochs;
fFitted := false;
end;
function LogisticRegression.Fit(X: Matrix; y: Vector): IModel;
begin
var n := X.RowCount;
var p := X.ColCount;
if y.Length <> n then
raise new Exception('Dimension mismatch in Fit');
fCoef := new Vector(p);
fIntercept := 0.0;
for var epoch := 1 to fEpochs do
begin
var z := X * fCoef + fIntercept;
var yHat := Activations.Sigmoid(z);
var error := yHat - y;
var gradW := (X.Transpose * error) / n;
if fLambda > 0 then
gradW := gradW + fLambda * fCoef;
var gradB := error.Sum / n;
fCoef := fCoef - fLearningRate * gradW;
fIntercept -= fLearningRate * gradB;
end;
fFitted := true;
Result := Self;
end;
function LogisticRegression.PredictProba(X: Matrix): Vector;
begin
if not fFitted then
raise new Exception('Model not fitted');
Result := Activations.Sigmoid(X * fCoef + fIntercept);
end;
function LogisticRegression.Predict(X: Matrix; threshold: real): Vector;
begin
var p := PredictProba(X);
Result := new Vector(p.Length);
for var i := 0 to p.Length - 1 do
if p[i] >= threshold then
Result[i] := 1.0
else
Result[i] := 0.0;
end;
function LogisticRegression.Predict(X: Matrix): Vector;
begin
Result := Predict(X, 0.5);
end;
//-----------------------------
// Pipeline
//-----------------------------
constructor Pipeline.Create(model: IModel);
begin
fModel := model;
fTransformers := new List<ITransformer>;
fFitted := false;
end;
static function Pipeline.Build(model: IModel; transformers: array of ITransformer): Pipeline;
begin
var pipe := new Pipeline(model);
foreach var t in transformers do
pipe.Add(t);
Result := pipe;
end;
function Pipeline.Add(t: ITransformer): Pipeline;
begin
fTransformers.Add(t);
Result := self;
end;
function Pipeline.Fit(X: Matrix; y: Vector): IModel;
begin
var Xcur := X;
foreach var t in fTransformers do
begin
t.Fit(Xcur);
Xcur := t.Transform(Xcur);
end;
fModel.Fit(Xcur, y);
fFitted := true;
Result := self;
end;
function Pipeline.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new Exception('Pipeline is not fitted');
var Xt := X;
foreach var t in fTransformers do
Xt := t.Transform(Xt);
Result := Xt;
end;
function Pipeline.Predict(X: Matrix): Vector;
begin
if not fFitted then
raise new Exception('Pipeline not fitted');
var Xcur := X;
foreach var t in fTransformers do
Xcur := t.Transform(Xcur);
Result := fModel.Predict(Xcur);
end;
function Pipeline.PredictProba(X: Matrix): Vector;
begin
if not fFitted then
raise new Exception('Pipeline is not fitted');
var Xt := X;
foreach var t in fTransformers do
Xt := t.Transform(Xt);
if fModel is IProbabilisticClassifier then
exit(IProbabilisticClassifier(fModel).PredictProba(Xt));
raise new Exception('Model does not support probability prediction');
end;
//-----------------------------
// StandardScaler
//-----------------------------
function StandardScaler.Fit(X: Matrix): ITransformer;
begin
fMean := X.ColumnMeans;
fStd := X.ColumnStd;
fFitted := true;
Result := self;
end;
function StandardScaler.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new Exception('StandardScaler not fitted');
var n := X.RowCount;
var p := X.ColCount;
Result := new Matrix(n, p);
for var j := 0 to p - 1 do
begin
var mean := fMean[j];
var std := fStd[j];
for var i := 0 to n - 1 do
begin
if std <> 0 then
Result[i,j] := (X[i,j] - mean) / std
else Result[i,j] := 0.0;
end;
end;
end;
//-----------------------------
// MinMaxScaler
//-----------------------------
constructor MinMaxScaler.Create(rangeMin: real; rangeMax: real);
begin
if rangeMax <= rangeMin then
raise new Exception('rangeMax must be greater than rangeMin');
fRangeMin := rangeMin;
fRangeMax := rangeMax;
fFitted := false;
end;
function MinMaxScaler.Fit(X: Matrix): ITransformer;
begin
fMin := X.ColumnMins;
fMax := X.ColumnMaxs;
fFitted := true;
Result := self;
end;
function MinMaxScaler.Transform(X: Matrix): Matrix;
begin
if not fFitted then
raise new Exception('MinMaxScaler is not fitted');
var n := X.RowCount;
var p := X.ColCount;
Result := new Matrix(n, p);
var scale := fRangeMax - fRangeMin;
for var j := 0 to p - 1 do
begin
var minVal := fMin[j];
var maxVal := fMax[j];
var denom := maxVal - minVal;
for var i := 0 to n - 1 do
begin
if denom <> 0 then
Result[i,j] := fRangeMin + (X[i,j] - minVal) / denom * scale
else Result[i,j] := fRangeMin; // если столбец константный
end;
end;
end;
end.

329
bin/Lib/MetricsABC.pas Normal file
View file

@ -0,0 +1,329 @@
unit MetricsABC;
interface
uses LinearAlgebraML;
type
/// Метрики оценки качества моделей машинного обучения
/// для задач регрессии и классификации.
Metrics = static class
public
/// Среднеквадратичная ошибка (MSE).
/// MSE = (1/n) * Σ (yTrue - yPred)^2.
/// Чувствительна к большим отклонениям.
static function MSE(yTrue, yPred: Vector): real;
/// Средняя абсолютная ошибка (MAE).
/// MAE = (1/n) * Σ |yTrue - yPred|.
/// Менее чувствительна к выбросам, чем MSE.
static function MAE(yTrue, yPred: Vector): real;
/// Коэффициент детерминации (R²).
/// R2 = 1 - SS_res / SS_tot.
/// Показывает долю объяснённой дисперсии.
/// R2 = 1 — идеальная модель,
/// R2 = 0 — не лучше среднего,
/// R2 < 0 — хуже константной модели.
static function R2(yTrue, yPred: Vector): real;
// ---- Вероятностные метрики классификации ----
/// Логарифмическая функция потерь (LogLoss).
/// Оценивает качество вероятностных предсказаний.
/// Сильно штрафует уверенные, но ошибочные прогнозы.
/// Используется для моделей, возвращающих вероятности
/// (например, LogisticRegression).
static function LogLoss(yTrue, yProb: Vector): real;
// ---- ROC / AUC ----
/// Строит точки ROC-кривой по вероятностным предсказаниям.
/// Возвращает кортеж из двух векторов:
/// FPR (доля ложных положительных)
/// и TPR (доля истинно положительных).
static function ROC(yTrue, yProb: Vector): (Vector, Vector);
/// Площадь под ROC-кривой (AUC).
/// Значение от 0 до 1.
/// 0.5 — случайная модель,
/// 1 — идеальная модель.
static function AUC(yTrue, yProb: Vector): real;
end;
type
/// Матрица ошибок (Confusion Matrix) для бинарной классификации.
/// Хранит количество истинно положительных (TP),
/// истинно отрицательных (TN),
/// ложно положительных (FP)
/// и ложно отрицательных (FN) предсказаний.
/// Используется для анализа качества классификатора
/// и вычисления метрик Accuracy, Precision, Recall и F1
ConfusionMatrix = class
private
fTP, fTN, fFP, fFN: integer;
public
/// Создаёт матрицу ошибок по истинным меткам yTrue
/// и предсказаниям модели yPred.
/// Ожидается, что векторы содержат значения 0 и 1
/// и имеют одинаковую длину.
/// Подсчитывает TP, TN, FP и FN.
constructor Create(yTrue, yPred: Vector);
/// Истинно положительные (True Positives).
/// Количество объектов класса 1,
/// которые модель правильно предсказала как 1.
property TP: integer read fTP;
/// Истинно отрицательные (True Negatives).
/// Количество объектов класса 0,
/// которые модель правильно предсказала как 0.
property TN: integer read fTN;
/// Ложно положительные (False Positives).
/// Количество объектов класса 0,
/// которые модель ошибочно предсказала как 1.
property FP: integer read fFP;
/// Ложно отрицательные (False Negatives).
/// Количество объектов класса 1,
/// которые модель ошибочно предсказала как 0.
property FN: integer read fFN;
/// Доля правильных предсказаний.
/// Показывает, какая часть объектов классифицирована верно.
/// Удобна, когда классы примерно сбалансированы.
/// Пример: Accuracy = 0.9 означает 90% правильных ответов.
function Accuracy: real;
/// Точность (Precision) для положительного класса (1).
/// Среди всех объектов, которые модель предсказала как 1,
/// показывает долю действительно принадлежащих этому классу.
/// Важна, когда критичны ложные положительные результаты («ложные тревоги»).
function Precision: real;
/// Полнота (Recall) для положительного класса (1).
/// Среди всех объектов, которые в действительности равны 1,
/// показывает долю правильно найденных моделью.
/// Важна, когда нежелательны пропуски положительных случаев (например, заболеваний).
function Recall: real;
/// F1-мера.
/// Объединённая метрика, учитывающая и Precision, и Recall одновременно.
/// Высока только тогда, когда и Precision, и Recall высоки.
/// Используется, когда классы сильно различаются по количеству элементов
function F1: real;
end;
implementation
//-----------------------------
// Metrics
//-----------------------------
static function Metrics.MSE(yTrue, yPred: Vector): real;
begin
if yTrue.Length <> yPred.Length then
raise new Exception('Dimension mismatch in MSE');
var n := yTrue.Length;
var s := 0.0;
for var i := 0 to n - 1 do
begin
var d := yTrue[i] - yPred[i];
s += d * d;
end;
Result := s / n;
end;
static function Metrics.MAE(yTrue, yPred: Vector): real;
begin
if yTrue.Length <> yPred.Length then
raise new Exception('Dimension mismatch in MAE');
var n := yTrue.Length;
var s := 0.0;
for var i := 0 to n - 1 do
s += Abs(yTrue[i] - yPred[i]);
Result := s / n;
end;
static function Metrics.R2(yTrue, yPred: Vector): real;
begin
if yTrue.Length <> yPred.Length then
raise new Exception('Dimension mismatch in R2');
var n := yTrue.Length;
var meanY := yTrue.Mean;
var ssRes := 0.0;
var ssTot := 0.0;
for var i := 0 to n - 1 do
begin
var d1 := yTrue[i] - yPred[i];
ssRes += d1 * d1;
var d2 := yTrue[i] - meanY;
ssTot += d2 * d2;
end;
if ssTot = 0 then
exit(0.0);
Result := 1 - ssRes / ssTot;
end;
static function Metrics.LogLoss(yTrue, yProb: Vector): real;
begin
if yTrue.Length <> yProb.Length then
raise new Exception('Dimension mismatch in LogLoss');
var n := yTrue.Length;
var eps := 1e-15;
var s := 0.0;
for var i := 0 to n - 1 do
begin
var p := yProb[i];
if p < eps then p := eps;
if p > 1 - eps then p := 1 - eps;
s += yTrue[i] * Ln(p) + (1 - yTrue[i]) * Ln(1 - p);
end;
Result := -s / n;
end;
static function Metrics.ROC(yTrue, yProb: Vector): (Vector, Vector);
begin
if yTrue.Length <> yProb.Length then
raise new Exception('Dimension mismatch in ROC');
var n := yTrue.Length;
// Индексы, отсортированные по убыванию вероятностей
var idx :=
(0..n-1)
.OrderByDescending(i -> yProb[i])
.ToArray;
var tp := 0;
var fp := 0;
var totalPos := 0;
var totalNeg := 0;
for var i := 0 to n-1 do
if yTrue[i] = 1 then
totalPos += 1
else
totalNeg += 1;
var fprList := new List<real>;
var tprList := new List<real>;
for var k := 0 to n-1 do
begin
var i := idx[k];
if yTrue[i] = 1 then
tp += 1
else
fp += 1;
var tpr := 0.0;
var fpr := 0.0;
if totalPos > 0 then
tpr := tp / totalPos;
if totalNeg > 0 then
fpr := fp / totalNeg;
tprList.Add(tpr);
fprList.Add(fpr);
end;
Result := (new Vector(fprList.ToArray),
new Vector(tprList.ToArray));
end;
static function Metrics.AUC(yTrue, yProb: Vector): real;
begin
var (fpr, tpr) := ROC(yTrue, yProb);
var area := 0.0;
for var i := 1 to fpr.Length - 1 do
begin
var dx := fpr[i] - fpr[i - 1];
var avgY := (tpr[i] + tpr[i - 1]) / 2;
area += dx * avgY;
end;
Result := area;
end;
//-----------------------------
// ConfusionMatrix
//-----------------------------
constructor ConfusionMatrix.Create(yTrue, yPred: Vector);
begin
if yTrue.Length <> yPred.Length then
raise new Exception('Dimension mismatch in ConfusionMatrix');
for var i := 0 to yTrue.Length - 1 do
begin
if yTrue[i] = 1 then
if yPred[i] = 1 then
fTP += 1
else
fFN += 1
else
if yPred[i] = 1 then
fFP += 1
else
fTN += 1;
end;
end;
function ConfusionMatrix.Accuracy: real;
begin
var total := fTP + fTN + fFP + fFN;
if total = 0 then
exit(0.0);
Result := (fTP + fTN) / total;
end;
function ConfusionMatrix.Precision: real;
begin
if fTP + fFP = 0 then
exit(0.0);
Result := fTP / (fTP + fFP);
end;
function ConfusionMatrix.Recall: real;
begin
if fTP + fFN = 0 then
exit(0.0);
Result := fTP / (fTP + fFN);
end;
function ConfusionMatrix.F1: real;
begin
var p := Precision;
var r := Recall;
if p + r = 0 then
exit(0.0);
Result := 2 * p * r / (p + r);
end;
end.

View file

@ -40,7 +40,7 @@ type
/// При Fit вычисляет среднее значение и стандартное отклонение столбцов.
/// Применяет преобразование: x' = (x - mean) / std.
/// Пропущенные значения (NA) сохраняются.
StandardScaler = class(IPreprocessor)
DataStandardScaler = class(IPreprocessor)
private
cols: array of string;
means: array of real;
@ -61,7 +61,7 @@ type
/// При Fit вычисляет минимальное и максимальное значения столбцов
/// Применяет преобразование: x' = (x - min) / (max - min)
/// Пропущенные значения (NA) сохраняются
MinMaxScaler = class(IPreprocessor)
DataMinMaxScaler = class(IPreprocessor)
private
cols: array of string;
mins: array of real;
@ -153,18 +153,18 @@ type
type
/// Pipeline (конвейер) шагов подготовки данных.
/// Выполняет шаги последовательно с семантикой Fit / Transform. Pipeline = class
Pipeline = class
/// DataPipeline (конвейер) шагов подготовки данных.
/// Выполняет шаги последовательно с семантикой Fit / Transform.
DataPipeline = class
private
steps: List<IPreprocessor>;
fitted: boolean;
public
constructor;
/// Добавляет шаг в конец pipeline
function Add(p: IPreprocessor): Pipeline;
function Add(p: IPreprocessor): DataPipeline;
/// Обучает все шаги pipeline на DataFrame
function Fit(df: DataFrame): Pipeline;
function Fit(df: DataFrame): DataPipeline;
/// Применяет обученный pipeline к DataFrame
function Transform(df: DataFrame): DataFrame;
/// Выполняет Fit и Transform последовательно
@ -177,7 +177,7 @@ implementation
// StandardScaler
//-----------------------------
constructor StandardScaler.Create(params columns: array of string);
constructor DataStandardScaler.Create(params columns: array of string);
begin
if (columns = nil) or (columns.Length = 0) then
raise new ArgumentException('StandardScaler: columns not specified');
@ -186,7 +186,7 @@ begin
fitted := false;
end;
function StandardScaler.Fit(df: DataFrame): IPreprocessor;
function DataStandardScaler.Fit(df: DataFrame): IPreprocessor;
begin
var n := cols.Length;
SetLength(means, n);
@ -230,7 +230,7 @@ begin
Result := Self;
end;
function StandardScaler.Transform(df: DataFrame): DataFrame;
function DataStandardScaler.Transform(df: DataFrame): DataFrame;
begin
if not fitted then
raise new Exception('StandardScaler: not fitted');
@ -256,7 +256,7 @@ begin
Result := res;
end;
function StandardScaler.FitTransform(df: DataFrame): DataFrame;
function DataStandardScaler.FitTransform(df: DataFrame): DataFrame;
begin
Fit(df);
Result := Transform(df);
@ -266,7 +266,7 @@ end;
// MinMaxScaler
//-----------------------------
constructor MinMaxScaler.Create(params columns: array of string);
constructor DataMinMaxScaler.Create(params columns: array of string);
begin
if (columns = nil) or (columns.Length = 0) then
raise new ArgumentException('MinMaxScaler: columns not specified');
@ -275,7 +275,7 @@ begin
fitted := false;
end;
function MinMaxScaler.Fit(df: DataFrame): IPreprocessor;
function DataMinMaxScaler.Fit(df: DataFrame): IPreprocessor;
begin
var n := cols.Length;
SetLength(mins, n);
@ -326,7 +326,7 @@ begin
Result := Self;
end;
function MinMaxScaler.Transform(df: DataFrame): DataFrame;
function DataMinMaxScaler.Transform(df: DataFrame): DataFrame;
begin
if not fitted then
raise new Exception('MinMaxScaler: not fitted');
@ -353,7 +353,7 @@ begin
Result := res;
end;
function MinMaxScaler.FitTransform(df: DataFrame): DataFrame;
function DataMinMaxScaler.FitTransform(df: DataFrame): DataFrame;
begin
Fit(df);
Result := Transform(df);
@ -678,13 +678,13 @@ end;
// Pipeline
//-----------------------------
constructor Pipeline.Create;
constructor DataPipeline.Create;
begin
steps := new List<IPreprocessor>;
fitted := false;
end;
function Pipeline.Add(p: IPreprocessor): Pipeline;
function DataPipeline.Add(p: IPreprocessor): DataPipeline;
begin
if fitted then
raise new Exception('Cannot add step after Fit');
@ -693,7 +693,7 @@ begin
Result := Self;
end;
function Pipeline.Fit(df: DataFrame): Pipeline;
function DataPipeline.Fit(df: DataFrame): DataPipeline;
begin
var current := df;
@ -707,7 +707,7 @@ begin
Result := Self;
end;
function Pipeline.Transform(df: DataFrame): DataFrame;
function DataPipeline.Transform(df: DataFrame): DataFrame;
begin
if not fitted then
raise new Exception('Pipeline is not fitted');
@ -719,7 +719,7 @@ begin
Result := current;
end;
function Pipeline.FitTransform(df: DataFrame): DataFrame;
function DataPipeline.FitTransform(df: DataFrame): DataFrame;
begin
Fit(df);
Result := Transform(df);

270
bin/Lib/ValidationML.pas Normal file
View file

@ -0,0 +1,270 @@
unit ValidationML;
interface
uses LinearAlgebraML, MLModelsABC;
type
Validation = static class
public
/// Делит данные на обучающую и тестовую выборки.
/// testRatio — доля объектов, попадающих в тестовую выборку (по умолчанию 0.2).
/// Перемешивает объекты перед разбиением.
/// Возвращает (X_train, X_test, y_train, y_test).
static function TrainTestSplit(X: Matrix; y: Vector;
testRatio: real := 0.2; seed: integer := 0): (Matrix, Matrix, Vector, Vector);
/// Разбивает индексы объектов на k непересекающихся частей (fold).
/// На каждом шаге одна часть используется как тестовая, остальные — как обучающая.
/// Применяется для k-fold кросс-валидации.
/// Возвращает последовательность пар (trainIdx, testIdx).
static function KFold(n, k: integer; seed: integer := 0):
sequence of (array of integer, array of integer);
/// Разбивает данные на k частей с сохранением пропорций классов.
/// В каждой части сохраняется примерно то же соотношение
/// объектов разных классов, что и во всей выборке.
/// Рекомендуется для задач классификации,
/// особенно при неравномерном распределении классов.
/// Возвращает последовательность пар (trainIdx, testIdx).
static function StratifiedKFold(y: Vector; k: integer;
seed: integer := 0): sequence of (array of integer, array of integer);
/// Выполняет k-fold кросс-валидацию модели.
/// На каждом шаге модель обучается на обучающей части и оценивается на тестовой.
/// metric — функция качества (например, Accuracy или MSE).
/// Возвращает среднее значение метрики по всем частям.
static function CrossValidate(model: IModel; X: Matrix; y: Vector;
k: integer; metric: (Vector,Vector) -> real; seed: integer := 0): real;
/// Выполняет k-fold кросс-валидацию с сохранением пропорций классов.
/// Использует StratifiedKFold для разбиения данных.
/// Подходит для задач классификации.
/// Возвращает среднее значение метрики по всем частям.
static function StratifiedCrossValidate(model: IModel; X: Matrix; y: Vector;
k: integer; metric: (Vector,Vector) -> real; seed: integer := 0): real;
end;
implementation
static function Validation.TrainTestSplit(X: Matrix; y: Vector;
testRatio: real; seed: integer): (Matrix, Matrix, Vector, Vector);
begin
if X.RowCount <> y.Length then
raise new Exception('Dimension mismatch in TrainTestSplit');
if (testRatio <= 0) or (testRatio >= 1) then
raise new Exception('testRatio must be in (0,1)');
var n := X.RowCount;
var p := X.ColCount;
var rnd := new System.Random(seed);
// Перемешанные индексы
var idx := (0..n-1).ToArray;
idx := idx.OrderBy(i -> rnd.Next).ToArray;
var testSize := Round(n * testRatio);
var trainSize := n - testSize;
var X_train := new Matrix(trainSize, p);
var X_test := new Matrix(testSize, p);
var y_train := new Vector(trainSize);
var y_test := new Vector(testSize);
// Заполняем train
for var i := 0 to trainSize - 1 do
begin
var row := idx[i];
for var j := 0 to p - 1 do
X_train[i,j] := X[row,j];
y_train[i] := y[row];
end;
// Заполняем test
for var i := 0 to testSize - 1 do
begin
var row := idx[trainSize + i];
for var j := 0 to p - 1 do
X_test[i,j] := X[row,j];
y_test[i] := y[row];
end;
Result := (X_train, X_test, y_train, y_test);
end;
static function Validation.KFold(n, k: integer; seed: integer):
sequence of (array of integer, array of integer);
begin
if (k < 2) or (k > n) then
raise new Exception('Invalid k in KFold');
var rnd := new System.Random(seed);
var idx := (0..n-1).OrderBy(i -> rnd.Next).ToArray;
var baseSize := n div k;
var extra := n mod k;
var start := 0;
for var fold := 0 to k - 1 do
begin
var size := baseSize + (if fold < extra then 1 else 0);
var testIdx := idx.Skip(start).Take(size).ToArray;
var trainIdx := idx.Take(start).Concat(idx.Skip(start + size)).ToArray;
yield (trainIdx, testIdx);
start += size;
end;
end;
static function Validation.StratifiedKFold(y: Vector; k: integer;
seed: integer): sequence of (array of integer, array of integer);
begin
var n := y.Length;
if (k < 2) or (k > n) then
raise new Exception('Invalid k in StratifiedKFold');
var rnd := new System.Random(seed);
var idx0 := (0..n-1)
.Where(i -> y[i] = 0)
.OrderBy(i -> rnd.Next)
.ToArray;
var idx1 := (0..n-1)
.Where(i -> y[i] = 1)
.OrderBy(i -> rnd.Next)
.ToArray;
if idx0.Length + idx1.Length <> n then
raise new Exception('StratifiedKFold supports only 0/1 labels');
var base0 := idx0.Length div k;
var extra0 := idx0.Length mod k;
var base1 := idx1.Length div k;
var extra1 := idx1.Length mod k;
for var fold := 0 to k - 1 do
begin
var start0 := fold * base0 + Min(fold, extra0);
var size0 := base0 + (if fold < extra0 then 1 else 0);
var start1 := fold * base1 + Min(fold, extra1);
var size1 := base1 + (if fold < extra1 then 1 else 0);
var testIdx :=
idx0.Skip(start0).Take(size0)
.Concat(idx1.Skip(start1).Take(size1))
.ToArray;
var trainIdx :=
(0..n-1)
.Where(i -> not testIdx.Contains(i))
.ToArray;
yield (trainIdx, testIdx);
end;
end;
static function Validation.CrossValidate(model: IModel; X: Matrix; y: Vector;
k: integer; metric: (Vector,Vector) -> real; seed: integer): real;
begin
if X.RowCount <> y.Length then
raise new Exception('Dimension mismatch in CrossValidate');
var total := 0.0;
var folds := 0;
var p := X.ColCount;
foreach var (trainIdx, testIdx) in KFold(X.RowCount, k, seed) do
begin
var Xtr := new Matrix(trainIdx.Length, p);
var ytr := new Vector(trainIdx.Length);
for var i := 0 to trainIdx.Length - 1 do
begin
var r := trainIdx[i];
for var j := 0 to p - 1 do
Xtr[i,j] := X[r,j];
ytr[i] := y[r];
end;
var Xte := new Matrix(testIdx.Length, p);
var yte := new Vector(testIdx.Length);
for var i := 0 to testIdx.Length - 1 do
begin
var r := testIdx[i];
for var j := 0 to p - 1 do
Xte[i,j] := X[r,j];
yte[i] := y[r];
end;
var m := model.Fit(Xtr, ytr);
var pred := m.Predict(Xte);
total += metric(yte, pred);
folds += 1;
end;
Result := total / folds;
end;
static function Validation.StratifiedCrossValidate(
model: IModel; X: Matrix; y: Vector;
k: integer; metric: (Vector,Vector) -> real; seed: integer
): real;
begin
if X.RowCount <> y.Length then
raise new Exception('Dimension mismatch in StratifiedCrossValidate');
var total := 0.0;
var folds := 0;
var p := X.ColCount;
foreach var (trainIdx, testIdx) in StratifiedKFold(y, k, seed) do
begin
var Xtr := new Matrix(trainIdx.Length, p);
var ytr := new Vector(trainIdx.Length);
for var i := 0 to trainIdx.Length - 1 do
begin
var r := trainIdx[i];
for var j := 0 to p - 1 do
Xtr[i,j] := X[r,j];
ytr[i] := y[r];
end;
var Xte := new Matrix(testIdx.Length, p);
var yte := new Vector(testIdx.Length);
for var i := 0 to testIdx.Length - 1 do
begin
var r := testIdx[i];
for var j := 0 to p - 1 do
Xte[i,j] := X[r,j];
yte[i] := y[r];
end;
var m := model.Fit(Xtr, ytr);
var pred := m.Predict(Xte);
total += metric(yte, pred);
folds += 1;
end;
Result := total / folds;
end;
end.