ML небольшие изменения

This commit is contained in:
Mikhalkovich Stanislav 2026-03-11 18:30:33 +03:00
parent 18e4c5ba79
commit d588671441
5 changed files with 156 additions and 42 deletions

View file

@ -111,6 +111,8 @@ type
function ColumnCount: integer; function ColumnCount: integer;
/// Возвращает тип столбца по номеру /// Возвращает тип столбца по номеру
function GetColumnType(colIndex: integer): ColumnType; function GetColumnType(colIndex: integer): ColumnType;
/// Возвращает тип столбца по имени
function GetColumnType(name: string): ColumnType := GetColumnType(ColumnIndex(name));
/// Возвращает индекс столбца по имени /// Возвращает индекс столбца по имени
function ColumnIndex(name: string): integer; function ColumnIndex(name: string): integer;

View file

@ -262,12 +262,27 @@ begin
Result := Data.ToMatrix(Features); Result := Data.ToMatrix(Features);
end; end;
function Dataset.ToY(): Vector; function Dataset.ToY: Vector;
begin begin
if Task = Clustering then var t := Data.GetColumnType(Target);
ArgumentError(ER_DATASET_NO_TARGET); case t of
ColumnType.ctInt:
Result := Data.ToVector(Target); Result := new Vector(Data.GetIntColumn(Target));
ColumnType.ctFloat:
Result := Data.ToVector(Target);
ColumnType.ctStr:
Result := new Vector(EncodeLabels(Data.GetStrColumn(Target)));
ColumnType.ctBool:
begin
var b := Data.GetBoolColumn(Target);
var v := new Vector(b.Length);
for var i := 0 to b.Length - 1 do
v[i] := if b[i] then 1.0 else 0.0;
Result := v;
end;
end;
end; end;
function Dataset.ToXY(): (Matrix, Vector); function Dataset.ToXY(): (Matrix, Vector);

View file

@ -430,7 +430,7 @@ type
public public
/// Создает классификационное дерево: /// Создает классификационное дерево:
/// • maxDepth — максимальная глубина дерева. /// • maxDepth — максимальная глубина дерева (-1 означает без ограничения).
/// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesSplit — минимальное число объектов для разбиения узла.
/// • minSamplesLeaf — минимальное число объектов в листе /// • minSamplesLeaf — минимальное число объектов в листе
constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1; seed: integer := -1); constructor Create(maxDepth: integer := 10; minSamplesSplit: integer := 2; minSamplesLeaf: integer := 1; seed: integer := -1);
@ -489,7 +489,7 @@ type
public public
/// Создает регрессионное дерево: /// Создает регрессионное дерево:
/// • maxDepth — максимальная глубина. /// • maxDepth — максимальная глубина (-1 означает без ограничения).
/// • minSamplesSplit — минимальное число объектов для разбиения. /// • minSamplesSplit — минимальное число объектов для разбиения.
/// • minSamplesLeaf — минимальное число объектов в листе. /// • minSamplesLeaf — минимальное число объектов в листе.
/// • leafL2 — коэффициент L2-регуляризации значения листа /// • leafL2 — коэффициент L2-регуляризации значения листа
@ -622,12 +622,12 @@ type
public public
/// Создает регрессионный случайный лес: /// Создает регрессионный случайный лес:
/// • nTrees — число деревьев в ансамбле. /// • nTrees — число деревьев в ансамбле.
/// • maxDepth — максимальная глубина деревьев. /// • maxDepth — максимальная глубина деревьев (-1 означает без ограничения).
/// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesSplit — минимальное число объектов для разбиения узла.
/// • minSamplesLeaf — минимальное число объектов в листе. /// • minSamplesLeaf — минимальное число объектов в листе.
/// • maxFeaturesMode — режим выбора числа признаков, рассматриваемых при поиске разбиения /// • maxFeaturesMode — режим выбора числа признаков, рассматриваемых при поиске разбиения
constructor Create(nTrees: integer := 100; constructor Create(nTrees: integer := 100;
maxDepth: integer := integer.MaxValue; maxDepth: integer := -1;
minSamplesSplit: integer := 2; minSamplesSplit: integer := 2;
minSamplesLeaf: integer := 1; minSamplesLeaf: integer := 1;
maxFeaturesMode: TMaxFeaturesMode := TMaxFeaturesMode.HalfFeatures; maxFeaturesMode: TMaxFeaturesMode := TMaxFeaturesMode.HalfFeatures;
@ -669,13 +669,13 @@ type
public public
/// Создает классификационный случайный лес: /// Создает классификационный случайный лес:
/// • nTrees — число деревьев в ансамбле. /// • nTrees — число деревьев в ансамбле.
/// • maxDepth — максимальная глубина каждого дерева. /// • maxDepth — максимальная глубина каждого дерева (-1 означает без ограничения).
/// • minSamplesSplit — минимальное число объектов для разбиения узла. /// • minSamplesSplit — минимальное число объектов для разбиения узла.
/// • minSamplesLeaf — минимальное число объектов в листе. /// • minSamplesLeaf — минимальное число объектов в листе.
/// • maxFeaturesMode — режим выбора числа признаков при поиске разбиения, по умолчанию используется sqrt(p), /// • maxFeaturesMode — режим выбора числа признаков при поиске разбиения, по умолчанию используется sqrt(p),
/// что является стандартом для классификации /// что является стандартом для классификации
constructor Create(nTrees: integer := 100; constructor Create(nTrees: integer := 100;
maxDepth: integer := integer.MaxValue; maxDepth: integer := -1;
minSamplesSplit: integer := 2; minSamplesSplit: integer := 2;
minSamplesLeaf: integer := 1; minSamplesLeaf: integer := 1;
maxFeaturesMode: TMaxFeaturesMode := TMaxFeaturesMode.SqrtFeatures; maxFeaturesMode: TMaxFeaturesMode := TMaxFeaturesMode.SqrtFeatures;
@ -2777,7 +2777,7 @@ constructor DecisionTreeBase.Create(
minSamplesLeaf: integer; minSamplesLeaf: integer;
seed: integer); seed: integer);
begin begin
if maxDepth <= 0 then if maxDepth < -1 then
ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, maxDepth); ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, maxDepth);
if maxDepth > MAX_ALLOWED_TREE_DEPTH then if maxDepth > MAX_ALLOWED_TREE_DEPTH then
@ -2898,7 +2898,7 @@ end;
function DecisionTreeBase.BuildTree(X: Matrix; y: Vector; function DecisionTreeBase.BuildTree(X: Matrix; y: Vector;
indices: array of integer; depth: integer): DecisionTreeNode; indices: array of integer; depth: integer): DecisionTreeNode;
begin begin
if (fMaxDepth > 0) and (depth >= fMaxDepth) then if (fMaxDepth >= 0) and (depth >= fMaxDepth) then
exit(LeafNode(LeafValue(y, indices))); exit(LeafNode(LeafValue(y, indices)));
if indices.Length < fMinSamplesSplit then if indices.Length < fMinSamplesSplit then
@ -3298,7 +3298,7 @@ begin
if fMinSamplesLeaf < 1 then if fMinSamplesLeaf < 1 then
ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf); ArgumentOutOfRangeError(ER_MINSAMPLESLEAF_INVALID, fMinSamplesLeaf);
if (fMaxDepth < 0) then if fMaxDepth < -1 then
ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth);
if fMaxDepth > 10000 then if fMaxDepth > 10000 then
@ -3530,7 +3530,7 @@ begin
if fLeafL2 < 0 then if fLeafL2 < 0 then
ArgumentOutOfRangeError(ER_L2_NEGATIVE, fLeafL2); ArgumentOutOfRangeError(ER_L2_NEGATIVE, fLeafL2);
if fMaxDepth < 0 then if fMaxDepth < -1 then
ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth);
if fMaxDepth > 10000 then if fMaxDepth > 10000 then
@ -3651,7 +3651,7 @@ begin
if nTrees <= 0 then if nTrees <= 0 then
ArgumentOutOfRangeError(ER_NTREES_INVALID, nTrees); ArgumentOutOfRangeError(ER_NTREES_INVALID, nTrees);
if maxDepth <= 0 then if maxDepth < -1 then
ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, maxDepth); ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, maxDepth);
if minSamplesSplit < 2 then if minSamplesSplit < 2 then
@ -3759,7 +3759,7 @@ begin
if fMinSamplesLeaf >= fMinSamplesSplit then if fMinSamplesLeaf >= fMinSamplesSplit then
ArgumentError(ER_MIN_LEAF_GE_SPLIT, fMinSamplesLeaf, fMinSamplesSplit); ArgumentError(ER_MIN_LEAF_GE_SPLIT, fMinSamplesLeaf, fMinSamplesSplit);
if fMaxDepth < 0 then if fMaxDepth < -1 then
ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth);
if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then
@ -3940,7 +3940,7 @@ end;
function RandomForestRegressor.ToString: string; function RandomForestRegressor.ToString: string;
begin begin
var depthStr := var depthStr :=
if fMaxDepth = integer.MaxValue then '∞' if fMaxDepth = -1 then '∞'
else fMaxDepth.ToString; else fMaxDepth.ToString;
var seedPart := var seedPart :=
@ -4003,7 +4003,7 @@ begin
if fMinSamplesLeaf >= fMinSamplesSplit then if fMinSamplesLeaf >= fMinSamplesSplit then
ArgumentError(ER_MIN_LEAF_GE_SPLIT, fMinSamplesLeaf, fMinSamplesSplit); ArgumentError(ER_MIN_LEAF_GE_SPLIT, fMinSamplesLeaf, fMinSamplesSplit);
if fMaxDepth < 0 then if fMaxDepth < -1 then
ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth); ArgumentOutOfRangeError(ER_MAX_DEPTH_INVALID, fMaxDepth);
if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then if fMaxDepth > MAX_ALLOWED_TREE_DEPTH then
@ -4274,9 +4274,12 @@ begin
rf.fHasOOBScore := fHasOOBScore; rf.fHasOOBScore := fHasOOBScore;
// --- trees --- // --- trees ---
SetLength(rf.fTrees, fTrees.Length); if fTrees <> nil then
for var i := 0 to fTrees.Length - 1 do begin
rf.fTrees[i] := DecisionTreeClassifier(fTrees[i].Clone); SetLength(rf.fTrees, fTrees.Length);
for var i := 0 to fTrees.Length - 1 do
rf.fTrees[i] := DecisionTreeClassifier(fTrees[i].Clone);
end;
Result := rf; Result := rf;
end; end;
@ -4300,7 +4303,7 @@ end;
function RandomForestClassifier.ToString: string; function RandomForestClassifier.ToString: string;
begin begin
var depthStr := var depthStr :=
if fMaxDepth = integer.MaxValue then '∞' if fMaxDepth = -1 then '∞'
else fMaxDepth.ToString; else fMaxDepth.ToString;
var seedPart := var seedPart :=

View file

@ -523,7 +523,27 @@ end;
static function Metrics.Accuracy(yTrue, yPred: Vector): real; static function Metrics.Accuracy(yTrue, yPred: Vector): real;
begin begin
Result := ConfusionMatrix.Create(yTrue, yPred).Accuracy; if yTrue = nil then
ArgumentNullError(ER_ARG_NULL, 'yTrue');
if yPred = nil then
ArgumentNullError(ER_ARG_NULL, 'yPred');
var n := yTrue.Length;
if n <> yPred.Length then
DimensionError(ER_DIM_MISMATCH, yTrue.Length, yPred.Length);
if n = 0 then
ArgumentError(ER_EMPTY_DATA, 'Accuracy');
var correct := 0;
for var i := 0 to n - 1 do
if yTrue[i] = yPred[i] then
correct += 1;
Result := correct / n;
end; end;
static function Metrics.Precision(yTrue, yPred: Vector): real; static function Metrics.Precision(yTrue, yPred: Vector): real;

View file

@ -25,7 +25,10 @@ type
BrushWPF = System.Windows.Media.SolidColorBrush; BrushWPF = System.Windows.Media.SolidColorBrush;
Colors = System.Windows.Media.Colors; Colors = System.Windows.Media.Colors;
ColorWPF = System.Windows.Media.Color; ColorWPF = System.Windows.Media.Color;
const DefaultColor = default(ColorWPF);
type
MarkerType = (Circle, Box, Triangle, Diamond, Cross); MarkerType = (Circle, Box, Triangle, Diamond, Cross);
Palettes = static class Palettes = static class
@ -60,14 +63,19 @@ type
public public
constructor Create(g: GridWPF; r,c: integer); constructor Create(g: GridWPF; r,c: integer);
procedure LineGraph(x, y: array of real; color: ColorWPF? := nil; procedure LineGraph(x, y: array of real; color: ColorWPF := DefaultColor;
thickness: real := 2; legend: string := nil); thickness: real := 2; legend: string := nil);
procedure Points(x, y: array of real; color: ColorWPF? := nil; procedure Points(x, y: array of real; color: ColorWPF := DefaultColor;
size: real := 6; marker: MarkerType := MarkerType.Circle; legend: string := nil); size: real := 6; marker: MarkerType := MarkerType.Circle; legend: string := nil);
procedure Points(x, y: array of real; labels: array of integer;
size: real := 6; marker: MarkerType := MarkerType.Circle);
procedure Heatmap(m: array[,] of real); procedure Heatmap(m: array[,] of real);
procedure Text(s: string; x: real := 0.5; y: real := 0.5);
procedure SetPalette(name: string); procedure SetPalette(name: string);
procedure Title(s: string); procedure Title(s: string);
procedure XLabel(s: string); procedure XLabel(s: string);
@ -99,6 +107,8 @@ type
static procedure DrawLine(chart: ChartWPF; x, y: array of real; static procedure DrawLine(chart: ChartWPF; x, y: array of real;
color: ColorWPF; thickness: real; legend: string); color: ColorWPF; thickness: real; legend: string);
static procedure DrawText(chart: ChartWPF; s: string; x, y: real);
static procedure DrawPoints(chart: ChartWPF; x, y: array of real; static procedure DrawPoints(chart: ChartWPF; x, y: array of real;
color: ColorWPF; size: real; marker: MarkerType; legend: string); color: ColorWPF; size: real; marker: MarkerType; legend: string);
@ -109,11 +119,11 @@ type
static procedure AddSeries(chart: ChartWPF; series: UIElement); static procedure AddSeries(chart: ChartWPF; series: UIElement);
static procedure LineGraph(x, y: array of real; static procedure LineGraph(x, y: array of real;
color: ColorWPF? := nil; thickness: real := 2; legend: string := nil); color: ColorWPF := DefaultColor; thickness: real := 2; legend: string := nil);
static procedure Points(x, y: array of real; static procedure Points(x, y: array of real;
color: ColorWPF? := nil; size: real := 6; marker: MarkerType := MarkerType.Circle; legend: string := nil); color: ColorWPF := DefaultColor; size: real := 6; marker: MarkerType := MarkerType.Circle; legend: string := nil);
static procedure Points(x, y: array of real; static procedure Points(x, y: array of real;
labels: array of integer; color: ColorWPF? := nil; size: real := 6; marker: MarkerType := MarkerType.Circle); labels: array of integer; color: ColorWPF := DefaultColor; size: real := 6; marker: MarkerType := MarkerType.Circle);
static procedure Heatmap(m: array[,] of real); static procedure Heatmap(m: array[,] of real);
@ -418,32 +428,59 @@ begin
parentGrid.Children.Add(chart); parentGrid.Children.Add(chart);
end; end;
procedure Cell.LineGraph(x, y: array of real; color: ColorWPF?; procedure Cell.LineGraph(x, y: array of real; color: ColorWPF;
thickness: real; legend: string); thickness: real; legend: string);
begin begin
Plot.RunUI(() -> Plot.RunUI(() ->
begin begin
EnsureChart; EnsureChart;
var clr := if color.HasValue then color.Value else NextColor; var clr := if color<>DefaultColor then color else NextColor;
Plot.DrawLine(chart, x, y, clr, thickness, legend); Plot.DrawLine(chart, x, y, clr, thickness, legend);
end); end);
end; end;
procedure Cell.Points(x, y: array of real; color: ColorWPF?; procedure Cell.Points(x, y: array of real; color: ColorWPF;
size: real; marker: MarkerType; legend: string); size: real; marker: MarkerType; legend: string);
begin begin
Plot.RunUI(() -> Plot.RunUI(() ->
begin begin
EnsureChart; EnsureChart;
var clr := if color.HasValue then color.Value else NextColor; var clr := if color<>DefaultColor then color else NextColor;
Plot.DrawPoints(chart, x, y, clr, size, marker, legend); Plot.DrawPoints(chart, x, y, clr, size, marker, legend);
end); end);
end; end;
procedure Cell.Points(x, y: array of real; labels: array of integer;
size: real; marker: MarkerType);
begin
if (x = nil) or (y = nil) or (labels = nil) then
raise new System.ArgumentNullException;
if (x.Length <> y.Length) or (x.Length <> labels.Length) then
raise new System.ArgumentException('Points: array sizes mismatch');
var classes := labels.Distinct.ToArray;
&Array.Sort(classes);
var pal := CurrentPalette;
foreach var c in classes do
begin
var ind := labels.Indices(v -> v = c).ToArray;
var xs := ind.ConvertAll(i -> x[i]);
var ys := ind.ConvertAll(i -> y[i]);
var clr := pal.Colors[c mod pal.Colors.Length];
self.Points(xs, ys, clr, size, marker, nil);
end;
end;
procedure Cell.Heatmap(m: array[,] of real); procedure Cell.Heatmap(m: array[,] of real);
begin begin
Plot.RunUI(() -> Plot.RunUI(() ->
@ -453,6 +490,16 @@ begin
end); end);
end; end;
procedure Cell.Text(s: string; x: real; y: real);
begin
Plot.RunUI(() ->
begin
EnsureChart;
Plot.DrawText(chart, s, x, y);
end);
end;
constructor Figure.Create(rows,cols: integer); constructor Figure.Create(rows,cols: integer);
begin begin
grid := new GridWPF; grid := new GridWPF;
@ -628,6 +675,25 @@ begin
AddSeries(chart, g); AddSeries(chart, g);
end; end;
static procedure Plot.DrawText(chart: ChartWPF; s: string; x, y: real);
begin
var tb := new System.Windows.Controls.TextBlock;
tb.Text := s;
tb.FontSize := 14;
tb.FontWeight := System.Windows.FontWeights.Bold;
tb.HorizontalAlignment := System.Windows.HorizontalAlignment.Center;
tb.VerticalAlignment := System.Windows.VerticalAlignment.Center;
var grid := chart.Parent as System.Windows.Controls.Grid;
if grid <> nil then
begin
tb.HorizontalAlignment := System.Windows.HorizontalAlignment.Center;
tb.VerticalAlignment := System.Windows.VerticalAlignment.Center;
grid.Children.Add(tb);
end;
end;
static procedure Plot.DrawPoints(chart: ChartWPF; x, y: array of real; static procedure Plot.DrawPoints(chart: ChartWPF; x, y: array of real;
color: ColorWPF; size: real; marker: MarkerType; legend: string); color: ColorWPF; size: real; marker: MarkerType; legend: string);
begin begin
@ -671,29 +737,29 @@ begin
end; end;
class procedure Plot.LineGraph(x, y: array of real; class procedure Plot.LineGraph(x, y: array of real;
color: ColorWPF?; thickness: real; legend: string); color: ColorWPF; thickness: real; legend: string);
begin begin
RunUI(() -> RunUI(() ->
begin begin
var clr := if color.HasValue then color.Value else NextRootColor; var clr := if color<>DefaultColor then color else NextRootColor;
DrawLine(rootChart, x, y, clr, thickness, legend); DrawLine(rootChart, x, y, clr, thickness, legend);
end); end);
end; end;
static procedure Plot.Points(x, y: array of real; static procedure Plot.Points(x, y: array of real;
color: ColorWPF?; size: real; marker: MarkerType; legend: string); color: ColorWPF; size: real; marker: MarkerType; legend: string);
begin begin
RunUI(() -> RunUI(() ->
begin begin
var clr := if color.HasValue then color.Value else NextRootColor; var clr := if color<>DefaultColor then color else NextRootColor;
DrawPoints(rootChart, x, y, clr, size, marker, legend); DrawPoints(rootChart, x, y, clr, size, marker, legend);
end); end);
end; end;
static procedure Plot.Points(x, y: array of real; labels: array of integer; static procedure Plot.Points(x, y: array of real; labels: array of integer;
color: ColorWPF?; size: real; marker: MarkerType); color: ColorWPF; size: real; marker: MarkerType);
begin begin
if (x = nil) or (y = nil) or (labels = nil) then if (x = nil) or (y = nil) or (labels = nil) then
raise new System.ArgumentNullException; raise new System.ArgumentNullException;
@ -701,16 +767,24 @@ begin
if (x.Length <> y.Length) or (x.Length <> labels.Length) then if (x.Length <> y.Length) or (x.Length <> labels.Length) then
raise new System.ArgumentException('Points: array sizes mismatch'); raise new System.ArgumentException('Points: array sizes mismatch');
var k := labels.Max + 1; var classes := labels.Distinct.ToArray;
&Array.Sort(classes);
for var c := 0 to k-1 do var pal := CurrentPalette;
foreach var c in classes do
begin begin
var ind := labels.Indices(v -> v = c).ToArray; var ind := labels.Indices(v -> v = c).ToArray;
var xs := ind.ConvertAll(i -> x[i]); var xs := ind.ConvertAll(i -> x[i]);
var ys := ind.ConvertAll(i -> y[i]); var ys := ind.ConvertAll(i -> y[i]);
Points(xs, ys, color, size, marker, 'cluster ' + c); var clr :=
if color<>DefaultColor
then color
else pal.Colors[c mod pal.Colors.Length];
Points(xs, ys, clr, size, marker, nil);
end; end;
end; end;