Почистил примеры

This commit is contained in:
Mikhalkovich Stanislav 2026-05-09 23:00:12 +03:00
parent ce84830349
commit de75f00031
23 changed files with 327 additions and 134 deletions

View file

@ -7,7 +7,7 @@ begin
var features := ['rooms','area','kitchen_area','floor','floors_total','metro_minutes']; var features := ['rooms','area','kitchen_area','floor','floors_total','metro_minutes'];
var target := 'price'; var target := 'price';
var (trainDf, testDf) := df.TrainTestSplit(0.2, 42); var (trainDf, testDf) := df.TrainTestSplit(0.2, seed := 42);
var Xtrain := trainDf.ToMatrix(features); var Xtrain := trainDf.ToMatrix(features);
var ytrain := trainDf.ToVector(target); var ytrain := trainDf.ToVector(target);

View file

@ -7,7 +7,7 @@ begin
var features := ['rooms','area','kitchen_area','floor','floors_total','metro_minutes']; var features := ['rooms','area','kitchen_area','floor','floors_total','metro_minutes'];
var target := 'price'; var target := 'price';
var (trainDf, testDf) := df.TrainTestSplit(0.2, 42); var (trainDf, testDf) := df.TrainTestSplit(0.2, seed := 42);
var Xtrain := trainDf.ToMatrix(features); var Xtrain := trainDf.ToMatrix(features);
var ytrain := trainDf.ToVector(target); var ytrain := trainDf.ToVector(target);

View file

@ -11,9 +11,10 @@ begin
var pipe := var pipe :=
DataPipeline.Build( DataPipeline.Build(
TaskKind.tkRegression,
target, target,
features, features,
new LabelEncoder('renovation'), new OrdinalEncoder('renovation'),
model model
); );

View file

@ -12,10 +12,10 @@ begin
df := portImputer.FitTransform(df); df := portImputer.FitTransform(df);
// Кодируем категориальные признаки числами. // Кодируем категориальные признаки числами.
var sexEncoder := new LabelEncoder('Пол'); var sexEncoder := new OrdinalEncoder('Пол');
df := sexEncoder.FitTransform(df); df := sexEncoder.FitTransform(df);
var portEncoder := new LabelEncoder('ПортПосадки'); var portEncoder := new OrdinalEncoder('ПортПосадки');
df := portEncoder.FitTransform(df); df := portEncoder.FitTransform(df);
var features := ['Класс', 'Пол', 'Возраст', 'БратьяИСупруги', 'РодителиИДети', 'ЦенаБилета', 'ПортПосадки']; var features := ['Класс', 'Пол', 'Возраст', 'БратьяИСупруги', 'РодителиИДети', 'ЦенаБилета', 'ПортПосадки'];

View file

@ -10,10 +10,10 @@ begin
var portImputer := new Imputer('Саутгемптон', ['ПортПосадки']); var portImputer := new Imputer('Саутгемптон', ['ПортПосадки']);
df := portImputer.FitTransform(df); df := portImputer.FitTransform(df);
var sexEncoder := new LabelEncoder('Пол'); var sexEncoder := new OrdinalEncoder('Пол');
df := sexEncoder.FitTransform(df); df := sexEncoder.FitTransform(df);
var portEncoder := new LabelEncoder('ПортПосадки'); var portEncoder := new OrdinalEncoder('ПортПосадки');
df := portEncoder.FitTransform(df); df := portEncoder.FitTransform(df);
var features := ['Класс', 'Пол', 'Возраст', 'БратьяИСупруги', 'РодителиИДети', 'ЦенаБилета', 'ПортПосадки']; var features := ['Класс', 'Пол', 'Возраст', 'БратьяИСупруги', 'РодителиИДети', 'ЦенаБилета', 'ПортПосадки'];

View file

@ -11,10 +11,10 @@ begin
df := imputer.FitTransform(df); df := imputer.FitTransform(df);
// Кодируем категориальные признаки // Кодируем категориальные признаки
var encoder := new LabelEncoder('region_name'); var encoder := new OrdinalEncoder('region_name');
df := encoder.FitTransform(df); df := encoder.FitTransform(df);
var encoder2 := new LabelEncoder('federal_district'); var encoder2 := new OrdinalEncoder('federal_district');
df := encoder2.FitTransform(df); df := encoder2.FitTransform(df);
df.Print; df.Print;

View file

@ -16,10 +16,10 @@ begin
var imputer := new Imputer(['population', 'lat', 'lon']); var imputer := new Imputer(['population', 'lat', 'lon']);
df := imputer.FitTransform(df); df := imputer.FitTransform(df);
var le1 := new LabelEncoder('region_name'); var le1 := new OrdinalEncoder('region_name');
df := le1.FitTransform(df); df := le1.FitTransform(df);
var le2 := new LabelEncoder('federal_district'); var le2 := new OrdinalEncoder('federal_district');
df := le2.FitTransform(df); df := le2.FitTransform(df);
// Явно задаём признаки // Явно задаём признаки

View file

@ -7,10 +7,10 @@ begin
var imputer := new Imputer(['population', 'lat', 'lon']); var imputer := new Imputer(['population', 'lat', 'lon']);
df := imputer.FitTransform(df); df := imputer.FitTransform(df);
var le1 := new LabelEncoder('region_name'); var le1 := new OrdinalEncoder('region_name');
df := le1.FitTransform(df); df := le1.FitTransform(df);
var le2 := new LabelEncoder('federal_district'); var le2 := new OrdinalEncoder('federal_district');
df := le2.FitTransform(df); df := le2.FitTransform(df);
var features := ['lat', 'lon', 'region_name', 'federal_district']; var features := ['lat', 'lon', 'region_name', 'federal_district'];

View file

@ -1 +1 @@
3.11.1.3815 3.11.1.3821

View file

@ -1 +1 @@
!define VERSION '3.11.1.3815' !define VERSION '3.11.1.3821'

View file

@ -6,7 +6,7 @@ begin
df.AddStrColumn('Region', Arr('South', 'North', 'South', 'East')); df.AddStrColumn('Region', Arr('South', 'North', 'South', 'East'));
df := df.SetCategorical(['Region']); df := df.SetCategorical(['Region']);
var enc := new LabelEncoder('Region'); var enc := new OrdinalEncoder('Region');
enc.Fit(df); enc.Fit(df);
var res := enc.Transform(df); var res := enc.Transform(df);

View file

@ -10,7 +10,7 @@ begin
testDf.AddIntColumn('City', Arr(1, 2, 3)); testDf.AddIntColumn('City', Arr(1, 2, 3));
testDf := testDf.SetCategorical(['City']); testDf := testDf.SetCategorical(['City']);
var enc := new LabelEncoder('City'); var enc := new OrdinalEncoder('City');
enc.Fit(trainDf); enc.Fit(trainDf);
CheckRaises(procedure -> begin CheckRaises(procedure -> begin

View file

@ -120,7 +120,7 @@ type
Inspection = InspectionML.Inspection; Inspection = InspectionML.Inspection;
IPreprocessor = PreprocessorABC.IPreprocessor; IPreprocessor = PreprocessorABC.IPreprocessor;
LabelEncoder = PreprocessorABC.LabelEncoder; OrdinalEncoder = PreprocessorABC.OrdinalEncoder;
OneHotEncoder = PreprocessorABC.OneHotEncoder; OneHotEncoder = PreprocessorABC.OneHotEncoder;
ImputeStrategy = PreprocessorABC.ImputeStrategy; ImputeStrategy = PreprocessorABC.ImputeStrategy;
Imputer = PreprocessorABC.Imputer; Imputer = PreprocessorABC.Imputer;

View file

@ -1157,6 +1157,7 @@ begin
ArgumentNullError(ER_ARG_NULL, 'name'); ArgumentNullError(ER_ARG_NULL, 'name');
var baseDir := PascalABCDirectory + 'Files\Datasets\'; var baseDir := PascalABCDirectory + 'Files\Datasets\';
//var baseDir := 'C:\Program Files (x86)\PascalABC.NET\Files\Datasets\';
var metaPath := baseDir + name + '.meta'; var metaPath := baseDir + name + '.meta';
var csvPath := baseDir + name + '.csv'; var csvPath := baseDir + name + '.csv';

View file

@ -25,6 +25,14 @@ unit MLModelsABC;
// См. статистическую политику в модуле MLABC. // См. статистическую политику в модуле MLABC.
// ============================================================= // =============================================================
{
Производительность
DecisionTreeRegressor.Fit - 340 мс против 156 мс в Питоне при той же точности
GradientBoostingRegressor.Fit - 5500 мс против 5500 мс в Питоне
RandomForestRegressor.Fit - 1280 мс против 480 мс в Питоне
}
interface interface
uses MLCoreABC; uses MLCoreABC;
@ -432,6 +440,8 @@ type
Found: boolean; Found: boolean;
Feature: integer; Feature: integer;
Threshold: real; Threshold: real;
LeftCount: integer;
LeftOrderSize: integer;
WeightedScore: real; WeightedScore: real;
static function Invalid: RegSplitResult; static function Invalid: RegSplitResult;
@ -439,6 +449,8 @@ type
Result.Found := false; Result.Found := false;
Result.Feature := -1; Result.Feature := -1;
Result.Threshold := 0.0; Result.Threshold := 0.0;
Result.LeftCount := 0;
Result.LeftOrderSize := 0;
Result.WeightedScore := real.PositiveInfinity; Result.WeightedScore := real.PositiveInfinity;
end; end;
end; end;
@ -708,7 +720,8 @@ type
private private
fLeafL2: real; fLeafL2: real;
fSortedOrders: array of array of integer; fSortedOrders: array of array of integer;
fSortedValues: array of array of real; fUseSortedOrdersAsRoot: boolean;
fRowWeights: array of integer;
fVisitMarks: array of integer; fVisitMarks: array of integer;
fVisitId: integer; fVisitId: integer;
@ -726,12 +739,14 @@ type
function FindBestSplitReg(X: Matrix; y: Vector; nodeOrders: array of array of integer): RegSplitResult; function FindBestSplitReg(X: Matrix; y: Vector; nodeOrders: array of array of integer): RegSplitResult;
procedure BuildSortedOrders(X: Matrix; indices: array of integer); procedure BuildSortedOrders(X: Matrix; indices: array of integer);
function BuildInitialNodeOrders(indices: array of integer): array of array of integer; function BuildInitialNodeOrders(indices: array of integer): array of array of integer;
procedure SplitNodeOrders(X: Matrix; nodeOrders: array of array of integer; feature: integer; threshold: real; procedure SplitNodeOrders(nodeOrders: array of array of integer; feature: integer; leftCount, leftOrderSize: integer;
var leftOrders, rightOrders: array of array of integer); var leftOrders, rightOrders: array of array of integer);
function BuildMembershipMask(rowCount: integer; indices: array of integer): array of boolean; function BuildMembershipMask(rowCount: integer; indices: array of integer): array of boolean;
procedure ComputeNodeStats(yData: array of real; indices: array of integer; var sumAll, sumSqAll: real); procedure ComputeNodeStats(yData: array of real; indices: array of integer; var sumAll, sumSqAll: real);
function WeightedVariance(n, leftCount: integer; leftSum, leftSumSq, sumAll, sumSqAll: real): real; function WeightedVariance(n, leftCount: integer; leftSum, leftSumSq, sumAll, sumSqAll: real): real;
function GetFeatureSubset(p: integer): array of integer; function GetFeatureSubset(p: integer): array of integer;
function SampleWeight(rowIndex: integer): integer;
function TotalWeight(indices: array of integer): integer;
/// Проверяет, является ли узел "чистым". /// Проверяет, является ли узел "чистым".
/// Для регрессии это означает, что все значения y одинаковы /// Для регрессии это означает, что все значения y одинаковы
@ -759,6 +774,16 @@ type
/// Возвращает вектор вещественных значений. /// Возвращает вектор вещественных значений.
function Predict(X: Matrix): Vector; override; function Predict(X: Matrix): Vector; override;
/// Внутренний hook для ансамблей: позволяет переиспользовать
/// уже отсортированные порядки строк по признакам.
/// Обычному пользовательскому коду не нужен.
procedure SetPreSortedOrders(sortedOrders: array of array of integer);
/// Внутренний hook для bootstrap-подвыборок с повторами.
/// Передаются уже готовые сортированные порядки именно для корневого узла.
procedure SetPreSortedRootOrders(sortedOrders: array of array of integer);
procedure SetBootstrapRootOrders(sortedOrders: array of array of integer; rowWeights: array of integer);
/// Копирует только конфигурацию модели (без обученного состояния). /// Копирует только конфигурацию модели (без обученного состояния).
/// Используется для создания независимых экземпляров модели. /// Используется для создания независимых экземпляров модели.
function Clone: IModel; override; function Clone: IModel; override;
@ -4022,7 +4047,7 @@ function DecisionTreeRegressor.BuildTreeNode(X: Matrix; y: Vector;
nodeOrders: array of array of integer; depth: integer): DecisionTreeNode; nodeOrders: array of array of integer; depth: integer): DecisionTreeNode;
begin begin
var indices := nodeOrders[0]; var indices := nodeOrders[0];
var n := indices.Length; var n := TotalWeight(indices);
if (fMaxDepth >= 0) and (depth >= fMaxDepth) then if (fMaxDepth >= 0) and (depth >= fMaxDepth) then
exit(LeafNode(LeafValue(y, indices))); exit(LeafNode(LeafValue(y, indices)));
@ -4048,12 +4073,13 @@ begin
exit(LeafNode(LeafValue(y, indices))); exit(LeafNode(LeafValue(y, indices)));
var leftOrders, rightOrders: array of array of integer; var leftOrders, rightOrders: array of array of integer;
SplitNodeOrders(X, nodeOrders, split.Feature, split.Threshold, leftOrders, rightOrders); SplitNodeOrders(nodeOrders, split.Feature, split.LeftCount, split.LeftOrderSize, leftOrders, rightOrders);
var leftArr := leftOrders[0]; var leftArr := leftOrders[0];
var rightArr := rightOrders[0]; var rightArr := rightOrders[0];
var rightCount := n - split.LeftCount;
if (leftArr.Length < fMinSamplesLeaf) or if (split.LeftCount < fMinSamplesLeaf) or
(rightArr.Length < fMinSamplesLeaf) then (rightCount < fMinSamplesLeaf) then
exit(LeafNode(LeafValue(y, indices))); exit(LeafNode(LeafValue(y, indices)));
var delta := parentVar - split.WeightedScore; var delta := parentVar - split.WeightedScore;
@ -4095,7 +4121,7 @@ begin
Result := RegSplitResult.Invalid; Result := RegSplitResult.Invalid;
var indices := nodeOrders[0]; var indices := nodeOrders[0];
var n := indices.Length; var n := TotalWeight(indices);
if n < 2 then if n < 2 then
exit; exit;
@ -4108,6 +4134,11 @@ begin
var bestScore := real.PositiveInfinity; var bestScore := real.PositiveInfinity;
var bestFeature := -1; var bestFeature := -1;
var bestThreshold := 0.0; var bestThreshold := 0.0;
var bestLeftCount := 0;
var bestLeftOrderSize := 0;
var invN := 1.0 / n;
var minLeaf := fMinSamplesLeaf;
var maxLeftCount := n - minLeaf;
var features := GetFeatureSubset(X.ColCount); var features := GetFeatureSubset(X.ColCount);
@ -4117,63 +4148,61 @@ begin
var order := nodeOrders[j]; var order := nodeOrders[j];
var orderLen := order.Length; var orderLen := order.Length;
var leftCount := 0; if orderLen < 2 then
var leftSum := 0.0; continue;
var leftSumSq := 0.0;
var prevValue := 0.0; var firstIdx := order[0];
var firstIncluded := true; var firstWeight := SampleWeight(firstIdx);
var leftCount := firstWeight;
var leftSum := firstWeight * yData[firstIdx];
var leftSumSq := firstWeight * yData[firstIdx] * yData[firstIdx];
var prevValue := xData[firstIdx, j];
for var i := 0 to orderLen - 1 do for var i := 1 to orderLen - 1 do
begin begin
if leftCount > maxLeftCount then
break;
var idx := order[i]; var idx := order[i];
var xCur := xData[idx, j]; var xCur := xData[idx, j];
var yCur := yData[idx];
if not firstIncluded then if (leftCount >= minLeaf) and (prevValue <> xCur) then
begin begin
var rightCount := n - leftCount; var rightCount := n - leftCount;
if (leftCount >= fMinSamplesLeaf) and
(rightCount >= fMinSamplesLeaf) and
(prevValue <> xCur) then
begin
var rightCountReal := rightCount;
var leftMean := leftSum / leftCount;
var leftVar := leftSumSq / leftCount - leftMean * leftMean;
var rightSum := sumAll - leftSum; var rightSum := sumAll - leftSum;
var rightSumSq := sumSqAll - leftSumSq; var rightSumSq := sumSqAll - leftSumSq;
var rightMean := rightSum / rightCount;
var rightVar := rightSumSq / rightCount - rightMean * rightMean;
if leftVar < 0 then var leftScore := leftSumSq - (leftSum * leftSum) / leftCount;
leftVar := 0.0; var rightScore := rightSumSq - (rightSum * rightSum) / rightCount;
if rightVar < 0 then var weighted := (leftScore + rightScore) * invN;
rightVar := 0.0;
var weighted := (leftCount * leftVar + rightCountReal * rightVar) / n; if weighted < 0 then
weighted := 0.0;
if weighted < bestScore then if weighted < bestScore then
begin begin
bestScore := weighted; bestScore := weighted;
bestFeature := j; bestFeature := j;
bestThreshold := (prevValue + xCur) * 0.5; bestThreshold := (prevValue + xCur) * 0.5;
end; bestLeftCount := leftCount;
bestLeftOrderSize := i;
end; end;
end; end;
leftCount += 1; var wCur := SampleWeight(idx);
leftSum += yCur; var yCur := yData[idx];
leftSumSq += yCur * yCur; leftCount += wCur;
leftSum += wCur * yCur;
leftSumSq += wCur * yCur * yCur;
prevValue := xCur; prevValue := xCur;
firstIncluded := false;
end; end;
end; end;
Result.Found := bestFeature <> -1; Result.Found := bestFeature <> -1;
Result.Feature := bestFeature; Result.Feature := bestFeature;
Result.Threshold := bestThreshold; Result.Threshold := bestThreshold;
Result.LeftCount := bestLeftCount;
Result.LeftOrderSize := bestLeftOrderSize;
Result.WeightedScore := bestScore; Result.WeightedScore := bestScore;
end; end;
@ -4190,14 +4219,103 @@ type
end; end;
end; end;
function BuildPreSortedOrders(X: Matrix): array of array of integer;
begin
var p := X.ColCount;
var n := X.RowCount;
var xData := X.Data;
SetLength(Result, p);
for var j := 0 to p - 1 do
begin
var pairs: array of SortPair;
SetLength(pairs, n);
for var i := 0 to n - 1 do
begin
pairs[i].Value := xData[i, j];
pairs[i].Index := i;
end;
System.Array.Sort(pairs, new SortPairComparer);
Result[j] := new integer[n];
for var i := 0 to n - 1 do
Result[j][i] := pairs[i].Index;
end;
end;
function BuildRowCounts(rows: array of integer; rowCount: integer): array of integer;
begin
Result := new integer[rowCount];
for var i := 0 to rows.Length - 1 do
Result[rows[i]] += 1;
end;
function BuildSortedOrdersFromCounts(
fullSortedOrders: array of array of integer;
rowCounts: array of integer
): array of array of integer;
begin
var p := Length(fullSortedOrders);
SetLength(Result, p);
var total := 0;
for var i := 0 to rowCounts.Length - 1 do
total += rowCounts[i];
for var j := 0 to p - 1 do
begin
Result[j] := new integer[total];
var k := 0;
foreach var idx in fullSortedOrders[j] do
for var rep := 1 to rowCounts[idx] do
begin
Result[j][k] := idx;
k += 1;
end;
end;
end;
function BuildUniqueOrdersFromCounts(
fullSortedOrders: array of array of integer;
rowCounts: array of integer
): array of array of integer;
begin
var p := Length(fullSortedOrders);
SetLength(Result, p);
var total := 0;
for var i := 0 to rowCounts.Length - 1 do
if rowCounts[i] > 0 then
total += 1;
for var j := 0 to p - 1 do
begin
Result[j] := new integer[total];
var k := 0;
foreach var idx in fullSortedOrders[j] do
if rowCounts[idx] > 0 then
begin
Result[j][k] := idx;
k += 1;
end;
end;
end;
procedure DecisionTreeRegressor.BuildSortedOrders(X: Matrix; indices: array of integer); procedure DecisionTreeRegressor.BuildSortedOrders(X: Matrix; indices: array of integer);
begin begin
// Обычный путь для отдельного дерева:
// построить сортировку только по реально используемым строкам.
var p := X.ColCount; var p := X.ColCount;
var n := indices.Length; var n := indices.Length;
var xData := X.Data; var xData := X.Data;
SetLength(fSortedOrders, p); SetLength(fSortedOrders, p);
SetLength(fSortedValues, p);
for var j := 0 to p - 1 do for var j := 0 to p - 1 do
begin begin
@ -4214,15 +4332,11 @@ begin
System.Array.Sort(pairs,new SortPairComparer); System.Array.Sort(pairs,new SortPairComparer);
fSortedOrders[j] := new integer[n]; fSortedOrders[j] := new integer[n];
fSortedValues[j] := new real[n];
for var i := 0 to n - 1 do for var i := 0 to n - 1 do
begin
fSortedValues[j][i] := pairs[i].Value;
fSortedOrders[j][i] := pairs[i].Index; fSortedOrders[j][i] := pairs[i].Index;
end; end;
end; end;
end;
function DecisionTreeRegressor.BuildInitialNodeOrders(indices: array of integer): array of array of integer; function DecisionTreeRegressor.BuildInitialNodeOrders(indices: array of integer): array of array of integer;
begin begin
@ -4252,13 +4366,13 @@ begin
end; end;
end; end;
procedure DecisionTreeRegressor.SplitNodeOrders(X: Matrix; procedure DecisionTreeRegressor.SplitNodeOrders(
nodeOrders: array of array of integer; nodeOrders: array of array of integer;
feature: integer; feature: integer;
threshold: real; leftCount: integer;
leftOrderSize: integer;
var leftOrders, rightOrders: array of array of integer); var leftOrders, rightOrders: array of array of integer);
begin begin
var xData := X.Data;
var p := Length(nodeOrders); var p := Length(nodeOrders);
fVisitId += 1; fVisitId += 1;
@ -4266,31 +4380,33 @@ begin
var splitArr := nodeOrders[feature]; var splitArr := nodeOrders[feature];
var splitLen := splitArr.Length; var splitLen := splitArr.Length;
var rightCount := splitLen - leftOrderSize;
var markLeft := leftOrderSize <= rightCount;
var leftCount := 0; if markLeft then
for var i := 0 to leftOrderSize - 1 do
for var i := 0 to splitLen - 1 do fVisitMarks[splitArr[i]] := mark
begin else
var idx := splitArr[i]; for var i := leftOrderSize to splitLen - 1 do
fVisitMarks[splitArr[i]] := mark;
if xData[idx, feature] <= threshold then
begin
fVisitMarks[idx] := mark;
leftCount += 1;
end;
end;
var rightCount := splitLen - leftCount;
SetLength(leftOrders, p); SetLength(leftOrders, p);
SetLength(rightOrders, p); SetLength(rightOrders, p);
leftOrders[feature] := new integer[leftOrderSize];
rightOrders[feature] := new integer[rightCount];
System.Array.Copy(splitArr, 0, leftOrders[feature], 0, leftOrderSize);
System.Array.Copy(splitArr, leftOrderSize, rightOrders[feature], 0, rightCount);
for var j := 0 to p - 1 do for var j := 0 to p - 1 do
begin begin
if j = feature then
continue;
var src := nodeOrders[j]; var src := nodeOrders[j];
var n := src.Length; var n := src.Length;
var left := new integer[leftCount]; var left := new integer[leftOrderSize];
var right := new integer[rightCount]; var right := new integer[rightCount];
var li := 0; var li := 0;
@ -4300,6 +4416,8 @@ begin
begin begin
var idx := src[k]; var idx := src[k];
if markLeft then
begin
if fVisitMarks[idx] = mark then if fVisitMarks[idx] = mark then
begin begin
left[li] := idx; left[li] := idx;
@ -4310,6 +4428,20 @@ begin
right[ri] := idx; right[ri] := idx;
ri += 1; ri += 1;
end; end;
end
else
begin
if fVisitMarks[idx] = mark then
begin
right[ri] := idx;
ri += 1;
end
else
begin
left[li] := idx;
li += 1;
end;
end;
end; end;
leftOrders[j] := left; leftOrders[j] := left;
@ -4331,9 +4463,11 @@ begin
for var i := 0 to indices.Length - 1 do for var i := 0 to indices.Length - 1 do
begin begin
var v := yData[indices[i]]; var idx := indices[i];
sumAll += v; var w := SampleWeight(idx);
sumSqAll += v * v; var v := yData[idx];
sumAll += w * v;
sumSqAll += w * v * v;
end; end;
end; end;
@ -4380,6 +4514,48 @@ begin
end; end;
end; end;
procedure DecisionTreeRegressor.SetPreSortedOrders(sortedOrders: array of array of integer);
begin
// Внутренний fast-path для ансамблей:
// используем готовую полную сортировку X и затем фильтруем её по fRowIndices.
fSortedOrders := sortedOrders;
fRowWeights := nil;
fUseSortedOrdersAsRoot := false;
end;
procedure DecisionTreeRegressor.SetPreSortedRootOrders(sortedOrders: array of array of integer);
begin
// Внутренний fast-path для bootstrap-выборок с повторами:
// сортировка уже соответствует корневому узлу текущего дерева.
fSortedOrders := sortedOrders;
fRowWeights := nil;
fUseSortedOrdersAsRoot := true;
end;
procedure DecisionTreeRegressor.SetBootstrapRootOrders(
sortedOrders: array of array of integer;
rowWeights: array of integer);
begin
fSortedOrders := sortedOrders;
fRowWeights := rowWeights;
fUseSortedOrdersAsRoot := true;
end;
function DecisionTreeRegressor.SampleWeight(rowIndex: integer): integer;
begin
if fRowWeights = nil then
Result := 1
else
Result := fRowWeights[rowIndex];
end;
function DecisionTreeRegressor.TotalWeight(indices: array of integer): integer;
begin
Result := 0;
for var i := 0 to indices.Length - 1 do
Result += SampleWeight(indices[i]);
end;
//============================== //==============================
// DecisionTreeClassifier // DecisionTreeClassifier
//============================== //==============================
@ -4571,7 +4747,7 @@ end;
function DecisionTreeRegressor.LeafValue(y: Vector; indices: array of integer): real; function DecisionTreeRegressor.LeafValue(y: Vector; indices: array of integer): real;
begin begin
var n := indices.Length; var n := TotalWeight(indices);
if n = 0 then if n = 0 then
exit(0.0); // безопасный fallback, не должен происходить exit(0.0); // безопасный fallback, не должен происходить
@ -4582,7 +4758,7 @@ begin
var sum := 0.0; var sum := 0.0;
foreach var idx in indices do foreach var idx in indices do
sum += y[idx]; sum += SampleWeight(idx) * y[idx];
var denom: real; var denom: real;
@ -4637,11 +4813,15 @@ begin
if indices = nil then if indices = nil then
indices := Arr(0..X.RowCount - 1); indices := Arr(0..X.RowCount - 1);
if (fSortedOrders = nil) or (Length(fSortedOrders) = 0) then
BuildSortedOrders(X, indices); BuildSortedOrders(X, indices);
SetLength(fVisitMarks, X.RowCount); SetLength(fVisitMarks, X.RowCount);
fVisitId := 0; fVisitId := 0;
if fUseSortedOrdersAsRoot then
fRoot := BuildTreeNode(X, y, fSortedOrders, 0)
else
fRoot := BuildTreeNew(X, y, indices, 0); fRoot := BuildTreeNew(X, y, indices, 0);
var s := fFeatureImportances.Sum; var s := fFeatureImportances.Sum;
@ -4653,7 +4833,8 @@ begin
fRowIndices := nil; fRowIndices := nil;
fSortedOrders := nil; fSortedOrders := nil;
fSortedValues := nil; fUseSortedOrdersAsRoot := false;
fRowWeights := nil;
fVisitMarks := nil; fVisitMarks := nil;
@ -4830,6 +5011,7 @@ begin
fFeatureCount := p; fFeatureCount := p;
SetLength(fTrees, fNTrees); SetLength(fTrees, fNTrees);
var fullSortedOrders := BuildPreSortedOrders(X);
// --- OOB buffers (regression) --- // --- OOB buffers (regression) ---
var oobSum: Vector := nil; var oobSum: Vector := nil;
@ -4859,7 +5041,10 @@ begin
var rows: array of integer; var rows: array of integer;
BootstrapRowIndices(n, rows); BootstrapRowIndices(n, rows);
tree.SetRowIndices(rows); var rowCounts := BuildRowCounts(rows, n);
var bootSortedOrders := BuildUniqueOrdersFromCounts(fullSortedOrders, rowCounts);
tree.SetBootstrapRootOrders(bootSortedOrders, rowCounts);
tree.Fit(X, y); tree.Fit(X, y);
// --- OOB accumulate --- // --- OOB accumulate ---
@ -5730,6 +5915,9 @@ begin
for var i := 0 to nTrain - 1 do for var i := 0 to nTrain - 1 do
yPredTrain[i] := fInitValue; yPredTrain[i] := fInitValue;
var residuals := new Vector(nTrain);
var preSortedOrders := BuildPreSortedOrders(XTrain);
// --- OOB logic --- // --- OOB logic ---
var useSubsample := fSubsample < 1.0; var useSubsample := fSubsample < 1.0;
var useOOB := var useOOB :=
@ -5754,8 +5942,7 @@ begin
for var m := 0 to fNEstimators - 1 do for var m := 0 to fNEstimators - 1 do
begin begin
// 1. residuals // 1. residuals
var r := new Vector(nTrain); ComputePseudoResiduals(yTrain, yPredTrain, residuals);
ComputePseudoResiduals(yTrain, yPredTrain, r);
var stageSeed := fRng.Next(integer.MaxValue); var stageSeed := fRng.Next(integer.MaxValue);
@ -5766,6 +5953,7 @@ begin
fLeafL2, fLeafL2,
stageSeed stageSeed
); );
tree.SetPreSortedOrders(preSortedOrders);
// --- subsample --- // --- subsample ---
var rows: array of integer := nil; var rows: array of integer := nil;
@ -5784,7 +5972,7 @@ begin
end; end;
end; end;
tree.Fit(XTrain, r); tree.Fit(XTrain, residuals);
fEstimators.Add(tree); fEstimators.Add(tree);
var deltaTrain := tree.Predict(XTrain); var deltaTrain := tree.Predict(XTrain);

View file

@ -48,14 +48,14 @@ type
/// Пропущенные значения (NA) игнорируются при обучении /// Пропущенные значения (NA) игнорируются при обучении
/// и сохраняются как пропуски при преобразовании. /// и сохраняются как пропуски при преобразовании.
/// Работает только со строковыми столбцами и предназначен для признаков. /// Работает только со строковыми столбцами и предназначен для признаков.
/// Не должен применяться к целевому столбцу (target). /// Не должен применяться к целевому столбцу (target)
LabelEncoder = class(IPreprocessor, IColumnBoundStep) OrdinalEncoder = class(IPreprocessor, IColumnBoundStep)
private private
col: string; col: string;
mapping: Dictionary<string, integer>; mapping: Dictionary<string, integer>;
fitted: boolean; fitted: boolean;
public public
/// Создаёт LabelEncoder для указанного столбца /// Создаёт OrdinalEncoder для указанного столбца
constructor Create(column: string); constructor Create(column: string);
/// Определяет множество категорий столбца и сохраняет их числовое кодирование. /// Определяет множество категорий столбца и сохраняет их числовое кодирование.
@ -66,8 +66,10 @@ type
/// • отображение категорий НЕ копируется методом Clone /// • отображение категорий НЕ копируется методом Clone
function Fit(df: DataFrame): IPreprocessor; function Fit(df: DataFrame): IPreprocessor;
/// Заменяет категории их числовыми кодами /// Заменяет категории их числовыми кодами.
/// Возвращает новый DataFrame /// Неизвестные категории кодируются значением -1.
/// Пропущенные значения сохраняются как пропуски.
/// Возвращает новый DataFrame.
function Transform(df: DataFrame): DataFrame; function Transform(df: DataFrame): DataFrame;
/// Выполняет Fit и Transform последовательно /// Выполняет Fit и Transform последовательно
@ -174,14 +176,14 @@ implementation
uses MLExceptions; uses MLExceptions;
const const
ER_LABELENCODER_NO_COLUMN = ER_ORDINALENCODER_NO_COLUMN =
'LabelEncoder: столбец не указан!!LabelEncoder: column not specified'; 'OrdinalEncoder: столбец не указан!!OrdinalEncoder: column not specified';
ER_LABELENCODER_NOT_STRING = ER_ORDINALENCODER_NOT_STRING =
'LabelEncoder: столбец "{0}" не является строковым!!' + 'OrdinalEncoder: столбец "{0}" не является строковым!!' +
'LabelEncoder: column "{0}" is not string'; 'OrdinalEncoder: column "{0}" is not string';
ER_LABELENCODER_UNSEEN_CATEGORY = ER_ORDINALENCODER_UNSEEN_CATEGORY =
'LabelEncoder: неизвестная категория "{0}"!!' + 'OrdinalEncoder: неизвестная категория "{0}"!!' +
'LabelEncoder: unseen category "{0}"'; 'OrdinalEncoder: unseen category "{0}"';
ER_ONEHOT_NO_COLUMN = ER_ONEHOT_NO_COLUMN =
'OneHotEncoder: столбец не указан!!OneHotEncoder: column not specified'; 'OneHotEncoder: столбец не указан!!OneHotEncoder: column not specified';
ER_ONEHOT_NOT_STRING = ER_ONEHOT_NOT_STRING =
@ -229,19 +231,19 @@ const
//----------------------------- //-----------------------------
// LabelEncoder // OrdinalEncoder
//----------------------------- //-----------------------------
constructor LabelEncoder.Create(column: string); constructor OrdinalEncoder.Create(column: string);
begin begin
if column = '' then if column = '' then
ArgumentError(ER_LABELENCODER_NO_COLUMN); ArgumentError(ER_ORDINALENCODER_NO_COLUMN);
col := column; col := column;
fitted := false; fitted := false;
end; end;
function LabelEncoder.Fit(df: DataFrame): IPreprocessor; function OrdinalEncoder.Fit(df: DataFrame): IPreprocessor;
begin begin
if df = nil then if df = nil then
ArgumentNullError(ER_ARG_NULL, 'df'); ArgumentNullError(ER_ARG_NULL, 'df');
@ -252,7 +254,7 @@ begin
var idx := df.Schema.IndexOf(col); var idx := df.Schema.IndexOf(col);
if df.Schema.ColumnTypeAt(idx) <> ColumnType.ctStr then if df.Schema.ColumnTypeAt(idx) <> ColumnType.ctStr then
Error(ER_LABELENCODER_NOT_STRING, col); Error(ER_ORDINALENCODER_NOT_STRING, col);
mapping := new Dictionary<string, integer>; mapping := new Dictionary<string, integer>;
@ -275,7 +277,7 @@ begin
Result := Self; Result := Self;
end; end;
function LabelEncoder.Transform(df: DataFrame): DataFrame; function OrdinalEncoder.Transform(df: DataFrame): DataFrame;
begin begin
if not fitted then if not fitted then
NotFittedError(ER_FIT_NOT_CALLED); NotFittedError(ER_FIT_NOT_CALLED);
@ -299,10 +301,11 @@ begin
begin begin
var s := cur.Str(idx); var s := cur.Str(idx);
if not mapping.ContainsKey(s) then if s not in mapping then
Error(ER_LABELENCODER_UNSEEN_CATEGORY, s); data[row] := -1
else
data[row] := mapping[s]; data[row] := mapping[s];
valid[row] := True; valid[row] := True;
end; end;
@ -328,20 +331,20 @@ begin
Result := res.SetCategorical(catCols.ToArray); Result := res.SetCategorical(catCols.ToArray);
end; end;
function LabelEncoder.FitTransform(df: DataFrame): DataFrame; function OrdinalEncoder.FitTransform(df: DataFrame): DataFrame;
begin begin
Fit(df); Fit(df);
Result := Transform(df); Result := Transform(df);
end; end;
function LabelEncoder.ToString: string; function OrdinalEncoder.ToString: string;
begin begin
Result := 'LabelEncoder(' + col + ')'; Result := 'OrdinalEncoder(' + col + ')';
end; end;
function LabelEncoder.Clone: IPreprocessor; function OrdinalEncoder.Clone: IPreprocessor;
begin begin
Result := new LabelEncoder(col); Result := new OrdinalEncoder(col);
end; end;
procedure AppendAllColumnsExcept( procedure AppendAllColumnsExcept(