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 GetColumnType(colIndex: integer): ColumnType;
/// Возвращает тип столбца по имени
function GetColumnType(name: string): ColumnType := GetColumnType(ColumnIndex(name));
/// Возвращает индекс столбца по имени
function ColumnIndex(name: string): integer;

View file

@ -262,12 +262,27 @@ begin
Result := Data.ToMatrix(Features);
end;
function Dataset.ToY(): Vector;
function Dataset.ToY: Vector;
begin
if Task = Clustering then
ArgumentError(ER_DATASET_NO_TARGET);
Result := Data.ToVector(Target);
var t := Data.GetColumnType(Target);
case t of
ColumnType.ctInt:
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;
function Dataset.ToXY(): (Matrix, Vector);

View file

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

View file

@ -523,7 +523,27 @@ end;
static function Metrics.Accuracy(yTrue, yPred: Vector): real;
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;
static function Metrics.Precision(yTrue, yPred: Vector): real;

View file

@ -25,7 +25,10 @@ type
BrushWPF = System.Windows.Media.SolidColorBrush;
Colors = System.Windows.Media.Colors;
ColorWPF = System.Windows.Media.Color;
const DefaultColor = default(ColorWPF);
type
MarkerType = (Circle, Box, Triangle, Diamond, Cross);
Palettes = static class
@ -60,14 +63,19 @@ type
public
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);
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);
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 Text(s: string; x: real := 0.5; y: real := 0.5);
procedure SetPalette(name: string);
procedure Title(s: string);
procedure XLabel(s: string);
@ -99,6 +107,8 @@ type
static procedure DrawLine(chart: ChartWPF; x, y: array of real;
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;
color: ColorWPF; size: real; marker: MarkerType; legend: string);
@ -109,11 +119,11 @@ type
static procedure AddSeries(chart: ChartWPF; series: UIElement);
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;
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;
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);
@ -418,32 +428,59 @@ begin
parentGrid.Children.Add(chart);
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);
begin
Plot.RunUI(() ->
begin
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);
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);
begin
Plot.RunUI(() ->
begin
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);
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);
begin
Plot.RunUI(() ->
@ -453,6 +490,16 @@ begin
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);
begin
grid := new GridWPF;
@ -628,6 +675,25 @@ begin
AddSeries(chart, g);
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;
color: ColorWPF; size: real; marker: MarkerType; legend: string);
begin
@ -671,29 +737,29 @@ begin
end;
class procedure Plot.LineGraph(x, y: array of real;
color: ColorWPF?; thickness: real; legend: string);
color: ColorWPF; thickness: real; legend: string);
begin
RunUI(() ->
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);
end);
end;
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
RunUI(() ->
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);
end);
end;
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
if (x = nil) or (y = nil) or (labels = nil) then
raise new System.ArgumentNullException;
@ -701,16 +767,24 @@ begin
if (x.Length <> y.Length) or (x.Length <> labels.Length) then
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
var ind := labels.Indices(v -> v = c).ToArray;
var xs := ind.ConvertAll(i -> x[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;