1use std::sync::Arc;
2
3use super::state::{FormatOptions, NumericMetricState};
4use super::{MetricMetadata, SerializedEntry};
5use crate::metric::{
6 Metric, MetricAttributes, MetricName, Numeric, NumericAttributes, NumericEntry,
7};
8use burn_core::tensor::{Int, Tensor, TensorReadError, activation::sigmoid};
9
10#[derive(Clone)]
12pub struct HammingScore {
13 name: MetricName,
14 state: NumericMetricState,
15 threshold: f32,
16 sigmoid: bool,
17}
18
19#[derive(new)]
21pub struct HammingScoreInput {
22 outputs: Tensor<2>,
23 targets: Tensor<2, Int>,
24}
25
26impl HammingScore {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 fn update_name(&mut self) {
33 self.name = Arc::new(format!("Hamming Score @ Threshold({})", self.threshold));
34 }
35
36 pub fn with_threshold(mut self, threshold: f32) -> Self {
38 self.threshold = threshold;
39 self.update_name();
40 self
41 }
42
43 pub fn with_sigmoid(mut self, sigmoid: bool) -> Self {
45 self.sigmoid = sigmoid;
46 self.update_name();
47 self
48 }
49}
50
51impl Default for HammingScore {
52 fn default() -> Self {
54 let threshold = 0.5;
55 let name = Arc::new(format!("Hamming Score @ Threshold({})", threshold));
56
57 Self {
58 name,
59 state: NumericMetricState::default(),
60 threshold,
61 sigmoid: false,
62 }
63 }
64}
65
66impl Metric for HammingScore {
67 type Input = HammingScoreInput;
68
69 fn update(
70 &mut self,
71 input: &HammingScoreInput,
72 _metadata: &MetricMetadata,
73 ) -> Result<SerializedEntry, TensorReadError> {
74 let [batch_size, _n_classes] = input.outputs.dims();
75
76 let targets = input.targets.clone();
77
78 let mut outputs = input.outputs.clone();
79
80 if self.sigmoid {
81 outputs = sigmoid(outputs);
82 }
83
84 let score = outputs
85 .greater_scalar(self.threshold)
86 .equal(targets.bool())
87 .float()
88 .mean()
89 .try_into_scalar::<f64>()?;
90
91 self.state.update(100.0 * score, batch_size);
92 Ok(self
93 .state
94 .compute_update(FormatOptions::new(self.name()).unit("%").precision(2)))
95 }
96
97 fn compute(&mut self) -> Result<SerializedEntry, TensorReadError> {
98 Ok(self
99 .state
100 .compute_final(FormatOptions::new(self.name()).unit("%").precision(2)))
101 }
102
103 fn clear(&mut self) {
104 self.state.reset()
105 }
106
107 fn name(&self) -> MetricName {
108 self.name.clone()
109 }
110
111 fn attributes(&self) -> MetricAttributes {
112 NumericAttributes {
113 unit: Some("%".to_string()),
114 higher_is_better: true,
115 }
116 .into()
117 }
118}
119
120impl Numeric for HammingScore {
121 fn value(&self) -> Option<NumericEntry> {
122 Some(self.state.current_value())
123 }
124
125 fn running_value(&self) -> Option<NumericEntry> {
126 Some(self.state.running_value())
127 }
128
129 fn final_value(&self) -> NumericEntry {
130 self.state.final_value()
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_hamming_score() {
140 let device = Default::default();
141 let mut metric = HammingScore::new();
142
143 let x = Tensor::from_data(
144 [
145 [0.32, 0.52, 0.38, 0.68, 0.61], [0.43, 0.31, 0.21, 0.63, 0.53], [0.44, 0.25, 0.71, 0.39, 0.73], [0.49, 0.37, 0.68, 0.39, 0.31], ],
150 &device,
151 );
152 let y = Tensor::from_data(
153 [
154 [0, 1, 0, 1, 1],
155 [0, 0, 0, 1, 1],
156 [0, 0, 1, 0, 1],
157 [0, 0, 1, 0, 0],
158 ],
159 &device,
160 );
161
162 let _entry = metric
163 .update(
164 &HammingScoreInput::new(x.clone(), y.clone()),
165 &MetricMetadata::fake(),
166 )
167 .unwrap();
168 assert_eq!(100.0, metric.value().unwrap().current());
169
170 let y = y.neg().add_scalar(1);
172 let _entry = metric
173 .update(
174 &HammingScoreInput::new(x.clone(), y), &MetricMetadata::fake(),
176 )
177 .unwrap();
178 assert_eq!(0.0, metric.value().unwrap().current());
179
180 let y = Tensor::from_data(
182 [
183 [0, 1, 1, 0, 1],
184 [0, 0, 0, 0, 1],
185 [0, 0, 0, 0, 1],
186 [0, 1, 1, 0, 0],
187 ],
188 &device,
189 );
190 let _entry = metric
191 .update(
192 &HammingScoreInput::new(x, y), &MetricMetadata::fake(),
194 )
195 .unwrap();
196 assert_eq!(75.0, metric.value().unwrap().current());
197 }
198
199 #[test]
200 fn test_parameterized_unique_name() {
201 let metric_a = HammingScore::new().with_threshold(0.5);
202 let metric_b = HammingScore::new().with_threshold(0.75);
203 let metric_c = HammingScore::new().with_threshold(0.5);
204
205 assert_ne!(metric_a.name(), metric_b.name());
206 assert_eq!(metric_a.name(), metric_c.name());
207 }
208}