-
Notifications
You must be signed in to change notification settings - Fork 207
Expand file tree
/
Copy pathConfusionMatrixReport.lpr
More file actions
149 lines (132 loc) · 3.87 KB
/
Copy pathConfusionMatrixReport.lpr
File metadata and controls
149 lines (132 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
program ConfusionMatrixReport;
(*
ConfusionMatrixReport: trains a small MLP classifier on a synthetic 3-class
2D-cluster dataset, then prints TNNet.ConfusionMatrixReport for the
held-out validation split. Pure-CPU, runs in a few seconds.
The confusion matrix, per-class precision/recall/F1, top-1 / balanced
accuracy, most-confused pairs and per-class hard-example indices give
an at-a-glance view of which classes the model mixes up and where the
remaining error mass sits.
Copyright (C) 2026 Joao Paulo Schwarz Schuler
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
any later version.
Coded by Claude (AI).
*)
{$mode objfpc}{$H+}
uses {$IFDEF UNIX} cthreads, {$ENDIF}
Classes, SysUtils, Math,
neuralnetwork,
neuralvolume;
const
cNumClasses = 3;
cTrainPerClass = 200;
cValPerClass = 40;
cEpochs = 60;
cLearningRate = 0.05;
// Three 2D Gaussian clusters with overlapping tails so the model
// makes a few confusable predictions.
cCenters: array[0..2, 0..1] of TNeuralFloat =
((-1.2, -1.2),
( 1.2, -1.0),
( 0.0, 1.3));
cSigma: TNeuralFloat = 0.85;
procedure BuildNet(out NN: TNNet);
begin
NN := TNNet.Create();
NN.AddLayer(TNNetInput.Create(2, 1, 1));
NN.AddLayer(TNNetFullConnectReLU.Create(16));
NN.AddLayer(TNNetFullConnectReLU.Create(16));
NN.AddLayer(TNNetFullConnectLinear.Create(cNumClasses));
NN.AddLayer(TNNetSoftMax.Create());
end;
// Why: Box-Muller gives N(0,1) samples without pulling in a dependency.
function RandomGauss(): TNeuralFloat;
var
U1, U2: TNeuralFloat;
begin
repeat
U1 := Random;
until U1 > 1e-9;
U2 := Random;
Result := Sqrt(-2 * Ln(U1)) * Cos(2 * Pi * U2);
end;
procedure MakeSample(ClassId: integer; out X, Y: TNNetVolume);
begin
X := TNNetVolume.Create(2, 1, 1);
Y := TNNetVolume.Create(cNumClasses, 1, 1);
X.FData[0] := cCenters[ClassId][0] + RandomGauss() * cSigma;
X.FData[1] := cCenters[ClassId][1] + RandomGauss() * cSigma;
Y.Fill(0);
Y.FData[ClassId] := 1.0;
end;
procedure BuildSet(out Pairs: TNNetVolumePairList; PerClass: integer);
var
C, I: integer;
X, Y: TNNetVolume;
begin
Pairs := TNNetVolumePairList.Create();
for C := 0 to cNumClasses - 1 do
for I := 1 to PerClass do
begin
MakeSample(C, X, Y);
Pairs.Add(TNNetVolumePair.Create(X, Y));
end;
end;
procedure RunDemo();
var
NN: TNNet;
TrainSet, ValSet: TNNetVolumePairList;
Epoch, I: integer;
Pair: TNNetVolumePair;
Loss, Diff: TNeuralFloat;
Report: string;
K: integer;
begin
RandSeed := 12345;
BuildNet(NN);
BuildSet(TrainSet, cTrainPerClass);
BuildSet(ValSet, cValPerClass);
try
NN.SetLearningRate(cLearningRate, 0.9);
WriteLn('Architecture:');
NN.PrintSummary();
WriteLn;
WriteLn('Training for ', cEpochs, ' epochs on ',
TrainSet.Count, ' samples...');
for Epoch := 1 to cEpochs do
begin
Loss := 0;
for I := 0 to TrainSet.Count - 1 do
begin
Pair := TrainSet[I];
NN.Compute(Pair.I);
NN.Backpropagate(Pair.O);
Diff := -Ln(Max(1e-9,
NN.GetLastLayer().Output.FData[Pair.O.GetClass()]));
Loss := Loss + Diff;
end;
if (Epoch mod 20 = 0) or (Epoch = 1) then
WriteLn(' epoch ', Epoch:4, ' mean_nll=',
(Loss / TrainSet.Count):8:5);
end;
WriteLn;
WriteLn('Confusion matrix report on validation set (',
ValSet.Count, ' samples):');
WriteLn(StringOfChar('=', 78));
Report := TNNet.ConfusionMatrixReport(NN, ValSet, cNumClasses, 3, 2);
Write(Report);
WriteLn(StringOfChar('=', 78));
// Why: silence FPC hint about K being unused if needed for debug.
K := 0;
if K <> 0 then K := K;
finally
ValSet.Free;
TrainSet.Free;
NN.Free;
end;
end;
begin
RunDemo();
end.