sokuto_runtime/hierarchical/
calibration.rs1use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8
9use crate::error::{Result, RuntimeError};
10
11pub const EPSILON: f64 = 1e-12;
13
14pub const DEFAULT_ENTROPY_TEMP_GAMMA: f64 = 0.5;
16
17pub const DEFAULT_COARSE_POWER_ALPHA: f64 = 0.85;
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct DirichletCalibrator {
27 pub num_classes: usize,
29 pub weights: Vec<f64>,
31 pub bias: Vec<f64>,
33}
34
35impl DirichletCalibrator {
36 pub fn identity(num_classes: usize) -> Self {
40 let mut weights = vec![0.0; num_classes * num_classes];
41 for i in 0..num_classes {
42 weights[i * num_classes + i] = 1.0;
43 }
44 let bias = vec![0.0; num_classes];
45 Self {
46 num_classes,
47 weights,
48 bias,
49 }
50 }
51
52 pub fn new(num_classes: usize, weights: Vec<f64>, bias: Vec<f64>) -> Result<Self> {
59 if weights.len() != num_classes * num_classes {
60 return Err(RuntimeError::InvalidQuestion(format!(
61 "Dirichlet較正重み行列のサイズ ({}) が想定 ({}x{}={}) と一致しません。",
62 weights.len(),
63 num_classes,
64 num_classes,
65 num_classes * num_classes
66 )));
67 }
68 if bias.len() != num_classes {
69 return Err(RuntimeError::InvalidQuestion(format!(
70 "Dirichlet較正バイアスベクトルの長さ ({}) が想定 ({}) と一致しません。",
71 bias.len(),
72 num_classes
73 )));
74 }
75 Ok(Self {
76 num_classes,
77 weights,
78 bias,
79 })
80 }
81
82 pub fn calibrate(&self, probs: &[f64]) -> Result<Vec<f64>> {
86 if probs.len() != self.num_classes {
87 return Err(RuntimeError::InvalidQuestion(format!(
88 "入力確率ベクトルの長さ ({}) が較正器のクラス数 ({}) と一致しません。",
89 probs.len(),
90 self.num_classes
91 )));
92 }
93
94 let k = self.num_classes;
95 let mut log_p = Vec::with_capacity(k);
96 for &p in probs {
97 let clamped = p.max(EPSILON);
98 log_p.push(clamped.ln());
99 }
100
101 let mut logits = vec![0.0; k];
102 for (i, logit) in logits.iter_mut().enumerate() {
103 let mut sum = self.bias[i];
104 let row_offset = i * k;
105 for (j, &lp) in log_p.iter().enumerate() {
106 sum += self.weights[row_offset + j] * lp;
107 }
108 *logit = sum;
109 }
110
111 Ok(softmax_f64(&logits))
112 }
113}
114
115pub fn compute_normalized_entropy(probs: &[f64]) -> (f64, f64) {
123 let k = probs.len();
124 if k <= 1 {
125 return (0.0, 0.0);
126 }
127
128 let mut entropy = 0.0;
129 for &p in probs {
130 if p > EPSILON {
131 entropy -= p * p.ln();
132 }
133 }
134
135 let max_entropy = (k as f64).ln();
136 let normalized = if max_entropy > EPSILON {
137 (entropy / max_entropy).clamp(0.0, 1.0)
138 } else {
139 0.0
140 };
141
142 (entropy, normalized)
143}
144
145pub fn compute_fine_temperature(normalized_entropy: f64, gamma: f64) -> f64 {
152 (1.0 + gamma * normalized_entropy).max(1.0)
153}
154
155pub fn temperature_scaled_softmax(logits: &[f64], temperature: f64) -> Vec<f64> {
161 let temp = temperature.max(0.01);
162 let mut scaled = Vec::with_capacity(logits.len());
163 for &l in logits {
164 scaled.push(l / temp);
165 }
166 softmax_f64(&scaled)
167}
168
169pub fn softmax_f64(logits: &[f64]) -> Vec<f64> {
171 if logits.is_empty() {
172 return Vec::new();
173 }
174
175 let max_logit = logits
176 .iter()
177 .copied()
178 .fold(f64::NEG_INFINITY, |a, b| if a > b { a } else { b });
179
180 let mut exps = Vec::with_capacity(logits.len());
181 let mut sum = 0.0;
182 for &l in logits {
183 let e = (l - max_logit).exp();
184 exps.push(e);
185 sum += e;
186 }
187
188 if sum <= 0.0 || !sum.is_finite() {
189 let uniform = 1.0 / (logits.len() as f64);
190 return vec![uniform; logits.len()];
191 }
192
193 for e in &mut exps {
194 *e /= sum;
195 }
196 exps
197}
198
199pub fn combine_and_reconstruct_probabilities(
218 all_fine_keys: &[String],
219 fine_to_coarse: &IndexMap<String, String>,
220 coarse_probs: &IndexMap<String, f64>,
221 evaluated_fine_probs: &IndexMap<String, f64>,
222 coarse_alpha: f64,
223) -> Result<IndexMap<String, f64>> {
224 if all_fine_keys.is_empty() {
225 return Err(RuntimeError::InvalidQuestion(
226 "all_fine_keys が空です。".to_string(),
227 ));
228 }
229 if evaluated_fine_probs.is_empty() {
230 return Err(RuntimeError::InvalidQuestion(
231 "evaluated_fine_probs が空です。".to_string(),
232 ));
233 }
234
235 let mut raw_scores = IndexMap::with_capacity(evaluated_fine_probs.len());
237 let mut total_score = 0.0;
238
239 for (fine_key, &fine_prob) in evaluated_fine_probs {
240 let coarse_key = fine_to_coarse.get(fine_key).ok_or_else(|| {
241 RuntimeError::InvalidQuestion(format!(
242 "細分類 `{}` の親大分類が見つかりません。",
243 fine_key
244 ))
245 })?;
246
247 let coarse_prob = coarse_probs.get(coarse_key).copied().unwrap_or(0.0);
248 let attenuated_coarse = coarse_prob.max(EPSILON).powf(coarse_alpha);
249
250 let combined = attenuated_coarse * fine_prob.max(0.0);
251 raw_scores.insert(fine_key.clone(), combined);
252 total_score += combined;
253 }
254
255 let mut normalized_evaluated = IndexMap::with_capacity(evaluated_fine_probs.len());
257 if total_score > EPSILON && total_score.is_finite() {
258 for (k, s) in raw_scores {
259 normalized_evaluated.insert(k, s / total_score);
260 }
261 } else {
262 let uniform = 1.0 / (evaluated_fine_probs.len() as f64);
264 for k in evaluated_fine_probs.keys() {
265 normalized_evaluated.insert(k.clone(), uniform);
266 }
267 }
268
269 let mut full_probs = IndexMap::with_capacity(all_fine_keys.len());
271 let mut final_sum = 0.0;
272
273 for key in all_fine_keys {
274 let prob = normalized_evaluated.get(key).copied().unwrap_or(0.0);
275 full_probs.insert(key.clone(), prob);
276 final_sum += prob;
277 }
278
279 if (final_sum - 1.0).abs() > 1e-6 && final_sum > 0.0 {
281 for p in full_probs.values_mut() {
282 *p /= final_sum;
283 }
284 }
285
286 Ok(full_probs)
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn test_identity_calibrator() {
295 let cal = DirichletCalibrator::identity(3);
296 let probs = vec![0.6, 0.3, 0.1];
297 let calibrated = cal.calibrate(&probs).unwrap();
298
299 for (a, b) in probs.iter().zip(calibrated.iter()) {
300 assert!((a - b).abs() < 1e-4);
301 }
302 }
303
304 #[test]
305 fn test_entropy_computation() {
306 let certain = vec![1.0, 0.0, 0.0];
308 let (_, norm_certain) = compute_normalized_entropy(&certain);
309 assert!(norm_certain < 1e-5);
310
311 let uniform = vec![1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0];
313 let (_, norm_uniform) = compute_normalized_entropy(&uniform);
314 assert!((norm_uniform - 1.0).abs() < 1e-4);
315 }
316
317 #[test]
318 fn test_fine_temperature() {
319 let temp_certain = compute_fine_temperature(0.0, 0.5);
320 assert_eq!(temp_certain, 1.0);
321
322 let temp_uncertain = compute_fine_temperature(1.0, 0.5);
323 assert_eq!(temp_uncertain, 1.5);
324 }
325
326 #[test]
327 fn test_combine_and_reconstruct() {
328 let all_keys = vec![
329 "c1_f1".to_string(),
330 "c1_f2".to_string(),
331 "c2_f1".to_string(),
332 "c2_f2".to_string(),
333 "c3_f1".to_string(),
334 ];
335
336 let mut fine_to_coarse = IndexMap::new();
337 fine_to_coarse.insert("c1_f1".to_string(), "c1".to_string());
338 fine_to_coarse.insert("c1_f2".to_string(), "c1".to_string());
339 fine_to_coarse.insert("c2_f1".to_string(), "c2".to_string());
340 fine_to_coarse.insert("c2_f2".to_string(), "c2".to_string());
341 fine_to_coarse.insert("c3_f1".to_string(), "c3".to_string());
342
343 let mut coarse_probs = IndexMap::new();
344 coarse_probs.insert("c1".to_string(), 0.7);
345 coarse_probs.insert("c2".to_string(), 0.25);
346 coarse_probs.insert("c3".to_string(), 0.05);
347
348 let mut evaluated = IndexMap::new();
350 evaluated.insert("c1_f1".to_string(), 0.8);
351 evaluated.insert("c1_f2".to_string(), 0.2);
352
353 let full = combine_and_reconstruct_probabilities(
354 &all_keys,
355 &fine_to_coarse,
356 &coarse_probs,
357 &evaluated,
358 0.85,
359 )
360 .unwrap();
361
362 assert_eq!(full.len(), 5);
363 assert_eq!(full.get("c2_f1").copied().unwrap(), 0.0);
365 assert_eq!(full.get("c2_f2").copied().unwrap(), 0.0);
366 assert_eq!(full.get("c3_f1").copied().unwrap(), 0.0);
367
368 assert!(full.get("c1_f1").copied().unwrap() > full.get("c1_f2").copied().unwrap());
370
371 let sum: f64 = full.values().sum();
373 assert!((sum - 1.0).abs() < 1e-6);
374 }
375}