1use super::cer::edit_distance;
2use super::state::{FormatOptions, NumericMetricState};
3use super::{MetricMetadata, SerializedEntry};
4use crate::metric::{
5 Metric, MetricAttributes, MetricName, Numeric, NumericAttributes, NumericEntry,
6};
7use burn_core::tensor::{Int, Tensor, TensorReadError};
8use std::sync::Arc;
9
10#[derive(Clone)]
16pub struct WordErrorRate {
17 name: MetricName,
18 state: NumericMetricState,
19 pad_token: Option<usize>,
20}
21
22#[derive(new)]
24pub struct WerInput {
25 pub outputs: Tensor<2, Int>,
27 pub targets: Tensor<2, Int>,
29}
30impl Default for WordErrorRate {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl WordErrorRate {
37 pub fn new() -> Self {
39 Self {
40 name: Arc::new("WER".to_string()),
41 state: NumericMetricState::default(),
42 pad_token: None,
43 }
44 }
45
46 pub fn with_pad_token(mut self, index: usize) -> Self {
48 self.pad_token = Some(index);
49 self
50 }
51}
52
53impl Metric for WordErrorRate {
54 type Input = WerInput;
55
56 fn update(
57 &mut self,
58 input: &WerInput,
59 _metadata: &MetricMetadata,
60 ) -> Result<SerializedEntry, TensorReadError> {
61 let outputs = input.outputs.clone();
62 let targets = input.targets.clone();
63 let [batch_size, seq_len] = targets.dims();
64
65 let outputs_data = outputs.try_into_vec_as::<i32>()?;
66 let targets_data = targets.try_into_vec_as::<i32>()?;
67
68 let pad_token = self.pad_token.map(|p| p as i32);
69
70 let mut total_edit_distance = 0.0;
71 let mut total_target_length = 0usize;
72
73 for i in 0..batch_size {
75 let start = i * seq_len;
76 let end = (i + 1) * seq_len;
77 let output_seq = &outputs_data[start..end];
78 let target_seq = &targets_data[start..end];
79
80 let (ed, target_len) = match pad_token {
83 Some(pad) => {
84 let output_seq_no_pad = output_seq
85 .iter()
86 .take_while(|&&x| x != pad)
87 .copied()
88 .collect::<Vec<_>>();
89
90 let target_seq_no_pad = target_seq
91 .iter()
92 .take_while(|&&x| x != pad)
93 .copied()
94 .collect::<Vec<_>>();
95
96 (
97 edit_distance(&target_seq_no_pad, &output_seq_no_pad),
98 target_seq_no_pad.len(),
99 )
100 }
101 None => (edit_distance(target_seq, output_seq), target_seq.len()),
102 };
103
104 total_edit_distance += ed as f64;
105 total_target_length += target_len;
106 }
107
108 let value = if total_target_length > 0 {
110 100.0 * total_edit_distance / total_target_length as f64
111 } else {
112 0.0
113 };
114
115 self.state.update(value, total_target_length);
116 Ok(self
117 .state
118 .compute_update(FormatOptions::new(self.name()).unit("%").precision(2)))
119 }
120
121 fn compute(&mut self) -> Result<SerializedEntry, TensorReadError> {
122 Ok(self
123 .state
124 .compute_final(FormatOptions::new(self.name()).unit("%").precision(2)))
125 }
126
127 fn name(&self) -> MetricName {
128 self.name.clone()
129 }
130
131 fn clear(&mut self) {
132 self.state.reset();
133 }
134
135 fn attributes(&self) -> MetricAttributes {
136 NumericAttributes {
137 unit: Some("%".to_string()),
138 higher_is_better: false,
139 }
140 .into()
141 }
142}
143
144impl Numeric for WordErrorRate {
145 fn value(&self) -> Option<NumericEntry> {
146 Some(self.state.current_value())
147 }
148
149 fn running_value(&self) -> Option<NumericEntry> {
150 Some(self.state.running_value())
151 }
152
153 fn final_value(&self) -> NumericEntry {
154 self.state.final_value()
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
164 fn test_wer_without_padding() {
165 let device = Default::default();
166 let mut metric = WordErrorRate::new();
167
168 let preds = Tensor::from_data([[1, 2], [3, 4]], &device);
170 let tgts = Tensor::from_data([[1, 2], [3, 4]], &device);
171
172 metric
173 .update(&WerInput::new(preds, tgts), &MetricMetadata::fake())
174 .unwrap();
175
176 assert_eq!(0.0, metric.value().unwrap().current());
177 }
178
179 #[test]
181 fn test_wer_without_padding_two_errors() {
182 let device = Default::default();
183 let mut metric = WordErrorRate::new();
184
185 let preds = Tensor::from_data([[1, 2], [3, 5]], &device);
189 let tgts = Tensor::from_data([[1, 3], [3, 4]], &device);
190
191 metric
192 .update(&WerInput::new(preds, tgts), &MetricMetadata::fake())
193 .unwrap();
194
195 assert_eq!(50.0, metric.value().unwrap().current());
197 }
198
199 #[test]
201 fn test_wer_with_padding() {
202 let device = Default::default();
203 let pad = 9_i64;
204 let mut metric = WordErrorRate::new().with_pad_token(pad as usize);
205
206 let preds = Tensor::from_data([[1, 2, pad], [3, 5, pad]], &device);
210 let tgts = Tensor::from_data([[1, 3, pad], [3, 4, pad]], &device);
211
212 metric
213 .update(&WerInput::new(preds, tgts), &MetricMetadata::fake())
214 .unwrap();
215 assert_eq!(50.0, metric.value().unwrap().current());
216 }
217
218 #[test]
220 fn test_clear_resets_state() {
221 let device = Default::default();
222 let mut metric = WordErrorRate::new();
223
224 let preds = Tensor::from_data([[1, 2]], &device);
225 let tgts = Tensor::from_data([[1, 3]], &device); metric
228 .update(
229 &WerInput::new(preds.clone(), tgts.clone()),
230 &MetricMetadata::fake(),
231 )
232 .unwrap();
233 assert!(metric.value().unwrap().current() > 0.0);
234
235 metric.clear();
236 assert!(metric.value().unwrap().current().is_nan());
237 }
238}