From bc77b1564a4e70486cc0890e76832fa3cb74d8b0 Mon Sep 17 00:00:00 2001 From: Mikhalkovich Stanislav Date: Sat, 14 Feb 2026 11:55:00 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B5=20ML-=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D1=83=D0=BB=D0=B8=20=D0=B8=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReleaseGenerators/RebuildStandartModules.pas | 3 +- ReleaseGenerators/sect_Core.nsh | 21 + bin/Lib/DataFrameABC.pas | 82 ++- bin/Lib/DataFrameABCCore.pas | 44 ++ bin/Lib/LinearAlgebraML.pas | 286 +++++++++- bin/Lib/MLABC.pas | 84 +++ bin/Lib/MLCoreABC.pas | 53 ++ bin/Lib/MLModelsABC.pas | 560 +++++++++++++++++++ bin/Lib/MetricsABC.pas | 329 +++++++++++ bin/Lib/PreprocessorABC.pas | 40 +- bin/Lib/ValidationML.pas | 270 +++++++++ 11 files changed, 1717 insertions(+), 55 deletions(-) create mode 100644 bin/Lib/MLABC.pas create mode 100644 bin/Lib/MLCoreABC.pas create mode 100644 bin/Lib/MLModelsABC.pas create mode 100644 bin/Lib/MetricsABC.pas create mode 100644 bin/Lib/ValidationML.pas diff --git a/ReleaseGenerators/RebuildStandartModules.pas b/ReleaseGenerators/RebuildStandartModules.pas index 127e9ba3c..92c1f6b70 100644 --- a/ReleaseGenerators/RebuildStandartModules.pas +++ b/ReleaseGenerators/RebuildStandartModules.pas @@ -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 diff --git a/ReleaseGenerators/sect_Core.nsh b/ReleaseGenerators/sect_Core.nsh index 2e54b3eae..40b9dfe19 100644 --- a/ReleaseGenerators/sect_Core.nsh +++ b/ReleaseGenerators/sect_Core.nsh @@ -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" diff --git a/bin/Lib/DataFrameABC.pas b/bin/Lib/DataFrameABC.pas index 29d59f7b2..f39bb5497 100644 --- a/bin/Lib/DataFrameABC.pas +++ b/bin/Lib/DataFrameABC.pas @@ -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; /// Возвращает статистику по нескольким столбцам по индексам function Describe(colIndices: array of integer): Dictionary; - /// Возвращает статистику по всем числовым столбц ам + /// Возвращает статистику по всем числовым столбцам function DescribeAll: Dictionary; /// Группирует данные по столбцу по индексу @@ -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 diff --git a/bin/Lib/DataFrameABCCore.pas b/bin/Lib/DataFrameABCCore.pas index b1fd37fac..6d365812d 100644 --- a/bin/Lib/DataFrameABCCore.pas +++ b/bin/Lib/DataFrameABCCore.pas @@ -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; diff --git a/bin/Lib/LinearAlgebraML.pas b/bin/Lib/LinearAlgebraML.pas index fdf059ee2..4f06ecd5f 100644 --- a/bin/Lib/LinearAlgebraML.pas +++ b/bin/Lib/LinearAlgebraML.pas @@ -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 diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas new file mode 100644 index 000000000..807670951 --- /dev/null +++ b/bin/Lib/MLABC.pas @@ -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. \ No newline at end of file diff --git a/bin/Lib/MLCoreABC.pas b/bin/Lib/MLCoreABC.pas new file mode 100644 index 000000000..3092b64fd --- /dev/null +++ b/bin/Lib/MLCoreABC.pas @@ -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. \ No newline at end of file diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas new file mode 100644 index 000000000..69ec9aa32 --- /dev/null +++ b/bin/Lib/MLModelsABC.pas @@ -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; + 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; + 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. \ No newline at end of file diff --git a/bin/Lib/MetricsABC.pas b/bin/Lib/MetricsABC.pas new file mode 100644 index 000000000..dd3233e3b --- /dev/null +++ b/bin/Lib/MetricsABC.pas @@ -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; + var tprList := new List; + + 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. \ No newline at end of file diff --git a/bin/Lib/PreprocessorABC.pas b/bin/Lib/PreprocessorABC.pas index 9bfed56af..3a5b68ec2 100644 --- a/bin/Lib/PreprocessorABC.pas +++ b/bin/Lib/PreprocessorABC.pas @@ -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; 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; 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); diff --git a/bin/Lib/ValidationML.pas b/bin/Lib/ValidationML.pas new file mode 100644 index 000000000..173700545 --- /dev/null +++ b/bin/Lib/ValidationML.pas @@ -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.