ML - перед RandomForest
This commit is contained in:
parent
5ddc24e36a
commit
0c4aaad2d1
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue