Spaces:
Running on Zero
Running on Zero
File size: 8,271 Bytes
127b976 | 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 | from __future__ import annotations
from typing import Any, Dict, List
from .evidence import build_evidence_packet
from .labels import ID_TO_EXPLANATION
def decision_from_probability(
fake_probability: float,
real_threshold: float = 0.35,
fake_threshold: float = 0.85,
suspicious_segments: List[Dict[str, float]] | None = None,
suspicious_window_threshold: float = 0.75,
) -> str:
"""Safer decision gate for real-world speech.
Old/simple logic: fake_prob >= 0.65 => likely fake.
New logic: fake_prob must be very high, otherwise suspicious/human review.
This reduces false accusations on real accented/interview/noisy speech.
"""
suspicious_segments = suspicious_segments or []
if fake_probability < real_threshold:
return "likely real"
# If probability is not very high, avoid a hard fake decision.
if fake_probability < fake_threshold:
return "suspicious / human review"
# If very high probability, still check if there is some consistent evidence.
strong_windows = [
s for s in suspicious_segments
if float(s.get("fake_score", 0.0)) >= suspicious_window_threshold
]
# If no localization evidence is available, allow likely fake based on high utterance score.
if not suspicious_segments:
return "likely fake"
# If localization evidence exists and at least one strong window agrees, likely fake.
if strong_windows:
return "likely fake"
# High utterance score but weak/unclear window evidence: safer to review.
return "suspicious / human review"
def beginner_friendly_explanation(
fake_prob: float,
attack_type: str,
suspicious_segments: List[Dict[str, float]] | None = None,
) -> str:
suspicious_segments = suspicious_segments or []
if fake_prob < 0.35:
return (
"The model thinks this audio is likely real because the fake score is low. "
"The speech patterns look closer to real human speech examples than to synthetic or manipulated speech."
)
if fake_prob < 0.85:
return (
"The model is not fully sure. The fake score is not low, but it is also not safe enough to call the audio fake. "
"This should be treated as suspicious and reviewed by a human, especially if the clip contains an accent, interview speech, noise, or recording-quality differences."
)
if attack_type == "tts_vc":
reason = (
"The model thinks this audio may be fake because it contains patterns similar to text-to-speech "
"or voice-conversion examples from training."
)
elif attack_type == "codec":
reason = (
"The model thinks this audio may be fake because it contains patterns often linked to codec-based generated speech."
)
elif attack_type == "partial":
reason = (
"The model thinks only part of the audio may be manipulated because suspicious evidence appears in specific time regions."
)
elif attack_type == "bonafide":
reason = (
"The fake score is high, but the attack-type head predicts bonafide speech. "
"This conflict means the safest decision is human review rather than automatic accusation."
)
else:
reason = "The model found strong fake-like patterns in the audio."
if suspicious_segments:
top = max(suspicious_segments, key=lambda x: float(x.get("fake_score", 0.0)))
time_reason = (
f" The strongest suspicious part is around {float(top['start']):.1f}s to {float(top['end']):.1f}s, "
f"with a fake score of {float(top['fake_score']):.2f}."
)
else:
time_reason = " The evidence appears to affect the whole clip rather than one clear small region."
return reason + time_reason
def explanation_text(
fake_probability: float,
attack_type: str,
explanation_id: int,
suspicious_segments: List[Dict[str, float]] | None = None,
) -> List[str]:
suspicious_segments = suspicious_segments or []
explanation_name = ID_TO_EXPLANATION.get(int(explanation_id), "unknown")
reasons: List[str] = []
if fake_probability >= 0.85:
reasons.append("The shared AASIST-based representation produced very strong utterance-level fake evidence.")
elif fake_probability >= 0.65:
reasons.append("The shared AASIST-based representation produced moderate-to-strong fake evidence, but this is kept in the human-review range for real-world speech.")
elif fake_probability >= 0.35:
reasons.append("The fake probability is in the uncertain range, so the audio should be treated as suspicious rather than automatically fake.")
else:
reasons.append("The full-audio fake probability is low, so no strong utterance-level fake evidence was detected.")
if attack_type == "codec":
reasons.append("The attack-type head predicts a codec-based fake category; this is treated as one clue, not standalone codec source tracing.")
elif attack_type == "partial":
reasons.append("The attack-type head predicts localized or partially manipulated speech.")
elif attack_type == "tts_vc":
reasons.append("The attack-type head predicts a text-to-speech or voice-conversion style spoof.")
elif attack_type == "bonafide":
reasons.append("The attack-type head predicts bonafide speech.")
if suspicious_segments:
best = max(suspicious_segments, key=lambda x: float(x.get("fake_score", 0.0)))
reasons.append(
f"Sliding-window inference found suspicious evidence, with the strongest region at "
f"{best['start']}s–{best['end']}s and window fake score {float(best['fake_score']):.2f}."
)
else:
reasons.append("Sliding-window inference did not find a window above the suspicious threshold.")
if explanation_name == "codec_based_artifact":
reasons.append("The explanation head groups the evidence as codec-related artifacts.")
elif explanation_name == "localized_partial_manipulation":
reasons.append("The explanation head groups the evidence as localized partial manipulation.")
elif explanation_name == "global_synthetic_artifact":
reasons.append("The explanation head groups the evidence as global synthetic speech artifacts.")
elif explanation_name == "no_strong_fake_evidence":
reasons.append("The explanation head predicts the no-strong-fake-evidence category.")
return reasons
def build_report(
fake_probability: float,
attack_type: str,
explanation_id: int,
suspicious_segments: List[Dict[str, float]],
real_threshold: float = 0.35,
fake_threshold: float = 0.85,
suspicious_window_threshold: float = 0.75,
) -> Dict[str, Any]:
decision = decision_from_probability(
fake_probability,
real_threshold=real_threshold,
fake_threshold=fake_threshold,
suspicious_segments=suspicious_segments,
suspicious_window_threshold=suspicious_window_threshold,
)
explanation_category = ID_TO_EXPLANATION.get(int(explanation_id), "unknown")
evidence = build_evidence_packet(
fake_probability=fake_probability,
attack_type=attack_type,
explanation_category=explanation_category,
suspicious_segments=suspicious_segments,
thresholds={
"real": real_threshold,
"fake": fake_threshold,
"suspicious_window": suspicious_window_threshold,
},
)
return {
"decision": decision,
"fake_probability": round(float(fake_probability), 4),
"attack_type": attack_type,
"suspicious_segments": suspicious_segments,
"explanation_category": explanation_category,
"explanation": explanation_text(fake_probability, attack_type, explanation_id, suspicious_segments),
"beginner_explanation": beginner_friendly_explanation(fake_probability, attack_type, suspicious_segments),
"evidence": evidence,
}
|