From 94927c73e1f8e30adeafb4889a7bf6b7c7534a17 Mon Sep 17 00:00:00 2001 From: Mikhalkovich Stanislav Date: Tue, 14 Apr 2026 13:23:08 +0300 Subject: [PATCH] =?UTF-8?q?ML=20-=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20Clone,=20IsValid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/Lib/DataFrameABC.pas | 328 +++------- bin/Lib/DataFrameABCCore.pas | 182 +++--- bin/Lib/LinearAlgebraML.pas | 11 +- bin/Lib/MLABC.pas | 21 +- bin/Lib/MLModelsABC.pas | 1168 ++++++++++++++++++---------------- bin/Lib/MLUtilsABC.pas | 32 + bin/Lib/PreprocessorABC.pas | 69 +- 7 files changed, 918 insertions(+), 893 deletions(-) diff --git a/bin/Lib/DataFrameABC.pas b/bin/Lib/DataFrameABC.pas index defb25c4f..d93eb6a5c 100644 --- a/bin/Lib/DataFrameABC.pas +++ b/bin/Lib/DataFrameABC.pas @@ -1,6 +1,11 @@ -// Copyright (c) Ivan Bondarev, Stanislav Mikhalkovich (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) -// DataFrameABC v.1.1 +// ============================================================= +// СТАТИСТИЧЕСКОЕ СОГЛАШЕНИЕ (DataFrame) +// +// Используется выборочная дисперсия (деление на n - 1), +// как принято в описательной статистике. +// +// См. статистическую политику в модуле MLABC. +// ============================================================= /// Стандартный модуль для работы с табличными данными (датасетами) /// !! DataFrame module for tabular data processing @@ -95,7 +100,6 @@ type function GetColumn(name: string): Column; function CloneWithCopiedColumns: DataFrame; - static procedure EnsureValidArray(var valid: array of boolean; rowCount, filledCount: integer); public /// Создает пустой DataFrame constructor Create; @@ -692,17 +696,17 @@ begin if li >= 0 then begin data[i] := leftCol.Data[li]; - valid[i] := if leftCol.IsValid = nil then true else leftCol.IsValid[li]; + valid[i] := leftCol.IsValid[li]; end else if ri >= 0 then begin data[i] := rightCol.Data[ri]; - valid[i] := if rightCol.IsValid = nil then true else rightCol.IsValid[ri]; + valid[i] := rightCol.IsValid[ri]; end else begin data[i] := 0; - valid[i] := false; + valid[i] := False; end; end; @@ -729,17 +733,17 @@ begin if li >= 0 then begin data[i] := leftCol.Data[li]; - valid[i] := if leftCol.IsValid = nil then true else leftCol.IsValid[li]; + valid[i] := leftCol.IsValid[li]; end else if ri >= 0 then begin data[i] := rightCol.Data[ri]; - valid[i] := if rightCol.IsValid = nil then true else rightCol.IsValid[ri]; + valid[i] := rightCol.IsValid[ri]; end else begin data[i] := 0.0; - valid[i] := false; + valid[i] := False; end; end; @@ -766,17 +770,17 @@ begin if li >= 0 then begin data[i] := leftCol.Data[li]; - valid[i] := if leftCol.IsValid = nil then true else leftCol.IsValid[li]; + valid[i] := leftCol.IsValid[li]; end else if ri >= 0 then begin data[i] := rightCol.Data[ri]; - valid[i] := if rightCol.IsValid = nil then true else rightCol.IsValid[ri]; + valid[i] := rightCol.IsValid[ri]; end else begin data[i] := ''; - valid[i] := false; + valid[i] := False; end; end; @@ -803,12 +807,12 @@ begin if li >= 0 then begin data[i] := leftCol.Data[li]; - valid[i] := if leftCol.IsValid = nil then true else leftCol.IsValid[li]; + valid[i] := leftCol.IsValid[li]; end else if ri >= 0 then begin data[i] := rightCol.Data[ri]; - valid[i] := if rightCol.IsValid = nil then true else rightCol.IsValid[ri]; + valid[i] := rightCol.IsValid[ri]; end else begin @@ -838,12 +842,12 @@ begin if j < 0 then begin data[i] := 0; - valid[i] := false; + valid[i] := False; end else begin data[i] := src.Data[j]; - valid[i] := if src.IsValid = nil then true else src.IsValid[j]; + valid[i] := src.IsValid[j]; end; end; @@ -868,12 +872,12 @@ begin if j < 0 then begin data[i] := 0.0; - valid[i] := false; + valid[i] := False; end else begin data[i] := src.Data[j]; - valid[i] := if src.IsValid = nil then true else src.IsValid[j]; + valid[i] := src.IsValid[j]; end; end; @@ -898,12 +902,12 @@ begin if j < 0 then begin data[i] := ''; - valid[i] := false; + valid[i] := False; end else begin data[i] := src.Data[j]; - valid[i] := if src.IsValid = nil then true else src.IsValid[j]; + valid[i] := src.IsValid[j]; end; end; @@ -927,13 +931,13 @@ begin if j < 0 then begin - data[i] := false; - valid[i] := false; + data[i] := False; + valid[i] := False; end else begin data[i] := src.Data[j]; - valid[i] := if src.IsValid = nil then true else src.IsValid[j]; + valid[i] := src.IsValid[j]; end; end; @@ -2223,21 +2227,10 @@ end; procedure DataFrame.AddIntColumn(name: string; data: array of integer; valid: array of boolean); begin - if (columns.Count > 0) and (data.Length <> RowCount) then + if (columns.Count > 0) and (Length(data) <> RowCount) then DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); - var c := new IntColumn; - c.Info := new ColumnInfo(name, ctInt); - c.Data := data; - - if valid = nil then - c.IsValid := [True] * data.Length - else - begin - if valid.Length <> data.Length then - DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH); - c.IsValid := valid; - end; + var c := new IntColumn(name, data, valid); columns.Add(c); RebuildSchema; @@ -2245,21 +2238,10 @@ end; procedure DataFrame.AddFloatColumn(name: string; data: array of real; valid: array of boolean); begin - if (columns.Count > 0) and (data.Length <> RowCount) then + if (columns.Count > 0) and (Length(data) <> RowCount) then DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); - var c := new FloatColumn; - c.Info := new ColumnInfo(name, ctFloat); - c.Data := data; - - if valid = nil then - c.IsValid := [True] * data.Length - else - begin - if valid.Length <> data.Length then - DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH); - c.IsValid := valid; - end; + var c := new FloatColumn(name, data, valid); columns.Add(c); RebuildSchema; @@ -2267,21 +2249,10 @@ end; procedure DataFrame.AddStrColumn(name: string; data: array of string; valid: array of boolean); begin - if (columns.Count > 0) and (data.Length <> RowCount) then + if (columns.Count > 0) and (Length(data) <> RowCount) then DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); - var c := new StrColumn; - c.Info := new ColumnInfo(name, ctStr); - c.Data := data; - - if valid = nil then - c.IsValid := [True] * data.Length - else - begin - if valid.Length <> data.Length then - DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH); - c.IsValid := valid; - end; + var c := new StrColumn(name, data, valid); columns.Add(c); RebuildSchema; @@ -2295,27 +2266,15 @@ end; procedure DataFrame.AddBoolColumn(name: string; data: array of boolean; valid: array of boolean); begin - if (columns.Count > 0) and (data.Length <> RowCount) then + if (columns.Count > 0) and (Length(data) <> RowCount) then DimensionError(ER_ADD_COLUMN_ROW_MISMATCH); - var c := new BoolColumn; - c.Info := new ColumnInfo(name, ctBool); - c.Data := data; - - if valid = nil then - c.IsValid := [True] * data.Length - else - begin - if valid.Length <> data.Length then - DimensionError(ER_COLUMN_VALID_LENGTH_MISMATCH); - c.IsValid := valid; - end; + var c := new BoolColumn(name, data, valid); columns.Add(c); RebuildSchema; end; - procedure DataFrame.CheckColumnIndex(colIndex: integer); begin if (colIndex < 0) or (colIndex >= ColumnCount) then @@ -2917,17 +2876,13 @@ begin var src := IntColumn(col); var data := new integer[newCount]; - var valid: array of boolean := nil; - - if src.IsValid <> nil then - valid := new boolean[newCount]; + var valid := new boolean[newCount]; for var k := 0 to newCount - 1 do begin var i := mask[k]; data[k] := src.Data[i]; - if valid <> nil then - valid[k] := src.IsValid[i]; + valid[k] := src.IsValid[i]; end; res.AddIntColumn(src.Info.Name, data, valid); @@ -2939,17 +2894,13 @@ begin var src := FloatColumn(col); var data := new real[newCount]; - var valid: array of boolean := nil; - - if src.IsValid <> nil then - valid := new boolean[newCount]; + var valid := new boolean[newCount]; for var k := 0 to newCount - 1 do begin var i := mask[k]; data[k] := src.Data[i]; - if valid <> nil then - valid[k] := src.IsValid[i]; + valid[k] := src.IsValid[i]; end; res.AddFloatColumn(src.Info.Name, data, valid); @@ -2961,17 +2912,13 @@ begin var src := StrColumn(col); var data := new string[newCount]; - var valid: array of boolean := nil; - - if src.IsValid <> nil then - valid := new boolean[newCount]; + var valid := new boolean[newCount]; for var k := 0 to newCount - 1 do begin var i := mask[k]; data[k] := src.Data[i]; - if valid <> nil then - valid[k] := src.IsValid[i]; + valid[k] := src.IsValid[i]; end; res.AddStrColumn(src.Info.Name, data, valid); @@ -2983,17 +2930,13 @@ begin var src := BoolColumn(col); var data := new boolean[newCount]; - var valid: array of boolean := nil; - - if src.IsValid <> nil then - valid := new boolean[newCount]; + var valid := new boolean[newCount]; for var k := 0 to newCount - 1 do begin var i := mask[k]; data[k] := src.Data[i]; - if valid <> nil then - valid[k] := src.IsValid[i]; + valid[k] := src.IsValid[i]; end; res.AddBoolColumn(src.Info.Name, data, valid); @@ -3251,16 +3194,6 @@ begin end; end; -static procedure DataFrame.EnsureValidArray(var valid: array of boolean; rowCount, filledCount: integer); -begin - if valid = nil then - begin - valid := new boolean[rowCount]; - for var j := 0 to filledCount - 1 do - valid[j] := true; - end; -end; - function DataFrame.WithColumnInt(name: string; f: DataFrameCursor -> integer): DataFrame; begin if fSchema.HasColumn(name) then @@ -3269,7 +3202,7 @@ begin var res := CloneWithCopiedColumns; var data := new integer[RowCount]; - var valid: array of boolean := nil; + var valid := new boolean[RowCount]; var cur := GetCursor; var i := 0; @@ -3282,15 +3215,13 @@ begin on e: Exception do begin data[i] := 0; - EnsureValidArray(valid, RowCount, i); - valid[i] := false; + valid[i] := False; i += 1; continue; end; end; - if valid <> nil then - valid[i] := true; + valid[i] := True; i += 1; end; @@ -3310,7 +3241,7 @@ begin var res := CloneWithCopiedColumns; var data := new real[RowCount]; - var valid: array of boolean := nil; + var valid := new boolean[RowCount]; var cur := GetCursor; var i := 0; @@ -3323,15 +3254,13 @@ begin on e: Exception do begin data[i] := 0.0; - EnsureValidArray(valid, RowCount, i); - valid[i] := false; + valid[i] := False; i += 1; continue; end; end; - if valid <> nil then - valid[i] := true; + valid[i] := True; i += 1; end; @@ -3351,7 +3280,7 @@ begin var res := CloneWithCopiedColumns; var data := new string[RowCount]; - var valid: array of boolean := nil; + var valid := new boolean[RowCount]; var cur := GetCursor; var i := 0; @@ -3364,15 +3293,13 @@ begin on e: Exception do begin data[i] := nil; - EnsureValidArray(valid, RowCount, i); - valid[i] := false; + valid[i] := False; i += 1; continue; end; end; - if valid <> nil then - valid[i] := true; + valid[i] := True; i += 1; end; @@ -3392,7 +3319,7 @@ begin var res := CloneWithCopiedColumns; var data := new boolean[RowCount]; - var valid: array of boolean := nil; + var valid := new boolean[RowCount]; var cur := GetCursor; var i := 0; @@ -3404,16 +3331,14 @@ begin except on e: Exception do begin - data[i] := false; - EnsureValidArray(valid, RowCount, i); - valid[i] := false; + data[i] := False; + valid[i] := False; i += 1; continue; end; end; - if valid <> nil then - valid[i] := true; + valid[i] := True; i += 1; end; @@ -3458,7 +3383,7 @@ begin var rowCount := RowCount; var data := new real[rowCount]; - var valid: array of boolean := nil; + var valid := new boolean[rowCount]; var cur := GetCursor; var row := 0; @@ -3469,14 +3394,6 @@ begin if not cur.IsValid(colIndex) then begin data[row] := 0.0; - - if valid = nil then - begin - valid := new boolean[rowCount]; - for var j := 0 to row - 1 do - valid[j] := true; - end; - valid[row] := false; row += 1; continue; @@ -3489,22 +3406,13 @@ begin on e: Exception do begin data[row] := 0.0; - - if valid = nil then - begin - valid := new boolean[rowCount]; - for var j := 0 to row - 1 do - valid[j] := true; - end; - - valid[row] := false; + valid[row] := False; row += 1; continue; end; end; - if valid <> nil then - valid[row] := true; + valid[row] := True; row += 1; end; @@ -3540,7 +3448,7 @@ begin var rowCount := RowCount; var data := new integer[rowCount]; - var valid: array of boolean := nil; + var valid := new boolean[rowCount]; var cur := GetCursor; var row := 0; @@ -3548,17 +3456,12 @@ begin begin try data[row] := f(cur); - if valid <> nil then valid[row] := true; + valid[row] := True; except on e: Exception do begin data[row] := 0; - if valid = nil then - begin - valid := new boolean[rowCount]; - for var j := 0 to row - 1 do valid[j] := true; - end; - valid[row] := false; + valid[row] := False; end; end; row += 1; @@ -3597,7 +3500,7 @@ begin var rowCount := RowCount; var data := new integer[rowCount]; - var valid: array of boolean := nil; + var valid := new boolean[rowCount]; var cur := GetCursor; var row := 0; @@ -3605,17 +3508,12 @@ begin begin try data[row] := f(cur); - if valid <> nil then valid[row] := true; + valid[row] := True; except on e: Exception do begin data[row] := 0; - if valid = nil then - begin - valid := new boolean[rowCount]; - for var j := 0 to row - 1 do valid[j] := true; - end; - valid[row] := false; + valid[row] := False; end; end; row += 1; @@ -3863,11 +3761,7 @@ begin begin var src := IntColumn(col); var data := new integer[k]; - var validSrc := src.IsValid; - var validDst: array of boolean := nil; - - if validSrc <> nil then - validDst := new boolean[k]; + var validDst := new boolean[k]; for var j := 0 to k - 1 do begin @@ -3876,15 +3770,8 @@ begin if (i < 0) or (i >= RowCount) then ArgumentError(ER_ROW_INDEX_OUT_OF_RANGE, i); - if validSrc = nil then - data[j] := src.Data[i] - else if validSrc[i] then - begin - data[j] := src.Data[i]; - validDst[j] := true; - end - else - validDst[j] := false; + data[j] := src.Data[i]; + validDst[j] := src.IsValid[i]; end; res.AddIntColumn(name, data, validDst); @@ -3894,11 +3781,7 @@ begin begin var src := FloatColumn(col); var data := new real[k]; - var validSrc := src.IsValid; - var validDst: array of boolean := nil; - - if validSrc <> nil then - validDst := new boolean[k]; + var validDst := new boolean[k]; for var j := 0 to k - 1 do begin @@ -3907,15 +3790,8 @@ begin if (i < 0) or (i >= RowCount) then ArgumentError(ER_ROW_INDEX_OUT_OF_RANGE, i); - if validSrc = nil then - data[j] := src.Data[i] - else if validSrc[i] then - begin - data[j] := src.Data[i]; - validDst[j] := true; - end - else - validDst[j] := false; + data[j] := src.Data[i]; + validDst[j] := src.IsValid[i]; end; res.AddFloatColumn(name, data, validDst); @@ -3925,11 +3801,7 @@ begin begin var src := StrColumn(col); var data := new string[k]; - var validSrc := src.IsValid; - var validDst: array of boolean := nil; - - if validSrc <> nil then - validDst := new boolean[k]; + var validDst := new boolean[k]; for var j := 0 to k - 1 do begin @@ -3938,15 +3810,8 @@ begin if (i < 0) or (i >= RowCount) then ArgumentError(ER_ROW_INDEX_OUT_OF_RANGE, i); - if validSrc = nil then - data[j] := src.Data[i] - else if validSrc[i] then - begin - data[j] := src.Data[i]; - validDst[j] := true; - end - else - validDst[j] := false; + data[j] := src.Data[i]; + validDst[j] := src.IsValid[i]; end; res.AddStrColumn(name, data, validDst); @@ -3956,11 +3821,7 @@ begin begin var src := BoolColumn(col); var data := new boolean[k]; - var validSrc := src.IsValid; - var validDst: array of boolean := nil; - - if validSrc <> nil then - validDst := new boolean[k]; + var validDst := new boolean[k]; for var j := 0 to k - 1 do begin @@ -3969,15 +3830,8 @@ begin if (i < 0) or (i >= RowCount) then ArgumentError(ER_ROW_INDEX_OUT_OF_RANGE, i); - if validSrc = nil then - data[j] := src.Data[i] - else if validSrc[i] then - begin - data[j] := src.Data[i]; - validDst[j] := true; - end - else - validDst[j] := false; + data[j] := src.Data[i]; + validDst[j] := src.IsValid[i]; end; res.AddBoolColumn(name, data, validDst); @@ -4305,13 +4159,10 @@ begin case src.Info.ColType of ctInt: AddIntColumn(src.Info.Name, IntColumn(src).Data, IntColumn(src).IsValid); - ctFloat: AddFloatColumn(src.Info.Name, FloatColumn(src).Data, FloatColumn(src).IsValid); - ctStr: AddStrColumn(src.Info.Name, StrColumn(src).Data, StrColumn(src).IsValid); - ctBool: AddBoolColumn(src.Info.Name, BoolColumn(src).Data, BoolColumn(src).IsValid); end; @@ -4559,7 +4410,7 @@ begin begin var i := indices[j]; - if (valid <> nil) and not valid[i] then + if not valid[i] then continue; s += if isInt then dataInt[i] else dataFloat[i]; @@ -4586,7 +4437,7 @@ begin begin var i := indices[j]; - if (valid <> nil) and not valid[i] then + if not valid[i] then continue; s += if isInt then dataInt[i] else dataFloat[i]; @@ -4614,7 +4465,7 @@ begin begin var i := indices[j]; - if (valid <> nil) and not valid[i] then + if not valid[i] then continue; var v := if isInt then dataInt[i] else dataFloat[i]; @@ -4647,7 +4498,7 @@ begin begin var i := indices[j]; - if (valid <> nil) and not valid[i] then + if not valid[i] then continue; var v := if isInt then dataInt[i] else dataFloat[i]; @@ -5578,7 +5429,7 @@ begin if cur.IsValid(i) then col.Data[row] := (cur.Float(i) - means[i]) / stds[i] else - col.IsValid[row] := false; + col.IsValid[row] := False; end; row += 1; @@ -5661,20 +5512,11 @@ begin if cur.IsValid(i) then begin col.Data[row] := (cur.Float(i) - mins[i]) / (maxs[i] - mins[i]); - if col.IsValid <> nil then - col.IsValid[row] := true; + col.IsValid[row] := True; end else - begin // первый NA → создаём IsValid - if col.IsValid = nil then - begin - col.IsValid := new boolean[df.RowCount]; - for var r := 0 to row - 1 do - col.IsValid[r] := true; - end; - col.IsValid[row] := false; - end; + col.IsValid[row] := False; end; row += 1; end; diff --git a/bin/Lib/DataFrameABCCore.pas b/bin/Lib/DataFrameABCCore.pas index b58525a17..2d73a9236 100644 --- a/bin/Lib/DataFrameABCCore.pas +++ b/bin/Lib/DataFrameABCCore.pas @@ -1,5 +1,20 @@ unit DataFrameABCCore; +// ============================================================= +// ИНВАРИАНТ ВАЛИДНОСТИ СТОЛБЦОВ +// +// Для всех столбцов: +// • IsValid всегда инициализирован +// • Length(IsValid) = Length(Data) +// • nil не используется как специальное значение +// +// Пустой столбец: +// • Data имеет длину 0 +// • IsValid = new boolean[0] +// +// Нарушение этого инварианта считается ошибкой. +// ============================================================= + interface type @@ -275,7 +290,9 @@ const 'Неизвестный тип столбца!!Unknown column type'; ER_ROW_INDEX_OUT_OF_RANGE = 'Индекс строки {0} вне диапазона [0..{1})!!' + - 'Row index {0} out of range [0..{1})'; + 'Row index {0} out of range [0..{1})'; + ER_INVALID_ISVALID_LENGTH = + 'Длина IsValid должна совпадать с длиной Data!!IsValid length must match Data length'; //----------------------------- // Сервисные функции @@ -547,14 +564,23 @@ end; // Columns //----------------------------- -constructor IntColumn.Create(name: string; data: array of integer; - valid: array of boolean); +constructor IntColumn.Create(name: string; data: array of integer; valid: array of boolean); begin inherited Create; Info := new ColumnInfo(name, ctInt); - Data := data; - IsValid := valid; + var n := Length(data); + Data := if n = 0 then [] else data; + + if valid = nil then + IsValid := [True] * n + else + begin + if Length(valid) <> n then + Error(ER_INVALID_ISVALID_LENGTH); + + IsValid := valid; + end; end; constructor IntColumn.Create(name: string); @@ -562,55 +588,73 @@ begin inherited Create; Info := new ColumnInfo(name, ctInt); - Data := new integer[0]; - IsValid := nil; + Data := []; + IsValid := []; end; function IntColumn.TryGetNumericValue(i: integer; var value: real): boolean; begin - if (IsValid <> nil) and not IsValid[i] then - exit(false); + if not IsValid[i] then + exit(False); value := Data[i]; - exit(true); + exit(True); end; -constructor FloatColumn.Create(name: string; data: array of real; - valid: array of boolean); +constructor FloatColumn.Create(name: string; data: array of real; valid: array of boolean); begin inherited Create; Info := new ColumnInfo(name, ctFloat); - Data := data; - IsValid := valid; + var n := Length(data); + Data := if n = 0 then [] else data; + + if valid = nil then + IsValid := [True] * n + else + begin + if Length(valid) <> n then + Error(ER_INVALID_ISVALID_LENGTH); + + IsValid := valid; + end; end; constructor FloatColumn.Create(name: string); begin inherited Create; Info := new ColumnInfo(name, ctFloat); - Data := new real[0]; - IsValid := nil; -end; + Data := []; + IsValid := []; +end; function FloatColumn.TryGetNumericValue(i: integer; var value: real): boolean; begin - if (IsValid <> nil) and not IsValid[i] then - exit(false); + if not IsValid[i] then + exit(False); value := Data[i]; - exit(true); + exit(True); end; -constructor StrColumn.Create(name: string; data: array of string; - valid: array of boolean); +constructor StrColumn.Create(name: string; data: array of string; valid: array of boolean); begin inherited Create; Info := new ColumnInfo(name, ctStr); - Data := data; - IsValid := valid; + var n := Length(data); + Data := if n = 0 then [] else data; + + if valid = nil then + IsValid := [True] * n + else + begin + if Length(valid) <> n then + Error(ER_INVALID_ISVALID_LENGTH); + + IsValid := valid; + end; end; constructor StrColumn.Create(name: string); @@ -618,36 +662,32 @@ begin inherited Create; Info := new ColumnInfo(name, ctStr); - Data := new string[0]; - IsValid := nil; + Data := []; + IsValid := []; end; function StrColumn.TryGetNumericValue(i: integer; var value: real): boolean; begin - exit(false); + exit(False); end; -function BoolColumn.TryGetNumericValue(i: integer; var value: real): boolean; -begin - if (IsValid <> nil) and 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; data: array of boolean; - valid: array of boolean); +constructor BoolColumn.Create(name: string; data: array of boolean; valid: array of boolean); begin inherited Create; Info := new ColumnInfo(name, ctBool); - Data := data; - IsValid := valid; + var n := Length(data); + Data := if n = 0 then [] else data; + + if valid = nil then + IsValid := [True] * n + else + begin + if Length(valid) <> n then + Error(ER_INVALID_ISVALID_LENGTH); + + IsValid := valid; + end; end; constructor BoolColumn.Create(name: string); @@ -655,34 +695,24 @@ begin inherited Create; Info := new ColumnInfo(name, ctBool); - Data := new boolean[0]; - IsValid := nil; + Data := []; + IsValid := []; end; -{procedure BoolColumn.AppendFromCursor(cur: DataFrameCursor; colIndex: integer); +function BoolColumn.TryGetNumericValue(i: integer; var value: real): boolean; begin - if cur.IsValid(colIndex) then - begin - Data := Data + [cur.Bool(colIndex)]; - if IsValid <> nil then - IsValid := IsValid + [true]; - end - else AppendInvalid; -end;} + if not IsValid[i] then + exit(False); -{procedure BoolColumn.AppendInvalid; -begin - Data := Data + [false]; + if Data[i] then + value := 1.0 + else + value := 0.0; + + exit(True); +end; - if IsValid = nil then - begin - IsValid := new boolean[Length(Data) - 1]; - for var i := 0 to IsValid.Length - 1 do - IsValid[i] := true; - end; - IsValid := IsValid + [false]; -end;} //----------------------------- // JoinKey @@ -772,10 +802,7 @@ begin ctInt: begin var c := IntColumn(col); - if c.IsValid = nil then - validAcc[i] := pos -> true - else - validAcc[i] := pos -> c.IsValid[pos]; + validAcc[i] := pos -> c.IsValid[pos]; intAcc[i] := pos -> c.Data[pos]; floatAcc[i] := pos -> c.Data[pos]; @@ -784,10 +811,7 @@ begin ctFloat: begin var c := FloatColumn(col); - if c.IsValid = nil then - validAcc[i] := pos -> true - else - validAcc[i] := pos -> c.IsValid[pos]; + validAcc[i] := pos -> c.IsValid[pos]; floatAcc[i] := pos -> c.Data[pos]; intAcc[i] := NotInt; @@ -796,10 +820,7 @@ begin ctStr: begin var c := StrColumn(col); - if c.IsValid = nil then - validAcc[i] := pos -> true - else - validAcc[i] := pos -> c.IsValid[pos]; + validAcc[i] := pos -> c.IsValid[pos]; strAcc[i] := pos -> c.Data[pos]; end; @@ -807,10 +828,7 @@ begin ctBool: begin var c := BoolColumn(col); - if c.IsValid = nil then - validAcc[i] := pos -> true - else - validAcc[i] := pos -> c.IsValid[pos]; + validAcc[i] := pos -> c.IsValid[pos]; boolAcc[i] := pos -> c.Data[pos]; end; diff --git a/bin/Lib/LinearAlgebraML.pas b/bin/Lib/LinearAlgebraML.pas index b423c396d..87521e9d3 100644 --- a/bin/Lib/LinearAlgebraML.pas +++ b/bin/Lib/LinearAlgebraML.pas @@ -1,4 +1,13 @@ -/// Линейная алгебра для алгоритмов машинного обучения. +// ============================================================= +// СТАТИСТИЧЕСКОЕ СОГЛАШЕНИЕ (LinearAlgebraML) +// +// Используется дисперсия генеральной совокупности (деление на n), +// как принято в численных методах и алгоритмах машинного обучения. +// +// См. статистическую политику в модуле MLABC. +// ============================================================= + +/// Линейная алгебра для алгоритмов машинного обучения. /// /// Содержит типы Vector и Matrix и численные методы, /// используемые в моделях ML: diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index bef02847e..cff013057 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -1,4 +1,23 @@ -/// Основной модуль библиотеки машинного обучения. +// ============================================================= +// СТАТИСТИЧЕСКАЯ ПОЛИТИКА БИБЛИОТЕКИ ML PascalABC.NET +// +// В библиотеке используются следующие соглашения: +// +// 1. DataFrame (описательная статистика): +// • дисперсия вычисляется с делением на (n - 1) +// +// 2. LinearAlgebra и ML: +// • дисперсия вычисляется с делением на n +// +// 3. PCA: +// • ковариационная матрица вычисляется с делением на (n - 1) +// +// Это соответствует распространённой практике: +// • описательная статистика — выборочная дисперсия +// • алгоритмы ML — дисперсия генеральной совокупности +// ============================================================= + +/// Основной модуль библиотеки машинного обучения. /// Объединяет модели, метрики, валидацию и вспомогательные компоненты. unit MLABC; diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index 4fdd26ceb..52e07360f 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -1,4 +1,17 @@ -/// Модуль моделей машинного обучения и матричных преобразований. +// ============================================================= +// СТАТИСТИЧЕСКОЕ СОГЛАШЕНИЕ (MLModelsABC) +// +// Модели и трансформеры используют статистические методы +// из LinearAlgebraML. +// +// В частности: +// • StandardScaler, VarianceThreshold и др. используют +// дисперсию с делением на n (population variance) +// +// См. статистическую политику в модуле MLABC. +// ============================================================= + +/// Модуль моделей машинного обучения и матричных преобразований. /// /// Содержит: /// • функции активации @@ -68,9 +81,12 @@ type public constructor Create(); - /// Обучает модель на числовых данных - /// X — матрица m × n (m объектов, n признаков) - /// y — вектор длины m +/// Обучает модель на числовых данных +/// X — матрица m × n (m объектов, n признаков) +/// y — вектор длины m +/// +/// Примечание: +/// • обученное состояние модели НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Предсказывает значения для матрицы признаков @@ -93,7 +109,21 @@ type property IsFitted: boolean read fFitted; function ToString: string; override; - + +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; function Name: string := Self.GetType.Name; @@ -116,10 +146,13 @@ type /// • lambda — коэффициент L2-регуляризации (0 — обычная линейная регрессия) constructor Create(lambda: real := 1.0); - /// Обучает модель на числовых данных. - /// X — матрица m × n (m объектов, n признаков). - /// y — вектор длины m с непрерывными значениями. - /// Выполняется центрирование признаков и целевой переменной. +/// Обучает модель на числовых данных. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. +/// Выполняется центрирование признаков и целевой переменной. +/// +/// Примечание: +/// • обученное состояние модели НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Предсказывает непрерывные значения для объектов X. @@ -144,6 +177,20 @@ type function ToString: string; override; +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; function Name: string := Self.GetType.Name; @@ -180,10 +227,13 @@ type /// • tol — критерий остановки по изменению коэффициентов. constructor Create(lambda1, lambda2: real; maxIter: integer := 1000; tol: real := 1e-6); - /// Обучает модель на числовых данных. - /// X — матрица m × n (m объектов, n признаков). - /// y — вектор длины m с непрерывными значениями. - /// Выполняется центрирование признаков и целевой переменной. +/// Обучает модель на числовых данных. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. +/// Выполняется центрирование признаков и целевой переменной. +/// +/// Примечание: +/// • обученное состояние модели НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Предсказывает непрерывные значения для объектов X. @@ -205,6 +255,20 @@ type function ToString: string; override; +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; function Name: string := Self.GetType.Name; @@ -216,6 +280,9 @@ type LassoRegression = class(IRegressor) private fModel: ElasticNet; + fAlpha: real; + fMaxIter: integer; + fTol: real; public /// Создаёт модель Lasso-регрессии. /// alpha — коэффициент L1-регуляризации. @@ -227,10 +294,13 @@ type tol: real := 1e-6 ); - /// Обучает модель на данных. - /// X — матрица признаков размера [nSamples x nFeatures]. - /// y — вектор целевых значений длины nSamples. - /// Возвращает текущий экземпляр модели. +/// Обучает модель на числовых данных. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. +/// Выполняется центрирование признаков и целевой переменной. +/// +/// Примечание: +/// • обученное состояние модели НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Предсказывает значения целевой переменной для входных данных. @@ -238,7 +308,20 @@ type /// Возвращает вектор предсказаний длины nSamples. function Predict(X: Matrix): Vector; - /// Создаёт полную копию модели вместе с обученными параметрами. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; /// Показывает, была ли модель обучена. @@ -288,10 +371,12 @@ type ); /// Обучает модель логистической регрессии. -/// X — матрица признаков. -/// y — вектор меток классов (целочисленные значения). -/// Возвращает обученную модель. -/// После вызова IsFitted становится True. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с метками классов (целочисленные значения). +/// После вызова модель содержит обученные параметры и готова к Predict. +/// +/// Примечание: +/// • обученное состояние модели НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Возвращает матрицу вероятностей (m x k). @@ -319,9 +404,6 @@ type /// Возвращает строковое представление модели. function ToString: string; override; -/// Создает глубокую копию модели. - function Clone: IModel; - /// Матрица коэффициентов модели (p × k). property Weights: Matrix read GetWeights; @@ -333,6 +415,22 @@ type procedure SetClassLabels(classes: array of string); function GetClassLabels: array of string; + +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) + function Clone: IModel; end; DecisionTreeNode = class @@ -536,10 +634,12 @@ type /// Значения нормированы так, что сумма равна 1. function FeatureImportances: Vector; -/// Обучает дерево решений. -/// X — матрица признаков. -/// y — целевая переменная. -/// Реализация зависит от типа дерева (регрессия или классификация). +/// Обучает дерево решений для задачи регрессии. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. +/// +/// Примечание: +/// • обученное состояние дерева НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; virtual; abstract; /// Выполняет предсказание для матрицы X. @@ -548,8 +648,20 @@ type /// Для классификации — метки классов. function Predict(X: Matrix): Vector; virtual; abstract; -/// Создает глубокую копию дерева. -/// Копируется структура узлов, параметры и обученное состояние. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; virtual; abstract; /// Возвращает true, если дерево обучено. @@ -588,12 +700,17 @@ type /// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesLeaf — минимальное число объектов в листе constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1; - criterion: ISplitCriterion := nil; seed: integer := -1); + criterion: ISplitCriterion := nil; + maxFeatures: integer := 0; + seed: integer := -1); /// Обучает классификационное дерево. -/// X — матрица признаков. -/// y — вектор целевых меток (целые значения). -/// Строит структуру дерева путем минимизации нечистоты в узлах. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с метками классов (целочисленные значения). +/// Строит структуру дерева, минимизируя нечистоту в узлах. +/// +/// Примечание: +/// • обученное состояние дерева НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Выполняет предсказание меток классов для X. @@ -606,10 +723,6 @@ type /// Требует предварительного вызова Fit. function PredictLabels(X: Matrix): array of integer; -/// Создает глубокую копию дерева классификации. -/// Копируется структура узлов, параметры и обученное состояние. - function Clone: IModel; - /// Возвращает строковое представление модели. function ToString: string; override; @@ -626,6 +739,22 @@ type /// Возвращает true, если дерево обучено. /// Если false — Predict вызовет ошибку. property IsFitted: boolean read fFitted; + +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) + function Clone: IModel; end; //============================ @@ -668,16 +797,31 @@ type leafL2: real := 0.0; seed: integer := -1); /// Обучает регрессионное дерево. -/// X — матрица признаков. -/// y — вещественная целевая переменная. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. +/// +/// Примечание: +/// • обученное состояние дерева НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; override; /// Выполняет предсказание для всех объектов X. /// Возвращает вектор вещественных значений. function Predict(X: Matrix): Vector; override; -/// Создает глубокую копию дерева регрессии. -/// Копируется структура узлов, параметры и обученное состояние. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; override; /// Возвращает строковое представление модели. @@ -758,9 +902,14 @@ type useOOB: boolean := False; seed: integer := -1); -/// Обучает ансамбль деревьев на данных X и y. +/// Обучает ансамбль деревьев на данных. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с целевыми значениями. /// Для каждого дерева используется bootstrap-подвыборка -/// и случайное подмножество признаков. +/// и случайное подмножество признаков. +/// +/// Примечание: +/// • обученное состояние ансамбля НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; virtual; abstract; /// Выполняет предсказание для матрицы X. @@ -768,8 +917,20 @@ type /// В классификации — голосование (majority vote) или усреднение вероятностей. function Predict(X: Matrix): Vector; virtual; abstract; -/// Создает глубокую копию случайного леса. -/// Копируются все деревья и параметры ансамбля. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; virtual; abstract; /// Возвращает вектор важности признаков. @@ -811,17 +972,34 @@ type computeOOB: boolean := False; seed: integer := -1); -/// Обучает случайный лес на данных X и y. -/// Для каждого дерева используется bootstrap-выборка. -/// Возвращает обученную модель. +/// Обучает регрессионный случайный лес. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. +/// Для каждого дерева используется bootstrap-подвыборка +/// и случайное подмножество признаков. +/// +/// Примечание: +/// • обученное состояние ансамбля НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; override; /// Выполняет предсказание для X. /// Итоговое значение — среднее предсказаний всех деревьев ансамбля. function Predict(X: Matrix): Vector; override; -/// Создает глубокую копию случайного леса. -/// Копируются все деревья и параметры модели. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; override; /// Возвращает усредненную важность признаков по всем деревьям ансамбля. @@ -860,10 +1038,14 @@ type computeOOB: boolean := False; seed: integer := -1); -/// Обучает случайный лес на данных X и y. -/// Для каждого дерева используется bootstrap-выборка обучающих объектов. +/// Обучает классификационный случайный лес. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с метками классов (целочисленные значения). +/// Для каждого дерева используется bootstrap-подвыборка обучающих объектов. /// В каждом узле рассматривается случайное подмножество признаков согласно maxFeaturesMode. -/// Возвращает обученную модель. +/// +/// Примечание: +/// • обученное состояние ансамбля НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; override; /// Выполняет предсказание меток классов для матрицы X. @@ -885,8 +1067,20 @@ type /// Требует предварительного вызова Fit. function PredictProba(X: Matrix): Matrix; -/// Создает глубокую копию случайного леса классификации. -/// Копируются все деревья, параметры ансамбля и обученное состояние модели. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; override; /// Возвращает усредненную важность признаков по всем деревьям ансамбля. @@ -1027,18 +1221,36 @@ type seed: integer := -1 ); -/// Обучает модель градиентного бустинга на обучающей выборке. +/// Обучает модель градиентного бустинга для регрессии. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. /// Обучение выполняется без early stopping. /// Для предотвращения переобучения используйте FitWithValidation. /// Early stopping работает только при наличии валидационной выборки /// и параметре earlyStoppingPatience > 0. -/// Возвращает обученную модель. +/// +/// Примечание: +/// • обученное состояние модели НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; + /// Предсказывает значения целевой переменной. /// Используются все обученные деревья. function Predict(X: Matrix): Vector; -/// Создает глубокую копию модели. -/// Копируются все деревья и внутреннее состояние. + +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; /// Обучает модель градиентного бустинга с использованием валидационной выборки. @@ -1172,12 +1384,16 @@ type earlyStoppingPatience: integer := 20; seed: integer := -1); -/// Обучает модель градиентного бустинга на обучающей выборке. +/// Обучает модель градиентного бустинга для классификации. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с метками классов (целочисленные значения). /// Обучение выполняется без early stopping. /// Для предотвращения переобучения используйте FitWithValidation. /// Early stopping работает только при наличии валидационной выборки /// и параметре earlyStoppingPatience > 0. -/// Возвращает обученную модель. +/// +/// Примечание: +/// • обученное состояние модели НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Обучает модель градиентного бустинга с использованием валидационной выборки. @@ -1199,8 +1415,20 @@ type function GetClasses: array of real; -/// Создает глубокую копию классификатора. -/// Копируются все деревья и внутреннее состояние. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; /// История значения LogLoss на обучающей выборке. @@ -1285,15 +1513,33 @@ type /// weighting — режим взвешивания соседей constructor Create(k: integer; weighting: KNNWeighting := KNNWeighting.Uniform); - /// Обучает модель на матрице признаков X и целевом векторе y. - /// Возвращает текущий экземпляр модели +/// Обучает модель k ближайших соседей. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с целевыми значениями. +/// Обучение заключается в запоминании обучающей выборки. +/// +/// Примечание: +/// • обученное состояние модели (обучающая выборка) НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; virtual; abstract; /// Выполняет предсказание для матрицы признаков X. /// Возвращает вектор предсказанных значений или меток function Predict(X: Matrix): Vector; virtual; abstract; - /// Создаёт глубокую копию модели +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; virtual; abstract; function Name: string := Self.GetType.Name; @@ -1326,9 +1572,13 @@ type /// weighting — режим взвешивания голосов соседей constructor Create(k: integer; weighting: KNNWeighting := KNNWeighting.Uniform); - /// Обучает модель на матрице признаков X и векторе меток y. - /// Метки классов могут быть произвольными числами. - /// Возвращает текущий экземпляр модели +/// Обучает классификатор k ближайших соседей. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с метками классов (произвольные числовые значения). +/// Обучение заключается в запоминании обучающей выборки. +/// +/// Примечание: +/// • обученное состояние модели (обучающая выборка) НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; override; /// Выполняет предсказание меток классов для объектов X. @@ -1344,7 +1594,20 @@ type /// Возвращает массив меток классов в порядке столбцов PredictProba function GetClasses: array of real; - /// Создаёт глубокую копию классификатора +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; override; function Name: string := Self.GetType.Name; @@ -1369,15 +1632,33 @@ type /// weighting — режим взвешивания вкладов соседей constructor Create(k: integer; weighting: KNNWeighting := KNNWeighting.Uniform); - /// Обучает модель на матрице признаков X и целевом векторе y. - /// Возвращает текущий экземпляр модели +/// Обучает регрессор k ближайших соседей. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с непрерывными значениями. +/// Обучение заключается в запоминании обучающей выборки. +/// +/// Примечание: +/// • обученное состояние модели (обучающая выборка) НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; override; /// Выполняет предсказание числовых значений для объектов X. /// Возвращает вектор предсказанных значений function Predict(X: Matrix): Vector; override; - /// Создаёт глубокую копию регрессора +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; override; function Name: string := Self.GetType.Name; @@ -1423,8 +1704,12 @@ type seed: integer := -1 ); - /// Обучает модель по матрице признаков X. - /// Выполняет nInit запусков и выбирает решение с минимальной инерцией. +/// Обучает модель k-средних по матрице признаков. +/// X — матрица m × n (m объектов, n признаков). +/// Выполняет nInit запусков и выбирает решение с минимальной инерцией. +/// +/// Примечание: +/// • обученное состояние модели (центры кластеров) НЕ копируется методом Clone function Fit(X: Matrix): IUnsupervisedModel; /// Возвращает индекс кластера для каждого объекта из X. @@ -1438,7 +1723,20 @@ type /// Выполняет обучение и сразу возвращает метки кластеров. function FitPredict(X: Matrix): array of integer; - /// Создаёт глубокую копию модели. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; /// Количество кластеров @@ -1494,7 +1792,12 @@ type minSamples: integer := 5 ); - /// Обучает модель на матрице признаков. +/// Обучает модель DBSCAN по матрице признаков. +/// X — матрица m × n (m объектов, n признаков). +/// Выполняет кластеризацию на основе плотности и определяет метки кластеров. +/// +/// Примечание: +/// • обученное состояние модели (метки кластеров) НЕ копируется методом Clone function Fit(X: Matrix): IUnsupervisedModel; /// Возвращает метки кластеров. @@ -1503,7 +1806,20 @@ type /// Возвращает метки после обучения. function FitPredict(X: Matrix): array of integer; - /// Копия модели. +/// Создаёт копию модели с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение моделей +/// +/// Примечание: +/// • для копирования обученной модели следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: IModel; property Labels: array of integer read fLabels; @@ -1618,8 +1934,14 @@ type /// Добавляет преобразование в конец пайплайна function Add(t: ITransformer): Pipeline; - /// Обучает преобразования и модель - /// Модель может быть только supervised а трансформеры - любыми +/// Обучает пайплайн на данных. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с целевыми значениями. +/// Последовательно обучает все трансформеры и применяет их к данным, +/// после чего обучает финальную supervised-модель. +/// +/// Примечание: +/// • обученное состояние пайплайна НЕ копируется методом Clone function Fit(X: Matrix; y: Vector): ISupervisedModel; /// Применяет только преобразования (без модели) @@ -1636,7 +1958,17 @@ type property IsFitted: boolean read fFitted; function ToString: string; override; - + +/// Создаёт копию пайплайна с теми же шагами и моделью. +/// +/// ВАЖНО: +/// • Clone копирует только конфигурацию (transformers и модель) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Все шаги и модель клонируются через их Clone (config-only) +/// +/// Назначение: +/// • использование в CrossValidate и Pipeline +/// • обеспечивает независимое переобучение пайплайна function Clone: IModel; function Name: string := Self.GetType.Name; @@ -1656,6 +1988,13 @@ type function SetModel(m: IModel): UPipeline; function Add(t: ITransformer): UPipeline; +/// Обучает пайплайн на данных. +/// X — матрица m × n (m объектов, n признаков). +/// Последовательно обучает все трансформеры и применяет их к данным, +/// после чего обучает финальную модель. +/// +/// Примечание: +/// • обученное состояние пайплайна НЕ копируется методом Clone function Fit(X: Matrix): IUnsupervisedModel; function Transform(X: Matrix): Matrix; function Predict(X: Matrix): Vector; @@ -1663,6 +2002,17 @@ type property IsFitted: boolean read fFitted; function ToString: string; override; + +/// Создаёт копию пайплайна с теми же шагами и моделью. +/// +/// ВАЖНО: +/// • Clone копирует только конфигурацию (transformers и модель) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Все шаги и модель клонируются через их Clone (config-only) +/// +/// Назначение: +/// • использование в CrossValidate и Pipeline +/// • обеспечивает независимое переобучение пайплайна function Clone: IModel; function Name: string := Self.GetType.Name; end; @@ -1685,7 +2035,13 @@ type /// Параметры масштабирования (среднее и стандартное отклонение) /// вычисляются при вызове Fit. constructor Create(); begin end; - /// Вычисляет среднее и стандартное отклонение по каждому признаку. + +/// Вычисляет среднее и стандартное отклонение по каждому признаку. +/// X — матрица m × n (m объектов, n признаков). +/// Сохраняет параметры масштабирования для последующего применения в Transform. +/// +/// Примечание: +/// • вычисленные параметры НЕ копируются методом Clone function Fit(X: Matrix): IUnsupervisedTransformer; /// Применяет стандартизацию к данным. @@ -1702,6 +2058,20 @@ type function ToString: string; override; +/// Создаёт копию трансформера с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение трансформеров +/// +/// Примечание: +/// • для копирования обученного трансформера следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: ITransformer; end; @@ -1720,7 +2090,13 @@ type /// Создаёт MinMaxScaler с диапазоном [rangeMin, rangeMax]. /// По умолчанию масштабирование выполняется к [0, 1] constructor Create(rangeMin: real := 0.0; rangeMax: real := 1.0); - /// Вычисляет минимальные и максимальные значения по каждому признаку. + +/// Вычисляет минимальные и максимальные значения по каждому признаку. +/// X — матрица m × n (m объектов, n признаков). +/// Сохраняет параметры масштабирования для последующего применения в Transform. +/// +/// Примечание: +/// • вычисленные параметры НЕ копируются методом Clone function Fit(X: Matrix): IUnsupervisedTransformer; /// Применяет линейное масштабирование признаков к диапазону [0, 1]. @@ -1736,7 +2112,21 @@ type property IsFitted: boolean read fFitted; function ToString: string; override; - + +/// Создаёт копию трансформера с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение трансформеров +/// +/// Примечание: +/// • для копирования обученного трансформера следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: ITransformer; end; @@ -1758,8 +2148,13 @@ type /// • k — число главных компонент (k > 0). constructor Create(k: integer); - /// Обучает трансформер на матрице признаков X. - /// X — матрица m × n. +/// Обучает трансформер PCA на матрице признаков. +/// X — матрица m × n (m объектов, n признаков). +/// Вычисляет главные компоненты и среднее значение признаков +/// для последующего снижения размерности в Transform. +/// +/// Примечание: +/// • вычисленные параметры НЕ копируются методом Clone function Fit(X: Matrix): IUnsupervisedTransformer; /// Преобразует матрицу X в пространство главных компонент. @@ -1771,6 +2166,21 @@ type property IsFitted: boolean read fFitted; function ToString: string; override; + +/// Создаёт копию трансформера с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение трансформеров +/// +/// Примечание: +/// • для копирования обученного трансформера следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: ITransformer; end; @@ -1790,8 +2200,13 @@ type /// Столбцы с Var(X_j) < threshold удаляются. constructor Create(threshold: real := 0.0); - /// Вычисляет дисперсии признаков и запоминает индексы - /// признаков, удовлетворяющих порогу. +/// Вычисляет дисперсии признаков и выбирает признаки, +/// удовлетворяющие заданному порогу. +/// X — матрица m × n (m объектов, n признаков). +/// Сохраняет индексы выбранных признаков для последующего применения в Transform. +/// +/// Примечание: +/// • выбранные признаки НЕ копируются методом Clone function Fit(X: Matrix): IUnsupervisedTransformer; /// Возвращает матрицу, содержащую только отобранные признаки. @@ -1804,6 +2219,21 @@ type property IsFitted: boolean read fFitted; function ToString: string; override; + +/// Создаёт копию трансформера с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение трансформеров +/// +/// Примечание: +/// • для копирования обученного трансформера следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: ITransformer; end; @@ -1866,7 +2296,13 @@ type /// • scoreFunc — функция (feature, y) → real constructor Create(k: integer; scoreFunc: (Vector, Vector) -> real); - /// Вычисляет оценки признаков и выбирает k лучших. +/// Вычисляет оценки признаков и выбирает k лучших. +/// X — матрица m × n (m объектов, n признаков). +/// y — вектор длины m с целевыми значениями. +/// Сохраняет индексы выбранных признаков для последующего применения в Transform. +/// +/// Примечание: +/// • выбранные признаки НЕ копируются методом Clone function Fit(X: Matrix; y: Vector): ISupervisedTransformer; /// Возвращает матрицу, содержащую только выбранные признаки. @@ -1881,8 +2317,20 @@ type /// Возвращает строковое представление трансформера function ToString: string; override; -/// Создает глубокую копию. -/// Копируются параметры и внутреннее состояние. +/// Создаёт копию трансформера с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение трансформеров +/// +/// Примечание: +/// • для копирования обученного трансформера следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: ITransformer; end; @@ -1906,8 +2354,13 @@ type /// По умолчанию используется L2-норма. constructor Create(norm: NormType := NormType.L2); -/// Подготавливает трансформер к работе. -/// Для Normalizer этап обучения может быть формальным, так как параметры не накапливаются. +/// Подготавливает трансформер нормализации к работе. +/// X — матрица m × n (m объектов, n признаков). +/// Нормализация выполняется независимо для каждой строки, +/// поэтому этап Fit является формальным и не накапливает параметры. +/// +/// Примечание: +/// • у трансформера отсутствует обученное состояние function Fit(X: Matrix): IUnsupervisedTransformer; /// Применяет нормализацию к матрице X. @@ -1922,8 +2375,20 @@ type /// Возвращает строковое представление трансформера function ToString: string; override; -/// Создает глубокую копию нормализатора. -/// Копируются параметры и внутреннее состояние. +/// Создаёт копию трансформера с теми же гиперпараметрами. +/// +/// ВАЖНО: +/// • Clone копирует ТОЛЬКО конфигурацию (hyperparameters) +/// • Clone НЕ копирует обученное состояние (fitted state) +/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) +/// +/// Назначение: +/// • использование в CrossValidate, GridSearch и Pipeline +/// • гарантирует независимое переобучение трансформеров +/// +/// Примечание: +/// • для копирования обученного трансформера следует использовать отдельные механизмы +/// (сериализация, ModelIO и т.п.) function Clone: ITransformer; end; @@ -2152,6 +2617,8 @@ const 'Необходимо как минимум два различных класса!!At least two distinct classes are required'; ER_UNKNOWN_CLASS_LABEL = 'Неизвестная метка класса: {0}!!Unknown class label: {0}'; + ER_MAX_FEATURES_INVALID = + 'maxFeatures должен быть >= 0!!maxFeatures must be >= 0'; {$endregion ErrConstants} @@ -2285,16 +2752,7 @@ end; function LinearRegression.Clone: IModel; begin - var m := new LinearRegression; - - m.fFitted := fFitted; - - if fCoef <> nil then - m.fCoef := fCoef.Clone; - - m.fIntercept := fIntercept; - - Result := m; + Result := new LinearRegression; end; //----------------------------- @@ -2421,16 +2879,7 @@ end; function RidgeRegression.Clone: IModel; begin - var m := new RidgeRegression(fLambda); - - m.fFitted := fFitted; - - if fCoef <> nil then - m.fCoef := fCoef.Clone; - - m.fIntercept := fIntercept; - - Result := m; + Result := new RidgeRegression(fLambda); end; @@ -2623,16 +3072,7 @@ end; function ElasticNet.Clone: IModel; begin - var m := new ElasticNet(fLambda1, fLambda2, fMaxIter, fTol); - - m.fFitted := fFitted; - - if fCoef <> nil then - m.fCoef := fCoef.Clone; - - m.fIntercept := fIntercept; - - Result := m; + Result := new ElasticNet(fLambda1, fLambda2, fMaxIter, fTol); end; //----------------------------- @@ -2641,8 +3081,12 @@ end; constructor LassoRegression.Create(alpha: real; maxIter: integer; tol: real); begin + fAlpha := alpha; + fMaxIter := maxIter; + fTol := tol; + // Lasso = ElasticNet с L2 = 0 - fModel := new ElasticNet(alpha, 0.0, maxIter, tol); + fModel := new ElasticNet(fAlpha, 0.0, fMaxIter, fTol); end; function LassoRegression.Fit(X: Matrix; y: Vector): ISupervisedModel; @@ -2658,9 +3102,7 @@ end; function LassoRegression.Clone: IModel; begin - var m := new LassoRegression; - m.fModel := ElasticNet(fModel.Clone); - Result := m; + Result := new LassoRegression(fAlpha, fMaxIter, fTol); end; //----------------------------- @@ -2956,36 +3398,14 @@ end; function LogisticRegression.Clone: IModel; begin - var m := new LogisticRegression( + Result := new LogisticRegression( fLambda, fLearningRate, - fEpochs + fEpochs, + fTol, + fCheckConvergence, + fMinImprovement ); - - m.fFitted := fFitted; - m.fClassCount := fClassCount; - - if fW <> nil then - m.fW := fW.Clone; - - if fIntercept <> nil then - m.fIntercept := fIntercept.Clone; - - if fClassToIndex <> nil then - begin - m.fClassToIndex := new Dictionary; - foreach var kv in fClassToIndex do - m.fClassToIndex[kv.Key] := kv.Value; - end; - - if fIndexToClass <> nil then - begin - SetLength(m.fIndexToClass, Length(fIndexToClass)); - for var i := 0 to Length(fIndexToClass) - 1 do - m.fIndexToClass[i] := fIndexToClass[i]; - end; - - Result := m; end; function LogisticRegression.GetWeights: Matrix; @@ -3172,18 +3592,7 @@ begin fCriterion := criterion; fMaxFeatures := maxFeatures; - // --- seed (в твоём стиле) - if seed < 0 then - begin - fUserProvidedSeed := false; - fRandomSeed := System.Environment.TickCount and integer.MaxValue; - end - else - begin - fUserProvidedSeed := true; - fRandomSeed := seed; - end; - + fRandomSeed := ResolveRandomSeed(seed, fUserProvidedSeed); fRng := new System.Random(fRandomSeed); end; @@ -3531,17 +3940,7 @@ begin fMinSamplesLeaf := minSamplesLeaf; fCriterion := criterion; - if seed < 0 then - begin - fUserProvidedSeed := false; - fRandomSeed := System.Environment.TickCount and integer.MaxValue; - end - else - begin - fUserProvidedSeed := true; - fRandomSeed := seed; - end; - + fRandomSeed := ResolveRandomSeed(seed, fUserProvidedSeed); fRng := new System.Random(fRandomSeed); end; @@ -3832,7 +4231,11 @@ end; // DecisionTreeClassifier //============================== -constructor DecisionTreeClassifier.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer; criterion: ISplitCriterion; seed: integer); +constructor DecisionTreeClassifier.Create(maxDepth: integer; + minSamplesSplit: integer; minSamplesLeaf: integer; + criterion: ISplitCriterion; + maxFeatures: integer; + seed: integer); begin if maxDepth = 0 then ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, maxDepth); @@ -3845,23 +4248,18 @@ begin if minSamplesLeaf >= minSamplesSplit then ArgumentOutOfRangeError(ER_MIN_LEAF_GE_SPLIT, minSamplesLeaf, minSamplesSplit); + + if maxFeatures < 0 then + ArgumentOutOfRangeError(ER_MAX_FEATURES_INVALID); + + fMaxFeatures := maxFeatures; fMaxDepth := maxDepth; fMinSamplesSplit := minSamplesSplit; fMinSamplesLeaf := minSamplesLeaf; fCriterion := if criterion = nil then new GiniCriterion else criterion; - if seed < 0 then - begin - fUserProvidedSeed := false; - fRandomSeed := System.Environment.TickCount and integer.MaxValue; - end - else - begin - fUserProvidedSeed := true; - fRandomSeed := seed; - end; - + fRandomSeed := ResolveRandomSeed(seed, fUserProvidedSeed); fRng := new System.Random(fRandomSeed); end; @@ -3974,58 +4372,14 @@ end; function DecisionTreeClassifier.Clone: IModel; begin - var m := new DecisionTreeClassifier( + Result := new DecisionTreeClassifier( fMaxDepth, fMinSamplesSplit, fMinSamplesLeaf, fCriterion, + fMaxFeatures, fRandomSeed ); - - m.fMaxDepth := fMaxDepth; - m.fMinSamplesSplit := fMinSamplesSplit; - m.fMinSamplesLeaf := fMinSamplesLeaf; - m.fFitted := fFitted; - m.fRandomSeed := fRandomSeed; - m.fMaxFeatures := fMaxFeatures; - m.fUserProvidedSeed := fUserProvidedSeed; - - if fUserProvidedSeed then - m.fRng := new System.Random(fRandomSeed) - else - m.fRng := new System.Random; - - if fCriterion <> nil then - m.fCriterion := fCriterion; - - if fFeatureImportances <> nil then - m.fFeatureImportances := fFeatureImportances.Clone; - - // --- classes (единственный источник истины) - if fIndexToClass <> nil then - begin - SetLength(m.fIndexToClass, Length(fIndexToClass)); - for var i := 0 to Length(fIndexToClass) - 1 do - m.fIndexToClass[i] := fIndexToClass[i]; - end; - - // --- core (глубокая копия дерева) - if fCore <> nil then - begin - // предполагаем, что Node.Clone уже есть - m.fCore := new DecisionTreeCore( - fMaxDepth, - fMinSamplesSplit, - fMinSamplesLeaf, - fCriterion, - fMaxFeatures, - fRandomSeed - ); - - m.fCore.fRoot := fCore.fRoot.Clone; - end; - - Result := m; end; function DecisionTreeClassifier.ToString: string; @@ -4210,17 +4564,13 @@ end; function DecisionTreeRegressor.Clone: IModel; begin - var m := new DecisionTreeRegressor( + Result := new DecisionTreeRegressor( fMaxDepth, fMinSamplesSplit, fMinSamplesLeaf, fLeafL2, fRandomSeed ); - - CopyBaseState(m); - - Result := m; end; function DecisionTreeRegressor.ToString: string; @@ -4279,17 +4629,7 @@ begin fUseOOB := useOOB; fOOBScore := real.NaN; - if seed < 0 then - begin - fUserProvidedSeed := false; - fRandomSeed := System.Environment.TickCount and integer.MaxValue; - end - else - begin - fUserProvidedSeed := true; - fRandomSeed := seed; - end; - + fRandomSeed := ResolveRandomSeed(seed, fUserProvidedSeed); fRng := new System.Random(fRandomSeed); end; @@ -4482,7 +4822,7 @@ end; function RandomForestRegressor.Clone: IModel; begin - var rf := new RandomForestRegressor( + Result := new RandomForestRegressor( fNTrees, fMaxDepth, fMinSamplesSplit, @@ -4491,31 +4831,6 @@ begin fUseOOB, fRandomSeed ); - - // --- seed --- - rf.fRandomSeed := fRandomSeed; - rf.fUserProvidedSeed := fUserProvidedSeed; - - if fUserProvidedSeed then - rf.fRng := new System.Random(fRandomSeed) - else - rf.fRng := new System.Random; - - rf.fFitted := fFitted; - rf.fFeatureCount := fFeatureCount; - - rf.fOOBScore := fOOBScore; - rf.fHasOOBScore := fHasOOBScore; - - // --- trees --- - if fTrees <> nil then - begin - SetLength(rf.fTrees, fTrees.Length); - for var i := 0 to fTrees.Length - 1 do - rf.fTrees[i] := DecisionTreeRegressor(fTrees[i].Clone); - end; - - Result := rf; end; function RandomForestRegressor.FeatureImportances: Vector; @@ -4841,7 +5156,7 @@ end; function RandomForestClassifier.Clone: IModel; begin - var rf := new RandomForestClassifier( + Result := new RandomForestClassifier( fNTrees, fMaxDepth, fMinSamplesSplit, @@ -4850,45 +5165,6 @@ begin fUseOOB, fRandomSeed ); - - // --- seed --- - rf.fRandomSeed := fRandomSeed; - rf.fUserProvidedSeed := fUserProvidedSeed; - - if fUserProvidedSeed then - rf.fRng := new System.Random(fRandomSeed) - else - rf.fRng := new System.Random; - - rf.fFitted := fFitted; - rf.fFeatureCount := fFeatureCount; - - // --- classes --- - rf.fClassCount := fClassCount; - - if fIndexToClass <> nil then - rf.fIndexToClass := Copy(fIndexToClass); - - if fClassToIndex <> nil then - begin - rf.fClassToIndex := new Dictionary; - foreach var kv in fClassToIndex do - rf.fClassToIndex.Add(kv.Key, kv.Value); - end; - - // --- OOB --- - rf.fOOBScore := fOOBScore; - rf.fHasOOBScore := fHasOOBScore; - - // --- trees --- - if fTrees <> nil then - begin - SetLength(rf.fTrees, fTrees.Length); - for var i := 0 to fTrees.Length - 1 do - rf.fTrees[i] := fTrees[i].Clone; - end; - - Result := rf; end; function RandomForestClassifier.FeatureImportances: Vector; @@ -5045,17 +5321,7 @@ begin fEstimators := new List; fFitted := false; - if seed < 0 then - begin - fUserProvidedSeed := false; - fRandomSeed := System.Environment.TickCount and integer.MaxValue; - end - else - begin - fUserProvidedSeed := true; - fRandomSeed := seed; - end; - + fRandomSeed := ResolveRandomSeed(seed, fUserProvidedSeed); fRng := new System.Random(fRandomSeed); // ------------- @@ -5702,7 +5968,7 @@ end; function GradientBoostingRegressor.Clone: IModel; begin - var copy := new GradientBoostingRegressor( + Result := new GradientBoostingRegressor( fNEstimators, fLearningRate, fMaxDepth, @@ -5717,45 +5983,8 @@ begin fUseOOBEarlyStopping, fRandomSeed ); - - // --- seed --- - copy.fRandomSeed := fRandomSeed; - copy.fUserProvidedSeed := fUserProvidedSeed; - - if fUserProvidedSeed then - copy.fRng := new System.Random(fRandomSeed) - else - copy.fRng := new System.Random; - - // --- basic state --- - copy.fInitValue := fInitValue; - copy.fFeatureCount := fFeatureCount; - copy.fFitted := fFitted; - - // --- best iteration --- - copy.fBestIteration := fBestIteration; - copy.fBestTrainLoss := fBestTrainLoss; - copy.fBestScoreLoss := fBestScoreLoss; - - // --- histories --- - copy.fTrainLossHistory.Clear; - copy.fTrainLossHistory.AddRange(fTrainLossHistory); - - copy.fValLossHistory.Clear; - copy.fValLossHistory.AddRange(fValLossHistory); - - copy.fOOBLossHistory.Clear; - copy.fOOBLossHistory.AddRange(fOOBLossHistory); - - // --- estimators --- - copy.fEstimators.Clear; - foreach var tree in fEstimators do - copy.fEstimators.Add(tree.Clone as DecisionTreeRegressor); - - Result := copy; end; - //----------------------------- // GradientBoostingClassifier //----------------------------- @@ -6345,17 +6574,7 @@ begin fOOBLossHistory := new List; - if seed < 0 then - begin - fUserProvidedSeed := False; - fRandomSeed := System.Environment.TickCount and integer.MaxValue; - end - else - begin - fUserProvidedSeed := True; - fRandomSeed := seed; - end; - + fRandomSeed := ResolveRandomSeed(seed, fUserProvidedSeed); fRng := new System.Random(fRandomSeed); end; @@ -6566,7 +6785,7 @@ end; function GradientBoostingClassifier.Clone: IModel; begin - var model := new GradientBoostingClassifier( + Result := new GradientBoostingClassifier( fNEstimators, fLearningRate, fMaxDepth, @@ -6576,54 +6795,6 @@ begin fEarlyStoppingPatience, fRandomSeed ); - - // --- seed --- - model.fRandomSeed := fRandomSeed; - model.fUserProvidedSeed := fUserProvidedSeed; - - if fUserProvidedSeed then - model.fRng := new System.Random(fRandomSeed) - else - model.fRng := new System.Random; - - // --- fitted state --- - model.fFitted := fFitted; - model.fFeatureCount := fFeatureCount; - model.fClassCount := fClassCount; - - // --- classes --- - if fClasses <> nil then - begin - SetLength(model.fClasses, Length(fClasses)); - for var i := 0 to Length(fClasses) - 1 do - model.fClasses[i] := fClasses[i]; - end; - - if fClassIndex <> nil then - begin - model.fClassIndex := new Dictionary; - foreach var kv in fClassIndex do - model.fClassIndex.Add(kv.Key, kv.Value); - end; - - // --- estimators --- - foreach var trees in fEstimators do - begin - var newTrees := new DecisionTreeRegressor[Length(trees)]; - for var cls := 0 to Length(trees) - 1 do - newTrees[cls] := trees[cls].Clone as DecisionTreeRegressor; - model.fEstimators.Add(newTrees); - end; - - // --- histories --- - foreach var v in fTrainLossHistory do model.fTrainLossHistory.Add(v); - foreach var v in fValLossHistory do model.fValLossHistory.Add(v); - foreach var v in fOOBLossHistory do model.fOOBLossHistory.Add(v); - - model.fBestIteration := fBestIteration; - model.fBestScoreLoss := fBestScoreLoss; - - Result := model; end; function GradientBoostingClassifier.Fit(X: Matrix; y: Vector): ISupervisedModel; @@ -6869,26 +7040,7 @@ end; function KNNClassifier.Clone: IModel; begin - var clone := new KNNClassifier(fK, fWeighting); - - if fFitted then - begin - clone.fXTrain := fXTrain.Clone; - clone.fClasses := fClasses.Clone as array of double; - clone.fYEnc := fYEnc.Clone as array of integer; - - clone.fClassCount := fClassCount; - - SetLength(clone.fNeighbors, fNeighbors.Length); - SetLength(clone.fVotes, fVotes.Length); - SetLength(clone.fMark, fMark.Length); - SetLength(clone.fTouched, fTouched.Length); - - clone.fEpoch := 0; - clone.fFitted := true; - end; - - Result := clone; + Result := new KNNClassifier(fK, fWeighting); end; function KNNClassifier.Predict(X: Matrix): Vector; @@ -7301,19 +7453,7 @@ end; function KNNRegressor.Clone: IModel; begin - var clone := new KNNRegressor(fK, fWeighting); - - if fFitted then - begin - clone.fXTrain := fXTrain.Clone; - clone.fYTrain := fYTrain.Clone; - - SetLength(clone.fNeighbors, fNeighbors.Length); - - clone.fFitted := True; - end; - - Result := clone; + Result := new KNNRegressor(fK, fWeighting); end; //----------------------------- @@ -7605,35 +7745,13 @@ end; function KMeans.Clone: IModel; begin - var km := new KMeans( + Result := new KMeans( fNClusters, fMaxIter, fTol, fNInit, fRandomSeed ); - - // --- seed --- - km.fRandomSeed := fRandomSeed; - km.fUserProvidedSeed := fUserProvidedSeed; - - if fUserProvidedSeed then - km.fRng := new System.Random(fRandomSeed) - else - km.fRng := new System.Random; - - // --- state --- - km.fFitted := fFitted; - km.fFeatureCount := fFeatureCount; - km.fInertia := fInertia; - km.fIterations := fIterations; - km.fHasConverged := fHasConverged; - - // --- centers --- - if fCenters <> nil then - km.fCenters := fCenters.Clone; - - Result := km; end; function KMeans.ClustersCount: integer; @@ -7648,10 +7766,7 @@ end; // DBSCAN //----------------------------- -constructor DBSCAN.Create( - eps: real; - minSamples: integer -); +constructor DBSCAN.Create(eps: real; minSamples: integer); begin if eps <= 0 then ArgumentOutOfRangeError(ER_EPS_INVALID, eps); @@ -7795,22 +7910,7 @@ end; function DBSCAN.Clone: IModel; begin - var m := new DBSCAN(fEps, fMinSamples); - - m.fFitted := fFitted; - m.fFeatureCount := fFeatureCount; - m.fClusterCount := fClusterCount; - - if fLabels <> nil then - begin - var n := Length(fLabels); - SetLength(m.fLabels, n); - - for var i := 0 to n - 1 do - m.fLabels[i] := fLabels[i]; - end; - - Result := m; + Result := new DBSCAN(fEps, fMinSamples); end; function DBSCAN.ClustersCount: integer := fClusterCount; @@ -8019,10 +8119,8 @@ begin if not (m is ISupervisedModel) then Error(ER_INTERNAL_INVALID_MODEL_CLONE); - + p.SetModel(m as ISupervisedModel); - - p.fFitted := fFitted; Result := p; end; @@ -8205,8 +8303,6 @@ begin p.SetModel(fModel.Clone); - p.fFitted := fFitted; - Result := p; end; @@ -8264,12 +8360,7 @@ end; function StandardScaler.Clone: ITransformer; begin - var s := new StandardScaler; - s.fMean := fMean.Clone; - s.fStd := fStd.Clone; - s.fFitted := fFitted; - s.fFeatureCount := fFeatureCount; - Result := s; + Result := new StandardScaler; end; //----------------------------- @@ -8354,14 +8445,7 @@ end; function MinMaxScaler.Clone: ITransformer; begin - var s := new MinMaxScaler; - s.fMin := fMin.Clone; - s.fMax := fMax.Clone; - s.fFitted := fFitted; - s.fRangeMin := fRangeMin; - s.fRangeMax := fRangeMax; - s.fFeatureCount := fFeatureCount; - Result := s; + Result := new MinMaxScaler(fRangeMin, fRangeMax); end; //----------------------------- @@ -8442,12 +8526,7 @@ end; function PCATransformer.Clone: ITransformer; begin - var t := new PCATransformer(fK); - t.fComponents := fComponents.Clone; - t.fMean := fMean.Clone; - t.fFitted := fFitted; - t.fFeatureCount := fFeatureCount; - Result := t; + Result := new PCATransformer(fK); end; //----------------------------- @@ -8526,11 +8605,7 @@ end; function VarianceThreshold.Clone: ITransformer; begin - var t := new VarianceThreshold(fThreshold); - t.fSelected := Copy(fSelected); - t.fFitted := fFitted; - t.fFeatureCount := fFeatureCount; - Result := t; + Result := new VarianceThreshold(fThreshold); end; //----------------------------- @@ -8863,17 +8938,10 @@ end; function SelectKBest.Clone: ITransformer; begin - var t := new SelectKBest(fK, fScoreType); - - t.fScoreFunc := fScoreFunc; - - if fSelected <> nil then - t.fSelected := Copy(fSelected); - - t.fFeatureCount := fFeatureCount; - t.fFitted := fFitted; - - Result := t; + if fScoreFunc <> nil then + Result := new SelectKBest(fK, fScoreFunc) + else + Result := new SelectKBest(fK, fScoreType); end; //----------------------------- @@ -8954,11 +9022,7 @@ end; function Normalizer.Clone: ITransformer; begin - var t := new Normalizer(fNormType); - t.fFitted := fFitted; - t.fFeatureCount := fFeatureCount; - Result := t; + Result := new Normalizer(fNormType); end; - end. \ No newline at end of file diff --git a/bin/Lib/MLUtilsABC.pas b/bin/Lib/MLUtilsABC.pas index 3c1b13987..2937fdfb0 100644 --- a/bin/Lib/MLUtilsABC.pas +++ b/bin/Lib/MLUtilsABC.pas @@ -8,6 +8,24 @@ interface uses LinearAlgebraML; +/// Определяет фактический seed для генератора случайных чисел. +/// +/// Если seed >= 0: +/// • используется заданное пользователем значение +/// • userProvided := true +/// +/// Если seed < 0: +/// • генерируется псевдослучайный seed на основе системного времени +/// • userProvided := false +/// +/// Возвращает: +/// • неотрицательный seed, используемый для инициализации RNG +/// +/// Назначение: +/// • обеспечить единое поведение случайности во всех моделях +/// • сохранить воспроизводимость при явном задании seed +function ResolveRandomSeed(seed: integer; var userProvided: boolean): integer; + /// Преобразует вектор меток классов в массив целых чисел. /// Используется при визуализации и других задачах, /// где метки должны быть представлены как 0,1,2,... @@ -70,6 +88,20 @@ const 'Индекс метки {0} вне диапазона [0, {1})!!Label index {0} is out of range [0, {1})'; +function ResolveRandomSeed(seed: integer; var userProvided: boolean): integer; +begin + if seed < 0 then + begin + userProvided := false; + Result := System.Environment.TickCount and integer.MaxValue; + end + else + begin + userProvided := true; + Result := seed; + end; +end; + function LabelsToInts(y: Vector): array of integer; begin if y = nil then diff --git a/bin/Lib/PreprocessorABC.pas b/bin/Lib/PreprocessorABC.pas index 469df3f99..44315ad82 100644 --- a/bin/Lib/PreprocessorABC.pas +++ b/bin/Lib/PreprocessorABC.pas @@ -53,11 +53,18 @@ type /// Создаёт LabelEncoder для указанного столбца constructor Create(column: string); - /// Определяет множество категорий и сохраняет их коды +/// Определяет множество категорий столбца и сохраняет их числовое кодирование. +/// df — таблица данных. +/// Сохраняет отображение категорий в числовые коды для последующего применения. +/// +/// Примечание: +/// • отображение категорий НЕ копируется методом Clone function Fit(df: DataFrame): IPreprocessor; + /// Заменяет категории их числовыми кодами /// Возвращает новый DataFrame function Transform(df: DataFrame): DataFrame; + /// Выполняет Fit и Transform последовательно function FitTransform(df: DataFrame): DataFrame; @@ -65,6 +72,15 @@ type property ColumnName: string read col; +/// Создаёт копию препроцессора с той же конфигурацией. +/// +/// ВАЖНО: +/// • Clone копирует только конфигурацию (например, имя столбца) +/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.) +/// +/// Назначение: +/// • использование в Pipeline и CrossValidate +/// • обеспечивает независимое переобучение препроцессоров function Clone: IPreprocessor; end; @@ -82,8 +98,14 @@ type /// Создаёт OneHotEncoder для указанного столбца constructor Create(column: string); - /// Определяет множество категорий столбца +/// Определяет множество категорий столбца. +/// df — таблица данных. +/// Сохраняет категории для последующего преобразования в one-hot представление. +/// +/// Примечание: +/// • категории НЕ копируются методом Clone function Fit(df: DataFrame): IPreprocessor; + /// Заменяет столбец набором бинарных столбцов /// Возвращает новый DataFrame function Transform(df: DataFrame): DataFrame; @@ -93,7 +115,16 @@ type function ToString: string; override; property ColumnName: string read col; - + +/// Создаёт копию препроцессора с той же конфигурацией. +/// +/// ВАЖНО: +/// • Clone копирует только конфигурацию (например, имя столбца) +/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.) +/// +/// Назначение: +/// • использование в Pipeline и CrossValidate +/// • обеспечивает независимое переобучение препроцессоров function Clone: IPreprocessor; end; @@ -118,11 +149,19 @@ type /// Создаёт Imputer с константной стратегией заполнения constructor Create(value: object; params columns: array of string); - /// Вычисляет значения для заполнения пропусков +/// Вычисляет значения для заполнения пропусков. +/// df — таблица данных. +/// В зависимости от стратегии вычисляет параметры заполнения +/// (например, среднее, медиану или константу) для каждого столбца. +/// +/// Примечание: +/// • вычисленные параметры НЕ копируются методом Clone function Fit(df: DataFrame): IPreprocessor; + /// Заполняет пропущенные значения в DataFrame /// Возвращает новый DataFrame function Transform(df: DataFrame): DataFrame; + /// Выполняет Fit и Transform последовательно function FitTransform(df: DataFrame): DataFrame; @@ -130,6 +169,15 @@ type property Columns: array of string read cols; +/// Создаёт копию препроцессора с той же конфигурацией. +/// +/// ВАЖНО: +/// • Clone копирует только конфигурацию (например, имя столбца) +/// • Clone НЕ копирует обученное состояние (mapping, статистики и т.п.) +/// +/// Назначение: +/// • использование в Pipeline и CrossValidate +/// • обеспечивает независимое переобучение препроцессоров function Clone: IPreprocessor; end; @@ -262,7 +310,7 @@ begin var n := df.RowCount; var data := new integer[n]; - var valid: array of boolean := nil; + var valid := new boolean[n]; var cur := df.GetCursor; var row := 0; @@ -270,13 +318,7 @@ begin begin if not cur.IsValid(idx) then begin - if valid = nil then - begin - valid := new boolean[n]; - for var j := 0 to row - 1 do - valid[j] := true; - end; - valid[row] := false; + valid[row] := False; data[row] := 0; end else @@ -287,8 +329,7 @@ begin Error(ER_LABELENCODER_UNSEEN_CATEGORY, s); data[row] := mapping[s]; - if valid <> nil then - valid[row] := true; + valid[row] := True; end; row += 1;