diff --git a/bin/Lib/DataFrameABC.pas b/bin/Lib/DataFrameABC.pas index 7f40d5d16..04df3931a 100644 --- a/bin/Lib/DataFrameABC.pas +++ b/bin/Lib/DataFrameABC.pas @@ -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; diff --git a/bin/Lib/MLDatasets.pas b/bin/Lib/MLDatasets.pas index e32767a9c..344aa2385 100644 --- a/bin/Lib/MLDatasets.pas +++ b/bin/Lib/MLDatasets.pas @@ -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); diff --git a/bin/Lib/MLModelsABC.pas b/bin/Lib/MLModelsABC.pas index 2325eeea2..53790a4cd 100644 --- a/bin/Lib/MLModelsABC.pas +++ b/bin/Lib/MLModelsABC.pas @@ -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 := diff --git a/bin/Lib/MetricsABC.pas b/bin/Lib/MetricsABC.pas index c0c5797cf..cb8f69053 100644 --- a/bin/Lib/MetricsABC.pas +++ b/bin/Lib/MetricsABC.pas @@ -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; diff --git a/bin/Lib/PlotML.pas b/bin/Lib/PlotML.pas index 333498680..27c5a3a96 100644 --- a/bin/Lib/PlotML.pas +++ b/bin/Lib/PlotML.pas @@ -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;