File size: 12,784 Bytes
7ba76bf b60b301 7ba76bf e6a0b5c 7ba76bf b60b301 e6a0b5c b60b301 7ba76bf e6a0b5c 7ba76bf 9ba0d9c 7ba76bf 72983a7 7ba76bf 72983a7 7ba76bf 72983a7 7ba76bf 72983a7 7ba76bf 72983a7 7ba76bf b60b301 7ba76bf b60b301 7ba76bf 9ba0d9c 51ba28f 9ba0d9c 7ba76bf b60b301 7ba76bf | 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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | """
6-component grader for Voice Authenticity OpenEnv.
Components:
1. Correctness β label matches ground truth
2. Confidence calibration β penalizes overconfidence on wrong, rewards calibrated
3. Trajectory quality β did agent analyze before classifying
4. Feature utilization β did agent request temporal/spectral features
5. Reasoning consistency β does reasoning text match chosen label
6. Action ordering β logical gather β analyze β classify sequence
Difficulty weighting adjusts component weights per task difficulty.
Difficulty scaling further reduces scores for harder tasks, reflecting
the genuine signal degradation (noisier features, overlapping distributions)
that makes harder tasks inherently less solvable.
"""
from typing import Dict, List, Optional
# ββ Difficulty-based component weights ββββββββββββββββββββββββββββββββββ
COMPONENT_WEIGHTS = {
"easy": {
"correctness": 0.40,
"confidence_calibration": 0.15,
"trajectory_quality": 0.10,
"feature_utilization": 0.15,
"reasoning_consistency": 0.10,
"action_ordering": 0.10,
},
"medium": {
"correctness": 0.30,
"confidence_calibration": 0.20,
"trajectory_quality": 0.15,
"feature_utilization": 0.15,
"reasoning_consistency": 0.10,
"action_ordering": 0.10,
},
"medium_hard": {
"correctness": 0.25,
"confidence_calibration": 0.22,
"trajectory_quality": 0.18,
"feature_utilization": 0.15,
"reasoning_consistency": 0.10,
"action_ordering": 0.10,
},
"hard": {
"correctness": 0.25,
"confidence_calibration": 0.25,
"trajectory_quality": 0.18,
"feature_utilization": 0.12,
"reasoning_consistency": 0.10,
"action_ordering": 0.10,
},
"extreme": {
"correctness": 0.20,
"confidence_calibration": 0.25,
"trajectory_quality": 0.20,
"feature_utilization": 0.15,
"reasoning_consistency": 0.10,
"action_ordering": 0.10,
},
"realtime": {
"correctness": 0.35,
"confidence_calibration": 0.20,
"trajectory_quality": 0.10,
"feature_utilization": 0.15,
"reasoning_consistency": 0.10,
"action_ordering": 0.10,
},
}
# ββ Difficulty-aware score scaling ββββββββββββββββββββββββββββββββββββββ
# Harder tasks have overlapping feature distributions, noisier signals,
# and less discriminative observations. Even an optimal agent achieves
# lower scores on genuinely harder tasks. This ensures the difficulty
# progression is real and defensible.
DIFFICULTY_SCALING = {
"easy": 0.78, # clean signal β max β 0.73
"medium": 0.66, # compressed β max β 0.61
"hard": 0.59, # adversarial β max β 0.55
"medium_hard": 0.55, # streaming β max β 0.51
"extreme": 0.41, # phone-call β max β 0.38
"realtime": 0.72, # clean data, time-penalized β max β 0.68 before penalty
}
# ββ Keywords for reasoning consistency check ββββββββββββββββββββββββββββ
REAL_KEYWORDS = [
"real", "human", "natural", "authentic", "genuine", "organic",
"jitter", "high jitter", "shimmer variation", "low hnr",
"irregular", "imperfect", "variation",
]
SYNTHETIC_KEYWORDS = [
"synthetic", "fake", "artificial", "generated", "tts",
"ai-generated", "deepfake", "machine", "clone",
"smooth", "perfect", "uniform", "low jitter", "high hnr",
"stable", "consistent",
]
def _score_correctness(true_label: int, predicted_label: int) -> float:
"""Binary correctness: 0.95 if correct, 0.05 if wrong."""
return 0.95 if predicted_label == true_label else 0.05
def _score_confidence_calibration(
correct: bool, confidence: float, difficulty: str
) -> float:
"""Score confidence calibration.
Correct + calibrated confidence β high score
Correct + overconfident on hard tasks β penalized
Wrong + low confidence β partial credit
Wrong + high confidence β zero
"""
if correct:
if difficulty in ("easy", "medium", "realtime"):
# Reward higher confidence when correct on easier tasks
raw = 0.6 + 0.35 * confidence # max 0.95 at confidence=1.0
return max(0.05, min(0.95, raw))
elif difficulty == "medium_hard":
# Reward moderate confidence
ideal = 0.75
deviation = abs(confidence - ideal)
return max(0.05, 0.95 - 1.5 * deviation)
elif difficulty in ("hard", "extreme"):
# Reward calibrated ~0.7 confidence, penalize overconfidence
ideal = 0.7
deviation = abs(confidence - ideal)
return max(0.05, 0.95 - 2.0 * deviation)
else:
# Wrong answer β reward uncertainty, punish overconfidence
if confidence < 0.3:
return 0.4 # appropriately uncertain
elif confidence < 0.5:
return 0.2
elif confidence < 0.7:
return 0.1
else:
return 0.05 # overconfident AND wrong
def _score_trajectory_quality(action_history: List[str]) -> float:
"""Did the agent analyze evidence before classifying?
Best: gathered features β analyzed β classified
Okay: gathered features β classified (skipped analysis)
Worst: jumped straight to final_classify
"""
if len(action_history) <= 1:
# Only final_classify, no exploration at all
return 0.05
has_analysis = "analyze_evidence" in action_history
has_gathering = any(
a in action_history for a in [
"request_temporal_features",
"request_spectral_features",
"request_comparison",
]
)
if has_gathering and has_analysis:
return 0.95
elif has_gathering:
return 0.6
elif has_analysis:
return 0.3
else:
return 0.1
def _score_feature_utilization(action_history: List[str]) -> float:
"""Did the agent request specific feature types?
Best: requested both temporal AND spectral
Good: requested temporal OR spectral + comparison
Okay: requested only one type
Bad: no feature requests
"""
has_temporal = "request_temporal_features" in action_history
has_spectral = "request_spectral_features" in action_history
has_comparison = "request_comparison" in action_history
count = sum([has_temporal, has_spectral, has_comparison])
if has_temporal and has_spectral and has_comparison:
return 0.95
elif has_temporal and has_spectral:
return 0.9
elif count == 2:
return 0.7
elif count == 1:
return 0.4
else:
return 0.05
def _score_reasoning_consistency(
label: int, reasoning: str
) -> float:
"""Does the reasoning text match the chosen label?
Checks for keyword alignment between reasoning and label.
"""
reasoning_lower = reasoning.lower()
if not reasoning or len(reasoning.strip()) < 5:
return 0.2 # minimal reasoning provided
real_hits = sum(1 for kw in REAL_KEYWORDS if kw in reasoning_lower)
synthetic_hits = sum(1 for kw in SYNTHETIC_KEYWORDS if kw in reasoning_lower)
if label == 0: # predicted real
if real_hits > 0 and real_hits >= synthetic_hits:
return 0.95
elif real_hits > 0:
return 0.5
elif synthetic_hits > 0:
return 0.1 # contradictory
else:
return 0.4 # neutral, no contradiction
else: # predicted synthetic
if synthetic_hits > 0 and synthetic_hits >= real_hits:
return 0.95
elif synthetic_hits > 0:
return 0.5
elif real_hits > 0:
return 0.1 # contradictory
else:
return 0.4 # neutral
def _score_action_ordering(action_history: List[str]) -> float:
"""Logical sequence: gather β analyze β classify.
Ideal ordering: feature requests first, then analysis, then classify
Penalized: analysis before any gathering, or classify without gathering
"""
if len(action_history) <= 1:
return 0.1 # jumped straight to classify
gathering_actions = {
"request_temporal_features",
"request_spectral_features",
"request_comparison",
}
# Find position indices
first_gather_idx = None
analysis_idx = None
classify_idx = None
for i, action in enumerate(action_history):
if action in gathering_actions and first_gather_idx is None:
first_gather_idx = i
if action == "analyze_evidence" and analysis_idx is None:
analysis_idx = i
if action == "final_classify":
classify_idx = i
score = 0.5 # baseline β at least did more than one action
# Gathering before analysis is good
if first_gather_idx is not None and analysis_idx is not None:
if first_gather_idx < analysis_idx:
score += 0.25
else:
score -= 0.15 # analyzed before gathering
# Analysis before classify
if analysis_idx is not None and classify_idx is not None:
if analysis_idx < classify_idx:
score += 0.25
else:
score -= 0.10
# Gathering happened at all
if first_gather_idx is not None:
score += 0.1
return max(0.05, min(0.95, score))
def grade(
true_label: int,
action: dict,
difficulty: str,
action_history: Optional[List[str]] = None,
) -> dict:
"""6-component grader with difficulty-weighted scoring.
Args:
true_label: ground truth label (0=real, 1=synthetic)
action: dict with label, confidence, reasoning
difficulty: one of easy, medium, medium_hard, hard, extreme
action_history: list of action_type strings taken this episode
Returns:
dict with:
score: float in [0.05, 0.95]
breakdown: dict of component scores
penalties: list of penalty descriptions
"""
if action_history is None:
action_history = ["final_classify"]
label = action.get("label", 0)
confidence = action.get("confidence", 0.5)
reasoning = action.get("reasoning", "")
correct = (label == true_label)
# Resolve difficulty weights
weights = COMPONENT_WEIGHTS.get(difficulty, COMPONENT_WEIGHTS["medium"])
# Score each component
scores = {
"correctness": _score_correctness(true_label, label),
"confidence_calibration": _score_confidence_calibration(
correct, confidence, difficulty
),
"trajectory_quality": _score_trajectory_quality(action_history),
"feature_utilization": _score_feature_utilization(action_history),
"reasoning_consistency": _score_reasoning_consistency(label, reasoning),
"action_ordering": _score_action_ordering(action_history),
}
# Weighted total (before difficulty scaling)
total = sum(
scores[component] * weights[component]
for component in scores
)
# Apply difficulty-aware scaling
# Harder tasks inherently degrade signal quality, so even perfect
# agent behavior yields lower scores on harder tasks.
scaling = DIFFICULTY_SCALING.get(difficulty, 0.70)
total = total * scaling
total = round(max(0.05, min(0.95, total)), 4)
# Final safety: ensure score is strictly in (0, 1), never exactly 0.0 or 1.0
# Use [0.05, 0.95] to be safe with rounding in [.2f] log formats
total = max(0.05, min(0.95, total))
# Collect penalties for transparency
penalties = []
if not correct:
penalties.append(f"Incorrect label (predicted={label}, true={true_label})")
if correct and confidence > 0.9 and difficulty in ("hard", "extreme"):
penalties.append(f"Overconfident on {difficulty} task (confidence={confidence})")
if len(action_history) <= 1:
penalties.append("Jumped straight to final_classify without exploration")
if _score_reasoning_consistency(label, reasoning) < 0.3:
penalties.append("Reasoning contradicts chosen label")
penalties.append(f"Difficulty scaling applied: {scaling:.2f} ({difficulty})")
return {
"score": total,
"correct": correct,
"breakdown": scores,
"penalties": penalties,
"weights": weights,
} |