В конструкторах разрешены именованные аргументы.

Интересно, почему раньше были запрещены

ML - первая версия GradientBoostingClassifier
This commit is contained in:
Mikhalkovich Stanislav 2026-02-22 21:01:14 +03:00
parent e144f5c097
commit a7b113e6f8
11 changed files with 3034 additions and 2424 deletions

View file

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

View file

@ -1,4 +1,4 @@
%COREVERSION%=1
%REVISION%=3762
%MINOR%=11
%REVISION%=3763
%COREVERSION%=1
%MAJOR%=3

View file

@ -2,7 +2,7 @@
// This CSharp output file generated by Gardens Point LEX
// Version: 1.1.3.301
// Machine: DESKTOP-L2INHKG
// DateTime: 08.11.2025 23:08:35
// DateTime: 22.02.2026 20:16:29
// UserName: user
// GPLEX input file <ABCPascal.lex>
// GPLEX frame file <embedded resource>

View file

@ -84,7 +84,7 @@
%type <stn> exception_handler
%type <stn> exception_handler_list
%type <stn> exception_identifier
%type <stn> typed_const_list1 typed_const_list optional_expr_list optional_expr_list_func_param elem_list optional_expr_list_with_bracket expr_list expr_list_func_param const_elem_list1 /*const_expr_list*/ case_label_list const_elem_list optional_const_func_expr_list elem_list1
%type <stn> typed_const_list1 typed_const_list optional_expr_list optional_expr_list_func_param elem_list optional_expr_list_with_bracket optional_expr_list_func_param_with_bracket expr_list expr_list_func_param const_elem_list1 /*const_expr_list*/ case_label_list const_elem_list optional_const_func_expr_list elem_list1
%type <stn> enumeration_id expr_l1_or_unpacked_list
// %type <stn> expr_l1_list
%type <stn> enumeration_id_list
@ -3523,7 +3523,7 @@ optional_array_initializer
;
new_expr
: tkNew simple_or_template_type_reference optional_expr_list_with_bracket
: tkNew simple_or_template_type_reference optional_expr_list_func_param_with_bracket
{
$$ = new new_expr($2, $3 as expression_list, false, null, @$);
}
@ -3614,6 +3614,13 @@ optional_expr_list_with_bracket
| tkRoundOpen optional_expr_list tkRoundClose
{ $$ = $2; }
;
optional_expr_list_func_param_with_bracket
:
{ $$ = null; }
| tkRoundOpen optional_expr_list_func_param tkRoundClose
{ $$ = $2; }
;
relop_expr
: simple_expr

File diff suppressed because it is too large Load diff

View file

@ -396,5 +396,6 @@ script=

View file

@ -1 +1 @@
3.11.1.3762
3.11.1.3763

View file

@ -1 +1 @@
!define VERSION '3.11.1.3762'
!define VERSION '3.11.1.3763'

View file

@ -3083,7 +3083,7 @@ namespace PascalABCCompiler.TreeConverter
return AddError<function_node>(err_out);
return list[0];
}
else
else // здесь логика именованных аргументов
{
var indexOfFirstNamedArgumentSem = indexOfFirstNamedArgument + params_inc;

View file

@ -47,6 +47,7 @@ type
RandomForestRegressor = MLModelsABC.RandomForestRegressor;
RandomForestClassifier = MLModelsABC.RandomForestClassifier;
GradientBoostingRegressor = MLModelsABC.GradientBoostingRegressor;
GradientBoostingClassifier = MLModelsABC.GradientBoostingClassifier;
TGBLoss = MLModelsABC.TGBLoss;
TMaxFeaturesMode = MLModelsABC.TMaxFeaturesMode;

View file

@ -495,6 +495,75 @@ type
property ValLossHistory: List<real> read fValLossHistory;
property BestIteration: integer read fBestIteration;
end;
TGBCLoss = (LogLoss);
type
GradientBoostingClassifier = class(IProbabilisticClassifier)
private
// hyperparams
fNEstimators: integer;
fLearningRate: real;
fMaxDepth: integer;
fMinSamplesSplit: integer;
fMinSamplesLeaf: integer;
fSubsample: real;
fRandomSeed: integer;
fEarlyStoppingPatience: integer;
// fitted state
fFitted: boolean;
fFeatureCount: integer;
fClassCount: integer;
fClasses: array of integer; // реальные метки классов (unique)
fClassIndex: Dictionary<integer,integer>; // label -> 0..K-1
// [m][k] дерево для класса k на итерации m
fEstimators: List<array of DecisionTreeRegressor>;
// init logits (как минимум нули; позже можно log-prior)
fInitLogits: array of real; // длины K
// diagnostics
fTrainLossHistory: List<real>;
fValLossHistory: List<real>;
fBestIteration: integer;
fBestValLoss: real;
private
function FitInternal(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector; useValidation: boolean): IModel;
procedure BuildClassMapping(y: Vector);
function EncodeLabels(y: Vector): array of integer;
procedure SoftmaxRow(var logits: array of real; var probs: array of real);
procedure SoftmaxMatrix(logits: Matrix; probs: Matrix);
function ComputeLogLoss(yEncoded: array of integer; probs: Matrix): real;
public
constructor Create(
nEstimators: integer := 200;
learningRate: real := 0.05;
maxDepth: integer := 3;
minSamplesSplit: integer := 2;
minSamplesLeaf: integer := 1;
subsample: real := 1.0;
randomSeed: integer := 42;
earlyStoppingPatience: integer := 20);
function Fit(X: Matrix; y: Vector): IModel;
function FitWithValidation(XTrain: Matrix; yTrain: Vector; XVal: Matrix; yVal: Vector): IModel;
function Predict(X: Matrix): Vector;
function PredictProba(X: Matrix): Matrix;
function Clone: IModel;
property TrainLossHistory: List<real> read fTrainLossHistory;
property ValLossHistory: List<real> read fValLossHistory;
property BestIteration: integer read fBestIteration;
end;
{$endregion Models}
@ -844,7 +913,18 @@ const
ER_LEARNING_RATE_NOT_POSITIVE =
'Параметр learningRate должен быть > 0!!learningRate must be > 0';
ER_SUBSAMPLE_OUT_OF_RANGE =
'Параметр subsample должен быть в диапазоне (0, 1]!!subsample must be in (0, 1]';
'Параметр subsample должен быть в диапазоне (0, 1]!!subsample must be in (0, 1]';
ER_LABELS_NOT_INTEGER =
'Метки классов должны быть целыми!!Class labels must be integers';
ER_N_ESTIMATORS_INVALID =
'nEstimators должен быть > 0!!nEstimators must be > 0';
ER_LEARNING_RATE_INVALID =
'learningRate должен быть > 0!!learningRate must be > 0';
ER_MAX_DEPTH_INVALID =
'maxDepth должен быть > 0!!maxDepth must be > 0';
ER_SUBSAMPLE_INVALID =
'subsample должен быть > 0!!subsample must be > 0';
{$endregion ErrConstants}
@ -1242,7 +1322,21 @@ begin
var p := X.ColCount;
// --- internal encoding
var unique := y.data.Select(v -> integer(v)).Distinct.ToArray;
var classes := new HashSet<integer>;
for var i := 0 to y.Length - 1 do
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
classes.Add(ir);
end;
var unique := classes.ToArray;
&Array.Sort(unique);
fClassCount := unique.Length;
@ -1255,18 +1349,27 @@ begin
fIndexToClass[i] := unique[i];
end;
// --- encode labels
var yEncoded := new Vector(m);
for var i := 0 to m - 1 do
yEncoded[i] := fClassToIndex[integer(y[i])];
begin
var ir := Round(y[i]); // безопасно после проверки
yEncoded[i] := fClassToIndex[ir];
end;
// --- init
fW := new Matrix(p, fClassCount);
fIntercept := new Vector(fClassCount);
// --- one-hot
var YoneHot := new Matrix(m, fClassCount);
for var i := 0 to m - 1 do
YoneHot[i, integer(yEncoded[i])] := 1.0;
begin
var cls := Round(yEncoded[i]); // индекс класса 0..K-1
YoneHot[i, cls] := 1.0;
end;
for var epoch := 1 to fEpochs do
begin
@ -1312,23 +1415,6 @@ begin
Result := Self;
end;
{function LogisticRegression.PredictProba(X: Matrix): Vector;
begin
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
var P := PredictProbaMatrix(X);
var m := P.RowCount;
Result := new Vector(m);
for var i := 0 to m - 1 do
begin
var k := P.RowArgMax(i);
Result[i] := P[i, k];
end;
end;}
function LogisticRegression.PredictProba(X: Matrix): Matrix;
begin
if not fFitted then
@ -1971,31 +2057,39 @@ begin
fFeatureImportances := new Vector(X.Cols);
// --- 1. Найти уникальные классы
var unique := new HashSet<integer>;
// --- 1. Найти уникальные классы (с проверкой целочисленности)
var classes := new HashSet<integer>;
for var i := 0 to y.Length - 1 do
unique.Add(integer(y[i]));
fClassCount := unique.Count;
// --- 2. Создать массив index -> original class
SetLength(fIndexToClass, fClassCount);
fClassToIndex := new Dictionary<integer, integer>;
var idx := 0;
foreach var c in unique do
begin
fIndexToClass[idx] := c;
fClassToIndex[c] := idx;
idx += 1;
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
classes.Add(ir);
end;
fClassCount := classes.Count;
// --- 2. Отсортировать классы для детерминизма
fIndexToClass := classes.ToArray;
&Array.Sort(fIndexToClass);
fClassToIndex := new Dictionary<integer, integer>;
for var idx := 0 to fClassCount - 1 do
fClassToIndex[fIndexToClass[idx]] := idx;
// --- 3. Создать закодированный y
var yEncoded := new Vector(y.Length);
for var i := 0 to y.Length - 1 do
yEncoded[i] := fClassToIndex[integer(y[i])];
begin
var ir := Round(y[i]); // безопасно после проверки
yEncoded[i] := fClassToIndex[ir];
end;
// --- 4. Создать массив индексов строк
var indices := new integer[X.Rows];
@ -2769,6 +2863,500 @@ begin
Result := copy;
end;
//-----------------------------
// GradientBoostingClassifier
//-----------------------------
function GradientBoostingClassifier.FitInternal(
XTrain: Matrix; yTrain: Vector;
XVal: Matrix; yVal: Vector;
useValidation: boolean): IModel;
begin
// --- checks
if XTrain = nil then
ArgumentNullError(ER_X_NULL);
if yTrain = nil then
ArgumentNullError(ER_Y_NULL);
if XTrain.Rows <> yTrain.Length then
DimensionError(ER_XY_SIZE_MISMATCH);
if XTrain.Rows = 0 then
ArgumentError(ER_EMPTY_DATASET);
if useValidation then
begin
if XVal = nil then
ArgumentNullError(ER_X_NULL);
if yVal = nil then
ArgumentNullError(ER_Y_NULL);
if XVal.Rows <> yVal.Length then
DimensionError(ER_XY_SIZE_MISMATCH);
if XVal.Cols <> XTrain.Cols then
DimensionError(ER_FEATURE_COUNT_MISMATCH);
end;
// --- reset state
fEstimators.Clear;
fTrainLossHistory.Clear;
fValLossHistory.Clear;
fFeatureCount := XTrain.Cols;
fBestIteration := -1;
fBestValLoss := real.PositiveInfinity;
fFitted := false;
// --- mapping
BuildClassMapping(yTrain);
var yEncoded := EncodeLabels(yTrain);
var nTrain := XTrain.Rows;
var classCount := fClassCount;
// --- compute class priors
fInitLogits := new real[classCount];
var counts := new integer[classCount];
for var i := 0 to nTrain - 1 do
counts[yEncoded[i]] += 1;
for var cls := 0 to classCount - 1 do
begin
var pi := counts[cls] / nTrain;
if pi <= 0 then
fInitLogits[cls] := -20.0 // защита от log(0)
else
fInitLogits[cls] := Ln(pi);
end;
// --- init logits
var logitsTrain := new Matrix(nTrain, classCount);
for var i := 0 to nTrain - 1 do
for var cls := 0 to classCount - 1 do
logitsTrain[i, cls] := fInitLogits[cls];
for var i := 0 to nTrain - 1 do
for var cls := 0 to classCount - 1 do
logitsTrain[i, cls] := fInitLogits[cls];
var logitsVal: Matrix := nil;
var yValEncoded: array of integer;
if useValidation then
begin
logitsVal := new Matrix(XVal.Rows, classCount);
yValEncoded := EncodeLabels(yVal);
// --- инициализация log-prior
for var i := 0 to XVal.Rows - 1 do
for var cls := 0 to classCount - 1 do
logitsVal[i, cls] := fInitLogits[cls];
end;
// --- RNG
Randomize(fRandomSeed);
var noImprove := 0;
// --- boosting loop
for var iter := 0 to fNEstimators - 1 do
begin
// --- compute probabilities (train)
var probsTrain := new Matrix(nTrain, classCount);
SoftmaxMatrix(logitsTrain, probsTrain);
// --- compute residuals
var residuals := new Matrix(nTrain, classCount);
for var i := 0 to nTrain - 1 do
for var cls := 0 to classCount - 1 do
begin
var yik := 0.0;
if yEncoded[i] = cls then
yik := 1.0;
residuals[i, cls] := yik - probsTrain[i, cls];
end;
// --- train trees
var trees := new DecisionTreeRegressor[classCount];
for var cls := 0 to classCount - 1 do
begin
var rvec := new Vector(nTrain);
for var i := 0 to nTrain - 1 do
rvec[i] := residuals[i, cls];
var tree := new DecisionTreeRegressor(
fMaxDepth,
fMinSamplesSplit,
fMinSamplesLeaf
);
tree.Fit(XTrain, rvec);
trees[cls] := tree;
var deltaTrain := tree.Predict(XTrain);
for var i := 0 to nTrain - 1 do
logitsTrain[i, cls] += fLearningRate * deltaTrain[i];
if useValidation then
begin
var deltaVal := tree.Predict(XVal);
for var i := 0 to XVal.Rows - 1 do
logitsVal[i, cls] += fLearningRate * deltaVal[i];
end;
end;
fEstimators.Add(trees);
// --- compute loss
SoftmaxMatrix(logitsTrain, probsTrain);
var trainLoss := ComputeLogLoss(yEncoded, probsTrain);
fTrainLossHistory.Add(trainLoss);
var scoreLoss := trainLoss;
if useValidation then
begin
var probsVal := new Matrix(XVal.Rows, classCount);
SoftmaxMatrix(logitsVal, probsVal);
var valLoss := ComputeLogLoss(yValEncoded, probsVal);
fValLossHistory.Add(valLoss);
scoreLoss := valLoss;
end;
// --- early stopping
if fEarlyStoppingPatience > 0 then
begin
if fBestValLoss - scoreLoss > MinImprovement then
begin
fBestValLoss := scoreLoss;
fBestIteration := iter;
noImprove := 0;
end
else
begin
noImprove += 1;
if noImprove >= fEarlyStoppingPatience then
break;
end;
end;
end;
// --- cut trees after best iteration
if (fEarlyStoppingPatience > 0) and (fBestIteration >= 0) then
begin
var keep := fBestIteration + 1;
if fEstimators.Count > keep then
fEstimators.RemoveRange(keep, fEstimators.Count - keep);
end;
fFitted := true;
Result := Self;
end;
procedure GradientBoostingClassifier.BuildClassMapping(y: Vector);
begin
var classes := new HashSet<integer>;
for var i := 0 to y.Length - 1 do
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
classes.Add(ir);
end;
fClasses := classes.ToArray;
&Array.Sort(fClasses);
fClassCount := fClasses.Length;
fClassIndex := new Dictionary<integer, integer>;
for var cls := 0 to fClassCount - 1 do
fClassIndex[fClasses[cls]] := cls;
end;
function GradientBoostingClassifier.EncodeLabels(y: Vector): array of integer;
begin
var n := y.Length;
Result := new integer[n];
for var i := 0 to n - 1 do
begin
var ir := Round(y[i]);
Result[i] := fClassIndex[ir];
end;
end;
procedure GradientBoostingClassifier.SoftmaxRow(
var logits: array of real;
var probs: array of real);
begin
var classCount := Length(logits);
var maxLogit := logits[0];
for var cls := 1 to classCount - 1 do
if logits[cls] > maxLogit then
maxLogit := logits[cls];
var sumExp := 0.0;
for var cls := 0 to classCount - 1 do
begin
var e := Exp(logits[cls] - maxLogit);
probs[cls] := e;
sumExp += e;
end;
for var cls := 0 to classCount - 1 do
probs[cls] /= sumExp;
end;
procedure GradientBoostingClassifier.SoftmaxMatrix(
logits: Matrix;
probs: Matrix);
begin
var nSamples := logits.Rows;
var classCount := logits.Cols;
for var i := 0 to nSamples - 1 do
begin
var rowLogits := new real[classCount];
var rowProbs := new real[classCount];
for var cls := 0 to classCount - 1 do
rowLogits[cls] := logits[i, cls];
SoftmaxRow(rowLogits, rowProbs);
for var cls := 0 to classCount - 1 do
probs[i, cls] := rowProbs[cls];
end;
end;
function GradientBoostingClassifier.ComputeLogLoss(
yEncoded: array of integer;
probs: Matrix): real;
begin
var n := Length(yEncoded);
var eps := 1e-15;
var loss := 0.0;
for var i := 0 to n - 1 do
begin
var cls := yEncoded[i];
var p := probs[i, cls];
if p < eps then
p := eps;
loss -= Ln(p);
end;
Result := loss / n;
end;
constructor GradientBoostingClassifier.Create(
nEstimators: integer;
learningRate: real;
maxDepth: integer;
minSamplesSplit: integer;
minSamplesLeaf: integer;
subsample: real;
randomSeed: integer;
earlyStoppingPatience: integer);
begin
if nEstimators <= 0 then
ArgumentError(ER_N_ESTIMATORS_INVALID);
if learningRate <= 0 then
ArgumentError(ER_LEARNING_RATE_INVALID);
if maxDepth <= 0 then
ArgumentError(ER_MAX_DEPTH_INVALID);
if subsample <= 0 then
ArgumentError(ER_SUBSAMPLE_INVALID);
fNEstimators := nEstimators;
fLearningRate := learningRate;
fMaxDepth := maxDepth;
fMinSamplesSplit := minSamplesSplit;
fMinSamplesLeaf := minSamplesLeaf;
fSubsample := subsample;
fRandomSeed := randomSeed;
fEarlyStoppingPatience := earlyStoppingPatience;
fEstimators := new List<array of DecisionTreeRegressor>;
fTrainLossHistory := new List<real>;
fValLossHistory := new List<real>;
fFitted := false;
fFeatureCount := 0;
fClassCount := 0;
fBestIteration := -1;
fBestValLoss := real.PositiveInfinity;
end;
function GradientBoostingClassifier.PredictProba(X: Matrix): Matrix;
begin
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
var nSamples := X.Rows;
var classCount := fClassCount;
var logits := new Matrix(nSamples, classCount);
// --- накопление логитов от всех итераций
foreach var trees in fEstimators do
begin
for var cls := 0 to classCount - 1 do
begin
var delta := trees[cls].Predict(X);
for var i := 0 to nSamples - 1 do
logits[i, cls] += fLearningRate * delta[i];
end;
end;
// --- softmax
var probs := new Matrix(nSamples, classCount);
for var i := 0 to nSamples - 1 do
begin
var rowLogits := new real[classCount];
var rowProbs := new real[classCount];
for var cls := 0 to classCount - 1 do
rowLogits[cls] := logits[i, cls];
SoftmaxRow(rowLogits, rowProbs);
for var cls := 0 to classCount - 1 do
probs[i, cls] := rowProbs[cls];
end;
Result := probs;
end;
function GradientBoostingClassifier.Clone: IModel;
begin
var model := new GradientBoostingClassifier(
fNEstimators,
fLearningRate,
fMaxDepth,
fMinSamplesSplit,
fMinSamplesLeaf,
fSubsample,
fRandomSeed,
fEarlyStoppingPatience
);
// --- 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<integer, integer>;
foreach var kv in fClassIndex do
model.fClassIndex.Add(kv.Key, kv.Value);
end;
// --- estimators (deep copy)
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;
// --- history
foreach var v in fTrainLossHistory do
model.fTrainLossHistory.Add(v);
foreach var v in fValLossHistory do
model.fValLossHistory.Add(v);
model.fBestIteration := fBestIteration;
model.fBestValLoss := fBestValLoss;
Result := model;
end;
function GradientBoostingClassifier.Fit(X: Matrix; y: Vector): IModel;
begin
Result := FitInternal(X, y, nil, nil, false);
end;
function GradientBoostingClassifier.FitWithValidation(
XTrain: Matrix; yTrain: Vector;
XVal: Matrix; yVal: Vector): IModel;
begin
Result := FitInternal(XTrain, yTrain, XVal, yVal, true);
end;
function GradientBoostingClassifier.Predict(X: Matrix): Vector;
begin
if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED);
var probs := PredictProba(X);
var nSamples := X.Rows;
var classCount := fClassCount;
Result := new Vector(nSamples);
for var i := 0 to nSamples - 1 do
begin
var bestClassIndex := 0;
var bestValue := probs[i, 0];
for var cls := 1 to classCount - 1 do
if probs[i, cls] > bestValue then
begin
bestValue := probs[i, cls];
bestClassIndex := cls;
end;
// возвращаем оригинальную метку
Result[i] := fClasses[bestClassIndex];
end;
end;
//-----------------------------
// Pipeline