radiate_gp/regression/
accuracy.rs1use super::{DataSet, Loss};
2use crate::{Eval, EvalMut, Graph, GraphEvaluator, Op, Tree, ops::OpFloat};
3use std::fmt::Debug;
4
5#[derive(Clone, Default)]
6pub struct Accuracy<'a, F: OpFloat> {
7 name: Option<String>,
8 data_set: Option<&'a DataSet<F>>,
9 loss_fn: Option<Loss>,
10}
11
12impl<'a, F: OpFloat> Accuracy<'a, F> {
13 pub fn named(mut self, name: impl Into<String>) -> Self {
14 self.name = Some(name.into());
15 self
16 }
17
18 pub fn on(mut self, data_set: &'a DataSet<F>) -> Self {
19 self.data_set = Some(data_set);
20 self
21 }
22
23 pub fn loss(mut self, loss_fn: Loss) -> Self {
24 self.loss_fn = Some(loss_fn);
25 self
26 }
27
28 pub fn calc(&self, eval: &mut impl EvalMut<[F], Vec<F>>) -> AccuracyResult {
29 let data_set = self
30 .data_set
31 .expect("DataSet reference must be provided for accuracy calculation");
32 let loss_fn = self
33 .loss_fn
34 .expect("Loss function must be provided for accuracy calculation");
35
36 self.calc_internal(eval, data_set, loss_fn)
37 }
38
39 pub fn calc_internal(
40 &self,
41 eval: &mut impl EvalMut<[F], Vec<F>>,
42 data_set: &DataSet<F>,
43 loss_fn: Loss,
44 ) -> AccuracyResult {
45 let mut outputs = Vec::new();
46 let mut total_samples = F::ZERO;
47 let mut correct_predictions = F::ZERO;
48 let mut is_regression = true;
49
50 let mut mae = F::ZERO;
51 let mut mse = F::ZERO;
52 let mut min_output = F::MAX;
53 let mut max_output = F::MIN;
54 let mut ss_total = F::ZERO;
55 let mut ss_residual = F::ZERO;
56 let mut y_mean = F::ZERO;
57
58 let mut tp = F::ZERO;
59 let mut fp = F::ZERO;
60 let mut fn_ = F::ZERO;
61
62 let loss = loss_fn.calc(data_set, eval);
63
64 let total_values = data_set.len();
65 if total_values > 0 {
66 let mut sum = F::ZERO;
67 for row in data_set.iter() {
68 sum = sum + row.1[0];
69 }
70 y_mean = sum / F::from(total_values).unwrap();
71 }
72
73 for row in data_set.iter() {
74 let output = eval.eval_mut(row.0);
75 outputs.push(output.clone());
76
77 if output.len() == 1 {
78 is_regression = true;
79 let y_true = row.1[0];
80 let y_pred = output[0];
81
82 mae = mae + (y_true - y_pred).abs();
83 mse = mse + (y_true - y_pred).powi(2);
84 ss_residual = ss_residual + (y_true - y_pred).powi(2);
85 ss_total = ss_total + (y_true - y_mean).powi(2);
86
87 min_output = min_output.min(y_true);
88 max_output = max_output.max(y_true);
89 total_samples = total_samples + F::ONE;
90 } else {
91 is_regression = false;
92 if let Some((max_idx, _)) = output
93 .iter()
94 .enumerate()
95 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
96 {
97 if let Some(target) = row.1.iter().position(|&x| x == F::ONE) {
98 total_samples = total_samples + F::ONE;
99 if max_idx == target {
100 correct_predictions = correct_predictions + F::ONE;
101 tp = tp + F::ONE;
102 } else {
103 fp = fp + F::ONE;
104 }
105 } else {
106 fn_ = fn_ + F::ONE;
107 }
108 }
109 }
110 }
111
112 let accuracy = if is_regression {
114 if total_samples > F::ZERO && (max_output - min_output) > F::ZERO {
115 F::ONE - (mae / total_samples) / (max_output - min_output)
116 } else {
117 F::ZERO
118 }
119 } else if total_samples > F::ZERO {
120 correct_predictions / total_samples
121 } else {
122 F::ZERO
123 };
124
125 let (precision, recall, f1_score) = if is_regression {
127 (F::ZERO, F::ZERO, F::ZERO) } else {
129 let precision = if tp + fp > F::ZERO {
130 tp / (tp + fp)
131 } else {
132 F::ZERO
133 };
134 let recall = if tp + fn_ > F::ZERO {
135 tp / (tp + fn_)
136 } else {
137 F::ZERO
138 };
139 let f1_score = if precision + recall > F::ZERO {
140 F::TWO * (precision * recall) / (precision + recall)
141 } else {
142 F::ZERO
143 };
144 (precision, recall, f1_score)
145 };
146
147 let rmse = if total_samples > F::ZERO {
148 (mse / total_samples).sqrt()
149 } else {
150 F::ZERO
151 };
152
153 let r_squared = if ss_total > F::ZERO {
155 F::ONE - (ss_residual / ss_total)
156 } else {
157 F::ZERO
158 };
159
160 AccuracyResult {
161 name: match &self.name {
162 Some(name) => name.clone(),
163 None => {
164 if is_regression {
165 "Regression Accuracy".to_string()
166 } else {
167 "Classification Accuracy".to_string()
168 }
169 }
170 },
171 accuracy: accuracy.extract().unwrap_or(0.0),
172 precision: precision.extract().unwrap_or(0.0),
173 recall: recall.extract().unwrap_or(0.0),
174 f1_score: f1_score.extract().unwrap_or(0.0),
175 rmse: rmse.extract().unwrap_or(0.0),
176 r_squared: r_squared.extract().unwrap_or(0.0),
177 loss: loss.extract().unwrap_or(0.0),
178 loss_fn,
179 sample_count: data_set.len(),
180 is_regression,
181 }
182 }
183}
184
185pub struct AccuracyResult {
186 name: String,
187 accuracy: f32,
188 precision: f32, recall: f32, f1_score: f32, rmse: f32, r_squared: f32, sample_count: usize,
194 loss: f32,
195 loss_fn: Loss,
196 is_regression: bool,
197}
198
199impl AccuracyResult {
200 pub fn name(&self) -> &str {
201 &self.name
202 }
203
204 pub fn accuracy(&self) -> f32 {
205 self.accuracy
206 }
207
208 pub fn precision(&self) -> f32 {
209 self.precision
210 }
211
212 pub fn recall(&self) -> f32 {
213 self.recall
214 }
215
216 pub fn f1_score(&self) -> f32 {
217 self.f1_score
218 }
219
220 pub fn rmse(&self) -> f32 {
221 self.rmse
222 }
223
224 pub fn r_squared(&self) -> f32 {
225 self.r_squared
226 }
227
228 pub fn sample_count(&self) -> usize {
229 self.sample_count
230 }
231
232 pub fn loss(&self) -> f32 {
233 self.loss
234 }
235
236 pub fn loss_fn(&self) -> Loss {
237 self.loss_fn
238 }
239}
240
241impl Debug for AccuracyResult {
242 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
243 if self.is_regression {
244 write!(
245 f,
246 "{:?} {{\n\tN: {:?} \n\tAccuracy: {:.2}%\n\tR² Score: {:.5}\n\tRMSE: {:.5}\n\tLoss ({:?}): {:.5}\n}}",
247 self.name,
248 self.sample_count,
249 self.accuracy * 100.0,
250 self.r_squared,
251 self.rmse,
252 self.loss_fn,
253 self.loss
254 )
255 } else {
256 write!(
257 f,
258 "{:?} {{\n\tN: {:?} \n\tAccuracy: {:.2}%\n\tPrecision: {:.2}%\n\tRecall: {:.2}%\n\tF1 Score: {:.2}%\n\tLoss ({:?}): {:.5}\n}}",
259 self.name,
260 self.sample_count,
261 self.accuracy * 100.0,
262 self.precision * 100.0,
263 self.recall * 100.0,
264 self.f1_score * 100.0,
265 self.loss_fn,
266 self.loss
267 )
268 }
269 }
270}
271
272impl<T: OpFloat> Eval<Graph<Op<T>>, Option<AccuracyResult>> for Accuracy<'_, T> {
273 fn eval(&self, graph: &Graph<Op<T>>) -> Option<AccuracyResult> {
274 let mut evaluator = GraphEvaluator::new(graph);
275 Some(self.calc(&mut evaluator))
276 }
277}
278
279impl<T: OpFloat> Eval<Tree<Op<T>>, Option<AccuracyResult>> for Accuracy<'_, T> {
280 fn eval(&self, tree: &Tree<Op<T>>) -> Option<AccuracyResult> {
281 Some(self.calc(&mut tree.clone()))
282 }
283}
284
285impl<T: OpFloat> Eval<Vec<Tree<Op<T>>>, Option<AccuracyResult>> for Accuracy<'_, T> {
286 fn eval(&self, trees: &Vec<Tree<Op<T>>>) -> Option<AccuracyResult> {
287 let mut cloned_trees = trees.clone();
288 Some(self.calc(&mut cloned_trees))
289 }
290}