diff --git a/Configuration/GlobalAssemblyInfo.cs b/Configuration/GlobalAssemblyInfo.cs index 2b8df16db..a7fdf8f75 100644 --- a/Configuration/GlobalAssemblyInfo.cs +++ b/Configuration/GlobalAssemblyInfo.cs @@ -15,7 +15,7 @@ internal static class RevisionClass public const string Major = "3"; public const string Minor = "11"; public const string Build = "1"; - public const string Revision = "3790"; + public const string Revision = "3793"; public const string MainVersion = Major + "." + Minor; public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision; diff --git a/Configuration/Version.defs b/Configuration/Version.defs index 7a435981c..bceaaea61 100644 --- a/Configuration/Version.defs +++ b/Configuration/Version.defs @@ -1,4 +1,4 @@ -%COREVERSION%=1 -%REVISION%=3790 %MINOR%=11 +%REVISION%=3793 +%COREVERSION%=1 %MAJOR%=3 diff --git a/Release/pabcversion.txt b/Release/pabcversion.txt index 60296ca9a..07ecdcc9e 100644 --- a/Release/pabcversion.txt +++ b/Release/pabcversion.txt @@ -1 +1 @@ -3.11.1.3790 +3.11.1.3793 diff --git a/ReleaseGenerators/PascalABCNET_version.nsh b/ReleaseGenerators/PascalABCNET_version.nsh index b4cbf3d7f..5d7169767 100644 --- a/ReleaseGenerators/PascalABCNET_version.nsh +++ b/ReleaseGenerators/PascalABCNET_version.nsh @@ -1 +1 @@ -!define VERSION '3.11.1.3790' +!define VERSION '3.11.1.3793' diff --git a/bin/Lib/DataFrameABC.pas b/bin/Lib/DataFrameABC.pas index 7543fc76e..1ee86566f 100644 --- a/bin/Lib/DataFrameABC.pas +++ b/bin/Lib/DataFrameABC.pas @@ -10,7 +10,7 @@ interface uses DataFrameABCCore; -const DataFrameVersion = '1.0'; +const DataFrameVersion = '1.1'; // Реэкспортируем публичные типы для удобства type @@ -93,6 +93,9 @@ type function GetColumnIndex(name: string): integer; function GetColumn(name: string): Column; + + function CloneWithCopiedColumns: DataFrame; + static procedure EnsureValidArray(var valid: array of boolean; rowCount, filledCount: integer); public /// Создает пустой DataFrame constructor Create; @@ -2475,7 +2478,7 @@ begin cnt += 1; end; - if cnt = 0 then + if cnt <= 1 then exit(0.0); var mean := sum / cnt; @@ -2491,7 +2494,7 @@ begin acc += d * d; end; - Result := acc / cnt; + Result := acc / (cnt - 1); end; function DataFrame.Variance(colName: string): real @@ -3182,14 +3185,10 @@ begin Result := columns[idx]; end; -function DataFrame.WithColumnInt(name: string; f: DataFrameCursor -> integer): DataFrame; +function DataFrame.CloneWithCopiedColumns: DataFrame; begin - if fSchema.HasColumn(name) then - ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); - - var res := new DataFrame; + Result := new DataFrame; - // 1. копируем столбцы (без categorical!) for var i := 0 to columns.Count - 1 do begin var col := columns[i]; @@ -3198,30 +3197,47 @@ begin ctInt: begin var c := IntColumn(col); - res.AddIntColumn(c.Info.Name, c.Data, c.IsValid); + Result.AddIntColumn(c.Info.Name, c.Data, c.IsValid); end; ctStr: begin var c := StrColumn(col); - res.AddStrColumn(c.Info.Name, c.Data, c.IsValid); + Result.AddStrColumn(c.Info.Name, c.Data, c.IsValid); end; ctFloat: begin var c := FloatColumn(col); - res.AddFloatColumn(c.Info.Name, c.Data, c.IsValid); + Result.AddFloatColumn(c.Info.Name, c.Data, c.IsValid); end; ctBool: begin var c := BoolColumn(col); - res.AddBoolColumn(c.Info.Name, c.Data, c.IsValid); + Result.AddBoolColumn(c.Info.Name, c.Data, c.IsValid); end; end; 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 + ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); + + var res := CloneWithCopiedColumns; - // 2. вычисляем новый столбец var data := new integer[RowCount]; var valid: array of boolean := nil; @@ -3236,14 +3252,7 @@ begin on e: Exception do begin data[i] := 0; - - if valid = nil then - begin - valid := new boolean[RowCount]; - for var j := 0 to i - 1 do - valid[j] := true; - end; - + EnsureValidArray(valid, RowCount, i); valid[i] := false; i += 1; continue; @@ -3256,13 +3265,10 @@ begin i += 1; end; - // 3. добавляем новый столбец res.AddIntColumn(name, data, valid); - res.SetSchema(ExtendSchema(name, ctInt, false)); Result := res; - AssertSchemaConsistent; end; @@ -3270,41 +3276,9 @@ function DataFrame.WithColumnFloat(name: string; f: DataFrameCursor -> real): Da begin if fSchema.HasColumn(name) then ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); - - var res := new DataFrame; - // 1. копируем существующие колонки - for var i := 0 to columns.Count - 1 do - begin - var col := columns[i]; - case col.Info.ColType of - ctInt: - begin - var c := IntColumn(col); - res.AddIntColumn(c.Info.Name, c.Data, c.IsValid); - end; + var res := CloneWithCopiedColumns; - ctStr: - begin - var c := StrColumn(col); - res.AddStrColumn(c.Info.Name, c.Data, c.IsValid); - end; - - ctFloat: - begin - var c := FloatColumn(col); - res.AddFloatColumn(c.Info.Name, c.Data, c.IsValid); - end; - - ctBool: - begin - var c := BoolColumn(col); - res.AddBoolColumn(c.Info.Name, c.Data, c.IsValid); - end; - end; - end; - - // 2. вычисляем новый столбец var data := new real[RowCount]; var valid: array of boolean := nil; @@ -3316,16 +3290,10 @@ begin try data[i] := f(cur); except + on e: Exception do begin data[i] := 0.0; - - if valid = nil then - begin - valid := new boolean[RowCount]; - for var j := 0 to i - 1 do - valid[j] := true; - end; - + EnsureValidArray(valid, RowCount, i); valid[i] := false; i += 1; continue; @@ -3338,13 +3306,10 @@ begin i += 1; end; - // 3. добавляем новый столбец res.AddFloatColumn(name, data, valid); - res.SetSchema(ExtendSchema(name, ctFloat, false)); Result := res; - AssertSchemaConsistent; end; @@ -3352,41 +3317,9 @@ function DataFrame.WithColumnStr(name: string; f: DataFrameCursor -> string): Da begin if fSchema.HasColumn(name) then ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); - - var res := new DataFrame; - // 1. копируем существующие колонки - for var i := 0 to columns.Count - 1 do - begin - var col := columns[i]; - case col.Info.ColType of - ctInt: - begin - var c := IntColumn(col); - res.AddIntColumn(c.Info.Name, c.Data, c.IsValid); - end; + var res := CloneWithCopiedColumns; - ctStr: - begin - var c := StrColumn(col); - res.AddStrColumn(c.Info.Name, c.Data, c.IsValid); - end; - - ctFloat: - begin - var c := FloatColumn(col); - res.AddFloatColumn(c.Info.Name, c.Data, c.IsValid); - end; - - ctBool: - begin - var c := BoolColumn(col); - res.AddBoolColumn(c.Info.Name, c.Data, c.IsValid); - end; - end; - end; - - // 2. вычисляем новый столбец var data := new string[RowCount]; var valid: array of boolean := nil; @@ -3398,16 +3331,10 @@ begin try data[i] := f(cur); except + on e: Exception do begin - data[i] := ''; - - if valid = nil then - begin - valid := new boolean[RowCount]; - for var j := 0 to i - 1 do - valid[j] := true; - end; - + data[i] := nil; + EnsureValidArray(valid, RowCount, i); valid[i] := false; i += 1; continue; @@ -3420,13 +3347,10 @@ begin i += 1; end; - // 3. добавляем новый столбец res.AddStrColumn(name, data, valid); - res.SetSchema(ExtendSchema(name, ctStr, false)); Result := res; - AssertSchemaConsistent; end; @@ -3434,41 +3358,9 @@ function DataFrame.WithColumnBool(name: string; f: DataFrameCursor -> boolean): begin if fSchema.HasColumn(name) then ArgumentError(ER_COLUMN_ALREADY_EXISTS, name); - - var res := new DataFrame; - // 1. копируем существующие колонки - for var i := 0 to columns.Count - 1 do - begin - var col := columns[i]; - case col.Info.ColType of - ctInt: - begin - var c := IntColumn(col); - res.AddIntColumn(c.Info.Name, c.Data, c.IsValid); - end; + var res := CloneWithCopiedColumns; - ctStr: - begin - var c := StrColumn(col); - res.AddStrColumn(c.Info.Name, c.Data, c.IsValid); - end; - - ctFloat: - begin - var c := FloatColumn(col); - res.AddFloatColumn(c.Info.Name, c.Data, c.IsValid); - end; - - ctBool: - begin - var c := BoolColumn(col); - res.AddBoolColumn(c.Info.Name, c.Data, c.IsValid); - end; - end; - end; - - // 2. вычисляем новый столбец var data := new boolean[RowCount]; var valid: array of boolean := nil; @@ -3480,16 +3372,10 @@ begin try data[i] := f(cur); except + on e: Exception do begin data[i] := false; - - if valid = nil then - begin - valid := new boolean[RowCount]; - for var j := 0 to i - 1 do - valid[j] := true; - end; - + EnsureValidArray(valid, RowCount, i); valid[i] := false; i += 1; continue; @@ -3502,13 +3388,10 @@ begin i += 1; end; - // 3. добавляем новый столбец res.AddBoolColumn(name, data, valid); - res.SetSchema(ExtendSchema(name, ctBool, false)); Result := res; - AssertSchemaConsistent; end; @@ -3549,27 +3432,56 @@ begin var cur := GetCursor; var row := 0; + while cur.MoveNext do begin + // --- NA: обрабатываем явно, без исключений + 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; + end; + + // --- вычисление try data[row] := f(cur); - if valid <> nil then valid[row] := true; except 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; + for var j := 0 to row - 1 do + valid[j] := true; end; + valid[row] := false; + row += 1; + continue; end; end; + + if valid <> nil then + valid[row] := true; + row += 1; end; + // --- сборка результата var res := new DataFrame; + for var i := 0 to columns.Count - 1 do if i <> colIndex then res.AddColumnView(columns[i]) @@ -5452,11 +5364,8 @@ begin Error(ER_ZERO_STD_STANDARDIZE); Result := df.ReplaceColumnFloat(colName, cur -> - begin - if not cur.IsValid(idx) then - Error(ER_INVALID_VALUE_IN_COLUMN, colName); - Result := (cur.Float(idx) - mean) / std; - end); + (cur.Float(idx) - mean) / std + ); end; static function Statistics.StandardizeAll(df: DataFrame): DataFrame; @@ -5653,37 +5562,6 @@ end; // CSVLoader //----------------------------- -{procedure ScanFields(line: string; delimiter: char; - starts, lens: array of integer; var actualCount: integer); -begin - var j := 1; - var col := 0; - var n := line.Length; - - starts[0] := 1; - - while j <= n do - begin - if line[j] = delimiter then - begin - if col < starts.Length then - lens[col] := j - starts[col]; - col += 1; - if col < starts.Length then - starts[col] := j + 1; - end; - j += 1; - end; - - if col < starts.Length then - lens[col] := n - starts[col] + 1; - - for var k := col + 1 to starts.Length - 1 do - lens[k] := 0; - - actualCount := col + 1; -end;} - procedure ScanFieldsQuoted( line: string; delimiter: char; starts, lens: array of integer; @@ -6466,13 +6344,34 @@ begin System.IO.FileShare.ReadWrite ); try + // --- BOM check var bom := new byte[3]; var n := fs.Read(bom, 0, 3); - + + // UTF-8 BOM if (n >= 3) and (bom[0] = $EF) and (bom[1] = $BB) and (bom[2] = $BF) then - Result := Encoding.UTF8 - else - Result := Encoding.UTF8; + exit(Encoding.UTF8); + + // --- reset position + fs.Position := 0; + + // --- read small chunk + var bufferSize := 4096; + var buffer := new byte[bufferSize]; + var readBytes := fs.Read(buffer, 0, bufferSize); + + // --- strict UTF-8 check + try + var utf8 := new System.Text.UTF8Encoding(false, true); // strict + utf8.GetString(buffer, 0, readBytes); + exit(Encoding.UTF8); + except + // not UTF-8 + end; + + // --- fallback + Result := Encoding.Default; + finally fs.Close; end; diff --git a/bin/Lib/DataFrameABCCore.pas b/bin/Lib/DataFrameABCCore.pas index 53246852c..caac83eea 100644 --- a/bin/Lib/DataFrameABCCore.pas +++ b/bin/Lib/DataFrameABCCore.pas @@ -789,7 +789,14 @@ end;} function JoinKey.Equals(oth: object): boolean; begin - var other: JoinKey := JoinKey(oth); + if oth = nil then + exit(false); + + if not (oth is JoinKey) then + exit(false); + + var other := JoinKey(oth); + if Ints.Length <> other.Ints.Length then exit(false); if Floats.Length <> other.Floats.Length then exit(false); if Strs.Length <> other.Strs.Length then exit(false); diff --git a/bin/Lib/LinearAlgebraML.pas b/bin/Lib/LinearAlgebraML.pas index f15a48410..8801a17c4 100644 --- a/bin/Lib/LinearAlgebraML.pas +++ b/bin/Lib/LinearAlgebraML.pas @@ -327,6 +327,9 @@ const ER_SINGULAR_MATRIX = 'Матрица вырождена или плохо обусловлена!!Matrix is singular or ill-conditioned'; +type + MLNotSPDException = class(MLException); + //----------------------------- // Vector //----------------------------- @@ -1254,7 +1257,6 @@ begin end; - // Helper function Cholesky(A: Matrix): Matrix; begin @@ -1274,7 +1276,7 @@ begin if i = j then begin if s <= 0.0 then - Error(ER_MATRIX_NOT_SPD); + raise new MLNotSPDException('Матрица не является положительно определённой (SPD)'); L[i, i] := Sqrt(s); end else @@ -1423,6 +1425,7 @@ begin DimensionError(ER_VECTOR_SIZE_MISMATCH, b.Length, A.RowCount); var L := Cholesky(A); + var y := SolveLowerTriangular(L, b); Result := SolveUpperTriangular(L.Transpose, y); end; @@ -1432,7 +1435,8 @@ begin try Result := SolveSPD(A, b); except - Result := Solve(A, b); + on e: MLNotSPDException do + Result := Solve(A, b); end; end; diff --git a/bin/Lib/MLABC.pas b/bin/Lib/MLABC.pas index d4384b967..671150fd9 100644 --- a/bin/Lib/MLABC.pas +++ b/bin/Lib/MLABC.pas @@ -92,7 +92,7 @@ type IModel = MLCoreABC.IModel; ISupervisedModel = MLCoreABC.ISupervisedModel; - IUnSupervisedModel = MLCoreABC.IUnSupervisedModel; + IUnsupervisedModel = MLCoreABC.IUnsupervisedModel; UPipeline = MLModelsABC.UPipeline; UDataPipeline = MLPipelineABC.UDataPipeline; TaskKind = MLPipelineABC.TaskKind; diff --git a/bin/Lib/MLDatasets.pas b/bin/Lib/MLDatasets.pas index cb20a301f..fe1ad94f7 100644 --- a/bin/Lib/MLDatasets.pas +++ b/bin/Lib/MLDatasets.pas @@ -1303,26 +1303,31 @@ end; static function Datasets.StudentExam: Dataset; begin + NotImplementedError(ER_NOT_IMPLEMENTED, 'Datasets.StudentExam'); Result := nil; end; static function Datasets.BankClients: Dataset; begin + NotImplementedError(ER_NOT_IMPLEMENTED, 'Datasets.BankClients'); Result := nil; end; static function Datasets.TaxiTrips: Dataset; begin + NotImplementedError(ER_NOT_IMPLEMENTED, 'Datasets.TaxiTrips'); Result := nil; end; static function Datasets.MoscowTransport: Dataset; begin + NotImplementedError(ER_NOT_IMPLEMENTED, 'Datasets.MoscowTransport'); Result := nil; end; static function Datasets.OnlineShopping: Dataset; begin + NotImplementedError(ER_NOT_IMPLEMENTED, 'Datasets.OnlineShopping'); Result := nil; end; diff --git a/bin/Lib/MLExceptions.pas b/bin/Lib/MLExceptions.pas index db02c0e16..c72288fb3 100644 --- a/bin/Lib/MLExceptions.pas +++ b/bin/Lib/MLExceptions.pas @@ -23,7 +23,9 @@ const 'Пустой набор данных для {0}!!Empty dataset for {0}'; ER_COLUMN_NOT_FOUND = 'В DataFrame отсутствует столбец "{0}"!!DataFrame does not contain column "{0}"'; - + ER_NOT_IMPLEMENTED = + 'Метод {0} не реализован!!Method {0} is not implemented'; + type /// Базовое исключение ML-библиотеки MLException = class(Exception); @@ -33,6 +35,8 @@ type /// Несоответствие размерностей MLDimensionException = class(MLException); + + MLNotImplementedException = class(MLException); procedure Error(msg: string; params args: array of object); procedure ArgumentError(msg: string; params args: array of object); @@ -40,6 +44,7 @@ procedure NotFittedError(msg: string; params args: array of object); procedure DimensionError(msg: string; params args: array of object); procedure ArgumentNullError(msg: string; params args: array of object); procedure ArgumentOutOfRangeError(msg: string; params args: array of object); +procedure NotImplementedError(msg: string; params args: array of object); implementation @@ -83,4 +88,10 @@ begin raise new System.ArgumentOutOfRangeException(FormatSafe(msg, args)); end; +procedure NotImplementedError(msg: string; params args: array of object); +begin + raise new MLNotImplementedException(FormatSafe(msg, args)); +end; + + end. diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index 29fc0ad99..181549a2d 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -1013,7 +1013,7 @@ type : ISupervisedModel; procedure BuildClassMapping(y: Vector); - function EncodeLabels(y: Vector): array of integer; + function ApplyLabelEncoding(y: Vector): array of integer; procedure SoftmaxRow(var logits: array of real; var probs: array of real); procedure SoftmaxMatrix(logits: Matrix; probs: Matrix); @@ -1339,7 +1339,6 @@ type private fEps: real; fMinSamples: integer; - fSeed: integer; fFitted: boolean; fFeatureCount: integer; @@ -1356,8 +1355,7 @@ type /// seed — параметр для совместимости API. constructor Create( eps: real; - minSamples: integer := 5; - seed: integer := -1 + minSamples: integer := 5 ); /// Обучает модель на матрице признаков. @@ -3506,18 +3504,6 @@ begin if X.RowCount <> y.Length then DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); - if fMinSamplesSplit < 2 then - ArgumentOutOfRangeError(ER_MINSAMPLESSPLIT_INVALID, fMinSamplesSplit); - - if fMinSamplesLeaf < 1 then - ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf); - - if fMaxDepth < -1 then - ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); - - if fMaxDepth > 10000 then - ArgumentError(ER_MAX_DEPTH_TOO_LARGE, fMaxDepth); - fFeatureImportances := new Vector(X.ColCount); @@ -3758,21 +3744,9 @@ begin if X.RowCount <> y.Length then DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); - if fMinSamplesSplit < 2 then - ArgumentOutOfRangeError(ER_MINSAMPLESSPLIT_INVALID, fMinSamplesSplit); - - if fMinSamplesLeaf < 1 then - ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf); - if fLeafL2 < 0 then ArgumentOutOfRangeError(ER_L2_NEGATIVE, fLeafL2); - if fMaxDepth < -1 then - ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); - - if fMaxDepth > 10000 then - ArgumentError(ER_MAX_DEPTH_TOO_LARGE, fMaxDepth); - fFeatureImportances := new Vector(X.ColCount); var indices: array of integer; @@ -3986,24 +3960,6 @@ begin if X.RowCount <> y.Length then DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); - if fNTrees <= 0 then - ArgumentOutOfRangeError(ER_NTREES_INVALID, fNTrees); - - if fMinSamplesSplit < 2 then - ArgumentOutOfRangeError(ER_MINSAMPLESSPLIT_INVALID, fMinSamplesSplit); - - if fMinSamplesLeaf < 1 then - ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf); - - if fMinSamplesLeaf >= fMinSamplesSplit then - ArgumentError(ER_MIN_LEAF_GE_SPLIT, fMinSamplesLeaf, fMinSamplesSplit); - - if fMaxDepth < -1 then - ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); - - if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then - ArgumentError(ER_MAX_DEPTH_TOO_LARGE, fMaxDepth); - var n := X.RowCount; var p := X.ColCount; @@ -4231,24 +4187,6 @@ begin if X.RowCount <> y.Length then DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length); - if fNTrees <= 0 then - ArgumentOutOfRangeError(ER_NTREES_INVALID, fNTrees); - - if fMinSamplesSplit < 2 then - ArgumentOutOfRangeError(ER_MINSAMPLESSPLIT_INVALID, fMinSamplesSplit); - - if fMinSamplesLeaf < 1 then - ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf); - - if fMinSamplesLeaf >= fMinSamplesSplit then - ArgumentError(ER_MIN_LEAF_GE_SPLIT, fMinSamplesLeaf, fMinSamplesSplit); - - if fMaxDepth < -1 then - ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); - - if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then - ArgumentError(ER_MAX_DEPTH_TOO_LARGE, fMaxDepth); - var n := X.RowCount; var p := X.ColCount; @@ -4594,6 +4532,51 @@ begin Result := fClassLabels; end; +//----------------------------- +// Helper GBC GBR +//----------------------------- + +function BuildSubsampleIndices(nTrain: integer; subsample: real; rng: System.Random): array of integer; +begin + if nTrain <= 0 then + ArgumentOutOfRangeError(ER_EMPTY_DATASET); + + if (subsample <= 0.0) or (subsample > 1.0) then + ArgumentOutOfRangeError(ER_SUBSAMPLE_INVALID, subsample); + + var k := Floor(nTrain * subsample); + if k < 1 then + k := 1; + if k > nTrain then + k := nTrain; + + // если берём всё — можно быстро вернуть [0..nTrain-1] + if k = nTrain then + begin + Result := new integer[nTrain]; + for var i := 0 to nTrain - 1 do + Result[i] := i; + exit; + end; + + var all := new integer[nTrain]; + for var i := 0 to nTrain - 1 do + all[i] := i; + + // partial Fisher–Yates (без повторений) + for var i := 0 to k - 1 do + begin + var j := i + rng.Next(nTrain - i); + var tmp := all[i]; + all[i] := all[j]; + all[j] := tmp; + end; + + Result := new integer[k]; + for var i := 0 to k - 1 do + Result[i] := all[i]; +end; + //----------------------------- // GradientBoostingRegressor //----------------------------- @@ -4629,6 +4612,9 @@ begin if minSamplesLeaf < 1 then ArgumentOutOfRangeError(ER_MIN_SAMPLES_LEAF_INVALID, minSamplesLeaf); + + if maxDepth > MAX_ALLOWED_TREE_DEPTH then + ArgumentOutOfRangeError(ER_MAX_DEPTH_TOO_LARGE, maxDepth); fNEstimators := nEstimators; fLearningRate := learningRate; @@ -4905,28 +4891,6 @@ begin DimensionError(ER_FEATURE_COUNT_MISMATCH); end; - // --- hyperparameter checks --- - if fNEstimators <= 0 then - ArgumentOutOfRangeError(ER_NESTIMATORS_INVALID, fNEstimators); - - if fLearningRate <= 0 then - ArgumentOutOfRangeError(ER_LEARNING_RATE_INVALID, fLearningRate); - - if (fSubsample <= 0.0) or (fSubsample > 1.0) then - ArgumentOutOfRangeError(ER_SUBSAMPLE_INVALID, fSubsample); - - if fMinSamplesSplit < 2 then - ArgumentOutOfRangeError(ER_MINSAMPLESSPLIT_INVALID, fMinSamplesSplit); - - if fMinSamplesLeaf < 1 then - ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf); - - if fMaxDepth < 0 then - ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); - - if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then - ArgumentError(ER_MAX_DEPTH_TOO_LARGE, fMaxDepth); - // --- init state --- fOOBLossHistory.Clear; fEstimators.Clear; @@ -4994,40 +4958,20 @@ begin // --- subsample --- var rows: array of integer := nil; var used: array of boolean := nil; - + if useSubsample then begin - var k := Floor(nTrain * fSubsample); - if k < 1 then k := 1; - if k > nTrain then k := nTrain; - - var all := new integer[nTrain]; - for var i := 0 to nTrain - 1 do - all[i] := i; - - // partial Fisher–Yates - for var i := 0 to k - 1 do - begin - var j := i + fRng.Next(nTrain - i); - var tmp := all[i]; - all[i] := all[j]; - all[j] := tmp; - end; - - rows := new integer[k]; - for var i := 0 to k - 1 do - rows[i] := all[i]; - + rows := BuildSubsampleIndices(nTrain, fSubsample, fRng); tree.SetRowIndices(rows); - + if useOOB then begin used := new boolean[nTrain]; - for var i := 0 to k - 1 do + for var i := 0 to rows.Length - 1 do used[rows[i]] := true; end; end; - + tree.Fit(XTrain, r); fEstimators.Add(tree); @@ -5399,6 +5343,7 @@ begin Result := copy; end; + //----------------------------- // GradientBoostingClassifier //----------------------------- @@ -5450,28 +5395,6 @@ begin DimensionError(ER_FEATURE_COUNT_MISMATCH); end; - // --- hyperparameter checks --- - if fNEstimators <= 0 then - ArgumentOutOfRangeError(ER_NESTIMATORS_INVALID, fNEstimators); - - if fLearningRate <= 0 then - ArgumentOutOfRangeError(ER_LEARNING_RATE_INVALID, fLearningRate); - - if (fSubsample <= 0.0) or (fSubsample > 1.0) then - ArgumentOutOfRangeError(ER_SUBSAMPLE_INVALID, fSubsample); - - if fMinSamplesSplit < 2 then - ArgumentOutOfRangeError(ER_MINSAMPLESSPLIT_INVALID, fMinSamplesSplit); - - if fMinSamplesLeaf < 1 then - ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf); - - if fMaxDepth < 0 then - ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); - - if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then - ArgumentError(ER_MAX_DEPTH_TOO_LARGE, fMaxDepth); - // --- reset state fOOBLossHistory.Clear; fEstimators.Clear; @@ -5485,7 +5408,7 @@ begin // --- mapping BuildClassMapping(yTrain); - var yEncoded := EncodeLabels(yTrain); + var yEncoded := ApplyLabelEncoding(yTrain); var nTrain := XTrain.RowCount; var classCount := fClassCount; @@ -5540,7 +5463,7 @@ begin if useValidation then begin logitsVal := new Matrix(XVal.RowCount, classCount); - yValEncoded := EncodeLabels(yVal); + yValEncoded := ApplyLabelEncoding(yVal); // --- инициализация log-prior for var i := 0 to XVal.RowCount - 1 do @@ -5578,6 +5501,11 @@ begin // inBag var inBag: array of boolean := nil; + if useSubsample then + begin + subIndices := BuildSubsampleIndices(nTrain, fSubsample, fRng); + end; + if useOOB then begin SetLength(inBag, nTrain); @@ -5741,7 +5669,7 @@ begin fClassIndex[fClasses[cls]] := cls; end; -function GradientBoostingClassifier.EncodeLabels(y: Vector): array of integer; +function GradientBoostingClassifier.ApplyLabelEncoding(y: Vector): array of integer; begin var n := y.Length; Result := new integer[n]; @@ -7297,8 +7225,7 @@ end; constructor DBSCAN.Create( eps: real; - minSamples: integer; - seed: integer + minSamples: integer ); begin if eps <= 0 then @@ -7309,7 +7236,6 @@ begin fEps := eps; fMinSamples := minSamples; - fSeed := seed; fFitted := False; end; @@ -7454,7 +7380,7 @@ end; function DBSCAN.Clone: IModel; begin - var m := new DBSCAN(fEps, fMinSamples, fSeed); + var m := new DBSCAN(fEps, fMinSamples); m.fFitted := fFitted; m.fFeatureCount := fFeatureCount; diff --git a/bin/Lib/PlotML.pas b/bin/Lib/PlotML.pas index a26e7b022..cfaaf6f0d 100644 --- a/bin/Lib/PlotML.pas +++ b/bin/Lib/PlotML.pas @@ -101,8 +101,6 @@ type procedure Hist(x: Vector; bins: integer := 0; color: ColorWPF := DefaultColor; alpha: real := 0.7; legend: string := nil); - //procedure Surface(labels: array of integer; nx, ny: integer; xmin, xmax, ymin, ymax: real; pal: PlotML.Palette := nil); - procedure Surface(x1, x2: array of real; nx, ny: integer; f: Matrix -> array of integer; pal: PlotML.Palette := nil); procedure Heatmap(m: Matrix); @@ -1319,16 +1317,6 @@ begin end; end; -{procedure Cell.Surface(labels: array of integer; nx, ny: integer; xmin, xmax, ymin, ymax: real; pal: PlotML.Palette); -begin - Plot.RunUI(() -> - begin - EnsureChart; - var usePal := if pal <> nil then pal else palette; - Plot.DrawSurface(chart, labels, nx, ny, xmin, xmax, ymin, ymax, usePal); - end); -end;} - procedure Cell.Surface(x1, x2: array of real; nx, ny: integer; f: Matrix -> array of integer; pal: PlotML.Palette); begin Plot.RunUI(() -> diff --git a/bin/Lib/ValidationML.pas b/bin/Lib/ValidationML.pas index 76a476534..e9d675820 100644 --- a/bin/Lib/ValidationML.pas +++ b/bin/Lib/ValidationML.pas @@ -69,7 +69,8 @@ type X: Matrix; y: Vector; k: integer; metric: (Vector, Vector) -> real; - maximize: boolean := True + maximize: boolean := True; + seed: integer := -1 ): (real, real, T); where T: class,ISupervisedModel; end; @@ -447,7 +448,8 @@ class function GridSearch.Search( y: Vector; k: integer; metric: (Vector, Vector) -> real; - maximize: boolean + maximize: boolean; + seed: integer ): (real, real, T); where T: class,ISupervisedModel; begin if modelFactory = nil then @@ -480,7 +482,7 @@ begin var model := modelFactory(param); if model = nil then ArgumentError(ER_MODEL_NULL); - var avgScore := Validation.CrossValidate(model, X, y, k, metric); + var avgScore := Validation.CrossValidate(model, X, y, k, metric, seed); if double.IsNaN(avgScore) or double.IsInfinity(avgScore) then ArgumentError(ER_INVALID_VALUE, 'avgScore');