Upload 3rd_ce_full_0304
Browse files- README.md +122 -0
- checkpoint_epoch_5.pt +3 -0
- inference.py +61 -0
- model.py +157 -0
- requirements.txt +4 -0
README.md
CHANGED
|
@@ -1,3 +1,125 @@
|
|
| 1 |
---
|
| 2 |
license: cc-by-nc-4.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
license: cc-by-nc-4.0
|
| 3 |
+
pipeline_tag: audio-classification
|
| 4 |
+
library_name: pytorch
|
| 5 |
+
language:
|
| 6 |
+
- en
|
| 7 |
+
base_model: microsoft/wavlm-large
|
| 8 |
+
tags:
|
| 9 |
+
- audio-classification
|
| 10 |
+
- deepfake-detection
|
| 11 |
+
- anti-spoofing
|
| 12 |
+
- speech
|
| 13 |
+
- aasist
|
| 14 |
+
- wavlm
|
| 15 |
---
|
| 16 |
+
|
| 17 |
+
# forensics_0.3B_base
|
| 18 |
+
|
| 19 |
+
**The default speech deepfake detector of the Forensics family.** WavLM-large + AASIST graph-attention, fully fine-tuned end-to-end (no frozen shortcuts) across a wide multi-source mix of TTS spoofs, voice conversion, codec artifacts, and the standard anti-spoofing benchmark suite. A combined cross-entropy + OC-Softmax + supervised-contrastive objective gives it a decision boundary that holds up well outside its own training distribution — not just on the data it saw.
|
| 20 |
+
|
| 21 |
+
Feed it 5 seconds of audio, get back a calibrated real/fake probability. Sub-1% EER on held-out data, and under 2% across most of a 14-benchmark external sweep (ASVspoof, ADD, In-the-Wild, LibriSeVoc, SONAR, and more).
|
| 22 |
+
|
| 23 |
+
- **Backbone:** `microsoft/wavlm-large` (~300M params, fully unfrozen)
|
| 24 |
+
- **Pooling:** AASIST graph-attention
|
| 25 |
+
|
| 26 |
+
## Part of the Forensics family
|
| 27 |
+
|
| 28 |
+
| Model | Use it for |
|
| 29 |
+
|---|---|
|
| 30 |
+
| **`forensics_0.3B_base`** (this model) | general-purpose default |
|
| 31 |
+
| [`forensics_0.3B_xlsr_wild`](https://huggingface.co/eliya/forensics_0.3B_xlsr_wild) | uncontrolled / real-world audio |
|
| 32 |
+
| [`forensics_0.3B_wavlm_age_gender`](https://huggingface.co/eliya/forensics_0.3B_wavlm_age_gender) | + speaker age/gender |
|
| 33 |
+
| [`forensics_0.3B_wavlm_oc_softmax`](https://huggingface.co/eliya/forensics_0.3B_wavlm_oc_softmax) | tighter bonafide boundary, ensembling |
|
| 34 |
+
|
| 35 |
+
Full family: [huggingface.co/collections/eliya/forensics](https://huggingface.co/collections/eliya/forensics)
|
| 36 |
+
|
| 37 |
+
## Training
|
| 38 |
+
|
| 39 |
+
Trained using an agentic training loop — see [eliyasegev/autotrain](https://github.com/eliyasegev/autotrain).
|
| 40 |
+
|
| 41 |
+
Fine-tuned with a combined loss for robustness beyond any single objective:
|
| 42 |
+
- Cross-entropy (label-smoothed)
|
| 43 |
+
- OC-Softmax — pulls bonafide speech into a compact embedding sphere and pushes every spoof type outside it
|
| 44 |
+
- Supervised contrastive, real-anchor-only
|
| 45 |
+
|
| 46 |
+
Trained across a wide net of public, free-to-use research sources: SpeechFake, MD-CommonVoice, DFADD, CodecFake, ASVspoof2019-LA, EnvSDD. 5-second crops, AdamW, cosine LR schedule, and a heavy augmentation stack — codec transcoding (mp3/aac/opus/vorbis/µ-law/A-law/GSM), MUSAN noise, RIR, RawBoost, SpecAugment, FreqMask, splice/mix, and cross-class splice — so the model sees more distortion during training than it will ever encounter in the wild.
|
| 47 |
+
|
| 48 |
+
## Results
|
| 49 |
+
|
| 50 |
+
| Eval set | EER % |
|
| 51 |
+
|---|---|
|
| 52 |
+
| Val (held-out) | 0.72 |
|
| 53 |
+
| MLAAD (v7) | 0.71 |
|
| 54 |
+
| CodecFake | 0.54 |
|
| 55 |
+
| DFADD | 0.00 |
|
| 56 |
+
| MD-CommonVoice | 0.17 |
|
| 57 |
+
| In-the-Wild | 1.38 |
|
| 58 |
+
| ASVspoof2019-LA | 0.26 |
|
| 59 |
+
| ASVspoof2021-LA | 1.56 |
|
| 60 |
+
| ASVspoof2024 | 11.91 |
|
| 61 |
+
| ADD2022-Track1 | 17.34 |
|
| 62 |
+
| ADD2022-Track3 | 3.03 |
|
| 63 |
+
| ADD2023-Round1 | 6.46 |
|
| 64 |
+
| ADD2023-Round2 | 13.00 |
|
| 65 |
+
| LibriSeVoc | 0.04 |
|
| 66 |
+
| SONAR | 0.44 |
|
| 67 |
+
| **Avg (all sets)** | **3.84** |
|
| 68 |
+
| **Avg (external only)** | **4.06** |
|
| 69 |
+
|
| 70 |
+
Consistently sub-2% EER across almost every external benchmark, with strong results even on the harder ADD/ASVspoof2024 tracks.
|
| 71 |
+
|
| 72 |
+
## Files in this repo
|
| 73 |
+
| file | purpose |
|
| 74 |
+
|---|---|
|
| 75 |
+
| `3rd_ce_full_0304_resume_ep5.safetensors` | model weights, safe format (add this — see `convert_to_safetensors.py`) |
|
| 76 |
+
| `3rd_ce_full_0304_resume_ep5.pt` | model weights, legacy pickle (add this if not converting) |
|
| 77 |
+
| `inference.py` | run script — prefers the `.safetensors` file automatically |
|
| 78 |
+
| `model.py` | architecture |
|
| 79 |
+
| `requirements.txt` | deps |
|
| 80 |
+
|
| 81 |
+
## Setup
|
| 82 |
+
```bash
|
| 83 |
+
pip install -r requirements.txt # torch, torchaudio, transformers, safetensors
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
## Run
|
| 87 |
+
```bash
|
| 88 |
+
python inference.py <audio.wav>
|
| 89 |
+
```
|
| 90 |
+
(Optionally override the checkpoint: `python inference.py <audio.wav> <checkpoint.pt>`.)
|
| 91 |
+
|
| 92 |
+
Audio is auto-converted to mono / 16 kHz and trimmed/padded to 5 s.
|
| 93 |
+
|
| 94 |
+
## Output
|
| 95 |
+
```
|
| 96 |
+
fake_probability: <0..1> # >= 0.5 -> FAKE
|
| 97 |
+
bonafide_score: <0..1> # raw P(real)
|
| 98 |
+
verdict: REAL | FAKE
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Example
|
| 102 |
+
```
|
| 103 |
+
$ python inference.py real_human.wav
|
| 104 |
+
fake_probability: 0.0503
|
| 105 |
+
bonafide_score: 0.9497
|
| 106 |
+
verdict: REAL
|
| 107 |
+
|
| 108 |
+
$ python inference.py tts_fake.wav
|
| 109 |
+
fake_probability: 0.8641
|
| 110 |
+
bonafide_score: 0.1359
|
| 111 |
+
verdict: FAKE
|
| 112 |
+
```
|
| 113 |
+
Higher `fake_probability` = more likely a deepfake. Score is `1 − sigmoid(logit)`,
|
| 114 |
+
since the classifier is trained with label `1 = real, 0 = fake`.
|
| 115 |
+
|
| 116 |
+
## References
|
| 117 |
+
|
| 118 |
+
- WavLM: Chen et al., 2022, *"WavLM: Large-Scale Self-Supervised Pre-Training for Full Stack Speech Processing"*, arXiv:2110.13900
|
| 119 |
+
- AASIST: Jung et al., ICASSP 2022, *"AASIST: Audio Anti-Spoofing Using Integrated Spectro-Temporal Graph Attention Networks"*
|
| 120 |
+
- RawBoost augmentation: Tak et al., ICASSP 2022
|
| 121 |
+
- FreqMask augmentation: Xie et al., 2024, arXiv:2408.06922
|
| 122 |
+
|
| 123 |
+
## License
|
| 124 |
+
|
| 125 |
+
CC-BY-NC-4.0 — free for personal and research use. For commercial use, contact eliya@vocos.io.
|
checkpoint_epoch_5.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2293d041e54c475362d254bf48724c975417daf68cadb9a4a01220338bcbf356
|
| 3 |
+
size 3808741218
|
inference.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Speech deepfake detection.
|
| 2 |
+
Run: python inference.py <audio.wav> [checkpoint.pt]
|
| 3 |
+
fake_probability = 1 - sigmoid(logit) (>= 0.5 -> FAKE); model trained with label 1=real, 0=fake.
|
| 4 |
+
Audio is auto-converted to mono / 16 kHz and trimmed/padded to 5 s.
|
| 5 |
+
"""
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import torch
|
| 9 |
+
import torchaudio
|
| 10 |
+
from model import DeepfakeDetector
|
| 11 |
+
|
| 12 |
+
CHECKPOINT = "3rd_ce_full_0304_resume_ep5.pt"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def load_audio(path, sr=16000, seconds=5.0):
|
| 16 |
+
wav, orig = torchaudio.load(path)
|
| 17 |
+
if wav.shape[0] > 1:
|
| 18 |
+
wav = wav.mean(0, keepdim=True)
|
| 19 |
+
wav = wav.squeeze(0)
|
| 20 |
+
if orig != sr:
|
| 21 |
+
wav = torchaudio.functional.resample(wav, orig, sr)
|
| 22 |
+
wav = wav / (wav.abs().max() + 1e-8)
|
| 23 |
+
n, cur = int(seconds * sr), wav.shape[0]
|
| 24 |
+
if cur < n:
|
| 25 |
+
wav = wav.repeat((n + cur - 1) // cur)[:n]
|
| 26 |
+
elif cur > n:
|
| 27 |
+
s = (cur - n) // 2
|
| 28 |
+
wav = wav[s:s + n]
|
| 29 |
+
return wav
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def load_state_dict(pt_path):
|
| 33 |
+
"""Prefers a sibling .safetensors file (no code execution risk) over the pickled .pt."""
|
| 34 |
+
st_path = pt_path.rsplit(".", 1)[0] + ".safetensors"
|
| 35 |
+
if os.path.exists(st_path):
|
| 36 |
+
from safetensors.torch import load_file
|
| 37 |
+
return load_file(st_path)
|
| 38 |
+
ck = torch.load(pt_path, map_location="cpu", weights_only=False)
|
| 39 |
+
return ck["model_state_dict"] if isinstance(ck, dict) and "model_state_dict" in ck else ck
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@torch.no_grad()
|
| 43 |
+
def main():
|
| 44 |
+
if len(sys.argv) < 2:
|
| 45 |
+
sys.exit("Usage: python inference.py <audio.wav> [checkpoint.pt]")
|
| 46 |
+
audio = sys.argv[1]
|
| 47 |
+
ckpt = sys.argv[2] if len(sys.argv) > 2 else CHECKPOINT
|
| 48 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 49 |
+
|
| 50 |
+
model = DeepfakeDetector().to(device).eval()
|
| 51 |
+
model.load_state_dict(load_state_dict(ckpt), strict=False)
|
| 52 |
+
|
| 53 |
+
bonafide = torch.sigmoid(model(load_audio(audio).unsqueeze(0).to(device)).float()).item()
|
| 54 |
+
fake = 1.0 - bonafide
|
| 55 |
+
print(f"fake_probability: {fake:.4f}")
|
| 56 |
+
print(f"bonafide_score: {bonafide:.4f}")
|
| 57 |
+
print(f"verdict: {'FAKE' if fake >= 0.5 else 'REAL'}")
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
main()
|
model.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""WavLM-large + AASIST speech deepfake detector (inference only)."""
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from transformers import WavLMModel
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class _GraphAttentionLayer(nn.Module):
|
| 9 |
+
def __init__(self, in_dim, out_dim, temperature):
|
| 10 |
+
super().__init__()
|
| 11 |
+
self.att_proj = nn.Linear(in_dim, out_dim)
|
| 12 |
+
self.att_weight = nn.Parameter(torch.empty(out_dim, 1))
|
| 13 |
+
self.proj_with_att = nn.Linear(in_dim, out_dim)
|
| 14 |
+
self.proj_without_att = nn.Linear(in_dim, out_dim)
|
| 15 |
+
self.bn = nn.BatchNorm1d(out_dim)
|
| 16 |
+
self.temp = temperature
|
| 17 |
+
|
| 18 |
+
def forward(self, x):
|
| 19 |
+
N = x.size(1)
|
| 20 |
+
x_i = x.unsqueeze(2).expand(-1, -1, N, -1)
|
| 21 |
+
att = torch.matmul(torch.tanh(self.att_proj(x_i * x_i.transpose(1, 2))), self.att_weight) / self.temp
|
| 22 |
+
att = F.softmax(att, dim=-2)
|
| 23 |
+
out = self.proj_with_att(torch.matmul(att.squeeze(-1), x)) + self.proj_without_att(x)
|
| 24 |
+
s = out.shape
|
| 25 |
+
return F.selu(self.bn(out.reshape(-1, s[-1])).reshape(s))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class _HtrgGraphAttentionLayer(nn.Module):
|
| 29 |
+
def __init__(self, in_dim, out_dim, temperature):
|
| 30 |
+
super().__init__()
|
| 31 |
+
self.proj_type1 = nn.Linear(in_dim, in_dim)
|
| 32 |
+
self.proj_type2 = nn.Linear(in_dim, in_dim)
|
| 33 |
+
self.att_proj = nn.Linear(in_dim, out_dim)
|
| 34 |
+
self.att_projM = nn.Linear(in_dim, out_dim)
|
| 35 |
+
self.att_weight11 = nn.Parameter(torch.empty(out_dim, 1))
|
| 36 |
+
self.att_weight22 = nn.Parameter(torch.empty(out_dim, 1))
|
| 37 |
+
self.att_weight12 = nn.Parameter(torch.empty(out_dim, 1))
|
| 38 |
+
self.att_weightM = nn.Parameter(torch.empty(out_dim, 1))
|
| 39 |
+
self.proj_with_att = nn.Linear(in_dim, out_dim)
|
| 40 |
+
self.proj_without_att = nn.Linear(in_dim, out_dim)
|
| 41 |
+
self.proj_with_attM = nn.Linear(in_dim, out_dim)
|
| 42 |
+
self.proj_without_attM = nn.Linear(in_dim, out_dim)
|
| 43 |
+
self.bn = nn.BatchNorm1d(out_dim)
|
| 44 |
+
self.temp = temperature
|
| 45 |
+
|
| 46 |
+
def forward(self, x1, x2, master):
|
| 47 |
+
n1 = x1.size(1)
|
| 48 |
+
x = torch.cat([self.proj_type1(x1), self.proj_type2(x2)], dim=1)
|
| 49 |
+
N = x.size(1)
|
| 50 |
+
x_i = x.unsqueeze(2).expand(-1, -1, N, -1)
|
| 51 |
+
att_map = torch.tanh(self.att_proj(x_i * x_i.transpose(1, 2)))
|
| 52 |
+
board = torch.zeros_like(att_map[:, :, :, 0]).unsqueeze(-1)
|
| 53 |
+
board[:, :n1, :n1] = torch.matmul(att_map[:, :n1, :n1], self.att_weight11)
|
| 54 |
+
board[:, n1:, n1:] = torch.matmul(att_map[:, n1:, n1:], self.att_weight22)
|
| 55 |
+
board[:, :n1, n1:] = torch.matmul(att_map[:, :n1, n1:], self.att_weight12)
|
| 56 |
+
board[:, n1:, :n1] = torch.matmul(att_map[:, n1:, :n1], self.att_weight12)
|
| 57 |
+
board = F.softmax(board / self.temp, dim=-2)
|
| 58 |
+
out = self.proj_with_att(torch.matmul(board.squeeze(-1), x)) + self.proj_without_att(x)
|
| 59 |
+
s = out.shape
|
| 60 |
+
out = F.selu(self.bn(out.reshape(-1, s[-1])).reshape(s))
|
| 61 |
+
att_m = F.softmax(torch.matmul(torch.tanh(self.att_projM(x * master)), self.att_weightM) / self.temp, dim=-2)
|
| 62 |
+
master = self.proj_with_attM(torch.matmul(att_m.squeeze(-1).unsqueeze(1), x)) + self.proj_without_attM(master)
|
| 63 |
+
return out.narrow(1, 0, n1), out.narrow(1, n1, N - n1), master
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class _GraphPool(nn.Module):
|
| 67 |
+
def __init__(self, k, in_dim):
|
| 68 |
+
super().__init__()
|
| 69 |
+
self.k = k
|
| 70 |
+
self.proj = nn.Linear(in_dim, 1)
|
| 71 |
+
|
| 72 |
+
def forward(self, h):
|
| 73 |
+
scores = torch.sigmoid(self.proj(h))
|
| 74 |
+
n_keep = max(int(h.size(1) * self.k), 1)
|
| 75 |
+
idx = torch.topk(scores, n_keep, dim=1)[1].expand(-1, -1, h.size(-1))
|
| 76 |
+
return torch.gather(h * scores, 1, idx)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class AASISTPool(nn.Module):
|
| 80 |
+
def __init__(self, feat_dim, num_layers, gat_dims=(128, 64),
|
| 81 |
+
pool_ratios=(0.5, 0.7, 0.5, 0.5), temps=(2.0, 2.0, 100.0, 100.0)):
|
| 82 |
+
super().__init__()
|
| 83 |
+
g = list(gat_dims)
|
| 84 |
+
self.proj = nn.Linear(feat_dim, g[0])
|
| 85 |
+
self.pos_S = nn.Parameter(torch.randn(1, num_layers, g[0]))
|
| 86 |
+
self.master1 = nn.Parameter(torch.randn(1, 1, g[0]))
|
| 87 |
+
self.master2 = nn.Parameter(torch.randn(1, 1, g[0]))
|
| 88 |
+
self.GAT_S = _GraphAttentionLayer(g[0], g[0], temps[0])
|
| 89 |
+
self.GAT_T = _GraphAttentionLayer(g[0], g[0], temps[1])
|
| 90 |
+
self.HtrgGAT_11 = _HtrgGraphAttentionLayer(g[0], g[1], temps[2])
|
| 91 |
+
self.HtrgGAT_12 = _HtrgGraphAttentionLayer(g[1], g[1], temps[3])
|
| 92 |
+
self.HtrgGAT_21 = _HtrgGraphAttentionLayer(g[0], g[1], temps[2])
|
| 93 |
+
self.HtrgGAT_22 = _HtrgGraphAttentionLayer(g[1], g[1], temps[3])
|
| 94 |
+
self.pool_S = _GraphPool(pool_ratios[0], g[0])
|
| 95 |
+
self.pool_T = _GraphPool(pool_ratios[1], g[0])
|
| 96 |
+
self.pool_hS1 = _GraphPool(pool_ratios[2], g[1])
|
| 97 |
+
self.pool_hT1 = _GraphPool(pool_ratios[2], g[1])
|
| 98 |
+
self.pool_hS2 = _GraphPool(pool_ratios[2], g[1])
|
| 99 |
+
self.pool_hT2 = _GraphPool(pool_ratios[2], g[1])
|
| 100 |
+
self.output_dim = 5 * g[1]
|
| 101 |
+
|
| 102 |
+
def forward(self, hidden_states):
|
| 103 |
+
projected = self.proj(torch.stack(hidden_states, dim=1))
|
| 104 |
+
e_S = torch.max(torch.abs(projected), dim=2)[0] + self.pos_S
|
| 105 |
+
e_T = torch.max(torch.abs(projected), dim=1)[0]
|
| 106 |
+
out_S = self.pool_S(self.GAT_S(e_S))
|
| 107 |
+
out_T = self.pool_T(self.GAT_T(e_T))
|
| 108 |
+
out_T1, out_S1, m1 = self.HtrgGAT_11(out_T, out_S, self.master1)
|
| 109 |
+
out_S1, out_T1 = self.pool_hS1(out_S1), self.pool_hT1(out_T1)
|
| 110 |
+
a_T, a_S, a_m = self.HtrgGAT_12(out_T1, out_S1, m1)
|
| 111 |
+
out_T1, out_S1, m1 = out_T1 + a_T, out_S1 + a_S, m1 + a_m
|
| 112 |
+
out_T2, out_S2, m2 = self.HtrgGAT_21(out_T, out_S, self.master2)
|
| 113 |
+
out_S2, out_T2 = self.pool_hS2(out_S2), self.pool_hT2(out_T2)
|
| 114 |
+
a_T, a_S, a_m = self.HtrgGAT_22(out_T2, out_S2, m2)
|
| 115 |
+
out_T2, out_S2, m2 = out_T2 + a_T, out_S2 + a_S, m2 + a_m
|
| 116 |
+
out_T, out_S, master = torch.max(out_T1, out_T2), torch.max(out_S1, out_S2), torch.max(m1, m2)
|
| 117 |
+
return torch.cat([torch.max(torch.abs(out_T), 1)[0], out_T.mean(1),
|
| 118 |
+
torch.max(torch.abs(out_S), 1)[0], out_S.mean(1), master.squeeze(1)], dim=1)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class MLPBlock(nn.Module):
|
| 122 |
+
def __init__(self, dim):
|
| 123 |
+
super().__init__()
|
| 124 |
+
self.net = nn.Sequential(nn.Linear(dim, dim * 2), nn.GELU(), nn.Dropout(0.1),
|
| 125 |
+
nn.Linear(dim * 2, dim), nn.GELU(), nn.Dropout(0.1))
|
| 126 |
+
|
| 127 |
+
def forward(self, x):
|
| 128 |
+
return self.net(x)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class Classifier(nn.Module):
|
| 132 |
+
def __init__(self, input_dim, hidden_dim, num_layers, num_labels=1):
|
| 133 |
+
super().__init__()
|
| 134 |
+
self.input_proj = nn.Linear(input_dim, hidden_dim) if input_dim != hidden_dim else nn.Identity()
|
| 135 |
+
self.mlp_blocks = nn.ModuleList([MLPBlock(hidden_dim) for _ in range(num_layers)])
|
| 136 |
+
self.layer_norms = nn.ModuleList([nn.LayerNorm(hidden_dim) for _ in range(num_layers)])
|
| 137 |
+
self.output_head = nn.Sequential(nn.Linear(hidden_dim, hidden_dim), nn.GELU(),
|
| 138 |
+
nn.Dropout(0.1), nn.Linear(hidden_dim, num_labels))
|
| 139 |
+
|
| 140 |
+
def forward(self, x):
|
| 141 |
+
x = self.input_proj(x)
|
| 142 |
+
for mlp, ln in zip(self.mlp_blocks, self.layer_norms):
|
| 143 |
+
x = x + mlp(ln(x))
|
| 144 |
+
return self.output_head(x)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
class DeepfakeDetector(nn.Module):
|
| 148 |
+
def __init__(self, hidden_dim=320, classifier_layers=3, aasist_gat_dims=(128, 64)):
|
| 149 |
+
super().__init__()
|
| 150 |
+
self.wavlm = WavLMModel.from_pretrained("microsoft/wavlm-large")
|
| 151 |
+
self.pool = AASISTPool(self.wavlm.config.hidden_size, self.wavlm.config.num_hidden_layers, aasist_gat_dims)
|
| 152 |
+
self.classifier = Classifier(self.pool.output_dim, hidden_dim, classifier_layers)
|
| 153 |
+
|
| 154 |
+
@torch.no_grad()
|
| 155 |
+
def forward(self, waveform): # (B, T) 16 kHz -> logit (B,); sigmoid(logit)=P(real)
|
| 156 |
+
hs = list(self.wavlm(waveform, output_hidden_states=True).hidden_states[1:])
|
| 157 |
+
return self.classifier(self.pool(hs)).squeeze(-1)
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchaudio
|
| 3 |
+
transformers
|
| 4 |
+
safetensors
|