ML - перед RandomForest

This commit is contained in:
Mikhalkovich Stanislav 2026-02-19 22:06:38 +03:00
parent 5ddc24e36a
commit 0c4aaad2d1
3 changed files with 173 additions and 39 deletions

View file

@ -53,12 +53,14 @@ type
function Min: real; function Min: real;
/// Скалярное произведение /// Скалярное произведение
function Dot(b: Vector): real; function Dot(b: Vector): real;
// ---------- Сервисные методы ---------- // ---------- Сервисные методы ----------
function ToString: string; override := $'{data.Select(x -> x.ToString(''G3''))}'; function ToString: string; override := $'{data.Select(x -> x.ToString(''G3''))}';
function ToString(digits: integer): string := $'{data.Select(x -> x.ToString(''G''+digits))}'; function ToString(digits: integer): string := $'{data.Select(x -> x.ToString(''G''+digits))}';
procedure Print := data.Print; procedure Print := data.Print;
procedure Println := data.Println; procedure Println := data.Println;
function SubvectorBy(indices: array of integer): Vector;
end; end;
Matrix = class Matrix = class
@ -449,6 +451,17 @@ begin
Result[i] := f(self[i]); Result[i] := f(self[i]);
end; end;
function Vector.SubvectorBy(indices: array of integer): Vector;
begin
var n := indices.Length;
var resultVec := new Vector(n);
for var i := 0 to n - 1 do
resultVec[i] := self[indices[i]];
Result := resultVec;
end;
//----------------------------- //-----------------------------
// Matrix // Matrix

View file

@ -16,6 +16,11 @@ type
function Predict(X: Matrix): Vector; function Predict(X: Matrix): Vector;
function Clone: IModel; function Clone: IModel;
end; end;
/// Интерфейс древовидной модели машинного обучения
ITreeModel = interface(IModel)
function FeatureImportances: Vector;
end;
/// Интерфейс классификатора. /// Интерфейс классификатора.
/// Наследуется от IModel. /// Наследуется от IModel.

View file

@ -249,13 +249,30 @@ type
Threshold: real; Threshold: real;
end; end;
DecisionTreeBase = abstract class ISplitCriterion = interface
function Impurity(y: Vector): real;
end;
GiniCriterion = class(ISplitCriterion)
public
function Impurity(y: Vector): real;
end;
VarianceCriterion = class(ISplitCriterion)
public
function Impurity(y: Vector): real;
end;
DecisionTreeBase = abstract class(ITreeModel)
protected protected
fRoot: DecisionTreeNode; fRoot: DecisionTreeNode;
fMaxDepth: integer; fMaxDepth: integer;
fMinSamplesSplit: integer; fMinSamplesSplit: integer;
fMinSamplesLeaf: integer; fMinSamplesLeaf: integer;
fFitted: boolean; fFitted: boolean;
fCriterion: ISplitCriterion;
fFeatureImportances: Vector;
fRandomSeed: integer;
function BuildTree(X: Matrix; y: Vector; indices: array of integer; depth: integer): DecisionTreeNode; function BuildTree(X: Matrix; y: Vector; indices: array of integer; depth: integer): DecisionTreeNode;
@ -265,13 +282,19 @@ type
function LeafNode(value: real): DecisionTreeNode; function LeafNode(value: real): DecisionTreeNode;
function NodeImpurity(y: Vector; indices: array of integer): real; virtual; abstract;
procedure CopyBaseState(dest: DecisionTreeBase); procedure CopyBaseState(dest: DecisionTreeBase);
function GetFeatureSubset(nFeatures: integer): array of integer; virtual;
public public
constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1);
minSamplesLeaf: integer := 1);
function FeatureImportances: Vector;
function Fit(X: Matrix; y: Vector): IModel; virtual; abstract;
function Predict(X: Matrix): Vector; virtual; abstract;
function Clone: IModel; virtual; abstract;
function IsFitted: boolean; function IsFitted: boolean;
end; end;
@ -283,34 +306,34 @@ type
function PredictOne(x: Vector): integer; function PredictOne(x: Vector): integer;
function MajorityClass(y: Vector; indices: array of integer): integer; function MajorityClass(y: Vector; indices: array of integer): integer;
function Gini(y: Vector; indices: array of integer): real; //function Gini(y: Vector; indices: array of integer): real;
public public
constructor Create(maxDepth: integer := 10; constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1);
minSamplesSplit: integer := 2;
minSamplesLeaf: integer := 1);
function Fit(X: Matrix; y: Vector): IModel; function Fit(X: Matrix; y: Vector): IModel; override;
function Predict(X: Matrix): Vector; function Predict(X: Matrix): Vector; override;
function Clone: IModel; function Clone: IModel; override;
protected protected
function LeafValue(y: Vector; indices: array of integer): real; override; function LeafValue(y: Vector; indices: array of integer): real; override;
function NodeImpurity(y: Vector; indices: array of integer): real; override; //function NodeImpurity(y: Vector; indices: array of integer): real; override;
end; end;
DecisionTreeRegressor = class(DecisionTreeBase, IRegressor) DecisionTreeRegressor = class(DecisionTreeBase, IRegressor)
protected protected
function LeafValue(y: Vector; indices: array of integer): real; override; function LeafValue(y: Vector; indices: array of integer): real; override;
function NodeImpurity(y: Vector; indices: array of integer): real; override; //function NodeImpurity(y: Vector; indices: array of integer): real; override;
private private
function PredictOne(x: Vector): real; function PredictOne(x: Vector): real;
public public
function Fit(X: Matrix; y: Vector): IModel; constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1);
function Predict(X: Matrix): Vector;
function Clone: IModel; function Fit(X: Matrix; y: Vector): IModel; override;
function Predict(X: Matrix): Vector; override;
function Clone: IModel; override;
end; end;
@ -1221,6 +1244,47 @@ begin
Result := m; Result := m;
end; end;
function GiniCriterion.Impurity(y: Vector): real;
begin
if y.Length = 0 then exit(0.0);
var counts := Dict&<integer,integer>;
foreach var v in y.data do
begin
var c := integer(v);
if counts.ContainsKey(c) then
counts[c] += 1
else
counts[c] := 1;
end;
var n := y.Length;
var sumsq := 0.0;
foreach var kv in counts do
begin
var p := kv.Value / n;
sumsq += p * p;
end;
Result := 1.0 - sumsq;
end;
function VarianceCriterion.Impurity(y: Vector): real;
begin
if y.Length = 0 then exit(0.0);
var mean := y.Mean;
var s := 0.0;
foreach var v in y.data do
begin
var d := v - mean;
s += d*d;
end;
Result := s / y.Length;
end;
function LeafClass(c: integer): DecisionTreeNode; function LeafClass(c: integer): DecisionTreeNode;
begin begin
@ -1268,16 +1332,12 @@ begin
Result := n; Result := n;
end; end;
constructor DecisionTreeBase.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer);
constructor DecisionTreeBase.Create(
maxDepth: integer;
minSamplesSplit: integer;
minSamplesLeaf: integer
);
begin begin
fMaxDepth := maxDepth; fMaxDepth := maxDepth;
fMinSamplesSplit := minSamplesSplit; fMinSamplesSplit := minSamplesSplit;
fMinSamplesLeaf := minSamplesLeaf; fMinSamplesLeaf := minSamplesLeaf;
fRandomSeed := 0;
end; end;
procedure DecisionTreeBase.CopyBaseState(dest: DecisionTreeBase); procedure DecisionTreeBase.CopyBaseState(dest: DecisionTreeBase);
@ -1291,6 +1351,18 @@ begin
dest.fRoot := fRoot.Clone; dest.fRoot := fRoot.Clone;
end; end;
function DecisionTreeBase.GetFeatureSubset(nFeatures: integer): array of integer;
begin
Result := new integer[nFeatures];
for var i := 0 to nFeatures-1 do
Result[i] := i;
end;
function DecisionTreeBase.FeatureImportances: Vector;
begin
Result := fFeatureImportances.Clone;
end;
function DecisionTreeBase.IsFitted: boolean; function DecisionTreeBase.IsFitted: boolean;
begin begin
Result := fFitted; Result := fFitted;
@ -1316,7 +1388,7 @@ begin
exit(LeafNode(LeafValue(y, indices))); exit(LeafNode(LeafValue(y, indices)));
// Если узел уже чистый // Если узел уже чистый
if NodeImpurity(y, indices) = 0.0 then if fCriterion.Impurity(y.SubvectorBy(indices)) = 0.0 then
exit(LeafNode(LeafValue(y, indices))); exit(LeafNode(LeafValue(y, indices)));
// Поиск лучшего разбиения // Поиск лучшего разбиения
@ -1365,8 +1437,17 @@ begin
var bestThreshold := 0.0; var bestThreshold := 0.0;
var n := indices.Length; var n := indices.Length;
var parentImp := fCriterion.Impurity(y.SubvectorBy(indices));
for var j := 0 to X.Cols - 1 do var bestLeftImp := 0.0;
var bestRightImp := 0.0;
var bestLeftCount := 0;
var bestRightCount := 0;
var features := GetFeatureSubset(X.Cols);
foreach var j in features do
begin begin
// --- собрать пары (value, rowIndex) // --- собрать пары (value, rowIndex)
var pairs: array of (real, integer); var pairs: array of (real, integer);
@ -1407,8 +1488,8 @@ begin
var leftArr := left.ToArray; var leftArr := left.ToArray;
var rightArr := right.ToArray; var rightArr := right.ToArray;
var leftImp := NodeImpurity(y, leftArr); var leftImp := fCriterion.Impurity(y.SubvectorBy(leftArr));
var rightImp := NodeImpurity(y, rightArr); var rightImp := fCriterion.Impurity(y.SubvectorBy(rightArr));
var weighted := var weighted :=
(leftArr.Length / n) * leftImp + (leftArr.Length / n) * leftImp +
@ -1416,6 +1497,11 @@ begin
if weighted < bestScore then if weighted < bestScore then
begin begin
bestLeftImp := leftImp;
bestRightImp := rightImp;
bestLeftCount := leftArr.Length;
bestRightCount := rightArr.Length;
bestScore := weighted; bestScore := weighted;
bestFeature := j; bestFeature := j;
bestThreshold := threshold; bestThreshold := threshold;
@ -1431,6 +1517,16 @@ begin
end; end;
end; end;
end; end;
if bestFeature <> -1 then
begin
var wl := bestLeftCount / n;
var wr := bestRightCount / n;
var gain := parentImp - wl * bestLeftImp - wr * bestRightImp;
fFeatureImportances[bestFeature] += gain;
end;
Result.Found := bestFeature <> -1; Result.Found := bestFeature <> -1;
Result.Feature := bestFeature; Result.Feature := bestFeature;
@ -1479,7 +1575,7 @@ begin
Result := bestClass; Result := bestClass;
end; end;
function DecisionTreeClassifier.Gini(y: Vector; indices: array of integer): real; {function DecisionTreeClassifier.Gini(y: Vector; indices: array of integer): real;
begin begin
var counts := new integer[fClassCount]; var counts := new integer[fClassCount];
@ -1496,14 +1592,12 @@ begin
end; end;
Result := 1.0 - sum; Result := 1.0 - sum;
end; end;}
constructor DecisionTreeClassifier.Create(maxDepth: integer; constructor DecisionTreeClassifier.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer);
minSamplesSplit: integer; minSamplesLeaf: integer);
begin begin
fMaxDepth := maxDepth; inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf);
fMinSamplesSplit := minSamplesSplit; fCriterion := new GiniCriterion;
fMinSamplesLeaf := minSamplesLeaf;
end; end;
function DecisionTreeClassifier.Fit(X: Matrix; y: Vector): IModel; function DecisionTreeClassifier.Fit(X: Matrix; y: Vector): IModel;
@ -1513,6 +1607,11 @@ begin
if X.Rows = 0 then if X.Rows = 0 then
ArgumentError(ER_EMPTY_DATASET); ArgumentError(ER_EMPTY_DATASET);
if fRandomSeed <> 0 then
Randomize(fRandomSeed);
fFeatureImportances := new Vector(X.Cols);
// --- 1. Найти уникальные классы // --- 1. Найти уникальные классы
var unique := new HashSet<integer>; var unique := new HashSet<integer>;
@ -1547,6 +1646,11 @@ begin
// --- 5. Построить дерево // --- 5. Построить дерево
fRoot := BuildTree(X, yEncoded, indices, 0); fRoot := BuildTree(X, yEncoded, indices, 0);
var s := fFeatureImportances.Sum;
if s > 0 then
for var i := 0 to fFeatureImportances.Length-1 do
fFeatureImportances[i] /= s;
fFitted := true; fFitted := true;
@ -1605,9 +1709,10 @@ begin
Result := MajorityClass(y, indices); Result := MajorityClass(y, indices);
end; end;
function DecisionTreeClassifier.NodeImpurity(y: Vector; indices: array of integer): real; constructor DecisionTreeRegressor.Create(maxDepth: integer; minSamplesSplit: integer; minSamplesLeaf: integer);
begin begin
Result := Gini(y, indices); inherited Create(maxDepth, minSamplesSplit, minSamplesLeaf);
fCriterion := new VarianceCriterion;
end; end;
function DecisionTreeRegressor.LeafValue(y: Vector; indices: array of integer): real; function DecisionTreeRegressor.LeafValue(y: Vector; indices: array of integer): real;
@ -1619,7 +1724,7 @@ begin
Result := sum / indices.Length; Result := sum / indices.Length;
end; end;
function DecisionTreeRegressor.NodeImpurity(y: Vector; indices: array of integer): real; {function DecisionTreeRegressor.NodeImpurity(y: Vector; indices: array of integer): real;
begin begin
var sum := 0.0; var sum := 0.0;
var sqSum := 0.0; var sqSum := 0.0;
@ -1634,7 +1739,7 @@ begin
var mean := sum / n; var mean := sum / n;
Result := (sqSum / n) - mean * mean; Result := (sqSum / n) - mean * mean;
end; end;}
function DecisionTreeRegressor.Fit(X: Matrix; y: Vector): IModel; function DecisionTreeRegressor.Fit(X: Matrix; y: Vector): IModel;
begin begin
@ -1643,12 +1748,23 @@ begin
if X.Rows = 0 then if X.Rows = 0 then
ArgumentError(ER_EMPTY_DATASET); ArgumentError(ER_EMPTY_DATASET);
if fRandomSeed <> 0 then
Randomize(fRandomSeed);
fFeatureImportances := new Vector(X.Cols);
var indices := new integer[X.Rows]; var indices := new integer[X.Rows];
for var i := 0 to X.Rows - 1 do for var i := 0 to X.Rows - 1 do
indices[i] := i; indices[i] := i;
fRoot := BuildTree(X, y, indices, 0); fRoot := BuildTree(X, y, indices, 0);
var s := fFeatureImportances.Sum;
if s > 0 then
for var i := 0 to fFeatureImportances.Length-1 do
fFeatureImportances[i] /= s;
fFitted := true; fFitted := true;
Result := Self; Result := Self;